From e561129b3b5425c2b7641c36c725cb2f81800cc7 Mon Sep 17 00:00:00 2001 From: "randomizedcoder dave.seddon.ca@gmail.com" Date: Sun, 25 Oct 2026 15:54:08 -0700 Subject: [PATCH 01/27] feat: add reviewed TCP route treatment controller --- .../format_schemas/xtcp_flat_record.proto | 13 +- .../initdb.d/sql/xtcp_treatment_migration.sql | 30 + .../initdb.d/sql/xtcp_xtcp_flat_records.sql | 8 + .../sql/xtcp_xtcp_flat_records_kafka.sql | 8 + cmd/xtcp2-controller/main.go | 168 + cmd/xtcp2-controllerctl/main.go | 88 + cmd/xtcp2-treatmentctl/main.go | 78 + cmd/xtcp2/testdata/defineflags-golden.txt | 1 + cmd/xtcp2/treatments.go | 38 + cmd/xtcp2/treatments_test.go | 49 + cmd/xtcp2/xtcp2.go | 32 +- cmd/xtcp2/xtcp2_flags_helpers_test.go | 3 +- docs/README.md | 8 + docs/benchmarks/tcp-controller/after.txt | 90 + docs/benchmarks/tcp-controller/before.txt | 72 + .../tcp-controller/postgres-indexes.txt | 27 + docs/examples/controller-agent.json | 15 + docs/examples/controller-discovery.json | 23 + .../examples/xtcp2-treatment-watchdog.service | 18 + docs/integration-testing.md | 104 + docs/socket-analysis.md | 4 + docs/tcp-controller-testing.md | 121 + docs/tcp-controller.md | 460 + flake.nix | 9 + gen/go/xtcp_controller/controller.pb.go | 2712 ++++++ gen/go/xtcp_controller/controller_grpc.pb.go | 463 + .../xtcp_controller/controller_vtproto.pb.go | 7963 +++++++++++++++++ .../xtcp_flat_record/xtcp_flat_record.pb.go | 85 +- .../xtcp_flat_record_vtproto.pb.go | 304 +- go.mod | 1 + go.sum | 2 + internal/goip/treatment_contracts_test.go | 113 + internal/goip/treatment_linux.go | 570 ++ internal/goip/treatment_linux_test.go | 79 + internal/goip/treatment_live_test.go | 108 + nix/README.md | 8 +- nix/binaries.nix | 3 + nix/default.nix | 60 +- nix/modules/xtcp2-service.nix | 48 +- nix/tests/default.nix | 1 + nix/tests/tcp-controller/default.nix | 394 + nix/tests/tcp-controller/quality.nix | 57 + nix/versions.nix | 2 +- pkg/treatment/agent/agent.go | 382 + pkg/treatment/agent/agent_test.go | 41 + pkg/treatment/agent/backend_linux.go | 22 + pkg/treatment/agent/benchmark_test.go | 46 + pkg/treatment/agent/contracts_test.go | 170 + pkg/treatment/agent/lifecycle_test.go | 119 + pkg/treatment/agent/watch_linux.go | 50 + pkg/treatment/benchmark_test.go | 64 + pkg/treatment/controller/analysis.sql | 37 + pkg/treatment/controller/analyze.go | 87 + pkg/treatment/controller/auth.go | 97 + pkg/treatment/controller/auth_test.go | 147 + pkg/treatment/controller/clickhouse.go | 148 + pkg/treatment/controller/clickhouse_test.go | 74 + pkg/treatment/controller/contracts_test.go | 178 + pkg/treatment/controller/discovery.go | 199 + .../controller/discovery_contracts_test.go | 98 + .../controller/discovery_store_test.go | 55 + pkg/treatment/controller/discovery_test.go | 133 + pkg/treatment/controller/maintenance.go | 209 + pkg/treatment/controller/notifications.go | 77 + .../controller/notifications_test.go | 106 + .../controller/postgres_indexes_test.go | 210 + .../controller/rpc_contracts_test.go | 170 + pkg/treatment/controller/schema.sql | 45 + pkg/treatment/controller/server.go | 742 ++ pkg/treatment/controller/server_test.go | 387 + pkg/treatment/controller/status.go | 78 + pkg/treatment/controller/store.go | 175 + pkg/treatment/experiment.go | 270 + pkg/treatment/experiment_boundaries_test.go | 218 + pkg/treatment/experiment_test.go | 135 + pkg/treatment/journal.go | 137 + pkg/treatment/journal_test.go | 125 + pkg/treatment/reconcile.go | 282 + pkg/treatment/reconcile_contracts_test.go | 148 + pkg/treatment/reconcile_test.go | 266 + pkg/treatment/validate.go | 187 + pkg/treatment/validate_test.go | 319 + pkg/xtcp/deserialize.go | 3 + pkg/xtcp/destinations_s3parquet.go | 12 +- ...estinations_s3parquet_rowfromproto_test.go | 4 + pkg/xtcp/destinations_s3parquet_schema.go | 12 +- pkg/xtcp/treatment.go | 9 + pkg/xtcp/xtcp.go | 5 +- pkg/xtcpnl/xtcp_writer_test.go | 10 +- pkg/xtcpnl/xtcpnl_inet_diag_conginfo.go | 11 + pkg/xtcpnl/xtcpnl_inet_diag_conginfo_test.go | 29 + proto/xtcp/controller/v1/controller.proto | 240 + .../v1/xtcp_flat_record.proto | 13 +- tools/tcp-controller-integration/main.go | 452 + tools/tcp-controller-integration/nearby.go | 43 + tools/tcp-controller-integration/soak.go | 324 + tools/tcp-controller-integration/soak_test.go | 76 + 97 files changed, 22072 insertions(+), 44 deletions(-) create mode 100644 build/containers/clickhouse/initdb.d/sql/xtcp_treatment_migration.sql create mode 100644 cmd/xtcp2-controller/main.go create mode 100644 cmd/xtcp2-controllerctl/main.go create mode 100644 cmd/xtcp2-treatmentctl/main.go create mode 100644 cmd/xtcp2/treatments.go create mode 100644 cmd/xtcp2/treatments_test.go create mode 100644 docs/benchmarks/tcp-controller/after.txt create mode 100644 docs/benchmarks/tcp-controller/before.txt create mode 100644 docs/benchmarks/tcp-controller/postgres-indexes.txt create mode 100644 docs/examples/controller-agent.json create mode 100644 docs/examples/controller-discovery.json create mode 100644 docs/examples/xtcp2-treatment-watchdog.service create mode 100644 docs/tcp-controller-testing.md create mode 100644 docs/tcp-controller.md create mode 100644 gen/go/xtcp_controller/controller.pb.go create mode 100644 gen/go/xtcp_controller/controller_grpc.pb.go create mode 100644 gen/go/xtcp_controller/controller_vtproto.pb.go create mode 100644 internal/goip/treatment_contracts_test.go create mode 100644 internal/goip/treatment_linux.go create mode 100644 internal/goip/treatment_linux_test.go create mode 100644 internal/goip/treatment_live_test.go create mode 100644 nix/tests/tcp-controller/default.nix create mode 100644 nix/tests/tcp-controller/quality.nix create mode 100644 pkg/treatment/agent/agent.go create mode 100644 pkg/treatment/agent/agent_test.go create mode 100644 pkg/treatment/agent/backend_linux.go create mode 100644 pkg/treatment/agent/benchmark_test.go create mode 100644 pkg/treatment/agent/contracts_test.go create mode 100644 pkg/treatment/agent/lifecycle_test.go create mode 100644 pkg/treatment/agent/watch_linux.go create mode 100644 pkg/treatment/benchmark_test.go create mode 100644 pkg/treatment/controller/analysis.sql create mode 100644 pkg/treatment/controller/analyze.go create mode 100644 pkg/treatment/controller/auth.go create mode 100644 pkg/treatment/controller/auth_test.go create mode 100644 pkg/treatment/controller/clickhouse.go create mode 100644 pkg/treatment/controller/clickhouse_test.go create mode 100644 pkg/treatment/controller/contracts_test.go create mode 100644 pkg/treatment/controller/discovery.go create mode 100644 pkg/treatment/controller/discovery_contracts_test.go create mode 100644 pkg/treatment/controller/discovery_store_test.go create mode 100644 pkg/treatment/controller/discovery_test.go create mode 100644 pkg/treatment/controller/maintenance.go create mode 100644 pkg/treatment/controller/notifications.go create mode 100644 pkg/treatment/controller/notifications_test.go create mode 100644 pkg/treatment/controller/postgres_indexes_test.go create mode 100644 pkg/treatment/controller/rpc_contracts_test.go create mode 100644 pkg/treatment/controller/schema.sql create mode 100644 pkg/treatment/controller/server.go create mode 100644 pkg/treatment/controller/server_test.go create mode 100644 pkg/treatment/controller/status.go create mode 100644 pkg/treatment/controller/store.go create mode 100644 pkg/treatment/experiment.go create mode 100644 pkg/treatment/experiment_boundaries_test.go create mode 100644 pkg/treatment/experiment_test.go create mode 100644 pkg/treatment/journal.go create mode 100644 pkg/treatment/journal_test.go create mode 100644 pkg/treatment/reconcile.go create mode 100644 pkg/treatment/reconcile_contracts_test.go create mode 100644 pkg/treatment/reconcile_test.go create mode 100644 pkg/treatment/validate.go create mode 100644 pkg/treatment/validate_test.go create mode 100644 pkg/xtcp/treatment.go create mode 100644 proto/xtcp/controller/v1/controller.proto create mode 100644 tools/tcp-controller-integration/main.go create mode 100644 tools/tcp-controller-integration/nearby.go create mode 100644 tools/tcp-controller-integration/soak.go create mode 100644 tools/tcp-controller-integration/soak_test.go diff --git a/build/containers/clickhouse/format_schemas/xtcp_flat_record.proto b/build/containers/clickhouse/format_schemas/xtcp_flat_record.proto index 657a035bb..794de6e47 100644 --- a/build/containers/clickhouse/format_schemas/xtcp_flat_record.proto +++ b/build/containers/clickhouse/format_schemas/xtcp_flat_record.proto @@ -94,7 +94,7 @@ message XtcpFlatRecord { "class_id", "sock_opt", "c_group"; // ==== metadata (1-299) ===================================================== - // Free: 3-9, 11-19, 22-29, 33-39, 44-49, 52-59, 63-99, 108-119, 125-199, + // Free: 3-9, 11-19, 33-39, 44-49, 52-59, 63-99, 108-119, 125-199, // 208-219, 225-299. Uplink slots are fixed at two (100s, 200s); a third slot // would take 250-274, NOT 300 (that is the enrichment block). @@ -123,6 +123,17 @@ message XtcpFlatRecord { // -location flag or LOCATION env. Constant for every record from one // daemon instance. string location = 21; + // Controller provenance. Empty/zero means observation-only legacy producer. + string agent_id = 22; + string host_boot_id = 23; + string collection_id = 24; + uint64 treatment_state_revision = 25; + string treatment_assignment_id = 26; + uint64 treatment_generation = 27; + int64 treatment_applied_timestamp_ns = 28; + // True only when the observed socket algorithm matches a verified route + // assignment in this namespace. This does not prove initial-window exposure. + bool treatment_algorithm_exposed = 29; // ---- metadata: network namespace identity (30s) -------------------------- // network namespace — best-effort human name (bind-mount name, else a diff --git a/build/containers/clickhouse/initdb.d/sql/xtcp_treatment_migration.sql b/build/containers/clickhouse/initdb.d/sql/xtcp_treatment_migration.sql new file mode 100644 index 000000000..c789114d7 --- /dev/null +++ b/build/containers/clickhouse/initdb.d/sql/xtcp_treatment_migration.sql @@ -0,0 +1,30 @@ +-- Apply to an existing deployment before enabling controller agents. +-- Additive migration: never drop telemetry tables or reset Kafka offsets. +ALTER TABLE xtcp.xtcp_flat_records_v2 ADD COLUMN IF NOT EXISTS agent_id String; +ALTER TABLE xtcp.xtcp_flat_records_v2 ADD COLUMN IF NOT EXISTS host_boot_id String; +ALTER TABLE xtcp.xtcp_flat_records_v2 ADD COLUMN IF NOT EXISTS collection_id String; +ALTER TABLE xtcp.xtcp_flat_records_v2 ADD COLUMN IF NOT EXISTS treatment_state_revision UInt64; +ALTER TABLE xtcp.xtcp_flat_records_v2 ADD COLUMN IF NOT EXISTS treatment_assignment_id String; +ALTER TABLE xtcp.xtcp_flat_records_v2 ADD COLUMN IF NOT EXISTS treatment_generation UInt64; +ALTER TABLE xtcp.xtcp_flat_records_v2 ADD COLUMN IF NOT EXISTS treatment_applied_timestamp_ns Int64; +ALTER TABLE xtcp.xtcp_flat_records_v2 ADD COLUMN IF NOT EXISTS treatment_algorithm_exposed Bool; +ALTER TABLE xtcp.xtcp_flat_records ADD COLUMN IF NOT EXISTS agent_id String; +ALTER TABLE xtcp.xtcp_flat_records ADD COLUMN IF NOT EXISTS host_boot_id String; +ALTER TABLE xtcp.xtcp_flat_records ADD COLUMN IF NOT EXISTS collection_id String; +ALTER TABLE xtcp.xtcp_flat_records ADD COLUMN IF NOT EXISTS treatment_state_revision UInt64; +ALTER TABLE xtcp.xtcp_flat_records ADD COLUMN IF NOT EXISTS treatment_assignment_id String; +ALTER TABLE xtcp.xtcp_flat_records ADD COLUMN IF NOT EXISTS treatment_generation UInt64; +ALTER TABLE xtcp.xtcp_flat_records ADD COLUMN IF NOT EXISTS treatment_applied_timestamp_ns Int64; +ALTER TABLE xtcp.xtcp_flat_records ADD COLUMN IF NOT EXISTS treatment_algorithm_exposed Bool; +ALTER TABLE xtcp.xtcp_flat_records_kafka ADD COLUMN IF NOT EXISTS agent_id String; +ALTER TABLE xtcp.xtcp_flat_records_kafka ADD COLUMN IF NOT EXISTS host_boot_id String; +ALTER TABLE xtcp.xtcp_flat_records_kafka ADD COLUMN IF NOT EXISTS collection_id String; +ALTER TABLE xtcp.xtcp_flat_records_kafka ADD COLUMN IF NOT EXISTS treatment_state_revision UInt64; +ALTER TABLE xtcp.xtcp_flat_records_kafka ADD COLUMN IF NOT EXISTS treatment_assignment_id String; +ALTER TABLE xtcp.xtcp_flat_records_kafka ADD COLUMN IF NOT EXISTS treatment_generation UInt64; +ALTER TABLE xtcp.xtcp_flat_records_kafka ADD COLUMN IF NOT EXISTS treatment_applied_timestamp_ns Int64; +ALTER TABLE xtcp.xtcp_flat_records_kafka ADD COLUMN IF NOT EXISTS treatment_algorithm_exposed Bool; +ALTER TABLE xtcp.xtcp_flat_records_v2_mv MODIFY QUERY +SELECT fromUnixTimestamp64Nano(timestamp_ns) AS timestamp_ns, * EXCEPT (timestamp_ns) +FROM xtcp.xtcp_flat_records_kafka +WHERE length(_error) = 0 AND schema_version = 2; diff --git a/build/containers/clickhouse/initdb.d/sql/xtcp_xtcp_flat_records.sql b/build/containers/clickhouse/initdb.d/sql/xtcp_xtcp_flat_records.sql index 2d1ae1108..4454e1a77 100644 --- a/build/containers/clickhouse/initdb.d/sql/xtcp_xtcp_flat_records.sql +++ b/build/containers/clickhouse/initdb.d/sql/xtcp_xtcp_flat_records.sql @@ -342,6 +342,14 @@ CREATE TABLE IF NOT EXISTS xtcp.xtcp_flat_records_v2 -- https://clickhouse.com/docs/en/sql-reference/data-types/lowcardinality hostname LowCardinality(String), location LowCardinality(String), + agent_id String, + host_boot_id String, + collection_id String, + treatment_state_revision UInt64, + treatment_assignment_id String, + treatment_generation UInt64, + treatment_applied_timestamp_ns Int64, + treatment_algorithm_exposed Bool, -- ---- metadata: network namespace identity (30s) ------------------------ netns String CODEC(ZSTD), diff --git a/build/containers/clickhouse/initdb.d/sql/xtcp_xtcp_flat_records_kafka.sql b/build/containers/clickhouse/initdb.d/sql/xtcp_xtcp_flat_records_kafka.sql index 4aad06239..78562a137 100644 --- a/build/containers/clickhouse/initdb.d/sql/xtcp_xtcp_flat_records_kafka.sql +++ b/build/containers/clickhouse/initdb.d/sql/xtcp_xtcp_flat_records_kafka.sql @@ -49,6 +49,14 @@ CREATE TABLE IF NOT EXISTS xtcp.xtcp_flat_records_kafka -- https://clickhouse.com/docs/en/sql-reference/data-types/lowcardinality hostname LowCardinality(String), location LowCardinality(String), + agent_id String, + host_boot_id String, + collection_id String, + treatment_state_revision UInt64, + treatment_assignment_id String, + treatment_generation UInt64, + treatment_applied_timestamp_ns Int64, + treatment_algorithm_exposed Bool, -- ---- metadata: network namespace identity (30s) ------------------------ netns String CODEC(ZSTD), diff --git a/cmd/xtcp2-controller/main.go b/cmd/xtcp2-controller/main.go new file mode 100644 index 000000000..d7967bc72 --- /dev/null +++ b/cmd/xtcp2-controller/main.go @@ -0,0 +1,168 @@ +package main + +import ( + "context" + "crypto/tls" + "encoding/json" + "flag" + "fmt" + "log" + "net" + "net/http" + "os" + "os/signal" + "strings" + "sync" + "syscall" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" +) + +func main() { + if e := run(); e != nil { + log.Print(e) + os.Exit(1) + } +} +func run() error { + listen := flag.String("listen", ":9443", "mTLS gRPC listener") + statusListen := flag.String("statusListen", "127.0.0.1:9444", "mTLS status/metrics HTTP listener") + cert := flag.String("cert", "", "server certificate") + key := flag.String("key", "", "server private key") + ca := flag.String("ca", "", "client CA") + dsnFile := flag.String("databaseDSNFile", "", "file containing PostgreSQL DSN (or XTCP_CONTROLLER_DATABASE_DSN)") + migrate := flag.Bool("migrate", false, "apply additive PostgreSQL schema before serving") + clickhouse := flag.String("clickhouse", "", "ClickHouse HTTP(S) endpoint; absent disables analysis and lease renewal") + table := flag.String("clickhouseTable", "xtcp.xtcp_flat_records_v2", "source telemetry table") + templateFile := flag.String("discoveryTemplates", "", "optional JSON array of candidate discovery templates") + discoveryInterval := flag.Duration("discoveryInterval", 30*time.Minute, "candidate discovery interval (at least one minute; checked on maintenance ticks)") + discoveryWindow := flag.Duration("discoveryWindow", time.Hour, "candidate discovery lookback (one minute to seven days)") + flag.Parse() + if *discoveryInterval < time.Minute || *discoveryInterval > 24*time.Hour || *discoveryWindow < time.Minute || *discoveryWindow > 7*24*time.Hour { + return fmt.Errorf("discovery interval must be 1m..24h and window 1m..168h") + } + ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT) + defer cancel() + dsn := os.Getenv("XTCP_CONTROLLER_DATABASE_DSN") + if *dsnFile != "" { + b, e := os.ReadFile(*dsnFile) + if e != nil { + return e + } + dsn = strings.TrimSpace(string(b)) + } + if dsn == "" { + return fmt.Errorf("PostgreSQL DSN required") + } + tlsConfig, e := controller.TLSConfig(*cert, *key, *ca, "", true) + if e != nil { + return e + } + store, e := controller.Open(ctx, dsn) + if e != nil { + return e + } + defer store.DB.Close() + if *migrate { + if e = store.Migrate(ctx); e != nil { + return e + } + } + s := &controller.Server{Store: store, Hub: controller.NewNotificationHub(), DiscoveryWindow: *discoveryWindow} + var analysis *controller.ClickHouse + var source controller.EvidenceSource + if *clickhouse != "" { + analysis = &controller.ClickHouse{URL: *clickhouse, Table: *table, Username: os.Getenv("XTCP_CLICKHOUSE_USER"), Password: os.Getenv("XTCP_CLICKHOUSE_PASSWORD")} + source = analysis + } + var templates []controller.DiscoveryTemplate + s.EvidenceSource = source + if *templateFile != "" { + b, e := os.ReadFile(*templateFile) + if e != nil { + return e + } + if e = json.Unmarshal(b, &templates); e != nil { + return e + } + if analysis == nil { + return fmt.Errorf("discovery requires ClickHouse") + } + } + listener, e := net.Listen("tcp", *listen) + if e != nil { + return e + } + defer listener.Close() + grpcServer := grpc.NewServer(grpc.Creds(credentials.NewTLS(tlsConfig)), grpc.MaxRecvMsgSize(treatment.MaxStateBytes), grpc.MaxSendMsgSize(treatment.MaxStateBytes), grpc.MaxConcurrentStreams(8)) + pb.RegisterAgentControlServiceServer(grpcServer, s) + pb.RegisterOperatorServiceServer(grpcServer, s) + var wg sync.WaitGroup + wg.Add(2) + go func() { defer wg.Done(); s.RunNotifications(ctx) }() + go func() { + defer wg.Done() + tick := time.NewTicker(time.Minute) + defer tick.Stop() + nextDiscovery := time.Now() + for { + if e := s.Maintain(ctx, source); e != nil { + log.Printf("controller maintenance: %v", e) + } + if analysis != nil && !time.Now().Before(nextDiscovery) { + if e := s.Discover(ctx, analysis, templates); e != nil { + log.Printf("candidate discovery: %v", e) + } + // Keep the cadence independent of query duration; otherwise a + // one-minute interval can accidentally skip every other tick. + for !nextDiscovery.After(time.Now()) { + nextDiscovery = nextDiscovery.Add(*discoveryInterval) + } + } + select { + case <-ctx.Done(): + return + case <-tick.C: + } + } + }() + statusServer := &http.Server{Addr: *statusListen, Handler: s.StatusHandler(), TLSConfig: tlsConfig.Clone(), ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 15 * time.Second, WriteTimeout: 15 * time.Second, IdleTimeout: time.Minute} + errors := make(chan error, 2) + go func() { errors <- grpcServer.Serve(listener) }() + if *statusListen != "" { + go func() { + l, e := tls.Listen("tcp", *statusListen, tlsConfig.Clone()) + if e != nil { + errors <- e + return + } + errors <- statusServer.Serve(l) + }() + } + log.Printf("xtcp2 controller listening on %s; human review required", *listen) + select { + case <-ctx.Done(): + case e = <-errors: + cancel() + } + shutdown, done := context.WithTimeout(context.Background(), 5*time.Second) + defer done() + _ = statusServer.Shutdown(shutdown) + stopped := make(chan struct{}) + go func() { grpcServer.GracefulStop(); close(stopped) }() + select { + case <-stopped: + case <-shutdown.Done(): + grpcServer.Stop() + } + wg.Wait() + if e == grpc.ErrServerStopped || e == http.ErrServerClosed { + return nil + } + return e +} diff --git a/cmd/xtcp2-controllerctl/main.go b/cmd/xtcp2-controllerctl/main.go new file mode 100644 index 000000000..7be247fc2 --- /dev/null +++ b/cmd/xtcp2-controllerctl/main.go @@ -0,0 +1,88 @@ +package main + +import ( + "context" + "flag" + "fmt" + "log" + "os" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/protobuf/encoding/prototext" + "google.golang.org/protobuf/proto" +) + +func main() { + if e := run(); e != nil { + log.Print(e) + os.Exit(1) + } +} +func run() error { + target := flag.String("controller", "localhost:9443", "controller address") + name := flag.String("serverName", "", "TLS server name") + cert := flag.String("cert", "", "operator certificate") + key := flag.String("key", "", "operator key") + ca := flag.String("ca", "", "server CA") + method := flag.String("method", "inspect", "propose, analyze, review, control, inspect") + file := flag.String("file", "", "protobuf-text request file (empty InspectRequest if omitted)") + flag.Parse() + tls, e := controller.TLSConfig(*cert, *key, *ca, *name, false) + if e != nil { + return e + } + conn, e := grpc.NewClient(*target, grpc.WithTransportCredentials(credentials.NewTLS(tls)), grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(treatment.MaxStateBytes))) + if e != nil { + return e + } + defer conn.Close() + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + client := pb.NewOperatorServiceClient(conn) + var req, response proto.Message + switch *method { + case "propose": + req = new(pb.ProposeRequest) + case "review": + req = new(pb.ReviewRequest) + case "analyze": + req = new(pb.AnalyzeRequest) + case "control": + req = new(pb.ControlRequest) + case "inspect": + req = new(pb.InspectRequest) + default: + return fmt.Errorf("unknown method %q", *method) + } + if *file != "" { + b, e := os.ReadFile(*file) + if e != nil { + return e + } + if e = prototext.Unmarshal(b, req); e != nil { + return e + } + } + switch r := req.(type) { + case *pb.ProposeRequest: + response, e = client.Propose(ctx, r) + case *pb.ReviewRequest: + response, e = client.Review(ctx, r) + case *pb.AnalyzeRequest: + response, e = client.Analyze(ctx, r) + case *pb.ControlRequest: + response, e = client.Control(ctx, r) + case *pb.InspectRequest: + response, e = client.Inspect(ctx, r) + } + if e != nil { + return e + } + fmt.Print(prototext.Format(response)) + return nil +} diff --git a/cmd/xtcp2-treatmentctl/main.go b/cmd/xtcp2-treatmentctl/main.go new file mode 100644 index 000000000..884fb0ef6 --- /dev/null +++ b/cmd/xtcp2-treatmentctl/main.go @@ -0,0 +1,78 @@ +// xtcp2-treatmentctl is the local, filesystem-permission-protected emergency +// interface and independent lease watchdog. It never needs a controller RPC. +package main + +import ( + "context" + "flag" + "fmt" + "log" + "os" + "os/signal" + "syscall" + "time" + + "github.com/randomizedcoder/xtcp2/pkg/treatment/agent" + "google.golang.org/protobuf/encoding/prototext" +) + +func main() { + if err := run(); err != nil { + log.Print(err) + os.Exit(1) + } +} +func run() error { + config := flag.String("config", "/etc/xtcp2/controller-agent.json", "agent configuration") + mode := flag.String("mode", "status", "status, suspend, resume, reconcile, or watchdog") + reason := flag.String("reason", "", "required for suspend/resume") + flag.Parse() + ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer cancel() + c, e := agent.LoadConfig(*config) + if e != nil { + return e + } + a, e := agent.NewLocal(c) + if e != nil { + return e + } + defer a.Close() + switch *mode { + case "status": + b, e := os.ReadFile(c.Directory + "/state.textproto") + if e != nil { + return e + } + _, e = os.Stdout.Write(b) + return e + case "suspend", "resume": + if e = a.Reconciler.SuspendLocal(*mode == "suspend", *reason); e != nil { + return e + } + case "reconcile": + case "watchdog": + t := time.NewTicker(30 * time.Second) + defer t.Stop() + for { + pass, cancelPass := context.WithTimeout(ctx, 25*time.Second) + _, e = a.Reconciler.Run(pass, nil, true) + cancelPass() + if e != nil { + log.Printf("watchdog: %v", e) + } + select { + case <-ctx.Done(): + return nil + case <-t.C: + } + } + default: + return fmt.Errorf("unknown mode %q", *mode) + } + s, e := a.Reconciler.Run(ctx, nil, false) + if s != nil { + fmt.Print(prototext.Format(s)) + } + return e +} diff --git a/cmd/xtcp2/testdata/defineflags-golden.txt b/cmd/xtcp2/testdata/defineflags-golden.txt index 23e8d835e..06af2e153 100644 --- a/cmd/xtcp2/testdata/defineflags-golden.txt +++ b/cmd/xtcp2/testdata/defineflags-golden.txt @@ -117,6 +117,7 @@ s3UploadMaxAttempts 10 s3parquet: max upload attempts (original + retries) befor tag label applied to the protobuf timeout 5s Poll timeout per name space topic xtcp Kafka or NSQ topic +treatmentConfig optional controller agent JSON config; route application requires enable_apply uplinkCount 2 number of uplink slots to populate for LLDP/NIC enrichment (hosts are typically dual-homed = 2; max 2). Falls back to UPLINK_COUNT env. uplinkInterfaces comma-separated explicit uplink interface names (e.g. "eth0,eth1") overriding default-route auto-detection for -enrichLldp/-enrichNic. Falls back to UPLINK_INTERFACES env. v false show version diff --git a/cmd/xtcp2/treatments.go b/cmd/xtcp2/treatments.go new file mode 100644 index 000000000..325e06116 --- /dev/null +++ b/cmd/xtcp2/treatments.go @@ -0,0 +1,38 @@ +package main + +import ( + "context" + "sync" + "sync/atomic" + + "github.com/randomizedcoder/xtcp2/pkg/treatment/agent" +) + +var activeTreatments atomic.Pointer[agent.Agent] + +func reconcileTreatments() { + if a := activeTreatments.Load(); a != nil { + a.Wake() + } +} + +func startTreatments(parent context.Context, path *string) (func(), error) { + if path == nil || *path == "" { + return func() {}, nil + } + c, err := agent.LoadConfig(*path) + if err != nil { + return nil, err + } + ctx, cancel := context.WithCancel(parent) + a, err := agent.New(ctx, c) + if err != nil { + cancel() + return nil, err + } + activeTreatments.Store(a) + var wg sync.WaitGroup + wg.Add(1) + go func() { defer wg.Done(); a.Run(ctx) }() + return func() { cancel(); wg.Wait(); activeTreatments.CompareAndSwap(a, nil); a.Close() }, nil +} diff --git a/cmd/xtcp2/treatments_test.go b/cmd/xtcp2/treatments_test.go new file mode 100644 index 000000000..08b25560c --- /dev/null +++ b/cmd/xtcp2/treatments_test.go @@ -0,0 +1,49 @@ +package main + +import ( + "context" + "os" + "syscall" + "testing" + "time" +) + +func TestSIGHUPReconcilesWithoutRebaselineOrShutdown(t *testing.T) { + sigs := make(chan os.Signal, 3) + complete := make(chan struct{}, 1) + complete <- struct{}{} + reconciled := make(chan struct{}, 1) + rebaselined := make(chan struct{}, 1) + done := make(chan struct{}) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go func() { + awaitSignalAndShutdown(sigs, cancel, complete, time.Second, false, func() { rebaselined <- struct{}{} }, func() { reconciled <- struct{}{} }) + close(done) + }() + sigs <- syscall.SIGHUP + select { + case <-reconciled: + case <-time.After(time.Second): + t.Fatal("SIGHUP did not reconcile") + } + select { + case <-ctx.Done(): + t.Fatal("SIGHUP shut down daemon") + case <-rebaselined: + t.Fatal("SIGHUP changed link baseline") + default: + } + sigs <- syscall.SIGUSR1 + select { + case <-rebaselined: + case <-time.After(time.Second): + t.Fatal("SIGUSR1 regression") + } + sigs <- syscall.SIGTERM + select { + case <-done: + case <-time.After(time.Second): + t.Fatal("SIGTERM did not stop") + } +} diff --git a/cmd/xtcp2/xtcp2.go b/cmd/xtcp2/xtcp2.go index 1cdcd7852..2162b7edb 100644 --- a/cmd/xtcp2/xtcp2.go +++ b/cmd/xtcp2/xtcp2.go @@ -252,6 +252,7 @@ var ( // short and lets the per-section helpers (printFlags, buildConfig, // startProfile) take a single argument instead of 30 positional ones. type mainFlags struct { + treatmentConfig *string nltimeout *uint64 pollFrequency *time.Duration pollTimeout *time.Duration @@ -439,6 +440,7 @@ func defaultDest() string { // of all 92 are pinned by cmd/xtcp2/testdata/defineflags-golden.txt. func defineFlags() *mainFlags { f := &mainFlags{} + f.treatmentConfig = flag.String("treatmentConfig", "", "optional controller agent JSON config; route application requires enable_apply") f.nltimeout = flag.Uint64("nltimeout", nltimeoutCst, "Netlink socket timeout in milliseconds. Zero(0) for no timeout") f.pollFrequency = flag.Duration("frequency", pollFrequencyCst, "Poll frequency") f.pollTimeout = flag.Duration("timeout", pollTimeoutCst, "Poll timeout per name space") @@ -1025,7 +1027,7 @@ func runMain(ctx context.Context) int { defer cancel() complete := make(chan struct{}, signalChannelSizeCst) - initSignalHandler(cancel, complete, linkMonitorHTTP.rebaseline) + initSignalHandler(cancel, complete, linkMonitorHTTP.rebaseline, reconcileTreatments) f := defineFlags() flag.Parse() @@ -1129,6 +1131,13 @@ func runMain(ctx context.Context) int { log.Println("config validation succeeded") } + stopTreatments, err := startTreatments(runCtx, f.treatmentConfig) + if err != nil { + // Controller availability/configuration must not stop socket telemetry. + log.Printf("treatment agent unavailable: %v", err) + } else { + defer stopTreatments() + } daemonRunner(runCtx, cancel, c) // The signal handler exits once complete is sent, so drain first. cancel() @@ -1147,6 +1156,9 @@ func runMain(ctx context.Context) int { // and run RunWithPoller until ctx cancels the WG. func runDaemonDefault(ctx context.Context, cancel context.CancelFunc, c *xtcp_config.XtcpConfig) { x := xtcp.NewXTCP(ctx, cancel, c) + if a := activeTreatments.Load(); a != nil { + x.SetTreatmentStamper(a.Stamp) + } detach, err := linkMonitorHTTP.attach(x) if err != nil { fatalf("link monitor http: %v", err) @@ -1255,15 +1267,15 @@ func envWithoutKey(env []string, key string) []string { // goroutine, so a SIGTERM sent while flags are still being parsed takes the // graceful path instead of Go's default, which exits at once. (Go drops a // SIGUSR1 that arrives before any Notify; it never kills the process.) -func initSignalHandler(cancel context.CancelFunc, complete <-chan struct{}, rebaseline func()) { +func initSignalHandler(cancel context.CancelFunc, complete <-chan struct{}, rebaseline func(), reconcile ...func()) { c := notifySignals() - go awaitSignalAndShutdown(c, cancel, complete, cancelSleepTimeCst, true, rebaseline) + go awaitSignalAndShutdown(c, cancel, complete, cancelSleepTimeCst, true, rebaseline, reconcile...) } // notifySignals routes SIGINT, SIGTERM and SIGUSR1 to the returned channel. func notifySignals() chan os.Signal { c := make(chan os.Signal, signalChannelSizeCst) - signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGUSR1) + signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGUSR1, syscall.SIGHUP) return c } @@ -1280,12 +1292,20 @@ func awaitSignalAndShutdown( timeout time.Duration, doExit bool, rebaseline func(), + reconcile ...func(), ) { // SIGUSR1 never shuts down; a nil rebaseline ignores it. - for sig := <-sigs; sig == syscall.SIGUSR1; sig = <-sigs { - if rebaseline != nil { + for sig := <-sigs; sig == syscall.SIGUSR1 || sig == syscall.SIGHUP; sig = <-sigs { + if sig == syscall.SIGUSR1 && rebaseline != nil { rebaseline() } + if sig == syscall.SIGHUP { + for _, fn := range reconcile { + if fn != nil { + fn() + } + } + } } log.Printf("Signal caught, closing application") cancel() diff --git a/cmd/xtcp2/xtcp2_flags_helpers_test.go b/cmd/xtcp2/xtcp2_flags_helpers_test.go index af99b2431..50e51ee40 100644 --- a/cmd/xtcp2/xtcp2_flags_helpers_test.go +++ b/cmd/xtcp2/xtcp2_flags_helpers_test.go @@ -89,7 +89,7 @@ const ( // mainFlags field must be a registered flag and every registered flag must // have a field. A flag added without a field, or a field that stops being // registered, is the drift this file exists to notice. - mainFlagsFieldCountCst = 124 + mainFlagsFieldCountCst = 125 // optional treatmentConfig adds the controller agent printFlagsGoldenCst = "testdata/printflags-golden.txt" defineFlagsGoldenCst = "testdata/defineflags-golden.txt" @@ -155,6 +155,7 @@ func populatedMainFlags() *mainFlags { ms := func(v int) *time.Duration { d := time.Duration(v) * time.Millisecond; return &d } return &mainFlags{ + treatmentConfig: s("/etc/xtcp2/test-treatment-config.json"), nltimeout: u64(101), pollFrequency: ms(102), pollTimeout: ms(103), diff --git a/docs/README.md b/docs/README.md index 456782884..90f310645 100644 --- a/docs/README.md +++ b/docs/README.md @@ -30,6 +30,14 @@ This project is a complete rewrite of the original [xtcp](https://github.com/ran Each feature has a dedicated document with its own table of contents and component breakdown. +### [Reviewed TCP route treatments](tcp-controller.md) + +An optional controller uses ClickHouse evidence and PostgreSQL state to propose +destination-specific TCP treatments, run human-reviewed canaries, and withdraw +failed trials. Agents persist protobuf-text journals and reconcile owned routes; +an independent watchdog enforces trial expiry. Route application is disabled by +default. The controller has its own mTLS listener and operator client. + ### [Netlink TCP collection](netlink/collection.md) Reads TCP socket state from the kernel via the `inet_diag` netlink interface. A registry of 13 attribute deserializers (`info`, `cong`, `meminfo`, `skmem`, `bbr`, `dctcp`, `vegas`, `tos`, `tc`, `shut`, `classid`, `cgroup`, `sockopt`) decodes each socket's attributes into a flat record; you choose which to decode with `-deserializers`. diff --git a/docs/benchmarks/tcp-controller/after.txt b/docs/benchmarks/tcp-controller/after.txt new file mode 100644 index 000000000..76591e3ea --- /dev/null +++ b/docs/benchmarks/tcp-controller/after.txt @@ -0,0 +1,90 @@ +goos: linux +goarch: amd64 +pkg: github.com/randomizedcoder/xtcp2/pkg/treatment +cpu: AMD Ryzen Threadripper PRO 3945WX 12-Cores +BenchmarkFleetEvidence/Validate/100-24 15618 22979 ns/op 3500 B/op 3 allocs/op +BenchmarkFleetEvidence/Validate/100-24 15716 22848 ns/op 3500 B/op 3 allocs/op +BenchmarkFleetEvidence/Validate/100-24 15710 23008 ns/op 3500 B/op 3 allocs/op +BenchmarkFleetEvidence/Safety/100-24 10000 32458 ns/op 10499 B/op 9 allocs/op +BenchmarkFleetEvidence/Safety/100-24 10000 32123 ns/op 10499 B/op 9 allocs/op +BenchmarkFleetEvidence/Safety/100-24 8842 34691 ns/op 10499 B/op 9 allocs/op +BenchmarkFleetEvidence/Evaluate/100-24 6694 59860 ns/op 12998 B/op 15 allocs/op +BenchmarkFleetEvidence/Evaluate/100-24 5942 61621 ns/op 12998 B/op 15 allocs/op +BenchmarkFleetEvidence/Evaluate/100-24 6594 58432 ns/op 12998 B/op 15 allocs/op +BenchmarkFleetEvidence/Validate/1000-24 1266 274384 ns/op 54673 B/op 5 allocs/op +BenchmarkFleetEvidence/Validate/1000-24 1401 272011 ns/op 54674 B/op 5 allocs/op +BenchmarkFleetEvidence/Validate/1000-24 1309 286308 ns/op 54676 B/op 5 allocs/op +BenchmarkFleetEvidence/Safety/1000-24 1011 364178 ns/op 164025 B/op 15 allocs/op +BenchmarkFleetEvidence/Safety/1000-24 1039 357506 ns/op 164026 B/op 15 allocs/op +BenchmarkFleetEvidence/Safety/1000-24 936 354571 ns/op 164032 B/op 15 allocs/op +BenchmarkFleetEvidence/Evaluate/1000-24 859 419171 ns/op 188634 B/op 21 allocs/op +BenchmarkFleetEvidence/Evaluate/1000-24 858 415074 ns/op 188635 B/op 21 allocs/op +BenchmarkFleetEvidence/Evaluate/1000-24 885 418120 ns/op 188635 B/op 21 allocs/op +BenchmarkFleetEvidence/Validate/10000-24 132 2717192 ns/op 437242 B/op 33 allocs/op +BenchmarkFleetEvidence/Validate/10000-24 133 2698856 ns/op 437236 B/op 33 allocs/op +BenchmarkFleetEvidence/Validate/10000-24 129 2779841 ns/op 437253 B/op 33 allocs/op +BenchmarkFleetEvidence/Safety/10000-24 91 4062183 ns/op 1311648 B/op 99 allocs/op +BenchmarkFleetEvidence/Safety/10000-24 98 4027768 ns/op 1311664 B/op 99 allocs/op +BenchmarkFleetEvidence/Safety/10000-24 93 4071155 ns/op 1311647 B/op 99 allocs/op +BenchmarkFleetEvidence/Evaluate/10000-24 80 4641976 ns/op 1557547 B/op 106 allocs/op +BenchmarkFleetEvidence/Evaluate/10000-24 86 4598309 ns/op 1557578 B/op 106 allocs/op +BenchmarkFleetEvidence/Evaluate/10000-24 86 4536670 ns/op 1557578 B/op 106 allocs/op +PASS +ok github.com/randomizedcoder/xtcp2/pkg/treatment 9.859s +goos: linux +goarch: amd64 +pkg: github.com/randomizedcoder/xtcp2/pkg/treatment/agent +cpu: AMD Ryzen Threadripper PRO 3945WX 12-Cores +BenchmarkStamp/routes=1/match=true-24 10065704 35.36 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=1/match=true-24 10003551 35.31 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=1/match=true-24 9843661 35.27 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=1/match=false-24 17960965 19.45 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=1/match=false-24 18524896 19.36 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=1/match=false-24 18477922 19.34 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=100/match=true-24 10218624 34.42 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=100/match=true-24 10379076 34.12 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=100/match=true-24 10432940 34.46 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=100/match=false-24 16772404 21.60 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=100/match=false-24 16571926 21.36 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=100/match=false-24 16649026 21.53 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=4096/match=true-24 10058797 36.59 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=4096/match=true-24 9458271 37.78 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=4096/match=true-24 9816648 36.13 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=4096/match=false-24 15207280 23.66 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=4096/match=false-24 14958738 23.85 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=4096/match=false-24 15022704 24.07 ns/op 0 B/op 0 allocs/op +BenchmarkPublish/1-24 1435836 250.2 ns/op 360 B/op 3 allocs/op +BenchmarkPublish/1-24 1423382 252.5 ns/op 360 B/op 3 allocs/op +BenchmarkPublish/1-24 1395063 257.7 ns/op 360 B/op 3 allocs/op +BenchmarkPublish/100-24 26905 13185 ns/op 9392 B/op 12 allocs/op +BenchmarkPublish/100-24 27619 13046 ns/op 9392 B/op 12 allocs/op +BenchmarkPublish/100-24 28101 12880 ns/op 9392 B/op 12 allocs/op +BenchmarkPublish/4096-24 511 687413 ns/op 652373 B/op 49 allocs/op +BenchmarkPublish/4096-24 523 690086 ns/op 652352 B/op 49 allocs/op +BenchmarkPublish/4096-24 523 680441 ns/op 652352 B/op 49 allocs/op +PASS +ok github.com/randomizedcoder/xtcp2/pkg/treatment/agent 9.879s +goos: linux +goarch: amd64 +pkg: github.com/randomizedcoder/xtcp2/pkg/treatment/controller +cpu: AMD Ryzen Threadripper PRO 3945WX 12-Cores +BenchmarkNotificationFleet/100-24 26011 13993 ns/op 0 B/op 0 allocs/op +BenchmarkNotificationFleet/100-24 26156 13745 ns/op 0 B/op 0 allocs/op +BenchmarkNotificationFleet/100-24 25989 13681 ns/op 0 B/op 0 allocs/op +BenchmarkNotificationFleet/1000-24 2479 141174 ns/op 43 B/op 0 allocs/op +BenchmarkNotificationFleet/1000-24 2698 139311 ns/op 40 B/op 0 allocs/op +BenchmarkNotificationFleet/1000-24 2552 138808 ns/op 42 B/op 0 allocs/op +BenchmarkNotificationFleet/10000-24 248 1463026 ns/op 3522 B/op 0 allocs/op +BenchmarkNotificationFleet/10000-24 244 1508009 ns/op 3579 B/op 0 allocs/op +BenchmarkNotificationFleet/10000-24 178 2066908 ns/op 4907 B/op 0 allocs/op +PASS +ok github.com/randomizedcoder/xtcp2/pkg/treatment/controller 3.349s +goos: linux +goarch: amd64 +pkg: github.com/randomizedcoder/xtcp2/internal/goip +cpu: AMD Ryzen Threadripper PRO 3945WX 12-Cores +BenchmarkTreatmentPlanRoute-24 371434 917.5 ns/op 756 B/op 10 allocs/op +BenchmarkTreatmentPlanRoute-24 402067 1501 ns/op 756 B/op 10 allocs/op +BenchmarkTreatmentPlanRoute-24 346634 1116 ns/op 756 B/op 10 allocs/op +PASS +ok github.com/randomizedcoder/xtcp2/internal/goip 1.344s diff --git a/docs/benchmarks/tcp-controller/before.txt b/docs/benchmarks/tcp-controller/before.txt new file mode 100644 index 000000000..8f170ecf1 --- /dev/null +++ b/docs/benchmarks/tcp-controller/before.txt @@ -0,0 +1,72 @@ +goos: linux +goarch: amd64 +pkg: github.com/randomizedcoder/xtcp2/pkg/treatment +cpu: AMD Ryzen Threadripper PRO 3945WX 12-Cores +BenchmarkFleetEvidence/Validate/100-24 12350 28991 ns/op 6703 B/op 9 allocs/op +BenchmarkFleetEvidence/Validate/100-24 12961 28114 ns/op 6703 B/op 9 allocs/op +BenchmarkFleetEvidence/Validate/100-24 12174 29855 ns/op 6704 B/op 9 allocs/op +BenchmarkFleetEvidence/Safety/100-24 8277 47316 ns/op 20110 B/op 27 allocs/op +BenchmarkFleetEvidence/Safety/100-24 7406 47468 ns/op 20111 B/op 27 allocs/op +BenchmarkFleetEvidence/Safety/100-24 7742 46274 ns/op 20110 B/op 27 allocs/op +BenchmarkFleetEvidence/Evaluate/100-24 4461 74733 ns/op 25877 B/op 51 allocs/op +BenchmarkFleetEvidence/Evaluate/100-24 4440 73695 ns/op 25877 B/op 51 allocs/op +BenchmarkFleetEvidence/Evaluate/100-24 4948 80849 ns/op 25879 B/op 51 allocs/op +BenchmarkFleetEvidence/Validate/1000-24 1104 318501 ns/op 108898 B/op 20 allocs/op +BenchmarkFleetEvidence/Validate/1000-24 1156 322431 ns/op 108898 B/op 20 allocs/op +BenchmarkFleetEvidence/Validate/1000-24 1144 324561 ns/op 108896 B/op 20 allocs/op +BenchmarkFleetEvidence/Safety/1000-24 643 562375 ns/op 326712 B/op 60 allocs/op +BenchmarkFleetEvidence/Safety/1000-24 640 558050 ns/op 326706 B/op 60 allocs/op +BenchmarkFleetEvidence/Safety/1000-24 656 552844 ns/op 326704 B/op 60 allocs/op +BenchmarkFleetEvidence/Evaluate/1000-24 544 666721 ns/op 375536 B/op 102 allocs/op +BenchmarkFleetEvidence/Evaluate/1000-24 556 640359 ns/op 375537 B/op 102 allocs/op +BenchmarkFleetEvidence/Evaluate/1000-24 561 672998 ns/op 375549 B/op 102 allocs/op +BenchmarkFleetEvidence/Validate/10000-24 100 3383939 ns/op 874017 B/op 79 allocs/op +BenchmarkFleetEvidence/Validate/10000-24 100 3430797 ns/op 874017 B/op 79 allocs/op +BenchmarkFleetEvidence/Validate/10000-24 100 3399253 ns/op 874004 B/op 79 allocs/op +BenchmarkFleetEvidence/Safety/10000-24 69 5898750 ns/op 2621959 B/op 239 allocs/op +BenchmarkFleetEvidence/Safety/10000-24 67 5839557 ns/op 2621904 B/op 239 allocs/op +BenchmarkFleetEvidence/Safety/10000-24 69 6979109 ns/op 2621891 B/op 239 allocs/op +BenchmarkFleetEvidence/Evaluate/10000-24 57 7482686 ns/op 3391637 B/op 317 allocs/op +BenchmarkFleetEvidence/Evaluate/10000-24 57 7597250 ns/op 3391704 B/op 317 allocs/op +BenchmarkFleetEvidence/Evaluate/10000-24 48 7077716 ns/op 3391590 B/op 317 allocs/op +PASS +ok github.com/randomizedcoder/xtcp2/pkg/treatment 10.133s +goos: linux +goarch: amd64 +pkg: github.com/randomizedcoder/xtcp2/pkg/treatment/agent +cpu: AMD Ryzen Threadripper PRO 3945WX 12-Cores +BenchmarkStamp/routes=1/match=true-24 10195333 34.84 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=1/match=true-24 9735492 33.69 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=1/match=true-24 10685922 33.50 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=1/match=false-24 16964254 18.90 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=1/match=false-24 18726810 19.14 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=1/match=false-24 19162561 23.49 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=100/match=true-24 10103883 35.42 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=100/match=true-24 10498788 33.87 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=100/match=true-24 10056416 35.61 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=100/match=false-24 16187679 21.65 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=100/match=false-24 14169679 26.08 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=100/match=false-24 13624152 22.02 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=4096/match=true-24 10212040 35.09 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=4096/match=true-24 10251448 34.39 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=4096/match=true-24 9137064 39.17 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=4096/match=false-24 15448830 22.74 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=4096/match=false-24 14805097 26.18 ns/op 0 B/op 0 allocs/op +BenchmarkStamp/routes=4096/match=false-24 14845970 24.18 ns/op 0 B/op 0 allocs/op +PASS +ok github.com/randomizedcoder/xtcp2/pkg/treatment/agent 6.472s +goos: linux +goarch: amd64 +pkg: github.com/randomizedcoder/xtcp2/pkg/treatment/controller +cpu: AMD Ryzen Threadripper PRO 3945WX 12-Cores +BenchmarkNotificationFleet/100-24 26456 13532 ns/op 0 B/op 0 allocs/op +BenchmarkNotificationFleet/100-24 26252 13689 ns/op 0 B/op 0 allocs/op +BenchmarkNotificationFleet/100-24 26132 13644 ns/op 0 B/op 0 allocs/op +BenchmarkNotificationFleet/1000-24 2419 148186 ns/op 45 B/op 0 allocs/op +BenchmarkNotificationFleet/1000-24 2572 161498 ns/op 42 B/op 0 allocs/op +BenchmarkNotificationFleet/1000-24 2372 146506 ns/op 45 B/op 0 allocs/op +BenchmarkNotificationFleet/10000-24 226 1923710 ns/op 3864 B/op 0 allocs/op +BenchmarkNotificationFleet/10000-24 243 1516583 ns/op 3594 B/op 0 allocs/op +BenchmarkNotificationFleet/10000-24 190 1779397 ns/op 4597 B/op 0 allocs/op +PASS +ok github.com/randomizedcoder/xtcp2/pkg/treatment/controller 3.460s diff --git a/docs/benchmarks/tcp-controller/postgres-indexes.txt b/docs/benchmarks/tcp-controller/postgres-indexes.txt new file mode 100644 index 000000000..e398be938 --- /dev/null +++ b/docs/benchmarks/tcp-controller/postgres-indexes.txt @@ -0,0 +1,27 @@ +=== RUN TestPostgresExperimentQueryIndexes +=== RUN TestPostgresExperimentQueryIndexes/old-destination + postgres_indexes_test.go:207: 100005 experiments: unindexed 26.153 ms, indexed 0.023 ms (xtcp_control_experiments_destination) +=== RUN TestPostgresExperimentQueryIndexes/absent-destination + postgres_indexes_test.go:207: 100005 experiments: unindexed 24.508 ms, indexed 0.015 ms (xtcp_control_experiments_destination) +=== RUN TestPostgresExperimentQueryIndexes/active-budget + postgres_indexes_test.go:207: 100005 experiments: unindexed 9.218 ms, indexed 0.018 ms (xtcp_control_experiments_destination) +=== RUN TestPostgresExperimentQueryIndexes/idle-budget + postgres_indexes_test.go:207: 100005 experiments: unindexed 27.249 ms, indexed 0.023 ms (xtcp_control_experiments_status_location) +=== RUN TestPostgresExperimentQueryIndexes +=== RUN TestPostgresExperimentQueryIndexes/old-destination + postgres_indexes_test.go:207: 100005 experiments: unindexed 25.340 ms, indexed 0.022 ms (xtcp_control_experiments_destination) +=== RUN TestPostgresExperimentQueryIndexes/absent-destination + postgres_indexes_test.go:207: 100005 experiments: unindexed 23.556 ms, indexed 0.021 ms (xtcp_control_experiments_destination) +=== RUN TestPostgresExperimentQueryIndexes/active-budget + postgres_indexes_test.go:207: 100005 experiments: unindexed 9.146 ms, indexed 0.017 ms (xtcp_control_experiments_destination) +=== RUN TestPostgresExperimentQueryIndexes/idle-budget + postgres_indexes_test.go:207: 100005 experiments: unindexed 26.392 ms, indexed 0.036 ms (xtcp_control_experiments_status_location) +=== RUN TestPostgresExperimentQueryIndexes +=== RUN TestPostgresExperimentQueryIndexes/old-destination + postgres_indexes_test.go:207: 100005 experiments: unindexed 24.946 ms, indexed 0.024 ms (xtcp_control_experiments_destination) +=== RUN TestPostgresExperimentQueryIndexes/absent-destination + postgres_indexes_test.go:207: 100005 experiments: unindexed 23.196 ms, indexed 0.014 ms (xtcp_control_experiments_destination) +=== RUN TestPostgresExperimentQueryIndexes/active-budget + postgres_indexes_test.go:207: 100005 experiments: unindexed 9.920 ms, indexed 0.025 ms (xtcp_control_experiments_destination) +=== RUN TestPostgresExperimentQueryIndexes/idle-budget + postgres_indexes_test.go:207: 100005 experiments: unindexed 26.413 ms, indexed 0.023 ms (xtcp_control_experiments_status_location) diff --git a/docs/examples/controller-agent.json b/docs/examples/controller-agent.json new file mode 100644 index 000000000..0a76b77a5 --- /dev/null +++ b/docs/examples/controller-agent.json @@ -0,0 +1,15 @@ +{ + "agent_id": "host-001", + "location": "dc-example", + "controller": "controller.example.net:9443", + "server_name": "controller.example.net", + "certificate": "/etc/xtcp2/agent.crt", + "private_key": "/etc/xtcp2/agent.key", + "ca": "/etc/xtcp2/controller-ca.crt", + "directory": "/var/lib/xtcp2/routes", + "enable_apply": false, + "table": 10002, + "rule_priority": 30000, + "protocol": 242, + "protected_prefixes": ["192.0.2.0/24", "2001:db8:ffff::/48"] +} diff --git a/docs/examples/controller-discovery.json b/docs/examples/controller-discovery.json new file mode 100644 index 000000000..53877fc95 --- /dev/null +++ b/docs/examples/controller-discovery.json @@ -0,0 +1,23 @@ +[ + { + "id": "nearby-cubic", + "location": "dc-example", + "congestion_control": "cubic", + "rto_min_ms": 5, + "quick_ack": true, + "initial_congestion_window": 100, + "initial_receive_window": 100, + "minimum_rtt_ms": 0, + "maximum_rtt_ms": 2, + "minimum_retransmission_ratio": 0, + "prefixes": ["10.0.0.0/8"] + }, + { + "id": "distant-loss-bbr", + "location": "dc-example", + "congestion_control": "bbr", + "minimum_rtt_ms": 150, + "maximum_rtt_ms": 2000, + "minimum_retransmission_ratio": 0.001 + } +] diff --git a/docs/examples/xtcp2-treatment-watchdog.service b/docs/examples/xtcp2-treatment-watchdog.service new file mode 100644 index 000000000..ca847fff4 --- /dev/null +++ b/docs/examples/xtcp2-treatment-watchdog.service @@ -0,0 +1,18 @@ +[Unit] +Description=xtcp2 independent TCP treatment lease watchdog +After=local-fs.target + +[Service] +Type=simple +User=root +ExecStart=/usr/local/bin/xtcp2-treatmentctl -mode=watchdog -config=/etc/xtcp2/controller-agent.json +Restart=always +RestartSec=2s +StateDirectory=xtcp2 +StateDirectoryMode=0750 +AmbientCapabilities=CAP_NET_ADMIN CAP_SYS_PTRACE +CapabilityBoundingSet=CAP_NET_ADMIN CAP_SYS_PTRACE +NoNewPrivileges=true + +[Install] +WantedBy=multi-user.target diff --git a/docs/integration-testing.md b/docs/integration-testing.md index 77cde66fe..d014dad75 100644 --- a/docs/integration-testing.md +++ b/docs/integration-testing.md @@ -8,6 +8,7 @@ A comprehensive Nix-driven setup that boots xtcp2 inside QEMU microvms alongside - [Test surfaces at a glance](#test-surfaces-at-a-glance) - [Quick start](#quick-start) - [Architecture](#architecture) +- [TCP controller integration](#tcp-controller-integration) - [Microvm flavors](#microvm-flavors) - [Components](#components) - [xtcp2 daemon](#xtcp2-daemon) @@ -27,6 +28,109 @@ A comprehensive Nix-driven setup that boots xtcp2 inside QEMU microvms alongside - [Typical workflows](#typical-workflows) - [Troubleshooting](#troubleshooting) +## TCP controller integration + +Run `nix run path:.#test-tcp-controller-vm -- --accel=auto` for a fresh +controller test, or `nix build path:.#checks.x86_64-linux.vm-tcp-controller -L` +for the cached KVM check. It also participates in `vm-all` and `integration-all`. +The configuration is in `nix/tests/tcp-controller/`; the test driver is Go in +`tools/tcp-controller-integration/`, using the generated controller gRPC client. + +Six agent guests run the existing `tcp_client` load generator and real xtcp2 +service. A seventh guest runs `tcp_server`, PostgreSQL, ClickHouse and the +controller. Six hosts satisfy the production requirement for four treated hosts +and two controls. Each agent has 32 connections to each of two destination +addresses. `tc netem` adds 5 ms and 80 ms of outbound delay respectively; +management traffic stays unshaped. UNIX datagram links connect the guests to a +bridge inside the controller guest, without host networking privileges or +external network access. Allow approximately 9 GiB for guest memory, plus QEMU +overhead. KVM is preferred; `--accel=tcg` is available for software emulation. + +The Go driver verifies measured RTT separation in ClickHouse, reviewed nearby +route application on one host (`congctl cubic`, `rto_min 5ms`, `quickack 1`, +`initcwnd 100`, `initrwnd 100`), existing Reno versus newly exposed CUBIC sockets, +durable prototext state across daemon/controller restarts, local and fleet holds, +and reviewed expansion to two hosts. It then raises one treated host's delay to +180 ms and requires automatic rollback from measured harm. Finally, it stops +the controller and one agent, advances that isolated guest's clock past the +unchanged one-hour trial lease, and requires the independent watchdog to remove +the route without resurrecting it after restart/reconnection. Main routes and +the system default congestion algorithm must remain unchanged. + +This is a functional safety test, not a benchmark claiming that CUBIC improves +the synthetic workload. Observation intervals are shortened to 20 seconds using +the existing policy configuration; production authorization and lease behavior +are unchanged. The test exercises the direct protobuf/HTTP ingest path, not the +Kafka pipeline or automatic candidate discovery. It does not test reboot or +completion of a fleet-wide rollout. + +Evidence is retained under `~/.cache/xtcp2-vm/tcp-controller//` (or +`$XDG_CACHE_HOME`): controller and agent serial logs, RTT measurements, agent +journals/routes/prototext state, a PostgreSQL dump and a bounded recent telemetry +sample. Runtime-generated credentials remain inside the disposable guests. +The unauthenticated fixture HTTP endpoint is test-only and confined to the +isolated guest network; production controller communication uses mTLS. + +For repeated changes over time, run the Go-driven soak variant: + +```sh +nix run path:.#test-tcp-controller-soak-vm -- --cycles=3 --interval=3m +``` + +The default takes roughly 30 minutes: three cycles of three RTT phases, followed +by a cooldown check. `--cycles=2..6` and `--interval=3m|4m|5m` control the run; +`--accel` and `--keep` work as in the short test. The optional cached check is +`checks.x86_64-linux.vm-tcp-controller-soak`. It is excluded from `vm-all` and +included in `integration-all -- --soak` as a fixed-cycle run; the aggregate's +`--duration` applies to its other soak runners. + +Each cycle sends the six agents to a fresh destination and changes its netem +delay from 180 ms to 5 ms and back to 180 ms. High RTT must produce no nearby +candidate. Low RTT must cause the **controller's scheduled discovery** to propose +a new experiment with the same five nearby route settings. The test verifies +that discovery has applied no routes, +then acts as an authenticated test operator to approve the first canary. It +checks all five installed kernel metrics and actual CUBIC socket exposure, +and rejects any unapproved expansion. When +RTT rises again, an active trial must roll back automatically based on measured +harm; the driver never requests a rollback. The aggressive profile can also +cross a safety guard during the low-RTT phase. Such an early withdrawal is +accepted only with valid harmful evidence, observed canary exposure and verified +route removal. It is recorded separately as `low_rtt_safety_rollback`, and the +test continues to require that the route stays withdrawn. At least one cycle +must exercise an injected-latency rollback with high RTT on the canary; early +withdrawals cannot substitute for that coverage. The summary reports both counts. + +This variant uses a one-minute discovery interval and lookback, leaving the +production defaults (30 minutes and one hour), review requirements, rollout +timing and 24-hour destination cooldown intact. Its discovery template explicitly +sets `maximum_rtt_increase=1.0` (100%): the production 10% guard allows only about +0.5 ms at this fixture's low RTT and can trigger before netem changes. The wider +test tolerance still requires automatic rollback for the 5 ms to 180 ms jump. +Production templates keep the 10% default unless explicitly configured otherwise. +The retransmission guard remains unchanged at 0.005 (0.5 percentage points). +Fresh destinations allow each +cycle to discover a new candidate without bypassing that cooldown. The final +phase revisits the first destination at low RTT and verifies that it stays +withdrawn. New load connections reset TCP_INFO's lifetime minimum RTT at the +start of discovery phases; the regression phase retains the treated sockets. +The fixed 80 ms reference traffic continues throughout. + +Evidence under `~/.cache/xtcp2-vm/tcp-controller-soak//artifacts/` +includes `soak-events.jsonl` (RTT changes, proposals, explicit test approvals and +automatic rollbacks), `soak-rtt.csv` (per-host RTT and algorithm/exposure samples +every 15 seconds), and `soak-summary.json`. These are written during the run so +they can be inspected while the guests are running. The controller still +requires an operator to approve treatment application; this test does not make +it an autonomous optimizer or demonstrate that CUBIC outperforms Reno. + +To view the decision timeline for a run: + +```sh +jq -r '[.at, .event, .destination, (.experiment.id // "")] | @tsv' \ + ~/.cache/xtcp2-vm/tcp-controller-soak//artifacts/soak-events.jsonl +``` + ## Introduction This is the heavy integration-testing side of xtcp2. Unit tests live in `pkg/*/_test.go` and are run by `go test ./...`; the work documented here exercises code paths that only fire under a real Linux kernel, real namespaces, real network sockets, a real Kafka broker, and so on. diff --git a/docs/socket-analysis.md b/docs/socket-analysis.md index 94547c5fd..7abe320df 100644 --- a/docs/socket-analysis.md +++ b/docs/socket-analysis.md @@ -4,6 +4,10 @@ This guide is for a **data / analytics team** turning xtcp2's per-socket TCP tel Read [parquet-format.md](parquet-format.md) first for how to load the data and what the columns mean; this doc is the analysis companion. +To turn those observations into reviewed canaries, see the +[TCP treatment controller](tcp-controller.md), including destination scope, +actual socket exposure, persistence, and emergency withdrawal. + ## Table of contents - [The RTT-band mental model](#the-rtt-band-mental-model) diff --git a/docs/tcp-controller-testing.md b/docs/tcp-controller-testing.md new file mode 100644 index 000000000..739def973 --- /dev/null +++ b/docs/tcp-controller-testing.md @@ -0,0 +1,121 @@ +# TCP controller test and benchmark audit + +Audit date: 2026-10-25. All test drivers and benchmarks are Go. Nix provisions +fixtures; no Python driver is involved. + +## Reproduce + +```sh +nix build path:.#test-tcp-controller-unit --no-link --print-out-paths +nix build path:.#test-tcp-controller-race --no-link --print-out-paths +nix build path:.#bench-tcp-controller --no-link --print-out-paths +``` + +The first two targets are also flake checks, `tcp-controller-unit` and +`tcp-controller-race`. Their implementation is in +[`nix/tests/tcp-controller/quality.nix`](../nix/tests/tcp-controller/quality.nix). +They create a disposable PostgreSQL cluster using only a Unix socket and run +ClickHouse locally. They require the database tests to pass rather than silently +skip. No host routes are changed. Logs and coverage are retained in the output +store directory; benchmarks produce `benchmarks.txt`. + +The race target runs the feature suite and route-planning tests three times. +The benchmark target runs five one-second samples per case. Benchmark times are +observations, not CI thresholds: shared-machine load makes timing assertions +unreliable. Allocation counts are reported with `-benchmem`. + +For fast iteration without database fixtures: + +```sh +go test -ldflags=-checklinkname=0 ./pkg/treatment/... ./tools/tcp-controller-integration +CGO_ENABLED=1 go test -race -ldflags=-checklinkname=0 -count=10 \ + -run 'Concurrent|Notification|JournalConcurrent|AgentRun' ./pkg/treatment/... +go test -ldflags=-checklinkname=0 -run '^$' -bench . -benchmem ./pkg/treatment/... +``` + +Without `XTCP_CONTROLLER_TEST_DSN` and `XTCP_CLICKHOUSE_TEST_BINARY`, direct Go +commands skip the database tests. The Nix targets supply both automatically. + +## Coverage and test contracts + +New contract tables carry a description and expected outcome: an error substring, +gRPC/HTTP status, decision outcome, or explicit state/boolean values. Every case +gets a fresh fixture. Coverage includes positive, negative, boundary and corner +cases; it is not based only on happy-path workflows. + +| Area | Cases and assertions | +| --- | --- | +| Validation | Nil inputs, identifier length/characters, IPv4/IPv6 host scope, DCTCP/ECN prerequisites, explicit false versus absent metrics, inclusive RTO/window limits, duplicate identities/destinations, predecessor scope, stable/trial leases, 4,096 assignments and 10,000 hosts, NaN/infinite/negative measurements, timestamp validity, provenance and policy limits. | +| Decisions | Every evaluation outcome, observation/freshness/clock-skew boundaries, minimum samples, missing hosts/exposure, treated and control harm, small cohorts, full-fleet safety, reviewed transitions, input immutability and exact RTT guard boundaries. | +| Persistence and reconciliation | Intent before mutation, crash recovery, stale/conflicting revisions and generations, expiry at the exact deadline, predecessor restoration, holds/tombstones, failed plan/install/verify/remove/enable, recovery after failure, corrupt/oversized journals, symlink rejection and competing writer exclusion. | +| Agent | Config defaults and malformed input, attribution versus actual algorithm exposure, IPv4/IPv6 and padded addresses, namespace separation, expiry and holds, immutable snapshots, concurrent publish/stamp/state reads, wake coalescing, connected/offline cancellation and identity inspection failure. | +| Controller | mTLS role and enrollment boundaries, authenticated status/readiness, desired-state watch cancellation, notification coalescing/reconnects, HTTP failures/body closure/size limits, SQL counter deltas/resets, real transaction/review races, durable holds, discovery ASN/prefix filters and cooldown across controller restart. | +| Route planning | Opt-in and private resource bounds, unsupported forwarding attributes, IPv6 encoding, explicit ECN/quickack enable/disable, preservation of unrelated metrics/locks, complete nearby profile and baseline immutability. | + +Final direct Go run: race detector enabled, three repetitions, PostgreSQL and +ClickHouse fixtures enabled, no skips in `pkg/treatment/...` or the soak helper +suite. Route unit tests also passed three race-enabled repetitions. Focused +concurrency tests passed ten repetitions. `go vet` passed for the feature, +integration helper and goip packages. + +| Package | Direct race run | Nix unit check | +| --- | ---: | ---: | +| `pkg/treatment` | 89.1% | 89.0% | +| `pkg/treatment/agent` | 71.4% | 67.5% | +| `pkg/treatment/controller` | 74.3% | 73.9% | + +`Evaluate`, `HealthyEvidence`, and treatment, TCP-settings, policy, evidence and +proposal validators reached 100% statement coverage in both builds. In the direct +race run, `ValidateDesired` reached +91.3%, socket stamping 97.6%, discovery 80.2%, and agent `Run` 87.8%. +Statement coverage is not branch coverage or a proof of correctness. The final +Nix unit and race checks also passed; the figures above retain both measurements +rather than mixing coverage from different builds and environments. + +Remaining gaps include some filesystem/syscall error paths, live namespace +inspection, TLS client construction, reconnect/backoff timing, and all +combinations of controller database failures. Kernel route mutation and network +behavior remain the responsibility of the existing disposable-namespace and +seven-guest VM tests. The integration harness itself has low unit coverage +(3.8%); most of its code only executes inside those VMs. This audit did not rerun +the 30-minute soak; the previous three-cycle soak passed before these changes. + +The new RTT boundary regression exposed floating-point cancellation: `5.5/5-1` +can be slightly greater than `0.1`. Safety checks now compute the relative +change from the difference; evaluation uses `Log1p` and compares in log space. +Tests accept exactly 10% and reject a measurable increase above it, including +fleet-size cases. Policy limits themselves are unchanged. + +## Benchmark review + +Host: AMD Ryzen Threadripper PRO 3945WX, Linux/amd64, Go 1.27.0. These are medians +of three 300ms samples from the direct Go commands, not the separate Nix run. +Raw results are retained in [before.txt](benchmarks/tcp-controller/before.txt) and +[after.txt](benchmarks/tcp-controller/after.txt). The later run includes additional +snapshot-publication and route-planning benchmarks that had no earlier baseline. + +| Operation, 10,000 hosts | Before | After | Bytes/op before → after | Allocs/op before → after | +| --- | ---: | ---: | ---: | ---: | +| Validate evidence | 3.40 ms | 2.72 ms | 874,017 → 437,242 | 79 → 33 | +| Fast safety gate | 5.90 ms | 4.06 ms | 2,621,904 → 1,311,648 | 239 → 99 | +| Evaluate experiment | 7.48 ms | 4.60 ms | 3,391,637 → 1,557,578 | 317 → 106 | + +The straightforward improvement was reserving map and slice capacity from known +population sizes. Evaluation time fell about 39% and allocated bytes about 54% +in these samples. There are no new caches, synchronization or retained mutable +state. Tests exercise the same decision contracts before and after allocation +changes. + +The per-socket hot path was already cheap: with 4,096 routes, a matched stamp +measured 36.6ns and a miss 23.9ns, both zero allocations. Snapshot rebuilding at +4,096 routes measured 0.69ms and 652KB; it occurs during reconciliation rather +than per socket. Full-profile route planning measured 1.12µs and 756 bytes per +operation. Notification benchmarks cover complete 100/1,000/10,000-host fanout, +including coalescing for slow readers. Their first publication initializes the +revision map, so reported bytes/op include that amortized startup cost. + +No additional optimization was justified for stamping or route encoding. The +controller microbenchmarks exclude database/network latency and cannot establish +fleet-wide throughput. Future scaling work should measure ClickHouse queries, +PostgreSQL transaction contention and journal fsync latency under realistic +fleet traffic before adding complexity to these in-memory paths. diff --git a/docs/tcp-controller.md b/docs/tcp-controller.md new file mode 100644 index 000000000..362b88532 --- /dev/null +++ b/docs/tcp-controller.md @@ -0,0 +1,460 @@ +# Reviewed TCP route treatments + +xtcp2 can now opt into a separate controller that proposes TCP route treatments, +rolls them out after human review, and withdraws failed trials. Ordinary xtcp2 +collection and the goip CLI retain their existing behavior. Route application +requires both `-treatmentConfig` and `enable_apply: true`. + +## Components and authority + +```mermaid +flowchart LR + D[xtcp2 socket collection] --> K[ClickHouse telemetry] + K --> A[Candidate discovery and evidence queries] + A --> C[xtcp2-controller] + H[Human operator via mTLS] --> C + C <--> P[(PostgreSQL experiments, assignments, holds, audit)] + C -->|revision notification| D + D -->|check-in and observed state| C + D --> J[Atomic protobuf-text journal] + J --> R[goip route backend] + R --> L[Owned Linux routes and TCP lookup rules] + W[Independent local lease watchdog] --> J +``` + +The implementation lives in [pkg/treatment](../pkg/treatment), with a typed +[protobuf contract](../proto/xtcp/controller/v1/controller.proto). The binaries are: + +| Binary | Responsibility | +| --- | --- | +| `xtcp2-controller` | PostgreSQL state, discovery, analysis, review gates, leases, gRPC, status and metrics | +| `xtcp2-controllerctl` | Authenticated `propose`, `analyze`, `review`, `control`, and `inspect` requests | +| `xtcp2` | Optional outbound controller connection, reconciliation and telemetry attribution | +| `xtcp2-treatmentctl` | Local status, suspension, resume, reconciliation, independent watchdog | + +ClickHouse is an evidence source; PostgreSQL is the authority for desired state. +Every control mutation commits the experiment, affected assignments and audit +events in one transaction. A transaction-level advisory lock fences replicas. +Request IDs make operator retries idempotent; reusing an ID with a different +request is rejected. Reviews also require the expected experiment revision. +Agents consume complete revisioned snapshots, so missed notifications are safe. +A shared two-second notification scan coalesces updates; jittered check-ins run +approximately every thirty seconds. No message broker is required. + +## Supported routes and TCP semantics + +The first actuator supports IPv4 `/32` and IPv6 `/128` destinations in the host +network namespace, with ordinary local/main/default routing and a unique simple +unicast baseline route. It refuses policy-routing configurations, VRFs, ECMP, +nexthop objects and unsupported forwarding attributes. Run it on the host, not +inside a private PID/network namespace. Its `/proc/1/ns/net` check detects an +ordinary namespace mismatch; it cannot prove that a container's PID 1 is the +physical host's PID 1. + +The default reserved resources are table **10002**, rule priority **30000**, and +route/rule protocol **242**. The actuator copies the baseline gateway, interface, +preferred source and supported metrics into a destination override. TCP rules +with `iif lo` select locally originated traffic. Unmatched traffic falls through +to the normal tables. Main-table routes are never rewritten. These reserved +resources must have one owner; every mutation checks ownership and readback, +and refuses to overwrite or delete a changed/foreign route. Link, address, route +and rule notifications trigger replanning, with periodic reconciliation as a +fallback. A changed forwarding path invalidates the old override. + +Settings include congestion algorithm and optional lock, minimum RTO in ms, +initial send/receive windows in segments, quick ACK and ECN. Bounds are enforced +in [validate.go](../pkg/treatment/validate.go). Algorithms must already be loaded. +DCTCP requires explicit ECN and an operator assertion that the fabric has been +independently verified; a low RTT alone does not establish that requirement. +There is no setting to disable TCP slow start. + +The nearby-traffic template in +[controller-discovery.json](examples/controller-discovery.json) requests CUBIC, +`rto_min=5ms`, `quickack=1`, `initcwnd=100`, and `initrwnd=100`. These are explicit +route overrides for the discovered destination, including nearby NFS traffic; +they do not depend on matching an application port. The initial windows are in +segments. Discovery preserves these settings in the proposal, and the daemon +persists them in its protobuf-text journal before applying them through goip. +Omitting an optional metric preserves the baseline route's setting. The short +and soak VM tests verify all five installed metrics, including after journal +replay in the short test. + +A route treatment is destination-scoped, not socket-scoped, even though socket +data discovers it. Established connections may retain previous TCP settings. +The controller does not call `setsockopt` on application sockets or restart +applications. Withdrawing a route does not immediately undo an algorithm already +selected by a live socket. Long-lived NFS connections can therefore delay an +experiment or make it inconclusive. Initial-window exposure is not inferable +from an established socket's current congestion window. First-release experiments +require a congestion algorithm so the evaluator can observe actual algorithm +exposure; other settings may accompany it, but separate changes are easier to +interpret. + +## Experiments and evidence + +Candidate templates search per data center, using RTT, retransmission ratio and +optional destination prefixes/ASN filters. RTT/ASN conditions propose a +hypothesis; they do not prove that CUBIC or BBR will help. Existing ASN enrichment +identifies provider mappings, not verified BGP origins. Discovery runs every +thirty minutes with a one-hour lookback and never approves its own proposals. +`-discoveryInterval` (1m..24h) and `-discoveryWindow` (1m..168h) configure these; +discovery is checked on one-minute maintenance ticks. Short windows are useful +for integration testing but change the population and stability of the evidence. +Templates may set `maximum_rtt_increase` as a fractional increase in the range +0..1; omission uses the production default of 0.1 (10%). This policy value is +frozen in each proposal for operator review. The VM soak fixture uses 1.0 to +tolerate small timing variations at 5 ms while testing a jump to 180 ms. +Initially only one proposed, +running or held experiment is allowed per data center. + +Each proposal freezes a host population and baseline. Allocation is deterministic +from the experiment ID and interleaved across kernel-version strata. The operator +reviews that population before approving the first host. Eligibility and algorithm +availability are checked again before a newly selected host is assigned. + +The default sequence doubles `1, 2, 4, 8, ...`, retains at least two untreated +hosts before the last phase, and then reaches 100% of the **frozen eligible +population**. For eight hosts it is `1, 2, 4, 6, 8`. Newly enrolled hosts do not +silently join an approved experiment. Every expansion and final completion needs +another human review. Minimum observation defaults to thirty minutes **after +verified application**, not merely thirty minutes after sending a command. + +| Default policy | Value | +| --- | --- | +| Minimum observation | 30 minutes | +| Maximum phase lifetime | 24 hours | +| Maximum evidence age | 2 minutes | +| Minimum samples per host | 30 sockets and 1 MiB acknowledged | +| Minimum treated hosts for benefit inference | 4 | +| Required lower-bound improvement | 5% | +| Retransmission guardrail | increase of 0.005 in retransmitted/sent byte ratio | +| RTT guardrail | 10% increase in per-host p95 socket RTT | +| Trial lease / renewal interval | 1 hour / 5 minutes | + +The first seven values are configurable in the experiment policy. Leases are +currently controller constants. Maintenance runs each minute and may renew the +current cohort with fresh healthy evidence, or roll it back for harm/expiry; +maintenance cannot promote it. A pause stops renewal and promotion, so existing +trial leases eventually expire. Stable assignments have no lease. Completed +experiments receive hourly drift analysis; regressions request operator review +instead of automatically changing approved stable routes. + +[analysis.sql](../pkg/treatment/controller/analysis.sql) computes whole-window +counter deltas per boot, host, namespace and socket cookie, deduplicates poll +timestamps, then aggregates per host. It rejects negative endpoint deltas and +collection gaps over thirty seconds. Require normal collection at ten seconds +or faster. This is not a complete exporter-health detector: lost short flows, +resets followed by recovery within the window, and traffic missing altogether +still require external collector-health monitoring. + +The host is the experimental unit. Evaluation compares each host's log goodput +change with contemporaneous controls and uses a conservative Welch interval +budgeted over planned phases and minute-level looks. Tiny cohorts are safety +canaries, not evidence of causal benefit. The final 100% phase checks safety after +benefit has been established with controls. RTT/retransmission guardrails protect +both treated and control hosts; insufficient, stale or unexposed data blocks +promotion. Arbitrary repeated operator analyses, adaptive populations, workload +shifts and shared bottlenecks limit statistical interpretation. This is a review +aid, not a guarantee of causality or application-level benefit. Validate NFS/RPC +latency, errors and throughput alongside socket measurements before approval. + +Evidence includes analysis/window times, query version, a result digest and +per-host measurements, stored immutably in PostgreSQL. The digest identifies the +query result; the raw ClickHouse response is not archived as an external artifact. +Reviewed phases retain their evidence, reviewer, reason, issue time, observation +start and decision time. New maintenance evidence never overwrites the approved +phase record. + +This release uses scheduled whole-window queries against `xtcp_flat_records_v2`. +The existing ingestion materialized view carries the additive attribution fields. +It deliberately does not compute counter deltas in an insert-triggered MV: socket +samples cross insert blocks. A future aggregating-state MV must preserve boundary +samples and deduplication before replacing this query. Benchmark query cost and +end-to-end delivery latency on representative data before enabling many data +centers or approaching 5–10k hosts. The 10k-host notification/cohort tests are not +a production fleet load test. + +## PostgreSQL lookup costs and discovery reservations + +Discovery first checks the one-active-experiment budget for the data center. +If it is occupied, that template's ClickHouse scan is skipped. Otherwise each +candidate is checked in PostgreSQL by `(location, destination)` before fetching +baseline evidence. PROPOSED, RUNNING, PAUSED, SUSPENDED and COMPLETE experiments +reserve their destinations. RETIRED and ROLLED_BACK experiments reserve them until +24 hours after `updated_at`; at exactly 24 hours they become eligible again. +COMPLETE represents a stable treatment, so it does not expire out of this check. + +These checks use experiment state, not a fresh read of every machine's kernel +routes. Desired assignments describe the controller's intent, agent reports +describe verified application, and reconciliation repairs divergence. Drift +monitoring of a completed treatment can request operator review; automatic +discovery does not replace it. An explicit operator proposal may replace a stable +treatment using the existing reviewed predecessor/rollback mechanism. + +The early lookup is an optimization. The proposal transaction repeats the +destination check under the existing advisory lock, and repeats the DC budget +check, so concurrent controllers or changes during ClickHouse analysis cannot +insert competing automatic proposals. A database lookup failure stops discovery. + +Two B-tree expression indexes support these paths: + +| Index | Keys | Purpose | +| --- | --- | --- | +| `xtcp_control_experiments_destination` | location, treatment destination, updated_at descending | Existing-treatment and cooldown lookup without scanning unrelated history. | +| `xtcp_control_experiments_status_location` | experiment status, location | DC budget lookup and status-based maintenance selection. | + +The budget query uses `EXISTS` rather than counting every match. The large JSONB +state/evidence document is not indexed wholesale. Agent primary-key and location +indexes already cover their current lookups; no extra heartbeat indexes were +added. An all-agent notification scan still needs to read all agents, so an +additional index alone would not eliminate that work. + +`TestPostgresExperimentQueryIndexes` loads 100,000 retired experiments and five +active experiments, runs `ANALYZE`, and compares `EXPLAIN (ANALYZE, BUFFERS)` with +and without these indexes. It checks that PostgreSQL chooses an appropriate +index without disabling sequential scans; execution times are logged, not used +as brittle pass/fail thresholds. Eligibility tables cover all lifecycle states, +cooldown boundaries and location/destination isolation. Concurrent proposal tests +verify that only one automatic proposal is persisted. + +On the local PostgreSQL 15.19 fixture, three repeated runs measured a median +25.34 ms without indexes versus 0.023 ms with them for an old destination's +cooldown lookup. These are synthetic query execution times, not end-to-end +production latency. [Raw query-plan timing results](benchmarks/tcp-controller/postgres-indexes.txt) +also include missing destinations and active/idle data-center budget checks. + +Startup migration creates missing indexes idempotently. For an existing busy +database, prebuild these indexes before restarting controllers: + +```sql +-- Run outside a transaction, using the controller database/schema. +CREATE INDEX CONCURRENTLY IF NOT EXISTS xtcp_control_experiments_destination + ON xtcp_control_experiments + (location, (state->'treatment'->>'destination'), updated_at DESC); +CREATE INDEX CONCURRENTLY IF NOT EXISTS xtcp_control_experiments_status_location + ON xtcp_control_experiments ((state->>'status'), location); +``` + +Normal index creation blocks writes while building; PostgreSQL's +[concurrent index creation](https://www.postgresql.org/docs/current/sql-createindex.html#SQL-CREATEINDEX-CONCURRENTLY) +avoids that write-blocking build. If a concurrent build fails, inspect and remove +its invalid index before retrying: `IF NOT EXISTS` does not repair an invalid +index. The startup migration uses ordinary index creation, not concurrent DDL. + +Further scaling work should measure per-host SQL round trips during review and +lease renewal, large experiment-document rewrites, evidence/audit retention, and +the notification scan. Those need batching or data-layout changes, not blanket +indexes on every field. Discovery currently considers the ten busiest candidate +destinations per template before the PostgreSQL destination filter; pagination +or earlier exclusion would be needed to avoid already-treated destinations +occupying all ten slots. + +## Persistence, restart and withdrawal + +`/var/lib/xtcp2/routes/state.textproto` is the authoritative atomic snapshot. +`assignment-.textproto` files are readable projections containing receipt, +attempt, application, verification and removal times. Projection files are never +replayed independently. A private directory, file lock, temp-file rename and file +and directory fsync serialize the daemon, watchdog and local commands. Intent is +persisted before kernel mutation; kernel readback precedes success reporting. +Do not hand-edit projections to change desired state. + +Startup, periodic work and SIGHUP reconcile the snapshot. Boot IDs prevent stale +kernel fingerprints from being treated as current installations after reboot. +Stable assignments are restored without a controller round trip. Trial expiry is +latched locally: a delayed renewal of the same generation cannot resurrect it. +An explicit approved reactivation increments the generation. A trial replacing a +stable treatment stores a predecessor; expiry/rollback restores that predecessor +when it can be safely replanned. Permanent retirement removes the override. + +Run the independent watchdog even when xtcp2 itself stops. It checks every thirty +seconds and withdraws expired trials or persistent holds; it cannot accept new +desired state. Local time synchronization and monitoring of both processes are +required. If both processes are stopped, userspace cannot enforce the lease. +Corrupt journals fail closed for new application and require operator recovery; +they cannot prove ownership of routes already installed. + +| Operation | Result | +| --- | --- | +| Experiment `PAUSE` | Freeze promotion and renewal; existing leases run out | +| Experiment/host/DC/fleet `SUSPEND` | Persist a hold and withdraw owned overrides | +| Experiment `RESUME` | Explicitly reactivate the same cohort with a new generation and observation window | +| Host/DC/fleet `RESUME` | Remove that scope's hold; broader and local holds still win | +| Experiment `ROLLBACK` | End the experiment; restore a saved stable predecessor when present | +| Experiment `RETIRE` | End the experiment and remove its overrides | +| Local `suspend` | Offline persistent hold that controller requests cannot clear | + +Broad holds are inherited by new enrollments. A narrower resume cannot clear a +broader hold. Expired trials remain latched after a broad hold is lifted; pause +then explicitly resume the experiment, or retire it and propose a new one. +Suspending a completed experiment and resuming it preserves its stable approval. +Withdrawals affect future route/connection behavior; allow existing connections +to turn over before interpreting a temporary baseline test. Disconnected agents +cannot receive an immediate remote hold; use the local command or wait for trial +expiry. Stable routes intentionally persist during controller outages. + +Before disabling `enable_apply`, removing `-treatmentConfig`, or uninstalling +the watchdog, suspend the treatments and verify withdrawal. Disabling the +actuator prevents further writes; it does not itself delete existing kernel +routes. Keep the journal through cleanup so ownership can be checked. + +## Deployment + +1. Build the controller, client, treatmentctl and your normal xtcp2 build flavor. + Nix exposes `.#xtcp2-controller`, `.#xtcp2-controllerctl` and + `.#xtcp2-treatmentctl`; all three also build with ordinary `go build`. +2. Provision PostgreSQL with backups and a restricted database identity. The + controller's `-migrate` applies the additive schema; use it during controlled + deployment, then remove it from the long-running command. Back up audit and + evidence tables together. Restoring an older database can yield revisions + older than daemon journals: stop mutations and reconcile state explicitly; + do not reset daemon revisions to make the errors disappear. +3. Update the ClickHouse protobuf format schema and apply + [xtcp_treatment_migration.sql](../build/containers/clickhouse/initdb.d/sql/xtcp_treatment_migration.sql) + to an existing deployment before enabling agents. Fresh-install DDL already + contains the new columns. Preserve the telemetry tables and Kafka offsets. +4. Provision mutually authenticated TLS. The controller needs a server DNS SAN; + agents have exactly one URI SAN `spiffe://xtcp/agent//`; + operators have exactly one URI SAN `spiffe://xtcp/operator/`. + Agent IDs are globally unique and location is certificate-bound. Agent + certificates cannot invoke operator RPCs or fetch another agent's state. + Use protected key files; restart processes to load rotated certificates. + There is no online certificate revocation service in this release. +5. Configure explicit protected prefixes for management, DNS, telemetry, + controller dependencies and any infrastructure that must not be experimented + on. The agent also protects controller addresses resolved at startup. DNS + changes are why configured management prefixes remain necessary. +6. Start the controller near ClickHouse/PostgreSQL, enable agents with application + disabled first, validate identity/telemetry, then enable the actuator on a + small test population and deploy the independent watchdog. + +Example controller invocation (secrets remain outside the command line): + +```sh +xtcp2-controller -listen=:9443 -statusListen=127.0.0.1:9444 \ + -cert=/etc/xtcp2/controller.crt -key=/etc/xtcp2/controller.key \ + -ca=/etc/xtcp2/clients-ca.crt -databaseDSNFile=/etc/xtcp2/postgres.dsn \ + -clickhouse=https://clickhouse.example.net:8443 \ + -discoveryTemplates=/etc/xtcp2/discovery.json +``` + +ClickHouse credentials use `XTCP_CLICKHOUSE_USER` and +`XTCP_CLICKHOUSE_PASSWORD`. Without `-clickhouse`, analysis and lease renewal are +disabled; manual safety controls remain available. The example +[agent configuration](examples/controller-agent.json) is intentionally disabled; +replace its documentation addresses and protected prefixes before use. The +[discovery templates](examples/controller-discovery.json) are examples of +hypotheses, not recommended network-wide tuning defaults. + +On NixOS, set `services.xtcp2.treatments.enable = true`, its `configFile`, and +`watchdogPackage`. The module creates an independent watchdog service. A runtime +JSON file and private keys should remain outside the Nix store. The existing +`systemctl reload xtcp2` sends SIGUSR1 for link-monitor baseline relearning; +use `kill -HUP ` for route reconciliation. Other systems can use the +[example watchdog unit](examples/xtcp2-treatment-watchdog.service). + +## Operator workflow + +Every CLI request is protobuf text, following the messages in the contract. +Supply `-controller`, `-serverName`, `-cert`, `-key` and `-ca` on each invocation. +Use `-method=inspect` to find proposals and their revisions. Discovery creates +proposals automatically when configured; `-method=propose` accepts a manually +constructed `ProposeRequest` with a complete baseline and policy. + +Before approval, refresh and inspect evidence with `-method=analyze`: + +```protobuf +experiment_id: "hyp-example" +expected_revision: 1 +``` + +The result is an `AnalyzeResponse` containing `evidence { ... }` and `outcome`. +Copy the evidence block into a `ReviewRequest`, retaining its exact ID and values: + +```protobuf +experiment_id: "hyp-example" +expected_revision: 1 +approve: true +reason: "Reviewed baseline, eligible hosts and route scope" +request_id: "review-hyp-example-1" +# evidence { ...exact evidence returned by Analyze... } +``` + +Invoke `-method=review -file=review.textproto`. Subsequent reviews require fresh +evidence covering the observation window. Reuse the same request ID and exact +body after a network timeout; use a new ID for a new decision. Stale revision, +evidence, missing exposure, incomplete host reports or failed guards block review. +An operator may reject a proposal or stop a trial with `approve: false`. + +For an emergency fleet hold, use `-method=control` with: + +```protobuf +fleet: true +action: CONTROL_ACTION_SUSPEND +reason: "Temporary baseline comparison for incident 123" +request_id: "incident-123-fleet-hold" +``` + +The same message with a **new** ID and `CONTROL_ACTION_RESUME` lifts that fleet +hold. Substitute exactly one of `location`, `agent_id` or `experiment_id` for +`fleet` to narrow the scope. Offline local withdrawal: + +```sh +xtcp2-treatmentctl -config=/etc/xtcp2/controller-agent.json \ + -mode=suspend -reason='Local baseline comparison for incident 123' +xtcp2-treatmentctl -config=/etc/xtcp2/controller-agent.json -mode=status +xtcp2-treatmentctl -config=/etc/xtcp2/controller-agent.json \ + -mode=resume -reason='Comparison finished' +``` + +The optional status listener provides operator-mTLS-protected `/readyz`, `/status` +and `/metrics`. Monitor controller/agent availability, failed and expired route +records, holds, growing pending experiments, evidence freshness and watchdog +logs. `/status` is a JSON dashboard feed, not a graphical UI. A stopped controller +does not disable the ordinary xtcp2 collection service. + +## Validation and remaining extensions + +See the [test and benchmark audit](tcp-controller-testing.md) for table-driven +contracts, race coverage, measured performance, coverage limits, and reproducible +Nix unit/race/benchmark targets with isolated PostgreSQL and ClickHouse fixtures. + +Tests cover durable intent/crash recovery, stale generations, predecessor expiry, +local/broad holds, concurrent controller reviews, full human-reviewed rollout, +early safety withdrawal, actual algorithm attribution, and 10k-host cohort and +notification behavior. Optional tests exercise real PostgreSQL, ClickHouse SQL, +and IPv4/IPv6 netlink mutation in a disposable namespace: + +```sh +go test ./pkg/treatment/... ./internal/goip/... +XTCP_CONTROLLER_TEST_DSN='host=... dbname=... user=... sslmode=...' \ + go test ./pkg/treatment/controller -run Postgres +XTCP_CLICKHOUSE_TEST_BINARY=/path/to/clickhouse \ + go test ./pkg/treatment/controller -run ClickHouse +XTCP_TREATMENT_LIVE_TEST=1 go test ./internal/goip -run '^TestTreatmentLiveNamespace$' +``` + +The PostgreSQL tests create and drop only unique test schemas. The route test +creates its own user/network namespace and verifies that it differs from the +parent namespace before any write. It requires `ip`, `unshare` and a kernel that +permits unprivileged user namespaces. Existing daemon tests need this repository's +`-ldflags=-checklinkname=0` flag for giouring. + +The [Go-driven controller VM integration test](integration-testing.md#tcp-controller-integration) +uses the existing TCP load tools, six agent guests, real PostgreSQL/ClickHouse, +and `tc netem` latency bands. Run `nix run path:.#test-tcp-controller-vm`; +the cached target is `checks.x86_64-linux.vm-tcp-controller`. It checks reviewed +1→2 canaries, actual socket algorithm exposure, restart persistence, holds, +automatic rollback after injected harm, and independent watchdog lease expiry. +The longer `test-tcp-controller-soak-vm` target varies RTT every three minutes, +checks repeated scheduled discovery and automatic rollback, and verifies the +destination cooldown. Its test operator explicitly approves each new canary. + +Future work includes application SLO inputs, richer workload/fabric stratification, +aggregated analytical storage, provider-feed provenance, enrollment reconciliation +for new hosts, multi-experiment scheduling, automated withdrawal experiments, +and a graphical review dashboard. A future automated/LLM reviewer can consume the +same typed evidence and decision interface, but the deterministic authorization, +ownership, budget, freshness and guardrail checks must remain mandatory. This +release does not autonomously promote treatments or claim that a successful +small test has optimized an entire production fleet. diff --git a/flake.nix b/flake.nix index f35c263e1..e0086da9a 100644 --- a/flake.nix +++ b/flake.nix @@ -33,6 +33,15 @@ # See ./nix/README.md for individual targets, logs and Nix policy checks. # # Quick references: +# nix run path:.#test-tcp-controller-vm -- --accel=auto +# nix run path:.#test-tcp-controller-soak-vm -- --cycles=3 --interval=3m +# nix build path:.#test-tcp-controller-unit path:.#test-tcp-controller-race +# nix build path:.#bench-tcp-controller +# Repeated RTT changes, automatic discovery, test-operator approval, +# automatic rollback and cooldown checks (~30 minutes by default). +# Seven guests: TCP load, netem RTT bands, ClickHouse/PostgreSQL, reviewed +# canary, withdrawal and independent expiry. Also checks.vm-tcp-controller, +# checks.vm-all, and integration-all (nix/tests/tcp-controller). # nix develop # dev shell # nix build .#xtcp2 # main binary # nix build .#xtcp2-all # every cmd/* binary diff --git a/gen/go/xtcp_controller/controller.pb.go b/gen/go/xtcp_controller/controller.pb.go new file mode 100644 index 000000000..1d0bc74bb --- /dev/null +++ b/gen/go/xtcp_controller/controller.pb.go @@ -0,0 +1,2712 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v7.36.2 +// source: xtcp/controller/v1/controller.proto + +package xtcp_controller + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + durationpb "google.golang.org/protobuf/types/known/durationpb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type AssignmentMode int32 + +const ( + AssignmentMode_ASSIGNMENT_MODE_UNSPECIFIED AssignmentMode = 0 + AssignmentMode_ASSIGNMENT_MODE_TRIAL AssignmentMode = 1 + AssignmentMode_ASSIGNMENT_MODE_STABLE AssignmentMode = 2 + AssignmentMode_ASSIGNMENT_MODE_SUSPENDED AssignmentMode = 3 + AssignmentMode_ASSIGNMENT_MODE_RETIRED AssignmentMode = 4 +) + +// Enum value maps for AssignmentMode. +var ( + AssignmentMode_name = map[int32]string{ + 0: "ASSIGNMENT_MODE_UNSPECIFIED", + 1: "ASSIGNMENT_MODE_TRIAL", + 2: "ASSIGNMENT_MODE_STABLE", + 3: "ASSIGNMENT_MODE_SUSPENDED", + 4: "ASSIGNMENT_MODE_RETIRED", + } + AssignmentMode_value = map[string]int32{ + "ASSIGNMENT_MODE_UNSPECIFIED": 0, + "ASSIGNMENT_MODE_TRIAL": 1, + "ASSIGNMENT_MODE_STABLE": 2, + "ASSIGNMENT_MODE_SUSPENDED": 3, + "ASSIGNMENT_MODE_RETIRED": 4, + } +) + +func (x AssignmentMode) Enum() *AssignmentMode { + p := new(AssignmentMode) + *p = x + return p +} + +func (x AssignmentMode) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (AssignmentMode) Descriptor() protoreflect.EnumDescriptor { + return file_xtcp_controller_v1_controller_proto_enumTypes[0].Descriptor() +} + +func (AssignmentMode) Type() protoreflect.EnumType { + return &file_xtcp_controller_v1_controller_proto_enumTypes[0] +} + +func (x AssignmentMode) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use AssignmentMode.Descriptor instead. +func (AssignmentMode) EnumDescriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{0} +} + +type ApplyStatus int32 + +const ( + ApplyStatus_APPLY_STATUS_UNSPECIFIED ApplyStatus = 0 + ApplyStatus_APPLY_STATUS_PENDING ApplyStatus = 1 + ApplyStatus_APPLY_STATUS_VERIFIED ApplyStatus = 2 + ApplyStatus_APPLY_STATUS_REMOVED ApplyStatus = 3 + ApplyStatus_APPLY_STATUS_EXPIRED ApplyStatus = 4 + ApplyStatus_APPLY_STATUS_FAILED ApplyStatus = 5 +) + +// Enum value maps for ApplyStatus. +var ( + ApplyStatus_name = map[int32]string{ + 0: "APPLY_STATUS_UNSPECIFIED", + 1: "APPLY_STATUS_PENDING", + 2: "APPLY_STATUS_VERIFIED", + 3: "APPLY_STATUS_REMOVED", + 4: "APPLY_STATUS_EXPIRED", + 5: "APPLY_STATUS_FAILED", + } + ApplyStatus_value = map[string]int32{ + "APPLY_STATUS_UNSPECIFIED": 0, + "APPLY_STATUS_PENDING": 1, + "APPLY_STATUS_VERIFIED": 2, + "APPLY_STATUS_REMOVED": 3, + "APPLY_STATUS_EXPIRED": 4, + "APPLY_STATUS_FAILED": 5, + } +) + +func (x ApplyStatus) Enum() *ApplyStatus { + p := new(ApplyStatus) + *p = x + return p +} + +func (x ApplyStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ApplyStatus) Descriptor() protoreflect.EnumDescriptor { + return file_xtcp_controller_v1_controller_proto_enumTypes[1].Descriptor() +} + +func (ApplyStatus) Type() protoreflect.EnumType { + return &file_xtcp_controller_v1_controller_proto_enumTypes[1] +} + +func (x ApplyStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ApplyStatus.Descriptor instead. +func (ApplyStatus) EnumDescriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{1} +} + +type ExperimentStatus int32 + +const ( + ExperimentStatus_EXPERIMENT_STATUS_UNSPECIFIED ExperimentStatus = 0 + ExperimentStatus_EXPERIMENT_STATUS_PROPOSED ExperimentStatus = 1 + ExperimentStatus_EXPERIMENT_STATUS_RUNNING ExperimentStatus = 2 + ExperimentStatus_EXPERIMENT_STATUS_PAUSED ExperimentStatus = 3 + ExperimentStatus_EXPERIMENT_STATUS_SUSPENDED ExperimentStatus = 4 + ExperimentStatus_EXPERIMENT_STATUS_COMPLETE ExperimentStatus = 5 + ExperimentStatus_EXPERIMENT_STATUS_RETIRED ExperimentStatus = 6 + ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK ExperimentStatus = 7 +) + +// Enum value maps for ExperimentStatus. +var ( + ExperimentStatus_name = map[int32]string{ + 0: "EXPERIMENT_STATUS_UNSPECIFIED", + 1: "EXPERIMENT_STATUS_PROPOSED", + 2: "EXPERIMENT_STATUS_RUNNING", + 3: "EXPERIMENT_STATUS_PAUSED", + 4: "EXPERIMENT_STATUS_SUSPENDED", + 5: "EXPERIMENT_STATUS_COMPLETE", + 6: "EXPERIMENT_STATUS_RETIRED", + 7: "EXPERIMENT_STATUS_ROLLED_BACK", + } + ExperimentStatus_value = map[string]int32{ + "EXPERIMENT_STATUS_UNSPECIFIED": 0, + "EXPERIMENT_STATUS_PROPOSED": 1, + "EXPERIMENT_STATUS_RUNNING": 2, + "EXPERIMENT_STATUS_PAUSED": 3, + "EXPERIMENT_STATUS_SUSPENDED": 4, + "EXPERIMENT_STATUS_COMPLETE": 5, + "EXPERIMENT_STATUS_RETIRED": 6, + "EXPERIMENT_STATUS_ROLLED_BACK": 7, + } +) + +func (x ExperimentStatus) Enum() *ExperimentStatus { + p := new(ExperimentStatus) + *p = x + return p +} + +func (x ExperimentStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ExperimentStatus) Descriptor() protoreflect.EnumDescriptor { + return file_xtcp_controller_v1_controller_proto_enumTypes[2].Descriptor() +} + +func (ExperimentStatus) Type() protoreflect.EnumType { + return &file_xtcp_controller_v1_controller_proto_enumTypes[2] +} + +func (x ExperimentStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ExperimentStatus.Descriptor instead. +func (ExperimentStatus) EnumDescriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{2} +} + +type ControlAction int32 + +const ( + ControlAction_CONTROL_ACTION_UNSPECIFIED ControlAction = 0 + ControlAction_CONTROL_ACTION_PAUSE ControlAction = 1 + ControlAction_CONTROL_ACTION_SUSPEND ControlAction = 2 + ControlAction_CONTROL_ACTION_RESUME ControlAction = 3 + ControlAction_CONTROL_ACTION_ROLLBACK ControlAction = 4 + ControlAction_CONTROL_ACTION_RETIRE ControlAction = 5 +) + +// Enum value maps for ControlAction. +var ( + ControlAction_name = map[int32]string{ + 0: "CONTROL_ACTION_UNSPECIFIED", + 1: "CONTROL_ACTION_PAUSE", + 2: "CONTROL_ACTION_SUSPEND", + 3: "CONTROL_ACTION_RESUME", + 4: "CONTROL_ACTION_ROLLBACK", + 5: "CONTROL_ACTION_RETIRE", + } + ControlAction_value = map[string]int32{ + "CONTROL_ACTION_UNSPECIFIED": 0, + "CONTROL_ACTION_PAUSE": 1, + "CONTROL_ACTION_SUSPEND": 2, + "CONTROL_ACTION_RESUME": 3, + "CONTROL_ACTION_ROLLBACK": 4, + "CONTROL_ACTION_RETIRE": 5, + } +) + +func (x ControlAction) Enum() *ControlAction { + p := new(ControlAction) + *p = x + return p +} + +func (x ControlAction) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ControlAction) Descriptor() protoreflect.EnumDescriptor { + return file_xtcp_controller_v1_controller_proto_enumTypes[3].Descriptor() +} + +func (ControlAction) Type() protoreflect.EnumType { + return &file_xtcp_controller_v1_controller_proto_enumTypes[3] +} + +func (x ControlAction) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ControlAction.Descriptor instead. +func (ControlAction) EnumDescriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{3} +} + +type TCPSettings struct { + state protoimpl.MessageState `protogen:"open.v1"` + CongestionControl *string `protobuf:"bytes,1,opt,name=congestion_control,json=congestionControl,proto3,oneof" json:"congestion_control,omitempty"` + CongestionLock bool `protobuf:"varint,2,opt,name=congestion_lock,json=congestionLock,proto3" json:"congestion_lock,omitempty"` + RtoMinMs *uint32 `protobuf:"varint,3,opt,name=rto_min_ms,json=rtoMinMs,proto3,oneof" json:"rto_min_ms,omitempty"` + InitialCongestionWindow *uint32 `protobuf:"varint,4,opt,name=initial_congestion_window,json=initialCongestionWindow,proto3,oneof" json:"initial_congestion_window,omitempty"` + InitialReceiveWindow *uint32 `protobuf:"varint,5,opt,name=initial_receive_window,json=initialReceiveWindow,proto3,oneof" json:"initial_receive_window,omitempty"` + QuickAck *bool `protobuf:"varint,6,opt,name=quick_ack,json=quickAck,proto3,oneof" json:"quick_ack,omitempty"` + Ecn *bool `protobuf:"varint,7,opt,name=ecn,proto3,oneof" json:"ecn,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TCPSettings) Reset() { + *x = TCPSettings{} + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TCPSettings) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TCPSettings) ProtoMessage() {} + +func (x *TCPSettings) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TCPSettings.ProtoReflect.Descriptor instead. +func (*TCPSettings) Descriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{0} +} + +func (x *TCPSettings) GetCongestionControl() string { + if x != nil && x.CongestionControl != nil { + return *x.CongestionControl + } + return "" +} + +func (x *TCPSettings) GetCongestionLock() bool { + if x != nil { + return x.CongestionLock + } + return false +} + +func (x *TCPSettings) GetRtoMinMs() uint32 { + if x != nil && x.RtoMinMs != nil { + return *x.RtoMinMs + } + return 0 +} + +func (x *TCPSettings) GetInitialCongestionWindow() uint32 { + if x != nil && x.InitialCongestionWindow != nil { + return *x.InitialCongestionWindow + } + return 0 +} + +func (x *TCPSettings) GetInitialReceiveWindow() uint32 { + if x != nil && x.InitialReceiveWindow != nil { + return *x.InitialReceiveWindow + } + return 0 +} + +func (x *TCPSettings) GetQuickAck() bool { + if x != nil && x.QuickAck != nil { + return *x.QuickAck + } + return false +} + +func (x *TCPSettings) GetEcn() bool { + if x != nil && x.Ecn != nil { + return *x.Ecn + } + return false +} + +type Treatment struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Revision uint64 `protobuf:"varint,2,opt,name=revision,proto3" json:"revision,omitempty"` + Destination string `protobuf:"bytes,3,opt,name=destination,proto3" json:"destination,omitempty"` + Settings *TCPSettings `protobuf:"bytes,4,opt,name=settings,proto3" json:"settings,omitempty"` + EcnFabricVerified bool `protobuf:"varint,5,opt,name=ecn_fabric_verified,json=ecnFabricVerified,proto3" json:"ecn_fabric_verified,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Treatment) Reset() { + *x = Treatment{} + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Treatment) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Treatment) ProtoMessage() {} + +func (x *Treatment) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Treatment.ProtoReflect.Descriptor instead. +func (*Treatment) Descriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{1} +} + +func (x *Treatment) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Treatment) GetRevision() uint64 { + if x != nil { + return x.Revision + } + return 0 +} + +func (x *Treatment) GetDestination() string { + if x != nil { + return x.Destination + } + return "" +} + +func (x *Treatment) GetSettings() *TCPSettings { + if x != nil { + return x.Settings + } + return nil +} + +func (x *Treatment) GetEcnFabricVerified() bool { + if x != nil { + return x.EcnFabricVerified + } + return false +} + +type Assignment struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ExperimentId string `protobuf:"bytes,2,opt,name=experiment_id,json=experimentId,proto3" json:"experiment_id,omitempty"` + Treatment *Treatment `protobuf:"bytes,3,opt,name=treatment,proto3" json:"treatment,omitempty"` + Mode AssignmentMode `protobuf:"varint,4,opt,name=mode,proto3,enum=xtcp.controller.v1.AssignmentMode" json:"mode,omitempty"` + LeaseExpiresAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=lease_expires_at,json=leaseExpiresAt,proto3" json:"lease_expires_at,omitempty"` + // Previous approved stable treatment, restored on trial expiry/rollback. + Predecessor *Treatment `protobuf:"bytes,6,opt,name=predecessor,proto3" json:"predecessor,omitempty"` + // Increment only on an explicitly approved reactivation, not lease renewal. + Generation uint64 `protobuf:"varint,7,opt,name=generation,proto3" json:"generation,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Assignment) Reset() { + *x = Assignment{} + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Assignment) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Assignment) ProtoMessage() {} + +func (x *Assignment) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Assignment.ProtoReflect.Descriptor instead. +func (*Assignment) Descriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{2} +} + +func (x *Assignment) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Assignment) GetExperimentId() string { + if x != nil { + return x.ExperimentId + } + return "" +} + +func (x *Assignment) GetTreatment() *Treatment { + if x != nil { + return x.Treatment + } + return nil +} + +func (x *Assignment) GetMode() AssignmentMode { + if x != nil { + return x.Mode + } + return AssignmentMode_ASSIGNMENT_MODE_UNSPECIFIED +} + +func (x *Assignment) GetLeaseExpiresAt() *timestamppb.Timestamp { + if x != nil { + return x.LeaseExpiresAt + } + return nil +} + +func (x *Assignment) GetPredecessor() *Treatment { + if x != nil { + return x.Predecessor + } + return nil +} + +func (x *Assignment) GetGeneration() uint64 { + if x != nil { + return x.Generation + } + return 0 +} + +type DesiredState struct { + state protoimpl.MessageState `protogen:"open.v1"` + AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` + Revision uint64 `protobuf:"varint,2,opt,name=revision,proto3" json:"revision,omitempty"` + Suspended bool `protobuf:"varint,3,opt,name=suspended,proto3" json:"suspended,omitempty"` + Assignments []*Assignment `protobuf:"bytes,4,rep,name=assignments,proto3" json:"assignments,omitempty"` + IssuedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=issued_at,json=issuedAt,proto3" json:"issued_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DesiredState) Reset() { + *x = DesiredState{} + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DesiredState) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DesiredState) ProtoMessage() {} + +func (x *DesiredState) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DesiredState.ProtoReflect.Descriptor instead. +func (*DesiredState) Descriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{3} +} + +func (x *DesiredState) GetAgentId() string { + if x != nil { + return x.AgentId + } + return "" +} + +func (x *DesiredState) GetRevision() uint64 { + if x != nil { + return x.Revision + } + return 0 +} + +func (x *DesiredState) GetSuspended() bool { + if x != nil { + return x.Suspended + } + return false +} + +func (x *DesiredState) GetAssignments() []*Assignment { + if x != nil { + return x.Assignments + } + return nil +} + +func (x *DesiredState) GetIssuedAt() *timestamppb.Timestamp { + if x != nil { + return x.IssuedAt + } + return nil +} + +type Capabilities struct { + state protoimpl.MessageState `protogen:"open.v1"` + BootId string `protobuf:"bytes,1,opt,name=boot_id,json=bootId,proto3" json:"boot_id,omitempty"` + KernelRelease string `protobuf:"bytes,2,opt,name=kernel_release,json=kernelRelease,proto3" json:"kernel_release,omitempty"` + CongestionAlgorithms []string `protobuf:"bytes,3,rep,name=congestion_algorithms,json=congestionAlgorithms,proto3" json:"congestion_algorithms,omitempty"` + SimpleHostRouting bool `protobuf:"varint,4,opt,name=simple_host_routing,json=simpleHostRouting,proto3" json:"simple_host_routing,omitempty"` + ActuatorEnabled bool `protobuf:"varint,5,opt,name=actuator_enabled,json=actuatorEnabled,proto3" json:"actuator_enabled,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Capabilities) Reset() { + *x = Capabilities{} + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Capabilities) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Capabilities) ProtoMessage() {} + +func (x *Capabilities) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Capabilities.ProtoReflect.Descriptor instead. +func (*Capabilities) Descriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{4} +} + +func (x *Capabilities) GetBootId() string { + if x != nil { + return x.BootId + } + return "" +} + +func (x *Capabilities) GetKernelRelease() string { + if x != nil { + return x.KernelRelease + } + return "" +} + +func (x *Capabilities) GetCongestionAlgorithms() []string { + if x != nil { + return x.CongestionAlgorithms + } + return nil +} + +func (x *Capabilities) GetSimpleHostRouting() bool { + if x != nil { + return x.SimpleHostRouting + } + return false +} + +func (x *Capabilities) GetActuatorEnabled() bool { + if x != nil { + return x.ActuatorEnabled + } + return false +} + +type ApplicationRecord struct { + state protoimpl.MessageState `protogen:"open.v1"` + Assignment *Assignment `protobuf:"bytes,1,opt,name=assignment,proto3" json:"assignment,omitempty"` + DesiredRevision uint64 `protobuf:"varint,2,opt,name=desired_revision,json=desiredRevision,proto3" json:"desired_revision,omitempty"` + Status ApplyStatus `protobuf:"varint,3,opt,name=status,proto3,enum=xtcp.controller.v1.ApplyStatus" json:"status,omitempty"` + ReceivedAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=received_at,json=receivedAt,proto3" json:"received_at,omitempty"` + AttemptedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=attempted_at,json=attemptedAt,proto3" json:"attempted_at,omitempty"` + AppliedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=applied_at,json=appliedAt,proto3" json:"applied_at,omitempty"` + VerifiedAt *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=verified_at,json=verifiedAt,proto3" json:"verified_at,omitempty"` + RemovedAt *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=removed_at,json=removedAt,proto3" json:"removed_at,omitempty"` + Error string `protobuf:"bytes,9,opt,name=error,proto3" json:"error,omitempty"` + // Locally generated canonical netlink route body, never accepted from RPC. + InstalledRoute []byte `protobuf:"bytes,10,opt,name=installed_route,json=installedRoute,proto3" json:"installed_route,omitempty"` + InstalledBootId string `protobuf:"bytes,11,opt,name=installed_boot_id,json=installedBootId,proto3" json:"installed_boot_id,omitempty"` + ExpiredLatched bool `protobuf:"varint,12,opt,name=expired_latched,json=expiredLatched,proto3" json:"expired_latched,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ApplicationRecord) Reset() { + *x = ApplicationRecord{} + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ApplicationRecord) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ApplicationRecord) ProtoMessage() {} + +func (x *ApplicationRecord) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ApplicationRecord.ProtoReflect.Descriptor instead. +func (*ApplicationRecord) Descriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{5} +} + +func (x *ApplicationRecord) GetAssignment() *Assignment { + if x != nil { + return x.Assignment + } + return nil +} + +func (x *ApplicationRecord) GetDesiredRevision() uint64 { + if x != nil { + return x.DesiredRevision + } + return 0 +} + +func (x *ApplicationRecord) GetStatus() ApplyStatus { + if x != nil { + return x.Status + } + return ApplyStatus_APPLY_STATUS_UNSPECIFIED +} + +func (x *ApplicationRecord) GetReceivedAt() *timestamppb.Timestamp { + if x != nil { + return x.ReceivedAt + } + return nil +} + +func (x *ApplicationRecord) GetAttemptedAt() *timestamppb.Timestamp { + if x != nil { + return x.AttemptedAt + } + return nil +} + +func (x *ApplicationRecord) GetAppliedAt() *timestamppb.Timestamp { + if x != nil { + return x.AppliedAt + } + return nil +} + +func (x *ApplicationRecord) GetVerifiedAt() *timestamppb.Timestamp { + if x != nil { + return x.VerifiedAt + } + return nil +} + +func (x *ApplicationRecord) GetRemovedAt() *timestamppb.Timestamp { + if x != nil { + return x.RemovedAt + } + return nil +} + +func (x *ApplicationRecord) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +func (x *ApplicationRecord) GetInstalledRoute() []byte { + if x != nil { + return x.InstalledRoute + } + return nil +} + +func (x *ApplicationRecord) GetInstalledBootId() string { + if x != nil { + return x.InstalledBootId + } + return "" +} + +func (x *ApplicationRecord) GetExpiredLatched() bool { + if x != nil { + return x.ExpiredLatched + } + return false +} + +type LocalState struct { + state protoimpl.MessageState `protogen:"open.v1"` + Desired *DesiredState `protobuf:"bytes,1,opt,name=desired,proto3" json:"desired,omitempty"` + LocallySuspended bool `protobuf:"varint,2,opt,name=locally_suspended,json=locallySuspended,proto3" json:"locally_suspended,omitempty"` + SuspensionReason string `protobuf:"bytes,3,opt,name=suspension_reason,json=suspensionReason,proto3" json:"suspension_reason,omitempty"` + Records []*ApplicationRecord `protobuf:"bytes,4,rep,name=records,proto3" json:"records,omitempty"` + UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LocalState) Reset() { + *x = LocalState{} + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LocalState) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LocalState) ProtoMessage() {} + +func (x *LocalState) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LocalState.ProtoReflect.Descriptor instead. +func (*LocalState) Descriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{6} +} + +func (x *LocalState) GetDesired() *DesiredState { + if x != nil { + return x.Desired + } + return nil +} + +func (x *LocalState) GetLocallySuspended() bool { + if x != nil { + return x.LocallySuspended + } + return false +} + +func (x *LocalState) GetSuspensionReason() string { + if x != nil { + return x.SuspensionReason + } + return "" +} + +func (x *LocalState) GetRecords() []*ApplicationRecord { + if x != nil { + return x.Records + } + return nil +} + +func (x *LocalState) GetUpdatedAt() *timestamppb.Timestamp { + if x != nil { + return x.UpdatedAt + } + return nil +} + +type CheckInRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` + Location string `protobuf:"bytes,2,opt,name=location,proto3" json:"location,omitempty"` + Capabilities *Capabilities `protobuf:"bytes,3,opt,name=capabilities,proto3" json:"capabilities,omitempty"` + DesiredRevision uint64 `protobuf:"varint,4,opt,name=desired_revision,json=desiredRevision,proto3" json:"desired_revision,omitempty"` + Records []*ApplicationRecord `protobuf:"bytes,5,rep,name=records,proto3" json:"records,omitempty"` + LocallySuspended bool `protobuf:"varint,6,opt,name=locally_suspended,json=locallySuspended,proto3" json:"locally_suspended,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CheckInRequest) Reset() { + *x = CheckInRequest{} + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CheckInRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CheckInRequest) ProtoMessage() {} + +func (x *CheckInRequest) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CheckInRequest.ProtoReflect.Descriptor instead. +func (*CheckInRequest) Descriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{7} +} + +func (x *CheckInRequest) GetAgentId() string { + if x != nil { + return x.AgentId + } + return "" +} + +func (x *CheckInRequest) GetLocation() string { + if x != nil { + return x.Location + } + return "" +} + +func (x *CheckInRequest) GetCapabilities() *Capabilities { + if x != nil { + return x.Capabilities + } + return nil +} + +func (x *CheckInRequest) GetDesiredRevision() uint64 { + if x != nil { + return x.DesiredRevision + } + return 0 +} + +func (x *CheckInRequest) GetRecords() []*ApplicationRecord { + if x != nil { + return x.Records + } + return nil +} + +func (x *CheckInRequest) GetLocallySuspended() bool { + if x != nil { + return x.LocallySuspended + } + return false +} + +type CheckInResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Desired *DesiredState `protobuf:"bytes,1,opt,name=desired,proto3" json:"desired,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CheckInResponse) Reset() { + *x = CheckInResponse{} + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CheckInResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CheckInResponse) ProtoMessage() {} + +func (x *CheckInResponse) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CheckInResponse.ProtoReflect.Descriptor instead. +func (*CheckInResponse) Descriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{8} +} + +func (x *CheckInResponse) GetDesired() *DesiredState { + if x != nil { + return x.Desired + } + return nil +} + +type GetDesiredStateRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetDesiredStateRequest) Reset() { + *x = GetDesiredStateRequest{} + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetDesiredStateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetDesiredStateRequest) ProtoMessage() {} + +func (x *GetDesiredStateRequest) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetDesiredStateRequest.ProtoReflect.Descriptor instead. +func (*GetDesiredStateRequest) Descriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{9} +} + +func (x *GetDesiredStateRequest) GetAgentId() string { + if x != nil { + return x.AgentId + } + return "" +} + +type GetDesiredStateResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Desired *DesiredState `protobuf:"bytes,1,opt,name=desired,proto3" json:"desired,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetDesiredStateResponse) Reset() { + *x = GetDesiredStateResponse{} + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetDesiredStateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetDesiredStateResponse) ProtoMessage() {} + +func (x *GetDesiredStateResponse) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetDesiredStateResponse.ProtoReflect.Descriptor instead. +func (*GetDesiredStateResponse) Descriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{10} +} + +func (x *GetDesiredStateResponse) GetDesired() *DesiredState { + if x != nil { + return x.Desired + } + return nil +} + +type WatchDesiredStateRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` + AfterRevision uint64 `protobuf:"varint,2,opt,name=after_revision,json=afterRevision,proto3" json:"after_revision,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WatchDesiredStateRequest) Reset() { + *x = WatchDesiredStateRequest{} + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WatchDesiredStateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WatchDesiredStateRequest) ProtoMessage() {} + +func (x *WatchDesiredStateRequest) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WatchDesiredStateRequest.ProtoReflect.Descriptor instead. +func (*WatchDesiredStateRequest) Descriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{11} +} + +func (x *WatchDesiredStateRequest) GetAgentId() string { + if x != nil { + return x.AgentId + } + return "" +} + +func (x *WatchDesiredStateRequest) GetAfterRevision() uint64 { + if x != nil { + return x.AfterRevision + } + return 0 +} + +type WatchDesiredStateResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Revision uint64 `protobuf:"varint,1,opt,name=revision,proto3" json:"revision,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WatchDesiredStateResponse) Reset() { + *x = WatchDesiredStateResponse{} + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WatchDesiredStateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WatchDesiredStateResponse) ProtoMessage() {} + +func (x *WatchDesiredStateResponse) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WatchDesiredStateResponse.ProtoReflect.Descriptor instead. +func (*WatchDesiredStateResponse) Descriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{12} +} + +func (x *WatchDesiredStateResponse) GetRevision() uint64 { + if x != nil { + return x.Revision + } + return 0 +} + +type HostEvidence struct { + state protoimpl.MessageState `protogen:"open.v1"` + AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` + Sockets uint64 `protobuf:"varint,2,opt,name=sockets,proto3" json:"sockets,omitempty"` + BytesAcked uint64 `protobuf:"varint,3,opt,name=bytes_acked,json=bytesAcked,proto3" json:"bytes_acked,omitempty"` + GoodputBps float64 `protobuf:"fixed64,4,opt,name=goodput_bps,json=goodputBps,proto3" json:"goodput_bps,omitempty"` + RetransmissionRatio float64 `protobuf:"fixed64,5,opt,name=retransmission_ratio,json=retransmissionRatio,proto3" json:"retransmission_ratio,omitempty"` + RttP95Ms float64 `protobuf:"fixed64,6,opt,name=rtt_p95_ms,json=rttP95Ms,proto3" json:"rtt_p95_ms,omitempty"` + ExposedSockets uint64 `protobuf:"varint,7,opt,name=exposed_sockets,json=exposedSockets,proto3" json:"exposed_sockets,omitempty"` + CollectionHealthy bool `protobuf:"varint,8,opt,name=collection_healthy,json=collectionHealthy,proto3" json:"collection_healthy,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HostEvidence) Reset() { + *x = HostEvidence{} + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HostEvidence) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HostEvidence) ProtoMessage() {} + +func (x *HostEvidence) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HostEvidence.ProtoReflect.Descriptor instead. +func (*HostEvidence) Descriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{13} +} + +func (x *HostEvidence) GetAgentId() string { + if x != nil { + return x.AgentId + } + return "" +} + +func (x *HostEvidence) GetSockets() uint64 { + if x != nil { + return x.Sockets + } + return 0 +} + +func (x *HostEvidence) GetBytesAcked() uint64 { + if x != nil { + return x.BytesAcked + } + return 0 +} + +func (x *HostEvidence) GetGoodputBps() float64 { + if x != nil { + return x.GoodputBps + } + return 0 +} + +func (x *HostEvidence) GetRetransmissionRatio() float64 { + if x != nil { + return x.RetransmissionRatio + } + return 0 +} + +func (x *HostEvidence) GetRttP95Ms() float64 { + if x != nil { + return x.RttP95Ms + } + return 0 +} + +func (x *HostEvidence) GetExposedSockets() uint64 { + if x != nil { + return x.ExposedSockets + } + return 0 +} + +func (x *HostEvidence) GetCollectionHealthy() bool { + if x != nil { + return x.CollectionHealthy + } + return false +} + +type Evidence struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + AnalyzedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=analyzed_at,json=analyzedAt,proto3" json:"analyzed_at,omitempty"` + WindowStart *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=window_start,json=windowStart,proto3" json:"window_start,omitempty"` + WindowEnd *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=window_end,json=windowEnd,proto3" json:"window_end,omitempty"` + QueryVersion string `protobuf:"bytes,5,opt,name=query_version,json=queryVersion,proto3" json:"query_version,omitempty"` + EnrichmentVersion string `protobuf:"bytes,6,opt,name=enrichment_version,json=enrichmentVersion,proto3" json:"enrichment_version,omitempty"` + Hosts []*HostEvidence `protobuf:"bytes,7,rep,name=hosts,proto3" json:"hosts,omitempty"` + // Content-addressed reference to the complete immutable analysis artifact. + ArtifactSha256 string `protobuf:"bytes,8,opt,name=artifact_sha256,json=artifactSha256,proto3" json:"artifact_sha256,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Evidence) Reset() { + *x = Evidence{} + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Evidence) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Evidence) ProtoMessage() {} + +func (x *Evidence) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Evidence.ProtoReflect.Descriptor instead. +func (*Evidence) Descriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{14} +} + +func (x *Evidence) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Evidence) GetAnalyzedAt() *timestamppb.Timestamp { + if x != nil { + return x.AnalyzedAt + } + return nil +} + +func (x *Evidence) GetWindowStart() *timestamppb.Timestamp { + if x != nil { + return x.WindowStart + } + return nil +} + +func (x *Evidence) GetWindowEnd() *timestamppb.Timestamp { + if x != nil { + return x.WindowEnd + } + return nil +} + +func (x *Evidence) GetQueryVersion() string { + if x != nil { + return x.QueryVersion + } + return "" +} + +func (x *Evidence) GetEnrichmentVersion() string { + if x != nil { + return x.EnrichmentVersion + } + return "" +} + +func (x *Evidence) GetHosts() []*HostEvidence { + if x != nil { + return x.Hosts + } + return nil +} + +func (x *Evidence) GetArtifactSha256() string { + if x != nil { + return x.ArtifactSha256 + } + return "" +} + +type ExperimentPolicy struct { + state protoimpl.MessageState `protogen:"open.v1"` + MinimumObservation *durationpb.Duration `protobuf:"bytes,1,opt,name=minimum_observation,json=minimumObservation,proto3" json:"minimum_observation,omitempty"` + MaximumPhase *durationpb.Duration `protobuf:"bytes,2,opt,name=maximum_phase,json=maximumPhase,proto3" json:"maximum_phase,omitempty"` + MinimumSocketsPerHost uint64 `protobuf:"varint,3,opt,name=minimum_sockets_per_host,json=minimumSocketsPerHost,proto3" json:"minimum_sockets_per_host,omitempty"` + MinimumBytesPerHost uint64 `protobuf:"varint,4,opt,name=minimum_bytes_per_host,json=minimumBytesPerHost,proto3" json:"minimum_bytes_per_host,omitempty"` + MinimumTreatedHosts uint32 `protobuf:"varint,5,opt,name=minimum_treated_hosts,json=minimumTreatedHosts,proto3" json:"minimum_treated_hosts,omitempty"` + MinimumImprovement float64 `protobuf:"fixed64,6,opt,name=minimum_improvement,json=minimumImprovement,proto3" json:"minimum_improvement,omitempty"` + MaximumRetransmissionIncrease float64 `protobuf:"fixed64,7,opt,name=maximum_retransmission_increase,json=maximumRetransmissionIncrease,proto3" json:"maximum_retransmission_increase,omitempty"` + MaximumRttIncrease float64 `protobuf:"fixed64,8,opt,name=maximum_rtt_increase,json=maximumRttIncrease,proto3" json:"maximum_rtt_increase,omitempty"` + MaximumEvidenceAge *durationpb.Duration `protobuf:"bytes,9,opt,name=maximum_evidence_age,json=maximumEvidenceAge,proto3" json:"maximum_evidence_age,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExperimentPolicy) Reset() { + *x = ExperimentPolicy{} + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExperimentPolicy) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExperimentPolicy) ProtoMessage() {} + +func (x *ExperimentPolicy) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExperimentPolicy.ProtoReflect.Descriptor instead. +func (*ExperimentPolicy) Descriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{15} +} + +func (x *ExperimentPolicy) GetMinimumObservation() *durationpb.Duration { + if x != nil { + return x.MinimumObservation + } + return nil +} + +func (x *ExperimentPolicy) GetMaximumPhase() *durationpb.Duration { + if x != nil { + return x.MaximumPhase + } + return nil +} + +func (x *ExperimentPolicy) GetMinimumSocketsPerHost() uint64 { + if x != nil { + return x.MinimumSocketsPerHost + } + return 0 +} + +func (x *ExperimentPolicy) GetMinimumBytesPerHost() uint64 { + if x != nil { + return x.MinimumBytesPerHost + } + return 0 +} + +func (x *ExperimentPolicy) GetMinimumTreatedHosts() uint32 { + if x != nil { + return x.MinimumTreatedHosts + } + return 0 +} + +func (x *ExperimentPolicy) GetMinimumImprovement() float64 { + if x != nil { + return x.MinimumImprovement + } + return 0 +} + +func (x *ExperimentPolicy) GetMaximumRetransmissionIncrease() float64 { + if x != nil { + return x.MaximumRetransmissionIncrease + } + return 0 +} + +func (x *ExperimentPolicy) GetMaximumRttIncrease() float64 { + if x != nil { + return x.MaximumRttIncrease + } + return 0 +} + +func (x *ExperimentPolicy) GetMaximumEvidenceAge() *durationpb.Duration { + if x != nil { + return x.MaximumEvidenceAge + } + return nil +} + +type Phase struct { + state protoimpl.MessageState `protogen:"open.v1"` + TargetHosts uint32 `protobuf:"varint,1,opt,name=target_hosts,json=targetHosts,proto3" json:"target_hosts,omitempty"` + IssuedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=issued_at,json=issuedAt,proto3" json:"issued_at,omitempty"` + ObservationStartedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=observation_started_at,json=observationStartedAt,proto3" json:"observation_started_at,omitempty"` + EvaluatedAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=evaluated_at,json=evaluatedAt,proto3" json:"evaluated_at,omitempty"` + Evidence *Evidence `protobuf:"bytes,5,opt,name=evidence,proto3" json:"evidence,omitempty"` + Outcome string `protobuf:"bytes,6,opt,name=outcome,proto3" json:"outcome,omitempty"` + Reviewer string `protobuf:"bytes,7,opt,name=reviewer,proto3" json:"reviewer,omitempty"` + Reason string `protobuf:"bytes,8,opt,name=reason,proto3" json:"reason,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Phase) Reset() { + *x = Phase{} + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Phase) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Phase) ProtoMessage() {} + +func (x *Phase) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Phase.ProtoReflect.Descriptor instead. +func (*Phase) Descriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{16} +} + +func (x *Phase) GetTargetHosts() uint32 { + if x != nil { + return x.TargetHosts + } + return 0 +} + +func (x *Phase) GetIssuedAt() *timestamppb.Timestamp { + if x != nil { + return x.IssuedAt + } + return nil +} + +func (x *Phase) GetObservationStartedAt() *timestamppb.Timestamp { + if x != nil { + return x.ObservationStartedAt + } + return nil +} + +func (x *Phase) GetEvaluatedAt() *timestamppb.Timestamp { + if x != nil { + return x.EvaluatedAt + } + return nil +} + +func (x *Phase) GetEvidence() *Evidence { + if x != nil { + return x.Evidence + } + return nil +} + +func (x *Phase) GetOutcome() string { + if x != nil { + return x.Outcome + } + return "" +} + +func (x *Phase) GetReviewer() string { + if x != nil { + return x.Reviewer + } + return "" +} + +func (x *Phase) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +type Experiment struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Location string `protobuf:"bytes,2,opt,name=location,proto3" json:"location,omitempty"` + Hypothesis string `protobuf:"bytes,3,opt,name=hypothesis,proto3" json:"hypothesis,omitempty"` + Treatment *Treatment `protobuf:"bytes,4,opt,name=treatment,proto3" json:"treatment,omitempty"` + Policy *ExperimentPolicy `protobuf:"bytes,5,opt,name=policy,proto3" json:"policy,omitempty"` + Baseline *Evidence `protobuf:"bytes,6,opt,name=baseline,proto3" json:"baseline,omitempty"` + CohortOrder []string `protobuf:"bytes,7,rep,name=cohort_order,json=cohortOrder,proto3" json:"cohort_order,omitempty"` + Phases []*Phase `protobuf:"bytes,8,rep,name=phases,proto3" json:"phases,omitempty"` + Status ExperimentStatus `protobuf:"varint,9,opt,name=status,proto3,enum=xtcp.controller.v1.ExperimentStatus" json:"status,omitempty"` + Revision uint64 `protobuf:"varint,10,opt,name=revision,proto3" json:"revision,omitempty"` + ProposedAt *timestamppb.Timestamp `protobuf:"bytes,11,opt,name=proposed_at,json=proposedAt,proto3" json:"proposed_at,omitempty"` + LatestEvidence *Evidence `protobuf:"bytes,12,opt,name=latest_evidence,json=latestEvidence,proto3" json:"latest_evidence,omitempty"` + LatestOutcome string `protobuf:"bytes,13,opt,name=latest_outcome,json=latestOutcome,proto3" json:"latest_outcome,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Experiment) Reset() { + *x = Experiment{} + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Experiment) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Experiment) ProtoMessage() {} + +func (x *Experiment) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Experiment.ProtoReflect.Descriptor instead. +func (*Experiment) Descriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{17} +} + +func (x *Experiment) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Experiment) GetLocation() string { + if x != nil { + return x.Location + } + return "" +} + +func (x *Experiment) GetHypothesis() string { + if x != nil { + return x.Hypothesis + } + return "" +} + +func (x *Experiment) GetTreatment() *Treatment { + if x != nil { + return x.Treatment + } + return nil +} + +func (x *Experiment) GetPolicy() *ExperimentPolicy { + if x != nil { + return x.Policy + } + return nil +} + +func (x *Experiment) GetBaseline() *Evidence { + if x != nil { + return x.Baseline + } + return nil +} + +func (x *Experiment) GetCohortOrder() []string { + if x != nil { + return x.CohortOrder + } + return nil +} + +func (x *Experiment) GetPhases() []*Phase { + if x != nil { + return x.Phases + } + return nil +} + +func (x *Experiment) GetStatus() ExperimentStatus { + if x != nil { + return x.Status + } + return ExperimentStatus_EXPERIMENT_STATUS_UNSPECIFIED +} + +func (x *Experiment) GetRevision() uint64 { + if x != nil { + return x.Revision + } + return 0 +} + +func (x *Experiment) GetProposedAt() *timestamppb.Timestamp { + if x != nil { + return x.ProposedAt + } + return nil +} + +func (x *Experiment) GetLatestEvidence() *Evidence { + if x != nil { + return x.LatestEvidence + } + return nil +} + +func (x *Experiment) GetLatestOutcome() string { + if x != nil { + return x.LatestOutcome + } + return "" +} + +type ProposeRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Experiment *Experiment `protobuf:"bytes,1,opt,name=experiment,proto3" json:"experiment,omitempty"` + RequestId string `protobuf:"bytes,2,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProposeRequest) Reset() { + *x = ProposeRequest{} + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProposeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProposeRequest) ProtoMessage() {} + +func (x *ProposeRequest) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProposeRequest.ProtoReflect.Descriptor instead. +func (*ProposeRequest) Descriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{18} +} + +func (x *ProposeRequest) GetExperiment() *Experiment { + if x != nil { + return x.Experiment + } + return nil +} + +func (x *ProposeRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +type ProposeResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Experiment *Experiment `protobuf:"bytes,1,opt,name=experiment,proto3" json:"experiment,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProposeResponse) Reset() { + *x = ProposeResponse{} + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProposeResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProposeResponse) ProtoMessage() {} + +func (x *ProposeResponse) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProposeResponse.ProtoReflect.Descriptor instead. +func (*ProposeResponse) Descriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{19} +} + +func (x *ProposeResponse) GetExperiment() *Experiment { + if x != nil { + return x.Experiment + } + return nil +} + +type AnalyzeRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ExperimentId string `protobuf:"bytes,1,opt,name=experiment_id,json=experimentId,proto3" json:"experiment_id,omitempty"` + ExpectedRevision uint64 `protobuf:"varint,2,opt,name=expected_revision,json=expectedRevision,proto3" json:"expected_revision,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AnalyzeRequest) Reset() { + *x = AnalyzeRequest{} + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AnalyzeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AnalyzeRequest) ProtoMessage() {} + +func (x *AnalyzeRequest) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[20] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AnalyzeRequest.ProtoReflect.Descriptor instead. +func (*AnalyzeRequest) Descriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{20} +} + +func (x *AnalyzeRequest) GetExperimentId() string { + if x != nil { + return x.ExperimentId + } + return "" +} + +func (x *AnalyzeRequest) GetExpectedRevision() uint64 { + if x != nil { + return x.ExpectedRevision + } + return 0 +} + +type AnalyzeResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Evidence *Evidence `protobuf:"bytes,1,opt,name=evidence,proto3" json:"evidence,omitempty"` + Outcome string `protobuf:"bytes,2,opt,name=outcome,proto3" json:"outcome,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AnalyzeResponse) Reset() { + *x = AnalyzeResponse{} + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AnalyzeResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AnalyzeResponse) ProtoMessage() {} + +func (x *AnalyzeResponse) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[21] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AnalyzeResponse.ProtoReflect.Descriptor instead. +func (*AnalyzeResponse) Descriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{21} +} + +func (x *AnalyzeResponse) GetEvidence() *Evidence { + if x != nil { + return x.Evidence + } + return nil +} + +func (x *AnalyzeResponse) GetOutcome() string { + if x != nil { + return x.Outcome + } + return "" +} + +type ReviewRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ExperimentId string `protobuf:"bytes,1,opt,name=experiment_id,json=experimentId,proto3" json:"experiment_id,omitempty"` + ExpectedRevision uint64 `protobuf:"varint,2,opt,name=expected_revision,json=expectedRevision,proto3" json:"expected_revision,omitempty"` + Approve bool `protobuf:"varint,3,opt,name=approve,proto3" json:"approve,omitempty"` + Reason string `protobuf:"bytes,4,opt,name=reason,proto3" json:"reason,omitempty"` + RequestId string `protobuf:"bytes,5,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + // Initial review uses baseline; subsequent reviews require fresh evidence. + Evidence *Evidence `protobuf:"bytes,6,opt,name=evidence,proto3" json:"evidence,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ReviewRequest) Reset() { + *x = ReviewRequest{} + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ReviewRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReviewRequest) ProtoMessage() {} + +func (x *ReviewRequest) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[22] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReviewRequest.ProtoReflect.Descriptor instead. +func (*ReviewRequest) Descriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{22} +} + +func (x *ReviewRequest) GetExperimentId() string { + if x != nil { + return x.ExperimentId + } + return "" +} + +func (x *ReviewRequest) GetExpectedRevision() uint64 { + if x != nil { + return x.ExpectedRevision + } + return 0 +} + +func (x *ReviewRequest) GetApprove() bool { + if x != nil { + return x.Approve + } + return false +} + +func (x *ReviewRequest) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *ReviewRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *ReviewRequest) GetEvidence() *Evidence { + if x != nil { + return x.Evidence + } + return nil +} + +type ReviewResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Experiment *Experiment `protobuf:"bytes,1,opt,name=experiment,proto3" json:"experiment,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ReviewResponse) Reset() { + *x = ReviewResponse{} + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ReviewResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReviewResponse) ProtoMessage() {} + +func (x *ReviewResponse) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[23] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReviewResponse.ProtoReflect.Descriptor instead. +func (*ReviewResponse) Descriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{23} +} + +func (x *ReviewResponse) GetExperiment() *Experiment { + if x != nil { + return x.Experiment + } + return nil +} + +type ControlRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Exactly one selector, or fleet=true, is required. + ExperimentId string `protobuf:"bytes,1,opt,name=experiment_id,json=experimentId,proto3" json:"experiment_id,omitempty"` + AgentId string `protobuf:"bytes,2,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` + Location string `protobuf:"bytes,3,opt,name=location,proto3" json:"location,omitempty"` + Fleet bool `protobuf:"varint,4,opt,name=fleet,proto3" json:"fleet,omitempty"` + Action ControlAction `protobuf:"varint,5,opt,name=action,proto3,enum=xtcp.controller.v1.ControlAction" json:"action,omitempty"` + Reason string `protobuf:"bytes,6,opt,name=reason,proto3" json:"reason,omitempty"` + RequestId string `protobuf:"bytes,7,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ControlRequest) Reset() { + *x = ControlRequest{} + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ControlRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ControlRequest) ProtoMessage() {} + +func (x *ControlRequest) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[24] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ControlRequest.ProtoReflect.Descriptor instead. +func (*ControlRequest) Descriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{24} +} + +func (x *ControlRequest) GetExperimentId() string { + if x != nil { + return x.ExperimentId + } + return "" +} + +func (x *ControlRequest) GetAgentId() string { + if x != nil { + return x.AgentId + } + return "" +} + +func (x *ControlRequest) GetLocation() string { + if x != nil { + return x.Location + } + return "" +} + +func (x *ControlRequest) GetFleet() bool { + if x != nil { + return x.Fleet + } + return false +} + +func (x *ControlRequest) GetAction() ControlAction { + if x != nil { + return x.Action + } + return ControlAction_CONTROL_ACTION_UNSPECIFIED +} + +func (x *ControlRequest) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *ControlRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +type ControlResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + AffectedAgents uint32 `protobuf:"varint,1,opt,name=affected_agents,json=affectedAgents,proto3" json:"affected_agents,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ControlResponse) Reset() { + *x = ControlResponse{} + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ControlResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ControlResponse) ProtoMessage() {} + +func (x *ControlResponse) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[25] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ControlResponse.ProtoReflect.Descriptor instead. +func (*ControlResponse) Descriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{25} +} + +func (x *ControlResponse) GetAffectedAgents() uint32 { + if x != nil { + return x.AffectedAgents + } + return 0 +} + +type InspectRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ExperimentId string `protobuf:"bytes,1,opt,name=experiment_id,json=experimentId,proto3" json:"experiment_id,omitempty"` + AgentId string `protobuf:"bytes,2,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InspectRequest) Reset() { + *x = InspectRequest{} + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InspectRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InspectRequest) ProtoMessage() {} + +func (x *InspectRequest) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[26] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InspectRequest.ProtoReflect.Descriptor instead. +func (*InspectRequest) Descriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{26} +} + +func (x *InspectRequest) GetExperimentId() string { + if x != nil { + return x.ExperimentId + } + return "" +} + +func (x *InspectRequest) GetAgentId() string { + if x != nil { + return x.AgentId + } + return "" +} + +type InspectResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Experiments []*Experiment `protobuf:"bytes,1,rep,name=experiments,proto3" json:"experiments,omitempty"` + Desired []*DesiredState `protobuf:"bytes,2,rep,name=desired,proto3" json:"desired,omitempty"` + Observed []*CheckInRequest `protobuf:"bytes,3,rep,name=observed,proto3" json:"observed,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InspectResponse) Reset() { + *x = InspectResponse{} + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InspectResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InspectResponse) ProtoMessage() {} + +func (x *InspectResponse) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_controller_v1_controller_proto_msgTypes[27] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InspectResponse.ProtoReflect.Descriptor instead. +func (*InspectResponse) Descriptor() ([]byte, []int) { + return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{27} +} + +func (x *InspectResponse) GetExperiments() []*Experiment { + if x != nil { + return x.Experiments + } + return nil +} + +func (x *InspectResponse) GetDesired() []*DesiredState { + if x != nil { + return x.Desired + } + return nil +} + +func (x *InspectResponse) GetObserved() []*CheckInRequest { + if x != nil { + return x.Observed + } + return nil +} + +var File_xtcp_controller_v1_controller_proto protoreflect.FileDescriptor + +const file_xtcp_controller_v1_controller_proto_rawDesc = "" + + "\n" + + "#xtcp/controller/v1/controller.proto\x12\x12xtcp.controller.v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1egoogle/protobuf/duration.proto\"\xb7\x03\n" + + "\vTCPSettings\x122\n" + + "\x12congestion_control\x18\x01 \x01(\tH\x00R\x11congestionControl\x88\x01\x01\x12'\n" + + "\x0fcongestion_lock\x18\x02 \x01(\bR\x0econgestionLock\x12!\n" + + "\n" + + "rto_min_ms\x18\x03 \x01(\rH\x01R\brtoMinMs\x88\x01\x01\x12?\n" + + "\x19initial_congestion_window\x18\x04 \x01(\rH\x02R\x17initialCongestionWindow\x88\x01\x01\x129\n" + + "\x16initial_receive_window\x18\x05 \x01(\rH\x03R\x14initialReceiveWindow\x88\x01\x01\x12 \n" + + "\tquick_ack\x18\x06 \x01(\bH\x04R\bquickAck\x88\x01\x01\x12\x15\n" + + "\x03ecn\x18\a \x01(\bH\x05R\x03ecn\x88\x01\x01B\x15\n" + + "\x13_congestion_controlB\r\n" + + "\v_rto_min_msB\x1c\n" + + "\x1a_initial_congestion_windowB\x19\n" + + "\x17_initial_receive_windowB\f\n" + + "\n" + + "_quick_ackB\x06\n" + + "\x04_ecn\"\xc6\x01\n" + + "\tTreatment\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1a\n" + + "\brevision\x18\x02 \x01(\x04R\brevision\x12 \n" + + "\vdestination\x18\x03 \x01(\tR\vdestination\x12;\n" + + "\bsettings\x18\x04 \x01(\v2\x1f.xtcp.controller.v1.TCPSettingsR\bsettings\x12.\n" + + "\x13ecn_fabric_verified\x18\x05 \x01(\bR\x11ecnFabricVerified\"\xdd\x02\n" + + "\n" + + "Assignment\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12#\n" + + "\rexperiment_id\x18\x02 \x01(\tR\fexperimentId\x12;\n" + + "\ttreatment\x18\x03 \x01(\v2\x1d.xtcp.controller.v1.TreatmentR\ttreatment\x126\n" + + "\x04mode\x18\x04 \x01(\x0e2\".xtcp.controller.v1.AssignmentModeR\x04mode\x12D\n" + + "\x10lease_expires_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\x0eleaseExpiresAt\x12?\n" + + "\vpredecessor\x18\x06 \x01(\v2\x1d.xtcp.controller.v1.TreatmentR\vpredecessor\x12\x1e\n" + + "\n" + + "generation\x18\a \x01(\x04R\n" + + "generation\"\xde\x01\n" + + "\fDesiredState\x12\x19\n" + + "\bagent_id\x18\x01 \x01(\tR\aagentId\x12\x1a\n" + + "\brevision\x18\x02 \x01(\x04R\brevision\x12\x1c\n" + + "\tsuspended\x18\x03 \x01(\bR\tsuspended\x12@\n" + + "\vassignments\x18\x04 \x03(\v2\x1e.xtcp.controller.v1.AssignmentR\vassignments\x127\n" + + "\tissued_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\bissuedAt\"\xde\x01\n" + + "\fCapabilities\x12\x17\n" + + "\aboot_id\x18\x01 \x01(\tR\x06bootId\x12%\n" + + "\x0ekernel_release\x18\x02 \x01(\tR\rkernelRelease\x123\n" + + "\x15congestion_algorithms\x18\x03 \x03(\tR\x14congestionAlgorithms\x12.\n" + + "\x13simple_host_routing\x18\x04 \x01(\bR\x11simpleHostRouting\x12)\n" + + "\x10actuator_enabled\x18\x05 \x01(\bR\x0factuatorEnabled\"\xfa\x04\n" + + "\x11ApplicationRecord\x12>\n" + + "\n" + + "assignment\x18\x01 \x01(\v2\x1e.xtcp.controller.v1.AssignmentR\n" + + "assignment\x12)\n" + + "\x10desired_revision\x18\x02 \x01(\x04R\x0fdesiredRevision\x127\n" + + "\x06status\x18\x03 \x01(\x0e2\x1f.xtcp.controller.v1.ApplyStatusR\x06status\x12;\n" + + "\vreceived_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\n" + + "receivedAt\x12=\n" + + "\fattempted_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\vattemptedAt\x129\n" + + "\n" + + "applied_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\tappliedAt\x12;\n" + + "\vverified_at\x18\a \x01(\v2\x1a.google.protobuf.TimestampR\n" + + "verifiedAt\x129\n" + + "\n" + + "removed_at\x18\b \x01(\v2\x1a.google.protobuf.TimestampR\tremovedAt\x12\x14\n" + + "\x05error\x18\t \x01(\tR\x05error\x12'\n" + + "\x0finstalled_route\x18\n" + + " \x01(\fR\x0einstalledRoute\x12*\n" + + "\x11installed_boot_id\x18\v \x01(\tR\x0finstalledBootId\x12'\n" + + "\x0fexpired_latched\x18\f \x01(\bR\x0eexpiredLatched\"\x9e\x02\n" + + "\n" + + "LocalState\x12:\n" + + "\adesired\x18\x01 \x01(\v2 .xtcp.controller.v1.DesiredStateR\adesired\x12+\n" + + "\x11locally_suspended\x18\x02 \x01(\bR\x10locallySuspended\x12+\n" + + "\x11suspension_reason\x18\x03 \x01(\tR\x10suspensionReason\x12?\n" + + "\arecords\x18\x04 \x03(\v2%.xtcp.controller.v1.ApplicationRecordR\arecords\x129\n" + + "\n" + + "updated_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\"\xa6\x02\n" + + "\x0eCheckInRequest\x12\x19\n" + + "\bagent_id\x18\x01 \x01(\tR\aagentId\x12\x1a\n" + + "\blocation\x18\x02 \x01(\tR\blocation\x12D\n" + + "\fcapabilities\x18\x03 \x01(\v2 .xtcp.controller.v1.CapabilitiesR\fcapabilities\x12)\n" + + "\x10desired_revision\x18\x04 \x01(\x04R\x0fdesiredRevision\x12?\n" + + "\arecords\x18\x05 \x03(\v2%.xtcp.controller.v1.ApplicationRecordR\arecords\x12+\n" + + "\x11locally_suspended\x18\x06 \x01(\bR\x10locallySuspended\"M\n" + + "\x0fCheckInResponse\x12:\n" + + "\adesired\x18\x01 \x01(\v2 .xtcp.controller.v1.DesiredStateR\adesired\"3\n" + + "\x16GetDesiredStateRequest\x12\x19\n" + + "\bagent_id\x18\x01 \x01(\tR\aagentId\"U\n" + + "\x17GetDesiredStateResponse\x12:\n" + + "\adesired\x18\x01 \x01(\v2 .xtcp.controller.v1.DesiredStateR\adesired\"\\\n" + + "\x18WatchDesiredStateRequest\x12\x19\n" + + "\bagent_id\x18\x01 \x01(\tR\aagentId\x12%\n" + + "\x0eafter_revision\x18\x02 \x01(\x04R\rafterRevision\"7\n" + + "\x19WatchDesiredStateResponse\x12\x1a\n" + + "\brevision\x18\x01 \x01(\x04R\brevision\"\xae\x02\n" + + "\fHostEvidence\x12\x19\n" + + "\bagent_id\x18\x01 \x01(\tR\aagentId\x12\x18\n" + + "\asockets\x18\x02 \x01(\x04R\asockets\x12\x1f\n" + + "\vbytes_acked\x18\x03 \x01(\x04R\n" + + "bytesAcked\x12\x1f\n" + + "\vgoodput_bps\x18\x04 \x01(\x01R\n" + + "goodputBps\x121\n" + + "\x14retransmission_ratio\x18\x05 \x01(\x01R\x13retransmissionRatio\x12\x1c\n" + + "\n" + + "rtt_p95_ms\x18\x06 \x01(\x01R\brttP95Ms\x12'\n" + + "\x0fexposed_sockets\x18\a \x01(\x04R\x0eexposedSockets\x12-\n" + + "\x12collection_healthy\x18\b \x01(\bR\x11collectionHealthy\"\x86\x03\n" + + "\bEvidence\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12;\n" + + "\vanalyzed_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\n" + + "analyzedAt\x12=\n" + + "\fwindow_start\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\vwindowStart\x129\n" + + "\n" + + "window_end\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\twindowEnd\x12#\n" + + "\rquery_version\x18\x05 \x01(\tR\fqueryVersion\x12-\n" + + "\x12enrichment_version\x18\x06 \x01(\tR\x11enrichmentVersion\x126\n" + + "\x05hosts\x18\a \x03(\v2 .xtcp.controller.v1.HostEvidenceR\x05hosts\x12'\n" + + "\x0fartifact_sha256\x18\b \x01(\tR\x0eartifactSha256\"\xb8\x04\n" + + "\x10ExperimentPolicy\x12J\n" + + "\x13minimum_observation\x18\x01 \x01(\v2\x19.google.protobuf.DurationR\x12minimumObservation\x12>\n" + + "\rmaximum_phase\x18\x02 \x01(\v2\x19.google.protobuf.DurationR\fmaximumPhase\x127\n" + + "\x18minimum_sockets_per_host\x18\x03 \x01(\x04R\x15minimumSocketsPerHost\x123\n" + + "\x16minimum_bytes_per_host\x18\x04 \x01(\x04R\x13minimumBytesPerHost\x122\n" + + "\x15minimum_treated_hosts\x18\x05 \x01(\rR\x13minimumTreatedHosts\x12/\n" + + "\x13minimum_improvement\x18\x06 \x01(\x01R\x12minimumImprovement\x12F\n" + + "\x1fmaximum_retransmission_increase\x18\a \x01(\x01R\x1dmaximumRetransmissionIncrease\x120\n" + + "\x14maximum_rtt_increase\x18\b \x01(\x01R\x12maximumRttIncrease\x12K\n" + + "\x14maximum_evidence_age\x18\t \x01(\v2\x19.google.protobuf.DurationR\x12maximumEvidenceAge\"\xfc\x02\n" + + "\x05Phase\x12!\n" + + "\ftarget_hosts\x18\x01 \x01(\rR\vtargetHosts\x127\n" + + "\tissued_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\bissuedAt\x12P\n" + + "\x16observation_started_at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\x14observationStartedAt\x12=\n" + + "\fevaluated_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\vevaluatedAt\x128\n" + + "\bevidence\x18\x05 \x01(\v2\x1c.xtcp.controller.v1.EvidenceR\bevidence\x12\x18\n" + + "\aoutcome\x18\x06 \x01(\tR\aoutcome\x12\x1a\n" + + "\breviewer\x18\a \x01(\tR\breviewer\x12\x16\n" + + "\x06reason\x18\b \x01(\tR\x06reason\"\xe8\x04\n" + + "\n" + + "Experiment\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1a\n" + + "\blocation\x18\x02 \x01(\tR\blocation\x12\x1e\n" + + "\n" + + "hypothesis\x18\x03 \x01(\tR\n" + + "hypothesis\x12;\n" + + "\ttreatment\x18\x04 \x01(\v2\x1d.xtcp.controller.v1.TreatmentR\ttreatment\x12<\n" + + "\x06policy\x18\x05 \x01(\v2$.xtcp.controller.v1.ExperimentPolicyR\x06policy\x128\n" + + "\bbaseline\x18\x06 \x01(\v2\x1c.xtcp.controller.v1.EvidenceR\bbaseline\x12!\n" + + "\fcohort_order\x18\a \x03(\tR\vcohortOrder\x121\n" + + "\x06phases\x18\b \x03(\v2\x19.xtcp.controller.v1.PhaseR\x06phases\x12<\n" + + "\x06status\x18\t \x01(\x0e2$.xtcp.controller.v1.ExperimentStatusR\x06status\x12\x1a\n" + + "\brevision\x18\n" + + " \x01(\x04R\brevision\x12;\n" + + "\vproposed_at\x18\v \x01(\v2\x1a.google.protobuf.TimestampR\n" + + "proposedAt\x12E\n" + + "\x0flatest_evidence\x18\f \x01(\v2\x1c.xtcp.controller.v1.EvidenceR\x0elatestEvidence\x12%\n" + + "\x0elatest_outcome\x18\r \x01(\tR\rlatestOutcome\"o\n" + + "\x0eProposeRequest\x12>\n" + + "\n" + + "experiment\x18\x01 \x01(\v2\x1e.xtcp.controller.v1.ExperimentR\n" + + "experiment\x12\x1d\n" + + "\n" + + "request_id\x18\x02 \x01(\tR\trequestId\"Q\n" + + "\x0fProposeResponse\x12>\n" + + "\n" + + "experiment\x18\x01 \x01(\v2\x1e.xtcp.controller.v1.ExperimentR\n" + + "experiment\"b\n" + + "\x0eAnalyzeRequest\x12#\n" + + "\rexperiment_id\x18\x01 \x01(\tR\fexperimentId\x12+\n" + + "\x11expected_revision\x18\x02 \x01(\x04R\x10expectedRevision\"e\n" + + "\x0fAnalyzeResponse\x128\n" + + "\bevidence\x18\x01 \x01(\v2\x1c.xtcp.controller.v1.EvidenceR\bevidence\x12\x18\n" + + "\aoutcome\x18\x02 \x01(\tR\aoutcome\"\xec\x01\n" + + "\rReviewRequest\x12#\n" + + "\rexperiment_id\x18\x01 \x01(\tR\fexperimentId\x12+\n" + + "\x11expected_revision\x18\x02 \x01(\x04R\x10expectedRevision\x12\x18\n" + + "\aapprove\x18\x03 \x01(\bR\aapprove\x12\x16\n" + + "\x06reason\x18\x04 \x01(\tR\x06reason\x12\x1d\n" + + "\n" + + "request_id\x18\x05 \x01(\tR\trequestId\x128\n" + + "\bevidence\x18\x06 \x01(\v2\x1c.xtcp.controller.v1.EvidenceR\bevidence\"P\n" + + "\x0eReviewResponse\x12>\n" + + "\n" + + "experiment\x18\x01 \x01(\v2\x1e.xtcp.controller.v1.ExperimentR\n" + + "experiment\"\xf4\x01\n" + + "\x0eControlRequest\x12#\n" + + "\rexperiment_id\x18\x01 \x01(\tR\fexperimentId\x12\x19\n" + + "\bagent_id\x18\x02 \x01(\tR\aagentId\x12\x1a\n" + + "\blocation\x18\x03 \x01(\tR\blocation\x12\x14\n" + + "\x05fleet\x18\x04 \x01(\bR\x05fleet\x129\n" + + "\x06action\x18\x05 \x01(\x0e2!.xtcp.controller.v1.ControlActionR\x06action\x12\x16\n" + + "\x06reason\x18\x06 \x01(\tR\x06reason\x12\x1d\n" + + "\n" + + "request_id\x18\a \x01(\tR\trequestId\":\n" + + "\x0fControlResponse\x12'\n" + + "\x0faffected_agents\x18\x01 \x01(\rR\x0eaffectedAgents\"P\n" + + "\x0eInspectRequest\x12#\n" + + "\rexperiment_id\x18\x01 \x01(\tR\fexperimentId\x12\x19\n" + + "\bagent_id\x18\x02 \x01(\tR\aagentId\"\xcf\x01\n" + + "\x0fInspectResponse\x12@\n" + + "\vexperiments\x18\x01 \x03(\v2\x1e.xtcp.controller.v1.ExperimentR\vexperiments\x12:\n" + + "\adesired\x18\x02 \x03(\v2 .xtcp.controller.v1.DesiredStateR\adesired\x12>\n" + + "\bobserved\x18\x03 \x03(\v2\".xtcp.controller.v1.CheckInRequestR\bobserved*\xa4\x01\n" + + "\x0eAssignmentMode\x12\x1f\n" + + "\x1bASSIGNMENT_MODE_UNSPECIFIED\x10\x00\x12\x19\n" + + "\x15ASSIGNMENT_MODE_TRIAL\x10\x01\x12\x1a\n" + + "\x16ASSIGNMENT_MODE_STABLE\x10\x02\x12\x1d\n" + + "\x19ASSIGNMENT_MODE_SUSPENDED\x10\x03\x12\x1b\n" + + "\x17ASSIGNMENT_MODE_RETIRED\x10\x04*\xad\x01\n" + + "\vApplyStatus\x12\x1c\n" + + "\x18APPLY_STATUS_UNSPECIFIED\x10\x00\x12\x18\n" + + "\x14APPLY_STATUS_PENDING\x10\x01\x12\x19\n" + + "\x15APPLY_STATUS_VERIFIED\x10\x02\x12\x18\n" + + "\x14APPLY_STATUS_REMOVED\x10\x03\x12\x18\n" + + "\x14APPLY_STATUS_EXPIRED\x10\x04\x12\x17\n" + + "\x13APPLY_STATUS_FAILED\x10\x05*\x95\x02\n" + + "\x10ExperimentStatus\x12!\n" + + "\x1dEXPERIMENT_STATUS_UNSPECIFIED\x10\x00\x12\x1e\n" + + "\x1aEXPERIMENT_STATUS_PROPOSED\x10\x01\x12\x1d\n" + + "\x19EXPERIMENT_STATUS_RUNNING\x10\x02\x12\x1c\n" + + "\x18EXPERIMENT_STATUS_PAUSED\x10\x03\x12\x1f\n" + + "\x1bEXPERIMENT_STATUS_SUSPENDED\x10\x04\x12\x1e\n" + + "\x1aEXPERIMENT_STATUS_COMPLETE\x10\x05\x12\x1d\n" + + "\x19EXPERIMENT_STATUS_RETIRED\x10\x06\x12!\n" + + "\x1dEXPERIMENT_STATUS_ROLLED_BACK\x10\a*\xb8\x01\n" + + "\rControlAction\x12\x1e\n" + + "\x1aCONTROL_ACTION_UNSPECIFIED\x10\x00\x12\x18\n" + + "\x14CONTROL_ACTION_PAUSE\x10\x01\x12\x1a\n" + + "\x16CONTROL_ACTION_SUSPEND\x10\x02\x12\x19\n" + + "\x15CONTROL_ACTION_RESUME\x10\x03\x12\x1b\n" + + "\x17CONTROL_ACTION_ROLLBACK\x10\x04\x12\x19\n" + + "\x15CONTROL_ACTION_RETIRE\x10\x052\xc9\x02\n" + + "\x13AgentControlService\x12R\n" + + "\aCheckIn\x12\".xtcp.controller.v1.CheckInRequest\x1a#.xtcp.controller.v1.CheckInResponse\x12j\n" + + "\x0fGetDesiredState\x12*.xtcp.controller.v1.GetDesiredStateRequest\x1a+.xtcp.controller.v1.GetDesiredStateResponse\x12r\n" + + "\x11WatchDesiredState\x12,.xtcp.controller.v1.WatchDesiredStateRequest\x1a-.xtcp.controller.v1.WatchDesiredStateResponse0\x012\xb2\x03\n" + + "\x0fOperatorService\x12R\n" + + "\aPropose\x12\".xtcp.controller.v1.ProposeRequest\x1a#.xtcp.controller.v1.ProposeResponse\x12R\n" + + "\aAnalyze\x12\".xtcp.controller.v1.AnalyzeRequest\x1a#.xtcp.controller.v1.AnalyzeResponse\x12O\n" + + "\x06Review\x12!.xtcp.controller.v1.ReviewRequest\x1a\".xtcp.controller.v1.ReviewResponse\x12R\n" + + "\aControl\x12\".xtcp.controller.v1.ControlRequest\x1a#.xtcp.controller.v1.ControlResponse\x12R\n" + + "\aInspect\x12\".xtcp.controller.v1.InspectRequest\x1a#.xtcp.controller.v1.InspectResponseB9Z7github.com/randomizedcoder/xtcp2/gen/go/xtcp_controllerb\x06proto3" + +var ( + file_xtcp_controller_v1_controller_proto_rawDescOnce sync.Once + file_xtcp_controller_v1_controller_proto_rawDescData []byte +) + +func file_xtcp_controller_v1_controller_proto_rawDescGZIP() []byte { + file_xtcp_controller_v1_controller_proto_rawDescOnce.Do(func() { + file_xtcp_controller_v1_controller_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_xtcp_controller_v1_controller_proto_rawDesc), len(file_xtcp_controller_v1_controller_proto_rawDesc))) + }) + return file_xtcp_controller_v1_controller_proto_rawDescData +} + +var file_xtcp_controller_v1_controller_proto_enumTypes = make([]protoimpl.EnumInfo, 4) +var file_xtcp_controller_v1_controller_proto_msgTypes = make([]protoimpl.MessageInfo, 28) +var file_xtcp_controller_v1_controller_proto_goTypes = []any{ + (AssignmentMode)(0), // 0: xtcp.controller.v1.AssignmentMode + (ApplyStatus)(0), // 1: xtcp.controller.v1.ApplyStatus + (ExperimentStatus)(0), // 2: xtcp.controller.v1.ExperimentStatus + (ControlAction)(0), // 3: xtcp.controller.v1.ControlAction + (*TCPSettings)(nil), // 4: xtcp.controller.v1.TCPSettings + (*Treatment)(nil), // 5: xtcp.controller.v1.Treatment + (*Assignment)(nil), // 6: xtcp.controller.v1.Assignment + (*DesiredState)(nil), // 7: xtcp.controller.v1.DesiredState + (*Capabilities)(nil), // 8: xtcp.controller.v1.Capabilities + (*ApplicationRecord)(nil), // 9: xtcp.controller.v1.ApplicationRecord + (*LocalState)(nil), // 10: xtcp.controller.v1.LocalState + (*CheckInRequest)(nil), // 11: xtcp.controller.v1.CheckInRequest + (*CheckInResponse)(nil), // 12: xtcp.controller.v1.CheckInResponse + (*GetDesiredStateRequest)(nil), // 13: xtcp.controller.v1.GetDesiredStateRequest + (*GetDesiredStateResponse)(nil), // 14: xtcp.controller.v1.GetDesiredStateResponse + (*WatchDesiredStateRequest)(nil), // 15: xtcp.controller.v1.WatchDesiredStateRequest + (*WatchDesiredStateResponse)(nil), // 16: xtcp.controller.v1.WatchDesiredStateResponse + (*HostEvidence)(nil), // 17: xtcp.controller.v1.HostEvidence + (*Evidence)(nil), // 18: xtcp.controller.v1.Evidence + (*ExperimentPolicy)(nil), // 19: xtcp.controller.v1.ExperimentPolicy + (*Phase)(nil), // 20: xtcp.controller.v1.Phase + (*Experiment)(nil), // 21: xtcp.controller.v1.Experiment + (*ProposeRequest)(nil), // 22: xtcp.controller.v1.ProposeRequest + (*ProposeResponse)(nil), // 23: xtcp.controller.v1.ProposeResponse + (*AnalyzeRequest)(nil), // 24: xtcp.controller.v1.AnalyzeRequest + (*AnalyzeResponse)(nil), // 25: xtcp.controller.v1.AnalyzeResponse + (*ReviewRequest)(nil), // 26: xtcp.controller.v1.ReviewRequest + (*ReviewResponse)(nil), // 27: xtcp.controller.v1.ReviewResponse + (*ControlRequest)(nil), // 28: xtcp.controller.v1.ControlRequest + (*ControlResponse)(nil), // 29: xtcp.controller.v1.ControlResponse + (*InspectRequest)(nil), // 30: xtcp.controller.v1.InspectRequest + (*InspectResponse)(nil), // 31: xtcp.controller.v1.InspectResponse + (*timestamppb.Timestamp)(nil), // 32: google.protobuf.Timestamp + (*durationpb.Duration)(nil), // 33: google.protobuf.Duration +} +var file_xtcp_controller_v1_controller_proto_depIdxs = []int32{ + 4, // 0: xtcp.controller.v1.Treatment.settings:type_name -> xtcp.controller.v1.TCPSettings + 5, // 1: xtcp.controller.v1.Assignment.treatment:type_name -> xtcp.controller.v1.Treatment + 0, // 2: xtcp.controller.v1.Assignment.mode:type_name -> xtcp.controller.v1.AssignmentMode + 32, // 3: xtcp.controller.v1.Assignment.lease_expires_at:type_name -> google.protobuf.Timestamp + 5, // 4: xtcp.controller.v1.Assignment.predecessor:type_name -> xtcp.controller.v1.Treatment + 6, // 5: xtcp.controller.v1.DesiredState.assignments:type_name -> xtcp.controller.v1.Assignment + 32, // 6: xtcp.controller.v1.DesiredState.issued_at:type_name -> google.protobuf.Timestamp + 6, // 7: xtcp.controller.v1.ApplicationRecord.assignment:type_name -> xtcp.controller.v1.Assignment + 1, // 8: xtcp.controller.v1.ApplicationRecord.status:type_name -> xtcp.controller.v1.ApplyStatus + 32, // 9: xtcp.controller.v1.ApplicationRecord.received_at:type_name -> google.protobuf.Timestamp + 32, // 10: xtcp.controller.v1.ApplicationRecord.attempted_at:type_name -> google.protobuf.Timestamp + 32, // 11: xtcp.controller.v1.ApplicationRecord.applied_at:type_name -> google.protobuf.Timestamp + 32, // 12: xtcp.controller.v1.ApplicationRecord.verified_at:type_name -> google.protobuf.Timestamp + 32, // 13: xtcp.controller.v1.ApplicationRecord.removed_at:type_name -> google.protobuf.Timestamp + 7, // 14: xtcp.controller.v1.LocalState.desired:type_name -> xtcp.controller.v1.DesiredState + 9, // 15: xtcp.controller.v1.LocalState.records:type_name -> xtcp.controller.v1.ApplicationRecord + 32, // 16: xtcp.controller.v1.LocalState.updated_at:type_name -> google.protobuf.Timestamp + 8, // 17: xtcp.controller.v1.CheckInRequest.capabilities:type_name -> xtcp.controller.v1.Capabilities + 9, // 18: xtcp.controller.v1.CheckInRequest.records:type_name -> xtcp.controller.v1.ApplicationRecord + 7, // 19: xtcp.controller.v1.CheckInResponse.desired:type_name -> xtcp.controller.v1.DesiredState + 7, // 20: xtcp.controller.v1.GetDesiredStateResponse.desired:type_name -> xtcp.controller.v1.DesiredState + 32, // 21: xtcp.controller.v1.Evidence.analyzed_at:type_name -> google.protobuf.Timestamp + 32, // 22: xtcp.controller.v1.Evidence.window_start:type_name -> google.protobuf.Timestamp + 32, // 23: xtcp.controller.v1.Evidence.window_end:type_name -> google.protobuf.Timestamp + 17, // 24: xtcp.controller.v1.Evidence.hosts:type_name -> xtcp.controller.v1.HostEvidence + 33, // 25: xtcp.controller.v1.ExperimentPolicy.minimum_observation:type_name -> google.protobuf.Duration + 33, // 26: xtcp.controller.v1.ExperimentPolicy.maximum_phase:type_name -> google.protobuf.Duration + 33, // 27: xtcp.controller.v1.ExperimentPolicy.maximum_evidence_age:type_name -> google.protobuf.Duration + 32, // 28: xtcp.controller.v1.Phase.issued_at:type_name -> google.protobuf.Timestamp + 32, // 29: xtcp.controller.v1.Phase.observation_started_at:type_name -> google.protobuf.Timestamp + 32, // 30: xtcp.controller.v1.Phase.evaluated_at:type_name -> google.protobuf.Timestamp + 18, // 31: xtcp.controller.v1.Phase.evidence:type_name -> xtcp.controller.v1.Evidence + 5, // 32: xtcp.controller.v1.Experiment.treatment:type_name -> xtcp.controller.v1.Treatment + 19, // 33: xtcp.controller.v1.Experiment.policy:type_name -> xtcp.controller.v1.ExperimentPolicy + 18, // 34: xtcp.controller.v1.Experiment.baseline:type_name -> xtcp.controller.v1.Evidence + 20, // 35: xtcp.controller.v1.Experiment.phases:type_name -> xtcp.controller.v1.Phase + 2, // 36: xtcp.controller.v1.Experiment.status:type_name -> xtcp.controller.v1.ExperimentStatus + 32, // 37: xtcp.controller.v1.Experiment.proposed_at:type_name -> google.protobuf.Timestamp + 18, // 38: xtcp.controller.v1.Experiment.latest_evidence:type_name -> xtcp.controller.v1.Evidence + 21, // 39: xtcp.controller.v1.ProposeRequest.experiment:type_name -> xtcp.controller.v1.Experiment + 21, // 40: xtcp.controller.v1.ProposeResponse.experiment:type_name -> xtcp.controller.v1.Experiment + 18, // 41: xtcp.controller.v1.AnalyzeResponse.evidence:type_name -> xtcp.controller.v1.Evidence + 18, // 42: xtcp.controller.v1.ReviewRequest.evidence:type_name -> xtcp.controller.v1.Evidence + 21, // 43: xtcp.controller.v1.ReviewResponse.experiment:type_name -> xtcp.controller.v1.Experiment + 3, // 44: xtcp.controller.v1.ControlRequest.action:type_name -> xtcp.controller.v1.ControlAction + 21, // 45: xtcp.controller.v1.InspectResponse.experiments:type_name -> xtcp.controller.v1.Experiment + 7, // 46: xtcp.controller.v1.InspectResponse.desired:type_name -> xtcp.controller.v1.DesiredState + 11, // 47: xtcp.controller.v1.InspectResponse.observed:type_name -> xtcp.controller.v1.CheckInRequest + 11, // 48: xtcp.controller.v1.AgentControlService.CheckIn:input_type -> xtcp.controller.v1.CheckInRequest + 13, // 49: xtcp.controller.v1.AgentControlService.GetDesiredState:input_type -> xtcp.controller.v1.GetDesiredStateRequest + 15, // 50: xtcp.controller.v1.AgentControlService.WatchDesiredState:input_type -> xtcp.controller.v1.WatchDesiredStateRequest + 22, // 51: xtcp.controller.v1.OperatorService.Propose:input_type -> xtcp.controller.v1.ProposeRequest + 24, // 52: xtcp.controller.v1.OperatorService.Analyze:input_type -> xtcp.controller.v1.AnalyzeRequest + 26, // 53: xtcp.controller.v1.OperatorService.Review:input_type -> xtcp.controller.v1.ReviewRequest + 28, // 54: xtcp.controller.v1.OperatorService.Control:input_type -> xtcp.controller.v1.ControlRequest + 30, // 55: xtcp.controller.v1.OperatorService.Inspect:input_type -> xtcp.controller.v1.InspectRequest + 12, // 56: xtcp.controller.v1.AgentControlService.CheckIn:output_type -> xtcp.controller.v1.CheckInResponse + 14, // 57: xtcp.controller.v1.AgentControlService.GetDesiredState:output_type -> xtcp.controller.v1.GetDesiredStateResponse + 16, // 58: xtcp.controller.v1.AgentControlService.WatchDesiredState:output_type -> xtcp.controller.v1.WatchDesiredStateResponse + 23, // 59: xtcp.controller.v1.OperatorService.Propose:output_type -> xtcp.controller.v1.ProposeResponse + 25, // 60: xtcp.controller.v1.OperatorService.Analyze:output_type -> xtcp.controller.v1.AnalyzeResponse + 27, // 61: xtcp.controller.v1.OperatorService.Review:output_type -> xtcp.controller.v1.ReviewResponse + 29, // 62: xtcp.controller.v1.OperatorService.Control:output_type -> xtcp.controller.v1.ControlResponse + 31, // 63: xtcp.controller.v1.OperatorService.Inspect:output_type -> xtcp.controller.v1.InspectResponse + 56, // [56:64] is the sub-list for method output_type + 48, // [48:56] is the sub-list for method input_type + 48, // [48:48] is the sub-list for extension type_name + 48, // [48:48] is the sub-list for extension extendee + 0, // [0:48] is the sub-list for field type_name +} + +func init() { file_xtcp_controller_v1_controller_proto_init() } +func file_xtcp_controller_v1_controller_proto_init() { + if File_xtcp_controller_v1_controller_proto != nil { + return + } + file_xtcp_controller_v1_controller_proto_msgTypes[0].OneofWrappers = []any{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_xtcp_controller_v1_controller_proto_rawDesc), len(file_xtcp_controller_v1_controller_proto_rawDesc)), + NumEnums: 4, + NumMessages: 28, + NumExtensions: 0, + NumServices: 2, + }, + GoTypes: file_xtcp_controller_v1_controller_proto_goTypes, + DependencyIndexes: file_xtcp_controller_v1_controller_proto_depIdxs, + EnumInfos: file_xtcp_controller_v1_controller_proto_enumTypes, + MessageInfos: file_xtcp_controller_v1_controller_proto_msgTypes, + }.Build() + File_xtcp_controller_v1_controller_proto = out.File + file_xtcp_controller_v1_controller_proto_goTypes = nil + file_xtcp_controller_v1_controller_proto_depIdxs = nil +} diff --git a/gen/go/xtcp_controller/controller_grpc.pb.go b/gen/go/xtcp_controller/controller_grpc.pb.go new file mode 100644 index 000000000..a222a138c --- /dev/null +++ b/gen/go/xtcp_controller/controller_grpc.pb.go @@ -0,0 +1,463 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.2 +// - protoc v7.36.2 +// source: xtcp/controller/v1/controller.proto + +package xtcp_controller + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + AgentControlService_CheckIn_FullMethodName = "/xtcp.controller.v1.AgentControlService/CheckIn" + AgentControlService_GetDesiredState_FullMethodName = "/xtcp.controller.v1.AgentControlService/GetDesiredState" + AgentControlService_WatchDesiredState_FullMethodName = "/xtcp.controller.v1.AgentControlService/WatchDesiredState" +) + +// AgentControlServiceClient is the client API for AgentControlService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// All inbound messages are validated by the controller application layer. +// IDs are bounded ASCII identifiers; address selectors are canonical host CIDRs. +// Limits, required fields and contextual constraints are in pkg/treatment/validate.go. +type AgentControlServiceClient interface { + CheckIn(ctx context.Context, in *CheckInRequest, opts ...grpc.CallOption) (*CheckInResponse, error) + GetDesiredState(ctx context.Context, in *GetDesiredStateRequest, opts ...grpc.CallOption) (*GetDesiredStateResponse, error) + WatchDesiredState(ctx context.Context, in *WatchDesiredStateRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[WatchDesiredStateResponse], error) +} + +type agentControlServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewAgentControlServiceClient(cc grpc.ClientConnInterface) AgentControlServiceClient { + return &agentControlServiceClient{cc} +} + +func (c *agentControlServiceClient) CheckIn(ctx context.Context, in *CheckInRequest, opts ...grpc.CallOption) (*CheckInResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CheckInResponse) + err := c.cc.Invoke(ctx, AgentControlService_CheckIn_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *agentControlServiceClient) GetDesiredState(ctx context.Context, in *GetDesiredStateRequest, opts ...grpc.CallOption) (*GetDesiredStateResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetDesiredStateResponse) + err := c.cc.Invoke(ctx, AgentControlService_GetDesiredState_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *agentControlServiceClient) WatchDesiredState(ctx context.Context, in *WatchDesiredStateRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[WatchDesiredStateResponse], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &AgentControlService_ServiceDesc.Streams[0], AgentControlService_WatchDesiredState_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[WatchDesiredStateRequest, WatchDesiredStateResponse]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type AgentControlService_WatchDesiredStateClient = grpc.ServerStreamingClient[WatchDesiredStateResponse] + +// AgentControlServiceServer is the server API for AgentControlService service. +// All implementations must embed UnimplementedAgentControlServiceServer +// for forward compatibility. +// +// All inbound messages are validated by the controller application layer. +// IDs are bounded ASCII identifiers; address selectors are canonical host CIDRs. +// Limits, required fields and contextual constraints are in pkg/treatment/validate.go. +type AgentControlServiceServer interface { + CheckIn(context.Context, *CheckInRequest) (*CheckInResponse, error) + GetDesiredState(context.Context, *GetDesiredStateRequest) (*GetDesiredStateResponse, error) + WatchDesiredState(*WatchDesiredStateRequest, grpc.ServerStreamingServer[WatchDesiredStateResponse]) error + mustEmbedUnimplementedAgentControlServiceServer() +} + +// UnimplementedAgentControlServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedAgentControlServiceServer struct{} + +func (UnimplementedAgentControlServiceServer) CheckIn(context.Context, *CheckInRequest) (*CheckInResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CheckIn not implemented") +} +func (UnimplementedAgentControlServiceServer) GetDesiredState(context.Context, *GetDesiredStateRequest) (*GetDesiredStateResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetDesiredState not implemented") +} +func (UnimplementedAgentControlServiceServer) WatchDesiredState(*WatchDesiredStateRequest, grpc.ServerStreamingServer[WatchDesiredStateResponse]) error { + return status.Error(codes.Unimplemented, "method WatchDesiredState not implemented") +} +func (UnimplementedAgentControlServiceServer) mustEmbedUnimplementedAgentControlServiceServer() {} +func (UnimplementedAgentControlServiceServer) testEmbeddedByValue() {} + +// UnsafeAgentControlServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to AgentControlServiceServer will +// result in compilation errors. +type UnsafeAgentControlServiceServer interface { + mustEmbedUnimplementedAgentControlServiceServer() +} + +func RegisterAgentControlServiceServer(s grpc.ServiceRegistrar, srv AgentControlServiceServer) { + // If the following call panics, it indicates UnimplementedAgentControlServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&AgentControlService_ServiceDesc, srv) +} + +func _AgentControlService_CheckIn_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CheckInRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AgentControlServiceServer).CheckIn(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AgentControlService_CheckIn_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AgentControlServiceServer).CheckIn(ctx, req.(*CheckInRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AgentControlService_GetDesiredState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetDesiredStateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AgentControlServiceServer).GetDesiredState(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AgentControlService_GetDesiredState_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AgentControlServiceServer).GetDesiredState(ctx, req.(*GetDesiredStateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AgentControlService_WatchDesiredState_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(WatchDesiredStateRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(AgentControlServiceServer).WatchDesiredState(m, &grpc.GenericServerStream[WatchDesiredStateRequest, WatchDesiredStateResponse]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type AgentControlService_WatchDesiredStateServer = grpc.ServerStreamingServer[WatchDesiredStateResponse] + +// AgentControlService_ServiceDesc is the grpc.ServiceDesc for AgentControlService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var AgentControlService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "xtcp.controller.v1.AgentControlService", + HandlerType: (*AgentControlServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CheckIn", + Handler: _AgentControlService_CheckIn_Handler, + }, + { + MethodName: "GetDesiredState", + Handler: _AgentControlService_GetDesiredState_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "WatchDesiredState", + Handler: _AgentControlService_WatchDesiredState_Handler, + ServerStreams: true, + }, + }, + Metadata: "xtcp/controller/v1/controller.proto", +} + +const ( + OperatorService_Propose_FullMethodName = "/xtcp.controller.v1.OperatorService/Propose" + OperatorService_Analyze_FullMethodName = "/xtcp.controller.v1.OperatorService/Analyze" + OperatorService_Review_FullMethodName = "/xtcp.controller.v1.OperatorService/Review" + OperatorService_Control_FullMethodName = "/xtcp.controller.v1.OperatorService/Control" + OperatorService_Inspect_FullMethodName = "/xtcp.controller.v1.OperatorService/Inspect" +) + +// OperatorServiceClient is the client API for OperatorService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type OperatorServiceClient interface { + Propose(ctx context.Context, in *ProposeRequest, opts ...grpc.CallOption) (*ProposeResponse, error) + Analyze(ctx context.Context, in *AnalyzeRequest, opts ...grpc.CallOption) (*AnalyzeResponse, error) + Review(ctx context.Context, in *ReviewRequest, opts ...grpc.CallOption) (*ReviewResponse, error) + Control(ctx context.Context, in *ControlRequest, opts ...grpc.CallOption) (*ControlResponse, error) + Inspect(ctx context.Context, in *InspectRequest, opts ...grpc.CallOption) (*InspectResponse, error) +} + +type operatorServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewOperatorServiceClient(cc grpc.ClientConnInterface) OperatorServiceClient { + return &operatorServiceClient{cc} +} + +func (c *operatorServiceClient) Propose(ctx context.Context, in *ProposeRequest, opts ...grpc.CallOption) (*ProposeResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ProposeResponse) + err := c.cc.Invoke(ctx, OperatorService_Propose_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *operatorServiceClient) Analyze(ctx context.Context, in *AnalyzeRequest, opts ...grpc.CallOption) (*AnalyzeResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AnalyzeResponse) + err := c.cc.Invoke(ctx, OperatorService_Analyze_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *operatorServiceClient) Review(ctx context.Context, in *ReviewRequest, opts ...grpc.CallOption) (*ReviewResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ReviewResponse) + err := c.cc.Invoke(ctx, OperatorService_Review_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *operatorServiceClient) Control(ctx context.Context, in *ControlRequest, opts ...grpc.CallOption) (*ControlResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ControlResponse) + err := c.cc.Invoke(ctx, OperatorService_Control_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *operatorServiceClient) Inspect(ctx context.Context, in *InspectRequest, opts ...grpc.CallOption) (*InspectResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(InspectResponse) + err := c.cc.Invoke(ctx, OperatorService_Inspect_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// OperatorServiceServer is the server API for OperatorService service. +// All implementations must embed UnimplementedOperatorServiceServer +// for forward compatibility. +type OperatorServiceServer interface { + Propose(context.Context, *ProposeRequest) (*ProposeResponse, error) + Analyze(context.Context, *AnalyzeRequest) (*AnalyzeResponse, error) + Review(context.Context, *ReviewRequest) (*ReviewResponse, error) + Control(context.Context, *ControlRequest) (*ControlResponse, error) + Inspect(context.Context, *InspectRequest) (*InspectResponse, error) + mustEmbedUnimplementedOperatorServiceServer() +} + +// UnimplementedOperatorServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedOperatorServiceServer struct{} + +func (UnimplementedOperatorServiceServer) Propose(context.Context, *ProposeRequest) (*ProposeResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Propose not implemented") +} +func (UnimplementedOperatorServiceServer) Analyze(context.Context, *AnalyzeRequest) (*AnalyzeResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Analyze not implemented") +} +func (UnimplementedOperatorServiceServer) Review(context.Context, *ReviewRequest) (*ReviewResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Review not implemented") +} +func (UnimplementedOperatorServiceServer) Control(context.Context, *ControlRequest) (*ControlResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Control not implemented") +} +func (UnimplementedOperatorServiceServer) Inspect(context.Context, *InspectRequest) (*InspectResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Inspect not implemented") +} +func (UnimplementedOperatorServiceServer) mustEmbedUnimplementedOperatorServiceServer() {} +func (UnimplementedOperatorServiceServer) testEmbeddedByValue() {} + +// UnsafeOperatorServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to OperatorServiceServer will +// result in compilation errors. +type UnsafeOperatorServiceServer interface { + mustEmbedUnimplementedOperatorServiceServer() +} + +func RegisterOperatorServiceServer(s grpc.ServiceRegistrar, srv OperatorServiceServer) { + // If the following call panics, it indicates UnimplementedOperatorServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&OperatorService_ServiceDesc, srv) +} + +func _OperatorService_Propose_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ProposeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OperatorServiceServer).Propose(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OperatorService_Propose_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OperatorServiceServer).Propose(ctx, req.(*ProposeRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OperatorService_Analyze_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AnalyzeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OperatorServiceServer).Analyze(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OperatorService_Analyze_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OperatorServiceServer).Analyze(ctx, req.(*AnalyzeRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OperatorService_Review_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ReviewRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OperatorServiceServer).Review(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OperatorService_Review_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OperatorServiceServer).Review(ctx, req.(*ReviewRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OperatorService_Control_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ControlRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OperatorServiceServer).Control(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OperatorService_Control_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OperatorServiceServer).Control(ctx, req.(*ControlRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _OperatorService_Inspect_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(InspectRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OperatorServiceServer).Inspect(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: OperatorService_Inspect_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OperatorServiceServer).Inspect(ctx, req.(*InspectRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// OperatorService_ServiceDesc is the grpc.ServiceDesc for OperatorService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var OperatorService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "xtcp.controller.v1.OperatorService", + HandlerType: (*OperatorServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Propose", + Handler: _OperatorService_Propose_Handler, + }, + { + MethodName: "Analyze", + Handler: _OperatorService_Analyze_Handler, + }, + { + MethodName: "Review", + Handler: _OperatorService_Review_Handler, + }, + { + MethodName: "Control", + Handler: _OperatorService_Control_Handler, + }, + { + MethodName: "Inspect", + Handler: _OperatorService_Inspect_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "xtcp/controller/v1/controller.proto", +} diff --git a/gen/go/xtcp_controller/controller_vtproto.pb.go b/gen/go/xtcp_controller/controller_vtproto.pb.go new file mode 100644 index 000000000..5bae1914f --- /dev/null +++ b/gen/go/xtcp_controller/controller_vtproto.pb.go @@ -0,0 +1,7963 @@ +// Code generated by protoc-gen-go-vtproto. DO NOT EDIT. +// protoc-gen-go-vtproto version: v0.6.1-0.20240319094008-0393e58bdf10 +// source: xtcp/controller/v1/controller.proto + +package xtcp_controller + +import ( + binary "encoding/binary" + fmt "fmt" + protohelpers "github.com/planetscale/vtprotobuf/protohelpers" + durationpb "github.com/planetscale/vtprotobuf/types/known/durationpb" + timestamppb "github.com/planetscale/vtprotobuf/types/known/timestamppb" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + durationpb1 "google.golang.org/protobuf/types/known/durationpb" + timestamppb1 "google.golang.org/protobuf/types/known/timestamppb" + io "io" + math "math" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +func (m *TCPSettings) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TCPSettings) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *TCPSettings) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.Ecn != nil { + i-- + if *m.Ecn { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x38 + } + if m.QuickAck != nil { + i-- + if *m.QuickAck { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x30 + } + if m.InitialReceiveWindow != nil { + i = protohelpers.EncodeVarint(dAtA, i, uint64(*m.InitialReceiveWindow)) + i-- + dAtA[i] = 0x28 + } + if m.InitialCongestionWindow != nil { + i = protohelpers.EncodeVarint(dAtA, i, uint64(*m.InitialCongestionWindow)) + i-- + dAtA[i] = 0x20 + } + if m.RtoMinMs != nil { + i = protohelpers.EncodeVarint(dAtA, i, uint64(*m.RtoMinMs)) + i-- + dAtA[i] = 0x18 + } + if m.CongestionLock { + i-- + if m.CongestionLock { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x10 + } + if m.CongestionControl != nil { + i -= len(*m.CongestionControl) + copy(dAtA[i:], *m.CongestionControl) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(*m.CongestionControl))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Treatment) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Treatment) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *Treatment) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.EcnFabricVerified { + i-- + if m.EcnFabricVerified { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x28 + } + if m.Settings != nil { + size, err := m.Settings.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x22 + } + if len(m.Destination) > 0 { + i -= len(m.Destination) + copy(dAtA[i:], m.Destination) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Destination))) + i-- + dAtA[i] = 0x1a + } + if m.Revision != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Revision)) + i-- + dAtA[i] = 0x10 + } + if len(m.Id) > 0 { + i -= len(m.Id) + copy(dAtA[i:], m.Id) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Id))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Assignment) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Assignment) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *Assignment) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.Generation != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Generation)) + i-- + dAtA[i] = 0x38 + } + if m.Predecessor != nil { + size, err := m.Predecessor.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x32 + } + if m.LeaseExpiresAt != nil { + size, err := (*timestamppb.Timestamp)(m.LeaseExpiresAt).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x2a + } + if m.Mode != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Mode)) + i-- + dAtA[i] = 0x20 + } + if m.Treatment != nil { + size, err := m.Treatment.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x1a + } + if len(m.ExperimentId) > 0 { + i -= len(m.ExperimentId) + copy(dAtA[i:], m.ExperimentId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.ExperimentId))) + i-- + dAtA[i] = 0x12 + } + if len(m.Id) > 0 { + i -= len(m.Id) + copy(dAtA[i:], m.Id) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Id))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *DesiredState) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DesiredState) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *DesiredState) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.IssuedAt != nil { + size, err := (*timestamppb.Timestamp)(m.IssuedAt).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x2a + } + if len(m.Assignments) > 0 { + for iNdEx := len(m.Assignments) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.Assignments[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x22 + } + } + if m.Suspended { + i-- + if m.Suspended { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x18 + } + if m.Revision != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Revision)) + i-- + dAtA[i] = 0x10 + } + if len(m.AgentId) > 0 { + i -= len(m.AgentId) + copy(dAtA[i:], m.AgentId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.AgentId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Capabilities) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Capabilities) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *Capabilities) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.ActuatorEnabled { + i-- + if m.ActuatorEnabled { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x28 + } + if m.SimpleHostRouting { + i-- + if m.SimpleHostRouting { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x20 + } + if len(m.CongestionAlgorithms) > 0 { + for iNdEx := len(m.CongestionAlgorithms) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.CongestionAlgorithms[iNdEx]) + copy(dAtA[i:], m.CongestionAlgorithms[iNdEx]) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.CongestionAlgorithms[iNdEx]))) + i-- + dAtA[i] = 0x1a + } + } + if len(m.KernelRelease) > 0 { + i -= len(m.KernelRelease) + copy(dAtA[i:], m.KernelRelease) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.KernelRelease))) + i-- + dAtA[i] = 0x12 + } + if len(m.BootId) > 0 { + i -= len(m.BootId) + copy(dAtA[i:], m.BootId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.BootId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *ApplicationRecord) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ApplicationRecord) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *ApplicationRecord) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.ExpiredLatched { + i-- + if m.ExpiredLatched { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x60 + } + if len(m.InstalledBootId) > 0 { + i -= len(m.InstalledBootId) + copy(dAtA[i:], m.InstalledBootId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.InstalledBootId))) + i-- + dAtA[i] = 0x5a + } + if len(m.InstalledRoute) > 0 { + i -= len(m.InstalledRoute) + copy(dAtA[i:], m.InstalledRoute) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.InstalledRoute))) + i-- + dAtA[i] = 0x52 + } + if len(m.Error) > 0 { + i -= len(m.Error) + copy(dAtA[i:], m.Error) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Error))) + i-- + dAtA[i] = 0x4a + } + if m.RemovedAt != nil { + size, err := (*timestamppb.Timestamp)(m.RemovedAt).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x42 + } + if m.VerifiedAt != nil { + size, err := (*timestamppb.Timestamp)(m.VerifiedAt).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x3a + } + if m.AppliedAt != nil { + size, err := (*timestamppb.Timestamp)(m.AppliedAt).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x32 + } + if m.AttemptedAt != nil { + size, err := (*timestamppb.Timestamp)(m.AttemptedAt).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x2a + } + if m.ReceivedAt != nil { + size, err := (*timestamppb.Timestamp)(m.ReceivedAt).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x22 + } + if m.Status != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Status)) + i-- + dAtA[i] = 0x18 + } + if m.DesiredRevision != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.DesiredRevision)) + i-- + dAtA[i] = 0x10 + } + if m.Assignment != nil { + size, err := m.Assignment.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *LocalState) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *LocalState) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *LocalState) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.UpdatedAt != nil { + size, err := (*timestamppb.Timestamp)(m.UpdatedAt).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x2a + } + if len(m.Records) > 0 { + for iNdEx := len(m.Records) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.Records[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x22 + } + } + if len(m.SuspensionReason) > 0 { + i -= len(m.SuspensionReason) + copy(dAtA[i:], m.SuspensionReason) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.SuspensionReason))) + i-- + dAtA[i] = 0x1a + } + if m.LocallySuspended { + i-- + if m.LocallySuspended { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x10 + } + if m.Desired != nil { + size, err := m.Desired.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *CheckInRequest) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *CheckInRequest) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *CheckInRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.LocallySuspended { + i-- + if m.LocallySuspended { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x30 + } + if len(m.Records) > 0 { + for iNdEx := len(m.Records) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.Records[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x2a + } + } + if m.DesiredRevision != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.DesiredRevision)) + i-- + dAtA[i] = 0x20 + } + if m.Capabilities != nil { + size, err := m.Capabilities.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x1a + } + if len(m.Location) > 0 { + i -= len(m.Location) + copy(dAtA[i:], m.Location) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Location))) + i-- + dAtA[i] = 0x12 + } + if len(m.AgentId) > 0 { + i -= len(m.AgentId) + copy(dAtA[i:], m.AgentId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.AgentId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *CheckInResponse) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *CheckInResponse) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *CheckInResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.Desired != nil { + size, err := m.Desired.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *GetDesiredStateRequest) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GetDesiredStateRequest) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *GetDesiredStateRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.AgentId) > 0 { + i -= len(m.AgentId) + copy(dAtA[i:], m.AgentId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.AgentId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *GetDesiredStateResponse) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GetDesiredStateResponse) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *GetDesiredStateResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.Desired != nil { + size, err := m.Desired.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *WatchDesiredStateRequest) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *WatchDesiredStateRequest) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *WatchDesiredStateRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.AfterRevision != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.AfterRevision)) + i-- + dAtA[i] = 0x10 + } + if len(m.AgentId) > 0 { + i -= len(m.AgentId) + copy(dAtA[i:], m.AgentId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.AgentId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *WatchDesiredStateResponse) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *WatchDesiredStateResponse) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *WatchDesiredStateResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.Revision != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Revision)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *HostEvidence) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *HostEvidence) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *HostEvidence) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.CollectionHealthy { + i-- + if m.CollectionHealthy { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x40 + } + if m.ExposedSockets != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.ExposedSockets)) + i-- + dAtA[i] = 0x38 + } + if m.RttP95Ms != 0 { + i -= 8 + binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.RttP95Ms)))) + i-- + dAtA[i] = 0x31 + } + if m.RetransmissionRatio != 0 { + i -= 8 + binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.RetransmissionRatio)))) + i-- + dAtA[i] = 0x29 + } + if m.GoodputBps != 0 { + i -= 8 + binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.GoodputBps)))) + i-- + dAtA[i] = 0x21 + } + if m.BytesAcked != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.BytesAcked)) + i-- + dAtA[i] = 0x18 + } + if m.Sockets != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Sockets)) + i-- + dAtA[i] = 0x10 + } + if len(m.AgentId) > 0 { + i -= len(m.AgentId) + copy(dAtA[i:], m.AgentId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.AgentId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Evidence) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Evidence) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *Evidence) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.ArtifactSha256) > 0 { + i -= len(m.ArtifactSha256) + copy(dAtA[i:], m.ArtifactSha256) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.ArtifactSha256))) + i-- + dAtA[i] = 0x42 + } + if len(m.Hosts) > 0 { + for iNdEx := len(m.Hosts) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.Hosts[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x3a + } + } + if len(m.EnrichmentVersion) > 0 { + i -= len(m.EnrichmentVersion) + copy(dAtA[i:], m.EnrichmentVersion) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.EnrichmentVersion))) + i-- + dAtA[i] = 0x32 + } + if len(m.QueryVersion) > 0 { + i -= len(m.QueryVersion) + copy(dAtA[i:], m.QueryVersion) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.QueryVersion))) + i-- + dAtA[i] = 0x2a + } + if m.WindowEnd != nil { + size, err := (*timestamppb.Timestamp)(m.WindowEnd).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x22 + } + if m.WindowStart != nil { + size, err := (*timestamppb.Timestamp)(m.WindowStart).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x1a + } + if m.AnalyzedAt != nil { + size, err := (*timestamppb.Timestamp)(m.AnalyzedAt).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x12 + } + if len(m.Id) > 0 { + i -= len(m.Id) + copy(dAtA[i:], m.Id) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Id))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *ExperimentPolicy) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ExperimentPolicy) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *ExperimentPolicy) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.MaximumEvidenceAge != nil { + size, err := (*durationpb.Duration)(m.MaximumEvidenceAge).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x4a + } + if m.MaximumRttIncrease != 0 { + i -= 8 + binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.MaximumRttIncrease)))) + i-- + dAtA[i] = 0x41 + } + if m.MaximumRetransmissionIncrease != 0 { + i -= 8 + binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.MaximumRetransmissionIncrease)))) + i-- + dAtA[i] = 0x39 + } + if m.MinimumImprovement != 0 { + i -= 8 + binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.MinimumImprovement)))) + i-- + dAtA[i] = 0x31 + } + if m.MinimumTreatedHosts != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.MinimumTreatedHosts)) + i-- + dAtA[i] = 0x28 + } + if m.MinimumBytesPerHost != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.MinimumBytesPerHost)) + i-- + dAtA[i] = 0x20 + } + if m.MinimumSocketsPerHost != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.MinimumSocketsPerHost)) + i-- + dAtA[i] = 0x18 + } + if m.MaximumPhase != nil { + size, err := (*durationpb.Duration)(m.MaximumPhase).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x12 + } + if m.MinimumObservation != nil { + size, err := (*durationpb.Duration)(m.MinimumObservation).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Phase) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Phase) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *Phase) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.Reason) > 0 { + i -= len(m.Reason) + copy(dAtA[i:], m.Reason) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Reason))) + i-- + dAtA[i] = 0x42 + } + if len(m.Reviewer) > 0 { + i -= len(m.Reviewer) + copy(dAtA[i:], m.Reviewer) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Reviewer))) + i-- + dAtA[i] = 0x3a + } + if len(m.Outcome) > 0 { + i -= len(m.Outcome) + copy(dAtA[i:], m.Outcome) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Outcome))) + i-- + dAtA[i] = 0x32 + } + if m.Evidence != nil { + size, err := m.Evidence.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x2a + } + if m.EvaluatedAt != nil { + size, err := (*timestamppb.Timestamp)(m.EvaluatedAt).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x22 + } + if m.ObservationStartedAt != nil { + size, err := (*timestamppb.Timestamp)(m.ObservationStartedAt).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x1a + } + if m.IssuedAt != nil { + size, err := (*timestamppb.Timestamp)(m.IssuedAt).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x12 + } + if m.TargetHosts != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.TargetHosts)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *Experiment) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Experiment) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *Experiment) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.LatestOutcome) > 0 { + i -= len(m.LatestOutcome) + copy(dAtA[i:], m.LatestOutcome) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.LatestOutcome))) + i-- + dAtA[i] = 0x6a + } + if m.LatestEvidence != nil { + size, err := m.LatestEvidence.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x62 + } + if m.ProposedAt != nil { + size, err := (*timestamppb.Timestamp)(m.ProposedAt).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x5a + } + if m.Revision != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Revision)) + i-- + dAtA[i] = 0x50 + } + if m.Status != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Status)) + i-- + dAtA[i] = 0x48 + } + if len(m.Phases) > 0 { + for iNdEx := len(m.Phases) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.Phases[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x42 + } + } + if len(m.CohortOrder) > 0 { + for iNdEx := len(m.CohortOrder) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.CohortOrder[iNdEx]) + copy(dAtA[i:], m.CohortOrder[iNdEx]) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.CohortOrder[iNdEx]))) + i-- + dAtA[i] = 0x3a + } + } + if m.Baseline != nil { + size, err := m.Baseline.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x32 + } + if m.Policy != nil { + size, err := m.Policy.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x2a + } + if m.Treatment != nil { + size, err := m.Treatment.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x22 + } + if len(m.Hypothesis) > 0 { + i -= len(m.Hypothesis) + copy(dAtA[i:], m.Hypothesis) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Hypothesis))) + i-- + dAtA[i] = 0x1a + } + if len(m.Location) > 0 { + i -= len(m.Location) + copy(dAtA[i:], m.Location) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Location))) + i-- + dAtA[i] = 0x12 + } + if len(m.Id) > 0 { + i -= len(m.Id) + copy(dAtA[i:], m.Id) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Id))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *ProposeRequest) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ProposeRequest) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *ProposeRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.RequestId) > 0 { + i -= len(m.RequestId) + copy(dAtA[i:], m.RequestId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.RequestId))) + i-- + dAtA[i] = 0x12 + } + if m.Experiment != nil { + size, err := m.Experiment.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *ProposeResponse) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ProposeResponse) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *ProposeResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.Experiment != nil { + size, err := m.Experiment.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *AnalyzeRequest) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AnalyzeRequest) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *AnalyzeRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.ExpectedRevision != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.ExpectedRevision)) + i-- + dAtA[i] = 0x10 + } + if len(m.ExperimentId) > 0 { + i -= len(m.ExperimentId) + copy(dAtA[i:], m.ExperimentId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.ExperimentId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *AnalyzeResponse) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AnalyzeResponse) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *AnalyzeResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.Outcome) > 0 { + i -= len(m.Outcome) + copy(dAtA[i:], m.Outcome) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Outcome))) + i-- + dAtA[i] = 0x12 + } + if m.Evidence != nil { + size, err := m.Evidence.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *ReviewRequest) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ReviewRequest) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *ReviewRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.Evidence != nil { + size, err := m.Evidence.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x32 + } + if len(m.RequestId) > 0 { + i -= len(m.RequestId) + copy(dAtA[i:], m.RequestId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.RequestId))) + i-- + dAtA[i] = 0x2a + } + if len(m.Reason) > 0 { + i -= len(m.Reason) + copy(dAtA[i:], m.Reason) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Reason))) + i-- + dAtA[i] = 0x22 + } + if m.Approve { + i-- + if m.Approve { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x18 + } + if m.ExpectedRevision != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.ExpectedRevision)) + i-- + dAtA[i] = 0x10 + } + if len(m.ExperimentId) > 0 { + i -= len(m.ExperimentId) + copy(dAtA[i:], m.ExperimentId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.ExperimentId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *ReviewResponse) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ReviewResponse) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *ReviewResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.Experiment != nil { + size, err := m.Experiment.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *ControlRequest) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ControlRequest) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *ControlRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.RequestId) > 0 { + i -= len(m.RequestId) + copy(dAtA[i:], m.RequestId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.RequestId))) + i-- + dAtA[i] = 0x3a + } + if len(m.Reason) > 0 { + i -= len(m.Reason) + copy(dAtA[i:], m.Reason) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Reason))) + i-- + dAtA[i] = 0x32 + } + if m.Action != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Action)) + i-- + dAtA[i] = 0x28 + } + if m.Fleet { + i-- + if m.Fleet { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x20 + } + if len(m.Location) > 0 { + i -= len(m.Location) + copy(dAtA[i:], m.Location) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Location))) + i-- + dAtA[i] = 0x1a + } + if len(m.AgentId) > 0 { + i -= len(m.AgentId) + copy(dAtA[i:], m.AgentId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.AgentId))) + i-- + dAtA[i] = 0x12 + } + if len(m.ExperimentId) > 0 { + i -= len(m.ExperimentId) + copy(dAtA[i:], m.ExperimentId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.ExperimentId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *ControlResponse) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ControlResponse) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *ControlResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.AffectedAgents != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.AffectedAgents)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *InspectRequest) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *InspectRequest) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *InspectRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.AgentId) > 0 { + i -= len(m.AgentId) + copy(dAtA[i:], m.AgentId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.AgentId))) + i-- + dAtA[i] = 0x12 + } + if len(m.ExperimentId) > 0 { + i -= len(m.ExperimentId) + copy(dAtA[i:], m.ExperimentId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.ExperimentId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *InspectResponse) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *InspectResponse) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *InspectResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.Observed) > 0 { + for iNdEx := len(m.Observed) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.Observed[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x1a + } + } + if len(m.Desired) > 0 { + for iNdEx := len(m.Desired) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.Desired[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x12 + } + } + if len(m.Experiments) > 0 { + for iNdEx := len(m.Experiments) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.Experiments[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *TCPSettings) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.CongestionControl != nil { + l = len(*m.CongestionControl) + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.CongestionLock { + n += 2 + } + if m.RtoMinMs != nil { + n += 1 + protohelpers.SizeOfVarint(uint64(*m.RtoMinMs)) + } + if m.InitialCongestionWindow != nil { + n += 1 + protohelpers.SizeOfVarint(uint64(*m.InitialCongestionWindow)) + } + if m.InitialReceiveWindow != nil { + n += 1 + protohelpers.SizeOfVarint(uint64(*m.InitialReceiveWindow)) + } + if m.QuickAck != nil { + n += 2 + } + if m.Ecn != nil { + n += 2 + } + n += len(m.unknownFields) + return n +} + +func (m *Treatment) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Id) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Revision != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Revision)) + } + l = len(m.Destination) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Settings != nil { + l = m.Settings.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.EcnFabricVerified { + n += 2 + } + n += len(m.unknownFields) + return n +} + +func (m *Assignment) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Id) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.ExperimentId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Treatment != nil { + l = m.Treatment.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Mode != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Mode)) + } + if m.LeaseExpiresAt != nil { + l = (*timestamppb.Timestamp)(m.LeaseExpiresAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Predecessor != nil { + l = m.Predecessor.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Generation != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Generation)) + } + n += len(m.unknownFields) + return n +} + +func (m *DesiredState) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.AgentId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Revision != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Revision)) + } + if m.Suspended { + n += 2 + } + if len(m.Assignments) > 0 { + for _, e := range m.Assignments { + l = e.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + } + if m.IssuedAt != nil { + l = (*timestamppb.Timestamp)(m.IssuedAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *Capabilities) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.BootId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.KernelRelease) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if len(m.CongestionAlgorithms) > 0 { + for _, s := range m.CongestionAlgorithms { + l = len(s) + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + } + if m.SimpleHostRouting { + n += 2 + } + if m.ActuatorEnabled { + n += 2 + } + n += len(m.unknownFields) + return n +} + +func (m *ApplicationRecord) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Assignment != nil { + l = m.Assignment.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.DesiredRevision != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.DesiredRevision)) + } + if m.Status != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Status)) + } + if m.ReceivedAt != nil { + l = (*timestamppb.Timestamp)(m.ReceivedAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.AttemptedAt != nil { + l = (*timestamppb.Timestamp)(m.AttemptedAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.AppliedAt != nil { + l = (*timestamppb.Timestamp)(m.AppliedAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.VerifiedAt != nil { + l = (*timestamppb.Timestamp)(m.VerifiedAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.RemovedAt != nil { + l = (*timestamppb.Timestamp)(m.RemovedAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.Error) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.InstalledRoute) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.InstalledBootId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.ExpiredLatched { + n += 2 + } + n += len(m.unknownFields) + return n +} + +func (m *LocalState) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Desired != nil { + l = m.Desired.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.LocallySuspended { + n += 2 + } + l = len(m.SuspensionReason) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if len(m.Records) > 0 { + for _, e := range m.Records { + l = e.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + } + if m.UpdatedAt != nil { + l = (*timestamppb.Timestamp)(m.UpdatedAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *CheckInRequest) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.AgentId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.Location) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Capabilities != nil { + l = m.Capabilities.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.DesiredRevision != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.DesiredRevision)) + } + if len(m.Records) > 0 { + for _, e := range m.Records { + l = e.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + } + if m.LocallySuspended { + n += 2 + } + n += len(m.unknownFields) + return n +} + +func (m *CheckInResponse) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Desired != nil { + l = m.Desired.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *GetDesiredStateRequest) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.AgentId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *GetDesiredStateResponse) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Desired != nil { + l = m.Desired.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *WatchDesiredStateRequest) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.AgentId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.AfterRevision != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.AfterRevision)) + } + n += len(m.unknownFields) + return n +} + +func (m *WatchDesiredStateResponse) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Revision != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Revision)) + } + n += len(m.unknownFields) + return n +} + +func (m *HostEvidence) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.AgentId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Sockets != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Sockets)) + } + if m.BytesAcked != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.BytesAcked)) + } + if m.GoodputBps != 0 { + n += 9 + } + if m.RetransmissionRatio != 0 { + n += 9 + } + if m.RttP95Ms != 0 { + n += 9 + } + if m.ExposedSockets != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.ExposedSockets)) + } + if m.CollectionHealthy { + n += 2 + } + n += len(m.unknownFields) + return n +} + +func (m *Evidence) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Id) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.AnalyzedAt != nil { + l = (*timestamppb.Timestamp)(m.AnalyzedAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.WindowStart != nil { + l = (*timestamppb.Timestamp)(m.WindowStart).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.WindowEnd != nil { + l = (*timestamppb.Timestamp)(m.WindowEnd).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.QueryVersion) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.EnrichmentVersion) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if len(m.Hosts) > 0 { + for _, e := range m.Hosts { + l = e.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + } + l = len(m.ArtifactSha256) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *ExperimentPolicy) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.MinimumObservation != nil { + l = (*durationpb.Duration)(m.MinimumObservation).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.MaximumPhase != nil { + l = (*durationpb.Duration)(m.MaximumPhase).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.MinimumSocketsPerHost != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.MinimumSocketsPerHost)) + } + if m.MinimumBytesPerHost != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.MinimumBytesPerHost)) + } + if m.MinimumTreatedHosts != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.MinimumTreatedHosts)) + } + if m.MinimumImprovement != 0 { + n += 9 + } + if m.MaximumRetransmissionIncrease != 0 { + n += 9 + } + if m.MaximumRttIncrease != 0 { + n += 9 + } + if m.MaximumEvidenceAge != nil { + l = (*durationpb.Duration)(m.MaximumEvidenceAge).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *Phase) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.TargetHosts != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.TargetHosts)) + } + if m.IssuedAt != nil { + l = (*timestamppb.Timestamp)(m.IssuedAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.ObservationStartedAt != nil { + l = (*timestamppb.Timestamp)(m.ObservationStartedAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.EvaluatedAt != nil { + l = (*timestamppb.Timestamp)(m.EvaluatedAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Evidence != nil { + l = m.Evidence.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.Outcome) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.Reviewer) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.Reason) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *Experiment) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Id) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.Location) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.Hypothesis) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Treatment != nil { + l = m.Treatment.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Policy != nil { + l = m.Policy.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Baseline != nil { + l = m.Baseline.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if len(m.CohortOrder) > 0 { + for _, s := range m.CohortOrder { + l = len(s) + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + } + if len(m.Phases) > 0 { + for _, e := range m.Phases { + l = e.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + } + if m.Status != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Status)) + } + if m.Revision != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Revision)) + } + if m.ProposedAt != nil { + l = (*timestamppb.Timestamp)(m.ProposedAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.LatestEvidence != nil { + l = m.LatestEvidence.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.LatestOutcome) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *ProposeRequest) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Experiment != nil { + l = m.Experiment.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.RequestId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *ProposeResponse) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Experiment != nil { + l = m.Experiment.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *AnalyzeRequest) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ExperimentId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.ExpectedRevision != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.ExpectedRevision)) + } + n += len(m.unknownFields) + return n +} + +func (m *AnalyzeResponse) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Evidence != nil { + l = m.Evidence.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.Outcome) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *ReviewRequest) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ExperimentId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.ExpectedRevision != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.ExpectedRevision)) + } + if m.Approve { + n += 2 + } + l = len(m.Reason) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.RequestId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Evidence != nil { + l = m.Evidence.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *ReviewResponse) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Experiment != nil { + l = m.Experiment.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *ControlRequest) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ExperimentId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.AgentId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.Location) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Fleet { + n += 2 + } + if m.Action != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Action)) + } + l = len(m.Reason) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.RequestId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *ControlResponse) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.AffectedAgents != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.AffectedAgents)) + } + n += len(m.unknownFields) + return n +} + +func (m *InspectRequest) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ExperimentId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.AgentId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *InspectResponse) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Experiments) > 0 { + for _, e := range m.Experiments { + l = e.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + } + if len(m.Desired) > 0 { + for _, e := range m.Desired { + l = e.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + } + if len(m.Observed) > 0 { + for _, e := range m.Observed { + l = e.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + } + n += len(m.unknownFields) + return n +} + +func (m *TCPSettings) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TCPSettings: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TCPSettings: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CongestionControl", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.CongestionControl = &s + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field CongestionLock", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.CongestionLock = bool(v != 0) + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RtoMinMs", wireType) + } + var v uint32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.RtoMinMs = &v + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field InitialCongestionWindow", wireType) + } + var v uint32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.InitialCongestionWindow = &v + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field InitialReceiveWindow", wireType) + } + var v uint32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.InitialReceiveWindow = &v + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field QuickAck", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + b := bool(v != 0) + m.QuickAck = &b + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Ecn", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + b := bool(v != 0) + m.Ecn = &b + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Treatment) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Treatment: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Treatment: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Id = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Revision", wireType) + } + m.Revision = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Revision |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Destination", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Destination = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Settings", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Settings == nil { + m.Settings = &TCPSettings{} + } + if err := m.Settings.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field EcnFabricVerified", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.EcnFabricVerified = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Assignment) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Assignment: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Assignment: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Id = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExperimentId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ExperimentId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Treatment", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Treatment == nil { + m.Treatment = &Treatment{} + } + if err := m.Treatment.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Mode", wireType) + } + m.Mode = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Mode |= AssignmentMode(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LeaseExpiresAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.LeaseExpiresAt == nil { + m.LeaseExpiresAt = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.LeaseExpiresAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Predecessor", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Predecessor == nil { + m.Predecessor = &Treatment{} + } + if err := m.Predecessor.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Generation", wireType) + } + m.Generation = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Generation |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DesiredState) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DesiredState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DesiredState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AgentId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AgentId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Revision", wireType) + } + m.Revision = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Revision |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Suspended", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Suspended = bool(v != 0) + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Assignments", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Assignments = append(m.Assignments, &Assignment{}) + if err := m.Assignments[len(m.Assignments)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field IssuedAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.IssuedAt == nil { + m.IssuedAt = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.IssuedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Capabilities) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Capabilities: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Capabilities: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BootId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.BootId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field KernelRelease", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.KernelRelease = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CongestionAlgorithms", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CongestionAlgorithms = append(m.CongestionAlgorithms, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SimpleHostRouting", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.SimpleHostRouting = bool(v != 0) + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ActuatorEnabled", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.ActuatorEnabled = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ApplicationRecord) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ApplicationRecord: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ApplicationRecord: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Assignment", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Assignment == nil { + m.Assignment = &Assignment{} + } + if err := m.Assignment.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DesiredRevision", wireType) + } + m.DesiredRevision = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.DesiredRevision |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + m.Status = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Status |= ApplyStatus(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ReceivedAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ReceivedAt == nil { + m.ReceivedAt = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.ReceivedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AttemptedAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.AttemptedAt == nil { + m.AttemptedAt = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.AttemptedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppliedAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.AppliedAt == nil { + m.AppliedAt = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.AppliedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field VerifiedAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.VerifiedAt == nil { + m.VerifiedAt = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.VerifiedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RemovedAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.RemovedAt == nil { + m.RemovedAt = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.RemovedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Error", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Error = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 10: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field InstalledRoute", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.InstalledRoute = append(m.InstalledRoute[:0], dAtA[iNdEx:postIndex]...) + if m.InstalledRoute == nil { + m.InstalledRoute = []byte{} + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field InstalledBootId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.InstalledBootId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 12: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ExpiredLatched", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.ExpiredLatched = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *LocalState) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: LocalState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: LocalState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Desired", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Desired == nil { + m.Desired = &DesiredState{} + } + if err := m.Desired.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field LocallySuspended", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.LocallySuspended = bool(v != 0) + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SuspensionReason", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SuspensionReason = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Records", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Records = append(m.Records, &ApplicationRecord{}) + if err := m.Records[len(m.Records)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UpdatedAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.UpdatedAt == nil { + m.UpdatedAt = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.UpdatedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *CheckInRequest) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CheckInRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CheckInRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AgentId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AgentId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Location", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Location = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Capabilities", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Capabilities == nil { + m.Capabilities = &Capabilities{} + } + if err := m.Capabilities.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DesiredRevision", wireType) + } + m.DesiredRevision = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.DesiredRevision |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Records", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Records = append(m.Records, &ApplicationRecord{}) + if err := m.Records[len(m.Records)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field LocallySuspended", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.LocallySuspended = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *CheckInResponse) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CheckInResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CheckInResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Desired", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Desired == nil { + m.Desired = &DesiredState{} + } + if err := m.Desired.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GetDesiredStateRequest) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GetDesiredStateRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GetDesiredStateRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AgentId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AgentId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GetDesiredStateResponse) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GetDesiredStateResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GetDesiredStateResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Desired", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Desired == nil { + m.Desired = &DesiredState{} + } + if err := m.Desired.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *WatchDesiredStateRequest) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: WatchDesiredStateRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: WatchDesiredStateRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AgentId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AgentId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AfterRevision", wireType) + } + m.AfterRevision = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.AfterRevision |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *WatchDesiredStateResponse) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: WatchDesiredStateResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: WatchDesiredStateResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Revision", wireType) + } + m.Revision = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Revision |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *HostEvidence) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: HostEvidence: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: HostEvidence: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AgentId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AgentId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sockets", wireType) + } + m.Sockets = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Sockets |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field BytesAcked", wireType) + } + m.BytesAcked = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.BytesAcked |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field GoodputBps", wireType) + } + var v uint64 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + v = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + m.GoodputBps = float64(math.Float64frombits(v)) + case 5: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field RetransmissionRatio", wireType) + } + var v uint64 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + v = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + m.RetransmissionRatio = float64(math.Float64frombits(v)) + case 6: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field RttP95Ms", wireType) + } + var v uint64 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + v = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + m.RttP95Ms = float64(math.Float64frombits(v)) + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ExposedSockets", wireType) + } + m.ExposedSockets = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ExposedSockets |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 8: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field CollectionHealthy", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.CollectionHealthy = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Evidence) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Evidence: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Evidence: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Id = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AnalyzedAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.AnalyzedAt == nil { + m.AnalyzedAt = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.AnalyzedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field WindowStart", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.WindowStart == nil { + m.WindowStart = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.WindowStart).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field WindowEnd", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.WindowEnd == nil { + m.WindowEnd = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.WindowEnd).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field QueryVersion", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.QueryVersion = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field EnrichmentVersion", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.EnrichmentVersion = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Hosts", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Hosts = append(m.Hosts, &HostEvidence{}) + if err := m.Hosts[len(m.Hosts)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ArtifactSha256", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ArtifactSha256 = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ExperimentPolicy) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ExperimentPolicy: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ExperimentPolicy: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MinimumObservation", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.MinimumObservation == nil { + m.MinimumObservation = &durationpb1.Duration{} + } + if err := (*durationpb.Duration)(m.MinimumObservation).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MaximumPhase", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.MaximumPhase == nil { + m.MaximumPhase = &durationpb1.Duration{} + } + if err := (*durationpb.Duration)(m.MaximumPhase).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MinimumSocketsPerHost", wireType) + } + m.MinimumSocketsPerHost = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MinimumSocketsPerHost |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MinimumBytesPerHost", wireType) + } + m.MinimumBytesPerHost = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MinimumBytesPerHost |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MinimumTreatedHosts", wireType) + } + m.MinimumTreatedHosts = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MinimumTreatedHosts |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field MinimumImprovement", wireType) + } + var v uint64 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + v = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + m.MinimumImprovement = float64(math.Float64frombits(v)) + case 7: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field MaximumRetransmissionIncrease", wireType) + } + var v uint64 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + v = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + m.MaximumRetransmissionIncrease = float64(math.Float64frombits(v)) + case 8: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field MaximumRttIncrease", wireType) + } + var v uint64 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + v = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + m.MaximumRttIncrease = float64(math.Float64frombits(v)) + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MaximumEvidenceAge", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.MaximumEvidenceAge == nil { + m.MaximumEvidenceAge = &durationpb1.Duration{} + } + if err := (*durationpb.Duration)(m.MaximumEvidenceAge).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Phase) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Phase: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Phase: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TargetHosts", wireType) + } + m.TargetHosts = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.TargetHosts |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field IssuedAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.IssuedAt == nil { + m.IssuedAt = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.IssuedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ObservationStartedAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ObservationStartedAt == nil { + m.ObservationStartedAt = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.ObservationStartedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field EvaluatedAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.EvaluatedAt == nil { + m.EvaluatedAt = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.EvaluatedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Evidence", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Evidence == nil { + m.Evidence = &Evidence{} + } + if err := m.Evidence.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Outcome", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Outcome = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Reviewer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Reviewer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Reason", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Reason = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Experiment) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Experiment: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Experiment: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Id = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Location", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Location = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Hypothesis", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Hypothesis = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Treatment", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Treatment == nil { + m.Treatment = &Treatment{} + } + if err := m.Treatment.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Policy", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Policy == nil { + m.Policy = &ExperimentPolicy{} + } + if err := m.Policy.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Baseline", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Baseline == nil { + m.Baseline = &Evidence{} + } + if err := m.Baseline.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CohortOrder", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CohortOrder = append(m.CohortOrder, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Phases", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Phases = append(m.Phases, &Phase{}) + if err := m.Phases[len(m.Phases)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 9: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + m.Status = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Status |= ExperimentStatus(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 10: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Revision", wireType) + } + m.Revision = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Revision |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProposedAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ProposedAt == nil { + m.ProposedAt = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.ProposedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LatestEvidence", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.LatestEvidence == nil { + m.LatestEvidence = &Evidence{} + } + if err := m.LatestEvidence.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LatestOutcome", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.LatestOutcome = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ProposeRequest) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ProposeRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ProposeRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Experiment", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Experiment == nil { + m.Experiment = &Experiment{} + } + if err := m.Experiment.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RequestId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RequestId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ProposeResponse) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ProposeResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ProposeResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Experiment", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Experiment == nil { + m.Experiment = &Experiment{} + } + if err := m.Experiment.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *AnalyzeRequest) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AnalyzeRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AnalyzeRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExperimentId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ExperimentId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ExpectedRevision", wireType) + } + m.ExpectedRevision = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ExpectedRevision |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *AnalyzeResponse) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AnalyzeResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AnalyzeResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Evidence", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Evidence == nil { + m.Evidence = &Evidence{} + } + if err := m.Evidence.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Outcome", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Outcome = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ReviewRequest) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ReviewRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ReviewRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExperimentId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ExperimentId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ExpectedRevision", wireType) + } + m.ExpectedRevision = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ExpectedRevision |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Approve", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Approve = bool(v != 0) + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Reason", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Reason = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RequestId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RequestId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Evidence", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Evidence == nil { + m.Evidence = &Evidence{} + } + if err := m.Evidence.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ReviewResponse) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ReviewResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ReviewResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Experiment", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Experiment == nil { + m.Experiment = &Experiment{} + } + if err := m.Experiment.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ControlRequest) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ControlRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ControlRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExperimentId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ExperimentId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AgentId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AgentId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Location", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Location = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Fleet", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Fleet = bool(v != 0) + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Action", wireType) + } + m.Action = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Action |= ControlAction(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Reason", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Reason = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RequestId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RequestId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ControlResponse) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ControlResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ControlResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AffectedAgents", wireType) + } + m.AffectedAgents = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.AffectedAgents |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *InspectRequest) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: InspectRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: InspectRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExperimentId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ExperimentId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AgentId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AgentId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *InspectResponse) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: InspectResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: InspectResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Experiments", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Experiments = append(m.Experiments, &Experiment{}) + if err := m.Experiments[len(m.Experiments)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Desired", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Desired = append(m.Desired, &DesiredState{}) + if err := m.Desired[len(m.Desired)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Observed", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Observed = append(m.Observed, &CheckInRequest{}) + if err := m.Observed[len(m.Observed)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} diff --git a/gen/go/xtcp_flat_record/xtcp_flat_record.pb.go b/gen/go/xtcp_flat_record/xtcp_flat_record.pb.go index 1d806cdd0..6cf7a6456 100644 --- a/gen/go/xtcp_flat_record/xtcp_flat_record.pb.go +++ b/gen/go/xtcp_flat_record/xtcp_flat_record.pb.go @@ -52,7 +52,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc (unknown) +// protoc v7.36.2 // source: xtcp_flat_record/v1/xtcp_flat_record.proto package xtcp_flat_record @@ -265,6 +265,17 @@ type XtcpFlatRecord struct { // -location flag or LOCATION env. Constant for every record from one // daemon instance. Location string `protobuf:"bytes,21,opt,name=location,proto3" json:"location,omitempty"` + // Controller provenance. Empty/zero means observation-only legacy producer. + AgentId string `protobuf:"bytes,22,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` + HostBootId string `protobuf:"bytes,23,opt,name=host_boot_id,json=hostBootId,proto3" json:"host_boot_id,omitempty"` + CollectionId string `protobuf:"bytes,24,opt,name=collection_id,json=collectionId,proto3" json:"collection_id,omitempty"` + TreatmentStateRevision uint64 `protobuf:"varint,25,opt,name=treatment_state_revision,json=treatmentStateRevision,proto3" json:"treatment_state_revision,omitempty"` + TreatmentAssignmentId string `protobuf:"bytes,26,opt,name=treatment_assignment_id,json=treatmentAssignmentId,proto3" json:"treatment_assignment_id,omitempty"` + TreatmentGeneration uint64 `protobuf:"varint,27,opt,name=treatment_generation,json=treatmentGeneration,proto3" json:"treatment_generation,omitempty"` + TreatmentAppliedTimestampNs int64 `protobuf:"varint,28,opt,name=treatment_applied_timestamp_ns,json=treatmentAppliedTimestampNs,proto3" json:"treatment_applied_timestamp_ns,omitempty"` + // True only when the observed socket algorithm matches a verified route + // assignment in this namespace. This does not prove initial-window exposure. + TreatmentAlgorithmExposed bool `protobuf:"varint,29,opt,name=treatment_algorithm_exposed,json=treatmentAlgorithmExposed,proto3" json:"treatment_algorithm_exposed,omitempty"` // ---- metadata: network namespace identity (30s) -------------------------- // network namespace — best-effort human name (bind-mount name, else a // container-derived name, else "netns:[]"). Not a stable key; use @@ -606,6 +617,62 @@ func (x *XtcpFlatRecord) GetLocation() string { return "" } +func (x *XtcpFlatRecord) GetAgentId() string { + if x != nil { + return x.AgentId + } + return "" +} + +func (x *XtcpFlatRecord) GetHostBootId() string { + if x != nil { + return x.HostBootId + } + return "" +} + +func (x *XtcpFlatRecord) GetCollectionId() string { + if x != nil { + return x.CollectionId + } + return "" +} + +func (x *XtcpFlatRecord) GetTreatmentStateRevision() uint64 { + if x != nil { + return x.TreatmentStateRevision + } + return 0 +} + +func (x *XtcpFlatRecord) GetTreatmentAssignmentId() string { + if x != nil { + return x.TreatmentAssignmentId + } + return "" +} + +func (x *XtcpFlatRecord) GetTreatmentGeneration() uint64 { + if x != nil { + return x.TreatmentGeneration + } + return 0 +} + +func (x *XtcpFlatRecord) GetTreatmentAppliedTimestampNs() int64 { + if x != nil { + return x.TreatmentAppliedTimestampNs + } + return 0 +} + +func (x *XtcpFlatRecord) GetTreatmentAlgorithmExposed() bool { + if x != nil { + return x.TreatmentAlgorithmExposed + } + return false +} + func (x *XtcpFlatRecord) GetNetns() string { if x != nil { return x.Netns @@ -1942,14 +2009,23 @@ const file_xtcp_flat_record_v1_xtcp_flat_record_proto_rawDesc = "" + "*xtcp_flat_record/v1/xtcp_flat_record.proto\x12\x13xtcp_flat_record.v1\"A\n" + "\bEnvelope\x125\n" + "\x03row\x18\n" + - " \x03(\v2#.xtcp_flat_record.v1.XtcpFlatRecordR\x03row\"\xbaI\n" + + " \x03(\v2#.xtcp_flat_record.v1.XtcpFlatRecordR\x03row\"\xc6L\n" + "\x0eXtcpFlatRecord\x12%\n" + "\x0eschema_version\x18\x01 \x01(\rR\rschemaVersion\x12%\n" + "\x0edaemon_version\x18\x02 \x01(\tR\rdaemonVersion\x12!\n" + "\ftimestamp_ns\x18\n" + " \x01(\x03R\vtimestampNs\x12\x1a\n" + "\bhostname\x18\x14 \x01(\tR\bhostname\x12\x1a\n" + - "\blocation\x18\x15 \x01(\tR\blocation\x12\x14\n" + + "\blocation\x18\x15 \x01(\tR\blocation\x12\x19\n" + + "\bagent_id\x18\x16 \x01(\tR\aagentId\x12 \n" + + "\fhost_boot_id\x18\x17 \x01(\tR\n" + + "hostBootId\x12#\n" + + "\rcollection_id\x18\x18 \x01(\tR\fcollectionId\x128\n" + + "\x18treatment_state_revision\x18\x19 \x01(\x04R\x16treatmentStateRevision\x126\n" + + "\x17treatment_assignment_id\x18\x1a \x01(\tR\x15treatmentAssignmentId\x121\n" + + "\x14treatment_generation\x18\x1b \x01(\x04R\x13treatmentGeneration\x12C\n" + + "\x1etreatment_applied_timestamp_ns\x18\x1c \x01(\x03R\x1btreatmentAppliedTimestampNs\x12>\n" + + "\x1btreatment_algorithm_exposed\x18\x1d \x01(\bR\x19treatmentAlgorithmExposed\x12\x14\n" + "\x05netns\x18\x1e \x01(\tR\x05netns\x12\x1f\n" + "\vnetns_inode\x18\x1f \x01(\x04R\n" + "netnsInode\x12\x12\n" + @@ -2147,8 +2223,7 @@ const file_xtcp_flat_record_v1_xtcp_flat_record_proto_rawDesc = "" + "\x10xtcp_flat_record\x18\x01 \x01(\v2#.xtcp_flat_record.v1.XtcpFlatRecordR\x0extcpFlatRecord2\xed\x01\n" + "\x15XTCPFlatRecordService\x12b\n" + "\vFlatRecords\x12'.xtcp_flat_record.v1.FlatRecordsRequest\x1a(.xtcp_flat_record.v1.FlatRecordsResponse0\x01\x12p\n" + - "\x0fPollFlatRecords\x12+.xtcp_flat_record.v1.PollFlatRecordsRequest\x1a,.xtcp_flat_record.v1.PollFlatRecordsResponse(\x010\x01B\xcd\x01\n" + - "\x17com.xtcp_flat_record.v1B\x13XtcpFlatRecordProtoP\x01Z8github.com/randomizedcoder/xtcp2/gen/go/xtcp_flat_record\xa2\x02\x03XXX\xaa\x02\x11XtcpFlatRecord.V1\xca\x02\x11XtcpFlatRecord\\V1\xe2\x02\x1dXtcpFlatRecord\\V1\\GPBMetadata\xea\x02\x12XtcpFlatRecord::V1b\x06proto3" + "\x0fPollFlatRecords\x12+.xtcp_flat_record.v1.PollFlatRecordsRequest\x1a,.xtcp_flat_record.v1.PollFlatRecordsResponse(\x010\x01B:Z8github.com/randomizedcoder/xtcp2/gen/go/xtcp_flat_recordb\x06proto3" var ( file_xtcp_flat_record_v1_xtcp_flat_record_proto_rawDescOnce sync.Once diff --git a/gen/go/xtcp_flat_record/xtcp_flat_record_vtproto.pb.go b/gen/go/xtcp_flat_record/xtcp_flat_record_vtproto.pb.go index a98c2897d..cf625e899 100644 --- a/gen/go/xtcp_flat_record/xtcp_flat_record_vtproto.pb.go +++ b/gen/go/xtcp_flat_record/xtcp_flat_record_vtproto.pb.go @@ -1,5 +1,5 @@ // Code generated by protoc-gen-go-vtproto. DO NOT EDIT. -// protoc-gen-go-vtproto version: (devel) +// protoc-gen-go-vtproto version: v0.6.1-0.20240319094008-0393e58bdf10 // source: xtcp_flat_record/v1/xtcp_flat_record.proto package xtcp_flat_record @@ -1328,6 +1328,75 @@ func (m *XtcpFlatRecord) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i-- dAtA[i] = 0xf2 } + if m.TreatmentAlgorithmExposed { + i-- + if m.TreatmentAlgorithmExposed { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0xe8 + } + if m.TreatmentAppliedTimestampNs != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.TreatmentAppliedTimestampNs)) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0xe0 + } + if m.TreatmentGeneration != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.TreatmentGeneration)) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0xd8 + } + if len(m.TreatmentAssignmentId) > 0 { + i -= len(m.TreatmentAssignmentId) + copy(dAtA[i:], m.TreatmentAssignmentId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.TreatmentAssignmentId))) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0xd2 + } + if m.TreatmentStateRevision != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.TreatmentStateRevision)) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0xc8 + } + if len(m.CollectionId) > 0 { + i -= len(m.CollectionId) + copy(dAtA[i:], m.CollectionId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.CollectionId))) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0xc2 + } + if len(m.HostBootId) > 0 { + i -= len(m.HostBootId) + copy(dAtA[i:], m.HostBootId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.HostBootId))) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0xba + } + if len(m.AgentId) > 0 { + i -= len(m.AgentId) + copy(dAtA[i:], m.AgentId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.AgentId))) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0xb2 + } if len(m.Location) > 0 { i -= len(m.Location) copy(dAtA[i:], m.Location) @@ -1558,6 +1627,34 @@ func (m *XtcpFlatRecord) SizeVT() (n int) { if l > 0 { n += 2 + l + protohelpers.SizeOfVarint(uint64(l)) } + l = len(m.AgentId) + if l > 0 { + n += 2 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.HostBootId) + if l > 0 { + n += 2 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.CollectionId) + if l > 0 { + n += 2 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.TreatmentStateRevision != 0 { + n += 2 + protohelpers.SizeOfVarint(uint64(m.TreatmentStateRevision)) + } + l = len(m.TreatmentAssignmentId) + if l > 0 { + n += 2 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.TreatmentGeneration != 0 { + n += 2 + protohelpers.SizeOfVarint(uint64(m.TreatmentGeneration)) + } + if m.TreatmentAppliedTimestampNs != 0 { + n += 2 + protohelpers.SizeOfVarint(uint64(m.TreatmentAppliedTimestampNs)) + } + if m.TreatmentAlgorithmExposed { + n += 3 + } l = len(m.Netns) if l > 0 { n += 2 + l + protohelpers.SizeOfVarint(uint64(l)) @@ -2392,6 +2489,211 @@ func (m *XtcpFlatRecord) UnmarshalVT(dAtA []byte) error { } m.Location = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 22: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AgentId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AgentId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 23: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field HostBootId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.HostBootId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 24: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CollectionId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CollectionId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 25: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TreatmentStateRevision", wireType) + } + m.TreatmentStateRevision = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.TreatmentStateRevision |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 26: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TreatmentAssignmentId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.TreatmentAssignmentId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 27: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TreatmentGeneration", wireType) + } + m.TreatmentGeneration = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.TreatmentGeneration |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 28: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TreatmentAppliedTimestampNs", wireType) + } + m.TreatmentAppliedTimestampNs = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.TreatmentAppliedTimestampNs |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 29: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TreatmentAlgorithmExposed", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.TreatmentAlgorithmExposed = bool(v != 0) case 30: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Netns", wireType) diff --git a/go.mod b/go.mod index d1fb22cf4..eda8dee2b 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/grafana/pyroscope-go v1.4.3 github.com/grpc-ecosystem/grpc-gateway/v2 v2.32.0 github.com/klauspost/compress v1.20.3 + github.com/lib/pq v1.12.3 github.com/minio/minio-go/v7 v7.4.1 github.com/nats-io/nats.go v1.54.0 github.com/nsqio/go-nsq v1.1.0 diff --git a/go.sum b/go.sum index 281008d29..e4deeb083 100644 --- a/go.sum +++ b/go.sum @@ -88,6 +88,8 @@ github.com/klauspost/crc32 v1.3.0/go.mod h1:D7kQaZhnkX/Y0tstFGf8VUzv2UofNGqCjnC3 github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= +github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ= +github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI= github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg= diff --git a/internal/goip/treatment_contracts_test.go b/internal/goip/treatment_contracts_test.go new file mode 100644 index 000000000..f0e1fbfc8 --- /dev/null +++ b/internal/goip/treatment_contracts_test.go @@ -0,0 +1,113 @@ +package goip + +import ( + "testing" + + "github.com/randomizedcoder/xtcp2/pkg/xtcpnl" + "golang.org/x/sys/unix" +) + +func TestTreatmentBackendBounds(t *testing.T) { + for _, tc := range []struct { + name, description string + change func(*TreatmentBackend) + wantError bool + }{ + {"valid", "private routing resources are accepted", func(*TreatmentBackend) {}, false}, + {"disabled", "mutation is opt-in", func(b *TreatmentBackend) { b.Enabled = false }, true}, + {"table-low", "main and compatibility tables are excluded", func(b *TreatmentBackend) { b.Table = 255 }, true}, + {"table-min", "first private table is accepted", func(b *TreatmentBackend) { b.Table = 256 }, false}, + {"priority-zero", "local lookup priority is reserved", func(b *TreatmentBackend) { b.Priority = 0 }, true}, + {"priority-min", "first nonlocal priority is valid", func(b *TreatmentBackend) { b.Priority = 1 }, false}, + {"priority-max", "last priority before main is valid", func(b *TreatmentBackend) { b.Priority = 32765 }, false}, + {"priority-main", "main table priority is reserved", func(b *TreatmentBackend) { b.Priority = 32766 }, true}, + {"protocol-low", "well known protocol IDs are excluded", func(b *TreatmentBackend) { b.Protocol = 191 }, true}, + {"protocol-min", "first private protocol is valid", func(b *TreatmentBackend) { b.Protocol = 192 }, false}, + } { + t.Run(tc.name, func(t *testing.T) { + b := &TreatmentBackend{Enabled: true, Table: 10002, Priority: 30000, Protocol: 242} + tc.change(b) + err := b.valid() + if (err != nil) != tc.wantError { + t.Fatalf("%s: err=%v wantError=%t", tc.description, err, tc.wantError) + } + }) + } +} + +func TestTreatmentRejectsUnsupportedBaseline(t *testing.T) { + for _, tc := range []struct { + name, description string + change func(*xtcpnl.RouteInfo) + wantError bool + }{ + {"unicast", "simple forwarding route is supported", func(*xtcpnl.RouteInfo) {}, false}, + {"blackhole", "blackhole routes must not become forwarding routes", func(r *xtcpnl.RouteInfo) { r.Type = unix.RTN_BLACKHOLE }, true}, + {"no-interface", "output interface is required", func(r *xtcpnl.RouteInfo) { r.Oif = 0 }, true}, + {"multipath", "multipath must not be flattened", func(r *xtcpnl.RouteInfo) { r.HasMultipath = true }, true}, + {"via", "cross-family gateway must not be flattened", func(r *xtcpnl.RouteInfo) { r.HasVia = true }, true}, + {"nexthop", "nexthop object semantics must be preserved", func(r *xtcpnl.RouteInfo) { r.NhID = 1 }, true}, + {"source-specific", "source selectors are unsupported", func(r *xtcpnl.RouteInfo) { r.SrcLen = 32 }, true}, + {"tos", "TOS selectors are unsupported", func(r *xtcpnl.RouteInfo) { r.Tos = 1 }, true}, + {"flags", "unknown forwarding flags are unsupported", func(r *xtcpnl.RouteInfo) { r.Flags = 1 }, true}, + {"expiry", "expiring routes cannot become permanent", func(r *xtcpnl.RouteInfo) { r.HasExpires = true }, true}, + {"unknown-metrics", "unknown metrics cannot be discarded", func(r *xtcpnl.RouteInfo) { r.Metrics = &xtcpnl.RouteMetrics{Unknown: 1} }, true}, + } { + t.Run(tc.name, func(t *testing.T) { + b := &TreatmentBackend{Enabled: true, Table: 10002, Priority: 30000, Protocol: 242} + r := xtcpnl.RouteInfo{Family: unix.AF_INET, Type: unix.RTN_UNICAST, Oif: 4} + tc.change(&r) + _, err := b.planRoute(&RouteTreatment{Destination: "192.0.2.1/32", Settings: &TCPRouteSettings{QuickAck: routeTestPointer(true)}}, r) + if (err != nil) != tc.wantError { + t.Fatalf("%s: err=%v wantError=%t", tc.description, err, tc.wantError) + } + }) + } +} + +func TestTreatmentMetricBooleanOverrides(t *testing.T) { + for _, enabled := range []bool{false, true} { + name := "disable" + if enabled { + name = "enable" + } + t.Run(name, func(t *testing.T) { + b := &TreatmentBackend{Enabled: true, Table: 10002, Priority: 30000, Protocol: 242} + m := &xtcpnl.RouteMetrics{Present: 1<= 32766 || b.Protocol < 192 { + return errors.New("reserve a private table >=256, priority 1..32765 and protocol 192..255") + } + return nil +} + +func withTreatmentSource(ctx context.Context, fn func(*NetlinkSource, *service.Service) error) error { + if err := ctx.Err(); err != nil { + return err + } + s, err := OpenNetlinkTimeout(2 * time.Second) + if err != nil { + return err + } + defer s.Close() + seq := uint32(0) + svc := service.New(s, func() uint32 { seq++; return seq }) + return fn(s, svc) +} + +func (b *TreatmentBackend) rule(family uint8, del bool) ([]byte, error) { + args := []string{"priority", fmt.Sprint(b.Priority), "table", fmt.Sprint(b.Table), "ipproto", "tcp", "iif", "lo", "protocol", fmt.Sprint(b.Protocol)} + typ := uint16(unix.RTM_NEWRULE) + flags := uint16(unix.NLM_F_ACK | unix.NLM_F_CREATE | unix.NLM_F_EXCL) + if del { + typ = unix.RTM_DELRULE + flags = unix.NLM_F_ACK + } + w, err := parseRuleWriteArgs(family, typ, args) + if err != nil { + return nil, err + } + return xtcpnl.BuildWriteRequest(typ, flags, 1, w.frh(), w.attrs.Bytes()) +} + +func (b *TreatmentBackend) ownRule(r xtcpnl.RuleInfo) bool { + raw, err := b.rule(r.Family, false) + if err != nil { + return false + } + want, err := xtcpnl.ParseRule(raw[16:]) + clearDefaultSuppressors(&r) + clearDefaultSuppressors(&want) + // FRA_TABLE is authoritative; Linux emits RT_TABLE_COMPAT (252) in + // the legacy byte for a table ID that cannot fit there. + r.RawTable = 0 + want.RawTable = 0 + return err == nil && reflect.DeepEqual(r, want) +} + +func clearDefaultSuppressors(r *xtcpnl.RuleInfo) { + if r.HasSuppressPrefixlen && r.SuppressPrefixlen == ^uint32(0) { + r.HasSuppressPrefixlen = false + r.SuppressPrefixlen = 0 + } + if r.HasSuppressIfgroup && r.SuppressIfgroup == ^uint32(0) { + r.HasSuppressIfgroup = false + r.SuppressIfgroup = 0 + } +} + +func normalRule(r xtcpnl.RuleInfo) bool { + if !(r.Priority == 0 && r.Table == unix.RT_TABLE_LOCAL || r.Priority == 32766 && r.Table == unix.RT_TABLE_MAIN || r.Priority == 32767 && r.Table == unix.RT_TABLE_DEFAULT) { + return false + } + if r.Action != unix.FR_ACT_TO_TBL { + return false + } + clearDefaultSuppressors(&r) + // Kernel-generated default rules may carry FRA_PROTOCOL=kernel. + r.Family = 0 + r.Table = 0 + r.RawTable = 0 + r.Priority = 0 + r.HasPriority = false + r.Action = 0 + r.Protocol = 0 + r.HasProtocol = false + return reflect.DeepEqual(r, xtcpnl.RuleInfo{}) +} + +func (b *TreatmentBackend) checkRules(svc *service.Service, family uint8) error { + if e, ok := b.ruleCache[family]; ok { + return e + } + rs, err := svc.Rules(family) + if err != nil { + return err + } + local, main := false, false + owned := 0 + for _, v := range rs { + r := xtcpnl.RuleInfo(v) + if b.ownRule(r) { + owned++ + continue + } + if !normalRule(r) { + return errors.New("unsupported policy routing or reserved rule collision") + } + local = local || r.Table == unix.RT_TABLE_LOCAL + main = main || r.Table == unix.RT_TABLE_MAIN + } + if !local || !main || owned > 1 { + return errors.New("missing/duplicate baseline routing rules") + } + if b.ruleCache != nil { + b.ruleCache[family] = nil + } + return nil +} + +func (b *TreatmentBackend) Check(ctx context.Context) error { + if err := b.valid(); err != nil { + return err + } + b.BeginReconcile() + return withTreatmentSource(ctx, func(_ *NetlinkSource, svc *service.Service) error { + for _, f := range []uint8{unix.AF_INET, unix.AF_INET6} { + if err := b.checkRules(svc, f); err != nil { + return err + } + } + return nil + }) +} + +func (b *TreatmentBackend) Plan(ctx context.Context, t *RouteTreatment) (out []byte, err error) { + if err = b.valid(); err != nil { + return nil, err + } + if t == nil || t.Settings == nil { + return nil, errors.New("missing route settings") + } + p, err := netip.ParsePrefix(t.Destination) + if err != nil || p != p.Masked() || p.Bits() != p.Addr().BitLen() || p.Addr().Is4In6() || !p.Addr().IsGlobalUnicast() || p.Addr().IsLoopback() { + return nil, errors.New("expected canonical unicast host prefix") + } + ip := p.Addr() + for _, x := range b.Protected { + if x.Contains(ip) { + return nil, errors.New("protected destination") + } + } + if c := t.Settings.GetCongestionControl(); c != "" { + available, e := os.ReadFile("/proc/sys/net/ipv4/tcp_available_congestion_control") + if e != nil { + return nil, e + } + if !slices.Contains(strings.Fields(string(available)), c) { + return nil, fmt.Errorf("congestion algorithm %q not loaded", c) + } + } + family := uint8(unix.AF_INET) + if ip.Is6() { + family = unix.AF_INET6 + } + err = withTreatmentSource(ctx, func(_ *NetlinkSource, svc *service.Service) error { + if e := b.checkRules(svc, family); e != nil { + return e + } + locals, e := b.routeBodies(svc, family, unix.RT_TABLE_LOCAL) + if e != nil { + return e + } + for _, raw := range locals { + r, e := xtcpnl.ParseNewRoute(raw) + if e != nil { + return e + } + if a, ok := netip.AddrFromSlice(r.Dst); ok && netip.PrefixFrom(a, int(r.DstLen)).Contains(ip) { + return errors.New("destination is local/broadcast") + } + } + routes, e := b.routeBodies(svc, family, unix.RT_TABLE_MAIN) + if e != nil { + return e + } + var best *xtcpnl.RouteInfo + var bestRaw []byte + ambiguous := false + for _, v := range routes { + r, e := xtcpnl.ParseNewRoute(v) + if e != nil { + return e + } + a, ok := netip.AddrFromSlice(r.Dst) + if r.DstLen == 0 { + if family == unix.AF_INET { + a = netip.IPv4Unspecified() + } else { + a = netip.IPv6Unspecified() + } + ok = true + } + if !ok || !netip.PrefixFrom(a, int(r.DstLen)).Contains(ip) { + continue + } + if best == nil || r.DstLen > best.DstLen || r.DstLen == best.DstLen && r.Priority < best.Priority { + best = &r + bestRaw = v + ambiguous = false + } else if r.DstLen == best.DstLen && r.Priority == best.Priority { + ambiguous = true + } + } + if best == nil || ambiguous { + return errors.New("no unique baseline route") + } + unsupported := false + if e = xtcpnl.WalkRTAttrs(bestRaw[12:], func(typ uint16, _ []byte) { + switch typ { + case unix.RTA_DST, unix.RTA_GATEWAY, unix.RTA_OIF, unix.RTA_PREFSRC, unix.RTA_PRIORITY, unix.RTA_PREF, unix.RTA_CACHEINFO, unix.RTA_TABLE, unix.RTA_METRICS: + default: + unsupported = true + } + }); e != nil { + return e + } + if unsupported { + return errors.New("baseline route has unsupported attributes") + } + out, e = b.planRoute(t, *best) + return e + }) + return out, err +} + +// planRoute deliberately rejects route forms whose forwarding semantics this +// actuator cannot preserve. It never invents a gateway or alters the main table. +func (b *TreatmentBackend) planRoute(t *RouteTreatment, r xtcpnl.RouteInfo) ([]byte, error) { + if r.Type != unix.RTN_UNICAST || r.Oif == 0 || r.HasMultipath || r.HasVia || r.NhID != 0 || r.SrcLen != 0 || r.Tos != 0 || r.Flags != 0 || r.HasExpires || r.Metrics != nil && r.Metrics.Unknown != 0 { + return nil, errors.New("unsupported baseline route") + } + ip := netip.MustParsePrefix(t.Destination).Addr() + w := xtcpnl.RouteWrite{MsgType: unix.RTM_NEWROUTE, Flags: unix.NLM_F_ACK | unix.NLM_F_CREATE | unix.NLM_F_EXCL, Family: r.Family, DstLen: uint8(ip.BitLen()), Protocol: b.Protocol, Scope: r.Scope, Type: unix.RTN_UNICAST} + w.Attrs = []xtcpnl.RouteAttr{xtcpnl.RouteAttrBytes(unix.RTA_DST, ip.AsSlice()), xtcpnl.RouteAttrU32(unix.RTA_TABLE, b.Table), xtcpnl.RouteAttrU32(unix.RTA_OIF, r.Oif)} + if len(r.Gateway) > 0 { + w.Attrs = append(w.Attrs, xtcpnl.RouteAttrBytes(unix.RTA_GATEWAY, r.Gateway)) + } + if len(r.PrefSrc) > 0 { + w.Attrs = append(w.Attrs, xtcpnl.RouteAttrBytes(unix.RTA_PREFSRC, r.PrefSrc)) + } + if r.HasPriority { + w.Attrs = append(w.Attrs, xtcpnl.RouteAttrU32(unix.RTA_PRIORITY, r.Priority)) + } + if r.HasPref { + w.Attrs = append(w.Attrs, xtcpnl.RouteAttrBytes(unix.RTA_PREF, []byte{r.Pref})) + } + m := xtcpnl.RouteMetrics{} + if r.Metrics != nil { + m = *r.Metrics + } + set := func(k uint16, v uint32) { m.Present |= 1 << k; m.Values[k] = v } + s := t.Settings + if s.CongestionControl != nil { + m.CcAlgo = *s.CongestionControl + m.Present |= 1 << unix.RTAX_CC_ALGO + lock := m.Values[unix.RTAX_LOCK] &^ (1 << unix.RTAX_CC_ALGO) + if s.CongestionLock { + lock |= 1 << unix.RTAX_CC_ALGO + } + set(unix.RTAX_LOCK, lock) + } + if s.RtoMinMs != nil { + set(unix.RTAX_RTO_MIN, *s.RtoMinMs) + set(unix.RTAX_LOCK, m.Values[unix.RTAX_LOCK]|1< 0 { + msg, err := b.rule(f, !enable) + if err != nil { + return err + } + for range max(1, owned) { + if err = s.ApplyRouteControl(msg); err != nil { + return err + } + } + } + rs, err = svc.Rules(f) + if err != nil { + return err + } + owned = 0 + for _, r := range rs { + if b.ownRule(xtcpnl.RuleInfo(r)) { + owned++ + } + } + if enable && owned != 1 || !enable && owned != 0 { + return errors.New("rule readback mismatch") + } + } + return nil + }) +} +func (b *TreatmentBackend) Enable(ctx context.Context) error { return b.rules(ctx, true) } +func (b *TreatmentBackend) Disable(ctx context.Context) error { return b.rules(ctx, false) } diff --git a/internal/goip/treatment_linux_test.go b/internal/goip/treatment_linux_test.go new file mode 100644 index 000000000..cda9b52a4 --- /dev/null +++ b/internal/goip/treatment_linux_test.go @@ -0,0 +1,79 @@ +package goip + +import ( + "context" + "os" + "testing" + + "github.com/randomizedcoder/xtcp2/pkg/xtcpnl" + "golang.org/x/sys/unix" +) + +func routeTestPointer[T any](v T) *T { return &v } + +func TestTreatmentRoutePreservesPathAndMetrics(t *testing.T) { + b := &TreatmentBackend{Table: 10002, Priority: 30000, Protocol: 242, Enabled: true} + r := xtcpnl.RouteInfo{Family: unix.AF_INET, Table: unix.RT_TABLE_MAIN, Type: unix.RTN_UNICAST, Oif: 4, Gateway: []byte{192, 0, 2, 254}, PrefSrc: []byte{192, 0, 2, 10}, Metrics: &xtcpnl.RouteMetrics{Present: 1 << unix.RTAX_MTU}} + r.Metrics.Values[unix.RTAX_MTU] = 1400 + treatment := &RouteTreatment{Destination: "198.51.100.10/32", Settings: &TCPRouteSettings{CongestionControl: routeTestPointer("cubic"), RtoMinMs: routeTestPointer(uint32(5)), QuickAck: routeTestPointer(true), InitialCongestionWindow: routeTestPointer(uint32(100)), InitialReceiveWindow: routeTestPointer(uint32(100))}} + raw, e := b.planRoute(treatment, r) + if e != nil { + t.Fatal(e) + } + got, e := xtcpnl.ParseNewRoute(raw) + if e != nil { + t.Fatal(e) + } + if got.Table != 10002 || got.Protocol != 242 || got.Oif != 4 || got.Metrics.Values[unix.RTAX_MTU] != 1400 || got.Metrics.Values[unix.RTAX_RTO_MIN] != 5 || got.Metrics.CcAlgo != "cubic" { + t.Fatalf("wrong route %+v", got) + } + for metric, want := range map[uint16]uint32{unix.RTAX_INITCWND: 100, unix.RTAX_INITRWND: 100, unix.RTAX_QUICKACK: 1} { + if !got.Metrics.Has(metric) || got.Metrics.Values[metric] != want { + t.Fatalf("metric %d: got %d, want %d", metric, got.Metrics.Values[metric], want) + } + } + if got.Metrics.Values[unix.RTAX_LOCK]&(1< per-soak duration (default 1h). Ignored without --soak." \ "" \ " Prints a PASS/FAIL/TIMEOUT summary; exits non-zero if any flavor failed." \ @@ -937,6 +956,7 @@ let [ -z "$label" ] && continue run_job "$label" "$bin" --duration "$DURATION" done < <(printf '%s\n' "$SOAK_JOBS") + run_job "tcp-controller-soak" "${lib.getExe tcpControllerSoak}" fi echo "" @@ -992,6 +1012,8 @@ in # IA4-T01 of cmd/go-link-monitor/IP-ARGV-PATHS-IMPLEMENTATION-PLAN.md: # `nix run .#test-goip-argv-paths-vm`, which needs /dev/kvm or TCG. test-goip-argv-paths-vm = vmTests.goip-argv-paths; + test-tcp-controller-vm = vmTests.tcp-controller; + test-tcp-controller-soak-vm = tcpControllerSoak; test-linkmonitor-embedding-vm = vmTests.linkmonitor-embedding; test-linkmonitor-performance-vm = linkmonitorPerformanceVm; test-linkmonitor-rdma-vm = vmTests.linkmonitor-rdma; @@ -1073,6 +1095,9 @@ in # `nix build .#test-go-unit`, etc. test-go-unit = tests.go-unit; test-go-bench = tests.go-bench; + test-tcp-controller-unit = tests.tcp-controller-quality.unit; + test-tcp-controller-race = tests.tcp-controller-quality.race; + bench-tcp-controller = tests.tcp-controller-quality.bench; test-go-race = tests.go-race; test-listener-security = tests.listener-security; test-proto-deserialize-golden = tests.proto-deserialize-golden; @@ -1141,6 +1166,9 @@ in # sandbox (extra-sandbox-paths); without it, it fails with # VM_CHECK_NO_TUN. See linkmonitor-ionic.nix. vm-all = pkgs.linkFarm "vm-all" (lib.mapAttrsToList (name: path: { inherit name path; }) vmChecks); + vm-tcp-controller-soak = tcpControllerSoak.vmCheck.check; + tcp-controller-unit = tests.tcp-controller-quality.unit; + tcp-controller-race = tests.tcp-controller-quality.race; # Race-detector + per-flavor builds. These run as part of # `nix flake check` so a flavor-tag regression (e.g. dest_kafka @@ -1171,6 +1199,14 @@ in // (lib.filterAttrs (n: _v: lib.hasPrefix "test-go-flavor-" n) tests); apps = { + test-tcp-controller-vm = { + type = "app"; + program = "${vmTests.tcp-controller}/bin/test-tcp-controller-vm"; + }; + test-tcp-controller-soak-vm = { + type = "app"; + program = "${tcpControllerSoak}/bin/test-tcp-controller-soak-vm"; + }; regen-protos = { type = "app"; program = "${protos.regenerate}/bin/regen-protos"; diff --git a/nix/modules/xtcp2-service.nix b/nix/modules/xtcp2-service.nix index 2ec37f830..b8ca3ffe4 100644 --- a/nix/modules/xtcp2-service.nix +++ b/nix/modules/xtcp2-service.nix @@ -70,6 +70,19 @@ in ''; }; + treatments = { + enable = lib.mkEnableOption "reviewed TCP route treatments and the independent expiry watchdog"; + configFile = lib.mkOption { + type = lib.types.str; + default = "/etc/xtcp2/controller-agent.json"; + description = "Runtime agent JSON file; keep private keys outside the Nix store."; + }; + watchdogPackage = lib.mkOption { + type = lib.types.package; + description = "Package providing bin/xtcp2-treatmentctl, required when treatments are enabled."; + }; + }; + linkMonitor = { enable = lib.mkOption { type = lib.types.bool; @@ -188,7 +201,11 @@ in # `extraArgs` (CLI flags) instead. configFile is kept in the option # surface for forward-compatibility — when the daemon learns to # parse a JSON config, flip this back to `-config ${cfg.configFile}`. - ExecStart = xtcp2Exec.mkExecStart cfg.package (linkMonitorArgs ++ cfg.extraArgs); + ExecStart = xtcp2Exec.mkExecStart cfg.package ( + linkMonitorArgs + ++ lib.optionals cfg.treatments.enable [ "-treatmentConfig=${cfg.treatments.configFile}" ] + ++ cfg.extraArgs + ); # `systemctl reload xtcp2` asks the link monitor to relearn the # expected up-link count, as SIGUSR1 does for go-link-monitor. ExecReload = "${pkgs.coreutils}/bin/kill -USR1 $MAINPID"; @@ -220,5 +237,34 @@ in LimitNPROC = 8192; }; }; + + # Deliberately independent of xtcp2.service: daemon crashes must not leave + # trial routes installed past their local lease deadline. + systemd.services.xtcp2-treatment-watchdog = lib.mkIf cfg.treatments.enable { + description = "xtcp2 route treatment lease watchdog"; + wantedBy = [ "multi-user.target" ]; + after = [ "local-fs.target" ]; + serviceConfig = { + Type = "simple"; + User = cfg.user; + StateDirectory = "xtcp2"; + StateDirectoryMode = "0750"; + ExecStart = xtcp2Exec.escapeSystemdExecArgs [ + "${cfg.treatments.watchdogPackage}/bin/xtcp2-treatmentctl" + "-mode=watchdog" + "-config=${cfg.treatments.configFile}" + ]; + Restart = "always"; + RestartSec = "2s"; + AmbientCapabilities = [ + "CAP_NET_ADMIN" + "CAP_SYS_PTRACE" + ]; + CapabilityBoundingSet = [ + "CAP_NET_ADMIN" + "CAP_SYS_PTRACE" + ]; + }; + }; }; } diff --git a/nix/tests/default.nix b/nix/tests/default.nix index 502297bed..05c25ccff 100644 --- a/nix/tests/default.nix +++ b/nix/tests/default.nix @@ -36,6 +36,7 @@ in { go-unit = import ./go-unit.nix { inherit pkgs vendoredSource; }; go-bench = import ./go-bench.nix { inherit pkgs vendoredSource; }; + tcp-controller-quality = import ./tcp-controller/quality.nix { inherit pkgs vendoredSource; }; # No lib: that runner has no such argument any more. This file's own lib arg # stays — focused-quality.nix, both go-test-* imports and the mapAttrs' below # all need it. diff --git a/nix/tests/tcp-controller/default.nix b/nix/tests/tcp-controller/default.nix new file mode 100644 index 000000000..c0a8d4e36 --- /dev/null +++ b/nix/tests/tcp-controller/default.nix @@ -0,0 +1,394 @@ +# Go-driven, rootless seven-guest integration using the existing TCP load tools. +{ + pkgs, + lib, + nixpkgs, + binaries, + vendoredSource, + soak ? false, +}: +let + name = if soak then "tcp-controller-soak" else "tcp-controller"; + agents = lib.genList (i: "agent${toString (i + 1)}") 6; + controllerAddress = "192.168.1.254"; + versions = import ../../versions.nix { inherit pkgs; }; + harness = pkgs.runCommand "tcp-controller-integration" { nativeBuildInputs = [ versions.go ]; } '' + cp -r ${vendoredSource} source + chmod -R u+w source + cd source + export GOTOOLCHAIN=local GOFLAGS=-mod=vendor CGO_ENABLED=0 + export GOCACHE="$TMPDIR/go-cache" GOMODCACHE="$TMPDIR/go-modcache" + mkdir -p "$out/bin" + go build -trimpath -o "$out/bin/tcp-controller-integration" ./tools/tcp-controller-integration + ''; + pki = pkgs.writeShellApplication { + name = "controller-test-pki"; + runtimeInputs = [ + pkgs.openssl + pkgs.coreutils + ]; + text = '' + mkdir -p /var/lib/controller-test/pki + cd /var/lib/controller-test/pki + umask 077 + openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes \ + -keyout ca.key -out ca.crt -days 2 -subj /CN=xtcp-integration-CA + issue() { + local name=$1 san=$2 usage=$3 + openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes \ + -keyout "$name.key" -out "$name.csr" -subj "/CN=$name" + printf 'subjectAltName=%s\nextendedKeyUsage=%s\nkeyUsage=digitalSignature\n' "$san" "$usage" > "$name.ext" + openssl x509 -req -in "$name.csr" -CA ca.crt -CAkey ca.key -CAcreateserial \ + -out "$name.crt" -days 2 -extfile "$name.ext" + } + issue controller 'DNS:controller,IP:${controllerAddress}' serverAuth + issue operator 'URI:spiffe://xtcp/operator/integration' clientAuth + for agent in ${lib.escapeShellArgs agents}; do + issue "$agent" "URI:spiffe://xtcp/agent/test-dc/$agent" clientAuth + done + ''; + }; + fixturePath = [ + pkgs.iproute2 + pkgs.coreutils + pkgs.systemd + pkgs.procps + binaries.xtcp2-treatmentctl + ]; + base = { + system.stateVersion = "26.05"; + networking.firewall.enable = false; + networking.useDHCP = false; + networking.usePredictableInterfaceNames = false; + services.timesyncd.enable = false; + virtualisation = { + graphics = false; + memorySize = 768; + cores = 1; + diskImage = null; + qemu.options = [ "-no-reboot" ]; + }; + boot.kernelModules = [ + "sch_netem" + "sch_prio" + "cls_u32" + "tcp_cubic" + ]; + environment.etc."xtcp-controller-integration".text = "Disposable VM fixture only.\n"; + environment.systemPackages = fixturePath; + }; + # Point-to-point UNIX datagram NICs form a star, bridged inside the control + # guest. No host bridge, tap, root privileges or external network is needed. + nic = + index: controller: + let + localName = if controller then "controller" else "agent"; + remoteName = if controller then "agent" else "controller"; + macPrefix = if controller then "01" else "02"; + in + [ + "-netdev dgram,id=link${toString index},local.type=unix,local.path=$TEST_NETWORK/${localName}${toString index},remote.type=unix,remote.path=$TEST_NETWORK/${remoteName}${toString index}" + "-device virtio-net-pci,netdev=link${toString index},mac=02:00:00:${macPrefix}:00:0${toString index}" + ]; + mkVM = + name: config: + (nixpkgs.lib.nixosSystem { + system = pkgs.stdenv.hostPlatform.system; + modules = [ + "${nixpkgs}/nixos/modules/virtualisation/qemu-vm.nix" + base + { + nixpkgs.pkgs = pkgs; + networking.hostName = name; + } + config + ]; + }).config.system.build.vm; + agentVM = + index: + let + name = "agent${toString index}"; + host = toString (10 + index); + in + mkVM name { + imports = [ ../../modules/xtcp2-service.nix ]; + virtualisation.qemu.networkingOptions = lib.mkForce (nic index false); + networking.interfaces.eth0.ipv4.addresses = [ + { + address = "192.168.1.${host}"; + prefixLength = 24; + } + { + address = "198.18.0.${host}"; + prefixLength = 24; + } + ]; + boot.kernel.sysctl."net.ipv4.tcp_congestion_control" = "reno"; + environment.etc."xtcp2/controller-agent.json".text = builtins.toJSON { + agent_id = name; + location = "test-dc"; + controller = "${controllerAddress}:9443"; + server_name = "controller"; + certificate = "/var/lib/controller-test/pki/agent.crt"; + private_key = "/var/lib/controller-test/pki/agent.key"; + ca = "/var/lib/controller-test/pki/ca.crt"; + enable_apply = true; + protected_prefixes = [ "192.168.1.0/24" ]; + }; + services.xtcp2 = { + enable = true; + package = binaries.xtcp2-min; + configFile = "/etc/xtcp2/unused.json"; + linkMonitor.enable = false; + treatments = { + enable = true; + watchdogPackage = binaries.xtcp2-treatmentctl; + }; + extraArgs = [ + "-frequency=2s" + "-timeout=1s" + "-pollJitterPct=0" + "-marshal=protobufList" + "-dest=http://${controllerAddress}:8123/?query=INSERT+INTO+xtcp.ingest+FORMAT+ProtobufList&format_schema=xtcp_flat_record.proto:XtcpFlatRecord" + ]; + }; + # The Go fixture installs runtime credentials before starting the daemon. + systemd.services.xtcp2.wantedBy = lib.mkForce [ ]; + systemd.services.xtcp2-treatment-watchdog.wantedBy = lib.mkForce [ ]; + systemd.services.controller-test-agent = { + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + path = fixturePath; + serviceConfig.ExecStart = "${harness}/bin/tcp-controller-integration -role=agent -listen=192.168.1.${host}:9009"; + }; + systemd.services.load-near.serviceConfig = { + EnvironmentFile = "/var/lib/controller-test/load.env"; + ExecStart = "${binaries.tcp_client}/bin/tcp_client -count 32 -connect $TCP_TEST_DESTINATION -sleep 50ms -startsleep 5ms -pads 1024 -rto 3s -wto 3s"; + Restart = "always"; + RestartSec = 1; + StandardOutput = "null"; + StandardError = "null"; + }; + systemd.services.load-far.serviceConfig = { + ExecStart = "${binaries.tcp_client}/bin/tcp_client -count 32 -connect 198.18.0.2 -sleep 50ms -startsleep 5ms -pads 1024 -rto 3s -wto 3s"; + Restart = "always"; + RestartSec = 1; + StandardOutput = "null"; + StandardError = "null"; + }; + }; + agentVMs = lib.genList (i: agentVM (i + 1)) 6; + controllerVM = mkVM "controller" { + virtualisation.memorySize = lib.mkForce 4096; + virtualisation.cores = lib.mkForce 2; + virtualisation.qemu.networkingOptions = lib.mkForce (lib.concatMap (i: nic i true) (lib.range 1 6)); + virtualisation.sharedDirectories.evidence = { + source = "$VM_EVIDENCE/artifacts"; + target = "/var/lib/controller-test/evidence"; + writable = true; + }; + networking.bridges.br0.interfaces = lib.genList (i: "eth${toString i}") 6; + networking.interfaces.br0.ipv4.addresses = [ + { + address = controllerAddress; + prefixLength = 24; + } + { + address = "198.18.0.1"; + prefixLength = 24; + } + { + address = "198.18.0.2"; + prefixLength = 24; + } + ] + ++ lib.optionals soak ( + lib.genList (i: { + address = "198.18.0.${toString (101 + i)}"; + prefixLength = 24; + }) 6 + ); + services.postgresql = { + enable = true; + package = pkgs.postgresql_15; + ensureDatabases = [ "xtcp" ]; + ensureUsers = [ + { + name = "root"; + ensureClauses.superuser = true; + } + ]; + }; + services.clickhouse = { + enable = true; + serverConfig = { + listen_host = "0.0.0.0"; + format_schema_path = "/etc/xtcp-test/schemas/"; + max_server_memory_usage = 1500000000; + background_pool_size = 4; + background_merges_mutations_concurrency_ratio = 8; + }; + usersConfig.profiles.default = { + max_threads = 2; + max_memory_usage = 1000000000; + }; + }; + systemd.services.clickhouse.serviceConfig.TimeoutStopSec = "10s"; + environment.etc."xtcp-test/schemas/xtcp_flat_record.proto".source = + ../../../proto/xtcp_flat_record/v1/xtcp_flat_record.proto; + environment.etc."xtcp-test/schema.sql".source = + ../../../build/containers/clickhouse/initdb.d/sql/xtcp_xtcp_flat_records.sql; + environment.etc."xtcp-test/discovery.json".text = builtins.toJSON [ + { + id = "soak-nearby-cubic"; + location = "test-dc"; + congestion_control = "cubic"; + rto_min_ms = 5; + quick_ack = true; + initial_congestion_window = 100; + initial_receive_window = 100; + # A 10% guard at 5ms allows only ~0.5ms of VM scheduling variation. + # Keep this fixture tolerant of small changes but require withdrawal + # for the deliberate 5ms -> 180ms regression. Production stays at 10%. + maximum_rtt_increase = 1.0; + minimum_rtt_ms = 0; + maximum_rtt_ms = 15; + minimum_retransmission_ratio = 0; + prefixes = lib.genList (i: "198.18.0.${toString (101 + i)}/32") 6; + } + ]; + systemd.services.controller-test-pki = { + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStart = lib.getExe pki; + }; + }; + systemd.services.xtcp-controller = { + after = [ + "postgresql.service" + "postgresql-setup.service" + "controller-test-pki.service" + ]; + requires = [ + "postgresql.service" + "controller-test-pki.service" + ]; + environment.XTCP_CONTROLLER_DATABASE_DSN = "host=/run/postgresql dbname=xtcp user=root sslmode=disable"; + serviceConfig = { + ExecStart = + "${binaries.xtcp2-controller}/bin/xtcp2-controller -migrate -cert=/var/lib/controller-test/pki/controller.crt -key=/var/lib/controller-test/pki/controller.key -ca=/var/lib/controller-test/pki/ca.crt -clickhouse=http://127.0.0.1:8123" + + lib.optionalString soak " -discoveryTemplates=/etc/xtcp-test/discovery.json -discoveryInterval=1m -discoveryWindow=1m"; + Restart = "on-failure"; + }; + }; + systemd.services.tcp-echo = { + wantedBy = [ "multi-user.target" ]; + serviceConfig.ExecStart = "${binaries.tcp_server}/bin/tcp_server -count 32 -bind 0.0.0.0"; + serviceConfig.StandardOutput = "null"; + serviceConfig.StandardError = "null"; + }; + systemd.services.controller-integration = { + wantedBy = [ "multi-user.target" ]; + after = [ + "network-online.target" + "postgresql.service" + "postgresql-setup.service" + "clickhouse.service" + "controller-test-pki.service" + "tcp-echo.service" + ]; + wants = [ "network-online.target" ]; + requires = [ + "postgresql.service" + "clickhouse.service" + "controller-test-pki.service" + "tcp-echo.service" + ]; + path = fixturePath ++ [ + pkgs.clickhouse + pkgs.postgresql_15 + ]; + script = '' + trap 'systemctl poweroff --no-block' EXIT + ${harness}/bin/tcp-controller-integration -role=controller ${lib.optionalString soak "-soak"} + ''; + serviceConfig = { + Type = "oneshot"; + StandardOutput = "journal+console"; + StandardError = "journal+console"; + TimeoutStartSec = if soak then "115min" else "30min"; + }; + }; + }; + vmCheck = import ../../microvms/mkVmCheck.nix { inherit pkgs lib; } { + inherit name; + timeout = if soak then "120m" else "35m"; + launch = '' + mkdir -p links "$VM_EVIDENCE/artifacts" + ${lib.optionalString soak '' + printf '{"cycles":%s,"interval_seconds":%s}\n' \ + "''${XTCP_SOAK_CYCLES:-3}" "''${XTCP_SOAK_INTERVAL_SECONDS:-180}" > "$VM_EVIDENCE/artifacts/soak-config.json" + ''} + export TEST_NETWORK="$PWD/links" + pids=() + cleanup() { + kill "''${pids[@]}" 2>/dev/null || true + wait "''${pids[@]}" 2>/dev/null || true + } + trap cleanup EXIT + ${lib.concatImapStringsSep "\n" (i: vm: '' + mkdir agent${toString i} + (cd agent${toString i}; exec ${vm}/bin/run-agent${toString i}-vm) > "$VM_EVIDENCE/agent${toString i}.log" 2>&1 & + pids+=("$!") + '') agentVMs} + mkdir controller + cd controller + ${controllerVM}/bin/run-controller-vm + ''; + sentinels = + if soak then + [ + "TCP_CONTROLLER_SOAK_CYCLE_1_PASS" + "TCP_CONTROLLER_SOAK_CYCLE_2_PASS" + "TCP_CONTROLLER_SOAK_COOLDOWN_PASS" + "TCP_CONTROLLER_SOAK_PASS" + ] + else + [ + "TCP_CONTROLLER_RTT_PASS" + "TCP_CONTROLLER_CANARY_PASS" + "TCP_CONTROLLER_PERSIST_PASS" + "TCP_CONTROLLER_HOLD_PASS" + "TCP_CONTROLLER_ROLLBACK_PASS" + "TCP_CONTROLLER_WATCHDOG_PASS" + "TCP_CONTROLLER_PASS" + ]; + failPattern = "TCP_CONTROLLER_FAIL"; + evidence = { + inherit harness; + controller = controllerVM; + }; + }; + soakApp = pkgs.writeShellApplication { + name = "test-tcp-controller-soak-vm"; + text = '' + export XTCP_SOAK_CYCLES=3 XTCP_SOAK_INTERVAL_SECONDS=180 + args=() + for arg in "$@"; do + case "$arg" in + --cycles=[2-6]) XTCP_SOAK_CYCLES=''${arg#--cycles=} ;; + --interval=3m) XTCP_SOAK_INTERVAL_SECONDS=180 ;; + --interval=4m) XTCP_SOAK_INTERVAL_SECONDS=240 ;; + --interval=5m) XTCP_SOAK_INTERVAL_SECONDS=300 ;; + --accel=*|--keep=*) args+=("$arg") ;; + *) echo 'usage: test-tcp-controller-soak-vm [--cycles=2..6] [--interval=3m|4m|5m] [--accel=auto|kvm|tcg] [--keep=N]' >&2; exit 2 ;; + esac + done + exec ${vmCheck.app}/bin/test-tcp-controller-soak-vm "''${args[@]}" + ''; + }; +in +(if soak then soakApp else vmCheck.app) // { inherit vmCheck; } diff --git a/nix/tests/tcp-controller/quality.nix b/nix/tests/tcp-controller/quality.nix new file mode 100644 index 000000000..022de9115 --- /dev/null +++ b/nix/tests/tcp-controller/quality.nix @@ -0,0 +1,57 @@ +# Focused Go checks with real disposable database fixtures. No VM or root needed. +{ pkgs, vendoredSource }: +let + versions = import ../../versions.nix { inherit pkgs; }; + check = + race: + pkgs.runCommand "xtcp2-controller-${if race then "race" else "unit"}" + { + nativeBuildInputs = [ + versions.go + pkgs.gcc + pkgs.postgresql + ]; + } + '' + cp -r ${vendoredSource} source + chmod -R u+w source + cd source + export GOTOOLCHAIN=local GOFLAGS=-mod=vendor CGO_ENABLED=1 + export GOCACHE="$TMPDIR/go-cache" GOMODCACHE="$TMPDIR/go-modcache" + mkdir -p "$out" "$TMPDIR/pg-socket" + initdb -D "$TMPDIR/pg-data" -A trust --no-locale > "$out/postgres-init.log" 2>&1 + trap 'pg_ctl -D "$TMPDIR/pg-data" -m immediate -w stop >/dev/null 2>&1 || true' EXIT + pg_ctl -D "$TMPDIR/pg-data" -l "$out/postgres.log" \ + -o "-k $TMPDIR/pg-socket -h ''' -p 55439" -w start + export XTCP_CONTROLLER_TEST_DSN="host=$TMPDIR/pg-socket port=55439 dbname=postgres sslmode=disable" + export XTCP_CLICKHOUSE_TEST_BINARY=${pkgs.clickhouse}/bin/clickhouse + go test -ldflags=-checklinkname=0 ${ + if race then "-race -count=3" else "-count=1 -coverprofile=$out/coverage.out" + } \ + -timeout 5m -v ./pkg/treatment/... ./tools/tcp-controller-integration \ + > "$out/tests.log" 2>&1 || { cat "$out/tests.log"; exit 1; } + # These fixtures must execute, never silently skip in the focused checks. + grep -q -- '--- PASS: TestPostgresConcurrentReviewFencesReplicas' "$out/tests.log" + grep -q -- '--- PASS: TestClickHouseAnalysisCounterDeltas' "$out/tests.log" + go test -ldflags=-checklinkname=0 ${if race then "-race -count=3" else "-count=1"} \ + -timeout 5m -v -run '^TestTreatment' ./internal/goip > "$out/routes.log" 2>&1 \ + || { cat "$out/routes.log"; exit 1; } + ${if race then "" else ''go tool cover -func "$out/coverage.out" > "$out/coverage.txt"''} + ''; +in +{ + unit = check false; + race = check true; + bench = pkgs.runCommand "xtcp2-controller-bench" { nativeBuildInputs = [ versions.go ]; } '' + cp -r ${vendoredSource} source + chmod -R u+w source + cd source + export GOTOOLCHAIN=local GOFLAGS=-mod=vendor CGO_ENABLED=0 + export GOCACHE="$TMPDIR/go-cache" GOMODCACHE="$TMPDIR/go-modcache" + mkdir -p "$out" + go test -ldflags=-checklinkname=0 -run '^$' -bench . -benchmem -benchtime=1s -count=5 \ + ./pkg/treatment/... > "$out/benchmarks.txt" 2>&1 || { cat "$out/benchmarks.txt"; exit 1; } + go test -ldflags=-checklinkname=0 -run '^$' -bench '^BenchmarkTreatment' -benchmem -benchtime=1s -count=5 \ + ./internal/goip >> "$out/benchmarks.txt" 2>&1 || { cat "$out/benchmarks.txt"; exit 1; } + ''; +} diff --git a/nix/versions.nix b/nix/versions.nix index d4ecc5f80..26472f17f 100644 --- a/nix/versions.nix +++ b/nix/versions.nix @@ -171,5 +171,5 @@ # Go vendor hash. Update by running `nix build .#xtcp2` and pasting the # `got:` value from the hash mismatch error. Used by every Nix check that # needs deps in the sandbox (see nix/lib/goModules.nix). - goVendorHash = "sha256-WaTwMyuIhe3qJUCzdSTcSF/AfJ2yKlaa5DAL/5yCPzc="; + goVendorHash = "sha256-zWkXGRXmqKiugOVjzZoG1g1FVrCNXSZwaiSMultjTGI="; } diff --git a/pkg/treatment/agent/agent.go b/pkg/treatment/agent/agent.go new file mode 100644 index 000000000..646658dd2 --- /dev/null +++ b/pkg/treatment/agent/agent.go @@ -0,0 +1,382 @@ +package agent + +import ( + "context" + "crypto/rand" + "crypto/x509" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "log" + "math/big" + "net" + "net/netip" + "os" + "strings" + "sync" + "sync/atomic" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + flat "github.com/randomizedcoder/xtcp2/gen/go/xtcp_flat_record" + "github.com/randomizedcoder/xtcp2/internal/goip" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller" + "golang.org/x/sys/unix" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/protobuf/proto" +) + +type Config struct { + AgentID string `json:"agent_id"` + Location string `json:"location"` + Controller string `json:"controller"` + ServerName string `json:"server_name"` + Certificate string `json:"certificate"` + PrivateKey string `json:"private_key"` + CA string `json:"ca"` + Directory string `json:"directory"` + EnableApply bool `json:"enable_apply"` + Table uint32 `json:"table"` + RulePriority uint32 `json:"rule_priority"` + Protocol uint8 `json:"protocol"` + ProtectedPrefixes []string `json:"protected_prefixes"` +} + +func LoadConfig(path string) (Config, error) { + var c Config + f, e := os.Open(path) + if e != nil { + return c, e + } + defer f.Close() + d := json.NewDecoder(io.LimitReader(f, 65537)) + d.DisallowUnknownFields() + if e = d.Decode(&c); e != nil { + return c, e + } + var extra any + if d.Decode(&extra) != io.EOF { + return c, errors.New("trailing config data") + } + if !treatment.ValidID(c.AgentID) || !treatment.ValidID(c.Location) || c.Controller == "" { + return c, errors.New("agent_id, location and controller are required") + } + if c.Directory == "" { + c.Directory = "/var/lib/xtcp2/routes" + } + if c.Table == 0 { + c.Table = 10002 + } + if c.RulePriority == 0 { + c.RulePriority = 30000 + } + if c.Protocol == 0 { + c.Protocol = 242 + } + return c, nil +} + +type Agent struct { + Config Config + Reconciler *treatment.Reconciler + backend *routeBackend + client pb.AgentControlServiceClient + conn *grpc.ClientConn + state atomic.Pointer[pb.LocalState] + exposure atomic.Pointer[exposureSnapshot] + wake chan struct{} + collectionID string + namespace uint64 +} + +type exposureSnapshot struct { + revision uint64 + held bool + routes map[netip.Addr]*pb.ApplicationRecord +} + +// NewLocal creates the offline emergency/watchdog path. It intentionally does +// not read TLS credentials, resolve DNS, or connect to the controller. +func NewLocal(c Config) (*Agent, error) { + return newLocal(c, localIdentity) +} + +// Separate host inspection from construction so configuration and lifecycle +// tests do not depend on permission to inspect PID 1's network namespace. +func localIdentity() (string, uint64, error) { + boot, err := os.ReadFile("/proc/sys/kernel/random/boot_id") + if err != nil { + return "", 0, err + } + var ns, initNS unix.Stat_t + if err = unix.Stat("/proc/self/ns/net", &ns); err != nil { + return "", 0, err + } + if err = unix.Stat("/proc/1/ns/net", &initNS); err != nil { + return "", 0, err + } + if ns.Ino != initNS.Ino { + return "", 0, errors.New("actuator must run in the host network namespace") + } + return strings.TrimSpace(string(boot)), ns.Ino, nil +} + +func newLocal(c Config, identity func() (string, uint64, error)) (*Agent, error) { + if !treatment.ValidID(c.AgentID) || !treatment.ValidID(c.Location) { + return nil, errors.New("invalid local agent identity") + } + boot, namespace, e := identity() + if e != nil { + return nil, e + } + b := &routeBackend{TreatmentBackend: &goip.TreatmentBackend{Table: c.Table, Priority: c.RulePriority, Protocol: c.Protocol, Enabled: c.EnableApply}} + for _, v := range c.ProtectedPrefixes { + p, e := netip.ParsePrefix(v) + if e != nil { + return nil, e + } + b.Protected = append(b.Protected, p) + } + if c.EnableApply && len(b.Protected) == 0 { + return nil, errors.New("protected_prefixes must cover management, DNS and telemetry networks before enabling application") + } + var nonce [16]byte + if _, e = rand.Read(nonce[:]); e != nil { + return nil, e + } + a := &Agent{Config: c, backend: b, wake: make(chan struct{}, 1), namespace: namespace, collectionID: hex.EncodeToString(nonce[:])} + a.Reconciler = &treatment.Reconciler{Journal: treatment.Journal{Dir: c.Directory}, Backend: b, AgentID: c.AgentID, BootID: boot} + return a, nil +} + +// New validates identity before constructing any mutation-capable path. DNS +// resolution is only needed to add management destinations to the exclusions; +// deployments should list stable management prefixes for offline reboot use. +func New(ctx context.Context, c Config) (*Agent, error) { + tlsConfig, e := controller.TLSConfig(c.Certificate, c.PrivateKey, c.CA, c.ServerName, false) + if e != nil { + return nil, e + } + cert, e := x509.ParseCertificate(tlsConfig.Certificates[0].Certificate[0]) + if e != nil { + return nil, e + } + id, e := controller.CertificateIdentity(cert) + if e != nil { + return nil, e + } + if id.Role != "agent" || id.ID != c.AgentID || id.Location != c.Location { + return nil, errors.New("agent config does not match certificate identity") + } + a, e := NewLocal(c) + if e != nil { + return nil, e + } + b := a.backend + host, _, e := net.SplitHostPort(c.Controller) + if e != nil { + return nil, e + } + lookupCtx, cancel := context.WithTimeout(ctx, 3*time.Second) + defer cancel() + if ip, e := netip.ParseAddr(host); e == nil { + b.Protected = append(b.Protected, netip.PrefixFrom(ip, ip.BitLen())) + } else if ips, e := net.DefaultResolver.LookupNetIP(lookupCtx, "ip", host); e == nil { + for _, ip := range ips { + ip = ip.Unmap() + b.Protected = append(b.Protected, netip.PrefixFrom(ip, ip.BitLen())) + } + } + conn, e := grpc.NewClient(c.Controller, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)), grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(treatment.MaxStateBytes), grpc.MaxCallSendMsgSize(treatment.MaxStateBytes))) + if e != nil { + return nil, e + } + a.conn = conn + a.client = pb.NewAgentControlServiceClient(conn) + return a, nil +} +func (a *Agent) Close() error { + if a.conn == nil { + return nil + } + return a.conn.Close() +} +func (a *Agent) Wake() { + select { + case a.wake <- struct{}{}: + default: + } +} +func (a *Agent) State() *pb.LocalState { + s := a.state.Load() + if s == nil { + return nil + } + return proto.Clone(s).(*pb.LocalState) +} + +func (a *Agent) reconcile(ctx context.Context, d *pb.DesiredState) { + ctx, cancel := context.WithTimeout(ctx, 15*time.Second) + defer cancel() + s, e := a.Reconciler.Run(ctx, d, false) + if s != nil { + a.publish(s) + } + if e != nil { + log.Printf("treatment reconcile: %v", e) + } +} + +func (a *Agent) publish(s *pb.LocalState) { + index := &exposureSnapshot{revision: s.GetDesired().GetRevision(), held: s.LocallySuspended || s.GetDesired().GetSuspended(), routes: map[netip.Addr]*pb.ApplicationRecord{}} + for _, v := range s.Records { + if v.Status == pb.ApplyStatus_APPLY_STATUS_VERIFIED && v.Assignment != nil && v.AppliedAt != nil { + p, e := netip.ParsePrefix(v.Assignment.Treatment.Destination) + if e == nil { + index.routes[p.Addr()] = v + } + } + } + a.state.Store(s) + a.exposure.Store(index) +} + +func (a *Agent) Run(ctx context.Context) { + var workers sync.WaitGroup + workers.Add(1) + go func() { defer workers.Done(); a.watchRouting(ctx) }() + defer workers.Wait() + a.reconcile(ctx, nil) + watchStarted := false + for { + a.reconcile(ctx, nil) + callCtx, cancel := context.WithTimeout(ctx, 10*time.Second) + r := &pb.CheckInRequest{AgentId: a.Config.AgentID, Location: a.Config.Location, Capabilities: a.capabilities(callCtx)} + if s := a.state.Load(); s != nil { + r.DesiredRevision = s.GetDesired().GetRevision() + r.LocallySuspended = s.LocallySuspended + for _, record := range s.Records { + v := proto.Clone(record).(*pb.ApplicationRecord) + v.InstalledRoute = nil + v.InstalledBootId = "" + r.Records = append(r.Records, v) + } + } + response, e := a.client.CheckIn(callCtx, r) + cancel() + if e != nil { + log.Printf("treatment check-in: %v", e) + } else { + if response.Desired != nil { + a.reconcile(ctx, response.Desired) + } + if !watchStarted { + watchStarted = true + workers.Add(1) + go func() { defer workers.Done(); a.watch(ctx) }() + } + } + jitter, _ := rand.Int(rand.Reader, big.NewInt(6000)) + delay := 27 * time.Second + if jitter != nil { + delay += time.Duration(jitter.Int64()) * time.Millisecond + } + timer := time.NewTimer(delay) + select { + case <-ctx.Done(): + timer.Stop() + return + case <-a.wake: + timer.Stop() + case <-timer.C: + } + } +} + +func (a *Agent) watch(ctx context.Context) { + for ctx.Err() == nil { + revision := uint64(0) + if s := a.state.Load(); s != nil { + revision = s.GetDesired().GetRevision() + } + stream, e := a.client.WatchDesiredState(ctx, &pb.WatchDesiredStateRequest{AgentId: a.Config.AgentID, AfterRevision: revision}) + if e == nil { + for { + _, e = stream.Recv() + if e != nil { + break + } + a.Wake() + } + } + t := time.NewTimer(5 * time.Second) + select { + case <-ctx.Done(): + t.Stop() + return + case <-t.C: + } + } +} + +func (a *Agent) capabilities(ctx context.Context) *pb.Capabilities { + c := &pb.Capabilities{BootId: a.Reconciler.BootID, ActuatorEnabled: a.Config.EnableApply} + var u unix.Utsname + if unix.Uname(&u) == nil { + c.KernelRelease = unix.ByteSliceToString(u.Release[:]) + } + if data, e := os.ReadFile("/proc/sys/net/ipv4/tcp_available_congestion_control"); e == nil { + c.CongestionAlgorithms = strings.Fields(string(data)) + } + c.SimpleHostRouting = a.backend.Check(ctx) == nil + return c +} + +func (a *Agent) Stamp(r *flat.XtcpFlatRecord) { + r.AgentId = a.Config.AgentID + // Experiment location is certificate-bound, so it must not depend on a + // second independently configured telemetry label. + r.Location = a.Config.Location + r.HostBootId = a.Reconciler.BootID + r.CollectionId = a.collectionID + r.TreatmentStateRevision = 0 + r.TreatmentAssignmentId = "" + r.TreatmentGeneration = 0 + r.TreatmentAppliedTimestampNs = 0 + r.TreatmentAlgorithmExposed = false + s := a.exposure.Load() + if s == nil { + return + } + r.TreatmentStateRevision = s.revision + if s.held || r.NetnsInode != a.namespace { + return + } + raw := r.InetDiagMsgSocketDestination + if r.InetDiagMsgFamily == unix.AF_INET && len(raw) >= 4 { + raw = raw[:4] + } + ip, ok := netip.AddrFromSlice(raw) + if !ok { + return + } + if v := s.routes[ip]; v != nil { + a := v.Assignment + if a.Mode == pb.AssignmentMode_ASSIGNMENT_MODE_TRIAL && (a.LeaseExpiresAt == nil || !a.LeaseExpiresAt.AsTime().After(time.Now())) { + return + } + r.TreatmentAssignmentId = a.Id + r.TreatmentGeneration = a.Generation + r.TreatmentAppliedTimestampNs = v.AppliedAt.AsTime().UnixNano() + r.TreatmentAlgorithmExposed = a.Treatment.Settings.GetCongestionControl() != "" && r.InetDiagCong == a.Treatment.Settings.GetCongestionControl() + return + } +} + +func (c Config) String() string { + return fmt.Sprintf("agent=%s location=%s controller=%s apply=%t", c.AgentID, c.Location, c.Controller, c.EnableApply) +} diff --git a/pkg/treatment/agent/agent_test.go b/pkg/treatment/agent/agent_test.go new file mode 100644 index 000000000..6a73a5c4b --- /dev/null +++ b/pkg/treatment/agent/agent_test.go @@ -0,0 +1,41 @@ +package agent + +import ( + "testing" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + flat "github.com/randomizedcoder/xtcp2/gen/go/xtcp_flat_record" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func TestStampDistinguishesAssignedAndActuallyExposed(t *testing.T) { + a := &Agent{Config: Config{AgentID: "host-1"}, namespace: 123, collectionID: "session", Reconciler: &treatment.Reconciler{BootID: "boot-1"}} + s := &pb.LocalState{Desired: &pb.DesiredState{Revision: 2}, Records: []*pb.ApplicationRecord{{Status: pb.ApplyStatus_APPLY_STATUS_VERIFIED, AppliedAt: timestamppb.Now(), Assignment: &pb.Assignment{Id: "exp-1", Generation: 1, Mode: pb.AssignmentMode_ASSIGNMENT_MODE_STABLE, Treatment: &pb.Treatment{Destination: "192.0.2.1/32", Settings: &pb.TCPSettings{CongestionControl: proto.String("cubic")}}}}}} + a.publish(s) + r := &flat.XtcpFlatRecord{NetnsInode: 123, InetDiagMsgFamily: 2, InetDiagMsgSocketDestination: []byte{192, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, InetDiagCong: "bbr"} + a.Stamp(r) + if r.TreatmentAssignmentId != "exp-1" || r.TreatmentAlgorithmExposed { + t.Fatal("assignment mistaken for exposure") + } + r.InetDiagCong = "cubic" + a.Stamp(r) + if !r.TreatmentAlgorithmExposed { + t.Fatal("matching algorithm not attributed") + } + r.NetnsInode = 124 + a.Stamp(r) + if r.TreatmentAssignmentId != "" || r.TreatmentAlgorithmExposed { + t.Fatal("host route attributed to another namespace") + } + r.NetnsInode = 123 + s.Records[0].Assignment.Mode = pb.AssignmentMode_ASSIGNMENT_MODE_TRIAL + s.Records[0].Assignment.LeaseExpiresAt = timestamppb.New(time.Now().Add(-time.Second)) + a.publish(s) + a.Stamp(r) + if r.TreatmentAlgorithmExposed { + t.Fatal("expired trial remained exposed") + } +} diff --git a/pkg/treatment/agent/backend_linux.go b/pkg/treatment/agent/backend_linux.go new file mode 100644 index 000000000..01c1054ed --- /dev/null +++ b/pkg/treatment/agent/backend_linux.go @@ -0,0 +1,22 @@ +package agent + +import ( + "context" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "github.com/randomizedcoder/xtcp2/internal/goip" + "github.com/randomizedcoder/xtcp2/pkg/treatment" +) + +type routeBackend struct{ *goip.TreatmentBackend } + +func (b *routeBackend) Plan(ctx context.Context, t *pb.Treatment) ([]byte, error) { + if e := treatment.ValidateTreatment(t); e != nil { + return nil, e + } + s := t.Settings + return b.TreatmentBackend.Plan(ctx, &goip.RouteTreatment{Destination: t.Destination, Settings: &goip.TCPRouteSettings{ + CongestionControl: s.CongestionControl, CongestionLock: s.CongestionLock, RtoMinMs: s.RtoMinMs, + InitialCongestionWindow: s.InitialCongestionWindow, InitialReceiveWindow: s.InitialReceiveWindow, + QuickAck: s.QuickAck, Ecn: s.Ecn, + }}) +} diff --git a/pkg/treatment/agent/benchmark_test.go b/pkg/treatment/agent/benchmark_test.go new file mode 100644 index 000000000..d60cccaf6 --- /dev/null +++ b/pkg/treatment/agent/benchmark_test.go @@ -0,0 +1,46 @@ +package agent + +import ( + "fmt" + "net/netip" + "testing" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + flat "github.com/randomizedcoder/xtcp2/gen/go/xtcp_flat_record" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func stampFixture(n int) (*Agent, *pb.LocalState, *flat.XtcpFlatRecord) { + a := &Agent{Config: Config{AgentID: "host", Location: "dc"}, namespace: 123, collectionID: "session", Reconciler: &treatment.Reconciler{BootID: "boot"}, wake: make(chan struct{}, 1)} + s := &pb.LocalState{Desired: &pb.DesiredState{Revision: 1}} + for i := range n { + ip := netip.AddrFrom4([4]byte{192, 0, byte(i >> 8), byte(i)}) + s.Records = append(s.Records, &pb.ApplicationRecord{Status: pb.ApplyStatus_APPLY_STATUS_VERIFIED, AppliedAt: timestamppb.New(time.Unix(1700000000, 0)), Assignment: &pb.Assignment{Id: fmt.Sprintf("assignment-%d", i), Generation: 1, Mode: pb.AssignmentMode_ASSIGNMENT_MODE_STABLE, Treatment: &pb.Treatment{Destination: ip.String() + "/32", Settings: &pb.TCPSettings{CongestionControl: proto.String("cubic")}}}}) + } + a.publish(s) + r := &flat.XtcpFlatRecord{NetnsInode: 123, InetDiagMsgFamily: 2, InetDiagMsgSocketDestination: []byte{192, 0, 0, 0}, InetDiagCong: "cubic"} + return a, s, r +} + +func BenchmarkStamp(b *testing.B) { + for _, n := range []int{1, 100, 4096} { + for _, match := range []bool{true, false} { + b.Run(fmt.Sprintf("routes=%d/match=%t", n, match), func(b *testing.B) { + a, _, r := stampFixture(n) + if !match { + r.InetDiagMsgSocketDestination = []byte{198, 51, 100, 1} + } + b.ReportAllocs() + for b.Loop() { + a.Stamp(r) + } + if r.TreatmentAlgorithmExposed != match { + b.Fatal("wrong exposure") + } + }) + } + } +} diff --git a/pkg/treatment/agent/contracts_test.go b/pkg/treatment/agent/contracts_test.go new file mode 100644 index 000000000..ff93723e4 --- /dev/null +++ b/pkg/treatment/agent/contracts_test.go @@ -0,0 +1,170 @@ +package agent + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "sync" + "testing" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + flat "github.com/randomizedcoder/xtcp2/gen/go/xtcp_flat_record" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func TestLoadConfigContracts(t *testing.T) { + for _, tc := range []struct{ name, description, input, wantError string }{ + {"defaults", "minimal config receives safe defaults", `{"agent_id":"host","location":"dc","controller":"localhost:9000"}`, ""}, + {"explicit", "explicit private route settings are preserved", `{"agent_id":"host","location":"dc","controller":"localhost:9000","table":10003,"rule_priority":30001,"protocol":243}`, ""}, + {"unknown", "typos cannot silently disable settings", `{"agent_id":"host","location":"dc","controller":"localhost:9000","enable_aply":true}`, "unknown field"}, + {"trailing", "two JSON documents are rejected", `{} {}`, "trailing"}, + {"missing", "enrollment fields are required", `{}`, "required"}, + {"bad-id", "path-like agent identity is rejected", `{"agent_id":"../host","location":"dc","controller":"localhost:9000"}`, "required"}, + {"malformed", "truncated JSON is rejected", `{`, "EOF"}, + {"oversized", "oversized config cannot bypass the limit", `{"agent_id":"host","location":"dc","controller":"` + strings.Repeat("x", 65537) + `"}`, "EOF"}, + } { + t.Run(tc.name, func(t *testing.T) { + path := filepath.Join(t.TempDir(), "agent.json") + if err := os.WriteFile(path, []byte(tc.input), 0600); err != nil { + t.Fatal(err) + } + c, err := LoadConfig(path) + if tc.wantError != "" { + if err == nil || !strings.Contains(err.Error(), tc.wantError) { + t.Fatalf("%s: expected %q, got %v", tc.description, tc.wantError, err) + } + return + } + if err != nil { + t.Fatal(err) + } + wantTable, wantPriority, wantProtocol := uint32(10002), uint32(30000), uint8(242) + if tc.name == "explicit" { + wantTable++ + wantPriority++ + wantProtocol++ + } + if c.Table != wantTable || c.RulePriority != wantPriority || c.Protocol != wantProtocol || c.EnableApply || c.Directory != "/var/lib/xtcp2/routes" { + t.Fatalf("%s: unexpected config %+v", tc.description, c) + } + }) + } +} + +func TestStampContracts(t *testing.T) { + for _, tc := range []struct { + name, description string + change func(*pb.LocalState, *flat.XtcpFlatRecord) + wantAssignment, wantExposed bool + }{ + {"match", "verified matching algorithm is exposed", func(*pb.LocalState, *flat.XtcpFlatRecord) {}, true, true}, + {"different-algorithm", "assigned is distinct from exposed", func(_ *pb.LocalState, r *flat.XtcpFlatRecord) { r.InetDiagCong = "bbr" }, true, false}, + {"foreign-netns", "host routes do not apply in other namespaces", func(_ *pb.LocalState, r *flat.XtcpFlatRecord) { r.NetnsInode++ }, false, false}, + {"unknown-destination", "unmatched socket has no treatment", func(_ *pb.LocalState, r *flat.XtcpFlatRecord) { + r.InetDiagMsgSocketDestination = []byte{198, 51, 100, 1} + }, false, false}, + {"malformed-address", "short addresses are ignored", func(_ *pb.LocalState, r *flat.XtcpFlatRecord) { r.InetDiagMsgSocketDestination = []byte{192} }, false, false}, + {"local-hold", "local hold clears attribution", func(s *pb.LocalState, _ *flat.XtcpFlatRecord) { s.LocallySuspended = true }, false, false}, + {"fleet-hold", "controller hold clears attribution", func(s *pb.LocalState, _ *flat.XtcpFlatRecord) { s.Desired.Suspended = true }, false, false}, + {"failed", "failed application is never attributed", func(s *pb.LocalState, _ *flat.XtcpFlatRecord) { + s.Records[0].Status = pb.ApplyStatus_APPLY_STATUS_FAILED + }, false, false}, + {"no-applied-time", "unapplied record is not attributed", func(s *pb.LocalState, _ *flat.XtcpFlatRecord) { s.Records[0].AppliedAt = nil }, false, false}, + {"expired", "expired trial loses attribution", func(s *pb.LocalState, _ *flat.XtcpFlatRecord) { + a := s.Records[0].Assignment + a.Mode = pb.AssignmentMode_ASSIGNMENT_MODE_TRIAL + a.LeaseExpiresAt = timestamppb.New(time.Unix(1, 0)) + }, false, false}, + {"trial", "unexpired trial is exposed", func(s *pb.LocalState, _ *flat.XtcpFlatRecord) { + a := s.Records[0].Assignment + a.Mode = pb.AssignmentMode_ASSIGNMENT_MODE_TRIAL + a.LeaseExpiresAt = timestamppb.New(time.Now().Add(time.Hour)) + }, true, true}, + {"ipv6", "IPv6 host route matches 16-byte socket address", func(s *pb.LocalState, r *flat.XtcpFlatRecord) { + s.Records[0].Assignment.Treatment.Destination = "2001:db8::1/128" + r.InetDiagMsgFamily = 10 + r.InetDiagMsgSocketDestination = []byte{0x20, 1, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} + }, true, true}, + {"ipv4-padded", "kernel padded IPv4 addresses match", func(_ *pb.LocalState, r *flat.XtcpFlatRecord) { + r.InetDiagMsgSocketDestination = append(r.InetDiagMsgSocketDestination, make([]byte, 12)...) + }, true, true}, + } { + t.Run(tc.name, func(t *testing.T) { + a, s, r := stampFixture(1) + tc.change(s, r) + a.publish(s) + r.TreatmentAssignmentId = "old" + r.TreatmentAlgorithmExposed = true + r.TreatmentGeneration = 99 + a.Stamp(r) + if (r.TreatmentAssignmentId != "") != tc.wantAssignment || r.TreatmentAlgorithmExposed != tc.wantExposed { + t.Fatalf("%s: assignment=%q exposed=%t; want %t/%t", tc.description, r.TreatmentAssignmentId, r.TreatmentAlgorithmExposed, tc.wantAssignment, tc.wantExposed) + } + if !tc.wantAssignment && (r.TreatmentGeneration != 0 || r.TreatmentAppliedTimestampNs != 0) { + t.Fatal("stale attribution was not cleared") + } + if r.AgentId != "host" || r.Location != "dc" || r.HostBootId != "boot" || r.CollectionId != "session" || r.TreatmentStateRevision != 1 { + t.Fatal("collection identity missing") + } + }) + } +} + +// Reconciliation publishes immutable snapshots while socket workers stamp +// independent records and RPC check-ins clone the current state. +func TestConcurrentPublishStampState(t *testing.T) { + a, s, _ := stampFixture(16) + var wg sync.WaitGroup + wg.Go(func() { + for i := range 200 { + next := proto.Clone(s).(*pb.LocalState) + next.Desired.Revision = uint64(i + 2) + a.publish(next) + a.Wake() + } + }) + for range 8 { + wg.Go(func() { + r := &flat.XtcpFlatRecord{NetnsInode: 123, InetDiagMsgFamily: 2, InetDiagMsgSocketDestination: []byte{192, 0, 0, 0}, InetDiagCong: "cubic"} + for range 200 { + a.Stamp(r) + if r.TreatmentAssignmentId != "assignment-0" || !r.TreatmentAlgorithmExposed { + t.Error("inconsistent exposure snapshot") + return + } + state := a.State() + state.Records[0].Assignment.Id = "caller mutation" + a.Wake() + } + }) + } + wg.Wait() + if a.State().Records[0].Assignment.Id != "assignment-0" { + t.Fatal("State leaked mutable internal snapshot") + } + select { + case <-a.wake: + default: + t.Fatal("Wake dropped every notification") + } + select { + case <-a.wake: + t.Fatal("Wake did not coalesce") + default: + } +} + +func BenchmarkPublish(b *testing.B) { + for _, n := range []int{1, 100, 4096} { + b.Run(fmt.Sprint(n), func(b *testing.B) { + a, s, _ := stampFixture(n) + b.ReportAllocs() + for b.Loop() { + a.publish(s) + } + }) + } +} diff --git a/pkg/treatment/agent/lifecycle_test.go b/pkg/treatment/agent/lifecycle_test.go new file mode 100644 index 000000000..b7a6bf695 --- /dev/null +++ b/pkg/treatment/agent/lifecycle_test.go @@ -0,0 +1,119 @@ +package agent + +import ( + "context" + "errors" + "strings" + "testing" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "google.golang.org/grpc" + "google.golang.org/protobuf/types/known/timestamppb" +) + +type lifecycleClient struct { + pb.AgentControlServiceClient + t *testing.T + cancel context.CancelFunc + fail bool + checked, watched bool // inspected only after Run has joined all workers +} + +func (c *lifecycleClient) CheckIn(_ context.Context, r *pb.CheckInRequest, _ ...grpc.CallOption) (*pb.CheckInResponse, error) { + c.checked = true + if r.AgentId != "host" || r.Location != "dc" || r.Capabilities.BootId == "" || r.Capabilities.ActuatorEnabled { + c.t.Error("incorrect check-in identity/capabilities") + } + for _, v := range r.Records { + if len(v.InstalledRoute) != 0 || v.InstalledBootId != "" { + c.t.Error("local kernel state leaked into RPC") + } + } + if c.fail { + c.cancel() + return nil, errors.New("controller offline") + } + return &pb.CheckInResponse{Desired: &pb.DesiredState{AgentId: "host", Revision: 7, IssuedAt: timestamppb.Now()}}, nil +} +func (c *lifecycleClient) WatchDesiredState(_ context.Context, r *pb.WatchDesiredStateRequest, _ ...grpc.CallOption) (grpc.ServerStreamingClient[pb.WatchDesiredStateResponse], error) { + c.watched = true + if r.AgentId != "host" || r.AfterRevision != 7 { + c.t.Error("watch did not resume from reconciled revision") + } + c.cancel() + return nil, errors.New("stream disconnected") +} + +func TestLocalAgentContracts(t *testing.T) { + for _, tc := range []struct { + name, description string + change func(*Config) + wantError string + }{ + {"offline", "offline control does not require TLS or DNS", func(*Config) {}, ""}, + {"identity", "invalid agent identity fails before initialization", func(c *Config) { c.AgentID = "../host" }, "identity"}, + {"bad-prefix", "malformed protected prefixes fail closed", func(c *Config) { c.ProtectedPrefixes = []string{"bad"} }, "ParsePrefix"}, + {"no-exclusions", "application requires management exclusions", func(c *Config) { c.EnableApply = true }, "protected_prefixes"}, + {"protected", "explicit exclusions allow backend construction without writes", func(c *Config) { c.EnableApply = true; c.ProtectedPrefixes = []string{"192.0.2.0/24"} }, ""}, + } { + t.Run(tc.name, func(t *testing.T) { + c := Config{AgentID: "host", Location: "dc", Directory: t.TempDir(), Table: 10002, RulePriority: 30000, Protocol: 242} + tc.change(&c) + a, err := newLocal(c, testLocalIdentity) + if tc.wantError != "" { + if err == nil || !strings.Contains(err.Error(), tc.wantError) { + t.Fatalf("%s: got %v want %q", tc.description, err, tc.wantError) + } + return + } + if err != nil { + t.Fatal(err) + } + defer a.Close() + if a.State() != nil || a.namespace == 0 || len(a.collectionID) != 32 || a.Reconciler.BootID == "" { + t.Fatal("invalid initial identity/state") + } + if !strings.Contains(c.String(), "agent=host") { + t.Fatal("config diagnostic missing identity") + } + }) + } +} + +func TestAgentRunCancellation(t *testing.T) { + for _, fail := range []bool{false, true} { + name := "connected" + if fail { + name = "offline" + } + t.Run(name, func(t *testing.T) { + a, err := newLocal(Config{AgentID: "host", Location: "dc", Directory: t.TempDir(), Table: 10002, RulePriority: 30000, Protocol: 242}, testLocalIdentity) + if err != nil { + t.Fatal(err) + } + defer a.Close() + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + c := &lifecycleClient{t: t, cancel: cancel, fail: fail} + a.client = c + a.Run(ctx) + if !c.checked || c.watched == fail { + t.Fatalf("check-in/watch lifecycle: checked=%t watched=%t", c.checked, c.watched) + } + if !fail && a.State().GetDesired().GetRevision() != 7 { + t.Fatal("desired state was not reconciled") + } + }) + } +} + +func testLocalIdentity() (string, uint64, error) { return "boot", 123, nil } + +func TestLocalIdentityFailure(t *testing.T) { + sentinel := errors.New("identity unavailable") + a, err := newLocal(Config{AgentID: "host", Location: "dc"}, func() (string, uint64, error) { return "", 0, sentinel }) + if a != nil || !errors.Is(err, sentinel) { + t.Fatalf("identity failure must prevent construction: %v %v", a, err) + } +} diff --git a/pkg/treatment/agent/watch_linux.go b/pkg/treatment/agent/watch_linux.go new file mode 100644 index 000000000..4affe3405 --- /dev/null +++ b/pkg/treatment/agent/watch_linux.go @@ -0,0 +1,50 @@ +package agent + +import ( + "context" + "log" + + "golang.org/x/sys/unix" +) + +func (a *Agent) watchRouting(ctx context.Context) { + fd, e := unix.Socket(unix.AF_NETLINK, unix.SOCK_RAW|unix.SOCK_CLOEXEC|unix.SOCK_NONBLOCK, unix.NETLINK_ROUTE) + if e != nil { + log.Printf("treatment route watch unavailable: %v; periodic reconciliation remains enabled", e) + return + } + defer unix.Close(fd) + groups := uint32(unix.RTMGRP_LINK | unix.RTMGRP_IPV4_IFADDR | unix.RTMGRP_IPV6_IFADDR | unix.RTMGRP_IPV4_ROUTE | unix.RTMGRP_IPV6_ROUTE | unix.RTMGRP_IPV4_RULE) + // RTNLGRP_IPV6_RULE is a group number; sockaddr_nl expects its bitmask. + groups |= 1 << (unix.RTNLGRP_IPV6_RULE - 1) + if e = unix.Bind(fd, &unix.SockaddrNetlink{Family: unix.AF_NETLINK, Groups: groups}); e != nil { + log.Printf("treatment route watch bind: %v", e) + return + } + buf := make([]byte, 64<<10) + for ctx.Err() == nil { + p := []unix.PollFd{{Fd: int32(fd), Events: unix.POLLIN}} + _, e := unix.Poll(p, 1000) + if e == unix.EINTR { + continue + } + if e != nil { + log.Printf("treatment route watch: %v", e) + return + } + if p[0].Revents == 0 { + continue + } + _, from, e := unix.Recvfrom(fd, buf, 0) + if e == unix.EAGAIN { + continue + } + if e != nil { + a.Wake() + continue + } // ENOBUFS requires a complete resnapshot. + if peer, ok := from.(*unix.SockaddrNetlink); ok && peer.Pid == 0 { + a.Wake() + } + } +} diff --git a/pkg/treatment/benchmark_test.go b/pkg/treatment/benchmark_test.go new file mode 100644 index 000000000..2ce0fc54f --- /dev/null +++ b/pkg/treatment/benchmark_test.go @@ -0,0 +1,64 @@ +package treatment + +import ( + "fmt" + "testing" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "google.golang.org/protobuf/proto" +) + +func fleetFixture(n int) (*pb.Experiment, *pb.Evidence, time.Time) { + now := time.Unix(1700000000, 0) + e, v := experimentFixture(now) + e.CohortOrder, e.Baseline.Hosts, v.Hosts = nil, nil, nil + e.Phases[0].TargetHosts = uint32(n / 2) + for i := range n { + id := fmt.Sprintf("host-%d", i) + h := &pb.HostEvidence{AgentId: id, Sockets: 100, ExposedSockets: 100, BytesAcked: 1 << 25, GoodputBps: 1e8, RttP95Ms: 5, CollectionHealthy: true} + e.CohortOrder = append(e.CohortOrder, id) + e.Baseline.Hosts = append(e.Baseline.Hosts, h) + current := proto.Clone(h).(*pb.HostEvidence) + if i < n/2 { + current.GoodputBps *= 1.3 + float64(i%7)*.001 + } + v.Hosts = append(v.Hosts, current) + } + return e, v, now +} + +func BenchmarkFleetEvidence(b *testing.B) { + for _, n := range []int{100, 1000, 10000} { + e, v, now := fleetFixture(n) + for _, op := range []struct { + name string + run func() error + }{ + {"Validate", func() error { return ValidateEvidence(v) }}, + {"Safety", func() error { + h, bad := HealthyEvidence(e, v, now) + if !h || bad { + return fmt.Errorf("unexpected safety result") + } + return nil + }}, + {"Evaluate", func() error { + got, err := Evaluate(e, v, now) + if err == nil && got.Outcome != "beneficial" { + return fmt.Errorf("unexpected outcome %s", got.Outcome) + } + return err + }}, + } { + b.Run(fmt.Sprintf("%s/%d", op.name, n), func(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + if err := op.run(); err != nil { + b.Fatal(err) + } + } + }) + } + } +} diff --git a/pkg/treatment/controller/analysis.sql b/pkg/treatment/controller/analysis.sql new file mode 100644 index 000000000..9387c1fa5 --- /dev/null +++ b/pkg/treatment/controller/analysis.sql @@ -0,0 +1,37 @@ +-- Scheduled whole-window query: an incremental MV cannot correctly compute +-- cross-insert counter deltas. Deduplicate poll timestamps before differences. +WITH sockets AS ( + SELECT agent_id, host_boot_id, netns_inode, inet_diag_msg_socket_cookie, + min(timestamp_ns) AS first_at, max(timestamp_ns) AS last_at, + countDistinct(timestamp_ns) AS samples, + argMin(tcp_info_bytes_acked,timestamp_ns) AS first_acked, + argMax(tcp_info_bytes_acked,timestamp_ns) AS last_acked, + argMin(tcp_info_bytes_sent,timestamp_ns) AS first_sent, + argMax(tcp_info_bytes_sent,timestamp_ns) AS last_sent, + argMin(tcp_info_bytes_retrans,timestamp_ns) AS first_retrans, + argMax(tcp_info_bytes_retrans,timestamp_ns) AS last_retrans, + quantileExact(0.5)(tcp_info_rtt)/1000 AS socket_rtt, + min(treatment_algorithm_exposed AND treatment_assignment_id={assignment:String}) AS exposed, + arrayMax(arrayDifference(arraySort(groupUniqArray(20000)(toUnixTimestamp64Nano(timestamp_ns))))) AS maximum_gap_ns + FROM {{TABLE}} + WHERE timestamp_ns >= fromUnixTimestamp64Nano({start:Int64}) + AND timestamp_ns < fromUnixTimestamp64Nano({end:Int64}) + AND location={location:String} AND agent_id IN {agents:Array(String)} + AND inet_diag_msg_state=1 + AND hex(inet_diag_msg_socket_destination)={destination_hex:String} + GROUP BY agent_id,host_boot_id,netns_inode,inet_diag_msg_socket_cookie +) +SELECT agent_id, + countIf(samples>=2) AS sockets, + sum(if(last_acked>=first_acked,last_acked-first_acked,0)) AS bytes_acked, + bytes_acked*8/(({end:Int64}-{start:Int64})/1e9) AS goodput_bps, + sum(if(last_retrans>=first_retrans,last_retrans-first_retrans,0))/greatest(1,sum(if(last_sent>=first_sent,last_sent-first_sent,0))) AS retransmission_ratio, + quantileExact(0.95)(socket_rtt) AS rtt_p95_ms, + countIf(samples>=2 AND exposed) AS exposed_sockets, + toBool(max(last_at)>=fromUnixTimestamp64Nano({end:Int64}-30000000000) + AND max(maximum_gap_ns)<=30000000000 + AND countIf(last_acked 0 { + v, e := treatment.Evaluate(input, ev, s.now()) + if e != nil { + return nil, failure(e) + } + out.Outcome = v.Outcome + } + e = s.Store.mutate(ctx, func(tx *sql.Tx) error { + current, e := experiment(ctx, tx, n.Id) + if e != nil { + return e + } + if current.Revision != r.ExpectedRevision { + return errors.New("experiment changed during analysis; analyze again") + } + if e = saveEvidence(ctx, tx, n.Id, ev); e != nil { + return e + } + current.LatestEvidence = ev + current.LatestOutcome = out.Outcome + if e = saveExperiment(ctx, tx, current); e != nil { + return e + } + return audit(ctx, tx, actor, "", "analysis", out) + }) + return out, failure(e) +} diff --git a/pkg/treatment/controller/auth.go b/pkg/treatment/controller/auth.go new file mode 100644 index 000000000..11093d375 --- /dev/null +++ b/pkg/treatment/controller/auth.go @@ -0,0 +1,97 @@ +package controller + +import ( + "context" + "crypto/tls" + "crypto/x509" + "errors" + "os" + "strings" + + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/peer" + "google.golang.org/grpc/status" +) + +// Certificates bind agent identity AND enrollment location. Merely presenting +// any certificate from the CA does not confer operator permissions. +// URI SANs: spiffe://xtcp/agent// or spiffe://xtcp/operator/. +type Identity struct{ Role, ID, Location string } + +func CertificateIdentity(cert *x509.Certificate) (Identity, error) { + var found []Identity + for _, u := range cert.URIs { + if u.Scheme != "spiffe" || u.Host != "xtcp" || u.RawQuery != "" || u.Fragment != "" { + continue + } + p := strings.Split(strings.TrimPrefix(u.Path, "/"), "/") + if len(p) == 3 && p[0] == "agent" && treatment.ValidID(p[1]) && treatment.ValidID(p[2]) { + found = append(found, Identity{Role: p[0], Location: p[1], ID: p[2]}) + } + if len(p) == 2 && p[0] == "operator" && treatment.ValidID(p[1]) { + found = append(found, Identity{Role: p[0], ID: p[1]}) + } + } + if len(found) != 1 { + return Identity{}, errors.New("exactly one xtcp identity URI SAN required") + } + return found[0], nil +} +func identity(ctx context.Context) (Identity, error) { + p, ok := peer.FromContext(ctx) + if !ok { + return Identity{}, status.Error(codes.Unauthenticated, "mTLS required") + } + t, ok := p.AuthInfo.(credentials.TLSInfo) + if !ok || len(t.State.VerifiedChains) == 0 || len(t.State.PeerCertificates) == 0 { + return Identity{}, status.Error(codes.Unauthenticated, "verified client certificate required") + } + id, e := CertificateIdentity(t.State.PeerCertificates[0]) + if e != nil { + return Identity{}, status.Error(codes.PermissionDenied, e.Error()) + } + return id, nil +} +func AgentIdentity(ctx context.Context, want string) (Identity, error) { + id, e := identity(ctx) + if e != nil { + return id, e + } + if id.Role != "agent" || id.ID != want { + return Identity{}, status.Error(codes.PermissionDenied, "agent identity mismatch") + } + return id, nil +} +func OperatorIdentity(ctx context.Context) (string, error) { + id, e := identity(ctx) + if e != nil { + return "", e + } + if id.Role != "operator" { + return "", status.Error(codes.PermissionDenied, "operator certificate required") + } + return id.ID, nil +} + +func TLSConfig(certFile, keyFile, caFile, serverName string, server bool) (*tls.Config, error) { + cert, e := tls.LoadX509KeyPair(certFile, keyFile) + if e != nil { + return nil, e + } + ca, e := os.ReadFile(caFile) + if e != nil { + return nil, e + } + roots := x509.NewCertPool() + if !roots.AppendCertsFromPEM(ca) { + return nil, errors.New("CA file has no certificates") + } + c := &tls.Config{MinVersion: tls.VersionTLS13, Certificates: []tls.Certificate{cert}, RootCAs: roots, ServerName: serverName} + if server { + c.ClientCAs = roots + c.ClientAuth = tls.RequireAndVerifyClientCert + } + return c, nil +} diff --git a/pkg/treatment/controller/auth_test.go b/pkg/treatment/controller/auth_test.go new file mode 100644 index 000000000..ba25169d7 --- /dev/null +++ b/pkg/treatment/controller/auth_test.go @@ -0,0 +1,147 @@ +package controller + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "net" + "net/url" + "os" + "path/filepath" + "testing" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/status" + "google.golang.org/grpc/test/bufconn" +) + +type authProbe struct { + pb.UnimplementedAgentControlServiceServer + pb.UnimplementedOperatorServiceServer +} + +func (authProbe) CheckIn(ctx context.Context, r *pb.CheckInRequest) (*pb.CheckInResponse, error) { + _, e := AgentIdentity(ctx, r.AgentId) + return &pb.CheckInResponse{}, e +} +func (authProbe) Inspect(ctx context.Context, _ *pb.InspectRequest) (*pb.InspectResponse, error) { + _, e := OperatorIdentity(ctx) + return &pb.InspectResponse{}, e +} + +func TestMTLSHandshakesAndRoleBoundaries(t *testing.T) { + dir := t.TempDir() + now := time.Now() + caKey, e := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if e != nil { + t.Fatal(e) + } + ca := &x509.Certificate{SerialNumber: big.NewInt(1), Subject: pkix.Name{CommonName: "test CA"}, NotBefore: now.Add(-time.Minute), NotAfter: now.Add(time.Hour), IsCA: true, BasicConstraintsValid: true, KeyUsage: x509.KeyUsageCertSign} + der, e := x509.CreateCertificate(rand.Reader, ca, ca, &caKey.PublicKey, caKey) + if e != nil { + t.Fatal(e) + } + caFile := filepath.Join(dir, "ca.crt") + if e = os.WriteFile(caFile, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), 0600); e != nil { + t.Fatal(e) + } + issue := func(name, identity string, server bool) *tls.Config { + t.Helper() + key, e := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if e != nil { + t.Fatal(e) + } + leaf := &x509.Certificate{SerialNumber: big.NewInt(int64(len(name) + 2)), NotBefore: now.Add(-time.Minute), NotAfter: now.Add(time.Hour), KeyUsage: x509.KeyUsageDigitalSignature} + if server { + leaf.DNSNames = []string{"controller.test"} + leaf.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth} + } else { + u, e := url.Parse(identity) + if e != nil { + t.Fatal(e) + } + leaf.URIs = []*url.URL{u} + leaf.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth} + } + der, e := x509.CreateCertificate(rand.Reader, leaf, ca, &key.PublicKey, caKey) + if e != nil { + t.Fatal(e) + } + keyDER, e := x509.MarshalPKCS8PrivateKey(key) + if e != nil { + t.Fatal(e) + } + certFile, keyFile := filepath.Join(dir, name+".crt"), filepath.Join(dir, name+".key") + if e = os.WriteFile(certFile, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), 0600); e != nil { + t.Fatal(e) + } + if e = os.WriteFile(keyFile, pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}), 0600); e != nil { + t.Fatal(e) + } + config, e := TLSConfig(certFile, keyFile, caFile, "controller.test", server) + if e != nil { + t.Fatal(e) + } + return config + } + listener := bufconn.Listen(1 << 20) + server := grpc.NewServer(grpc.Creds(credentials.NewTLS(issue("server", "", true)))) + pb.RegisterAgentControlServiceServer(server, authProbe{}) + pb.RegisterOperatorServiceServer(server, authProbe{}) + go server.Serve(listener) + t.Cleanup(func() { server.Stop(); listener.Close() }) + connect := func(config *tls.Config) *grpc.ClientConn { + t.Helper() + conn, e := grpc.NewClient("passthrough:///controller.test", grpc.WithTransportCredentials(credentials.NewTLS(config)), grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { return listener.Dial() })) + if e != nil { + t.Fatal(e) + } + t.Cleanup(func() { conn.Close() }) + return conn + } + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + agentTLS := issue("agent", "spiffe://xtcp/agent/dc1/host-1", false) + agent := connect(agentTLS) + if _, e = pb.NewAgentControlServiceClient(agent).CheckIn(ctx, &pb.CheckInRequest{AgentId: "host-1"}); e != nil { + t.Fatal("authenticated agent", e) + } + if _, e = pb.NewAgentControlServiceClient(agent).CheckIn(ctx, &pb.CheckInRequest{AgentId: "host-2"}); status.Code(e) != codes.PermissionDenied { + t.Fatal("cross-host access", e) + } + if _, e = pb.NewOperatorServiceClient(agent).Inspect(ctx, &pb.InspectRequest{}); status.Code(e) != codes.PermissionDenied { + t.Fatal("agent operator access", e) + } + operator := connect(issue("operator", "spiffe://xtcp/operator/alice", false)) + if _, e = pb.NewOperatorServiceClient(operator).Inspect(ctx, &pb.InspectRequest{}); e != nil { + t.Fatal("authenticated operator", e) + } + for name, config := range map[string]*tls.Config{"missing_client_certificate": agentTLS.Clone(), "wrong_server_name": agentTLS.Clone(), "untrusted_server": agentTLS.Clone()} { + t.Run(name, func(t *testing.T) { + switch name { + case "missing_client_certificate": + config.Certificates = nil + case "wrong_server_name": + config.ServerName = "wrong.test" + case "untrusted_server": + config.RootCAs = x509.NewCertPool() + } + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + _, e := pb.NewAgentControlServiceClient(connect(config)).CheckIn(ctx, &pb.CheckInRequest{AgentId: "host-1"}) + if e == nil { + t.Fatal("invalid TLS was accepted") + } + }) + } +} diff --git a/pkg/treatment/controller/clickhouse.go b/pkg/treatment/controller/clickhouse.go new file mode 100644 index 000000000..eb372604c --- /dev/null +++ b/pkg/treatment/controller/clickhouse.go @@ -0,0 +1,148 @@ +package controller + +import ( + "bytes" + "context" + "crypto/sha256" + _ "embed" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/netip" + "net/url" + "regexp" + "strconv" + "strings" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "google.golang.org/protobuf/types/known/timestamppb" +) + +//go:embed analysis.sql +var analysisSQL string +var tableName = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*\.[a-zA-Z_][a-zA-Z0-9_]*$`) + +type ClickHouse struct { + URL, Username, Password, Table string + Client *http.Client + Now func() time.Time +} + +func (c *ClickHouse) now() time.Time { + if c.Now != nil { + return c.Now() + } + return time.Now() +} + +func (c *ClickHouse) query(ctx context.Context, query string, params map[string]string) ([]byte, error) { + if !tableName.MatchString(c.Table) { + return nil, errors.New("ClickHouse table must be database.table") + } + u, e := url.Parse(c.URL) + if e != nil { + return nil, e + } + if u.Scheme != "http" && u.Scheme != "https" { + return nil, errors.New("ClickHouse URL must be HTTP(S)") + } + v := u.Query() + for k, val := range params { + v.Set("param_"+k, val) + } + v.Set("max_execution_time", "30") + v.Set("max_result_rows", "10000") + v.Set("result_overflow_mode", "throw") + v.Set("output_format_json_quote_64bit_integers", "0") + u.RawQuery = v.Encode() + body := strings.ReplaceAll(query, "{{TABLE}}", c.Table) + r, e := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), strings.NewReader(body)) + if e != nil { + return nil, e + } + r.SetBasicAuth(c.Username, c.Password) + client := c.Client + if client == nil { + client = &http.Client{Timeout: 35 * time.Second} + } + response, e := client.Do(r) + if e != nil { + return nil, e + } + defer response.Body.Close() + b, e := io.ReadAll(io.LimitReader(response.Body, 8<<20)) + if e != nil { + return nil, e + } + if len(b) >= 8<<20 { + return nil, errors.New("ClickHouse result exceeds 8 MiB") + } + if response.StatusCode != http.StatusOK { + return nil, fmt.Errorf("ClickHouse status %d: %.1024s", response.StatusCode, b) + } + return b, nil +} + +func (c *ClickHouse) Analyze(ctx context.Context, e *pb.Experiment) (*pb.Evidence, error) { + if len(e.Phases) == 0 { + end := c.now().Add(-20 * time.Second) + return c.Evidence(ctx, e.Location, e.Treatment.Destination, e.Id, e.CohortOrder, end.Add(-time.Hour), end) + } + p := e.Phases[len(e.Phases)-1] + start := p.IssuedAt.AsTime() + if p.ObservationStartedAt != nil { + start = p.ObservationStartedAt.AsTime() + } + return c.Evidence(ctx, e.Location, e.Treatment.Destination, e.Id, e.CohortOrder, start, c.now().Add(-20*time.Second)) +} + +func (c *ClickHouse) Evidence(ctx context.Context, location, destination, assignment string, agents []string, start, end time.Time) (*pb.Evidence, error) { + if !end.After(start) || end.Sub(start) > 7*24*time.Hour || len(agents) == 0 || len(agents) > treatment.MaxAgents { + return nil, errors.New("invalid analysis window/population") + } + quoted := make([]string, len(agents)) + for i, id := range agents { + if !treatment.ValidID(id) { + return nil, errors.New("invalid agent") + } + quoted[i] = "'" + id + "'" + } + p, e := netip.ParsePrefix(destination) + if e != nil { + return nil, e + } + addr := p.Addr().AsSlice() + if p.Addr().Is4() { + addr = append(addr, make([]byte, 12)...) + } + params := map[string]string{"start": strconv.FormatInt(start.UnixNano(), 10), "end": strconv.FormatInt(end.UnixNano(), 10), "location": location, "assignment": assignment, "agents": "[" + strings.Join(quoted, ",") + "]", "destination_hex": strings.ToUpper(hex.EncodeToString(addr))} + b, e := c.query(ctx, analysisSQL, params) + if e != nil { + return nil, e + } + digest := sha256.Sum256(b) + sha := hex.EncodeToString(digest[:]) + analyzed := c.now() + v := &pb.Evidence{Id: "ev-" + sha[:24] + "-" + strconv.FormatInt(analyzed.UnixNano(), 10), AnalyzedAt: timestamppb.New(analyzed), WindowStart: timestamppb.New(start), WindowEnd: timestamppb.New(end), QueryVersion: "tcp-host-deltas-v1", EnrichmentVersion: "xtcp-schema-2", ArtifactSha256: sha} + dec := json.NewDecoder(bytes.NewReader(b)) + for { + h := new(pb.HostEvidence) + e := dec.Decode(h) + if e == io.EOF { + break + } + if e != nil { + return nil, e + } + v.Hosts = append(v.Hosts, h) + } + if e = treatment.ValidateEvidence(v); e != nil { + return nil, e + } + return v, nil +} diff --git a/pkg/treatment/controller/clickhouse_test.go b/pkg/treatment/controller/clickhouse_test.go new file mode 100644 index 000000000..1e958d368 --- /dev/null +++ b/pkg/treatment/controller/clickhouse_test.go @@ -0,0 +1,74 @@ +package controller + +import ( + "encoding/json" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" +) + +// Execute the production SQL against an isolated ClickHouse local database. +// No live service or production telemetry is accessed. +func TestClickHouseAnalysisCounterDeltas(t *testing.T) { + binary := os.Getenv("XTCP_CLICKHOUSE_TEST_BINARY") + if binary == "" { + t.Skip("set XTCP_CLICKHOUSE_TEST_BINARY to run real SQL") + } + setup := `CREATE TABLE default.sockets ( + agent_id String,host_boot_id String,netns_inode UInt64,inet_diag_msg_socket_cookie UInt64, + timestamp_ns DateTime64(9),tcp_info_bytes_acked UInt64,tcp_info_bytes_sent UInt64, + tcp_info_bytes_retrans UInt64,tcp_info_rtt UInt32,tcp_info_min_rtt UInt32, + treatment_algorithm_exposed Bool,treatment_assignment_id String,location String, + inet_diag_msg_state UInt8,inet_diag_msg_socket_destination FixedString(16), + inet_diag_msg_family UInt8,enrich_socket_dest_asn UInt64) ENGINE=Memory; + INSERT INTO default.sockets SELECT 'host-1','boot-1',1,1, + fromUnixTimestamp64Nano(1700000000000000000+toInt64(number)*10000000000), + 1000000+number*1000,1000000+number*1000,number*10,1000,500,true,'trial','dc1',1, + unhex('C0000201000000000000000000000000'),2,64512 FROM numbers(6); + INSERT INTO default.sockets SELECT * FROM default.sockets; + ` + query := strings.ReplaceAll(analysisSQL, "{{TABLE}}", "default.sockets") + run := func(sql string) []byte { + t.Helper() + dir := t.TempDir() + file := filepath.Join(dir, "query.sql") + if e := os.WriteFile(file, []byte(sql), 0600); e != nil { + t.Fatal(e) + } + cmd := exec.Command(binary, "local", "--path", filepath.Join(dir, "data"), "--queries-file", file, "--multiquery", + "--param_start=1700000000000000000", "--param_end=1700000060000000000", "--param_location=dc1", + "--param_agents=['host-1']", "--param_destination_hex=C0000201000000000000000000000000", "--param_assignment=trial", + "--param_minimum_rtt=0", "--param_maximum_rtt=10", "--param_minimum_retransmission=0", + "--output_format_json_quote_64bit_integers=0") + out, e := cmd.CombinedOutput() + if e != nil { + t.Fatalf("ClickHouse SQL: %v\n%s", e, out) + } + return out + } + var h pb.HostEvidence + if e := json.Unmarshal(run(setup+query), &h); e != nil { + t.Fatal(e) + } + if h.Sockets != 1 || h.BytesAcked != 5000 || h.ExposedSockets != 1 || !h.CollectionHealthy || h.RttP95Ms != 1 || h.RetransmissionRatio != .01 { + t.Fatalf("bad window deltas/deduplication: %v", &h) + } + reset := `INSERT INTO default.sockets SELECT 'host-1','boot-1',1,1, + fromUnixTimestamp64Nano(1700000055000000000),1,1,0,1000,500,true,'trial','dc1',1, + unhex('C0000201000000000000000000000000'),2,64512;` + if e := json.Unmarshal(run(setup+reset+query), &h); e != nil { + t.Fatal(e) + } + if h.CollectionHealthy { + t.Fatal("counter reset accepted as healthy") + } + // Syntax/type-check candidate discovery too; a one-host fixture cannot meet + // its six-host eligibility threshold. + if out := run(setup + strings.ReplaceAll(discoverySQL, "{{TABLE}}", "default.sockets")); len(out) != 0 { + t.Fatalf("unexpected discovery: %s", out) + } +} diff --git a/pkg/treatment/controller/contracts_test.go b/pkg/treatment/controller/contracts_test.go new file mode 100644 index 000000000..8b71d668b --- /dev/null +++ b/pkg/treatment/controller/contracts_test.go @@ -0,0 +1,178 @@ +package controller + +import ( + "context" + "crypto/x509" + "errors" + "io" + "net/http" + "net/url" + "strings" + "testing" + "time" +) + +func TestCertificateIdentityContracts(t *testing.T) { + for _, tc := range []struct { + name, description string + uris []string + want Identity + wantError bool + }{ + {"agent", "agent identity binds both location and host", []string{"spiffe://xtcp/agent/dc/host"}, Identity{Role: "agent", Location: "dc", ID: "host"}, false}, + {"operator", "operator identity is distinct from agent", []string{"spiffe://xtcp/operator/admin"}, Identity{Role: "operator", ID: "admin"}, false}, + {"absent", "certificate must carry an identity", nil, Identity{}, true}, + {"ambiguous", "two roles on one certificate are rejected", []string{"spiffe://xtcp/agent/dc/host", "spiffe://xtcp/operator/admin"}, Identity{}, true}, + {"duplicate", "even repeated identical SANs are ambiguous", []string{"spiffe://xtcp/agent/dc/host", "spiffe://xtcp/agent/dc/host"}, Identity{}, true}, + {"authority", "foreign trust domain is rejected", []string{"spiffe://other/agent/dc/host"}, Identity{}, true}, + {"query", "query suffix cannot change identity semantics", []string{"spiffe://xtcp/agent/dc/host?role=operator"}, Identity{}, true}, + {"fragment", "fragment suffix is rejected", []string{"spiffe://xtcp/agent/dc/host#operator"}, Identity{}, true}, + {"path", "escaped path separators are rejected", []string{"spiffe://xtcp/agent/dc/host%2Fextra"}, Identity{}, true}, + {"missing-location", "agent location cannot be omitted", []string{"spiffe://xtcp/agent/host"}, Identity{}, true}, + {"unrelated-san", "unrelated SANs do not confer another role", []string{"https://example.test/", "spiffe://xtcp/operator/admin"}, Identity{Role: "operator", ID: "admin"}, false}, + } { + t.Run(tc.name, func(t *testing.T) { + cert := &x509.Certificate{} + for _, raw := range tc.uris { + u, err := url.Parse(raw) + if err != nil { + t.Fatal(err) + } + cert.URIs = append(cert.URIs, u) + } + got, err := CertificateIdentity(cert) + if (err != nil) != tc.wantError || got != tc.want { + t.Fatalf("%s: got %+v %v; want %+v error=%t", tc.description, got, err, tc.want, tc.wantError) + } + }) + } +} + +type trackedBody struct { + io.Reader + closed bool +} + +func (b *trackedBody) Close() error { b.closed = true; return nil } + +type brokenReader struct{} + +func (brokenReader) Read([]byte) (int, error) { return 0, errors.New("broken response") } + +func TestClickHouseTransportContracts(t *testing.T) { + for _, tc := range []struct { + name, description, table, url, body, wantError string + status int + transportError, readError bool + }{ + {name: "valid", description: "query is parameterized and authenticated", status: 200, body: "ok"}, + {name: "table", description: "table names cannot inject SQL", table: "db.rows;DROP TABLE rows", wantError: "database.table"}, + {name: "scheme", description: "only HTTP(S) transports are accepted", url: "file:///tmp/rows", wantError: "HTTP(S)"}, + {name: "http-error", description: "server errors are surfaced", status: 500, body: "query failed", wantError: "status 500"}, + {name: "network", description: "transport errors are surfaced", transportError: true, wantError: "offline"}, + {name: "read", description: "partial response errors fail analysis", status: 200, readError: true, wantError: "broken response"}, + {name: "size-limit", description: "oversized responses fail closed", status: 200, body: strings.Repeat("x", 8<<20), wantError: "exceeds"}, + } { + t.Run(tc.name, func(t *testing.T) { + table := tc.table + if table == "" { + table = "db.rows" + } + endpoint := tc.url + if endpoint == "" { + endpoint = "https://fixture.invalid/" + } + body := &trackedBody{Reader: strings.NewReader(tc.body)} + if tc.readError { + body.Reader = brokenReader{} + } + called := false + c := &ClickHouse{URL: endpoint, Table: table, Username: "user", Password: "test-secret", Client: &http.Client{Transport: discoveryTransport(func(r *http.Request) (*http.Response, error) { + called = true + if tc.transportError { + return nil, errors.New("offline") + } + u, p, ok := r.BasicAuth() + if !ok || u != "user" || p != "test-secret" { + t.Fatal("missing authentication") + } + query, err := io.ReadAll(r.Body) + if err != nil { + t.Fatal(err) + } + if string(query) != "SELECT * FROM db.rows" || r.Method != "POST" || r.URL.Query().Get("param_location") != "dc'quote" || r.URL.Query().Get("result_overflow_mode") != "throw" { + t.Fatal("query contract changed") + } + return &http.Response{StatusCode: tc.status, Body: body, Header: make(http.Header)}, nil + })}} + got, err := c.query(context.Background(), "SELECT * FROM {{TABLE}}", map[string]string{"location": "dc'quote"}) + if tc.wantError == "" { + if err != nil || string(got) != tc.body { + t.Fatalf("%s: %q %v", tc.description, got, err) + } + } else if err == nil || !strings.Contains(err.Error(), tc.wantError) { + t.Fatalf("%s: want %q got %v", tc.description, tc.wantError, err) + } + if called && !tc.transportError && !body.closed { + t.Fatal("response body leaked") + } + if (tc.table != "" || tc.url != "") && called { + t.Fatal("invalid configuration reached transport") + } + }) + } +} + +func TestClickHouseEvidenceContracts(t *testing.T) { + const host = `{"agent_id":"host","sockets":30,"bytes_acked":1048576,"goodput_bps":1000,"rtt_p95_ms":5,"collection_healthy":true}` + now := time.Unix(1700000000, 0) + for _, tc := range []struct { + name, description, body, destination string + agents []string + window time.Duration + wantError string + }{ + {name: "valid", description: "complete response records provenance", body: host}, + {name: "empty", description: "empty response cannot renew a lease", wantError: "population"}, + {name: "malformed", description: "malformed JSON is rejected", body: "{", wantError: "EOF"}, + {name: "duplicate", description: "duplicate hosts cannot inflate sample size", body: host + "\n" + host, wantError: "duplicate"}, + {name: "bad-window", description: "empty analysis interval is rejected", window: -time.Second, wantError: "window"}, + {name: "long-window", description: "analysis is limited to seven days", window: 7*24*time.Hour + time.Second, wantError: "window"}, + {name: "bad-agent", description: "agent IDs cannot inject query parameters", agents: []string{"host'"}, wantError: "invalid agent"}, + {name: "bad-destination", description: "malformed destination fails before query", destination: "bad", wantError: "ParsePrefix"}, + } { + t.Run(tc.name, func(t *testing.T) { + window := tc.window + if window == 0 { + window = time.Minute + } + agents := tc.agents + if agents == nil { + agents = []string{"host"} + } + dest := tc.destination + if dest == "" { + dest = "192.0.2.1/32" + } + c := &ClickHouse{URL: "http://fixture.invalid", Table: "db.rows", Now: func() time.Time { return now }, Client: &http.Client{Transport: discoveryTransport(func(r *http.Request) (*http.Response, error) { + if r.URL.Query().Get("param_destination_hex") != "C0000201000000000000000000000000" { + t.Fatal("wrong destination encoding") + } + return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(tc.body)), Header: make(http.Header)}, nil + })}} + ev, err := c.Evidence(context.Background(), "dc", dest, "assignment", agents, now.Add(-window), now) + if tc.wantError != "" { + if err == nil || !strings.Contains(err.Error(), tc.wantError) { + t.Fatalf("%s: want %q got %v", tc.description, tc.wantError, err) + } + return + } + if err != nil { + t.Fatal(err) + } + if len(ev.Hosts) != 1 || len(ev.ArtifactSha256) != 64 || ev.QueryVersion != "tcp-host-deltas-v1" || !ev.AnalyzedAt.AsTime().Equal(now) { + t.Fatalf("%s: bad evidence %v", tc.description, ev) + } + }) + } +} diff --git a/pkg/treatment/controller/discovery.go b/pkg/treatment/controller/discovery.go new file mode 100644 index 000000000..0cbb943f8 --- /dev/null +++ b/pkg/treatment/controller/discovery.go @@ -0,0 +1,199 @@ +package controller + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "net/netip" + "strconv" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "google.golang.org/protobuf/proto" +) + +// Templates only authorize candidate discovery. Every generated hypothesis and +// every increase in exposure still requires an authenticated operator review. +type DiscoveryTemplate struct { + ID string `json:"id"` + Location string `json:"location"` + CongestionControl string `json:"congestion_control"` + RtoMinMs *uint32 `json:"rto_min_ms,omitempty"` + InitialCongestionWindow *uint32 `json:"initial_congestion_window,omitempty"` + InitialReceiveWindow *uint32 `json:"initial_receive_window,omitempty"` + QuickAck *bool `json:"quick_ack,omitempty"` + MaximumRTTIncrease *float64 `json:"maximum_rtt_increase,omitempty"` + MinimumRTTMs float64 `json:"minimum_rtt_ms"` + MaximumRTTMs float64 `json:"maximum_rtt_ms"` + MinimumRetransmissionRatio float64 `json:"minimum_retransmission_ratio"` + ASNs []uint64 `json:"asns"` + Prefixes []string `json:"prefixes"` +} + +func (t DiscoveryTemplate) policy() *pb.ExperimentPolicy { + p := treatment.DefaultPolicy() + if t.MaximumRTTIncrease != nil { + p.MaximumRttIncrease = *t.MaximumRTTIncrease + } + return p +} + +func (t DiscoveryTemplate) settings() *pb.TCPSettings { + return &pb.TCPSettings{ + CongestionControl: proto.String(t.CongestionControl), + RtoMinMs: t.RtoMinMs, + InitialCongestionWindow: t.InitialCongestionWindow, + InitialReceiveWindow: t.InitialReceiveWindow, + QuickAck: t.QuickAck, + } +} + +const discoverySQL = ` +WITH sockets AS ( + SELECT agent_id,host_boot_id,netns_inode,inet_diag_msg_socket_cookie, + any(hex(inet_diag_msg_socket_destination)) AS destination_hex, + any(inet_diag_msg_family) AS family, + argMax(enrich_socket_dest_asn,timestamp_ns) AS asn, + min(tcp_info_min_rtt)/1000 AS min_rtt_ms, + max(tcp_info_bytes_sent)-min(tcp_info_bytes_sent) AS sent, + max(tcp_info_bytes_retrans)-min(tcp_info_bytes_retrans) AS retrans + FROM {{TABLE}} + WHERE timestamp_ns>=fromUnixTimestamp64Nano({start:Int64}) AND timestamp_ns=3 +) +SELECT destination_hex,family,any(asn) AS asn,groupUniqArray(10000)(agent_id) AS agents, + quantileExact(0.5)(min_rtt_ms) AS rtt_ms,sum(retrans)/greatest(1,sum(sent)) AS retransmission_ratio +FROM sockets GROUP BY destination_hex,family +HAVING length(agents)>=6 AND rtt_ms>={minimum_rtt:Float64} AND rtt_ms<={maximum_rtt:Float64} + AND retransmission_ratio>={minimum_retransmission:Float64} +ORDER BY sum(sent) DESC LIMIT 10 FORMAT JSONEachRow` + +func (s *Server) Discover(ctx context.Context, c *ClickHouse, templates []DiscoveryTemplate) error { + window := s.DiscoveryWindow + if window == 0 { + window = time.Hour + } + if window < time.Minute || window > 7*24*time.Hour { + return errors.New("discovery window must be between one minute and seven days") + } + var failures []error + for _, t := range templates { + if !treatment.ValidID(t.ID) || !treatment.ValidID(t.Location) || (t.CongestionControl != "cubic" && t.CongestionControl != "bbr") || t.MaximumRTTMs <= t.MinimumRTTMs || t.MinimumRTTMs < 0 || t.MinimumRetransmissionRatio < 0 || t.MinimumRetransmissionRatio > 1 { + return errors.New("invalid discovery template; initial discovery supports CUBIC/BBR only") + } + if err := treatment.ValidateTCPSettings(t.settings(), false); err != nil { + return fmt.Errorf("discovery template %s: %w", t.ID, err) + } + if err := treatment.ValidatePolicy(t.policy()); err != nil { + return fmt.Errorf("discovery template %s policy: %w", t.ID, err) + } + var prefixes []netip.Prefix + for _, v := range t.Prefixes { + p, e := netip.ParsePrefix(v) + if e != nil { + return e + } + prefixes = append(prefixes, p) + } + // Avoid warehouse aggregation while this location cannot accept a + // new experiment. The transaction below still rechecks the budget. + busy, e := hasActiveExperiment(ctx, s.Store.DB, t.Location) + if e != nil { + failures = append(failures, e) + continue + } + if busy { + continue + } + end := s.now().Add(-20 * time.Second) + start := end.Add(-window) + params := map[string]string{"location": t.Location, "start": strconv.FormatInt(start.UnixNano(), 10), "end": strconv.FormatInt(end.UnixNano(), 10), "minimum_rtt": fmt.Sprint(t.MinimumRTTMs), "maximum_rtt": fmt.Sprint(t.MaximumRTTMs), "minimum_retransmission": fmt.Sprint(t.MinimumRetransmissionRatio)} + data, e := c.query(ctx, discoverySQL, params) + if e != nil { + failures = append(failures, e) + continue + } + dec := json.NewDecoder(bytes.NewReader(data)) + for { + var row struct { + DestinationHex string `json:"destination_hex"` + Family uint32 `json:"family"` + ASN uint64 `json:"asn"` + Agents []string `json:"agents"` + RTT float64 `json:"rtt_ms"` + Retransmission float64 `json:"retransmission_ratio"` + } + if e = dec.Decode(&row); e == io.EOF { + break + } + if e != nil { + failures = append(failures, e) + break + } + if len(t.ASNs) > 0 { + match := false + for _, a := range t.ASNs { + match = match || a == row.ASN + } + if !match { + continue + } + } + raw, e := hex.DecodeString(row.DestinationHex) + if e != nil { + failures = append(failures, e) + continue + } + if row.Family == 2 && len(raw) >= 4 { + raw = raw[:4] + } + ip, ok := netip.AddrFromSlice(raw) + if !ok { + continue + } + if len(prefixes) > 0 { + match := false + for _, p := range prefixes { + match = match || p.Contains(ip) + } + if !match { + continue + } + } + destination := netip.PrefixFrom(ip, ip.BitLen()).String() + // Completed or held treatments and recent rejected trials suppress + // repeat proposals. Cooldown is at least a day after terminal state. + exists, e := discoveryDestinationBlocked(ctx, s.Store.DB, t.Location, destination, s.now()) + if e != nil { + failures = append(failures, e) + continue + } + if exists { + continue + } + seed := sha256.Sum256([]byte(t.ID + "/" + destination + "/" + s.now().UTC().Format("2006-01-02"))) + id := "hyp-" + hex.EncodeToString(seed[:12]) + baseline, e := c.Evidence(ctx, t.Location, destination, id, row.Agents, start, end) + if e != nil { + failures = append(failures, e) + continue + } + n := &pb.Experiment{Id: id, Location: t.Location, Hypothesis: fmt.Sprintf("Template %s: test %s for %s (ASN %d), observed RTT %.3f ms and retransmission ratio %.6f; this is a hypothesis, not an efficacy claim", t.ID, t.CongestionControl, destination, row.ASN, row.RTT, row.Retransmission), Treatment: &pb.Treatment{Id: t.ID, Revision: 1, Destination: destination, Settings: t.settings()}, Policy: t.policy(), Baseline: baseline, CohortOrder: row.Agents} + if _, e = s.propose(ctx, &pb.ProposeRequest{Experiment: n, RequestId: id}, "candidate-discovery", true); e != nil { + if !errors.Is(e, errExperimentBudget) && !errors.Is(e, errDiscoverySuppressed) { + failures = append(failures, e) + } + break + } + } + } + return errors.Join(failures...) +} diff --git a/pkg/treatment/controller/discovery_contracts_test.go b/pkg/treatment/controller/discovery_contracts_test.go new file mode 100644 index 000000000..b6fe27108 --- /dev/null +++ b/pkg/treatment/controller/discovery_contracts_test.go @@ -0,0 +1,98 @@ +package controller + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "testing" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" +) + +func TestPostgresDiscoveryScopeAndCooldown(t *testing.T) { + for _, tc := range []struct { + name, description string + asns []uint64 + prefixes []string + want int + }{ + {"unfiltered", "eligible destination creates one reviewable proposal", nil, nil, 1}, + {"asn-match", "matching ASN permits discovery", []uint64{64512}, nil, 1}, + {"asn-miss", "foreign ASN is outside scope", []uint64{64513}, nil, 0}, + {"prefix-match", "matching destination prefix permits discovery", nil, []string{"192.0.2.0/24"}, 1}, + {"prefix-miss", "foreign destination prefix is outside scope", nil, []string{"198.51.100.0/24"}, 0}, + } { + t.Run(tc.name, func(t *testing.T) { + now := time.Now().UTC() + s := &Server{Store: testStore(t), Now: func() time.Time { return now }} + var agents []string + var evidence strings.Builder + for i := range 8 { + id := fmt.Sprintf("host-%d", i) + enroll(t, s, id) + agents = append(agents, id) + raw, err := json.Marshal(&pb.HostEvidence{AgentId: id, Sockets: 100, BytesAcked: 1 << 25, GoodputBps: 1e8, RttP95Ms: 5, CollectionHealthy: true}) + if err != nil { + t.Fatal(err) + } + evidence.Write(raw) + evidence.WriteByte('\n') + } + row, err := json.Marshal(map[string]any{"destination_hex": "C0000201000000000000000000000000", "family": 2, "asn": 64512, "agents": agents, "rtt_ms": 5, "retransmission_ratio": 0}) + if err != nil { + t.Fatal(err) + } + c := &ClickHouse{URL: "http://fixture.invalid", Table: "db.rows", Now: s.Now, Client: &http.Client{Transport: discoveryTransport(func(r *http.Request) (*http.Response, error) { + q, err := io.ReadAll(r.Body) + if err != nil { + return nil, err + } + body := evidence.String() + if string(q) == strings.ReplaceAll(discoverySQL, "{{TABLE}}", "db.rows") { + body = string(row) + } + return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(body)), Header: make(http.Header)}, nil + })}} + templates := []DiscoveryTemplate{{ID: "nearby", Location: "dc1", CongestionControl: "cubic", MaximumRTTMs: 15, ASNs: tc.asns, Prefixes: tc.prefixes}} + for range 2 { + if err := s.Discover(context.Background(), c, templates); err != nil { + t.Fatal(err) + } + } + var count int + if err := s.Store.DB.QueryRow("SELECT count(*) FROM xtcp_control_experiments").Scan(&count); err != nil { + t.Fatal(err) + } + if count != tc.want { + t.Fatalf("%s: proposals=%d want %d", tc.description, count, tc.want) + } + if count == 0 { + return + } + var state string + if err := s.Store.DB.QueryRow("SELECT state->>'status' FROM xtcp_control_experiments").Scan(&state); err != nil { + t.Fatal(err) + } + if state != "EXPERIMENT_STATUS_PROPOSED" { + t.Fatal("discovery started unreviewed experiment") + } + // Terminal destination is still suppressed for 24 hours, including after a + // controller restart (new Server, same authoritative database). + if _, err := s.Store.DB.Exec("UPDATE xtcp_control_experiments SET state=jsonb_set(state,'{status}','\"EXPERIMENT_STATUS_ROLLED_BACK\"'),updated_at=$1", now); err != nil { + t.Fatal(err) + } + now = now.Add(23 * time.Hour) + restarted := &Server{Store: s.Store, Now: s.Now} + if err := restarted.Discover(context.Background(), c, templates); err != nil { + t.Fatal(err) + } + if err := s.Store.DB.QueryRow("SELECT count(*) FROM xtcp_control_experiments").Scan(&count); err != nil || count != 1 { + t.Fatalf("cooldown lost across restart: %d %v", count, err) + } + }) + } +} diff --git a/pkg/treatment/controller/discovery_store_test.go b/pkg/treatment/controller/discovery_store_test.go new file mode 100644 index 000000000..214c72bea --- /dev/null +++ b/pkg/treatment/controller/discovery_store_test.go @@ -0,0 +1,55 @@ +package controller + +import ( + "context" + "database/sql" + "database/sql/driver" + "errors" + "io" + "testing" +) + +// Only the cheap pre-scan budget lookup is stubbed in template-validation unit +// tests. PostgreSQL integration tests exercise the real queries and indexes. +func emptyDiscoveryStore(t *testing.T) *Store { + t.Helper() + db := sql.OpenDB(discoveryConnector{}) + t.Cleanup(func() { db.Close() }) + return &Store{DB: db} +} + +type discoveryConnector struct{} + +func (discoveryConnector) Connect(context.Context) (driver.Conn, error) { return discoveryConn{}, nil } +func (discoveryConnector) Driver() driver.Driver { return discoveryDriver{} } + +type discoveryDriver struct{} + +func (discoveryDriver) Open(string) (driver.Conn, error) { return discoveryConn{}, nil } + +type discoveryConn struct{} + +func (discoveryConn) Prepare(string) (driver.Stmt, error) { + return nil, errors.New("unexpected prepared query") +} +func (discoveryConn) Close() error { return nil } +func (discoveryConn) Begin() (driver.Tx, error) { return nil, errors.New("unexpected transaction") } +func (discoveryConn) QueryContext(_ context.Context, q string, _ []driver.NamedValue) (driver.Rows, error) { + if q != activeExperimentSQL { + return nil, errors.New("unexpected discovery query") + } + return &discoveryRows{}, nil +} + +type discoveryRows struct{ done bool } + +func (*discoveryRows) Columns() []string { return []string{"exists"} } +func (*discoveryRows) Close() error { return nil } +func (r *discoveryRows) Next(values []driver.Value) error { + if r.done { + return io.EOF + } + r.done = true + values[0] = false + return nil +} diff --git a/pkg/treatment/controller/discovery_test.go b/pkg/treatment/controller/discovery_test.go new file mode 100644 index 000000000..fc2827dde --- /dev/null +++ b/pkg/treatment/controller/discovery_test.go @@ -0,0 +1,133 @@ +package controller + +import ( + "context" + "encoding/json" + "io" + "math" + "net/http" + "strconv" + "strings" + "testing" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "google.golang.org/protobuf/proto" +) + +type discoveryTransport func(*http.Request) (*http.Response, error) + +func (f discoveryTransport) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) } + +func TestDiscoveryWindow(t *testing.T) { + now := time.Unix(1800000000, 0) + for _, tc := range []struct { + name string + window, want time.Duration + invalid bool + }{ + {"production-default", 0, time.Hour, false}, + {"short-window", time.Minute, time.Minute, false}, + {"maximum", 7 * 24 * time.Hour, 7 * 24 * time.Hour, false}, + {"negative", -time.Second, 0, true}, + {"too-short", 59 * time.Second, 0, true}, + {"too-long", 7*24*time.Hour + time.Second, 0, true}, + } { + t.Run(tc.name, func(t *testing.T) { + called := false + ch := &ClickHouse{URL: "http://fixture.invalid", Table: "xtcp.rows", Client: &http.Client{Transport: discoveryTransport(func(r *http.Request) (*http.Response, error) { + called = true + params := r.URL.Query() + wantEnd := now.Add(-20 * time.Second) + if params.Get("param_start") != strconv.FormatInt(wantEnd.Add(-tc.want).UnixNano(), 10) || params.Get("param_end") != strconv.FormatInt(wantEnd.UnixNano(), 10) { + t.Fatalf("wrong discovery bounds: %s", r.URL.RawQuery) + } + return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader("")), Header: make(http.Header)}, nil + })}} + s := &Server{Store: emptyDiscoveryStore(t), Now: func() time.Time { return now }, DiscoveryWindow: tc.window} + err := s.Discover(context.Background(), ch, []DiscoveryTemplate{{ID: "near", Location: "test-dc", CongestionControl: "cubic", MaximumRTTMs: 15}}) + if (err != nil) != tc.invalid || called == tc.invalid { + t.Fatalf("err=%v query-called=%t invalid=%t", err, called, tc.invalid) + } + }) + } +} + +func TestDiscoveryRTTPolicy(t *testing.T) { + for _, tc := range []struct { + name string + limit *float64 + want float64 + invalid bool + }{ + {"production-default", nil, .1, false}, + {"zero", proto.Float64(0), 0, false}, + {"soak", proto.Float64(1), 1, false}, + {"negative", proto.Float64(-.1), 0, true}, + {"over-maximum", proto.Float64(1.01), 0, true}, + {"nan", proto.Float64(math.NaN()), 0, true}, + } { + t.Run(tc.name, func(t *testing.T) { + template := DiscoveryTemplate{ID: "near", Location: "test-dc", CongestionControl: "cubic", MaximumRTTMs: 15, MaximumRTTIncrease: tc.limit} + called := false + ch := &ClickHouse{URL: "http://fixture.invalid", Table: "xtcp.rows", Client: &http.Client{Transport: discoveryTransport(func(r *http.Request) (*http.Response, error) { + called = true + return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader("")), Header: make(http.Header)}, nil + })}} + err := (&Server{Store: emptyDiscoveryStore(t)}).Discover(context.Background(), ch, []DiscoveryTemplate{template}) + if (err != nil) != tc.invalid || called == tc.invalid { + t.Fatalf("err=%v queried=%t", err, called) + } + if tc.invalid { + return + } + want := treatment.DefaultPolicy() + want.MaximumRttIncrease = tc.want + if !proto.Equal(template.policy(), want) { + t.Fatalf("wrong policy: %v", template.policy()) + } + }) + } +} + +func TestDiscoveryRouteSettings(t *testing.T) { + for _, tc := range []struct { + name, fields string + want *pb.TCPSettings + invalid bool + }{ + {"algorithm-only", "", &pb.TCPSettings{CongestionControl: proto.String("cubic")}, false}, + {"nearby", `,"rto_min_ms":5,"quick_ack":true,"initial_congestion_window":100,"initial_receive_window":100`, &pb.TCPSettings{CongestionControl: proto.String("cubic"), RtoMinMs: proto.Uint32(5), QuickAck: proto.Bool(true), InitialCongestionWindow: proto.Uint32(100), InitialReceiveWindow: proto.Uint32(100)}, false}, + {"explicit-off", `,"quick_ack":false`, &pb.TCPSettings{CongestionControl: proto.String("cubic"), QuickAck: proto.Bool(false)}, false}, + {"zero-rto", `,"rto_min_ms":0`, nil, true}, + {"large-rto", `,"rto_min_ms":120001`, nil, true}, + {"zero-cwnd", `,"initial_congestion_window":0`, nil, true}, + {"large-rwnd", `,"initial_receive_window":1025`, nil, true}, + } { + t.Run(tc.name, func(t *testing.T) { + var template DiscoveryTemplate + if err := json.Unmarshal([]byte(`{"id":"near","location":"test-dc","congestion_control":"cubic","maximum_rtt_ms":15`+tc.fields+`}`), &template); err != nil { + t.Fatal(err) + } + called := false + ch := &ClickHouse{URL: "http://fixture.invalid", Table: "xtcp.rows", Client: &http.Client{Transport: discoveryTransport(func(r *http.Request) (*http.Response, error) { + called = true + return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader("")), Header: make(http.Header)}, nil + })}} + err := (&Server{Store: emptyDiscoveryStore(t)}).Discover(context.Background(), ch, []DiscoveryTemplate{template}) + if (err != nil) != tc.invalid || called == tc.invalid { + t.Fatalf("err=%v query-called=%t", err, called) + } + if tc.invalid { + return + } + if !proto.Equal(template.settings(), tc.want) { + t.Fatalf("got %v, want %v", template.settings(), tc.want) + } + if err := treatment.ValidateTreatment(&pb.Treatment{Id: template.ID, Revision: 1, Destination: "192.0.2.1/32", Settings: template.settings()}); err != nil { + t.Fatal(err) + } + }) + } +} diff --git a/pkg/treatment/controller/maintenance.go b/pkg/treatment/controller/maintenance.go new file mode 100644 index 000000000..b136b1464 --- /dev/null +++ b/pkg/treatment/controller/maintenance.go @@ -0,0 +1,209 @@ +package controller + +import ( + "context" + "database/sql" + "errors" + "fmt" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" +) + +// EvidenceSource must produce immutable per-host evidence for a frozen +// population and treatment. Failure never authorizes lease renewal/promotion. +type EvidenceSource interface { + Analyze(context.Context, *pb.Experiment) (*pb.Evidence, error) +} + +// Maintain refreshes exposure, stores evidence, renews healthy leases, and +// withdraws harmful/expired trials. It NEVER promotes a phase. A fresh human +// review remains necessary for each increase in exposure. +func (s *Server) Maintain(ctx context.Context, source EvidenceSource) error { + rows, e := s.Store.DB.QueryContext(ctx, "SELECT id FROM xtcp_control_experiments WHERE state->>'status' IN ('EXPERIMENT_STATUS_RUNNING','EXPERIMENT_STATUS_PAUSED','EXPERIMENT_STATUS_COMPLETE')") + if e != nil { + return e + } + var ids []string + for rows.Next() { + var id string + if e = rows.Scan(&id); e != nil { + break + } + ids = append(ids, id) + } + e = errors.Join(e, rows.Err()) + rows.Close() + if e != nil { + return e + } + var failures []error + for _, id := range ids { + var snapshot *pb.Experiment + e = s.Store.mutate(ctx, func(tx *sql.Tx) error { + n, e := experiment(ctx, tx, id) + if e != nil { + return e + } + if n.Status != pb.ExperimentStatus_EXPERIMENT_STATUS_RUNNING && n.Status != pb.ExperimentStatus_EXPERIMENT_STATUS_PAUSED && n.Status != pb.ExperimentStatus_EXPERIMENT_STATUS_COMPLETE { + return nil + } + if n.Status == pb.ExperimentStatus_EXPERIMENT_STATUS_COMPLETE { + if n.LatestEvidence != nil && s.now().Sub(n.LatestEvidence.AnalyzedAt.AsTime()) < time.Hour { + return nil + } + } else if e = s.startObservation(ctx, tx, n); e != nil { + return e + } + snapshot = n + return saveExperiment(ctx, tx, n) + }) + if e != nil { + failures = append(failures, e) + continue + } + if snapshot == nil || len(snapshot.Phases) == 0 { + continue + } + var ev *pb.Evidence + if source != nil && snapshot.Status != pb.ExperimentStatus_EXPERIMENT_STATUS_PAUSED { + input := snapshot + if snapshot.Status == pb.ExperimentStatus_EXPERIMENT_STATUS_COMPLETE { + input = proto.Clone(snapshot).(*pb.Experiment) + p := input.Phases[len(input.Phases)-1] + p.IssuedAt = timestamppb.New(s.now().Add(-time.Hour)) + p.ObservationStartedAt = p.IssuedAt + } + ev, e = source.Analyze(ctx, input) + if e != nil { + failures = append(failures, fmt.Errorf("%s analysis: %w", id, e)) + } + } + e = s.Store.mutate(ctx, func(tx *sql.Tx) error { + n, e := experiment(ctx, tx, id) + if e != nil { + return e + } + if n.Revision != snapshot.Revision { + return nil + } + phase := n.Phases[len(n.Phases)-1] + now := s.now() + outcome := "unavailable" + if n.Status == pb.ExperimentStatus_EXPERIMENT_STATUS_COMPLETE { + if ev != nil { + if e = saveEvidence(ctx, tx, id, ev); e != nil { + return e + } + healthy, harmful := treatment.HealthyEvidence(n, ev, now) + n.LatestEvidence = ev + n.LatestOutcome = "stable_observed" + if !healthy { + n.LatestOutcome = "stable_evidence_incomplete" + } + if harmful { + n.LatestOutcome = "stable_regression_review_required" + if e = audit(ctx, tx, "drift-monitor", "", "stable_regression", ev); e != nil { + return e + } + } + } + return saveExperiment(ctx, tx, n) + } + if now.Sub(phase.IssuedAt.AsTime()) >= n.Policy.MaximumPhase.AsDuration() { + outcome = "expired" + } else if ev != nil { + if e = saveEvidence(ctx, tx, id, ev); e != nil { + return e + } + v, e := treatment.Evaluate(n, ev, now) + if e != nil { + return e + } + outcome = v.Outcome + // Keep analysis visible to reviewers without marking a phase as + // approved/evaluated. Only Review writes reviewer and outcome. + n.LatestEvidence = ev + if _, harmful := treatment.HealthyEvidence(n, ev, now); harmful { + outcome = "harmful" + } + } + n.LatestOutcome = outcome + if outcome == "harmful" || outcome == "expired" { + phase.Outcome = outcome + phase.EvaluatedAt = timestamppb.New(now) + n.Status = pb.ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK + n.Revision++ + if e = s.assign(ctx, tx, n, false); e != nil { + return e + } + if e = audit(ctx, tx, "safety-controller", "", "automatic_withdrawal", n); e != nil { + return e + } + } else if ev != nil && n.Status == pb.ExperimentStatus_EXPERIMENT_STATUS_RUNNING { + // Before the 30-minute decision window completes, fresh health + // evidence can renew the existing phase but cannot expand it. + healthy, _ := treatment.HealthyEvidence(n, ev, now) + if healthy && outcome != "stale" && outcome != "insufficient_data" { + if e = s.renew(ctx, tx, n, now); e != nil { + return e + } + } + } + return saveExperiment(ctx, tx, n) + }) + if e != nil { + failures = append(failures, e) + } + } + return errors.Join(failures...) +} + +func (s *Server) renew(ctx context.Context, tx *sql.Tx, n *pb.Experiment, now time.Time) error { + for _, id := range n.CohortOrder[:n.Phases[len(n.Phases)-1].TargetHosts] { + d, e := desired(ctx, tx, id) + if e != nil { + return e + } + if d.Suspended { + continue + } + var raw []byte + var seen time.Time + if e = tx.QueryRowContext(ctx, "SELECT observed,last_seen FROM xtcp_control_agents WHERE id=$1", id).Scan(&raw, &seen); e != nil { + return e + } + o := new(pb.CheckInRequest) + if e = protojson.Unmarshal(raw, o); e != nil { + return e + } + if now.Sub(seen) > 2*time.Minute || o.LocallySuspended || o.DesiredRevision != d.Revision { + continue + } + for _, a := range d.Assignments { + if a.ExperimentId != n.Id || a.Mode != pb.AssignmentMode_ASSIGNMENT_MODE_TRIAL || a.LeaseExpiresAt == nil || !a.LeaseExpiresAt.AsTime().After(now) { + continue + } + verified := false + for _, r := range o.Records { + if r.Assignment.Id == a.Id && r.Assignment.Generation == a.Generation && r.Status == pb.ApplyStatus_APPLY_STATUS_VERIFIED { + verified = true + } + } + if !verified || a.LeaseExpiresAt.AsTime().After(now.Add(treatment.TrialLease-treatment.LeaseRenewInterval)) { + continue + } + a.LeaseExpiresAt = timestamppb.New(now.Add(treatment.TrialLease)) + d.Revision++ + d.IssuedAt = timestamppb.New(now) + if e = saveDesired(ctx, tx, d); e != nil { + return e + } + } + } + return nil +} diff --git a/pkg/treatment/controller/notifications.go b/pkg/treatment/controller/notifications.go new file mode 100644 index 000000000..4f7a37525 --- /dev/null +++ b/pkg/treatment/controller/notifications.go @@ -0,0 +1,77 @@ +package controller + +import ( + "context" + "sync" + "time" +) + +// NotificationHub collapses fleet notification work into one indexed-table scan +// per interval, rather than one database poll per connected agent. Channels hold +// only the latest revision. Desired state remains authoritative in PostgreSQL. +type NotificationHub struct { + mu sync.Mutex + subs map[string]map[chan uint64]struct{} + last map[string]uint64 +} + +func NewNotificationHub() *NotificationHub { + return &NotificationHub{subs: map[string]map[chan uint64]struct{}{}, last: map[string]uint64{}} +} +func (h *NotificationHub) subscribe(id string) (chan uint64, func()) { + h.mu.Lock() + defer h.mu.Unlock() + ch := make(chan uint64, 1) + if h.subs[id] == nil { + h.subs[id] = map[chan uint64]struct{}{} + } + h.subs[id][ch] = struct{}{} + if v := h.last[id]; v > 0 { + ch <- v + } + return ch, func() { + h.mu.Lock() + defer h.mu.Unlock() + delete(h.subs[id], ch) + if len(h.subs[id]) == 0 { + delete(h.subs, id) + } + } +} +func (h *NotificationHub) publish(id string, revision uint64) { + h.mu.Lock() + defer h.mu.Unlock() + if h.last[id] == revision { + return + } + h.last[id] = revision + for ch := range h.subs[id] { + select { + case <-ch: + default: + } + ch <- revision + } +} +func (s *Server) RunNotifications(ctx context.Context) { + t := time.NewTicker(2 * time.Second) + defer t.Stop() + for { + rows, e := s.Store.DB.QueryContext(ctx, "SELECT id,(desired->>'revision')::bigint FROM xtcp_control_agents") + if e == nil { + for rows.Next() { + var id string + var rev uint64 + if rows.Scan(&id, &rev) == nil { + s.Hub.publish(id, rev) + } + } + rows.Close() + } + select { + case <-ctx.Done(): + return + case <-t.C: + } + } +} diff --git a/pkg/treatment/controller/notifications_test.go b/pkg/treatment/controller/notifications_test.go new file mode 100644 index 000000000..b91380925 --- /dev/null +++ b/pkg/treatment/controller/notifications_test.go @@ -0,0 +1,106 @@ +package controller + +import ( + "fmt" + "sync" + "testing" +) + +func TestNotificationLifecycle(t *testing.T) { + for _, tc := range []struct { + name, description string + revisions []uint64 + before bool + want uint64 + }{ + {"first", "an existing subscriber receives a new revision", []uint64{1}, false, 1}, + {"coalesce", "a slow subscriber receives only the latest revision", []uint64{1, 2, 3}, false, 3}, + {"duplicate", "duplicate publication preserves the pending revision", []uint64{2, 2}, false, 2}, + {"late", "new subscribers immediately receive the known revision", []uint64{1, 3}, true, 3}, + } { + t.Run(tc.name, func(t *testing.T) { + h := NewNotificationHub() + var ch chan uint64 + var cancel func() + if !tc.before { + ch, cancel = h.subscribe("host") + } + for _, r := range tc.revisions { + h.publish("host", r) + } + if tc.before { + ch, cancel = h.subscribe("host") + } + defer cancel() + select { + case got := <-ch: + if got != tc.want { + t.Fatalf("%s: got %d want %d", tc.description, got, tc.want) + } + default: + t.Fatalf("%s: no notification", tc.description) + } + select { + case got := <-ch: + t.Fatalf("unexpected duplicate %d", got) + default: + } + cancel() + cancel() + h.publish("host", 4) + select { + case <-ch: + t.Fatal("cancelled subscription received an update") + default: + } + }) + } +} + +// Run under -race: publication competes with reconnects and cancellation. +func TestNotificationConcurrentReconnect(t *testing.T) { + h := NewNotificationHub() + var wg sync.WaitGroup + for worker := range 16 { + wg.Go(func() { + id := fmt.Sprintf("host-%d", worker%4) + for i := range 100 { + ch, cancel := h.subscribe(id) + h.publish(id, uint64(i+1)) + select { + case <-ch: + default: + } + cancel() + } + }) + } + wg.Wait() + h.mu.Lock() + defer h.mu.Unlock() + if len(h.subs) != 0 { + t.Fatalf("leaked %d subscriptions", len(h.subs)) + } +} + +func BenchmarkNotificationFleet(b *testing.B) { + for _, n := range []int{100, 1000, 10000} { + b.Run(fmt.Sprint(n), func(b *testing.B) { + h := NewNotificationHub() + ids := make([]string, n) + for i := range ids { + ids[i] = fmt.Sprintf("host-%d", i) + _, cancel := h.subscribe(ids[i]) + b.Cleanup(cancel) + } + var revision uint64 + b.ReportAllocs() + for b.Loop() { + revision++ + for _, id := range ids { + h.publish(id, revision) + } + } + }) + } +} diff --git a/pkg/treatment/controller/postgres_indexes_test.go b/pkg/treatment/controller/postgres_indexes_test.go new file mode 100644 index 000000000..5b39f8441 --- /dev/null +++ b/pkg/treatment/controller/postgres_indexes_test.go @@ -0,0 +1,210 @@ +package controller + +import ( + "context" + "database/sql" + "encoding/json" + "errors" + "fmt" + "net/http" + "strings" + "sync" + "testing" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "google.golang.org/protobuf/proto" +) + +func TestPostgresDiscoveryEligibility(t *testing.T) { + for _, tc := range []struct { + name, description, status string + age time.Duration + location, destination string + wantBlocked, wantActive bool + }{ + {"proposed", "pending review reserves the destination", "PROPOSED", 48 * time.Hour, "dc1", "192.0.2.1/32", true, true}, + {"running", "an active trial is not rediscovered", "RUNNING", 48 * time.Hour, "dc1", "192.0.2.1/32", true, true}, + {"paused", "paused trials remain reserved", "PAUSED", 48 * time.Hour, "dc1", "192.0.2.1/32", true, true}, + {"suspended", "temporary holds must not be bypassed", "SUSPENDED", 48 * time.Hour, "dc1", "192.0.2.1/32", true, true}, + {"stable", "completed stable treatments remain reserved indefinitely", "COMPLETE", 48 * time.Hour, "dc1", "192.0.2.1/32", true, false}, + {"recent-rollback", "recent withdrawal is in cooldown", "ROLLED_BACK", 23 * time.Hour, "dc1", "192.0.2.1/32", true, false}, + {"recent-retirement", "recent retirement is in cooldown", "RETIRED", 23 * time.Hour, "dc1", "192.0.2.1/32", true, false}, + {"boundary", "cooldown ends at exactly 24 hours", "ROLLED_BACK", 24 * time.Hour, "dc1", "192.0.2.1/32", false, false}, + {"old-retirement", "old retirement does not reserve destination", "RETIRED", 25 * time.Hour, "dc1", "192.0.2.1/32", false, false}, + {"other-dc", "reservation is data-center specific", "RUNNING", time.Hour, "dc2", "192.0.2.1/32", false, false}, + {"other-destination", "another destination consumes the DC budget only", "RUNNING", time.Hour, "dc1", "192.0.2.2/32", false, true}, + } { + t.Run(tc.name, func(t *testing.T) { + s := testStore(t) + now := time.Now().UTC().Truncate(time.Microsecond) + state := fmt.Sprintf(`{"status":"EXPERIMENT_STATUS_%s","treatment":{"destination":%q}}`, tc.status, tc.destination) + if _, err := s.DB.Exec("INSERT INTO xtcp_control_experiments(id,location,state,updated_at) VALUES('prior',$1,$2,$3)", tc.location, state, now.Add(-tc.age)); err != nil { + t.Fatal(err) + } + blocked, err := discoveryDestinationBlocked(context.Background(), s.DB, "dc1", "192.0.2.1/32", now) + if err != nil || blocked != tc.wantBlocked { + t.Fatalf("%s: blocked=%t err=%v want=%t", tc.description, blocked, err, tc.wantBlocked) + } + active, err := hasActiveExperiment(context.Background(), s.DB, "dc1") + if err != nil || active != tc.wantActive { + t.Fatalf("%s: active=%t err=%v want=%t", tc.description, active, err, tc.wantActive) + } + }) + } +} + +func TestPostgresDiscoverySkipsBusyWarehouse(t *testing.T) { + store := testStore(t) + if _, err := store.DB.Exec(`INSERT INTO xtcp_control_experiments(id,location,state) VALUES('active','dc1','{"status":"EXPERIMENT_STATUS_RUNNING"}')`); err != nil { + t.Fatal(err) + } + s := &Server{Store: store} + called := false + ch := &ClickHouse{URL: "http://fixture.invalid", Table: "db.rows", Client: &http.Client{Transport: discoveryTransport(func(*http.Request) (*http.Response, error) { + called = true + return nil, errors.New("warehouse must not be queried") + })}} + templates := []DiscoveryTemplate{{ID: "nearby", Location: "dc1", CongestionControl: "cubic", MaximumRTTMs: 15}} + if err := s.Discover(context.Background(), ch, templates); err != nil || called { + t.Fatalf("busy location queried warehouse: called=%t err=%v", called, err) + } + if err := store.DB.Close(); err != nil { + t.Fatal(err) + } + if err := s.Discover(context.Background(), ch, templates); err == nil || called { + t.Fatalf("database failure must stop discovery: called=%t err=%v", called, err) + } +} + +func TestPostgresDiscoveryTransactionFence(t *testing.T) { + now := time.Now().UTC() + s := &Server{Store: testStore(t), Now: func() time.Time { return now }} + for i := range 8 { + enroll(t, s, fmt.Sprintf("host-%d", i)) + } + // Reproduce a stable treatment appearing after discovery's initial lookup. + if _, err := s.Store.DB.Exec(`INSERT INTO xtcp_control_experiments(id,location,state) VALUES('stable','dc1','{"status":"EXPERIMENT_STATUS_COMPLETE","treatment":{"destination":"192.0.2.1/32"}}')`); err != nil { + t.Fatal(err) + } + if _, err := s.propose(context.Background(), proposal(now), "candidate-discovery", true); !errors.Is(err, errDiscoverySuppressed) { + t.Fatalf("transaction did not suppress newly stable destination: %v", err) + } + // Operator-driven replacement remains possible; route assignment will retain + // the stable predecessor and still require review before applying anything. + if _, err := s.Propose(testIdentity("operator/alice"), proposal(now)); err != nil { + t.Fatalf("explicit replacement was blocked: %v", err) + } +} + +func TestPostgresConcurrentDiscoveryProposals(t *testing.T) { + now := time.Now().UTC() + s := &Server{Store: testStore(t), Now: func() time.Time { return now }} + for i := range 8 { + enroll(t, s, fmt.Sprintf("host-%d", i)) + } + start := make(chan struct{}) + results := make(chan error, 2) + var wg sync.WaitGroup + for i := range 2 { + wg.Go(func() { + r := proto.Clone(proposal(now)).(*pb.ProposeRequest) + r.RequestId = fmt.Sprintf("candidate-%d", i) + r.Experiment.Id = r.RequestId + <-start + _, err := s.propose(context.Background(), r, "candidate-discovery", true) + results <- err + }) + } + close(start) + wg.Wait() + close(results) + successes, suppressed := 0, 0 + for err := range results { + if err == nil { + successes++ + } else if errors.Is(err, errDiscoverySuppressed) { + suppressed++ + } else { + t.Fatal(err) + } + } + if successes != 1 || suppressed != 1 { + t.Fatalf("want one proposal and one suppressed competitor; got %d/%d", successes, suppressed) + } + var count int + if err := s.Store.DB.QueryRow("SELECT count(*) FROM xtcp_control_experiments").Scan(&count); err != nil || count != 1 { + t.Fatalf("duplicate persisted proposal: count=%d err=%v", count, err) + } +} + +func TestPostgresExperimentQueryIndexes(t *testing.T) { + s := testStore(t) + const seed = `INSERT INTO xtcp_control_experiments(id,location,state,updated_at) + SELECT 'history-'||i,'dc1',jsonb_build_object('status','EXPERIMENT_STATUS_RETIRED', + 'treatment',jsonb_build_object('destination','10.'||(i/65536)||'.'||((i/256)%256)||'.'||(i%256)||'/32')),now()-interval '48 hours' + FROM generate_series(1,100000) i; + INSERT INTO xtcp_control_experiments(id,location,state) + SELECT 'active-'||i,'active-dc-'||i,jsonb_build_object('status','EXPERIMENT_STATUS_RUNNING', + 'treatment',jsonb_build_object('destination','192.0.2.1/32')) FROM generate_series(1,5) i; + ANALYZE xtcp_control_experiments;` + if _, err := s.DB.Exec(seed); err != nil { + t.Fatal(err) + } + // Existing databases can re-run the migration without duplicating indexes. + if err := s.Migrate(context.Background()); err != nil { + t.Fatal(err) + } + for _, tc := range []struct { + name, description, query, index string + args []any + }{ + {"old-destination", "history lookup is selective by location and destination", discoveryDestinationSQL, "xtcp_control_experiments_destination", []any{"dc1", "10.0.0.1/32", time.Now().Add(-24 * time.Hour)}}, + {"absent-destination", "missing destination does not scan historical experiments", discoveryDestinationSQL, "xtcp_control_experiments_destination", []any{"dc1", "203.0.113.1/32", time.Now().Add(-24 * time.Hour)}}, + {"active-budget", "active budget lookup uses status and location", activeExperimentSQL, "xtcp_control_experiments_status_location", []any{"active-dc-1"}}, + {"idle-budget", "idle DC does not scan retired history", activeExperimentSQL, "xtcp_control_experiments_status_location", []any{"dc1"}}, + } { + t.Run(tc.name, func(t *testing.T) { + explain := func(q rowQuerier) ([]byte, float64) { + t.Helper() + var raw []byte + if err := q.QueryRowContext(context.Background(), "EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) "+tc.query, tc.args...).Scan(&raw); err != nil { + t.Fatal(err) + } + var plan []struct { + Milliseconds float64 `json:"Execution Time"` + } + if err := json.Unmarshal(raw, &plan); err != nil { + t.Fatal(err) + } + return raw, plan[0].Milliseconds + } + indexed, after := explain(s.DB) + used := tc.index + // For a location containing only one row, the destination index's + // location prefix is also an efficient budget lookup. Either plan + // is valid; don't force PostgreSQL's cost-based choice. + if tc.query == activeExperimentSQL && strings.Contains(string(indexed), `"Index Name": "xtcp_control_experiments_destination"`) { + used = "xtcp_control_experiments_destination" + } + if !strings.Contains(string(indexed), `"Index Name": "`+used+`"`) { + t.Fatalf("%s: expected planner to choose %s without forced settings: %s", tc.description, tc.index, indexed) + } + // Transactional DDL gives a real no-index comparison and rollback restores + // the production schema. Timings are evidence, never a flaky speed assertion. + tx, err := s.DB.BeginTx(context.Background(), &sql.TxOptions{}) + if err != nil { + t.Fatal(err) + } + defer tx.Rollback() + if _, err = tx.Exec("DROP INDEX xtcp_control_experiments_destination; DROP INDEX xtcp_control_experiments_status_location"); err != nil { + t.Fatal(err) + } + _, before := explain(tx) + if err = tx.Rollback(); err != nil { + t.Fatal(err) + } + t.Logf("100005 experiments: unindexed %.3f ms, indexed %.3f ms (%s)", before, after, used) + }) + } +} diff --git a/pkg/treatment/controller/rpc_contracts_test.go b/pkg/treatment/controller/rpc_contracts_test.go new file mode 100644 index 000000000..6ace60c9a --- /dev/null +++ b/pkg/treatment/controller/rpc_contracts_test.go @@ -0,0 +1,170 @@ +package controller + +import ( + "context" + "crypto/tls" + "crypto/x509" + "errors" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func TestCheckInValidation(t *testing.T) { + for _, tc := range []struct { + name, description string + change func(*pb.CheckInRequest) + want codes.Code + }{ + {"wrong-host", "certificate identity cannot report another host", func(r *pb.CheckInRequest) { r.AgentId = "other" }, codes.PermissionDenied}, + {"wrong-location", "certificate location is authoritative", func(r *pb.CheckInRequest) { r.Location = "other" }, codes.InvalidArgument}, + {"missing-capabilities", "capabilities must be present", func(r *pb.CheckInRequest) { r.Capabilities = nil }, codes.InvalidArgument}, + {"invalid-boot", "boot identity is required", func(r *pb.CheckInRequest) { r.Capabilities.BootId = "" }, codes.InvalidArgument}, + {"long-kernel", "kernel version is bounded", func(r *pb.CheckInRequest) { r.Capabilities.KernelRelease = strings.Repeat("a", 257) }, codes.InvalidArgument}, + {"long-algorithm", "algorithm names fit kernel ABI", func(r *pb.CheckInRequest) { r.Capabilities.CongestionAlgorithms = []string{strings.Repeat("a", 16)} }, codes.InvalidArgument}, + {"nil-record", "nil application record fails closed", func(r *pb.CheckInRequest) { r.Records = []*pb.ApplicationRecord{nil} }, codes.InvalidArgument}, + {"kernel-bytes", "kernel route bytes must remain local", func(r *pb.CheckInRequest) { + r.Records = []*pb.ApplicationRecord{{Assignment: &pb.Assignment{Id: "assignment"}, InstalledRoute: []byte{1}}} + }, codes.InvalidArgument}, + } { + t.Run(tc.name, func(t *testing.T) { + r := &pb.CheckInRequest{AgentId: "host", Location: "dc1", Capabilities: &pb.Capabilities{BootId: "boot", CongestionAlgorithms: []string{"cubic"}}} + tc.change(r) + _, err := (&Server{}).CheckIn(testIdentity("agent/dc1/host"), r) + if status.Code(err) != tc.want { + t.Fatalf("%s: got %v want %v", tc.description, err, tc.want) + } + }) + } +} + +func operatorHTTP(method, path, identity string) *http.Request { + r := httptest.NewRequest(method, path, nil) + if identity != "" { + u, _ := url.Parse("spiffe://xtcp/" + identity) + cert := &x509.Certificate{URIs: []*url.URL{u}} + r.TLS = &tls.ConnectionState{PeerCertificates: []*x509.Certificate{cert}, VerifiedChains: [][]*x509.Certificate{{cert}}} + } + return r +} +func TestStatusAuthorization(t *testing.T) { + for _, tc := range []struct { + description, method, path, identity string + want int + }{ + {"mTLS is mandatory", "GET", "/status", "", 401}, + {"agents cannot inspect fleet status", "GET", "/status", "agent/dc1/host", 403}, + {"status has no mutation endpoint", "POST", "/status", "operator/alice", 405}, + {"unknown paths return not found", "GET", "/unknown", "operator/alice", 404}, + } { + t.Run(tc.description, func(t *testing.T) { + w := httptest.NewRecorder() + (&Server{}).StatusHandler().ServeHTTP(w, operatorHTTP(tc.method, tc.path, tc.identity)) + if w.Code != tc.want { + t.Fatalf("got %d want %d", w.Code, tc.want) + } + }) + } +} + +func TestPostgresStatusAndDesiredAuthorization(t *testing.T) { + s := &Server{Store: testStore(t)} + enroll(t, s, "host") + for _, tc := range []struct { + description, identity string + want codes.Code + }{ + {"enrolled identity can fetch its desired state", "agent/dc1/host", codes.OK}, + {"enrollment location cannot change", "agent/other/host", codes.PermissionDenied}, + {"different host cannot fetch desired state", "agent/dc1/other", codes.PermissionDenied}, + {"operator identity is not an agent", "operator/alice", codes.PermissionDenied}, + } { + t.Run(tc.description, func(t *testing.T) { + got, err := s.GetDesiredState(testIdentity(tc.identity), &pb.GetDesiredStateRequest{AgentId: "host"}) + if status.Code(err) != tc.want { + t.Fatalf("got %v want %v", err, tc.want) + } + if err == nil && (got.Desired.AgentId != "host" || got.Desired.Revision != 1) { + t.Fatal("wrong desired state") + } + }) + } + for _, path := range []string{"/status", "/metrics", "/readyz"} { + t.Run(path, func(t *testing.T) { + w := httptest.NewRecorder() + s.StatusHandler().ServeHTTP(w, operatorHTTP(http.MethodGet, path, "operator/alice")) + if w.Code != 200 || w.Body.Len() == 0 { + t.Fatalf("got %d %s", w.Code, w.Body.String()) + } + if path == "/status" && !strings.Contains(w.Body.String(), `"agents":1`) { + t.Fatal("enrolled agent missing from status") + } + }) + } + if err := s.Store.DB.Close(); err != nil { + t.Fatal(err) + } + w := httptest.NewRecorder() + s.StatusHandler().ServeHTTP(w, operatorHTTP(http.MethodGet, "/readyz", "operator/alice")) + if w.Code != 503 { + t.Fatalf("closed DB reported ready: %d", w.Code) + } +} + +type desiredTestStream struct { + grpc.ServerStream + ctx context.Context + sent chan uint64 +} + +func (s *desiredTestStream) Context() context.Context { return s.ctx } +func (s *desiredTestStream) Send(r *pb.WatchDesiredStateResponse) error { + select { + case s.sent <- r.Revision: + return nil + case <-s.ctx.Done(): + return s.ctx.Err() + } +} + +func TestPostgresWatchLifecycle(t *testing.T) { + s := &Server{Store: testStore(t), Hub: NewNotificationHub()} + enroll(t, s, "host") + ctx, cancel := context.WithTimeout(testIdentity("agent/dc1/host"), 5*time.Second) + defer cancel() + stream := &desiredTestStream{ctx: ctx, sent: make(chan uint64, 4)} + done := make(chan error, 1) + go func() { done <- s.WatchDesiredState(&pb.WatchDesiredStateRequest{AgentId: "host"}, stream) }() + receive := func(want uint64) { + t.Helper() + select { + case got := <-stream.sent: + if got != want { + t.Fatalf("revision=%d want %d", got, want) + } + case <-ctx.Done(): + t.Fatal("watch timed out") + } + } + receive(1) + s.Hub.publish("host", 1) + s.Hub.publish("host", 2) + receive(2) + cancel() + if err := <-done; !errors.Is(err, context.Canceled) { + t.Fatalf("watch cancellation: %v", err) + } + s.Hub.mu.Lock() + defer s.Hub.mu.Unlock() + if len(s.Hub.subs) != 0 { + t.Fatal("watch leaked subscription") + } +} diff --git a/pkg/treatment/controller/schema.sql b/pkg/treatment/controller/schema.sql new file mode 100644 index 000000000..31462538a --- /dev/null +++ b/pkg/treatment/controller/schema.sql @@ -0,0 +1,45 @@ +-- Control-plane state is transactional. ClickHouse is never a coordination lock. +CREATE TABLE IF NOT EXISTS xtcp_control_agents ( + id text PRIMARY KEY, + location text NOT NULL, + desired jsonb NOT NULL, + observed jsonb, + last_seen timestamptz, + suspended boolean NOT NULL DEFAULT false +); +CREATE INDEX IF NOT EXISTS xtcp_control_agents_location ON xtcp_control_agents(location); +CREATE TABLE IF NOT EXISTS xtcp_control_experiments ( + id text PRIMARY KEY, + location text NOT NULL, + state jsonb NOT NULL +); +ALTER TABLE xtcp_control_experiments ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT now(); +-- Match discovery's equality lookups and its terminal-experiment cooldown. +-- Do not index the large evidence-bearing state document as a whole. +CREATE INDEX IF NOT EXISTS xtcp_control_experiments_destination + ON xtcp_control_experiments(location, (state->'treatment'->>'destination'), updated_at DESC); +-- Status first also supports maintenance across all data centers; location +-- narrows the one-active-experiment budget check. +CREATE INDEX IF NOT EXISTS xtcp_control_experiments_status_location + ON xtcp_control_experiments((state->>'status'), location); +CREATE TABLE IF NOT EXISTS xtcp_control_holds ( + scope text PRIMARY KEY, + reason text NOT NULL +); +CREATE TABLE IF NOT EXISTS xtcp_control_events ( + sequence bigserial PRIMARY KEY, + occurred_at timestamptz NOT NULL DEFAULT now(), + actor text NOT NULL, + request_id text UNIQUE, + kind text NOT NULL, + payload jsonb NOT NULL +); +-- Durable event rows are the outbox and audit log. Watch subscribers only need +-- revision hints; reconnect/check-in always fetches authoritative agent state. +CREATE INDEX IF NOT EXISTS xtcp_control_events_time ON xtcp_control_events(occurred_at); +CREATE TABLE IF NOT EXISTS xtcp_control_evidence ( + id text PRIMARY KEY, + experiment_id text NOT NULL, + received_at timestamptz NOT NULL DEFAULT now(), + state jsonb NOT NULL +); diff --git a/pkg/treatment/controller/server.go b/pkg/treatment/controller/server.go new file mode 100644 index 000000000..45ba96b30 --- /dev/null +++ b/pkg/treatment/controller/server.go @@ -0,0 +1,742 @@ +package controller + +import ( + "context" + "crypto/sha256" + "database/sql" + "errors" + "fmt" + "sort" + "strings" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" +) + +type Server struct { + pb.UnimplementedAgentControlServiceServer + pb.UnimplementedOperatorServiceServer + Store *Store + Hub *NotificationHub + EvidenceSource EvidenceSource + Now func() time.Time + // Zero preserves the production one-hour candidate discovery window. + DiscoveryWindow time.Duration +} + +func (s *Server) now() time.Time { + if s.Now != nil { + return s.Now() + } + return time.Now() +} +func failure(e error) error { + if e == nil { + return nil + } + if status.Code(e) != codes.Unknown { + return e + } + if errors.Is(e, sql.ErrNoRows) { + return status.Error(codes.NotFound, "not found") + } + if errors.Is(e, context.Canceled) { + return status.Error(codes.Canceled, "cancelled") + } + return status.Error(codes.FailedPrecondition, e.Error()) +} +func validRequest(id, reason string) error { + if !treatment.ValidID(id) || strings.HasSuffix(id, ".result") || reason == "" || len(reason) > 4096 { + return status.Error(codes.InvalidArgument, "request ID and bounded reason required") + } + return nil +} + +func (s *Server) CheckIn(ctx context.Context, r *pb.CheckInRequest) (*pb.CheckInResponse, error) { + id, e := AgentIdentity(ctx, r.GetAgentId()) + if e != nil { + return nil, e + } + if id.Location != r.Location || r.Capabilities == nil || !treatment.ValidID(r.Capabilities.BootId) || len(r.Capabilities.KernelRelease) > 256 || len(r.Capabilities.CongestionAlgorithms) > 64 || len(r.Records) > treatment.MaxAssignments*2 { + return nil, status.Error(codes.InvalidArgument, "invalid agent report") + } + for _, a := range r.Capabilities.CongestionAlgorithms { + if !treatment.ValidID(a) || len(a) > 15 { + return nil, status.Error(codes.InvalidArgument, "invalid algorithm capability") + } + } + seen := map[string]bool{} + for _, v := range r.Records { + if v == nil || v.Assignment == nil || !treatment.ValidID(v.Assignment.Id) || seen[v.Assignment.Id] || len(v.Error) > 4096 || len(v.InstalledRoute) > 0 { + return nil, status.Error(codes.InvalidArgument, "invalid application record (kernel bytes are local only)") + } + seen[v.Assignment.Id] = true + if e := treatment.ValidateTreatment(v.Assignment.Treatment); e != nil { + return nil, status.Error(codes.InvalidArgument, e.Error()) + } + } + now := s.now() + initial := &pb.DesiredState{AgentId: r.AgentId, Revision: 1, IssuedAt: timestamppb.New(now)} + // New enrollment and hold inheritance are serialized against operator + // controls. The overwhelmingly common heartbeat is one independent update. + var exists bool + e = s.Store.DB.QueryRowContext(ctx, "SELECT EXISTS(SELECT 1 FROM xtcp_control_agents WHERE id=$1)", r.AgentId).Scan(&exists) + if e != nil { + return nil, failure(e) + } + if !exists { + e = s.Store.mutate(ctx, func(tx *sql.Tx) error { + if err := tx.QueryRowContext(ctx, "SELECT EXISTS(SELECT 1 FROM xtcp_control_holds WHERE scope IN ('fleet',$1,$2))", "location:"+r.Location, "agent:"+r.AgentId).Scan(&initial.Suspended); err != nil { + return err + } + b, err := jsonMessage(initial) + if err != nil { + return err + } + _, err = tx.ExecContext(ctx, "INSERT INTO xtcp_control_agents(id,location,desired) VALUES($1,$2,$3) ON CONFLICT(id) DO NOTHING", r.AgentId, r.Location, b) + return err + }) + if e != nil { + return nil, failure(e) + } + } + b, e := jsonMessage(r) + if e != nil { + return nil, failure(e) + } + res, e := s.Store.DB.ExecContext(ctx, "UPDATE xtcp_control_agents SET observed=$2,last_seen=$3 WHERE id=$1 AND location=$4", r.AgentId, b, now, r.Location) + if e != nil { + return nil, failure(e) + } + n, e := res.RowsAffected() + if e != nil { + return nil, failure(e) + } + if n != 1 { + return nil, status.Error(codes.PermissionDenied, "agent location cannot change during enrollment") + } + d, e := desired(ctx, s.Store.DB, r.AgentId) + if e != nil { + return nil, failure(e) + } + if d.Revision == r.DesiredRevision { + return &pb.CheckInResponse{}, nil + } + return &pb.CheckInResponse{Desired: d}, nil +} +func (s *Server) GetDesiredState(ctx context.Context, r *pb.GetDesiredStateRequest) (*pb.GetDesiredStateResponse, error) { + if e := s.authorizeEnrolledAgent(ctx, r.GetAgentId()); e != nil { + return nil, e + } + d, e := desired(ctx, s.Store.DB, r.AgentId) + return &pb.GetDesiredStateResponse{Desired: d}, failure(e) +} +func (s *Server) WatchDesiredState(r *pb.WatchDesiredStateRequest, stream grpc.ServerStreamingServer[pb.WatchDesiredStateResponse]) error { + if e := s.authorizeEnrolledAgent(stream.Context(), r.GetAgentId()); e != nil { + return e + } + if s.Hub == nil { + return status.Error(codes.Unavailable, "notification service unavailable; use check-in") + } + ch, unsubscribe := s.Hub.subscribe(r.AgentId) + defer unsubscribe() + last := r.AfterRevision + d, e := desired(stream.Context(), s.Store.DB, r.AgentId) + if e != nil { + return failure(e) + } + if d.Revision != last { + if e = stream.Send(&pb.WatchDesiredStateResponse{Revision: d.Revision}); e != nil { + return e + } + last = d.Revision + } + for { + select { + case <-stream.Context().Done(): + return stream.Context().Err() + case revision := <-ch: + if revision > last { + if e = stream.Send(&pb.WatchDesiredStateResponse{Revision: revision}); e != nil { + return e + } + last = revision + } + } + } +} + +func (s *Server) authorizeEnrolledAgent(ctx context.Context, want string) error { + id, e := AgentIdentity(ctx, want) + if e != nil { + return e + } + var location string + if e = s.Store.DB.QueryRowContext(ctx, "SELECT location FROM xtcp_control_agents WHERE id=$1", want).Scan(&location); e != nil { + return failure(e) + } + if location != id.Location { + return status.Error(codes.PermissionDenied, "agent location differs from enrollment") + } + return nil +} + +func (s *Server) Propose(ctx context.Context, r *pb.ProposeRequest) (out *pb.ProposeResponse, err error) { + actor, e := OperatorIdentity(ctx) + if e != nil { + return nil, e + } + out, err = s.propose(ctx, r, actor, false) + return out, failure(err) +} + +func (s *Server) propose(ctx context.Context, r *pb.ProposeRequest, actor string, discovered bool) (out *pb.ProposeResponse, err error) { + var e error + if e = validRequest(r.GetRequestId(), "proposal"); e != nil { + return nil, e + } + if e = treatment.ValidateProposal(r.Experiment); e != nil { + return nil, status.Error(codes.InvalidArgument, e.Error()) + } + if r.Experiment.Treatment.Settings.GetCongestionControl() == "" { + return nil, status.Error(codes.InvalidArgument, "v1 experiments require congestion_control to measure actual socket exposure") + } + if len(r.Experiment.CohortOrder) < int(r.Experiment.Policy.MinimumTreatedHosts)+2 { + return nil, status.Error(codes.InvalidArgument, "population must leave at least two controls after minimum treated hosts") + } + out = new(pb.ProposeResponse) + err = s.Store.mutate(ctx, func(tx *sql.Tx) error { + // Discovery's earlier lookup is only an optimization. Recheck under + // the controller mutation lock after warehouse analysis. Explicit + // operator proposals may intentionally replace a stable predecessor. + if discovered { + blocked, err := discoveryDestinationBlocked(ctx, tx, r.Experiment.Location, r.Experiment.Treatment.Destination, s.now()) + if err != nil { + return err + } + if blocked { + return errDiscoverySuppressed + } + } + if yes, e := replay(ctx, tx, r.RequestId, "propose", r, out); yes || e != nil { + return e + } + var exists bool + if e := tx.QueryRowContext(ctx, "SELECT EXISTS(SELECT 1 FROM xtcp_control_experiments WHERE id=$1)", r.Experiment.Id).Scan(&exists); e != nil { + return e + } + if exists { + return errors.New("experiment already exists") + } + n := proto.Clone(r.Experiment).(*pb.Experiment) + n.Revision = 1 + n.Status = pb.ExperimentStatus_EXPERIMENT_STATUS_PROPOSED + n.ProposedAt = timestamppb.New(s.now()) + n.Phases = nil + n.LatestEvidence = nil + n.LatestOutcome = "" + active, e := hasActiveExperiment(ctx, tx, n.Location) + if e != nil { + return e + } + if active { + return errExperimentBudget + } + // Randomized, reproducible allocation within kernel strata. The full + // returned order is reviewed and persisted before any treatment occurs. + strata := map[string][]string{} + for _, id := range n.CohortOrder { + var location string + var observed []byte + var seen time.Time + if e := tx.QueryRowContext(ctx, "SELECT location,observed,last_seen FROM xtcp_control_agents WHERE id=$1", id).Scan(&location, &observed, &seen); e != nil { + return e + } + o := new(pb.CheckInRequest) + if e := protojson.Unmarshal(observed, o); e != nil { + return e + } + if location != n.Location || s.now().Sub(seen) > 2*time.Minute || !o.GetCapabilities().GetSimpleHostRouting() || !o.GetCapabilities().GetActuatorEnabled() { + return fmt.Errorf("agent %s is not eligible", id) + } + if algo := n.Treatment.Settings.GetCongestionControl(); algo != "" { + found := false + for _, v := range o.Capabilities.CongestionAlgorithms { + found = found || v == algo + } + if !found { + return fmt.Errorf("agent %s lacks %s", id, algo) + } + } + strata[o.Capabilities.KernelRelease] = append(strata[o.Capabilities.KernelRelease], id) + } + keys := make([]string, 0, len(strata)) + for k, ids := range strata { + keys = append(keys, k) + sort.Slice(ids, func(i, j int) bool { + a := sha256.Sum256([]byte(n.Id + "/" + ids[i])) + b := sha256.Sum256([]byte(n.Id + "/" + ids[j])) + return string(a[:]) < string(b[:]) + }) + } + sort.Strings(keys) + n.CohortOrder = nil + for i := 0; ; i++ { + added := false + for _, k := range keys { + if i < len(strata[k]) { + n.CohortOrder = append(n.CohortOrder, strata[k][i]) + added = true + } + } + if !added { + break + } + } + if e := saveExperiment(ctx, tx, n); e != nil { + return e + } + out.Experiment = n + if e := audit(ctx, tx, actor, r.RequestId, "propose", r); e != nil { + return e + } + return audit(ctx, tx, actor, r.RequestId+".result", "result", out) + }) + return out, err +} + +func (s *Server) Review(ctx context.Context, r *pb.ReviewRequest) (out *pb.ReviewResponse, err error) { + actor, e := OperatorIdentity(ctx) + if e != nil { + return nil, e + } + if e = validRequest(r.GetRequestId(), r.GetReason()); e != nil { + return nil, e + } + out = new(pb.ReviewResponse) + err = s.Store.mutate(ctx, func(tx *sql.Tx) error { + if yes, e := replay(ctx, tx, r.RequestId, "review", r, out); yes || e != nil { + return e + } + n, e := experiment(ctx, tx, r.ExperimentId) + if e != nil { + return e + } + if n.Revision != r.ExpectedRevision { + return errors.New("experiment revision changed; review fresh evidence") + } + if !r.Approve { + n.Status = pb.ExperimentStatus_EXPERIMENT_STATUS_RETIRED + n.Revision++ + } else { + if len(n.Phases) == 0 { + if r.Evidence != nil { + if e = saveEvidence(ctx, tx, n.Id, r.Evidence); e != nil { + return e + } + n.Baseline = proto.Clone(r.Evidence).(*pb.Evidence) + } + if e = treatment.ValidateProposal(n); e != nil { + return e + } + if n.Baseline.AnalyzedAt.AsTime().After(s.now().Add(30*time.Second)) || s.now().Sub(n.Baseline.WindowEnd.AsTime()) > n.Policy.MaximumEvidenceAge.AsDuration() { + return errors.New("baseline stale/future; supply refreshed baseline evidence with review") + } + } + if len(n.Phases) > 0 { + if e = s.startObservation(ctx, tx, n); e != nil { + return e + } + if n.LatestOutcome == "awaiting_application" { + return errors.New("cohort is not currently verified") + } + if e = saveEvidence(ctx, tx, n.Id, r.Evidence); e != nil { + return e + } + } + n, e = treatment.Advance(n, r.Evidence, s.now(), actor, r.Reason) + if e != nil { + return e + } + } + if e = s.assign(ctx, tx, n, false); e != nil { + return e + } + if e = saveExperiment(ctx, tx, n); e != nil { + return e + } + out.Experiment = n + if e = audit(ctx, tx, actor, r.RequestId, "review", r); e != nil { + return e + } + return audit(ctx, tx, actor, r.RequestId+".result", "result", out) + }) + return out, failure(err) +} + +func saveEvidence(ctx context.Context, tx *sql.Tx, id string, v *pb.Evidence) error { + if e := treatment.ValidateEvidence(v); e != nil { + return e + } + b, e := jsonMessage(v) + if e != nil { + return e + } + var old []byte + var owner string + e = tx.QueryRowContext(ctx, "SELECT state,experiment_id FROM xtcp_control_evidence WHERE id=$1", v.Id).Scan(&old, &owner) + if e == nil { + p := new(pb.Evidence) + if e = protojson.Unmarshal(old, p); e != nil { + return e + } + if owner != id || !proto.Equal(p, v) { + return errors.New("evidence IDs are immutable and experiment-specific") + } + return nil + } + if !errors.Is(e, sql.ErrNoRows) { + return e + } + _, e = tx.ExecContext(ctx, "INSERT INTO xtcp_control_evidence(id,experiment_id,state) VALUES($1,$2,$3)", v.Id, id, b) + return e +} + +func (s *Server) startObservation(ctx context.Context, tx *sql.Tx, e *pb.Experiment) error { + if len(e.Phases) == 0 { + return nil + } + p := e.Phases[len(e.Phases)-1] + latest := p.IssuedAt.AsTime() + notReady := func() error { e.LatestOutcome = "awaiting_application"; return nil } + for _, id := range e.CohortOrder[:p.TargetHosts] { + var raw []byte + var seen time.Time + if err := tx.QueryRowContext(ctx, "SELECT observed,last_seen FROM xtcp_control_agents WHERE id=$1", id).Scan(&raw, &seen); err != nil { + return err + } + o := new(pb.CheckInRequest) + if err := protojson.Unmarshal(raw, o); err != nil { + return err + } + if s.now().Sub(seen) > 2*time.Minute || o.LocallySuspended { + return notReady() + } + d, err := desired(ctx, tx, id) + if err != nil { + return err + } + if d.Suspended || o.DesiredRevision != d.Revision { + return notReady() + } + var expected *pb.Assignment + for _, a := range d.Assignments { + if a.ExperimentId == e.Id { + expected = a + } + } + if expected == nil || expected.Mode != pb.AssignmentMode_ASSIGNMENT_MODE_TRIAL || expected.LeaseExpiresAt == nil || !expected.LeaseExpiresAt.AsTime().After(s.now()) { + return notReady() + } + found := false + for _, a := range o.Records { + if a.Assignment.Id == expected.Id && a.Assignment.Generation == expected.Generation && a.Status == pb.ApplyStatus_APPLY_STATUS_VERIFIED && !a.ExpiredLatched && a.AppliedAt != nil && a.AppliedAt.CheckValid() == nil && proto.Equal(a.Assignment.Treatment, expected.Treatment) { + found = true + t := a.AppliedAt.AsTime() + if t.After(s.now().Add(30 * time.Second)) { + return errors.New("agent clock ahead") + } + if t.After(latest) { + latest = t + } + } + } + if !found { + return notReady() + } + } + if p.ObservationStartedAt == nil { + p.ObservationStartedAt = timestamppb.New(latest) + } + if e.LatestOutcome == "awaiting_application" { + e.LatestOutcome = "observing" + } + return nil +} + +func (s *Server) assign(ctx context.Context, tx *sql.Tx, e *pb.Experiment, reactivate bool) error { + target := 0 + if len(e.Phases) > 0 { + target = int(e.Phases[len(e.Phases)-1].TargetHosts) + } + for i, id := range e.CohortOrder { + d, err := desired(ctx, tx, id) + if err != nil { + return err + } + var a *pb.Assignment + for _, x := range d.Assignments { + if x.ExperimentId == e.Id { + a = x + break + } + } + if i >= target && a == nil { + continue + } + if a == nil { + var report []byte + var seen time.Time + if err = tx.QueryRowContext(ctx, "SELECT observed,last_seen FROM xtcp_control_agents WHERE id=$1", id).Scan(&report, &seen); err != nil { + return err + } + o := new(pb.CheckInRequest) + if err = protojson.Unmarshal(report, o); err != nil { + return err + } + if d.Suspended || o.LocallySuspended || s.now().Sub(seen) > 2*time.Minute || !o.GetCapabilities().GetSimpleHostRouting() || !o.GetCapabilities().GetActuatorEnabled() { + return fmt.Errorf("agent %s no longer eligible", id) + } + available := false + for _, cc := range o.GetCapabilities().GetCongestionAlgorithms() { + available = available || cc == e.Treatment.Settings.GetCongestionControl() + } + if !available { + return fmt.Errorf("agent %s no longer has required algorithm", id) + } + var predecessor *pb.Treatment + for _, x := range d.Assignments { + if x.Mode != pb.AssignmentMode_ASSIGNMENT_MODE_RETIRED && x.Treatment.Destination == e.Treatment.Destination { + if x.Mode != pb.AssignmentMode_ASSIGNMENT_MODE_STABLE { + return errors.New("destination already has an active trial/hold") + } + predecessor = proto.Clone(x.Treatment).(*pb.Treatment) + x.Mode = pb.AssignmentMode_ASSIGNMENT_MODE_RETIRED + } + } + a = &pb.Assignment{Id: e.Id, ExperimentId: e.Id, Treatment: proto.Clone(e.Treatment).(*pb.Treatment), Generation: 1} + d.Assignments = append(d.Assignments, a) + a.Predecessor = predecessor + } + switch e.Status { + case pb.ExperimentStatus_EXPERIMENT_STATUS_COMPLETE: + a.Mode = pb.AssignmentMode_ASSIGNMENT_MODE_STABLE + a.LeaseExpiresAt = nil + case pb.ExperimentStatus_EXPERIMENT_STATUS_SUSPENDED: + a.Mode = pb.AssignmentMode_ASSIGNMENT_MODE_SUSPENDED + case pb.ExperimentStatus_EXPERIMENT_STATUS_RETIRED, pb.ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK: + a.Mode = pb.AssignmentMode_ASSIGNMENT_MODE_RETIRED + if e.Status == pb.ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK && a.Predecessor != nil { + a.Treatment = proto.Clone(a.Predecessor).(*pb.Treatment) + a.Mode = pb.AssignmentMode_ASSIGNMENT_MODE_STABLE + a.Generation++ + } + default: + a.Mode = pb.AssignmentMode_ASSIGNMENT_MODE_TRIAL + if a.LeaseExpiresAt == nil || reactivate { + a.LeaseExpiresAt = timestamppb.New(s.now().Add(treatment.TrialLease)) + } + } + if reactivate { + a.Generation++ + } + d.Revision++ + d.IssuedAt = timestamppb.New(s.now()) + if err = treatment.ValidateDesired(d); err != nil { + return err + } + if err = saveDesired(ctx, tx, d); err != nil { + return err + } + } + return nil +} + +func (s *Server) Control(ctx context.Context, r *pb.ControlRequest) (out *pb.ControlResponse, err error) { + actor, e := OperatorIdentity(ctx) + if e != nil { + return nil, e + } + if e = validRequest(r.GetRequestId(), r.GetReason()); e != nil { + return nil, e + } + nsel := 0 + for _, x := range []string{r.ExperimentId, r.AgentId, r.Location} { + if x != "" { + if !treatment.ValidID(x) { + return nil, status.Error(codes.InvalidArgument, "invalid selector") + } + nsel++ + } + } + if r.Fleet { + nsel++ + } + if nsel != 1 || r.Action < pb.ControlAction_CONTROL_ACTION_PAUSE || r.Action > pb.ControlAction_CONTROL_ACTION_RETIRE { + return nil, status.Error(codes.InvalidArgument, "one scope and a valid action required") + } + out = new(pb.ControlResponse) + err = s.Store.mutate(ctx, func(tx *sql.Tx) error { + if yes, e := replay(ctx, tx, r.RequestId, "control", r, out); yes || e != nil { + return e + } + if r.ExperimentId != "" { + n, e := experiment(ctx, tx, r.ExperimentId) + if e != nil { + return e + } + if n.Status == pb.ExperimentStatus_EXPERIMENT_STATUS_RETIRED || n.Status == pb.ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK { + return errors.New("terminal experiment cannot be reactivated") + } + switch r.Action { + case pb.ControlAction_CONTROL_ACTION_PAUSE: + if n.Status != pb.ExperimentStatus_EXPERIMENT_STATUS_RUNNING { + return errors.New("only running experiment can pause") + } + n.Status = pb.ExperimentStatus_EXPERIMENT_STATUS_PAUSED + case pb.ControlAction_CONTROL_ACTION_SUSPEND: + n.Status = pb.ExperimentStatus_EXPERIMENT_STATUS_SUSPENDED + case pb.ControlAction_CONTROL_ACTION_RETIRE: + n.Status = pb.ExperimentStatus_EXPERIMENT_STATUS_RETIRED + case pb.ControlAction_CONTROL_ACTION_ROLLBACK: + n.Status = pb.ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK + case pb.ControlAction_CONTROL_ACTION_RESUME: + if n.Status != pb.ExperimentStatus_EXPERIMENT_STATUS_PAUSED && n.Status != pb.ExperimentStatus_EXPERIMENT_STATUS_SUSPENDED { + return errors.New("experiment is not held") + } + if len(n.Phases) == 0 { + return errors.New("proposal requires initial review") + } + n.Status = pb.ExperimentStatus_EXPERIMENT_STATUS_RUNNING + p := n.Phases[len(n.Phases)-1] + if p.Outcome == "final_safety" { + n.Status = pb.ExperimentStatus_EXPERIMENT_STATUS_COMPLETE + } else { + n.Phases = append(n.Phases, &pb.Phase{TargetHosts: p.TargetHosts, IssuedAt: timestamppb.New(s.now()), Reviewer: actor, Reason: r.Reason}) + } + } + n.Revision++ + if e = s.assign(ctx, tx, n, r.Action == pb.ControlAction_CONTROL_ACTION_RESUME); e != nil { + return e + } + if e = saveExperiment(ctx, tx, n); e != nil { + return e + } + out.AffectedAgents = uint32(len(n.CohortOrder)) + } else { + if r.Action != pb.ControlAction_CONTROL_ACTION_SUSPEND && r.Action != pb.ControlAction_CONTROL_ACTION_RESUME { + return errors.New("non-experiment controls support suspend/resume only") + } + scope := "fleet" + if r.AgentId != "" { + scope = "agent:" + r.AgentId + } + if r.Location != "" { + scope = "location:" + r.Location + } + var e error + if r.Action == pb.ControlAction_CONTROL_ACTION_SUSPEND { + _, e = tx.ExecContext(ctx, "INSERT INTO xtcp_control_holds(scope,reason) VALUES($1,$2) ON CONFLICT(scope) DO UPDATE SET reason=EXCLUDED.reason", scope, r.Reason) + } else { + _, e = tx.ExecContext(ctx, "DELETE FROM xtcp_control_holds WHERE scope=$1", scope) + } + if e != nil { + return e + } + rows, e := tx.QueryContext(ctx, "SELECT id,location FROM xtcp_control_agents WHERE ($1='' OR id=$1) AND ($2='' OR location=$2)", r.AgentId, r.Location) + if e != nil { + return e + } + type host struct{ id, loc string } + var hosts []host + for rows.Next() { + var h host + if e = rows.Scan(&h.id, &h.loc); e != nil { + rows.Close() + return e + } + hosts = append(hosts, h) + } + e = rows.Err() + rows.Close() + if e != nil { + return e + } + for _, h := range hosts { + d, e := desired(ctx, tx, h.id) + if e != nil { + return e + } + if e = tx.QueryRowContext(ctx, "SELECT EXISTS(SELECT 1 FROM xtcp_control_holds WHERE scope IN ('fleet',$1,$2))", "location:"+h.loc, "agent:"+h.id).Scan(&d.Suspended); e != nil { + return e + } + d.Revision++ + d.IssuedAt = timestamppb.New(s.now()) + if e = saveDesired(ctx, tx, d); e != nil { + return e + } + } + out.AffectedAgents = uint32(len(hosts)) + } + if e := audit(ctx, tx, actor, r.RequestId, "control", r); e != nil { + return e + } + return audit(ctx, tx, actor, r.RequestId+".result", "result", out) + }) + return out, failure(err) +} + +func (s *Server) Inspect(ctx context.Context, r *pb.InspectRequest) (*pb.InspectResponse, error) { + if _, e := OperatorIdentity(ctx); e != nil { + return nil, e + } + out := new(pb.InspectResponse) + rows, e := s.Store.DB.QueryContext(ctx, "SELECT state FROM xtcp_control_experiments WHERE ($1='' OR id=$1) ORDER BY id LIMIT 1000", r.ExperimentId) + if e != nil { + return nil, failure(e) + } + for rows.Next() { + var b []byte + if e = rows.Scan(&b); e != nil { + break + } + v := new(pb.Experiment) + if e = protojson.Unmarshal(b, v); e != nil { + break + } + out.Experiments = append(out.Experiments, v) + } + e = errors.Join(e, rows.Err()) + rows.Close() + if e != nil { + return nil, failure(e) + } + // Host details are deliberately explicit; a dashboard must not pull the + // entire fleet's route journals on every refresh. + if r.AgentId != "" { + d, e := desired(ctx, s.Store.DB, r.AgentId) + if e != nil { + return nil, failure(e) + } + out.Desired = append(out.Desired, d) + var b []byte + if e = s.Store.DB.QueryRowContext(ctx, "SELECT observed FROM xtcp_control_agents WHERE id=$1", r.AgentId).Scan(&b); e != nil { + return nil, failure(e) + } + if len(b) > 0 { + o := new(pb.CheckInRequest) + if e = protojson.Unmarshal(b, o); e != nil { + return nil, failure(e) + } + out.Observed = append(out.Observed, o) + } + } + return out, nil +} diff --git a/pkg/treatment/controller/server_test.go b/pkg/treatment/controller/server_test.go new file mode 100644 index 000000000..5b762c426 --- /dev/null +++ b/pkg/treatment/controller/server_test.go @@ -0,0 +1,387 @@ +package controller + +import ( + "context" + "crypto/tls" + "crypto/x509" + "database/sql" + "fmt" + "net/url" + "os" + "strings" + "sync" + "testing" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/peer" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func testIdentity(path string) context.Context { + u, _ := url.Parse("spiffe://xtcp/" + path) + cert := &x509.Certificate{URIs: []*url.URL{u}} + return peer.NewContext(context.Background(), &peer.Peer{AuthInfo: credentials.TLSInfo{State: tls.ConnectionState{PeerCertificates: []*x509.Certificate{cert}, VerifiedChains: [][]*x509.Certificate{{cert}}}}}) +} + +func testStore(t *testing.T) *Store { + t.Helper() + dsn := os.Getenv("XTCP_CONTROLLER_TEST_DSN") + if dsn == "" { + t.Skip("set XTCP_CONTROLLER_TEST_DSN for isolated PostgreSQL integration") + } + db, e := sql.Open("postgres", dsn) + if e != nil { + t.Fatal(e) + } + schema := fmt.Sprintf("xtcp_test_%d", time.Now().UnixNano()) + if _, e = db.Exec("CREATE SCHEMA " + schema); e != nil { + db.Close() + t.Fatal(e) + } + if strings.HasPrefix(dsn, "postgres://") || strings.HasPrefix(dsn, "postgresql://") { + u, e := url.Parse(dsn) + if e != nil { + t.Fatal(e) + } + q := u.Query() + q.Set("search_path", schema) + u.RawQuery = q.Encode() + dsn = u.String() + } else { + dsn += " search_path=" + schema + } + s, e := Open(context.Background(), dsn) + if e != nil { + t.Fatal(e) + } + t.Cleanup(func() { s.DB.Close(); _, _ = db.Exec("DROP SCHEMA " + schema + " CASCADE"); db.Close() }) + if e = s.Migrate(context.Background()); e != nil { + t.Fatal(e) + } + return s +} + +func enroll(t *testing.T, s *Server, id string) { + t.Helper() + _, e := s.CheckIn(testIdentity("agent/dc1/"+id), &pb.CheckInRequest{AgentId: id, Location: "dc1", Capabilities: &pb.Capabilities{BootId: "boot-1", KernelRelease: "test", CongestionAlgorithms: []string{"cubic", "bbr"}, SimpleHostRouting: true, ActuatorEnabled: true}}) + if e != nil { + t.Fatal(e) + } +} +func proposal(now time.Time) *pb.ProposeRequest { + e := &pb.Experiment{Id: "experiment-1", Location: "dc1", Hypothesis: "test cubic", Policy: treatment.DefaultPolicy(), Treatment: &pb.Treatment{Id: "cubic", Revision: 1, Destination: "192.0.2.1/32", Settings: &pb.TCPSettings{CongestionControl: proto.String("cubic")}}, Baseline: &pb.Evidence{Id: "baseline-1", AnalyzedAt: timestamppb.New(now), WindowStart: timestamppb.New(now.Add(-time.Hour)), WindowEnd: timestamppb.New(now.Add(-time.Second)), QueryVersion: "test", ArtifactSha256: strings.Repeat("a", 64)}} + for i := range 8 { + id := fmt.Sprintf("host-%d", i) + e.CohortOrder = append(e.CohortOrder, id) + e.Baseline.Hosts = append(e.Baseline.Hosts, &pb.HostEvidence{AgentId: id, Sockets: 100, BytesAcked: 1 << 25, GoodputBps: 1e8, RttP95Ms: 1, CollectionHealthy: true}) + } + return &pb.ProposeRequest{Experiment: e, RequestId: "proposal-1"} +} + +func TestPostgresReviewedRolloutAndPersistentFleetHold(t *testing.T) { + now := time.Now().UTC() + s := &Server{Store: testStore(t), Now: func() time.Time { return now }} + operator := testIdentity("operator/alice") + for i := range 8 { + enroll(t, s, fmt.Sprintf("host-%d", i)) + } + p, e := s.Propose(operator, proposal(now)) + if e != nil { + t.Fatal(e) + } + if len(p.Experiment.Phases) != 0 { + t.Fatal("proposal applied without approval") + } + r := &pb.ReviewRequest{ExperimentId: p.Experiment.Id, ExpectedRevision: 1, Approve: true, Reason: "reviewed", RequestId: "review-1"} + v, e := s.Review(operator, r) + if e != nil { + t.Fatal(e) + } + if len(v.Experiment.Phases) != 1 || v.Experiment.Phases[0].TargetHosts != 1 { + t.Fatal("wrong initial canary") + } + replay, e := s.Review(operator, r) + if e != nil || !proto.Equal(replay, v) { + t.Fatal("review retry was not idempotent", e) + } + bad := proto.Clone(r).(*pb.ReviewRequest) + bad.Reason = "changed" + if _, e = s.Review(operator, bad); e == nil { + t.Fatal("accepted reused request ID with changed body") + } + var assignments int + if e = s.Store.DB.QueryRow("SELECT sum(jsonb_array_length(coalesce(desired->'assignments','[]'))) FROM xtcp_control_agents").Scan(&assignments); e != nil { + t.Fatal(e) + } + if assignments != 1 { + t.Fatal("incorrect fleet exposure", assignments) + } + if _, e = s.Control(operator, &pb.ControlRequest{Fleet: true, Action: pb.ControlAction_CONTROL_ACTION_SUSPEND, Reason: "incident", RequestId: "hold-1"}); e != nil { + t.Fatal(e) + } + enroll(t, s, "new-host") + d, e := desired(context.Background(), s.Store.DB, "new-host") + if e != nil || !d.Suspended { + t.Fatal("new host escaped persistent fleet hold", e) + } + // A narrower resume cannot clear the fleet-wide hold. + if _, e = s.Control(operator, &pb.ControlRequest{AgentId: "new-host", Action: pb.ControlAction_CONTROL_ACTION_RESUME, Reason: "host resume", RequestId: "resume-1"}); e != nil { + t.Fatal(e) + } + d, e = desired(context.Background(), s.Store.DB, "new-host") + if e != nil || !d.Suspended { + t.Fatal("narrower resume overrode fleet hold") + } + if _, e = s.Review(testIdentity("agent/dc1/host-0"), r); e == nil { + t.Fatal("agent obtained operator authority") + } +} + +func TestPostgresConcurrentReviewFencesReplicas(t *testing.T) { + now := time.Now().UTC() + s := &Server{Store: testStore(t), Now: func() time.Time { return now }} + operator := testIdentity("operator/alice") + for i := range 8 { + enroll(t, s, fmt.Sprintf("host-%d", i)) + } + if _, e := s.Propose(operator, proposal(now)); e != nil { + t.Fatal(e) + } + var wg sync.WaitGroup + errs := make(chan error, 2) + for i := range 2 { + wg.Add(1) + go func(i int) { + defer wg.Done() + replica := &Server{Store: s.Store, Now: s.Now} + _, e := replica.Review(operator, &pb.ReviewRequest{ExperimentId: "experiment-1", ExpectedRevision: 1, Approve: true, Reason: "reviewed", RequestId: fmt.Sprintf("concurrent-%d", i)}) + errs <- e + }(i) + } + wg.Wait() + close(errs) + success := 0 + for e := range errs { + if e == nil { + success++ + } + } + if success != 1 { + t.Fatal("concurrent reviewers both advanced", success) + } +} + +func TestCertificateIdentityIsolation(t *testing.T) { + if _, e := AgentIdentity(testIdentity("agent/dc1/host-1"), "host-2"); e == nil { + t.Fatal("cross-agent identity accepted") + } + if _, e := OperatorIdentity(context.Background()); e == nil { + t.Fatal("missing TLS accepted") + } + u, _ := url.Parse("spiffe://xtcp/operator/alice") + v, _ := url.Parse("spiffe://xtcp/agent/dc1/host-1") + if _, e := CertificateIdentity(&x509.Certificate{URIs: []*url.URL{u, v}}); e == nil { + t.Fatal("ambiguous role accepted") + } +} + +func TestNotificationFanoutTenThousandAgents(t *testing.T) { + h := NewNotificationHub() + var channels []chan uint64 + var cleanup []func() + for i := range 10000 { + ch, done := h.subscribe(fmt.Sprint(i)) + channels = append(channels, ch) + cleanup = append(cleanup, done) + } + for i := range 10000 { + h.publish(fmt.Sprint(i), 1) + h.publish(fmt.Sprint(i), 2) + } + for _, ch := range channels { + select { + case v := <-ch: + if v != 2 { + t.Fatal("did not coalesce latest revision") + } + default: + t.Fatal("missing suspension notification") + } + } + for _, done := range cleanup { + done() + } +} + +type fixedEvidence struct{ value *pb.Evidence } + +func (f fixedEvidence) Analyze(context.Context, *pb.Experiment) (*pb.Evidence, error) { + return f.value, nil +} + +func acknowledgeFleet(t *testing.T, s *Server, n *pb.Experiment) { + t.Helper() + for _, id := range n.CohortOrder { + d, e := desired(context.Background(), s.Store.DB, id) + if e != nil { + t.Fatal(e) + } + r := &pb.CheckInRequest{AgentId: id, Location: n.Location, DesiredRevision: d.Revision, Capabilities: &pb.Capabilities{BootId: "boot-1", KernelRelease: "test", CongestionAlgorithms: []string{"cubic", "bbr"}, SimpleHostRouting: true, ActuatorEnabled: true}} + for _, a := range d.Assignments { + r.Records = append(r.Records, &pb.ApplicationRecord{Assignment: proto.Clone(a).(*pb.Assignment), DesiredRevision: d.Revision, Status: pb.ApplyStatus_APPLY_STATUS_VERIFIED, AppliedAt: n.Phases[len(n.Phases)-1].IssuedAt}) + } + if _, e = s.CheckIn(testIdentity("agent/dc1/"+id), r); e != nil { + t.Fatal(e) + } + } +} + +func TestPostgresCompleteReviewedRolloutAndStableWithdrawal(t *testing.T) { + now := time.Now().UTC() + s := &Server{Store: testStore(t), Now: func() time.Time { return now }} + operator := testIdentity("operator/alice") + for i := range 8 { + enroll(t, s, fmt.Sprintf("host-%d", i)) + } + if _, e := s.Propose(operator, proposal(now)); e != nil { + t.Fatal(e) + } + approved, e := s.Review(operator, &pb.ReviewRequest{ExperimentId: "experiment-1", ExpectedRevision: 1, Approve: true, Reason: "initial review", RequestId: "approve-initial"}) + if e != nil { + t.Fatal(e) + } + n := approved.Experiment + for phase, target := range []uint32{1, 2, 4, 6, 8} { + if n.Phases[len(n.Phases)-1].TargetHosts != target { + t.Fatal("wrong cohort", n) + } + now = now.Add(31 * time.Minute) + acknowledgeFleet(t, s, n) + ev := proto.Clone(n.Baseline).(*pb.Evidence) + ev.Id = fmt.Sprintf("phase-evidence-%d", phase) + ev.WindowStart = n.Phases[len(n.Phases)-1].IssuedAt + ev.WindowEnd = timestamppb.New(now.Add(-time.Second)) + ev.AnalyzedAt = timestamppb.New(now) + for _, h := range ev.Hosts { + for i, id := range n.CohortOrder { + if h.AgentId == id && i < int(target) { + h.GoodputBps *= 1.2 + h.ExposedSockets = h.Sockets + } + } + } + if e = s.Maintain(context.Background(), fixedEvidence{ev}); e != nil { + t.Fatal("maintenance", e) + } + // Maintenance may renew leases but must never expand the cohort. + inspected, e := s.Inspect(operator, &pb.InspectRequest{ExperimentId: n.Id}) + if e != nil { + t.Fatal(e) + } + if inspected.Experiments[0].Revision != n.Revision || len(inspected.Experiments[0].Phases) != len(n.Phases) { + t.Fatal("maintenance promoted without review") + } + acknowledgeFleet(t, s, n) + approved, e = s.Review(operator, &pb.ReviewRequest{ExperimentId: n.Id, ExpectedRevision: n.Revision, Approve: true, Reason: "evidence reviewed", RequestId: fmt.Sprintf("approve-phase-%d", phase), Evidence: ev}) + if e != nil { + t.Fatalf("phase %d review: %v", phase, e) + } + n = approved.Experiment + } + if n.Status != pb.ExperimentStatus_EXPERIMENT_STATUS_COMPLETE { + t.Fatal("rollout did not complete") + } + for _, id := range n.CohortOrder { + d, e := desired(context.Background(), s.Store.DB, id) + if e != nil { + t.Fatal(e) + } + if d.Assignments[0].Mode != pb.AssignmentMode_ASSIGNMENT_MODE_STABLE || d.Assignments[0].LeaseExpiresAt != nil { + t.Fatal("stable treatment still leased") + } + } + for i, action := range []pb.ControlAction{pb.ControlAction_CONTROL_ACTION_SUSPEND, pb.ControlAction_CONTROL_ACTION_RESUME} { + if _, e = s.Control(operator, &pb.ControlRequest{ExperimentId: n.Id, Action: action, Reason: "withdrawal comparison", RequestId: fmt.Sprintf("withdrawal-%d", i)}); e != nil { + t.Fatal(e) + } + } + inspected, e := s.Inspect(operator, &pb.InspectRequest{ExperimentId: n.Id}) + if e != nil { + t.Fatal(e) + } + if inspected.Experiments[0].Status != pb.ExperimentStatus_EXPERIMENT_STATUS_COMPLETE { + t.Fatal("resume did not retain stable approval") + } +} + +func TestPostgresFastSafetyWithdrawalBeforePromotionWindow(t *testing.T) { + now := time.Now().UTC() + s := &Server{Store: testStore(t), Now: func() time.Time { return now }} + operator := testIdentity("operator/alice") + for i := range 8 { + enroll(t, s, fmt.Sprintf("host-%d", i)) + } + if _, e := s.Propose(operator, proposal(now)); e != nil { + t.Fatal(e) + } + r, e := s.Review(operator, &pb.ReviewRequest{ExperimentId: "experiment-1", ExpectedRevision: 1, Approve: true, Reason: "initial", RequestId: "initial"}) + if e != nil { + t.Fatal(e) + } + now = now.Add(5 * time.Minute) + acknowledgeFleet(t, s, r.Experiment) + ev := proto.Clone(r.Experiment.Baseline).(*pb.Evidence) + ev.Id = "harmful" + ev.WindowStart = r.Experiment.Phases[0].IssuedAt + ev.WindowEnd = timestamppb.New(now) + ev.AnalyzedAt = ev.WindowEnd + ev.Hosts[0].RetransmissionRatio = .1 + if e = s.Maintain(context.Background(), fixedEvidence{ev}); e != nil { + t.Fatal(e) + } + v, e := s.Inspect(operator, &pb.InspectRequest{ExperimentId: r.Experiment.Id}) + if e != nil { + t.Fatal(e) + } + if v.Experiments[0].Status != pb.ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK { + t.Fatal("harm was not withdrawn before thirty minutes") + } +} + +func TestPostgresAnalyzeRefreshesWithoutApproval(t *testing.T) { + now := time.Now().UTC() + s := &Server{Store: testStore(t), Now: func() time.Time { return now }} + operator := testIdentity("operator/alice") + for i := range 8 { + enroll(t, s, fmt.Sprintf("host-%d", i)) + } + p := proposal(now) + if _, e := s.Propose(operator, p); e != nil { + t.Fatal(e) + } + ev := proto.Clone(p.Experiment.Baseline).(*pb.Evidence) + ev.Id = "refresh-1" + s.EvidenceSource = fixedEvidence{ev} + out, e := s.Analyze(operator, &pb.AnalyzeRequest{ExperimentId: p.Experiment.Id, ExpectedRevision: 1}) + if e != nil || out.Outcome != "baseline" { + t.Fatal("refresh", out, e) + } + inspected, e := s.Inspect(operator, &pb.InspectRequest{ExperimentId: p.Experiment.Id}) + if e != nil { + t.Fatal(e) + } + n := inspected.Experiments[0] + if n.Status != pb.ExperimentStatus_EXPERIMENT_STATUS_PROPOSED || n.Revision != 1 || len(n.Phases) > 0 || !proto.Equal(n.LatestEvidence, ev) { + t.Fatal("analysis changed rollout authority") + } + if _, e = s.Analyze(operator, &pb.AnalyzeRequest{ExperimentId: n.Id, ExpectedRevision: 2}); e == nil { + t.Fatal("stale analysis revision accepted") + } + if _, e = s.Analyze(testIdentity("agent/dc1/host-0"), &pb.AnalyzeRequest{ExperimentId: n.Id, ExpectedRevision: 1}); e == nil { + t.Fatal("agent could analyze as operator") + } +} diff --git a/pkg/treatment/controller/status.go b/pkg/treatment/controller/status.go new file mode 100644 index 000000000..301323113 --- /dev/null +++ b/pkg/treatment/controller/status.go @@ -0,0 +1,78 @@ +package controller + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "sort" + "time" +) + +// StatusHandler provides an operator-authenticated JSON dashboard feed and +// Prometheus metrics. Avoid destination/agent labels on fleet metrics. +func (s *Server) StatusHandler() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.TLS == nil || len(r.TLS.VerifiedChains) == 0 || len(r.TLS.PeerCertificates) == 0 { + http.Error(w, "mTLS required", http.StatusUnauthorized) + return + } + id, e := CertificateIdentity(r.TLS.PeerCertificates[0]) + if e != nil || id.Role != "operator" { + http.Error(w, "operator required", http.StatusForbidden) + return + } + if r.Method != http.MethodGet { + http.Error(w, "GET required", http.StatusMethodNotAllowed) + return + } + if r.URL.Path != "/status" && r.URL.Path != "/metrics" && r.URL.Path != "/readyz" { + http.NotFound(w, r) + return + } + ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second) + defer cancel() + if r.URL.Path == "/readyz" { + if e := s.Store.DB.PingContext(ctx); e != nil { + http.Error(w, "database unavailable", http.StatusServiceUnavailable) + return + } + fmt.Fprintln(w, "ready") + return + } + queries := map[string]string{ + "agents": "SELECT count(*) FROM xtcp_control_agents", + "agents_connected": "SELECT count(*) FROM xtcp_control_agents WHERE last_seen>now()-interval '2 minutes'", + "agents_suspended": "SELECT count(*) FROM xtcp_control_agents WHERE desired->>'suspended'='true' OR observed->>'locallySuspended'='true'", + "assignments": "SELECT coalesce(sum(jsonb_array_length(coalesce(desired->'assignments','[]'))),0) FROM xtcp_control_agents", + "routes_verified": "SELECT count(*) FROM xtcp_control_agents a CROSS JOIN LATERAL jsonb_array_elements(coalesce(a.observed->'records','[]')) r WHERE r->>'status'='APPLY_STATUS_VERIFIED'", + "routes_failed": "SELECT count(*) FROM xtcp_control_agents a CROSS JOIN LATERAL jsonb_array_elements(coalesce(a.observed->'records','[]')) r WHERE r->>'status'='APPLY_STATUS_FAILED'", + "routes_expired": "SELECT count(*) FROM xtcp_control_agents a CROSS JOIN LATERAL jsonb_array_elements(coalesce(a.observed->'records','[]')) r WHERE r->>'status'='APPLY_STATUS_EXPIRED'", + "experiments_pending": "SELECT count(*) FROM xtcp_control_experiments WHERE state->>'status'='EXPERIMENT_STATUS_PROPOSED'", + "experiments_running": "SELECT count(*) FROM xtcp_control_experiments WHERE state->>'status'='EXPERIMENT_STATUS_RUNNING'", + "experiments_complete": "SELECT count(*) FROM xtcp_control_experiments WHERE state->>'status'='EXPERIMENT_STATUS_COMPLETE'", + "audit_events": "SELECT count(*) FROM xtcp_control_events", + } + values := map[string]float64{} + keys := make([]string, 0, len(queries)) + for k, q := range queries { + var v float64 + if e := s.Store.DB.QueryRowContext(ctx, q).Scan(&v); e != nil { + http.Error(w, "status query failed", http.StatusServiceUnavailable) + return + } + values[k] = v + keys = append(keys, k) + } + if r.URL.Path == "/status" { + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(values) + return + } + w.Header().Set("Content-Type", "text/plain; version=0.0.4") + sort.Strings(keys) + for _, k := range keys { + fmt.Fprintf(w, "# TYPE xtcp_controller_%s gauge\nxtcp_controller_%s %g\n", k, k, values[k]) + } + }) +} diff --git a/pkg/treatment/controller/store.go b/pkg/treatment/controller/store.go new file mode 100644 index 000000000..72ceae8f2 --- /dev/null +++ b/pkg/treatment/controller/store.go @@ -0,0 +1,175 @@ +package controller + +import ( + "context" + "database/sql" + _ "embed" + "errors" + "fmt" + "time" + + _ "github.com/lib/pq" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" +) + +//go:embed schema.sql +var schema string + +type Store struct{ DB *sql.DB } + +type rowQuerier interface { + QueryRowContext(context.Context, string, ...any) *sql.Row +} + +const activeExperimentSQL = `SELECT EXISTS(SELECT 1 FROM xtcp_control_experiments + WHERE location=$1 AND state->>'status' IN + ('EXPERIMENT_STATUS_PROPOSED','EXPERIMENT_STATUS_RUNNING','EXPERIMENT_STATUS_PAUSED','EXPERIMENT_STATUS_SUSPENDED'))` + +const discoveryDestinationSQL = `SELECT EXISTS(SELECT 1 FROM xtcp_control_experiments + WHERE location=$1 AND state->'treatment'->>'destination'=$2 + AND (state->>'status' NOT IN ('EXPERIMENT_STATUS_RETIRED','EXPERIMENT_STATUS_ROLLED_BACK') OR updated_at > $3))` + +var errExperimentBudget = errors.New("data-center experiment budget exhausted (one active experiment)") +var errDiscoverySuppressed = errors.New("destination already treated, held, proposed, or in cooldown") + +func hasActiveExperiment(ctx context.Context, q rowQuerier, location string) (bool, error) { + var exists bool + err := q.QueryRowContext(ctx, activeExperimentSQL, location).Scan(&exists) + return exists, err +} + +func discoveryDestinationBlocked(ctx context.Context, q rowQuerier, location, destination string, now time.Time) (bool, error) { + var exists bool + err := q.QueryRowContext(ctx, discoveryDestinationSQL, location, destination, now.Add(-24*time.Hour)).Scan(&exists) + return exists, err +} + +func Open(ctx context.Context, dsn string) (*Store, error) { + db, e := sql.Open("postgres", dsn) + if e != nil { + return nil, e + } + db.SetMaxOpenConns(32) + db.SetMaxIdleConns(8) + db.SetConnMaxLifetime(30 * time.Minute) + if e = db.PingContext(ctx); e != nil { + db.Close() + return nil, e + } + return &Store{DB: db}, nil +} +func (s *Store) Migrate(ctx context.Context) error { _, e := s.DB.ExecContext(ctx, schema); return e } +func jsonMessage(m proto.Message) (string, error) { b, e := protojson.Marshal(m); return string(b), e } + +// Control mutations are rare and use one transaction-level advisory lock. +// This fences controller replicas without holding leadership during analysis. +// Agent heartbeats do not take this lock or rewrite experiments/fleet state. +func (s *Store) mutate(ctx context.Context, fn func(*sql.Tx) error) error { + tx, e := s.DB.BeginTx(ctx, nil) + if e != nil { + return e + } + defer tx.Rollback() + if _, e = tx.ExecContext(ctx, "SELECT pg_advisory_xact_lock(867530902)"); e != nil { + return e + } + if e = fn(tx); e != nil { + return e + } + return tx.Commit() +} + +func experiment(ctx context.Context, tx *sql.Tx, id string) (*pb.Experiment, error) { + var raw []byte + if e := tx.QueryRowContext(ctx, "SELECT state FROM xtcp_control_experiments WHERE id=$1 FOR UPDATE", id).Scan(&raw); e != nil { + return nil, e + } + v := new(pb.Experiment) + if e := protojson.Unmarshal(raw, v); e != nil { + return nil, e + } + return v, nil +} +func saveExperiment(ctx context.Context, tx *sql.Tx, e *pb.Experiment) error { + b, err := jsonMessage(e) + if err != nil { + return err + } + _, err = tx.ExecContext(ctx, "INSERT INTO xtcp_control_experiments(id,location,state) VALUES($1,$2,$3) ON CONFLICT(id) DO UPDATE SET state=EXCLUDED.state,updated_at=now()", e.Id, e.Location, b) + return err +} +func desired(ctx context.Context, q interface { + QueryRowContext(context.Context, string, ...any) *sql.Row +}, id string) (*pb.DesiredState, error) { + var raw []byte + if e := q.QueryRowContext(ctx, "SELECT desired FROM xtcp_control_agents WHERE id=$1", id).Scan(&raw); e != nil { + return nil, e + } + d := new(pb.DesiredState) + if e := protojson.Unmarshal(raw, d); e != nil { + return nil, e + } + return d, nil +} +func saveDesired(ctx context.Context, tx *sql.Tx, d *pb.DesiredState) error { + b, e := jsonMessage(d) + if e != nil { + return e + } + res, e := tx.ExecContext(ctx, "UPDATE xtcp_control_agents SET desired=$2 WHERE id=$1", d.AgentId, b) + if e != nil { + return e + } + n, e := res.RowsAffected() + if e == nil && n != 1 { + return errors.New("agent not enrolled") + } + return e +} +func audit(ctx context.Context, tx *sql.Tx, actor, id, kind string, m proto.Message) error { + b, e := jsonMessage(m) + if e != nil { + return e + } + _, e = tx.ExecContext(ctx, "INSERT INTO xtcp_control_events(actor,request_id,kind,payload) VALUES($1,NULLIF($2,''),$3,$4)", actor, id, kind, b) + return e +} + +// Repeated request IDs must have the same operation and request body. The +// original response is returned rather than applying a second mutation. +func replay(ctx context.Context, tx *sql.Tx, id, kind string, request, response proto.Message) (bool, error) { + var k string + var raw []byte + e := tx.QueryRowContext(ctx, "SELECT kind,payload FROM xtcp_control_events WHERE request_id=$1", id).Scan(&k, &raw) + if errors.Is(e, sql.ErrNoRows) { + return false, nil + } + if e != nil { + return false, e + } + if k != kind { + return false, errors.New("request_id reused for another operation") + } + // Request audit is paired with a response event under request_id + '.result'. + want, e := jsonMessage(request) + if e != nil { + return false, e + } + copyReq := request.ProtoReflect().New().Interface() + if e = protojson.Unmarshal(raw, copyReq); e != nil { + return false, e + } + got, e := jsonMessage(copyReq) + if e != nil { + return false, e + } + if got != want { + return false, errors.New("request_id reused with different body") + } + if e = tx.QueryRowContext(ctx, "SELECT payload FROM xtcp_control_events WHERE request_id=$1", id+".result").Scan(&raw); e != nil { + return false, fmt.Errorf("missing idempotent response: %w", e) + } + return true, protojson.Unmarshal(raw, response) +} diff --git a/pkg/treatment/experiment.go b/pkg/treatment/experiment.go new file mode 100644 index 000000000..eb27aa120 --- /dev/null +++ b/pkg/treatment/experiment.go @@ -0,0 +1,270 @@ +package treatment + +import ( + "errors" + "fmt" + "math" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" +) + +type Evaluation struct { + Outcome string + Improvement, LowerBound float64 +} + +// Evaluate compares per-host log changes against contemporaneous controls. +// The host is the randomization unit; polls and sockets are not replicates. +// A conservative Welch interval budgets minute-by-minute looks across all +// planned phases. This interval is a review aid, not an automatic promotion. +func Evaluate(e *pb.Experiment, ev *pb.Evidence, now time.Time) (Evaluation, error) { + if err := ValidateEvidence(ev); err != nil { + return Evaluation{}, err + } + if len(e.Phases) == 0 { + return Evaluation{}, errors.New("experiment has no phase") + } + p := e.Policy + phase := e.Phases[len(e.Phases)-1] + if phase.ObservationStartedAt == nil { + return Evaluation{Outcome: "awaiting_exposure"}, nil + } + if now.Sub(phase.IssuedAt.AsTime()) >= p.MaximumPhase.AsDuration() { + return Evaluation{Outcome: "expired"}, nil + } + if ev.AnalyzedAt.AsTime().After(now.Add(30*time.Second)) || now.Sub(ev.WindowEnd.AsTime()) > p.MaximumEvidenceAge.AsDuration() { + return Evaluation{Outcome: "stale"}, nil + } + if ev.WindowStart.AsTime().Before(phase.ObservationStartedAt.AsTime()) || ev.WindowEnd.AsTime().Sub(ev.WindowStart.AsTime()) < p.MinimumObservation.AsDuration() { + return Evaluation{Outcome: "insufficient_duration"}, nil + } + base := make(map[string]*pb.HostEvidence, len(e.Baseline.Hosts)) + current := make(map[string]*pb.HostEvidence, len(ev.Hosts)) + for _, h := range e.Baseline.Hosts { + base[h.AgentId] = h + } + for _, h := range ev.Hosts { + current[h.AgentId] = h + } + target := int(phase.TargetHosts) + if target < 1 || target > len(e.CohortOrder) { + return Evaluation{}, errors.New("invalid phase cohort size") + } + treated, control := make([]float64, 0, target), make([]float64, 0, len(e.CohortOrder)-target) + tr, cr := make([]float64, 0, target), make([]float64, 0, len(e.CohortOrder)-target) + tlat, clat := make([]float64, 0, target), make([]float64, 0, len(e.CohortOrder)-target) + for i, id := range e.CohortOrder { + h, b := current[id], base[id] + if h == nil || b == nil || !h.CollectionHealthy || !b.CollectionHealthy || h.Sockets < p.MinimumSocketsPerHost || h.BytesAcked < p.MinimumBytesPerHost || h.GoodputBps <= 0 || b.GoodputBps <= 0 || b.RttP95Ms <= 0 { + return Evaluation{Outcome: "insufficient_data"}, nil + } + if i < target && h.ExposedSockets < p.MinimumSocketsPerHost { + return Evaluation{Outcome: "awaiting_exposure"}, nil + } + x := math.Log(h.GoodputBps / b.GoodputBps) + if i < target { + treated = append(treated, x) + tr = append(tr, h.RetransmissionRatio-b.RetransmissionRatio) + tlat = append(tlat, math.Log1p((max(h.RttP95Ms, .001)-b.RttP95Ms)/b.RttP95Ms)) + } else { + control = append(control, x) + cr = append(cr, h.RetransmissionRatio-b.RetransmissionRatio) + clat = append(clat, math.Log1p((max(h.RttP95Ms, .001)-b.RttP95Ms)/b.RttP95Ms)) + } + } + // Guardrails protect controls as well as treated traffic. A shared-link + // regression is harmful even if treated hosts outperform their peers. + if mean(tr) > p.MaximumRetransmissionIncrease || mean(cr) > p.MaximumRetransmissionIncrease || mean(tlat) > math.Log1p(p.MaximumRttIncrease) || mean(clat) > math.Log1p(p.MaximumRttIncrease) { + return Evaluation{Outcome: "harmful"}, nil + } + effect := mean(treated) - mean(control) + if len(control) == 0 { + // Final 100% phase is a safety check; causal benefit was established + // before the last control cohort received the treatment. + if effect < -math.Log1p(p.MinimumImprovement) { + return Evaluation{Outcome: "harmful"}, nil + } + return Evaluation{Outcome: "final_safety", Improvement: math.Expm1(effect)}, nil + } + if len(treated) < int(p.MinimumTreatedHosts) { + if effect < -math.Log1p(p.MinimumImprovement) { + return Evaluation{Outcome: "harmful"}, nil + } + return Evaluation{Outcome: "safety_only", Improvement: math.Expm1(effect)}, nil + } + if len(control) < 2 { + return Evaluation{Outcome: "insufficient_controls"}, nil + } + a, b := variance(treated)/float64(len(treated)), variance(control)/float64(len(control)) + se := math.Sqrt(a + b) + lower := effect + if se > 0 { + df := (a + b) * (a + b) / (a*a/float64(len(treated)-1) + b*b/float64(len(control)-1)) + looks := (math.Ceil(math.Log2(float64(len(e.CohortOrder)))) + 2) * math.Ceil(p.MaximumPhase.AsDuration().Minutes()) + lower -= studentQuantile(1-.05/looks, df) * se + } + out := Evaluation{Outcome: "inconclusive", Improvement: math.Expm1(effect), LowerBound: math.Expm1(lower)} + if lower > math.Log1p(p.MinimumImprovement) { + out.Outcome = "beneficial" + } + return out, nil +} + +// HealthyEvidence is the fast safety gate, independent of the minimum benefit +// observation window. It also prevents missing hosts from renewing leases. +func HealthyEvidence(e *pb.Experiment, v *pb.Evidence, now time.Time) (healthy, harmful bool) { + if ValidateEvidence(v) != nil || v.AnalyzedAt.AsTime().After(now.Add(30*time.Second)) || now.Sub(v.WindowEnd.AsTime()) > e.Policy.MaximumEvidenceAge.AsDuration() { + return false, false + } + base := make(map[string]*pb.HostEvidence, len(e.Baseline.Hosts)) + current := make(map[string]*pb.HostEvidence, len(v.Hosts)) + for _, h := range e.Baseline.Hosts { + base[h.AgentId] = h + } + for _, h := range v.Hosts { + current[h.AgentId] = h + } + healthy = true + for _, id := range e.CohortOrder { + h, b := current[id], base[id] + if h == nil || b == nil || !h.CollectionHealthy || h.Sockets < e.Policy.MinimumSocketsPerHost || h.BytesAcked < e.Policy.MinimumBytesPerHost { + healthy = false + continue + } + if h.RetransmissionRatio-b.RetransmissionRatio > e.Policy.MaximumRetransmissionIncrease || b.RttP95Ms > 0 && (h.RttP95Ms-b.RttP95Ms)/b.RttP95Ms > e.Policy.MaximumRttIncrease { + harmful = true + } + } + return healthy && !harmful, harmful +} + +func mean(x []float64) float64 { + if len(x) == 0 { + return 0 + } + s := 0. + for _, v := range x { + s += v + } + return s / float64(len(x)) +} +func variance(x []float64) float64 { + if len(x) < 2 { + return 0 + } + m, s := mean(x), 0. + for _, v := range x { + s += (v - m) * (v - m) + } + return s / float64(len(x)-1) +} + +// Continued fraction for the regularized incomplete beta, used for the +// Student-t CDF. Numerical Recipes' convergent fraction; bounded iterations. +func betaFraction(a, b, x float64) float64 { + const tiny = 1e-300 + c, d := 1., 1-(a+b)*x/(a+1) + if math.Abs(d) < tiny { + d = tiny + } + d = 1 / d + h := d + for m := 1; m <= 200; m++ { + mf := float64(m) + m2 := 2 * mf + for _, aa := range []float64{mf * (b - mf) * x / ((a - 1 + m2) * (a + m2)), -(a + mf) * (a + b + mf) * x / ((a + m2) * (a + 1 + m2))} { + d = 1 + aa*d + if math.Abs(d) < tiny { + d = tiny + } + c = 1 + aa/c + if math.Abs(c) < tiny { + c = tiny + } + d = 1 / d + delta := d * c + h *= delta + if aa < 0 && math.Abs(delta-1) < 1e-12 { + return h + } + } + } + return h +} +func betaRegularized(x, a, b float64) float64 { + if x <= 0 { + return 0 + } + if x >= 1 { + return 1 + } + lab, _ := math.Lgamma(a + b) + la, _ := math.Lgamma(a) + lb, _ := math.Lgamma(b) + bt := math.Exp(lab - la - lb + a*math.Log(x) + b*math.Log1p(-x)) + if x < (a+1)/(a+b+2) { + return bt * betaFraction(a, b, x) / a + } + return 1 - bt*betaFraction(b, a, 1-x)/b +} +func studentQuantile(p, df float64) float64 { + lo, hi := 0., 1e6 + for range 100 { + mid := (lo + hi) / 2 + cdf := 1 - .5*betaRegularized(df/(df+mid*mid), df/2, .5) + if cdf < p { + lo = mid + } else { + hi = mid + } + } + return (lo + hi) / 2 +} + +func NextCohort(current, total int) int { + if current == 0 { + return 1 + } + if current >= total-2 { + return total + } + return min(current*2, total-2) +} + +// Advance is pure and returns a new experiment. Callers persist it and all +// assignments in one PostgreSQL transaction after authenticating the reviewer. +func Advance(e *pb.Experiment, ev *pb.Evidence, now time.Time, reviewer, reason string) (*pb.Experiment, error) { + n := proto.Clone(e).(*pb.Experiment) + if n.Status != pb.ExperimentStatus_EXPERIMENT_STATUS_PROPOSED && n.Status != pb.ExperimentStatus_EXPERIMENT_STATUS_RUNNING { + return nil, errors.New("experiment cannot advance in its current state") + } + target := 0 + if len(n.Phases) > 0 { + phase := n.Phases[len(n.Phases)-1] + target = int(phase.TargetHosts) + v, err := Evaluate(n, ev, now) + if err != nil { + return nil, err + } + if v.Outcome != "beneficial" && v.Outcome != "safety_only" && v.Outcome != "final_safety" { + return nil, fmt.Errorf("promotion blocked: %s", v.Outcome) + } + phase.Evidence = proto.Clone(ev).(*pb.Evidence) + phase.Outcome = v.Outcome + phase.EvaluatedAt = timestamppb.New(now) + phase.Reviewer = reviewer + phase.Reason = reason + if target == len(n.CohortOrder) { + n.Status = pb.ExperimentStatus_EXPERIMENT_STATUS_COMPLETE + n.Revision++ + return n, nil + } + } + n.Phases = append(n.Phases, &pb.Phase{TargetHosts: uint32(NextCohort(target, len(n.CohortOrder))), IssuedAt: timestamppb.New(now), Reviewer: reviewer, Reason: reason}) + n.Status = pb.ExperimentStatus_EXPERIMENT_STATUS_RUNNING + n.Revision++ + return n, nil +} diff --git a/pkg/treatment/experiment_boundaries_test.go b/pkg/treatment/experiment_boundaries_test.go new file mode 100644 index 000000000..88ce079a8 --- /dev/null +++ b/pkg/treatment/experiment_boundaries_test.go @@ -0,0 +1,218 @@ +package treatment + +import ( + "testing" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func TestEvaluationBoundaries(t *testing.T) { + now := time.Unix(1700000000, 0) + for _, tc := range []struct { + name, description, want string + wantError bool + change func(*pb.Experiment, *pb.Evidence) + }{ + {"no-phase", "unstarted experiment cannot be evaluated", "", true, func(e *pb.Experiment, _ *pb.Evidence) { e.Phases = nil }}, + {"no-observation", "all canaries must acknowledge exposure", "awaiting_exposure", false, func(e *pb.Experiment, _ *pb.Evidence) { e.Phases[0].ObservationStartedAt = nil }}, + {"expiry-equality", "phase expires exactly at its deadline", "expired", false, func(e *pb.Experiment, _ *pb.Evidence) { + e.Phases[0].IssuedAt = timestamppb.New(now.Add(-24 * time.Hour)) + }}, + {"freshness-equality", "evidence at maximum age is usable", "beneficial", false, func(_ *pb.Experiment, v *pb.Evidence) { v.WindowEnd = timestamppb.New(now.Add(-2 * time.Minute)) }}, + {"freshness-over", "one nanosecond beyond maximum age is stale", "stale", false, func(_ *pb.Experiment, v *pb.Evidence) { + v.WindowEnd = timestamppb.New(now.Add(-2*time.Minute - time.Nanosecond)) + }}, + {"clock-equality", "30 seconds of forward clock skew is allowed", "beneficial", false, func(_ *pb.Experiment, v *pb.Evidence) { v.AnalyzedAt = timestamppb.New(now.Add(30 * time.Second)) }}, + {"clock-over", "excess forward skew is rejected", "stale", false, func(_ *pb.Experiment, v *pb.Evidence) { + v.AnalyzedAt = timestamppb.New(now.Add(30*time.Second + time.Nanosecond)) + }}, + {"duration-equality", "exactly minimum observation is sufficient", "beneficial", false, func(_ *pb.Experiment, v *pb.Evidence) { + v.WindowStart = timestamppb.New(v.WindowEnd.AsTime().Add(-30 * time.Minute)) + }}, + {"duration-under", "one nanosecond short is insufficient", "insufficient_duration", false, func(_ *pb.Experiment, v *pb.Evidence) { + v.WindowStart = timestamppb.New(v.WindowEnd.AsTime().Add(-30*time.Minute + time.Nanosecond)) + }}, + {"old-window", "pre-treatment data cannot support promotion", "insufficient_duration", false, func(e *pb.Experiment, v *pb.Evidence) { + v.WindowStart = timestamppb.New(e.Phases[0].ObservationStartedAt.AsTime().Add(-time.Nanosecond)) + }}, + {"zero-target", "phase must have at least one canary", "", true, func(e *pb.Experiment, _ *pb.Evidence) { e.Phases[0].TargetHosts = 0 }}, + {"over-target", "phase cannot exceed fleet size", "", true, func(e *pb.Experiment, _ *pb.Evidence) { e.Phases[0].TargetHosts = 17 }}, + {"sample-equality", "exact sample and exposure thresholds suffice", "beneficial", false, func(e *pb.Experiment, v *pb.Evidence) { + for _, h := range v.Hosts { + h.Sockets = e.Policy.MinimumSocketsPerHost + h.ExposedSockets = h.Sockets + h.BytesAcked = e.Policy.MinimumBytesPerHost + } + }}, + {"missing-host", "missing cohort host blocks evaluation", "insufficient_data", false, func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts = v.Hosts[1:] }}, + {"missing-baseline", "missing baseline host blocks evaluation", "insufficient_data", false, func(e *pb.Experiment, _ *pb.Evidence) { e.Baseline.Hosts = e.Baseline.Hosts[1:] }}, + {"low-bytes", "too few bytes blocks evaluation", "insufficient_data", false, func(e *pb.Experiment, v *pb.Evidence) { v.Hosts[0].BytesAcked = e.Policy.MinimumBytesPerHost - 1 }}, + {"zero-goodput", "zero goodput cannot enter log comparison", "insufficient_data", false, func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts[0].GoodputBps = 0 }}, + {"zero-baseline-rtt", "zero baseline RTT cannot enter ratio", "insufficient_data", false, func(e *pb.Experiment, _ *pb.Evidence) { e.Baseline.Hosts[0].RttP95Ms = 0 }}, + {"first-canary", "small cohorts permit safety review only", "safety_only", false, func(e *pb.Experiment, _ *pb.Evidence) { e.Phases[0].TargetHosts = 1 }}, + {"small-cohort-regression", "large goodput loss blocks small cohort promotion", "harmful", false, func(e *pb.Experiment, v *pb.Evidence) { e.Phases[0].TargetHosts = 1; v.Hosts[0].GoodputBps = 1 }}, + {"one-control", "one control cannot estimate variance", "insufficient_controls", false, func(e *pb.Experiment, _ *pb.Evidence) { e.Phases[0].TargetHosts = 15 }}, + {"full-fleet", "last phase checks safety without controls", "final_safety", false, func(e *pb.Experiment, _ *pb.Evidence) { e.Phases[0].TargetHosts = 16 }}, + {"full-fleet-harm", "last phase still rejects goodput regression", "harmful", false, func(e *pb.Experiment, v *pb.Evidence) { + e.Phases[0].TargetHosts = 16 + for _, h := range v.Hosts { + h.GoodputBps = 1 + } + }}, + {"invalid-evidence", "malformed evidence fails before decision", "", true, func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts[0] = nil }}, + } { + t.Run(tc.name, func(t *testing.T) { + e, v := experimentFixture(now) + tc.change(e, v) + before := proto.Clone(e) + evidenceBefore := proto.Clone(v) + got, err := Evaluate(e, v, now) + if (err != nil) != tc.wantError || got.Outcome != tc.want { + t.Fatalf("%s: got %+v err=%v; want %s error=%t", tc.description, got, err, tc.want, tc.wantError) + } + if !proto.Equal(e, before) || !proto.Equal(v, evidenceBefore) { + t.Fatal("evaluation mutated its input") + } + }) + } +} + +func TestSafetyGateBoundaries(t *testing.T) { + now := time.Unix(1700000000, 0) + for _, tc := range []struct { + name, description string + change func(*pb.Experiment, *pb.Evidence) + healthy, harmful bool + }{ + {"healthy", "complete unchanged observations renew leases", func(*pb.Experiment, *pb.Evidence) {}, true, false}, + {"missing-host", "partial evidence cannot renew leases", func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts = v.Hosts[1:] }, false, false}, + {"unhealthy-collector", "collection failures cannot renew leases", func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts[0].CollectionHealthy = false }, false, false}, + {"low-samples", "too few samples cannot renew leases", func(e *pb.Experiment, v *pb.Evidence) { + v.Hosts[0].Sockets = e.Policy.MinimumSocketsPerHost - 1 + v.Hosts[0].ExposedSockets = 0 + }, false, false}, + {"stale", "old observations cannot renew leases", func(_ *pb.Experiment, v *pb.Evidence) { v.WindowEnd = timestamppb.New(now.Add(-3 * time.Minute)) }, false, false}, + {"loss-equality", "loss guard is strictly greater than its limit", func(e *pb.Experiment, v *pb.Evidence) { + v.Hosts[0].RetransmissionRatio = e.Policy.MaximumRetransmissionIncrease + }, true, false}, + {"loss-over", "excess loss is harmful", func(e *pb.Experiment, v *pb.Evidence) { + v.Hosts[0].RetransmissionRatio = e.Policy.MaximumRetransmissionIncrease + 1e-9 + }, false, true}, + {"rtt-equality", "exactly representable RTT limit is inclusive", func(e *pb.Experiment, v *pb.Evidence) { e.Policy.MaximumRttIncrease = .5; v.Hosts[0].RttP95Ms = 1.5 }, true, false}, + {"control-harm", "controls are protected too", func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts[15].RttP95Ms = 10 }, false, true}, + {"partial-harm", "missing data does not conceal observed harm", func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts = v.Hosts[1:]; v.Hosts[0].RttP95Ms = 10 }, false, true}, + } { + t.Run(tc.name, func(t *testing.T) { + e, v := experimentFixture(now) + tc.change(e, v) + h, bad := HealthyEvidence(e, v, now) + if h != tc.healthy || bad != tc.harmful { + t.Fatalf("%s: healthy=%t harmful=%t; want %t/%t", tc.description, h, bad, tc.healthy, tc.harmful) + } + }) + } +} + +func TestAdvanceTransitions(t *testing.T) { + now := time.Unix(1700000000, 0) + for _, tc := range []struct { + name, description string + change func(*pb.Experiment, *pb.Evidence) + want pb.ExperimentStatus + phases int + wantError bool + }{ + {"initial", "approved proposal starts one canary", func(e *pb.Experiment, _ *pb.Evidence) { + e.Phases = nil + e.Status = pb.ExperimentStatus_EXPERIMENT_STATUS_PROPOSED + }, pb.ExperimentStatus_EXPERIMENT_STATUS_RUNNING, 1, false}, + {"promotion", "benefit permits the next reviewed phase", func(*pb.Experiment, *pb.Evidence) {}, pb.ExperimentStatus_EXPERIMENT_STATUS_RUNNING, 2, false}, + {"completion", "full fleet safety review completes the experiment", func(e *pb.Experiment, _ *pb.Evidence) { e.Phases[0].TargetHosts = 16 }, pb.ExperimentStatus_EXPERIMENT_STATUS_COMPLETE, 1, false}, + {"rolled-back", "rolled back experiments cannot advance", func(e *pb.Experiment, _ *pb.Evidence) { e.Status = pb.ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK }, 0, 0, true}, + {"harm", "harm cannot be promoted", func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts[0].RttP95Ms = 100 }, 0, 0, true}, + } { + t.Run(tc.name, func(t *testing.T) { + e, v := experimentFixture(now) + tc.change(e, v) + before := proto.Clone(e) + got, err := Advance(e, v, now, "reviewer", "measured benefit") + if (err != nil) != tc.wantError { + t.Fatalf("%s: err=%v wantError=%t", tc.description, err, tc.wantError) + } + if err == nil { + if got.Status != tc.want || len(got.Phases) != tc.phases || got.Revision != e.Revision+1 { + t.Fatalf("%s: wrong transition: %v", tc.description, got) + } + last := got.Phases[len(got.Phases)-1] + if last.Reviewer != "reviewer" || last.Reason != "measured benefit" { + t.Fatal("review provenance missing") + } + } + if !proto.Equal(e, before) { + t.Fatal("advance mutated input") + } + }) + } +} + +func TestNextCohortBoundaries(t *testing.T) { + for _, tc := range []struct { + description string + current, total, want int + }{ + {"start smallest fleet", 0, 3, 1}, {"reserve two controls", 1, 3, 3}, {"start large fleet", 0, 10000, 1}, {"double first canary", 1, 10000, 2}, {"double second phase", 2, 10000, 4}, {"cap before consuming controls", 4096, 10000, 8192}, {"retain final two controls", 8192, 10000, 9998}, {"final reviewed expansion", 9998, 10000, 10000}, {"completed fleet is unchanged", 10000, 10000, 10000}, + } { + t.Run(tc.description, func(t *testing.T) { + if got := NextCohort(tc.current, tc.total); got != tc.want { + t.Fatalf("got %d want %d", got, tc.want) + } + }) + } +} + +func TestDefaultRTTGuardExactBoundary(t *testing.T) { + for _, tc := range []struct { + description string + rtt float64 + want string + healthy bool + }{ + {"exactly ten percent increase is within the configured guard", 5.5, "beneficial", true}, + {"measurable increase above ten percent is harmful", 5.500001, "harmful", false}, + } { + t.Run(tc.description, func(t *testing.T) { + now := time.Unix(1700000000, 0) + e, v := experimentFixture(now) + for _, h := range e.Baseline.Hosts { + h.RttP95Ms = 5 + } + for _, h := range v.Hosts { + h.RttP95Ms = tc.rtt + } + healthy, harm := HealthyEvidence(e, v, now) + if healthy != tc.healthy || harm == tc.healthy { + t.Errorf("safety gate healthy=%t harmful=%t; want healthy=%t", healthy, harm, tc.healthy) + } + got, err := Evaluate(e, v, now) + if err != nil || got.Outcome != tc.want { + t.Fatalf("evaluation %v %v; want %s", got, err, tc.want) + } + }) + } +} + +func TestRTTGuardFleetBoundary(t *testing.T) { + for _, n := range []int{100, 1000, 10000} { + e, v, now := fleetFixture(n) + for _, h := range v.Hosts { + h.RttP95Ms = 5.5 + } + got, err := Evaluate(e, v, now) + if err != nil || got.Outcome != "beneficial" { + t.Errorf("%d hosts at exactly the RTT guard: %v %v", n, got, err) + } + } +} diff --git a/pkg/treatment/experiment_test.go b/pkg/treatment/experiment_test.go new file mode 100644 index 000000000..bc96a0706 --- /dev/null +++ b/pkg/treatment/experiment_test.go @@ -0,0 +1,135 @@ +package treatment + +import ( + "fmt" + "math" + "strings" + "testing" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func experimentFixture(now time.Time) (*pb.Experiment, *pb.Evidence) { + b := &pb.Evidence{Id: "baseline", AnalyzedAt: timestamppb.New(now.Add(-time.Hour)), WindowStart: timestamppb.New(now.Add(-2 * time.Hour)), WindowEnd: timestamppb.New(now.Add(-time.Hour)), QueryVersion: "v1", ArtifactSha256: strings.Repeat("a", 64)} + e := &pb.Experiment{Id: "exp", Location: "dc1", Hypothesis: "cubic improves throughput", Treatment: fixture(now).Assignments[0].Treatment, Policy: DefaultPolicy(), Baseline: b, Status: pb.ExperimentStatus_EXPERIMENT_STATUS_RUNNING, Revision: 2, Phases: []*pb.Phase{{TargetHosts: 4, IssuedAt: timestamppb.New(now.Add(-40 * time.Minute)), ObservationStartedAt: timestamppb.New(now.Add(-35 * time.Minute))}}} + for i := range 16 { + id := fmt.Sprintf("host-%d", i) + e.CohortOrder = append(e.CohortOrder, id) + b.Hosts = append(b.Hosts, &pb.HostEvidence{AgentId: id, Sockets: 100, ExposedSockets: 100, BytesAcked: 1 << 25, GoodputBps: 1e8, RttP95Ms: 1, CollectionHealthy: true}) + } + ev := proto.Clone(b).(*pb.Evidence) + ev.Id = "after" + ev.WindowStart = timestamppb.New(now.Add(-34 * time.Minute)) + ev.WindowEnd = timestamppb.New(now.Add(-time.Second)) + ev.AnalyzedAt = timestamppb.New(now) + for _, h := range ev.Hosts[:4] { + h.GoodputBps *= 1.3 + } + return e, ev +} +func TestEvaluationBenefitHarmAndEvidenceGates(t *testing.T) { + now := time.Now() + e, v := experimentFixture(now) + for _, tc := range []struct { + name, want string + change func(*pb.Experiment, *pb.Evidence) + }{ + {"benefit", "beneficial", func(*pb.Experiment, *pb.Evidence) {}}, + {"loss", "harmful", func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts[0].RetransmissionRatio = .2 }}, + {"control harm", "harmful", func(_ *pb.Experiment, v *pb.Evidence) { + for _, h := range v.Hosts[4:] { + h.RttP95Ms = 3 + } + }}, + {"missing exposure", "awaiting_exposure", func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts[0].ExposedSockets = 0 }}, + {"stale", "stale", func(_ *pb.Experiment, v *pb.Evidence) { v.WindowEnd = timestamppb.New(now.Add(-3 * time.Minute)) }}, + {"missing data", "insufficient_data", func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts[0].CollectionHealthy = false }}, + {"too early", "insufficient_duration", func(_ *pb.Experiment, v *pb.Evidence) { v.WindowStart = timestamppb.New(now.Add(-5 * time.Minute)) }}, + {"no benefit", "inconclusive", func(_ *pb.Experiment, v *pb.Evidence) { + for _, h := range v.Hosts { + h.GoodputBps = 1e8 + } + }}, + } { + t.Run(tc.name, func(t *testing.T) { + n := proto.Clone(e).(*pb.Experiment) + w := proto.Clone(v).(*pb.Evidence) + tc.change(n, w) + got, err := Evaluate(n, w, now) + if err != nil || got.Outcome != tc.want { + t.Fatalf("got %+v %v want %s", got, err, tc.want) + } + }) + } +} + +func TestHealthyEvidenceSoakRTTRegression(t *testing.T) { + // Reproduce the low-netem failure: 5.305 -> 5.922ms crosses production's + // 10% guard. The fixture's explicit tolerance must still reject 180ms. + for _, tc := range []struct { + name string + limit, rtt, retrans float64 + wantHarm bool + }{ + {"production-rejects-observed-increase", .1, 5.922, .00005452860025083156, true}, + {"soak-allows-observed-increase", 1, 5.922, .00005452860025083156, false}, + {"soak-rejects-netem-regression", 1, 180, 0, true}, + {"soak-still-rejects-loss", 1, 5.305, .006, true}, + } { + t.Run(tc.name, func(t *testing.T) { + now := time.Now() + e, v := experimentFixture(now) + e.Policy.MaximumRttIncrease = tc.limit + for _, h := range e.Baseline.Hosts { + h.RttP95Ms = 5.305 + } + for _, h := range v.Hosts { + h.RttP95Ms = 5.305 + } + v.Hosts[0].RttP95Ms, v.Hosts[0].RetransmissionRatio = tc.rtt, tc.retrans + healthy, harmful := HealthyEvidence(e, v, now) + if harmful != tc.wantHarm || healthy == harmful { + t.Fatalf("healthy=%t harmful=%t", healthy, harmful) + } + }) + } +} +func TestAdvancePreservesInputAndRequiresReviewableEvidence(t *testing.T) { + now := time.Now() + e, v := experimentFixture(now) + next, err := Advance(e, v, now, "operator", "reviewed") + if err != nil { + t.Fatal(err) + } + if len(e.Phases) != 1 || len(next.Phases) != 2 || next.Phases[1].TargetHosts != 8 { + t.Fatal("incorrect phase transition") + } + if _, err = Advance(next, v, now, "operator", "reuse evidence"); err == nil { + t.Fatal("reused previous phase evidence") + } +} +func TestStudentQuantile(t *testing.T) { + for _, tc := range []struct{ p, df, want float64 }{{.975, 1, 12.706204736}, {.975, 10, 2.228138852}, {.95, 30, 1.697260887}} { + if got := studentQuantile(tc.p, tc.df); math.Abs(got-tc.want) > 1e-5 { + t.Fatalf("got %g want %g", got, tc.want) + } + } +} +func TestTenThousandAgentCohorts(t *testing.T) { + n := 0 + steps := 0 + for n < 10000 { + next := NextCohort(n, 10000) + if next <= n || next > 10000 { + t.Fatal("invalid growth") + } + n = next + steps++ + } + if steps > 16 { + t.Fatal("unexpected phases", steps) + } +} diff --git a/pkg/treatment/journal.go b/pkg/treatment/journal.go new file mode 100644 index 000000000..3f96db3d2 --- /dev/null +++ b/pkg/treatment/journal.go @@ -0,0 +1,137 @@ +package treatment + +import ( + "bytes" + "errors" + "fmt" + "io" + "os" + "path/filepath" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "golang.org/x/sys/unix" + "google.golang.org/protobuf/encoding/prototext" +) + +// Journal serializes the daemon, watchdog and local administrative commands. +// The single atomic snapshot is authoritative. Per-assignment files are human +// readable projections only, never an input to recovery. +type Journal struct{ Dir string } + +func (j Journal) WithLock(fn func(*pb.LocalState, func() error) error) error { + if err := os.MkdirAll(j.Dir, 0700); err != nil { + return err + } + fd, err := unix.Open(filepath.Join(j.Dir, ".lock"), unix.O_CREAT|unix.O_RDWR|unix.O_CLOEXEC|unix.O_NOFOLLOW, 0600) + if err != nil { + return err + } + defer unix.Close(fd) + if err = unix.Flock(fd, unix.LOCK_EX|unix.LOCK_NB); err != nil { + return fmt.Errorf("treatment journal busy: %w", err) + } + defer unix.Flock(fd, unix.LOCK_UN) + s := new(pb.LocalState) + f, err := os.OpenFile(filepath.Join(j.Dir, "state.textproto"), os.O_RDONLY|unix.O_NOFOLLOW, 0) + if err == nil { + b, rerr := io.ReadAll(io.LimitReader(f, MaxStateBytes+1)) + cerr := f.Close() + if rerr != nil { + return rerr + } + if cerr != nil { + return cerr + } + if len(b) > MaxStateBytes { + return errors.New("journal too large") + } + if err = prototext.Unmarshal(b, s); err != nil { + return fmt.Errorf("invalid treatment journal: %w", err) + } + if s.Desired != nil { + if err = ValidateDesired(s.Desired); err != nil { + return fmt.Errorf("invalid persisted desired state: %w", err) + } + } + if len(s.Records) > MaxAssignments*2 { + return errors.New("too many journal records") + } + seen := map[string]bool{} + for _, r := range s.Records { + if r.Assignment == nil || !ValidID(r.Assignment.Id) { + return errors.New("invalid journal record") + } + if seen[r.Assignment.Id] { + return errors.New("duplicate journal record") + } + seen[r.Assignment.Id] = true + if err = ValidateTreatment(r.Assignment.Treatment); err != nil { + return fmt.Errorf("invalid journal treatment: %w", err) + } + } + } else if !errors.Is(err, os.ErrNotExist) { + return err + } + dirty := false + save := func() error { + b, err := prototext.MarshalOptions{Multiline: true, Indent: " "}.Marshal(s) + if err != nil { + return err + } + if len(b) > MaxStateBytes { + return errors.New("journal exceeds size limit") + } + if err = atomicFile(j.Dir, "state.textproto", b); err != nil { + return err + } + dirty = true + return nil + } + result := fn(s, save) + if dirty { + for _, r := range s.Records { + b, err := prototext.MarshalOptions{Multiline: true, Indent: " "}.Marshal(r) + if err != nil { + return errors.Join(result, err) + } + name := "assignment-" + r.Assignment.Id + ".textproto" + old, _ := os.ReadFile(filepath.Join(j.Dir, name)) + if bytes.Equal(old, b) { + continue + } + if err = atomicFile(j.Dir, name, b); err != nil { + return errors.Join(result, err) + } + } + } + return result +} + +func atomicFile(dir, name string, b []byte) error { + f, err := os.CreateTemp(dir, ".pending-") + if err != nil { + return err + } + tmp := f.Name() + defer os.Remove(tmp) + if _, err = f.Write(b); err != nil { + f.Close() + return err + } + if err = f.Sync(); err != nil { + f.Close() + return err + } + if err = f.Close(); err != nil { + return err + } + if err = os.Rename(tmp, filepath.Join(dir, name)); err != nil { + return err + } + d, err := os.Open(dir) + if err != nil { + return err + } + defer d.Close() + return d.Sync() +} diff --git a/pkg/treatment/journal_test.go b/pkg/treatment/journal_test.go new file mode 100644 index 000000000..3be1987bf --- /dev/null +++ b/pkg/treatment/journal_test.go @@ -0,0 +1,125 @@ +package treatment + +import ( + "errors" + "os" + "path/filepath" + "strings" + "testing" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "golang.org/x/sys/unix" + "google.golang.org/protobuf/encoding/prototext" +) + +func TestJournalRejectsInvalidSnapshots(t *testing.T) { + for _, tc := range []struct { + name, description string + change func(*pb.LocalState) + want string + }{ + {"valid", "valid snapshot can be reopened", func(*pb.LocalState) {}, ""}, + {"bad-desired", "invalid desired state is not recovered", func(s *pb.LocalState) { s.Desired.Revision = 0 }, "invalid persisted"}, + {"missing-assignment", "records require an assignment", func(s *pb.LocalState) { s.Records[0].Assignment = nil }, "invalid journal record"}, + {"bad-id", "projection names cannot contain paths", func(s *pb.LocalState) { s.Records[0].Assignment.Id = "../escape"; s.Desired = nil }, "invalid journal record"}, + {"duplicate", "duplicate records cannot shadow each other", func(s *pb.LocalState) { s.Records = append(s.Records, s.Records[0]) }, "duplicate"}, + {"bad-treatment", "invalid treatment cannot be recovered", func(s *pb.LocalState) { s.Records[0].Assignment.Treatment = nil; s.Desired = nil }, "invalid journal treatment"}, + } { + t.Run(tc.name, func(t *testing.T) { + j := Journal{Dir: t.TempDir()} + d := fixture(time.Unix(1700000000, 0)) + s := &pb.LocalState{Desired: d, Records: []*pb.ApplicationRecord{{Assignment: d.Assignments[0]}}} + tc.change(s) + raw, err := prototext.Marshal(s) + if err != nil { + t.Fatal(err) + } + if err = os.WriteFile(filepath.Join(j.Dir, "state.textproto"), raw, 0600); err != nil { + t.Fatal(err) + } + called := false + err = j.WithLock(func(*pb.LocalState, func() error) error { called = true; return nil }) + if tc.want == "" { + if err != nil || !called { + t.Fatalf("%s: %v", tc.description, err) + } + } else if err == nil || !strings.Contains(err.Error(), tc.want) || called { + t.Fatalf("%s: got %v, callback=%t; want %q without callback", tc.description, err, called, tc.want) + } + }) + } +} + +func TestJournalConcurrentExclusion(t *testing.T) { + j := Journal{Dir: t.TempDir()} + entered, release, done := make(chan struct{}), make(chan struct{}), make(chan error, 1) + go func() { + done <- j.WithLock(func(s *pb.LocalState, save func() error) error { + close(entered) + <-release + s.SuspensionReason = "first writer" + return save() + }) + }() + <-entered + called := false + err := j.WithLock(func(*pb.LocalState, func() error) error { called = true; return nil }) + close(release) + if first := <-done; first != nil { + t.Fatal(first) + } + if !errors.Is(err, unix.EWOULDBLOCK) || called { + t.Fatalf("competing writer should fail busy before callback: %v callback=%t", err, called) + } + if err = j.WithLock(func(s *pb.LocalState, _ func() error) error { + if s.SuspensionReason != "first writer" { + t.Fatal("lost first write") + } + return nil + }); err != nil { + t.Fatal("lock not released", err) + } +} + +func TestJournalSymlinkProtection(t *testing.T) { + for _, name := range []string{".lock", "state.textproto"} { + t.Run(name, func(t *testing.T) { + dir := t.TempDir() + target := filepath.Join(t.TempDir(), "unrelated") + if err := os.WriteFile(target, []byte("sentinel"), 0600); err != nil { + t.Fatal(err) + } + if err := os.Symlink(target, filepath.Join(dir, name)); err != nil { + t.Fatal(err) + } + called := false + err := (Journal{Dir: dir}).WithLock(func(*pb.LocalState, func() error) error { called = true; return nil }) + if err == nil || called { + t.Fatal("followed journal symlink") + } + raw, err := os.ReadFile(target) + if err != nil || string(raw) != "sentinel" { + t.Fatal("modified symlink target") + } + }) + } +} + +func TestJournalSizeLimit(t *testing.T) { + dir := t.TempDir() + f, err := os.Create(filepath.Join(dir, "state.textproto")) + if err != nil { + t.Fatal(err) + } + if err = f.Truncate(MaxStateBytes + 1); err != nil { + t.Fatal(err) + } + if err = f.Close(); err != nil { + t.Fatal(err) + } + err = (Journal{Dir: dir}).WithLock(func(*pb.LocalState, func() error) error { t.Fatal("oversized snapshot reached callback"); return nil }) + if err == nil || !strings.Contains(err.Error(), "too large") { + t.Fatalf("want size limit, got %v", err) + } +} diff --git a/pkg/treatment/reconcile.go b/pkg/treatment/reconcile.go new file mode 100644 index 000000000..e456f5c3f --- /dev/null +++ b/pkg/treatment/reconcile.go @@ -0,0 +1,282 @@ +package treatment + +import ( + "bytes" + "context" + "errors" + "fmt" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" +) + +// Backend only accepts locally planned routes. Install must be create-exclusive +// or an idempotent match; Remove must refuse changed/foreign routes. Enable and +// Disable only touch the deployment's exact owned lookup rules. +type Backend interface { + Plan(context.Context, *pb.Treatment) ([]byte, error) + Install(context.Context, []byte) error + Verify(context.Context, []byte) error + Remove(context.Context, []byte) error + Enable(context.Context) error + Disable(context.Context) error +} + +type Reconciler struct { + Journal Journal + Backend Backend + AgentID string + BootID string + Now func() time.Time +} + +func (r *Reconciler) now() time.Time { + if r.Now != nil { + return r.Now() + } + return time.Now() +} + +// Run saves intent before touching the kernel and verifies before reporting +// success. expiryOnly is used by the independent watchdog and cannot install a +// new treatment or clear a suspension. +func (r *Reconciler) Run(ctx context.Context, in *pb.DesiredState, expiryOnly bool) (*pb.LocalState, error) { + var result *pb.LocalState + err := r.Journal.WithLock(func(s *pb.LocalState, save func() error) error { + defer func() { result = proto.Clone(s).(*pb.LocalState) }() + if b, ok := r.Backend.(interface{ BeginReconcile() }); ok { + b.BeginReconcile() + } + now := r.now() + stamp := func() *timestamppb.Timestamp { return timestamppb.New(now) } + if in != nil { + if expiryOnly { + return errors.New("watchdog cannot accept desired state") + } + if err := ValidateDesired(in); err != nil { + return err + } + if in.AgentId != r.AgentID { + return errors.New("desired state is for another agent") + } + if s.Desired != nil { + if in.Revision < s.Desired.Revision { + return errors.New("stale desired revision") + } + if in.Revision == s.Desired.Revision && !proto.Equal(in, s.Desired) { + return errors.New("conflicting desired state at same revision") + } + } + for _, a := range in.Assignments { + for _, record := range s.Records { + if record.Assignment.Id == a.Id && a.Generation < record.Assignment.Generation { + return errors.New("stale assignment generation") + } + } + } + s.Desired = proto.Clone(in).(*pb.DesiredState) + s.UpdatedAt = stamp() + if err := save(); err != nil { + return err + } + } + if s.Desired == nil { + result = proto.Clone(s).(*pb.LocalState) + return nil + } + if s.Desired.AgentId != r.AgentID { + return errors.New("journal belongs to another agent") + } + byID := map[string]*pb.ApplicationRecord{} + for _, v := range s.Records { + byID[v.Assignment.Id] = v + } + active := map[string]bool{} + var errs []error + // Disable first: an interrupted cleanup must not leave suspended routes + // effective. Removal remains per-route and ownership checked. + held := s.LocallySuspended || s.Desired.Suspended + if held { + if err := r.Backend.Disable(ctx); err != nil { + errs = append(errs, err) + } + } + for _, a := range s.Desired.Assignments { + if err := ctx.Err(); err != nil { + return errors.Join(err, save()) + } + active[a.Id] = true + v := byID[a.Id] + if v == nil { + v = &pb.ApplicationRecord{Assignment: proto.Clone(a).(*pb.Assignment), ReceivedAt: stamp(), Status: pb.ApplyStatus_APPLY_STATUS_PENDING} + s.Records = append(s.Records, v) + byID[a.Id] = v + } + if a.Generation < v.Assignment.Generation { + return errors.New("stale assignment generation") + } + if a.Generation > v.Assignment.Generation { + v.ExpiredLatched = false + v.ReceivedAt = stamp() + } + v.Assignment = proto.Clone(a).(*pb.Assignment) + v.DesiredRevision = s.Desired.Revision + if a.Mode == pb.AssignmentMode_ASSIGNMENT_MODE_TRIAL && !a.LeaseExpiresAt.AsTime().After(now) { + v.ExpiredLatched = true + } + remove := held || a.Mode == pb.AssignmentMode_ASSIGNMENT_MODE_SUSPENDED || a.Mode == pb.AssignmentMode_ASSIGNMENT_MODE_RETIRED || v.ExpiredLatched + if expiryOnly && !remove { + continue + } + var err error + if v.ExpiredLatched && !held && a.Mode == pb.AssignmentMode_ASSIGNMENT_MODE_TRIAL && a.Predecessor != nil { + err = r.install(ctx, v, a.Predecessor, save, now) + if err == nil { + v.Status = pb.ApplyStatus_APPLY_STATUS_EXPIRED + } + } else if remove { + v.AttemptedAt = stamp() + if len(v.InstalledRoute) > 0 { + if err := save(); err != nil { + return err + } + } + if len(v.InstalledRoute) > 0 && v.InstalledBootId == r.BootID { + err = r.Backend.Remove(ctx, v.InstalledRoute) + } + if err == nil { + v.InstalledRoute = nil + v.RemovedAt = stamp() + v.Status = pb.ApplyStatus_APPLY_STATUS_REMOVED + if v.ExpiredLatched { + v.Status = pb.ApplyStatus_APPLY_STATUS_EXPIRED + } + } + } else { + err = r.install(ctx, v, a.Treatment, save, now) + } + if err != nil { + v.Status = pb.ApplyStatus_APPLY_STATUS_FAILED + v.Error = err.Error() + errs = append(errs, fmt.Errorf("%s: %w", a.Id, err)) + } else { + v.Error = "" + } + } + // Entries absent from the committed snapshot are tombstones, never + // candidates for startup application. + for _, v := range s.Records { + if active[v.Assignment.Id] || len(v.InstalledRoute) == 0 { + continue + } + if v.InstalledBootId == r.BootID { + if err := r.Backend.Remove(ctx, v.InstalledRoute); err != nil { + v.Error = err.Error() + v.Status = pb.ApplyStatus_APPLY_STATUS_FAILED + errs = append(errs, err) + continue + } + } + v.InstalledRoute = nil + v.Status = pb.ApplyStatus_APPLY_STATUS_REMOVED + v.RemovedAt = stamp() + } + if len(errs) > 0 { + // A failed withdrawal must not leave an expired/unknown override + // selected. Isolate the whole owned table until a clean pass. + if err := r.Backend.Disable(ctx); err != nil { + errs = append(errs, err) + } + for _, v := range s.Records { + if v.Status == pb.ApplyStatus_APPLY_STATUS_VERIFIED { + v.Status = pb.ApplyStatus_APPLY_STATUS_FAILED + v.Error = "owned route lookup disabled after reconciliation failure" + } + } + } else if !held && !expiryOnly { + hasRoutes := false + for _, v := range s.Records { + hasRoutes = hasRoutes || len(v.InstalledRoute) > 0 + } + if hasRoutes { + if err := r.Backend.Enable(ctx); err != nil { + errs = append(errs, err, r.Backend.Disable(ctx)) + for _, v := range s.Records { + if v.Status == pb.ApplyStatus_APPLY_STATUS_VERIFIED { + v.Status = pb.ApplyStatus_APPLY_STATUS_FAILED + v.Error = err.Error() + } + } + } + } else if err := r.Backend.Disable(ctx); err != nil { + errs = append(errs, err) + } + } + s.UpdatedAt = stamp() + if err := save(); err != nil { + return err + } + result = proto.Clone(s).(*pb.LocalState) + return errors.Join(errs...) + }) + return result, err +} + +func (r *Reconciler) install(ctx context.Context, v *pb.ApplicationRecord, t *pb.Treatment, save func() error, now time.Time) error { + b, err := r.Backend.Plan(ctx, t) + if err != nil { + // A forwarding change invalidates the override; remove it immediately. + if len(v.InstalledRoute) > 0 && v.InstalledBootId == r.BootID { + if e := r.Backend.Remove(ctx, v.InstalledRoute); e != nil { + return errors.Join(err, e) + } + v.InstalledRoute = nil + } + return err + } + if len(v.InstalledRoute) > 0 && v.InstalledBootId == r.BootID && !bytes.Equal(b, v.InstalledRoute) { + if err = r.Backend.Remove(ctx, v.InstalledRoute); err != nil { + return err + } + } + changed := !bytes.Equal(b, v.InstalledRoute) || v.InstalledBootId != r.BootID + if !changed && (v.Status == pb.ApplyStatus_APPLY_STATUS_VERIFIED || v.Status == pb.ApplyStatus_APPLY_STATUS_EXPIRED && v.ExpiredLatched) { + if err = r.Backend.Verify(ctx, b); err == nil { + v.VerifiedAt = timestamppb.New(now) + return nil + } + } + v.AttemptedAt = timestamppb.New(now) + v.InstalledRoute = b + v.InstalledBootId = r.BootID + v.Status = pb.ApplyStatus_APPLY_STATUS_PENDING + if err = save(); err != nil { + return err + } + if err = r.Backend.Install(ctx, b); err != nil { + return err + } + if changed || v.AppliedAt == nil { + v.AppliedAt = timestamppb.New(now) + } + if err = r.Backend.Verify(ctx, b); err != nil { + return err + } + v.VerifiedAt = timestamppb.New(now) + v.Status = pb.ApplyStatus_APPLY_STATUS_VERIFIED + return nil +} + +func (r *Reconciler) SuspendLocal(suspended bool, reason string) error { + if len(reason) == 0 || len(reason) > 1024 { + return errors.New("suspension/resume reason required (maximum 1024 bytes)") + } + return r.Journal.WithLock(func(s *pb.LocalState, save func() error) error { + s.LocallySuspended = suspended + s.SuspensionReason = reason + s.UpdatedAt = timestamppb.New(r.now()) + return save() + }) +} diff --git a/pkg/treatment/reconcile_contracts_test.go b/pkg/treatment/reconcile_contracts_test.go new file mode 100644 index 000000000..54aa0c3d4 --- /dev/null +++ b/pkg/treatment/reconcile_contracts_test.go @@ -0,0 +1,148 @@ +package treatment + +import ( + "context" + "errors" + "strings" + "testing" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" +) + +type faultBackend struct { + *fakeBackend + fail string +} + +func (b *faultBackend) Plan(ctx context.Context, t *pb.Treatment) ([]byte, error) { + if b.fail == "plan" { + return nil, errors.New("plan failed") + } + return b.fakeBackend.Plan(ctx, t) +} +func (b *faultBackend) Install(ctx context.Context, raw []byte) error { + if b.fail == "install" { + return errors.New("install failed") + } + return b.fakeBackend.Install(ctx, raw) +} +func (b *faultBackend) Enable(ctx context.Context) error { + if b.fail == "enable" { + return errors.New("enable failed") + } + return b.fakeBackend.Enable(ctx) +} + +func TestReconcileFailureRecovery(t *testing.T) { + for _, tc := range []struct { + name, description string + wantRoutes int + }{ + {"plan", "forwarding changes withdraw an existing override", 0}, + {"install", "failed installation cannot enable lookup", 0}, + {"enable", "failed rule installation isolates installed routes", 1}, + } { + t.Run(tc.name, func(t *testing.T) { + now := time.Unix(1700000000, 0) + b := &faultBackend{fakeBackend: &fakeBackend{routes: map[string]bool{}}} + r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot", Now: func() time.Time { return now }} + d := fixture(now) + if tc.name == "plan" { + if _, err := r.Run(context.Background(), d, false); err != nil { + t.Fatal(err) + } + } + b.fail = tc.name + s, err := r.Run(context.Background(), d, false) + if err == nil || !strings.Contains(err.Error(), tc.name+" failed") || s.Records[0].Status != pb.ApplyStatus_APPLY_STATUS_FAILED || b.enabled || len(b.routes) != tc.wantRoutes { + t.Fatalf("%s: err=%v enabled=%t routes=%d state=%v", tc.description, err, b.enabled, len(b.routes), s) + } + b.fail = "" + s, err = r.Run(context.Background(), nil, false) + if err != nil || !b.enabled || len(b.routes) != 1 || s.Records[0].Status != pb.ApplyStatus_APPLY_STATUS_VERIFIED { + t.Fatalf("retry did not recover: %v %v", s, err) + } + }) + } +} + +func TestReconcileRevisionContracts(t *testing.T) { + for _, tc := range []struct { + name, description string + change func(*pb.DesiredState) + watchdog bool + wantError string + }{ + {"idempotent", "identical state verifies without reinstall", func(*pb.DesiredState) {}, false, ""}, + {"conflict", "same revision cannot carry different contents", func(d *pb.DesiredState) { d.Suspended = true }, false, "conflicting"}, + {"wrong-host", "another agent's state is rejected", func(d *pb.DesiredState) { d.AgentId = "other" }, false, "another agent"}, + {"watchdog", "watchdog cannot accept controller instructions", func(*pb.DesiredState) {}, true, "watchdog"}, + } { + t.Run(tc.name, func(t *testing.T) { + now := time.Unix(1700000000, 0) + b := &fakeBackend{routes: map[string]bool{}} + r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot", Now: func() time.Time { return now }} + d := fixture(now) + s, err := r.Run(context.Background(), d, false) + if err != nil { + t.Fatal(err) + } + original := proto.Clone(s.Desired) + tc.change(d) + _, err = r.Run(context.Background(), d, tc.watchdog) + if tc.wantError == "" { + if err != nil { + t.Fatal(err) + } + } else if err == nil || !strings.Contains(err.Error(), tc.wantError) { + t.Fatalf("%s: expected %q got %v", tc.description, tc.wantError, err) + } + if b.installs != 1 { + t.Fatal("rejected/idempotent state reinstalled route") + } + if err = r.Journal.WithLock(func(s *pb.LocalState, _ func() error) error { + if !proto.Equal(s.Desired, original) { + t.Fatal("rejected request changed journal") + } + return nil + }); err != nil { + t.Fatal(err) + } + }) + } +} + +func TestReconcileLeaseBoundary(t *testing.T) { + for _, tc := range []struct { + description string + offset time.Duration + want pb.ApplyStatus + routes int + }{ + {"one nanosecond before expiry is active", time.Nanosecond, pb.ApplyStatus_APPLY_STATUS_VERIFIED, 1}, + {"exact expiry is withdrawn", 0, pb.ApplyStatus_APPLY_STATUS_EXPIRED, 0}, + {"one nanosecond after expiry is withdrawn", -time.Nanosecond, pb.ApplyStatus_APPLY_STATUS_EXPIRED, 0}, + } { + t.Run(tc.description, func(t *testing.T) { + now := time.Unix(1700000000, 0) + b := &fakeBackend{routes: map[string]bool{}} + r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot", Now: func() time.Time { return now }} + d := fixture(now) + d.Assignments[0].LeaseExpiresAt = timestamppb.New(now.Add(tc.offset)) + s, err := r.Run(context.Background(), d, false) + if err != nil || s.Records[0].Status != tc.want || len(b.routes) != tc.routes { + t.Fatalf("got %v %v routes=%d; want %v routes=%d", s, err, len(b.routes), tc.want, tc.routes) + } + record := s.Records[0] + if !record.ReceivedAt.AsTime().Equal(now) { + t.Fatal("receipt timestamp missing") + } + if tc.routes == 1 && !record.AppliedAt.AsTime().Equal(now) { + t.Fatal("application timestamp missing") + } + }) + } +} diff --git a/pkg/treatment/reconcile_test.go b/pkg/treatment/reconcile_test.go new file mode 100644 index 000000000..a9b1e4d07 --- /dev/null +++ b/pkg/treatment/reconcile_test.go @@ -0,0 +1,266 @@ +package treatment + +import ( + "context" + "errors" + "os" + "path/filepath" + "testing" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" +) + +type fakeBackend struct { + routes map[string]bool + enabled bool + failVerify bool + failRemove bool + installs int + onInstall func() +} + +func (f *fakeBackend) Plan(_ context.Context, t *pb.Treatment) ([]byte, error) { + return []byte(t.Destination + ":" + t.Settings.GetCongestionControl()), nil +} +func (f *fakeBackend) Install(_ context.Context, b []byte) error { + f.installs++ + if f.onInstall != nil { + f.onInstall() + } + f.routes[string(b)] = true + return nil +} +func (f *fakeBackend) Verify(_ context.Context, b []byte) error { + if f.failVerify || !f.routes[string(b)] { + return errors.New("readback failed") + } + return nil +} +func (f *fakeBackend) Remove(_ context.Context, b []byte) error { + if f.failRemove { + return errors.New("withdrawal failed") + } + delete(f.routes, string(b)) + return nil +} + +func TestFailedWithdrawalDisablesOwnedLookup(t *testing.T) { + now := time.Now() + b := &fakeBackend{routes: map[string]bool{}} + r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot-1", Now: func() time.Time { return now }} + if _, e := r.Run(context.Background(), fixture(now), false); e != nil { + t.Fatal(e) + } + if !b.enabled { + t.Fatal("lookup never enabled") + } + now = now.Add(61 * time.Minute) + b.failRemove = true + if _, e := r.Run(context.Background(), nil, true); e == nil { + t.Fatal("withdrawal failure hidden") + } + if b.enabled { + t.Fatal("expired route still selected after failed withdrawal") + } + b.failRemove = false + if _, e := r.Run(context.Background(), nil, false); e != nil { + t.Fatal(e) + } + if len(b.routes) != 0 || b.enabled { + t.Fatal("failed withdrawal did not recover") + } +} + +func TestStableRouteSurvivesOfflineReboot(t *testing.T) { + now := time.Now() + b := &fakeBackend{routes: map[string]bool{}} + r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot-1", Now: func() time.Time { return now }} + d := fixture(now) + d.Assignments[0].Mode = pb.AssignmentMode_ASSIGNMENT_MODE_STABLE + d.Assignments[0].LeaseExpiresAt = nil + if _, e := r.Run(context.Background(), d, false); e != nil { + t.Fatal(e) + } + now = now.Add(48 * time.Hour) + r.BootID = "boot-2" + b.routes = map[string]bool{} + b.enabled = false + s, e := r.Run(context.Background(), nil, false) + if e != nil || len(b.routes) != 1 || !b.enabled || s.Records[0].InstalledBootId != "boot-2" { + t.Fatal("offline reboot did not restore stable treatment", e) + } +} +func (f *fakeBackend) Enable(context.Context) error { f.enabled = true; return nil } +func (f *fakeBackend) Disable(context.Context) error { f.enabled = false; return nil } + +func fixture(now time.Time) *pb.DesiredState { + return &pb.DesiredState{AgentId: "host-1", Revision: 1, IssuedAt: timestamppb.New(now), Assignments: []*pb.Assignment{{Id: "trial-1", ExperimentId: "trial-1", Generation: 1, Mode: pb.AssignmentMode_ASSIGNMENT_MODE_TRIAL, LeaseExpiresAt: timestamppb.New(now.Add(time.Hour)), Treatment: &pb.Treatment{Id: "cubic", Revision: 1, Destination: "192.0.2.1/32", Settings: &pb.TCPSettings{CongestionControl: proto.String("cubic")}}}}} +} + +func TestJournalIntentPrecedesKernelAndCrashRecovery(t *testing.T) { + now := time.Now() + dir := t.TempDir() + b := &fakeBackend{routes: map[string]bool{}, failVerify: true} + r := &Reconciler{Journal: Journal{Dir: dir}, Backend: b, AgentID: "host-1", BootID: "boot-1", Now: func() time.Time { return now }} + b.onInstall = func() { + raw, e := os.ReadFile(filepath.Join(dir, "state.textproto")) + if e != nil || len(raw) == 0 { + t.Fatal("intent not durable before install", e) + } + } + s, e := r.Run(context.Background(), fixture(now), false) + if e == nil || s.Records[0].Status != pb.ApplyStatus_APPLY_STATUS_FAILED { + t.Fatal("must report readback failure", s, e) + } + // The kernel changed before success was persisted. A fresh reconciler uses + // the journal to recover without needing a controller or another command. + b.failVerify = false + r2 := *r + s, e = r2.Run(context.Background(), nil, false) + if e != nil || s.Records[0].Status != pb.ApplyStatus_APPLY_STATUS_VERIFIED || len(b.routes) != 1 { + t.Fatal("restart did not converge", s, e) + } +} + +func TestTrialExpiryCannotBeResurrectedByRenewalOrRestart(t *testing.T) { + now := time.Now() + b := &fakeBackend{routes: map[string]bool{}} + r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot-1", Now: func() time.Time { return now }} + d := fixture(now) + if _, e := r.Run(context.Background(), d, false); e != nil { + t.Fatal(e) + } + now = now.Add(61 * time.Minute) + s, e := r.Run(context.Background(), nil, true) + if e != nil || len(b.routes) != 0 || s.Records[0].Status != pb.ApplyStatus_APPLY_STATUS_EXPIRED { + t.Fatal("watchdog expiry", s, e) + } + d.Revision++ + d.Assignments[0].LeaseExpiresAt = timestamppb.New(now.Add(time.Hour)) + if _, e = r.Run(context.Background(), d, false); e != nil { + t.Fatal(e) + } + if len(b.routes) != 0 { + t.Fatal("lease renewal resurrected expired generation") + } + d.Revision++ + d.Assignments[0].Generation++ + if _, e = r.Run(context.Background(), d, false); e != nil { + t.Fatal(e) + } + if len(b.routes) != 1 { + t.Fatal("explicit approved generation did not activate") + } +} + +func TestPersistentHoldAndTombstones(t *testing.T) { + now := time.Now() + b := &fakeBackend{routes: map[string]bool{}} + r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot-1"} + d := fixture(now) + d.Assignments[0].Mode = pb.AssignmentMode_ASSIGNMENT_MODE_STABLE + if _, e := r.Run(context.Background(), d, false); e != nil { + t.Fatal(e) + } + if e := r.SuspendLocal(true, "incident test"); e != nil { + t.Fatal(e) + } + if _, e := r.Run(context.Background(), nil, false); e != nil { + t.Fatal(e) + } + if b.enabled || len(b.routes) != 0 { + t.Fatal("hold did not restore baseline") + } + d.Revision++ + if _, e := r.Run(context.Background(), d, false); e != nil { + t.Fatal(e) + } + if len(b.routes) != 0 { + t.Fatal("controller update cleared local hold") + } + if e := r.SuspendLocal(false, "test complete"); e != nil { + t.Fatal(e) + } + d.Revision++ + d.Assignments = nil + if _, e := r.Run(context.Background(), d, false); e != nil { + t.Fatal(e) + } + if _, e := r.Run(context.Background(), nil, false); e != nil { + t.Fatal(e) + } + if len(b.routes) != 0 { + t.Fatal("orphan projection resurrected treatment") + } + d.Revision-- + if _, e := r.Run(context.Background(), d, false); e == nil { + t.Fatal("accepted stale desired state") + } +} + +func TestCorruptJournalDoesNotInstall(t *testing.T) { + dir := t.TempDir() + if e := os.WriteFile(filepath.Join(dir, "state.textproto"), []byte("invalid!!"), 0600); e != nil { + t.Fatal(e) + } + b := &fakeBackend{routes: map[string]bool{}} + r := &Reconciler{Journal: Journal{Dir: dir}, Backend: b, AgentID: "host-1"} + if _, e := r.Run(context.Background(), fixture(time.Now()), false); e == nil { + t.Fatal("corrupt journal overwritten") + } + if b.installs != 0 { + t.Fatal("kernel touched with corrupt state") + } +} + +func TestPredecessorExpiryAndGenerationRejectionPreserveJournal(t *testing.T) { + now := time.Now() + b := &fakeBackend{routes: map[string]bool{}} + r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot-1", Now: func() time.Time { return now }} + d := fixture(now) + a := d.Assignments[0] + a.Id = "state" + a.Generation = 2 + a.Predecessor = proto.Clone(a.Treatment).(*pb.Treatment) + a.Predecessor.Settings.CongestionControl = proto.String("bbr") + if _, e := r.Run(context.Background(), d, false); e != nil { + t.Fatal(e) + } + now = now.Add(61 * time.Minute) + s, e := r.Run(context.Background(), nil, true) + if e != nil || !b.routes["192.0.2.1/32:bbr"] || s.Records[0].Status != pb.ApplyStatus_APPLY_STATUS_EXPIRED { + t.Fatal("predecessor not restored", e) + } + installs := b.installs + if _, e = r.Run(context.Background(), nil, true); e != nil { + t.Fatal(e) + } + if b.installs != installs { + t.Fatal("watchdog repeatedly reinstalled predecessor") + } + bad := proto.Clone(d).(*pb.DesiredState) + bad.Revision++ + bad.Assignments[0].Generation-- + if _, e = r.Run(context.Background(), bad, false); e == nil { + t.Fatal("stale generation accepted") + } + if e = r.Journal.WithLock(func(s *pb.LocalState, _ func() error) error { + if s.Desired.Revision != d.Revision { + t.Fatal("rejected generation poisoned persisted desired state") + } + return nil + }); e != nil { + t.Fatal(e) + } + d.Revision++ + a.Mode = pb.AssignmentMode_ASSIGNMENT_MODE_RETIRED + if _, e = r.Run(context.Background(), d, false); e != nil { + t.Fatal(e) + } + if len(b.routes) != 0 { + t.Fatal("retired assignment restored predecessor") + } +} diff --git a/pkg/treatment/validate.go b/pkg/treatment/validate.go new file mode 100644 index 000000000..25d28d54b --- /dev/null +++ b/pkg/treatment/validate.go @@ -0,0 +1,187 @@ +// Package treatment implements the controller's route-treatment contracts. +// Validation is shared by RPC handlers, local journals, and direct callers. +package treatment + +import ( + "errors" + "fmt" + "math" + "net/netip" + "regexp" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "google.golang.org/protobuf/types/known/durationpb" +) + +const MaxAssignments = 4096 +const MaxAgents = 10000 +const MaxStateBytes = 16 << 20 +const TrialLease = time.Hour +const LeaseRenewInterval = 5 * time.Minute + +var identifier = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$`) + +func ValidID(s string) bool { return identifier.MatchString(s) } + +func ValidateTreatment(t *pb.Treatment) error { + if t == nil || !ValidID(t.Id) || t.Revision == 0 || t.Settings == nil { + return errors.New("treatment requires id, revision and settings") + } + p, err := netip.ParsePrefix(t.Destination) + if err != nil || p != p.Masked() || p.Bits() != p.Addr().BitLen() || p.Addr().Is4In6() || !p.Addr().IsGlobalUnicast() || p.Addr().IsLoopback() { + return errors.New("destination must be a canonical unicast /32 or /128 host prefix") + } + return ValidateTCPSettings(t.Settings, t.EcnFabricVerified) +} + +// ValidateTCPSettings also validates discovery templates before querying traffic. +func ValidateTCPSettings(s *pb.TCPSettings, ecnFabricVerified bool) error { + if s == nil { + return errors.New("TCP settings required") + } + if s.CongestionControl != nil && !identifier.MatchString(*s.CongestionControl) { + return errors.New("invalid congestion algorithm") + } + if len(s.GetCongestionControl()) > 15 { + return errors.New("congestion algorithm exceeds kernel name limit") + } + if s.CongestionLock && s.CongestionControl == nil { + return errors.New("congestion_lock requires an algorithm") + } + if s.GetCongestionControl() == "dctcp" && (!ecnFabricVerified || !s.GetEcn()) { + return errors.New("DCTCP requires independently verified ECN fabric and explicit ECN") + } + if s.RtoMinMs != nil && (*s.RtoMinMs < 1 || *s.RtoMinMs > 120000) { + return errors.New("rto_min_ms outside 1..120000") + } + for _, w := range []*uint32{s.InitialCongestionWindow, s.InitialReceiveWindow} { + if w != nil && (*w < 1 || *w > 1024) { + return errors.New("initial window outside 1..1024 segments") + } + } + if s.CongestionControl == nil && s.RtoMinMs == nil && s.InitialCongestionWindow == nil && s.InitialReceiveWindow == nil && s.QuickAck == nil && s.Ecn == nil { + return errors.New("empty treatment") + } + return nil +} + +func ValidateDesired(d *pb.DesiredState) error { + if d == nil || !ValidID(d.AgentId) || d.Revision == 0 || len(d.Assignments) > MaxAssignments || d.IssuedAt == nil || d.IssuedAt.CheckValid() != nil { + return errors.New("invalid desired-state header") + } + ids, destinations := map[string]bool{}, map[string]bool{} + for _, a := range d.Assignments { + if a == nil || !ValidID(a.Id) || !ValidID(a.ExperimentId) || a.Generation == 0 || ids[a.Id] { + return errors.New("invalid or duplicate assignment identity") + } + ids[a.Id] = true + if err := ValidateTreatment(a.Treatment); err != nil { + return err + } + if a.Mode < pb.AssignmentMode_ASSIGNMENT_MODE_TRIAL || a.Mode > pb.AssignmentMode_ASSIGNMENT_MODE_RETIRED { + return errors.New("invalid assignment mode") + } + if a.Mode != pb.AssignmentMode_ASSIGNMENT_MODE_RETIRED { + if destinations[a.Treatment.Destination] { + return errors.New("overlapping treatments") + } + destinations[a.Treatment.Destination] = true + } + if a.Mode == pb.AssignmentMode_ASSIGNMENT_MODE_TRIAL && (a.LeaseExpiresAt == nil || a.LeaseExpiresAt.CheckValid() != nil) { + return errors.New("trial requires a valid expiry") + } + if a.Predecessor != nil { + if err := ValidateTreatment(a.Predecessor); err != nil { + return err + } + if a.Predecessor.Destination != a.Treatment.Destination { + return errors.New("predecessor destination differs") + } + } + } + return nil +} + +func DefaultPolicy() *pb.ExperimentPolicy { + return &pb.ExperimentPolicy{MinimumObservation: durationpb.New(30 * time.Minute), MaximumPhase: durationpb.New(24 * time.Hour), MaximumEvidenceAge: durationpb.New(2 * time.Minute), MinimumSocketsPerHost: 30, MinimumBytesPerHost: 1 << 20, MinimumTreatedHosts: 4, MinimumImprovement: .05, MaximumRetransmissionIncrease: .005, MaximumRttIncrease: .1} +} + +func ValidatePolicy(p *pb.ExperimentPolicy) error { + if p == nil { + return errors.New("policy required") + } + for _, d := range []*durationpb.Duration{p.MinimumObservation, p.MaximumPhase, p.MaximumEvidenceAge} { + if d == nil || d.CheckValid() != nil || d.AsDuration() <= 0 || d.AsDuration() > 7*24*time.Hour { + return errors.New("invalid policy duration") + } + } + if p.MinimumObservation.AsDuration() > p.MaximumPhase.AsDuration() || p.MinimumSocketsPerHost == 0 || p.MinimumBytesPerHost == 0 || p.MinimumTreatedHosts < 2 || p.MinimumTreatedHosts > MaxAgents { + return errors.New("invalid policy sample bounds") + } + for _, v := range []float64{p.MinimumImprovement, p.MaximumRetransmissionIncrease, p.MaximumRttIncrease} { + if math.IsNaN(v) || math.IsInf(v, 0) || v < 0 || v > 1 { + return errors.New("invalid effect/guardrail bound") + } + } + return nil +} + +func ValidateEvidence(e *pb.Evidence) error { + if e == nil || !ValidID(e.Id) || e.AnalyzedAt == nil || e.WindowStart == nil || e.WindowEnd == nil || e.AnalyzedAt.CheckValid() != nil || e.WindowStart.CheckValid() != nil || e.WindowEnd.CheckValid() != nil || !e.WindowEnd.AsTime().After(e.WindowStart.AsTime()) || e.AnalyzedAt.AsTime().Before(e.WindowEnd.AsTime()) { + return errors.New("invalid evidence interval") + } + if e.QueryVersion == "" || len(e.QueryVersion) > 256 || len(e.EnrichmentVersion) > 256 || len(e.ArtifactSha256) != 64 { + return errors.New("evidence provenance required") + } + for _, c := range e.ArtifactSha256 { + if !(c >= '0' && c <= '9' || c >= 'a' && c <= 'f') { + return errors.New("invalid evidence sha256") + } + } + if len(e.Hosts) == 0 || len(e.Hosts) > MaxAgents { + return errors.New("invalid evidence population") + } + seen := make(map[string]bool, len(e.Hosts)) + for _, h := range e.Hosts { + if h == nil || !ValidID(h.AgentId) || seen[h.AgentId] || h.ExposedSockets > h.Sockets { + return errors.New("invalid/duplicate evidence host") + } + seen[h.AgentId] = true + for _, v := range []float64{h.GoodputBps, h.RetransmissionRatio, h.RttP95Ms} { + if math.IsNaN(v) || math.IsInf(v, 0) || v < 0 { + return errors.New("invalid host measurement") + } + } + } + return nil +} + +func ValidateProposal(e *pb.Experiment) error { + if e == nil || !ValidID(e.Id) || !ValidID(e.Location) || len(e.Hypothesis) == 0 || len(e.Hypothesis) > 8192 || len(e.CohortOrder) < 3 || len(e.CohortOrder) > MaxAgents { + return errors.New("invalid experiment identity or population (minimum three hosts)") + } + if err := ValidateTreatment(e.Treatment); err != nil { + return err + } + if err := ValidatePolicy(e.Policy); err != nil { + return err + } + if err := ValidateEvidence(e.Baseline); err != nil { + return err + } + seen := map[string]bool{} + for _, id := range e.CohortOrder { + if !ValidID(id) || seen[id] { + return errors.New("invalid cohort") + } + seen[id] = true + } + for _, h := range e.Baseline.Hosts { + delete(seen, h.AgentId) + } + if len(seen) > 0 { + return fmt.Errorf("baseline missing %d cohort hosts", len(seen)) + } + return nil +} diff --git a/pkg/treatment/validate_test.go b/pkg/treatment/validate_test.go new file mode 100644 index 000000000..a6a63cc4d --- /dev/null +++ b/pkg/treatment/validate_test.go @@ -0,0 +1,319 @@ +package treatment + +import ( + "fmt" + "math" + "strings" + "testing" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/timestamppb" +) + +// Each row states the contract, not just whether the implementation returned. +type validationCase[T any] struct { + name, description string + change func(*T) + wantError string // empty means acceptance; otherwise a required error substring +} + +func checkValidation[T any](t *testing.T, fresh func() T, validate func(T) error, cases []validationCase[T]) { + t.Helper() + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + input := fresh() + tc.change(&input) + err := validate(input) + if tc.wantError == "" { + if err != nil { + t.Fatalf("%s: expected acceptance, got %v", tc.description, err) + } + } else if err == nil || !strings.Contains(err.Error(), tc.wantError) { + t.Fatalf("%s: expected error containing %q, got %v", tc.description, tc.wantError, err) + } + }) + } +} + +func TestValidIDBoundaries(t *testing.T) { + for _, tc := range []struct { + name, description, input string + want bool + }{ + {"minimum", "one alphanumeric character is sufficient", "a", true}, + {"maximum", "128 characters are accepted", strings.Repeat("a", 128), true}, + {"too-long", "129 characters are rejected", strings.Repeat("a", 129), false}, + {"punctuation", "safe internal punctuation is accepted", "host-1.dc_2", true}, + {"empty", "empty identities are rejected", "", false}, + {"leading-dot", "identities must start with an alphanumeric", ".host", false}, + {"path", "path traversal is not an identity", "../host", false}, + {"newline", "a trailing newline is not ignored", "host\n", false}, + {"unicode", "identities are ASCII", "höst", false}, + } { + t.Run(tc.name, func(t *testing.T) { + if got := ValidID(tc.input); got != tc.want { + t.Fatalf("%s: got %t want %t", tc.description, got, tc.want) + } + }) + } +} + +func TestTreatmentValidation(t *testing.T) { + cases := []validationCase[*pb.Treatment]{ + {"valid", "the supported nearby profile is accepted", func(**pb.Treatment) {}, ""}, + {"nil", "missing treatments are rejected", func(p **pb.Treatment) { *p = nil }, "requires"}, + {"revision", "zero revision is invalid", func(p **pb.Treatment) { (*p).Revision = 0 }, "requires"}, + {"missing-settings", "settings are required", func(p **pb.Treatment) { (*p).Settings = nil }, "requires"}, + } + for _, tc := range []struct { + destination string + valid bool + }{ + {"192.168.1.1/32", true}, {"2001:db8::1/128", true}, {"192.0.2.0/24", false}, {"192.0.2.1/24", false}, {"::ffff:192.0.2.1/128", false}, {"127.0.0.1/32", false}, {"::1/128", false}, {"224.0.0.1/32", false}, {"fe80::1/128", false}, {"0.0.0.0/32", false}, {"invalid", false}, + } { + want := "" + if !tc.valid { + want = "canonical unicast" + } + cases = append(cases, validationCase[*pb.Treatment]{tc.destination, "only unicast host destinations may be treated", func(p **pb.Treatment) { (*p).Destination = tc.destination }, want}) + } + checkValidation(t, func() *pb.Treatment { return fixture(time.Unix(1700000000, 0)).Assignments[0].Treatment }, ValidateTreatment, cases) +} + +func TestTCPSettingsValidation(t *testing.T) { + type input struct { + s *pb.TCPSettings + verified bool + } + cases := []validationCase[input]{ + {"cubic", "ordinary CUBIC is accepted", func(*input) {}, ""}, + {"nil", "nil settings are rejected", func(p *input) { p.s = nil }, "required"}, + {"empty", "at least one explicit metric is required", func(p *input) { p.s = &pb.TCPSettings{} }, "empty"}, + {"explicit-false", "false quickack is a real setting", func(p *input) { p.s = &pb.TCPSettings{QuickAck: proto.Bool(false)} }, ""}, + {"lock-without-algorithm", "locking requires an algorithm", func(p *input) { p.s = &pb.TCPSettings{CongestionLock: true} }, "requires"}, + {"algorithm-max", "15 byte kernel name is accepted", func(p *input) { p.s.CongestionControl = proto.String(strings.Repeat("a", 15)) }, ""}, + {"algorithm-long", "16 byte kernel name is rejected", func(p *input) { p.s.CongestionControl = proto.String(strings.Repeat("a", 16)) }, "kernel name"}, + {"algorithm-empty", "explicit empty algorithm is invalid", func(p *input) { p.s.CongestionControl = proto.String("") }, "invalid congestion"}, + {"dctcp-unverified", "ECN fabric verification is required", func(p *input) { p.s.CongestionControl = proto.String("dctcp"); p.s.Ecn = proto.Bool(true) }, "DCTCP"}, + {"dctcp-no-ecn", "verification alone is insufficient", func(p *input) { p.s.CongestionControl = proto.String("dctcp"); p.verified = true }, "DCTCP"}, + {"dctcp-verified", "verified fabric and explicit ECN permit DCTCP", func(p *input) { + p.s.CongestionControl = proto.String("dctcp") + p.s.Ecn = proto.Bool(true) + p.verified = true + }, ""}, + } + for _, metric := range []string{"rto", "cwnd", "rwnd"} { + maximum := uint32(1024) + errorText := "initial window" + if metric == "rto" { + maximum = 120000 + errorText = "rto_min" + } + for _, value := range []uint32{0, 1, maximum, maximum + 1} { + want := "" + if value == 0 || value > maximum { + want = errorText + } + cases = append(cases, validationCase[input]{fmt.Sprintf("%s-%d", metric, value), "metric bounds are inclusive", func(p *input) { + switch metric { + case "rto": + p.s.RtoMinMs = proto.Uint32(value) + case "cwnd": + p.s.InitialCongestionWindow = proto.Uint32(value) + case "rwnd": + p.s.InitialReceiveWindow = proto.Uint32(value) + } + }, want}) + } + } + checkValidation(t, func() input { return input{s: &pb.TCPSettings{CongestionControl: proto.String("cubic")}} }, func(p input) error { return ValidateTCPSettings(p.s, p.verified) }, cases) +} + +func TestPolicyValidation(t *testing.T) { + cases := []validationCase[*pb.ExperimentPolicy]{ + {"default", "production defaults are valid", func(**pb.ExperimentPolicy) {}, ""}, + {"nil", "policy is required", func(p **pb.ExperimentPolicy) { *p = nil }, "required"}, + {"observation-after-phase", "observation cannot exceed phase", func(p **pb.ExperimentPolicy) { (*p).MaximumPhase = durationpb.New(time.Minute) }, "sample bounds"}, + {"zero-sockets", "sample count must be positive", func(p **pb.ExperimentPolicy) { (*p).MinimumSocketsPerHost = 0 }, "sample bounds"}, + {"zero-bytes", "byte count must be positive", func(p **pb.ExperimentPolicy) { (*p).MinimumBytesPerHost = 0 }, "sample bounds"}, + } + for _, n := range []uint32{1, 2, MaxAgents, MaxAgents + 1} { + want := "" + if n < 2 || n > MaxAgents { + want = "sample bounds" + } + cases = append(cases, validationCase[*pb.ExperimentPolicy]{fmt.Sprintf("hosts-%d", n), "treated population bounds are inclusive", func(p **pb.ExperimentPolicy) { (*p).MinimumTreatedHosts = n }, want}) + } + for _, field := range []string{"observation", "phase", "age"} { + for _, tc := range []struct { + name string + d *durationpb.Duration + want string + }{ + {"nil", nil, "duration"}, {"zero", durationpb.New(0), "duration"}, {"negative", durationpb.New(-time.Second), "duration"}, {"maximum", durationpb.New(7 * 24 * time.Hour), ""}, {"over", durationpb.New(7*24*time.Hour + time.Nanosecond), "duration"}, {"invalid", &durationpb.Duration{Nanos: 1e9}, "duration"}, + } { + cases = append(cases, validationCase[*pb.ExperimentPolicy]{field + "-" + tc.name, "durations must be valid and within seven days", func(p **pb.ExperimentPolicy) { + (*p).MaximumPhase = durationpb.New(7 * 24 * time.Hour) + switch field { + case "observation": + (*p).MinimumObservation = tc.d + case "phase": + (*p).MaximumPhase = tc.d + case "age": + (*p).MaximumEvidenceAge = tc.d + } + }, tc.want}) + } + } + for _, field := range []string{"effect", "loss", "rtt"} { + for _, v := range []float64{-1, 0, 1, 1.0001, math.NaN(), math.Inf(1), math.Inf(-1)} { + want := "" + if math.IsNaN(v) || math.IsInf(v, 0) || v < 0 || v > 1 { + want = "guardrail" + } + cases = append(cases, validationCase[*pb.ExperimentPolicy]{fmt.Sprintf("%s-%g", field, v), "effect bounds must be finite fractions in [0,1]", func(p **pb.ExperimentPolicy) { + switch field { + case "effect": + (*p).MinimumImprovement = v + case "loss": + (*p).MaximumRetransmissionIncrease = v + case "rtt": + (*p).MaximumRttIncrease = v + } + }, want}) + } + } + checkValidation(t, DefaultPolicy, ValidatePolicy, cases) +} + +func TestEvidenceValidation(t *testing.T) { + now := time.Unix(1700000000, 0) + cases := []validationCase[*pb.Evidence]{ + {"valid", "complete finite evidence is accepted", func(**pb.Evidence) {}, ""}, + {"nil", "evidence is required", func(p **pb.Evidence) { *p = nil }, "interval"}, + {"empty-window", "a zero length window is invalid", func(p **pb.Evidence) { (*p).WindowStart = (*p).WindowEnd }, "interval"}, + {"analysis-before-end", "analysis cannot predate data", func(p **pb.Evidence) { (*p).AnalyzedAt = (*p).WindowStart }, "interval"}, + {"missing-time", "all timestamps are required", func(p **pb.Evidence) { (*p).WindowEnd = nil }, "interval"}, + {"invalid-time", "malformed protobuf timestamps fail", func(p **pb.Evidence) { (*p).WindowEnd = ×tamppb.Timestamp{Nanos: 1e9} }, "interval"}, + {"query-version", "query provenance is required", func(p **pb.Evidence) { (*p).QueryVersion = "" }, "provenance"}, + {"long-version", "query version length is bounded", func(p **pb.Evidence) { (*p).QueryVersion = strings.Repeat("v", 257) }, "provenance"}, + {"digest-length", "digest is exactly 64 characters", func(p **pb.Evidence) { (*p).ArtifactSha256 = "a" }, "provenance"}, + {"digest-hex", "digest is lower case hexadecimal", func(p **pb.Evidence) { (*p).ArtifactSha256 = strings.Repeat("z", 64) }, "sha256"}, + {"empty-hosts", "empty evidence is not healthy evidence", func(p **pb.Evidence) { (*p).Hosts = nil }, "population"}, + {"nil-host", "nil host entries are rejected", func(p **pb.Evidence) { (*p).Hosts[0] = nil }, "host"}, + {"duplicate-host", "hosts are independent units", func(p **pb.Evidence) { (*p).Hosts[1] = (*p).Hosts[0] }, "host"}, + {"overexposure", "exposed sockets cannot exceed total sockets", func(p **pb.Evidence) { (*p).Hosts[0].ExposedSockets = 101 }, "host"}, + } + for _, field := range []string{"goodput", "loss", "rtt"} { + for _, v := range []float64{0, -1, math.NaN(), math.Inf(1), math.Inf(-1)} { + want := "" + if v != 0 { + want = "measurement" + } + cases = append(cases, validationCase[*pb.Evidence]{fmt.Sprintf("%s-%g", field, v), "measurements must be finite and nonnegative", func(p **pb.Evidence) { + h := (*p).Hosts[0] + switch field { + case "goodput": + h.GoodputBps = v + case "loss": + h.RetransmissionRatio = v + case "rtt": + h.RttP95Ms = v + } + }, want}) + } + } + for _, n := range []int{MaxAgents, MaxAgents + 1} { + want := "" + if n > MaxAgents { + want = "population" + } + cases = append(cases, validationCase[*pb.Evidence]{fmt.Sprintf("population-%d", n), "fleet population is bounded", func(p **pb.Evidence) { + (*p).Hosts = nil + for i := range n { + (*p).Hosts = append((*p).Hosts, &pb.HostEvidence{AgentId: fmt.Sprintf("host-%d", i)}) + } + }, want}) + } + checkValidation(t, func() *pb.Evidence { _, v := experimentFixture(now); return v }, ValidateEvidence, cases) +} + +func TestDesiredValidation(t *testing.T) { + now := time.Unix(1700000000, 0) + cases := []validationCase[*pb.DesiredState]{ + {"valid", "leased trials are accepted", func(**pb.DesiredState) {}, ""}, + {"nil", "desired header is required", func(p **pb.DesiredState) { *p = nil }, "header"}, + {"empty", "empty assignment set withdraws treatments", func(p **pb.DesiredState) { (*p).Assignments = nil }, ""}, + {"zero-revision", "revisions start at one", func(p **pb.DesiredState) { (*p).Revision = 0 }, "header"}, + {"missing-time", "issuance time is required", func(p **pb.DesiredState) { (*p).IssuedAt = nil }, "header"}, + {"nil-assignment", "assignment cannot be nil", func(p **pb.DesiredState) { (*p).Assignments[0] = nil }, "identity"}, + {"zero-generation", "generations start at one", func(p **pb.DesiredState) { (*p).Assignments[0].Generation = 0 }, "identity"}, + {"duplicate-id", "assignment IDs must be unique", func(p **pb.DesiredState) { + (*p).Assignments = append((*p).Assignments, proto.Clone((*p).Assignments[0]).(*pb.Assignment)) + }, "identity"}, + {"duplicate-destination", "active routes cannot overlap", func(p **pb.DesiredState) { + a := proto.Clone((*p).Assignments[0]).(*pb.Assignment) + a.Id = "other" + (*p).Assignments = append((*p).Assignments, a) + }, "overlapping"}, + {"retired-overlap", "retired routes may overlap active routes", func(p **pb.DesiredState) { + a := proto.Clone((*p).Assignments[0]).(*pb.Assignment) + a.Id = "other" + a.Mode = pb.AssignmentMode_ASSIGNMENT_MODE_RETIRED + (*p).Assignments = append((*p).Assignments, a) + }, ""}, + {"invalid-mode", "unknown modes are rejected", func(p **pb.DesiredState) { (*p).Assignments[0].Mode = 99 }, "mode"}, + {"missing-lease", "trials require an expiry", func(p **pb.DesiredState) { (*p).Assignments[0].LeaseExpiresAt = nil }, "expiry"}, + {"stable-no-lease", "stable routes need no lease", func(p **pb.DesiredState) { + a := (*p).Assignments[0] + a.Mode = pb.AssignmentMode_ASSIGNMENT_MODE_STABLE + a.LeaseExpiresAt = nil + }, ""}, + {"predecessor", "matching predecessor is accepted", func(p **pb.DesiredState) { + a := (*p).Assignments[0] + a.Predecessor = proto.Clone(a.Treatment).(*pb.Treatment) + }, ""}, + {"wrong-predecessor", "predecessor must cover the same destination", func(p **pb.DesiredState) { + a := (*p).Assignments[0] + a.Predecessor = proto.Clone(a.Treatment).(*pb.Treatment) + a.Predecessor.Destination = "192.0.2.2/32" + }, "differs"}, + } + for _, n := range []int{MaxAssignments, MaxAssignments + 1} { + want := "" + if n > MaxAssignments { + want = "header" + } + cases = append(cases, validationCase[*pb.DesiredState]{fmt.Sprintf("assignments-%d", n), "assignment count is bounded", func(p **pb.DesiredState) { + base := (*p).Assignments[0] + (*p).Assignments = nil + for i := range n { + a := proto.Clone(base).(*pb.Assignment) + a.Id = fmt.Sprintf("a-%d", i) + a.Treatment.Destination = fmt.Sprintf("198.18.%d.%d/32", i/256, i%256) + (*p).Assignments = append((*p).Assignments, a) + } + }, want}) + } + checkValidation(t, func() *pb.DesiredState { return fixture(now) }, ValidateDesired, cases) +} + +func TestProposalValidation(t *testing.T) { + now := time.Unix(1700000000, 0) + checkValidation(t, func() *pb.Experiment { e, _ := experimentFixture(now); return e }, ValidateProposal, []validationCase[*pb.Experiment]{ + {"valid", "complete proposal is reviewable", func(**pb.Experiment) {}, ""}, + {"nil", "proposal is required", func(p **pb.Experiment) { *p = nil }, "identity"}, + {"no-hypothesis", "proposal must state a hypothesis", func(p **pb.Experiment) { (*p).Hypothesis = "" }, "identity"}, + {"long-hypothesis", "hypothesis is bounded", func(p **pb.Experiment) { (*p).Hypothesis = strings.Repeat("x", 8193) }, "identity"}, + {"three-hosts", "one canary and two controls suffice", func(p **pb.Experiment) { (*p).CohortOrder = (*p).CohortOrder[:3] }, ""}, + {"two-hosts", "two hosts cannot provide a canary and two controls", func(p **pb.Experiment) { (*p).CohortOrder = (*p).CohortOrder[:2] }, "population"}, + {"duplicate", "duplicate hosts cannot inflate the cohort", func(p **pb.Experiment) { (*p).CohortOrder[1] = (*p).CohortOrder[0] }, "cohort"}, + {"missing-baseline-host", "every cohort member needs a baseline", func(p **pb.Experiment) { (*p).CohortOrder[0] = "absent" }, "baseline missing"}, + {"invalid-treatment", "proposal validates treatment", func(p **pb.Experiment) { (*p).Treatment = nil }, "requires"}, + {"invalid-policy", "proposal validates policy", func(p **pb.Experiment) { (*p).Policy = nil }, "policy"}, + {"invalid-evidence", "proposal validates evidence", func(p **pb.Experiment) { (*p).Baseline = nil }, "interval"}, + }) +} diff --git a/pkg/xtcp/deserialize.go b/pkg/xtcp/deserialize.go index d711d3b10..2838f5282 100644 --- a/pkg/xtcp/deserialize.go +++ b/pkg/xtcp/deserialize.go @@ -226,6 +226,9 @@ func (x *XTCP) processInetDiagRecord( // plus per-namespace container labels (Docker Engine API, keyed by netns // inode) and opt-in nsid. All O(1) and no-op when their enricher is off. x.applyEnrichment(xtcpRecord) + if x.treatmentStamp != nil { + x.treatmentStamp(xtcpRecord) + } // Fallback container resolution from the socket's cgroup id (populated by // DeserializeAttributes above). Only runs when the Docker index didn't diff --git a/pkg/xtcp/destinations_s3parquet.go b/pkg/xtcp/destinations_s3parquet.go index 92dd0259c..719e3a1e9 100644 --- a/pkg/xtcp/destinations_s3parquet.go +++ b/pkg/xtcp/destinations_s3parquet.go @@ -768,8 +768,16 @@ func rowFromProto(r *xtcp_flat_record.XtcpFlatRecord) ParquetRow { TimestampNs: r.TimestampNs, - Hostname: r.Hostname, - Location: r.Location, + Hostname: r.Hostname, + Location: r.Location, + AgentId: r.AgentId, + HostBootId: r.HostBootId, + CollectionId: r.CollectionId, + TreatmentStateRevision: r.TreatmentStateRevision, + TreatmentAssignmentId: r.TreatmentAssignmentId, + TreatmentGeneration: r.TreatmentGeneration, + TreatmentAppliedTimestampNs: r.TreatmentAppliedTimestampNs, + TreatmentAlgorithmExposed: r.TreatmentAlgorithmExposed, Netns: r.Netns, NetnsInode: r.NetnsInode, diff --git a/pkg/xtcp/destinations_s3parquet_rowfromproto_test.go b/pkg/xtcp/destinations_s3parquet_rowfromproto_test.go index b1aec6fca..d162002dc 100644 --- a/pkg/xtcp/destinations_s3parquet_rowfromproto_test.go +++ b/pkg/xtcp/destinations_s3parquet_rowfromproto_test.go @@ -47,6 +47,8 @@ var rowFromProtoSkip = map[string]bool{"EventDate": true} func sentinelFor(t *testing.T, typ reflect.Type, seq int) reflect.Value { t.Helper() switch typ.Kind() { + case reflect.Bool: + return reflect.ValueOf(true).Convert(typ) case reflect.String: return reflect.ValueOf(fmt.Sprintf("v%d", seq)).Convert(typ) case reflect.Uint32, reflect.Uint64: @@ -71,6 +73,8 @@ func sentinelFor(t *testing.T, typ reflect.Type, seq int) reflect.Value { func extremeFor(t *testing.T, typ reflect.Type, low bool) reflect.Value { t.Helper() switch typ.Kind() { + case reflect.Bool: + return reflect.ValueOf(!low).Convert(typ) case reflect.String: if low { return reflect.ValueOf("").Convert(typ) diff --git a/pkg/xtcp/destinations_s3parquet_schema.go b/pkg/xtcp/destinations_s3parquet_schema.go index 69cecdfda..cbf6cd543 100644 --- a/pkg/xtcp/destinations_s3parquet_schema.go +++ b/pkg/xtcp/destinations_s3parquet_schema.go @@ -37,8 +37,16 @@ type ParquetRow struct { EventDate string `parquet:"event_date,zstd"` // ---- metadata: host identity (20s) - Hostname string `parquet:"hostname,zstd"` - Location string `parquet:"location,zstd"` + Hostname string `parquet:"hostname,zstd"` + Location string `parquet:"location,zstd"` + AgentId string `parquet:"agent_id,zstd"` + HostBootId string `parquet:"host_boot_id,zstd"` + CollectionId string `parquet:"collection_id,zstd"` + TreatmentStateRevision uint64 `parquet:"treatment_state_revision,snappy"` + TreatmentAssignmentId string `parquet:"treatment_assignment_id,zstd"` + TreatmentGeneration uint64 `parquet:"treatment_generation,snappy"` + TreatmentAppliedTimestampNs int64 `parquet:"treatment_applied_timestamp_ns,snappy"` + TreatmentAlgorithmExposed bool `parquet:"treatment_algorithm_exposed"` // ---- metadata: network namespace identity (30s) Netns string `parquet:"netns,zstd"` diff --git a/pkg/xtcp/treatment.go b/pkg/xtcp/treatment.go new file mode 100644 index 000000000..f4a51620d --- /dev/null +++ b/pkg/xtcp/treatment.go @@ -0,0 +1,9 @@ +package xtcp + +import "github.com/randomizedcoder/xtcp2/gen/go/xtcp_flat_record" + +// SetTreatmentStamper attaches the optional controller provenance hook before +// RunWithPoller. The hook must be concurrency-safe and must not block on I/O. +func (x *XTCP) SetTreatmentStamper(fn func(*xtcp_flat_record.XtcpFlatRecord)) { + x.treatmentStamp = fn +} diff --git a/pkg/xtcp/xtcp.go b/pkg/xtcp/xtcp.go index 28806bc6f..6a5c97583 100644 --- a/pkg/xtcp/xtcp.go +++ b/pkg/xtcp/xtcp.go @@ -37,8 +37,9 @@ const ( ) type XTCP struct { - ctx context.Context - cancel context.CancelFunc + treatmentStamp func(*xtcp_flat_record.XtcpFlatRecord) + ctx context.Context + cancel context.CancelFunc config *xtcp_config.XtcpConfig diff --git a/pkg/xtcpnl/xtcp_writer_test.go b/pkg/xtcpnl/xtcp_writer_test.go index 468b9491f..d53a444da 100644 --- a/pkg/xtcpnl/xtcp_writer_test.go +++ b/pkg/xtcpnl/xtcp_writer_test.go @@ -129,16 +129,14 @@ func TestDeserializeXXXXTCP(t *testing.T) { }, }, { - // CongInfo writes only on a recognized "cub" / "bbr" / etc. - // prefix. Garbage 0x01 bytes fall through the switch with - // no observable side effect, which is the intended kernel- - // compatibility behavior. We only assert the parse succeeds. + // Names absent from our enum are retained as raw strings. name: "CongInfo", min: CongInfoSizeCst, parse: DeserializeCongInfoXTCP, verify: func(t *testing.T, x *xtcp_flat_record.XtcpFlatRecord) { - // No assertion — successful parse with unknown algorithm - // leaves all fields at zero, which is correct. + if x.InetDiagCong == "" || x.InetDiagCongEnum != xtcp_flat_record.XtcpFlatRecord_CONGESTION_ALGORITHM_UNSPECIFIED { + t.Error("unknown algorithm name must be retained without inventing an enum") + } }, }, { diff --git a/pkg/xtcpnl/xtcpnl_inet_diag_conginfo.go b/pkg/xtcpnl/xtcpnl_inet_diag_conginfo.go index da80b85c5..4711c2f3d 100644 --- a/pkg/xtcpnl/xtcpnl_inet_diag_conginfo.go +++ b/pkg/xtcpnl/xtcpnl_inet_diag_conginfo.go @@ -1,6 +1,7 @@ package xtcpnl import ( + "bytes" "errors" "github.com/randomizedcoder/xtcp2/gen/go/xtcp_flat_record" @@ -81,6 +82,16 @@ func DeserializeCongInfoXTCP(data []byte, x *xtcp_flat_record.XtcpFlatRecord) (e return ErrCongInfoSmall } + // Preserve the actual kernel name, including algorithms absent from our + // enum (for example Reno). Controller exposure is based on this field. + // Copy out of the reused netlink buffer and exclude the NUL terminator. + name := data + if end := bytes.IndexByte(name, 0); end >= 0 { + name = name[:end] + } + x.InetDiagCong = string(name) + x.InetDiagCongEnum = xtcp_flat_record.XtcpFlatRecord_CONGESTION_ALGORITHM_UNSPECIFIED + // Match on the first 3 bytes — the kernel attribute is a null-terminated // algorithm name like "cubic", "bbr", "dctcp", "vegas". Comparing data[0:4] // against 3-char strings would never match, so we use the 3-char prefix. diff --git a/pkg/xtcpnl/xtcpnl_inet_diag_conginfo_test.go b/pkg/xtcpnl/xtcpnl_inet_diag_conginfo_test.go index f697b8845..5b19216a3 100644 --- a/pkg/xtcpnl/xtcpnl_inet_diag_conginfo_test.go +++ b/pkg/xtcpnl/xtcpnl_inet_diag_conginfo_test.go @@ -19,8 +19,37 @@ import ( "reflect" "strings" "testing" + + "github.com/randomizedcoder/xtcp2/gen/go/xtcp_flat_record" ) +func TestDeserializeCongInfoXTCPName(t *testing.T) { + // Reuse the same record to catch a stale enum after an unknown algorithm. + record := new(xtcp_flat_record.XtcpFlatRecord) + for _, tc := range []struct { + wire string + name string + algorithm xtcp_flat_record.XtcpFlatRecord_CongestionAlgorithm + }{ + {"cubic\x00\x00\x00", "cubic", xtcp_flat_record.XtcpFlatRecord_CONGESTION_ALGORITHM_CUBIC}, + {"reno\x00", "reno", xtcp_flat_record.XtcpFlatRecord_CONGESTION_ALGORITHM_UNSPECIFIED}, + {"bbr\x00", "bbr", xtcp_flat_record.XtcpFlatRecord_CONGESTION_ALGORITHM_BBR1}, + {"bbr2\x00", "bbr2", xtcp_flat_record.XtcpFlatRecord_CONGESTION_ALGORITHM_BBR2}, + {"bbr3\x00", "bbr3", xtcp_flat_record.XtcpFlatRecord_CONGESTION_ALGORITHM_BBR3}, + } { + t.Run(tc.name, func(t *testing.T) { + data := []byte(tc.wire) + if err := DeserializeCongInfoXTCP(data, record); err != nil { + t.Fatal(err) + } + data[0] = 'x' // The netlink receive buffer is reused after decoding. + if record.InetDiagCong != tc.name || record.InetDiagCongEnum != tc.algorithm { + t.Fatalf("algorithm = %q/%v, want %q/%v", record.InetDiagCong, record.InetDiagCongEnum, tc.name, tc.algorithm) + } + }) + } +} + type DeserializeCongInfoTest struct { description string filename string diff --git a/proto/xtcp/controller/v1/controller.proto b/proto/xtcp/controller/v1/controller.proto new file mode 100644 index 000000000..0ea33c324 --- /dev/null +++ b/proto/xtcp/controller/v1/controller.proto @@ -0,0 +1,240 @@ +syntax = "proto3"; + +package xtcp.controller.v1; + +option go_package = "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller"; + +import "google/protobuf/timestamp.proto"; +import "google/protobuf/duration.proto"; + +// All inbound messages are validated by the controller application layer. +// IDs are bounded ASCII identifiers; address selectors are canonical host CIDRs. +// Limits, required fields and contextual constraints are in pkg/treatment/validate.go. +service AgentControlService { + rpc CheckIn(CheckInRequest) returns (CheckInResponse); + rpc GetDesiredState(GetDesiredStateRequest) returns (GetDesiredStateResponse); + rpc WatchDesiredState(WatchDesiredStateRequest) returns (stream WatchDesiredStateResponse); +} + +service OperatorService { + rpc Propose(ProposeRequest) returns (ProposeResponse); + rpc Analyze(AnalyzeRequest) returns (AnalyzeResponse); + rpc Review(ReviewRequest) returns (ReviewResponse); + rpc Control(ControlRequest) returns (ControlResponse); + rpc Inspect(InspectRequest) returns (InspectResponse); +} + +enum AssignmentMode { + ASSIGNMENT_MODE_UNSPECIFIED = 0; + ASSIGNMENT_MODE_TRIAL = 1; + ASSIGNMENT_MODE_STABLE = 2; + ASSIGNMENT_MODE_SUSPENDED = 3; + ASSIGNMENT_MODE_RETIRED = 4; +} + +enum ApplyStatus { + APPLY_STATUS_UNSPECIFIED = 0; + APPLY_STATUS_PENDING = 1; + APPLY_STATUS_VERIFIED = 2; + APPLY_STATUS_REMOVED = 3; + APPLY_STATUS_EXPIRED = 4; + APPLY_STATUS_FAILED = 5; +} + +message TCPSettings { + optional string congestion_control = 1; + bool congestion_lock = 2; + optional uint32 rto_min_ms = 3; + optional uint32 initial_congestion_window = 4; + optional uint32 initial_receive_window = 5; + optional bool quick_ack = 6; + optional bool ecn = 7; +} + +message Treatment { + string id = 1; + uint64 revision = 2; + string destination = 3; + TCPSettings settings = 4; + bool ecn_fabric_verified = 5; +} + +message Assignment { + string id = 1; + string experiment_id = 2; + Treatment treatment = 3; + AssignmentMode mode = 4; + google.protobuf.Timestamp lease_expires_at = 5; + // Previous approved stable treatment, restored on trial expiry/rollback. + Treatment predecessor = 6; + // Increment only on an explicitly approved reactivation, not lease renewal. + uint64 generation = 7; +} + +message DesiredState { + string agent_id = 1; + uint64 revision = 2; + bool suspended = 3; + repeated Assignment assignments = 4; + google.protobuf.Timestamp issued_at = 5; +} + +message Capabilities { + string boot_id = 1; + string kernel_release = 2; + repeated string congestion_algorithms = 3; + bool simple_host_routing = 4; + bool actuator_enabled = 5; +} + +message ApplicationRecord { + Assignment assignment = 1; + uint64 desired_revision = 2; + ApplyStatus status = 3; + google.protobuf.Timestamp received_at = 4; + google.protobuf.Timestamp attempted_at = 5; + google.protobuf.Timestamp applied_at = 6; + google.protobuf.Timestamp verified_at = 7; + google.protobuf.Timestamp removed_at = 8; + string error = 9; + // Locally generated canonical netlink route body, never accepted from RPC. + bytes installed_route = 10; + string installed_boot_id = 11; + bool expired_latched = 12; +} + +message LocalState { + DesiredState desired = 1; + bool locally_suspended = 2; + string suspension_reason = 3; + repeated ApplicationRecord records = 4; + google.protobuf.Timestamp updated_at = 5; +} + +message CheckInRequest { + string agent_id = 1; + string location = 2; + Capabilities capabilities = 3; + uint64 desired_revision = 4; + repeated ApplicationRecord records = 5; + bool locally_suspended = 6; +} +message CheckInResponse { DesiredState desired = 1; } +message GetDesiredStateRequest { string agent_id = 1; } +message GetDesiredStateResponse { DesiredState desired = 1; } +message WatchDesiredStateRequest { string agent_id = 1; uint64 after_revision = 2; } +message WatchDesiredStateResponse { uint64 revision = 1; } + +message HostEvidence { + string agent_id = 1; + uint64 sockets = 2; + uint64 bytes_acked = 3; + double goodput_bps = 4; + double retransmission_ratio = 5; + double rtt_p95_ms = 6; + uint64 exposed_sockets = 7; + bool collection_healthy = 8; +} + +message Evidence { + string id = 1; + google.protobuf.Timestamp analyzed_at = 2; + google.protobuf.Timestamp window_start = 3; + google.protobuf.Timestamp window_end = 4; + string query_version = 5; + string enrichment_version = 6; + repeated HostEvidence hosts = 7; + // Content-addressed reference to the complete immutable analysis artifact. + string artifact_sha256 = 8; +} + +message ExperimentPolicy { + google.protobuf.Duration minimum_observation = 1; + google.protobuf.Duration maximum_phase = 2; + uint64 minimum_sockets_per_host = 3; + uint64 minimum_bytes_per_host = 4; + uint32 minimum_treated_hosts = 5; + double minimum_improvement = 6; + double maximum_retransmission_increase = 7; + double maximum_rtt_increase = 8; + google.protobuf.Duration maximum_evidence_age = 9; +} + +enum ExperimentStatus { + EXPERIMENT_STATUS_UNSPECIFIED = 0; + EXPERIMENT_STATUS_PROPOSED = 1; + EXPERIMENT_STATUS_RUNNING = 2; + EXPERIMENT_STATUS_PAUSED = 3; + EXPERIMENT_STATUS_SUSPENDED = 4; + EXPERIMENT_STATUS_COMPLETE = 5; + EXPERIMENT_STATUS_RETIRED = 6; + EXPERIMENT_STATUS_ROLLED_BACK = 7; +} + +message Phase { + uint32 target_hosts = 1; + google.protobuf.Timestamp issued_at = 2; + google.protobuf.Timestamp observation_started_at = 3; + google.protobuf.Timestamp evaluated_at = 4; + Evidence evidence = 5; + string outcome = 6; + string reviewer = 7; + string reason = 8; +} + +message Experiment { + string id = 1; + string location = 2; + string hypothesis = 3; + Treatment treatment = 4; + ExperimentPolicy policy = 5; + Evidence baseline = 6; + repeated string cohort_order = 7; + repeated Phase phases = 8; + ExperimentStatus status = 9; + uint64 revision = 10; + google.protobuf.Timestamp proposed_at = 11; + Evidence latest_evidence = 12; + string latest_outcome = 13; +} + +message ProposeRequest { Experiment experiment = 1; string request_id = 2; } +message ProposeResponse { Experiment experiment = 1; } +message AnalyzeRequest { string experiment_id = 1; uint64 expected_revision = 2; } +message AnalyzeResponse { Evidence evidence = 1; string outcome = 2; } +message ReviewRequest { + string experiment_id = 1; + uint64 expected_revision = 2; + bool approve = 3; + string reason = 4; + string request_id = 5; + // Initial review uses baseline; subsequent reviews require fresh evidence. + Evidence evidence = 6; +} +message ReviewResponse { Experiment experiment = 1; } + +enum ControlAction { + CONTROL_ACTION_UNSPECIFIED = 0; + CONTROL_ACTION_PAUSE = 1; + CONTROL_ACTION_SUSPEND = 2; + CONTROL_ACTION_RESUME = 3; + CONTROL_ACTION_ROLLBACK = 4; + CONTROL_ACTION_RETIRE = 5; +} +message ControlRequest { + // Exactly one selector, or fleet=true, is required. + string experiment_id = 1; + string agent_id = 2; + string location = 3; + bool fleet = 4; + ControlAction action = 5; + string reason = 6; + string request_id = 7; +} +message ControlResponse { uint32 affected_agents = 1; } +message InspectRequest { string experiment_id = 1; string agent_id = 2; } +message InspectResponse { + repeated Experiment experiments = 1; + repeated DesiredState desired = 2; + repeated CheckInRequest observed = 3; +} diff --git a/proto/xtcp_flat_record/v1/xtcp_flat_record.proto b/proto/xtcp_flat_record/v1/xtcp_flat_record.proto index 9a0f67c8a..9894b9f0f 100644 --- a/proto/xtcp_flat_record/v1/xtcp_flat_record.proto +++ b/proto/xtcp_flat_record/v1/xtcp_flat_record.proto @@ -88,7 +88,7 @@ message XtcpFlatRecord { "class_id", "sock_opt", "c_group"; // ==== metadata (1-299) ===================================================== - // Free: 3-9, 11-19, 22-29, 33-39, 44-49, 52-59, 63-99, 108-119, 125-199, + // Free: 3-9, 11-19, 33-39, 44-49, 52-59, 63-99, 108-119, 125-199, // 208-219, 225-299. Uplink slots are fixed at two (100s, 200s); a third slot // would take 250-274, NOT 300 (that is the enrichment block). @@ -117,6 +117,17 @@ message XtcpFlatRecord { // -location flag or LOCATION env. Constant for every record from one // daemon instance. string location = 21; + // Controller provenance. Empty/zero means observation-only legacy producer. + string agent_id = 22; + string host_boot_id = 23; + string collection_id = 24; + uint64 treatment_state_revision = 25; + string treatment_assignment_id = 26; + uint64 treatment_generation = 27; + int64 treatment_applied_timestamp_ns = 28; + // True only when the observed socket algorithm matches a verified route + // assignment in this namespace. This does not prove initial-window exposure. + bool treatment_algorithm_exposed = 29; // ---- metadata: network namespace identity (30s) -------------------------- // network namespace — best-effort human name (bind-mount name, else a diff --git a/tools/tcp-controller-integration/main.go b/tools/tcp-controller-integration/main.go new file mode 100644 index 000000000..37a7599d5 --- /dev/null +++ b/tools/tcp-controller-integration/main.go @@ -0,0 +1,452 @@ +// Command tcp-controller-integration is a disposable-VM test fixture. It is not +// installed with xtcp2. Nix supplies the isolated network and the required marker. +package main + +import ( + "bytes" + "context" + "encoding/json" + "flag" + "fmt" + "io" + "log" + "net/http" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/protobuf/encoding/prototext" + "google.golang.org/protobuf/types/known/durationpb" +) + +const pkiDir = "/var/lib/controller-test/pki" +const statePath = "/var/lib/xtcp2/routes/state.textproto" +const evidenceDir = "/var/lib/controller-test/evidence" + +var hosts = []string{"agent1", "agent2", "agent3", "agent4", "agent5", "agent6"} +var client = &http.Client{Timeout: 30 * time.Second} + +type action struct { + Name string + CA, Certificate, Key []byte + Seconds int64 + Destination string +} +type snapshot struct { + Routes, Main, State, Journal, Rules, Qdisc, Services, DefaultCC string + Projection string +} + +func must[T any](v T, err error) T { + if err != nil { + panic(err) + } + return v +} +func check(err error) { + if err != nil { + panic(err) + } +} +func require(ok bool, format string, args ...any) { + if !ok { + panic(fmt.Sprintf(format, args...)) + } +} +func command(name string, args ...string) string { + ctx, cancel := context.WithTimeout(context.Background(), 25*time.Second) + defer cancel() + out, err := exec.CommandContext(ctx, name, args...).CombinedOutput() + if err != nil { + panic(fmt.Sprintf("%s %v: %v: %s", name, args, err, out)) + } + return string(out) +} +func read(path string) []byte { return must(os.ReadFile(path)) } +func write(path string, data []byte) { + check(os.MkdirAll(filepath.Dir(path), 0700)) + check(os.WriteFile(path, data, 0600)) +} +func systemctl(args ...string) string { return command("systemctl", args...) } +func agentReady() { + probe := &http.Client{Timeout: time.Second} + eventually("local daemon ready", 15*time.Second, func() bool { + response := must(probe.Get("http://127.0.0.1:9088/readyz")) + defer response.Body.Close() + return response.StatusCode == http.StatusOK + }) +} +func routeTable() string { + // Before the first assignment the private table does not exist. + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + out, err := exec.CommandContext(ctx, "ip", "-j", "route", "show", "table", "10002").CombinedOutput() + if err != nil && strings.Contains(string(out), "FIB table does not exist") { + return "[]" + } + check(err) + return string(out) +} +func localSnapshot() snapshot { + s := snapshot{Routes: routeTable(), Main: command("ip", "-j", "route", "show", "table", "main"), DefaultCC: strings.TrimSpace(command("sysctl", "-n", "net.ipv4.tcp_congestion_control"))} + b, err := os.ReadFile(statePath) + if err == nil { + s.State = string(b) + } else if !os.IsNotExist(err) { + panic(err) + } + b, _ = os.ReadFile("/var/lib/xtcp2/routes/assignment-netem-canary.textproto") + s.Projection = string(b) + return s +} +func serveAgent(address string) { + mux := http.NewServeMux() + mux.HandleFunc("POST /action", func(w http.ResponseWriter, r *http.Request) { + defer func() { + if p := recover(); p != nil { + http.Error(w, fmt.Sprint(p), 500) + } + }() + var a action + check(json.NewDecoder(http.MaxBytesReader(w, r.Body, 64<<10)).Decode(&a)) + var result any = "ok" + switch a.Name { + case "ready": + case "setup": + require(len(a.CA) > 0 && len(a.Certificate) > 0 && len(a.Key) > 0, "missing fixture credentials") + write(pkiDir+"/ca.crt", a.CA) + write(pkiDir+"/agent.crt", a.Certificate) + write(pkiDir+"/agent.key", a.Key) + write("/var/lib/controller-test/load.env", []byte("TCP_TEST_DESTINATION=198.18.0.1\n")) + command("tc", "qdisc", "replace", "dev", "eth0", "root", "handle", "1:", "prio", "bands", "3", "priomap", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0") + command("tc", "qdisc", "add", "dev", "eth0", "parent", "1:2", "handle", "20:", "netem", "delay", "80ms") + command("tc", "qdisc", "add", "dev", "eth0", "parent", "1:3", "handle", "30:", "netem", "delay", "5ms") + command("tc", "filter", "add", "dev", "eth0", "parent", "1:", "protocol", "ip", "prio", "1", "u32", "match", "ip", "dst", "198.18.0.2/32", "flowid", "1:2") + command("tc", "filter", "add", "dev", "eth0", "parent", "1:", "protocol", "ip", "prio", "2", "u32", "match", "ip", "dst", "198.18.0.0/24", "flowid", "1:3") + systemctl("start", "load-near", "load-far", "xtcp2", "xtcp2-treatment-watchdog") + case "status": + result = localSnapshot() + case "restart-agent": + systemctl("restart", "xtcp2") + agentReady() + case "stop-agent": + systemctl("stop", "xtcp2") + systemctl("is-active", "xtcp2-treatment-watchdog") + case "start-agent": + systemctl("start", "xtcp2") + agentReady() + case "restart-load": + systemctl("restart", "load-near") + case "destination": + require(validSoakDestination(a.Destination), "invalid fixture destination") + write("/var/lib/controller-test/load.env", []byte("TCP_TEST_DESTINATION="+a.Destination+"\n")) + systemctl("restart", "load-near") + case "hup": + agentReady() + systemctl("kill", "-s", "HUP", "xtcp2") + case "suspend", "resume": + command("xtcp2-treatmentctl", "-mode="+a.Name, "-reason=integration-local-"+a.Name) + case "delay": + require(a.Seconds == 5 || a.Seconds == 180, "invalid fixture delay") + command("tc", "qdisc", "change", "dev", "eth0", "parent", "1:3", "handle", "30:", "netem", "delay", fmt.Sprintf("%dms", a.Seconds)) + case "expire": + command("date", "-s", fmt.Sprintf("@%d", time.Now().Unix()+3700)) + case "restore-clock": + require(a.Seconds > 0, "invalid clock") + command("date", "-s", fmt.Sprintf("@%d", a.Seconds)) + case "healthy": + result = systemctl("is-active", "xtcp2", "load-near", "load-far", "xtcp2-treatment-watchdog") + case "evidence": + s := localSnapshot() + s.Journal = command("journalctl", "-b", "--no-pager", "-n", "500", "-u", "xtcp2", "-u", "xtcp2-treatment-watchdog", "-u", "controller-test-agent") + s.Rules = command("ip", "-j", "rule") + s.Qdisc = command("tc", "-s", "qdisc", "show") + result = s + default: + panic("unknown fixture action") + } + w.Header().Set("Content-Type", "application/json") + check(json.NewEncoder(w).Encode(result)) + }) + server := &http.Server{Addr: address, Handler: mux, ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 10 * time.Second, WriteTimeout: 120 * time.Second} + log.Fatal(server.ListenAndServe()) +} +func remote(host string, a action, result any) { + index := 0 + for i, h := range hosts { + if h == host { + index = i + 11 + } + } + require(index != 0, "unknown agent %q", host) + body := must(json.Marshal(a)) + response := must(client.Post(fmt.Sprintf("http://192.168.1.%d:9009/action", index), "application/json", bytes.NewReader(body))) + defer response.Body.Close() + data := must(io.ReadAll(io.LimitReader(response.Body, 2<<20))) + require(response.StatusCode == 200, "%s %s: %s", host, a.Name, data) + if result != nil { + check(json.Unmarshal(data, result)) + } +} +func act(host, name string) { remote(host, action{Name: name}, nil) } +func status(host string) snapshot { var s snapshot; remote(host, action{Name: "status"}, &s); return s } +func hasRoute(host string) bool { return strings.Contains(status(host).Routes, "198.18.0.1") } +func routeCount() int { + n := 0 + for _, h := range hosts { + if hasRoute(h) { + n++ + } + } + return n +} +func eventually(label string, timeout time.Duration, fn func() bool) { + log.Print(label) + deadline := time.Now().Add(timeout) + var last any + for time.Now().Before(deadline) { + ok := func() (ok bool) { + defer func() { + if p := recover(); p != nil { + last = p + } + }() + return fn() + }() + if ok { + return + } + time.Sleep(3 * time.Second) + } + panic(fmt.Sprintf("%s: timed out after %s (last error: %v)", label, timeout, last)) +} +func sql(query string) string { return command("clickhouse-client", "--query", query) } +func count(query string) int { return must(strconv.Atoi(strings.TrimSpace(sql(query)))) } +func pass(stage string) { fmt.Println("TCP_CONTROLLER_" + stage + "_PASS") } +func rpcContext() (context.Context, context.CancelFunc) { + return context.WithTimeout(context.Background(), 20*time.Second) +} +func inspect(op pb.OperatorServiceClient, id string) *pb.Experiment { + ctx, cancel := rpcContext() + defer cancel() + response := must(op.Inspect(ctx, &pb.InspectRequest{ExperimentId: id})) + require(len(response.Experiments) == 1, "expected one experiment") + return response.Experiments[0] +} +func review(op pb.OperatorServiceClient, e *pb.Experiment, id string, evidence *pb.Evidence) *pb.Experiment { + ctx, cancel := rpcContext() + defer cancel() + return must(op.Review(ctx, &pb.ReviewRequest{ExperimentId: e.Id, ExpectedRevision: e.Revision, Approve: true, Reason: "Integration: reviewed measured evidence and destination scope", RequestId: id, Evidence: evidence})).Experiment +} +func propose(op pb.OperatorServiceClient, id string) *pb.Experiment { + ch := controller.ClickHouse{URL: "http://127.0.0.1:8123", Table: "xtcp.xtcp_flat_records_v2"} + var baseline *pb.Evidence + eventually("fresh six-host baseline", 3*time.Minute, func() bool { + end := time.Now().Add(-20 * time.Second) + ctx, cancel := rpcContext() + defer cancel() + baseline = must(ch.Evidence(ctx, "test-dc", "198.18.0.1/32", id, hosts, end.Add(-60*time.Second), end)) + require(len(baseline.Hosts) == 6, "baseline needs six hosts: %v", baseline.Hosts) + for _, h := range baseline.Hosts { + require(h.CollectionHealthy && h.Sockets >= 8 && h.BytesAcked >= 4096, "baseline not ready: %v", h) + } + return true + }) + e := &pb.Experiment{Id: id, Location: "test-dc", Hypothesis: "Integration: review CUBIC on measured low-RTT destination", CohortOrder: hosts, Baseline: baseline, + Treatment: &pb.Treatment{Id: "cubic-near", Revision: 1, Destination: "198.18.0.1/32", Settings: nearbySettings()}, + Policy: &pb.ExperimentPolicy{MinimumObservation: durationpb.New(20 * time.Second), MaximumPhase: durationpb.New(15 * time.Minute), MaximumEvidenceAge: durationpb.New(2 * time.Minute), MinimumSocketsPerHost: 8, MinimumBytesPerHost: 4096, MinimumTreatedHosts: 4, MinimumImprovement: 1, MaximumRetransmissionIncrease: .1, MaximumRttIncrease: 1}, + } + ctx, cancel := rpcContext() + defer cancel() + e = must(op.Propose(ctx, &pb.ProposeRequest{Experiment: e, RequestId: id + "-propose"})).Experiment + require(len(e.Phases) == 0 && routeCount() == 0, "unapproved proposal applied") + return review(op, e, id+"-initial", nil) +} +func control(op pb.OperatorServiceClient, id string, a pb.ControlAction) { + ctx, cancel := rpcContext() + defer cancel() + must(op.Control(ctx, &pb.ControlRequest{RequestId: id, Action: a, Reason: "Integration withdrawal comparison", Fleet: true})) +} +func evidence() { + // A failed assertion must still leave useful diagnostics, without replacing + // the original failure if a crashed guest cannot answer. + attempt := func(name string, fn func() []byte) { + defer func() { + if p := recover(); p != nil { + log.Printf("evidence %s: %v", name, p) + } + }() + write(evidenceDir+"/"+name, fn()) + } + for _, h := range hosts { + attempt(h+".json", func() []byte { + var s snapshot + remote(h, action{Name: "evidence"}, &s) + return must(json.MarshalIndent(s, "", " ")) + }) + } + attempt("controller.log", func() []byte { return []byte(command("journalctl", "-b", "--no-pager", "-n", "1500")) }) + attempt("controller.sql", func() []byte { return []byte(command("pg_dump", "-h", "/run/postgresql", "-U", "root", "xtcp")) }) + attempt("telemetry.jsonl", func() []byte { + return []byte(sql("SELECT * FROM xtcp.xtcp_flat_records_v2 ORDER BY timestamp_ns DESC LIMIT 2000 FORMAT JSONEachRow")) + }) +} +func runController(soak bool) { + defer evidence() + eventually("ClickHouse ready", 3*time.Minute, func() bool { return strings.TrimSpace(sql("SELECT 1")) == "1" }) + sql("CREATE DATABASE xtcp") + cmd := exec.Command("clickhouse-client", "--multiquery") + cmd.Stdin = bytes.NewReader(read("/etc/xtcp-test/schema.sql")) + out, err := cmd.CombinedOutput() + require(err == nil, "schema: %v: %s", err, out) + sql("CREATE TABLE xtcp.ingest ENGINE=Null AS SELECT toInt64(0) AS timestamp_ns, * EXCEPT(timestamp_ns) FROM xtcp.xtcp_flat_records_v2 WHERE 0") + sql("CREATE MATERIALIZED VIEW xtcp.ingest_mv TO xtcp.xtcp_flat_records_v2 AS SELECT fromUnixTimestamp64Nano(timestamp_ns) AS timestamp_ns, * EXCEPT(timestamp_ns) FROM xtcp.ingest") + systemctl("start", "xtcp-controller") + for _, h := range hosts { + eventually(h+" fixture ready", 3*time.Minute, func() bool { act(h, "ready"); return true }) + remote(h, action{Name: "setup", CA: read(pkiDir + "/ca.crt"), Certificate: read(pkiDir + "/" + h + ".crt"), Key: read(pkiDir + "/" + h + ".key")}, nil) + } + tls := must(controller.TLSConfig(pkiDir+"/operator.crt", pkiDir+"/operator.key", pkiDir+"/ca.crt", "controller", false)) + conn := must(grpc.NewClient("127.0.0.1:9443", grpc.WithTransportCredentials(credentials.NewTLS(tls)))) + defer conn.Close() + op := pb.NewOperatorServiceClient(conn) + if soak { + runSoak(op) + return + } + eventually("both latency bands in real socket telemetry", 3*time.Minute, func() bool { + rows := strings.Split(strings.TrimSpace(sql("SELECT hex(inet_diag_msg_socket_destination), uniqExact(agent_id), quantileExact(0.5)(tcp_info_rtt)/1000 FROM xtcp.xtcp_flat_records_v2 WHERE timestamp_ns>now()-INTERVAL 30 SECOND AND inet_diag_msg_state=1 AND inet_diag_msg_socket_destination IN (unhex('C6120001000000000000000000000000'),unhex('C6120002000000000000000000000000')) GROUP BY inet_diag_msg_socket_destination ORDER BY inet_diag_msg_socket_destination FORMAT TabSeparated")), "\n") + require(len(rows) == 2, "expected two RTT bands: %q", rows) + a, b := strings.Fields(rows[0]), strings.Fields(rows[1]) + require(len(a) == 3 && len(b) == 3 && a[1] == "6" && b[1] == "6", "expected six hosts per RTT band: %q", rows) + near, far := must(strconv.ParseFloat(a[2], 64)), must(strconv.ParseFloat(b[2], 64)) + require(far > near+35, "RTT bands not separated: %q", rows) + write(evidenceDir+"/rtt-bands.tsv", []byte(strings.Join(rows, "\n"))) + return true + }) + pass("RTT") + mainRoutes := map[string]string{} + for _, h := range hosts { + mainRoutes[h] = status(h).Main + } + e := propose(op, "netem-canary") + canary := e.CohortOrder[0] + eventually("single assigned CUBIC route", 2*time.Minute, func() bool { + return hasRoute(canary) && routeCount() == 1 && strings.Contains(status(canary).Routes, "cubic") + }) + assertNearbyRoute(canary, "198.18.0.1") + eventually("existing sockets retain Reno", time.Minute, func() bool { + return count(fmt.Sprintf("SELECT count() FROM xtcp.xtcp_flat_records_v2 WHERE agent_id='%s' AND treatment_assignment_id='netem-canary' AND inet_diag_cong='reno' AND NOT treatment_algorithm_exposed", canary)) > 0 + }) + act(canary, "restart-load") + eventually("new sockets expose actual CUBIC", time.Minute, func() bool { + return count(fmt.Sprintf("SELECT count() FROM xtcp.xtcp_flat_records_v2 WHERE agent_id='%s' AND treatment_assignment_id='netem-canary' AND inet_diag_cong='cubic' AND treatment_algorithm_exposed", canary)) >= 8 + }) + pass("CANARY") + act(canary, "restart-agent") + eventually("journal replay", time.Minute, func() bool { s := status(canary); return hasRoute(canary) && s.State != "" && s.Projection != "" }) + assertNearbyRoute(canary, "198.18.0.1") + systemctl("restart", "xtcp-controller") + eventually("controller state survived restart", time.Minute, func() bool { return inspect(op, e.Id).Revision == e.Revision }) + pass("PERSIST") + act(canary, "suspend") + require(!hasRoute(canary), "local hold retained route") + act(canary, "restart-agent") + control(op, "cannot-clear-local", pb.ControlAction_CONTROL_ACTION_RESUME) + act(canary, "hup") + time.Sleep(5 * time.Second) + s := status(canary) + state := new(pb.LocalState) + check(prototext.Unmarshal([]byte(s.State), state)) + require(state.LocallySuspended && !hasRoute(canary), "remote resume cleared local hold") + act(canary, "resume") + eventually("local resume", time.Minute, func() bool { return hasRoute(canary) }) + control(op, "fleet-hold", pb.ControlAction_CONTROL_ACTION_SUSPEND) + eventually("fleet withdrawal", time.Minute, func() bool { return routeCount() == 0 }) + control(op, "fleet-resume", pb.ControlAction_CONTROL_ACTION_RESUME) + eventually("fleet resume", time.Minute, func() bool { return hasRoute(canary) }) + assertNearbyRoute(canary, "198.18.0.1") + act(canary, "restart-load") + pass("HOLD") + var analysis *pb.AnalyzeResponse + eventually("measured first-canary safety", 4*time.Minute, func() bool { + e = inspect(op, e.Id) + ctx, cancel := rpcContext() + defer cancel() + analysis = must(op.Analyze(ctx, &pb.AnalyzeRequest{ExperimentId: e.Id, ExpectedRevision: e.Revision})) + log.Printf("analysis: %s", analysis.Outcome) + return analysis.Outcome == "safety_only" + }) + eventually("approve second host with current check-ins", time.Minute, func() bool { e = review(op, inspect(op, e.Id), "approve-second", analysis.Evidence); return true }) + require(e.Phases[len(e.Phases)-1].TargetHosts == 2, "wrong phase target") + second := e.CohortOrder[1] + eventually("exactly two treated hosts", time.Minute, func() bool { return routeCount() == 2 && hasRoute(second) }) + assertNearbyRoute(second, "198.18.0.1") + remote(second, action{Name: "delay", Seconds: 180}, nil) + act(second, "restart-load") + eventually("automatic rollback on measured netem harm", 5*time.Minute, func() bool { return inspect(op, e.Id).Status == pb.ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK }) + eventually("all overrides withdrawn", time.Minute, func() bool { return routeCount() == 0 }) + e = inspect(op, e.Id) + require(e.Phases[len(e.Phases)-1].Outcome == "harmful", "wrong rollback reason") + pass("ROLLBACK") + remote(second, action{Name: "delay", Seconds: 5}, nil) + for _, h := range hosts { + act(h, "restart-load") + } + time.Sleep(85 * time.Second) + e = propose(op, "watchdog-outage") + victim := e.CohortOrder[0] + eventually("outage trial installed", time.Minute, func() bool { return hasRoute(victim) }) + systemctl("stop", "xtcp-controller") + act(victim, "stop-agent") + act(victim, "expire") + eventually("independent watchdog expires unchanged one-hour lease", 90*time.Second, func() bool { + return !hasRoute(victim) && strings.Contains(status(victim).State, "APPLY_STATUS_EXPIRED") + }) + act(victim, "start-agent") + time.Sleep(5 * time.Second) + require(!hasRoute(victim), "restart resurrected expired trial") + remote(victim, action{Name: "restore-clock", Seconds: time.Now().Unix()}, nil) + systemctl("start", "xtcp-controller") + act(victim, "hup") + time.Sleep(5 * time.Second) + require(!hasRoute(victim), "reconnect resurrected expired generation") + pass("WATCHDOG") + for _, h := range hosts { + s := status(h) + require(s.Main == mainRoutes[h], "%s main routes changed", h) + require(s.DefaultCC == "reno", "%s default CC changed", h) + act(h, "healthy") + } + fmt.Println("TCP_CONTROLLER_PASS") +} +func main() { + role := flag.String("role", "", "controller or agent") + listen := flag.String("listen", "", "isolated guest fixture address") + soak := flag.Bool("soak", false, "run repeated RTT/discovery/rollback cycles from the shared soak-config.json") + flag.Parse() + if _, err := os.Stat("/etc/xtcp-controller-integration"); err != nil { + log.Fatal("requires disposable integration VM marker") + } + if *role == "agent" { + serveAgent(*listen) + return + } + if *role != "controller" { + log.Fatal("invalid role") + } + defer func() { + if p := recover(); p != nil { + fmt.Printf("TCP_CONTROLLER_FAIL: %v\n", p) + os.Exit(1) + } + }() + runController(*soak) +} diff --git a/tools/tcp-controller-integration/nearby.go b/tools/tcp-controller-integration/nearby.go new file mode 100644 index 000000000..6e261ce43 --- /dev/null +++ b/tools/tcp-controller-integration/nearby.go @@ -0,0 +1,43 @@ +package main + +import ( + "encoding/json" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "google.golang.org/protobuf/proto" +) + +func nearbySettings() *pb.TCPSettings { + return &pb.TCPSettings{ + CongestionControl: proto.String("cubic"), RtoMinMs: proto.Uint32(5), + QuickAck: proto.Bool(true), InitialCongestionWindow: proto.Uint32(100), + InitialReceiveWindow: proto.Uint32(100), + } +} + +// Check the kernel's installed metrics, not just the controller's desired state. +func assertNearbyRoute(host, destination string) { + raw := status(host).Routes + var routes []struct { + Destination string `json:"dst"` + Metrics []struct { + Congestion string `json:"congestion"` + RtoMin uint32 `json:"rto_min"` + QuickAck uint32 `json:"quickack"` + InitCwnd uint32 `json:"initcwnd"` + InitRwnd uint32 `json:"initrwnd"` + } `json:"metrics"` + } + check(json.Unmarshal([]byte(raw), &routes)) + for _, r := range routes { + if r.Destination != destination && r.Destination != destination+"/32" { + continue + } + require(len(r.Metrics) == 1, "%s missing route metrics: %s", host, raw) + m := r.Metrics[0] + require(m.Congestion == "cubic" && m.RtoMin == 5 && m.QuickAck == 1 && m.InitCwnd == 100 && m.InitRwnd == 100, "%s incorrect nearby route: %s", host, raw) + write(evidenceDir+"/"+host+"-nearby-route.json", []byte(raw)) + return + } + panic("missing nearby route on " + host) +} diff --git a/tools/tcp-controller-integration/soak.go b/tools/tcp-controller-integration/soak.go new file mode 100644 index 000000000..149a82482 --- /dev/null +++ b/tools/tcp-controller-integration/soak.go @@ -0,0 +1,324 @@ +package main + +import ( + "encoding/csv" + "encoding/json" + "fmt" + "log" + "os" + "strings" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" +) + +type soakConfig struct { + Cycles int `json:"cycles"` + IntervalSeconds int `json:"interval_seconds"` +} + +func (c soakConfig) validate() error { + if c.Cycles < 2 || c.Cycles > 6 || c.IntervalSeconds < 180 || c.IntervalSeconds > 300 { + return fmt.Errorf("soak requires 2..6 cycles and 180..300 seconds per RTT phase") + } + return nil +} +func validSoakDestination(s string) bool { + for i := 101; i <= 106; i++ { + if s == fmt.Sprintf("198.18.0.%d", i) { + return true + } + } + return false +} + +type soakRun struct { + op pb.OperatorServiceClient + config soakConfig + started time.Time + cycle int + phase, destination string + delay int64 + events *os.File + samples *csv.Writer + sampleFile *os.File +} + +func (s *soakRun) event(name string, e *pb.Experiment) { + row := map[string]any{"at": time.Now().UTC(), "elapsed_seconds": time.Since(s.started).Seconds(), "cycle": s.cycle, "phase": s.phase, "destination": s.destination, "delay_ms": s.delay, "event": name} + if e != nil { + row["experiment"] = json.RawMessage(must(protojson.Marshal(e))) + } + check(json.NewEncoder(s.events).Encode(row)) + check(s.events.Sync()) + log.Printf("SOAK cycle=%d phase=%s destination=%s delay=%dms event=%s", s.cycle, s.phase, s.destination, s.delay, name) +} +func (s *soakRun) experiments() []*pb.Experiment { + ctx, cancel := rpcContext() + defer cancel() + return must(s.op.Inspect(ctx, &pb.InspectRequest{})).Experiments +} +func (s *soakRun) current() *pb.Experiment { + var found *pb.Experiment + for _, e := range s.experiments() { + if e.Treatment.Destination == s.destination+"/32" { + require(found == nil, "duplicate proposal for %s", s.destination) + found = e + } + } + return found +} +func destinationRoutes(destination string) int { + n := 0 + for _, h := range hosts { + var rows []struct { + Destination string `json:"dst"` + } + check(json.Unmarshal([]byte(status(h).Routes), &rows)) + for _, r := range rows { + if r.Destination == destination || r.Destination == destination+"/32" { + n++ + } + } + } + return n +} +func ownedRoutes() int { + n := 0 + for _, h := range hosts { + var routes []json.RawMessage + check(json.Unmarshal([]byte(status(h).Routes), &routes)) + n += len(routes) + } + return n +} +func (s *soakRun) sample() { + // Restrict to the active destination. Read actual SRTT, not the lifetime + // minimum used by discovery. Keep each host's values instead of pooling it. + query := fmt.Sprintf(`SELECT agent_id, quantileExact(0.5)(tcp_info_rtt)/1000, + countIf(inet_diag_cong='reno'), countIf(inet_diag_cong='cubic'), countIf(treatment_algorithm_exposed) + FROM xtcp.xtcp_flat_records_v2 WHERE timestamp_ns>now()-INTERVAL 20 SECOND + AND inet_diag_msg_state=1 AND hex(inet_diag_msg_socket_destination)=concat(hex(IPv4StringToNum('%s')),'000000000000000000000000') + GROUP BY agent_id ORDER BY agent_id FORMAT TabSeparated`, s.destination) + raw := strings.TrimSpace(sql(query)) + e := s.current() + id, st := "", "none" + if e != nil { + id = e.Id + st = e.Status.String() + } + if raw != "" { + for _, line := range strings.Split(raw, "\n") { + fields := strings.Split(line, "\t") + require(len(fields) == 5, "invalid RTT sample: %q", line) + row := []string{time.Now().UTC().Format(time.RFC3339Nano), fmt.Sprint(s.cycle), s.phase, s.destination, fmt.Sprint(s.delay), id, st} + check(s.samples.Write(append(row, fields...))) + } + } + s.samples.Flush() + check(s.samples.Error()) + check(s.sampleFile.Sync()) +} +func (s *soakRun) setPhase(phase string, delay int64, newDestination bool) time.Time { + s.phase, s.delay = phase, delay + for _, h := range hosts { + remote(h, action{Name: "delay", Seconds: delay}, nil) + } + if newDestination { + for _, h := range hosts { + remote(h, action{Name: "destination", Destination: s.destination}, nil) + } + } + s.event("netem_changed", nil) + return time.Now().Add(time.Duration(s.config.IntervalSeconds) * time.Second) +} +func (s *soakRun) until(end time.Time, inspectState func()) { + for time.Now().Before(end) { + inspectState() + s.sample() + remaining := time.Until(end) + if remaining > 15*time.Second { + remaining = 15 * time.Second + } + if remaining > 0 { + time.Sleep(remaining) + } + } + inspectState() + s.sample() +} +func (s *soakRun) assertMeasuredRTT() { + // Last twenty seconds must contain all six agents at the injected RTT. + query := fmt.Sprintf(`SELECT uniqExact(agent_id), quantileExact(0.5)(tcp_info_rtt)/1000 + FROM xtcp.xtcp_flat_records_v2 WHERE timestamp_ns>now()-INTERVAL 20 SECOND + AND inet_diag_msg_state=1 AND hex(inet_diag_msg_socket_destination)=concat(hex(IPv4StringToNum('%s')),'000000000000000000000000') FORMAT TabSeparated`, s.destination) + fields := strings.Fields(sql(query)) + require(len(fields) == 2 && fields[0] == "6", "missing RTT population: %v", fields) + var rtt float64 + _, err := fmt.Sscan(fields[1], &rtt) + check(err) + require(rtt >= float64(s.delay)*.8 && rtt <= float64(s.delay)*1.5+3, "RTT %.3fms does not match netem %dms", rtt, s.delay) +} + +// A low netem delay does not guarantee this aggressive profile is beneficial. +// Accept an early safety withdrawal only with valid, exposed, harmful evidence. +func validateSoakRollback(e *pb.Experiment, injectedLatency bool) error { + if e == nil || e.Status != pb.ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK || len(e.Phases) != 1 || e.Phases[0].Outcome != "harmful" || len(e.CohortOrder) == 0 || e.Policy == nil || e.Baseline == nil || e.LatestEvidence == nil { + return fmt.Errorf("rollback is missing its harmful phase or evidence") + } + if err := treatment.ValidateEvidence(e.Baseline); err != nil { + return err + } + if err := treatment.ValidateEvidence(e.LatestEvidence); err != nil { + return err + } + if err := treatment.ValidatePolicy(e.Policy); err != nil { + return err + } + _, harmful := treatment.HealthyEvidence(e, e.LatestEvidence, e.LatestEvidence.AnalyzedAt.AsTime()) + if !harmful { + return fmt.Errorf("rollback evidence does not cross a safety guard") + } + for _, h := range e.LatestEvidence.Hosts { + if h.AgentId == e.CohortOrder[0] && h.ExposedSockets > 0 { + if injectedLatency && h.RttP95Ms < 90 { + return fmt.Errorf("rollback evidence did not observe injected latency on the canary") + } + return nil + } + } + return fmt.Errorf("rollback evidence has no exposed canary sockets") +} + +func injectedLatencyRollback(e *pb.Experiment, changedAt time.Time) bool { + return validateSoakRollback(e, true) == nil && e.LatestEvidence.WindowEnd.AsTime().After(changedAt) +} + +func runSoak(op pb.OperatorServiceClient) { + var config soakConfig + check(json.Unmarshal(read(evidenceDir+"/soak-config.json"), &config)) + check(config.validate()) + events := must(os.OpenFile(evidenceDir+"/soak-events.jsonl", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)) + defer events.Close() + samples := must(os.OpenFile(evidenceDir+"/soak-rtt.csv", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)) + defer samples.Close() + s := &soakRun{op: op, config: config, started: time.Now(), events: events, sampleFile: samples, samples: csv.NewWriter(samples)} + check(s.samples.Write([]string{"at", "cycle", "phase", "destination", "netem_ms", "experiment", "status", "agent", "median_rtt_ms", "reno_samples", "cubic_samples", "exposed_samples"})) + mainRoutes := map[string]string{} + for _, h := range hosts { + mainRoutes[h] = status(h).Main + } + latencyRollbacks, lowRTTRollbacks := 0, 0 + for cycle := 1; cycle <= config.Cycles; cycle++ { + s.cycle = cycle + s.destination = fmt.Sprintf("198.18.0.%d", 100+cycle) + end := s.setPhase("high_no_candidate", 180, true) + s.until(end, func() { + require(s.current() == nil, "high-RTT destination incorrectly proposed") + require(ownedRoutes() == 0, "unapproved route installed") + }) + s.assertMeasuredRTT() + s.event("high_rtt_filtered", nil) + // New connections reset TCP_INFO's lifetime minimum RTT. The discovery + // window and 20-second ingest watermark must both age out the high phase. + end = s.setPhase("low_discovery", 5, true) + var approved *pb.Experiment + rolledBack := false + s.until(end, func() { + e := s.current() + if e == nil { + return + } + if approved == nil { + require(e.Status == pb.ExperimentStatus_EXPERIMENT_STATUS_PROPOSED && len(e.Phases) == 0 && ownedRoutes() == 0, "discovery bypassed operator approval") + require(e.Treatment.Id == "soak-nearby-cubic" && strings.HasPrefix(e.Hypothesis, "Template soak-nearby-cubic:"), "candidate was not created by discovery") + require(proto.Equal(e.Treatment.Settings, nearbySettings()), "discovery lost nearby route settings: %v", e.Treatment.Settings) + require(e.GetPolicy().GetMaximumRttIncrease() == 1, "soak discovery did not use the fixture RTT tolerance") + s.event("controller_proposed", e) + approved = review(op, e, fmt.Sprintf("soak-review-%d", cycle), nil) + s.event("test_operator_approved", approved) + eventually("soak canary route installed", time.Minute, func() bool { return destinationRoutes(s.destination) == 1 }) + assertNearbyRoute(approved.CohortOrder[0], s.destination) + act(approved.CohortOrder[0], "restart-load") + } + e = inspect(op, approved.Id) + if e.Status == pb.ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK { + if !rolledBack { + s.event("low_rtt_rollback_observed", e) + check(validateSoakRollback(e, false)) + eventually("early safety overrides withdrawn", time.Minute, func() bool { return ownedRoutes() == 0 }) + s.event("low_rtt_safety_rollback", e) + lowRTTRollbacks++ + rolledBack = true + } + require(ownedRoutes() == 0, "early safety withdrawal was reversed") + return + } + require(!rolledBack, "early rolled-back experiment was revived") + if e.Status != pb.ExperimentStatus_EXPERIMENT_STATUS_RUNNING || len(e.Phases) != 1 { + s.event("unexpected_low_rtt_state", e) + panic(fmt.Sprintf("low-RTT phase: status=%s outcome=%s; full evidence in soak-events.jsonl", e.Status, e.LatestOutcome)) + } + require(destinationRoutes(s.destination) == 1 && ownedRoutes() == 1, "unapproved expansion") + }) + require(approved != nil, "no controller proposal after low RTT") + s.assertMeasuredRTT() + if !rolledBack { + require(count(fmt.Sprintf("SELECT count() FROM xtcp.xtcp_flat_records_v2 WHERE timestamp_ns>now()-INTERVAL 20 SECOND AND treatment_assignment_id='%s' AND inet_diag_cong='cubic' AND treatment_algorithm_exposed", approved.Id)) >= 8, "missing actual CUBIC exposure") + s.event("canary_exposed", inspect(op, approved.Id)) + } + // Keep existing connections so maintenance sees the RTT regression on + // the treated sockets; the harness never requests a rollback. + highStarted := time.Now() + end = s.setPhase("high_automatic_rollback", 180, false) + s.until(end, func() { + e := inspect(op, approved.Id) + require(len(e.Phases) == 1, "controller promoted without review") + if e.Status == pb.ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK && !rolledBack { + s.event("controller_rollback_observed", e) + check(validateSoakRollback(e, false)) + eventually("soak overrides withdrawn", time.Minute, func() bool { return ownedRoutes() == 0 }) + // Maintenance may deliver a low-RTT safety decision after the + // phase boundary. Classify its evidence, not its arrival time. + if injectedLatencyRollback(e, highStarted) { + s.event("controller_automatic_rollback", e) + latencyRollbacks++ + } else { + s.event("low_rtt_safety_rollback", e) + lowRTTRollbacks++ + } + rolledBack = true + } + if rolledBack { + require(e.Status == pb.ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK, "rolled-back experiment was revived") + require(ownedRoutes() == 0, "rolled-back treatment restored") + } + }) + require(rolledBack, "controller did not roll back the impaired trial") + s.assertMeasuredRTT() + pass(fmt.Sprintf("SOAK_CYCLE_%d", cycle)) + } + require(latencyRollbacks > 0, "all %d trials withdrew at low RTT; injected-latency rollback was not exercised (see soak-events.jsonl)", lowRTTRollbacks) + // Restore a previously failed destination. A fresh low-RTT window must not + // bypass the unchanged 24-hour cooldown or revive the old assignment. + s.destination = "198.18.0.101" + end := s.setPhase("low_cooldown", 5, true) + s.until(end, func() { + e := s.current() + require(e != nil && e.Status == pb.ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK, "cooldown state changed") + require(len(s.experiments()) == config.Cycles && ownedRoutes() == 0, "cooldown allowed a new action") + }) + s.assertMeasuredRTT() + s.event("cooldown_verified", s.current()) + pass("SOAK_COOLDOWN") + for _, h := range hosts { + st := status(h) + require(st.Main == mainRoutes[h] && st.DefaultCC == "reno", "%s baseline routing changed", h) + act(h, "healthy") + } + write(evidenceDir+"/soak-summary.json", must(json.MarshalIndent(map[string]any{"cycles": config.Cycles, "interval_seconds": config.IntervalSeconds, "elapsed_seconds": time.Since(s.started).Seconds(), "proposals": len(s.experiments()), "automatic_rollbacks": latencyRollbacks + lowRTTRollbacks, "injected_latency_rollbacks": latencyRollbacks, "low_rtt_safety_rollbacks": lowRTTRollbacks, "cooldown_verified": true}, "", " "))) + pass("SOAK") +} diff --git a/tools/tcp-controller-integration/soak_test.go b/tools/tcp-controller-integration/soak_test.go new file mode 100644 index 000000000..39b2cfa2c --- /dev/null +++ b/tools/tcp-controller-integration/soak_test.go @@ -0,0 +1,76 @@ +package main + +import ( + "strings" + "testing" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func TestSoakRollbackEvidence(t *testing.T) { + now := time.Now() + baseline := &pb.Evidence{Id: "baseline", AnalyzedAt: timestamppb.New(now.Add(-3 * time.Minute)), WindowStart: timestamppb.New(now.Add(-4 * time.Minute)), WindowEnd: timestamppb.New(now.Add(-3 * time.Minute)), QueryVersion: "test", ArtifactSha256: strings.Repeat("a", 64), Hosts: []*pb.HostEvidence{{AgentId: "canary", Sockets: 32, BytesAcked: 1 << 25, GoodputBps: 4e6, RttP95Ms: 5.305, CollectionHealthy: true}}} + latest := proto.Clone(baseline).(*pb.Evidence) + latest.Id = "latest" + latest.AnalyzedAt = timestamppb.New(now) + latest.WindowStart = timestamppb.New(now.Add(-time.Minute)) + latest.WindowEnd = timestamppb.New(now.Add(-20 * time.Second)) + latest.Hosts[0].ExposedSockets = 32 + policy := treatment.DefaultPolicy() + policy.MaximumRttIncrease = 1 + fixture := &pb.Experiment{Status: pb.ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK, CohortOrder: []string{"canary"}, Policy: policy, Baseline: baseline, LatestEvidence: latest, Phases: []*pb.Phase{{Outcome: "harmful"}}} + for _, tc := range []struct { + name string + injected, valid bool + change func(*pb.Experiment) + }{ + {"observed-early-loss", false, true, func(e *pb.Experiment) { e.LatestEvidence.Hosts[0].RetransmissionRatio = .010227199171467409 }}, + {"early-loss-is-not-latency-coverage", true, false, func(e *pb.Experiment) { e.LatestEvidence.Hosts[0].RetransmissionRatio = .010227199171467409 }}, + {"injected-latency", true, true, func(e *pb.Experiment) { e.LatestEvidence.Hosts[0].RttP95Ms = 180 }}, + {"healthy-evidence", false, false, func(e *pb.Experiment) {}}, + {"unexposed", false, false, func(e *pb.Experiment) { + e.LatestEvidence.Hosts[0].RttP95Ms = 180 + e.LatestEvidence.Hosts[0].ExposedSockets = 0 + }}, + {"missing-evidence", false, false, func(e *pb.Experiment) { e.LatestEvidence = nil }}, + {"wrong-outcome", false, false, func(e *pb.Experiment) { e.Phases[0].Outcome = "expired" }}, + } { + t.Run(tc.name, func(t *testing.T) { + e := proto.Clone(fixture).(*pb.Experiment) + tc.change(e) + if err := validateSoakRollback(e, tc.injected); (err == nil) != tc.valid { + t.Fatalf("valid=%t err=%v", tc.valid, err) + } + if tc.name == "injected-latency" { + if !injectedLatencyRollback(e, now.Add(-time.Minute)) { + t.Fatal("lost injected latency coverage") + } + if injectedLatencyRollback(e, now.Add(-5*time.Second)) { + t.Fatal("pre-change evidence counted as injected latency") + } + } + }) + } +} + +func TestSoakConfigBounds(t *testing.T) { + for _, c := range []soakConfig{{3, 180}, {2, 180}, {6, 300}} { + if err := c.validate(); err != nil { + t.Fatal(err) + } + } + for _, c := range []soakConfig{{0, 180}, {1, 180}, {7, 180}, {3, 0}, {3, 179}, {3, 301}} { + if c.validate() == nil { + t.Fatalf("accepted unsafe/vacuous run: %+v", c) + } + } + for _, destination := range []string{"198.18.0.1", "192.168.1.254", "198.18.0.107", "198.18.0.101\nOTHER=value"} { + if validSoakDestination(destination) { + t.Fatalf("accepted %q", destination) + } + } +} From 7dc118ca3ac983d2025659d520adbeb605019e4d Mon Sep 17 00:00:00 2001 From: "randomizedcoder dave.seddon.ca@gmail.com" Date: Sun, 25 Oct 2026 16:41:34 -0700 Subject: [PATCH 02/27] refactor(treatment): extract identity package and enforce import layering The agent imported pkg/treatment/controller for TLSConfig and CertificateIdentity, which pulled database/sql, lib/pq, the ClickHouse client and both gRPC server implementations into every xtcp2 build flavor. Move SPIFFE identity parsing, peer resolution and TLS config into pkg/treatment/identity, a leaf package both sides can share, and repoint the agent, controller handlers, status page, both binaries and the integration tool. Add pkg/treatment/boundaries_test.go, a parser-walk layering test in the style of internal/goip/imports_test.go. It asserts that cmd/xtcp2 and pkg/treatment/agent never reach the controller, a SQL driver or ClickHouse, that the controller never imports the agent, and records two rows to tighten once the contract package stops importing the proto and the watchdog builds on the reconcile package. The identity table test moves into the new package and gains boundary and corner rows (128-byte IDs, escaped separators, scheme casing). Co-Authored-By: Claude Fable 5.1 --- cmd/xtcp2-controller/main.go | 3 +- cmd/xtcp2-controllerctl/main.go | 4 +- pkg/treatment/agent/agent.go | 8 +- pkg/treatment/boundaries_test.go | 208 ++++++++++++++++++ pkg/treatment/controller/analyze.go | 3 +- pkg/treatment/controller/auth.go | 97 -------- pkg/treatment/controller/contracts_test.go | 38 ---- pkg/treatment/controller/server.go | 13 +- pkg/treatment/controller/server_test.go | 7 +- pkg/treatment/controller/status.go | 5 +- pkg/treatment/identity/identity.go | 107 +++++++++ pkg/treatment/identity/identity_test.go | 152 +++++++++++++ .../auth_test.go => identity/mtls_test.go} | 6 +- pkg/treatment/identity/tls.go | 46 ++++ tools/tcp-controller-integration/main.go | 3 +- 15 files changed, 542 insertions(+), 158 deletions(-) create mode 100644 pkg/treatment/boundaries_test.go delete mode 100644 pkg/treatment/controller/auth.go create mode 100644 pkg/treatment/identity/identity.go create mode 100644 pkg/treatment/identity/identity_test.go rename pkg/treatment/{controller/auth_test.go => identity/mtls_test.go} (98%) create mode 100644 pkg/treatment/identity/tls.go diff --git a/cmd/xtcp2-controller/main.go b/cmd/xtcp2-controller/main.go index d7967bc72..0b626c216 100644 --- a/cmd/xtcp2-controller/main.go +++ b/cmd/xtcp2-controller/main.go @@ -19,6 +19,7 @@ import ( pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" "github.com/randomizedcoder/xtcp2/pkg/treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/controller" + "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" "google.golang.org/grpc" "google.golang.org/grpc/credentials" ) @@ -59,7 +60,7 @@ func run() error { if dsn == "" { return fmt.Errorf("PostgreSQL DSN required") } - tlsConfig, e := controller.TLSConfig(*cert, *key, *ca, "", true) + tlsConfig, e := identity.TLSConfig(*cert, *key, *ca, "", true) if e != nil { return e } diff --git a/cmd/xtcp2-controllerctl/main.go b/cmd/xtcp2-controllerctl/main.go index 7be247fc2..5e1904a87 100644 --- a/cmd/xtcp2-controllerctl/main.go +++ b/cmd/xtcp2-controllerctl/main.go @@ -10,7 +10,7 @@ import ( pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" "github.com/randomizedcoder/xtcp2/pkg/treatment" - "github.com/randomizedcoder/xtcp2/pkg/treatment/controller" + "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/protobuf/encoding/prototext" @@ -32,7 +32,7 @@ func run() error { method := flag.String("method", "inspect", "propose, analyze, review, control, inspect") file := flag.String("file", "", "protobuf-text request file (empty InspectRequest if omitted)") flag.Parse() - tls, e := controller.TLSConfig(*cert, *key, *ca, *name, false) + tls, e := identity.TLSConfig(*cert, *key, *ca, *name, false) if e != nil { return e } diff --git a/pkg/treatment/agent/agent.go b/pkg/treatment/agent/agent.go index 646658dd2..2419df99f 100644 --- a/pkg/treatment/agent/agent.go +++ b/pkg/treatment/agent/agent.go @@ -23,7 +23,7 @@ import ( flat "github.com/randomizedcoder/xtcp2/gen/go/xtcp_flat_record" "github.com/randomizedcoder/xtcp2/internal/goip" "github.com/randomizedcoder/xtcp2/pkg/treatment" - "github.com/randomizedcoder/xtcp2/pkg/treatment/controller" + "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" "golang.org/x/sys/unix" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -157,7 +157,7 @@ func newLocal(c Config, identity func() (string, uint64, error)) (*Agent, error) // resolution is only needed to add management destinations to the exclusions; // deployments should list stable management prefixes for offline reboot use. func New(ctx context.Context, c Config) (*Agent, error) { - tlsConfig, e := controller.TLSConfig(c.Certificate, c.PrivateKey, c.CA, c.ServerName, false) + tlsConfig, e := identity.TLSConfig(c.Certificate, c.PrivateKey, c.CA, c.ServerName, false) if e != nil { return nil, e } @@ -165,11 +165,11 @@ func New(ctx context.Context, c Config) (*Agent, error) { if e != nil { return nil, e } - id, e := controller.CertificateIdentity(cert) + id, e := identity.FromCertificate(cert) if e != nil { return nil, e } - if id.Role != "agent" || id.ID != c.AgentID || id.Location != c.Location { + if id.Role != identity.RoleAgent || id.ID != c.AgentID || id.Location != c.Location { return nil, errors.New("agent config does not match certificate identity") } a, e := NewLocal(c) diff --git a/pkg/treatment/boundaries_test.go b/pkg/treatment/boundaries_test.go new file mode 100644 index 000000000..c0991703f --- /dev/null +++ b/pkg/treatment/boundaries_test.go @@ -0,0 +1,208 @@ +package treatment + +import ( + "go/parser" + "go/token" + "os" + "path/filepath" + "sort" + "strconv" + "strings" + "testing" +) + +// The treatment feature is split by audience, and the split only means +// something if it is enforced. This test walks first-party imports with the +// parser (no toolchain, no module cache, works outside nix exactly like +// internal/goip/imports_test.go) and asserts the layering below. +// +// pkg/treatment contract: IDs, limits, validators [everyone] +// pkg/treatment/identity SPIFFE identity + TLS [agent, controller, tools] +// pkg/treatment/reconcile journal + reconciler + Backend [agent, watchdog] +// pkg/treatment/experiment statistics + state machine [controller] +// pkg/treatment/agent check-in loop + stamping [daemon, agent binary] +// pkg/treatment/controller gRPC handlers, store, evidence [controller binary only] +// +// The daemon (cmd/xtcp2) must never reach the controller, a SQL driver or the +// ClickHouse client: that was the regression that prompted this file. +const ( + modulePath = "github.com/randomizedcoder/xtcp2" + repoRoot = "../.." +) + +// boundaryRule is one root whose transitive first-party closure must not +// contain any forbidden package, and whose third-party edges must not match +// any forbidden substring. +type boundaryRule struct { + description string + root string + forbidden []string // first-party package paths + forbidden3p []string // substrings of third-party import paths + expectAny []string // first-party packages that must be present, so the walk is not vacuous +} + +func treatmentBoundaryRules() []boundaryRule { + tp := func(p string) string { return modulePath + "/" + p } + return []boundaryRule{ + { + description: "positive: the daemon links neither the controller, a SQL driver nor ClickHouse", + root: tp("cmd/xtcp2"), + forbidden: []string{tp("pkg/treatment/controller"), tp("pkg/treatment/experiment")}, + forbidden3p: []string{"database/sql", "lib/pq", "jackc/pgx", "clickhouse"}, + expectAny: []string{tp("pkg/xtcp")}, + }, + { + description: "positive: the agent library stands on identity and the contract, never on the controller", + root: tp("pkg/treatment/agent"), + forbidden: []string{tp("pkg/treatment/controller"), tp("pkg/treatment/experiment")}, + forbidden3p: []string{"database/sql", "lib/pq", "jackc/pgx"}, + expectAny: []string{tp("pkg/treatment/identity"), tp("pkg/treatment")}, + }, + { + // TODO(item 2): add gen/go/xtcp_controller to forbidden once the + // contract package stops importing the proto (domain types land). + description: "negative: the identity package is a leaf below the contract and gRPC credentials", + root: tp("pkg/treatment/identity"), + forbidden: []string{tp("pkg/treatment/controller"), tp("pkg/treatment/agent")}, + forbidden3p: []string{"database/sql"}, + expectAny: []string{tp("pkg/treatment")}, + }, + { + // TODO(item 2): forbid pkg/treatment/identity and google.golang.org/grpc + // once the watchdog builds on pkg/treatment/reconcile instead of agent. + description: "boundary: the watchdog never reaches the controller or a database", + root: tp("cmd/xtcp2-treatmentctl"), + forbidden: []string{tp("pkg/treatment/controller")}, + forbidden3p: []string{"database/sql", "lib/pq", "jackc/pgx"}, + expectAny: []string{tp("pkg/treatment")}, + }, + { + description: "corner: the controller never imports the agent (the dependency points one way)", + root: tp("pkg/treatment/controller"), + forbidden: []string{tp("pkg/treatment/agent"), tp("internal/goip")}, + expectAny: []string{tp("pkg/treatment/identity")}, + }, + } +} + +// go test ./pkg/treatment -run TestTreatmentImportBoundaries +func TestTreatmentImportBoundaries(t *testing.T) { + for _, r := range treatmentBoundaryRules() { + t.Run(r.description, func(t *testing.T) { + closure, thirdParty := firstPartyClosure(t, r.root) + for _, p := range r.forbidden { + if closure[p] { + t.Errorf("%s reaches %s; the chain is: %s", r.root, p, importChain(t, r.root, p)) + } + } + for imp, from := range thirdParty { + for _, bad := range r.forbidden3p { + if strings.Contains(imp+"\"", bad) { + t.Errorf("%s reaches third-party %q via %s", r.root, imp, from) + } + } + } + for _, p := range r.expectAny { + if !closure[p] { + t.Errorf("%s no longer reaches %s; the walk may be vacuous, update expectAny if intentional", r.root, p) + } + } + }) + } +} + +type anImport struct{ path, file string } + +func packageImports(t *testing.T, pkgPath string) []anImport { + t.Helper() + dir := filepath.Join(repoRoot, strings.TrimPrefix(pkgPath, modulePath+"/")) + entries, err := os.ReadDir(dir) + if err != nil { + t.Fatalf("read package dir %s (for import %q): %v", dir, pkgPath, err) + } + var out []anImport + fset := token.NewFileSet() + for _, e := range entries { + if e.IsDir() || !strings.HasSuffix(e.Name(), ".go") || strings.HasSuffix(e.Name(), "_test.go") { + continue + } + path := filepath.Join(dir, e.Name()) + f, perr := parser.ParseFile(fset, path, nil, parser.ImportsOnly|parser.SkipObjectResolution) + if perr != nil { + t.Fatalf("parse %s: %v", path, perr) + } + for _, spec := range f.Imports { + imp, uerr := strconv.Unquote(spec.Path.Value) + if uerr != nil { + t.Fatalf("%s: unquote import %s: %v", path, spec.Path.Value, uerr) + } + out = append(out, anImport{path: imp, file: path}) + } + } + return out +} + +func isFirstParty(imp string) bool { + return imp == modulePath || strings.HasPrefix(imp, modulePath+"/") +} + +// firstPartyClosure follows non-test first-party imports from root and returns +// the packages reached plus third-party edges mapped to one importer. +func firstPartyClosure(t *testing.T, root string) (map[string]bool, map[string]string) { + t.Helper() + closure := map[string]bool{} + thirdParty := map[string]string{} + var walk func(string) + walk = func(p string) { + if closure[p] { + return + } + closure[p] = true + for _, imp := range packageImports(t, p) { + switch { + case isFirstParty(imp.path): + walk(imp.path) + case !strings.Contains(strings.SplitN(imp.path, "/", 2)[0], "."): + // stdlib: only database/sql is interesting, record it as a third-party edge. + if imp.path == "database/sql" { + thirdParty[imp.path] = p + } + default: + if _, seen := thirdParty[imp.path]; !seen { + thirdParty[imp.path] = p + } + } + } + } + walk(root) + return closure, thirdParty +} + +// importChain finds one path root -> ... -> target so a failure says where the +// edge came from instead of only that it exists. +func importChain(t *testing.T, root, target string) string { + t.Helper() + parent := map[string]string{root: ""} + queue := []string{root} + for len(queue) > 0 { + p := queue[0] + queue = queue[1:] + if p == target { + var chain []string + for q := p; q != ""; q = parent[q] { + chain = append(chain, strings.TrimPrefix(q, modulePath+"/")) + } + sort.SliceStable(chain, func(i, j int) bool { return i > j }) + return strings.Join(chain, " -> ") + } + for _, imp := range packageImports(t, p) { + if isFirstParty(imp.path) { + if _, seen := parent[imp.path]; !seen { + parent[imp.path] = p + queue = append(queue, imp.path) + } + } + } + } + return "(no chain found)" +} diff --git a/pkg/treatment/controller/analyze.go b/pkg/treatment/controller/analyze.go index fd4a0406a..c99da03f1 100644 --- a/pkg/treatment/controller/analyze.go +++ b/pkg/treatment/controller/analyze.go @@ -8,6 +8,7 @@ import ( pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" @@ -18,7 +19,7 @@ import ( // Warehouse work stays outside the transaction; the revision fence rejects // evidence computed across an intervening operator decision. func (s *Server) Analyze(ctx context.Context, r *pb.AnalyzeRequest) (*pb.AnalyzeResponse, error) { - actor, e := OperatorIdentity(ctx) + actor, e := identity.Operator(ctx) if e != nil { return nil, e } diff --git a/pkg/treatment/controller/auth.go b/pkg/treatment/controller/auth.go deleted file mode 100644 index 11093d375..000000000 --- a/pkg/treatment/controller/auth.go +++ /dev/null @@ -1,97 +0,0 @@ -package controller - -import ( - "context" - "crypto/tls" - "crypto/x509" - "errors" - "os" - "strings" - - "github.com/randomizedcoder/xtcp2/pkg/treatment" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/credentials" - "google.golang.org/grpc/peer" - "google.golang.org/grpc/status" -) - -// Certificates bind agent identity AND enrollment location. Merely presenting -// any certificate from the CA does not confer operator permissions. -// URI SANs: spiffe://xtcp/agent// or spiffe://xtcp/operator/. -type Identity struct{ Role, ID, Location string } - -func CertificateIdentity(cert *x509.Certificate) (Identity, error) { - var found []Identity - for _, u := range cert.URIs { - if u.Scheme != "spiffe" || u.Host != "xtcp" || u.RawQuery != "" || u.Fragment != "" { - continue - } - p := strings.Split(strings.TrimPrefix(u.Path, "/"), "/") - if len(p) == 3 && p[0] == "agent" && treatment.ValidID(p[1]) && treatment.ValidID(p[2]) { - found = append(found, Identity{Role: p[0], Location: p[1], ID: p[2]}) - } - if len(p) == 2 && p[0] == "operator" && treatment.ValidID(p[1]) { - found = append(found, Identity{Role: p[0], ID: p[1]}) - } - } - if len(found) != 1 { - return Identity{}, errors.New("exactly one xtcp identity URI SAN required") - } - return found[0], nil -} -func identity(ctx context.Context) (Identity, error) { - p, ok := peer.FromContext(ctx) - if !ok { - return Identity{}, status.Error(codes.Unauthenticated, "mTLS required") - } - t, ok := p.AuthInfo.(credentials.TLSInfo) - if !ok || len(t.State.VerifiedChains) == 0 || len(t.State.PeerCertificates) == 0 { - return Identity{}, status.Error(codes.Unauthenticated, "verified client certificate required") - } - id, e := CertificateIdentity(t.State.PeerCertificates[0]) - if e != nil { - return Identity{}, status.Error(codes.PermissionDenied, e.Error()) - } - return id, nil -} -func AgentIdentity(ctx context.Context, want string) (Identity, error) { - id, e := identity(ctx) - if e != nil { - return id, e - } - if id.Role != "agent" || id.ID != want { - return Identity{}, status.Error(codes.PermissionDenied, "agent identity mismatch") - } - return id, nil -} -func OperatorIdentity(ctx context.Context) (string, error) { - id, e := identity(ctx) - if e != nil { - return "", e - } - if id.Role != "operator" { - return "", status.Error(codes.PermissionDenied, "operator certificate required") - } - return id.ID, nil -} - -func TLSConfig(certFile, keyFile, caFile, serverName string, server bool) (*tls.Config, error) { - cert, e := tls.LoadX509KeyPair(certFile, keyFile) - if e != nil { - return nil, e - } - ca, e := os.ReadFile(caFile) - if e != nil { - return nil, e - } - roots := x509.NewCertPool() - if !roots.AppendCertsFromPEM(ca) { - return nil, errors.New("CA file has no certificates") - } - c := &tls.Config{MinVersion: tls.VersionTLS13, Certificates: []tls.Certificate{cert}, RootCAs: roots, ServerName: serverName} - if server { - c.ClientCAs = roots - c.ClientAuth = tls.RequireAndVerifyClientCert - } - return c, nil -} diff --git a/pkg/treatment/controller/contracts_test.go b/pkg/treatment/controller/contracts_test.go index 8b71d668b..f0f0981dc 100644 --- a/pkg/treatment/controller/contracts_test.go +++ b/pkg/treatment/controller/contracts_test.go @@ -2,52 +2,14 @@ package controller import ( "context" - "crypto/x509" "errors" "io" "net/http" - "net/url" "strings" "testing" "time" ) -func TestCertificateIdentityContracts(t *testing.T) { - for _, tc := range []struct { - name, description string - uris []string - want Identity - wantError bool - }{ - {"agent", "agent identity binds both location and host", []string{"spiffe://xtcp/agent/dc/host"}, Identity{Role: "agent", Location: "dc", ID: "host"}, false}, - {"operator", "operator identity is distinct from agent", []string{"spiffe://xtcp/operator/admin"}, Identity{Role: "operator", ID: "admin"}, false}, - {"absent", "certificate must carry an identity", nil, Identity{}, true}, - {"ambiguous", "two roles on one certificate are rejected", []string{"spiffe://xtcp/agent/dc/host", "spiffe://xtcp/operator/admin"}, Identity{}, true}, - {"duplicate", "even repeated identical SANs are ambiguous", []string{"spiffe://xtcp/agent/dc/host", "spiffe://xtcp/agent/dc/host"}, Identity{}, true}, - {"authority", "foreign trust domain is rejected", []string{"spiffe://other/agent/dc/host"}, Identity{}, true}, - {"query", "query suffix cannot change identity semantics", []string{"spiffe://xtcp/agent/dc/host?role=operator"}, Identity{}, true}, - {"fragment", "fragment suffix is rejected", []string{"spiffe://xtcp/agent/dc/host#operator"}, Identity{}, true}, - {"path", "escaped path separators are rejected", []string{"spiffe://xtcp/agent/dc/host%2Fextra"}, Identity{}, true}, - {"missing-location", "agent location cannot be omitted", []string{"spiffe://xtcp/agent/host"}, Identity{}, true}, - {"unrelated-san", "unrelated SANs do not confer another role", []string{"https://example.test/", "spiffe://xtcp/operator/admin"}, Identity{Role: "operator", ID: "admin"}, false}, - } { - t.Run(tc.name, func(t *testing.T) { - cert := &x509.Certificate{} - for _, raw := range tc.uris { - u, err := url.Parse(raw) - if err != nil { - t.Fatal(err) - } - cert.URIs = append(cert.URIs, u) - } - got, err := CertificateIdentity(cert) - if (err != nil) != tc.wantError || got != tc.want { - t.Fatalf("%s: got %+v %v; want %+v error=%t", tc.description, got, err, tc.want, tc.wantError) - } - }) - } -} - type trackedBody struct { io.Reader closed bool diff --git a/pkg/treatment/controller/server.go b/pkg/treatment/controller/server.go index 45ba96b30..fb8805756 100644 --- a/pkg/treatment/controller/server.go +++ b/pkg/treatment/controller/server.go @@ -12,6 +12,7 @@ import ( pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -60,7 +61,7 @@ func validRequest(id, reason string) error { } func (s *Server) CheckIn(ctx context.Context, r *pb.CheckInRequest) (*pb.CheckInResponse, error) { - id, e := AgentIdentity(ctx, r.GetAgentId()) + id, e := identity.Agent(ctx, r.GetAgentId()) if e != nil { return nil, e } @@ -174,7 +175,7 @@ func (s *Server) WatchDesiredState(r *pb.WatchDesiredStateRequest, stream grpc.S } func (s *Server) authorizeEnrolledAgent(ctx context.Context, want string) error { - id, e := AgentIdentity(ctx, want) + id, e := identity.Agent(ctx, want) if e != nil { return e } @@ -189,7 +190,7 @@ func (s *Server) authorizeEnrolledAgent(ctx context.Context, want string) error } func (s *Server) Propose(ctx context.Context, r *pb.ProposeRequest) (out *pb.ProposeResponse, err error) { - actor, e := OperatorIdentity(ctx) + actor, e := identity.Operator(ctx) if e != nil { return nil, e } @@ -313,7 +314,7 @@ func (s *Server) propose(ctx context.Context, r *pb.ProposeRequest, actor string } func (s *Server) Review(ctx context.Context, r *pb.ReviewRequest) (out *pb.ReviewResponse, err error) { - actor, e := OperatorIdentity(ctx) + actor, e := identity.Operator(ctx) if e != nil { return nil, e } @@ -560,7 +561,7 @@ func (s *Server) assign(ctx context.Context, tx *sql.Tx, e *pb.Experiment, react } func (s *Server) Control(ctx context.Context, r *pb.ControlRequest) (out *pb.ControlResponse, err error) { - actor, e := OperatorIdentity(ctx) + actor, e := identity.Operator(ctx) if e != nil { return nil, e } @@ -694,7 +695,7 @@ func (s *Server) Control(ctx context.Context, r *pb.ControlRequest) (out *pb.Con } func (s *Server) Inspect(ctx context.Context, r *pb.InspectRequest) (*pb.InspectResponse, error) { - if _, e := OperatorIdentity(ctx); e != nil { + if _, e := identity.Operator(ctx); e != nil { return nil, e } out := new(pb.InspectResponse) diff --git a/pkg/treatment/controller/server_test.go b/pkg/treatment/controller/server_test.go index 5b762c426..fa1eca86a 100644 --- a/pkg/treatment/controller/server_test.go +++ b/pkg/treatment/controller/server_test.go @@ -15,6 +15,7 @@ import ( pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" "google.golang.org/grpc/credentials" "google.golang.org/grpc/peer" "google.golang.org/protobuf/proto" @@ -176,15 +177,15 @@ func TestPostgresConcurrentReviewFencesReplicas(t *testing.T) { } func TestCertificateIdentityIsolation(t *testing.T) { - if _, e := AgentIdentity(testIdentity("agent/dc1/host-1"), "host-2"); e == nil { + if _, e := identity.Agent(testIdentity("agent/dc1/host-1"), "host-2"); e == nil { t.Fatal("cross-agent identity accepted") } - if _, e := OperatorIdentity(context.Background()); e == nil { + if _, e := identity.Operator(context.Background()); e == nil { t.Fatal("missing TLS accepted") } u, _ := url.Parse("spiffe://xtcp/operator/alice") v, _ := url.Parse("spiffe://xtcp/agent/dc1/host-1") - if _, e := CertificateIdentity(&x509.Certificate{URIs: []*url.URL{u, v}}); e == nil { + if _, e := identity.FromCertificate(&x509.Certificate{URIs: []*url.URL{u, v}}); e == nil { t.Fatal("ambiguous role accepted") } } diff --git a/pkg/treatment/controller/status.go b/pkg/treatment/controller/status.go index 301323113..673517b60 100644 --- a/pkg/treatment/controller/status.go +++ b/pkg/treatment/controller/status.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" "net/http" "sort" "time" @@ -17,8 +18,8 @@ func (s *Server) StatusHandler() http.Handler { http.Error(w, "mTLS required", http.StatusUnauthorized) return } - id, e := CertificateIdentity(r.TLS.PeerCertificates[0]) - if e != nil || id.Role != "operator" { + id, e := identity.FromCertificate(r.TLS.PeerCertificates[0]) + if e != nil || id.Role != identity.RoleOperator { http.Error(w, "operator required", http.StatusForbidden) return } diff --git a/pkg/treatment/identity/identity.go b/pkg/treatment/identity/identity.go new file mode 100644 index 000000000..a0f45e6be --- /dev/null +++ b/pkg/treatment/identity/identity.go @@ -0,0 +1,107 @@ +// Package identity is the shared mTLS identity contract between the treatment +// controller, agents, operator tooling and tests. It is deliberately a leaf: +// it knows about certificates, SPIFFE URI SANs and gRPC peer metadata, and +// nothing about persistence, experiments or the wire schema. +// +// Certificates bind agent identity AND enrollment location. Merely presenting +// any certificate from the CA does not confer operator permissions. +// URI SANs: spiffe://xtcp/agent// or spiffe://xtcp/operator/. +package identity + +import ( + "context" + "crypto/x509" + "errors" + "strings" + + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/peer" + "google.golang.org/grpc/status" +) + +// Role is the principal class encoded in the certificate URI SAN. +type Role string + +const ( + RoleAgent Role = "agent" + RoleOperator Role = "operator" +) + +// Identity is the parsed principal. Location is empty for operators. +type Identity struct { + Role Role + ID string + Location string +} + +// ErrNoIdentity is returned when a certificate does not carry exactly one +// xtcp identity URI SAN. Two SANs are as invalid as zero: ambiguity fails. +var ErrNoIdentity = errors.New("exactly one xtcp identity URI SAN required") + +// FromCertificate parses the single xtcp SPIFFE URI SAN of a leaf certificate. +func FromCertificate(cert *x509.Certificate) (Identity, error) { + var found []Identity + for _, u := range cert.URIs { + if u.Scheme != "spiffe" || u.Host != "xtcp" || u.RawQuery != "" || u.Fragment != "" { + continue + } + p := strings.Split(strings.TrimPrefix(u.Path, "/"), "/") + switch { + case len(p) == 3 && p[0] == string(RoleAgent) && treatment.ValidID(p[1]) && treatment.ValidID(p[2]): + found = append(found, Identity{Role: RoleAgent, Location: p[1], ID: p[2]}) + case len(p) == 2 && p[0] == string(RoleOperator) && treatment.ValidID(p[1]): + found = append(found, Identity{Role: RoleOperator, ID: p[1]}) + } + } + if len(found) != 1 { + return Identity{}, ErrNoIdentity + } + return found[0], nil +} + +// FromPeer resolves the verified client certificate of the gRPC peer. It is +// the only function here that reads the context; errors already carry gRPC +// status codes so handlers and interceptors can return them unchanged. +func FromPeer(ctx context.Context) (Identity, error) { + p, ok := peer.FromContext(ctx) + if !ok { + return Identity{}, status.Error(codes.Unauthenticated, "mTLS required") + } + t, ok := p.AuthInfo.(credentials.TLSInfo) + if !ok || len(t.State.VerifiedChains) == 0 || len(t.State.PeerCertificates) == 0 { + return Identity{}, status.Error(codes.Unauthenticated, "verified client certificate required") + } + id, e := FromCertificate(t.State.PeerCertificates[0]) + if e != nil { + return Identity{}, status.Error(codes.PermissionDenied, e.Error()) + } + return id, nil +} + +// Agent requires the peer to be the agent named by want. Agents may only +// speak for themselves; the controller passes the request's agent_id here. +func Agent(ctx context.Context, want string) (Identity, error) { + id, e := FromPeer(ctx) + if e != nil { + return id, e + } + if id.Role != RoleAgent || id.ID != want { + return Identity{}, status.Error(codes.PermissionDenied, "agent identity mismatch") + } + return id, nil +} + +// Operator requires an operator certificate and returns the operator ID, +// which callers record as the audit actor. +func Operator(ctx context.Context) (string, error) { + id, e := FromPeer(ctx) + if e != nil { + return "", e + } + if id.Role != RoleOperator { + return "", status.Error(codes.PermissionDenied, "operator certificate required") + } + return id.ID, nil +} diff --git a/pkg/treatment/identity/identity_test.go b/pkg/treatment/identity/identity_test.go new file mode 100644 index 000000000..b85c6229a --- /dev/null +++ b/pkg/treatment/identity/identity_test.go @@ -0,0 +1,152 @@ +package identity + +import ( + "crypto/x509" + "net/url" + "strings" + "testing" +) + +func certWithURIs(t *testing.T, uris ...string) *x509.Certificate { + t.Helper() + c := &x509.Certificate{} + for _, raw := range uris { + u, e := url.Parse(raw) + if e != nil { + t.Fatal(e) + } + c.URIs = append(c.URIs, u) + } + return c +} + +// go test ./pkg/treatment/identity -run TestFromCertificate +func TestFromCertificate(t *testing.T) { + long := strings.Repeat("a", 128) + tests := []struct { + description string + uris []string + expected Identity + wantErr bool + }{ + { + description: "positive: agent SAN yields role, location and id", + uris: []string{"spiffe://xtcp/agent/dc1/host-1"}, + expected: Identity{Role: RoleAgent, Location: "dc1", ID: "host-1"}, + }, + { + description: "positive: operator SAN yields role and id with empty location", + uris: []string{"spiffe://xtcp/operator/alice"}, + expected: Identity{Role: RoleOperator, ID: "alice"}, + }, + { + description: "positive: foreign SANs are ignored around the one xtcp SAN", + uris: []string{"spiffe://other/agent/dc1/host-1", "https://example.com/x", "spiffe://xtcp/operator/bob"}, + expected: Identity{Role: RoleOperator, ID: "bob"}, + }, + { + description: "negative: no SAN at all", + uris: nil, + wantErr: true, + }, + { + description: "negative: two xtcp SANs are ambiguous and rejected", + uris: []string{"spiffe://xtcp/agent/dc1/host-1", "spiffe://xtcp/operator/alice"}, + wantErr: true, + }, + { + description: "negative: agent SAN missing the location segment", + uris: []string{"spiffe://xtcp/agent/host-1"}, + wantErr: true, + }, + { + description: "negative: operator SAN with an extra segment is not an agent either", + uris: []string{"spiffe://xtcp/operator/alice/extra"}, + wantErr: true, + }, + { + description: "negative: unknown role segment", + uris: []string{"spiffe://xtcp/root/dc1/host-1"}, + wantErr: true, + }, + { + description: "negative: wrong trust domain host", + uris: []string{"spiffe://xtcp2/agent/dc1/host-1"}, + wantErr: true, + }, + { + description: "negative: query string is rejected even with a valid path", + uris: []string{"spiffe://xtcp/agent/dc1/host-1?x=1"}, + wantErr: true, + }, + { + description: "negative: fragment is rejected even with a valid path", + uris: []string{"spiffe://xtcp/agent/dc1/host-1#f"}, + wantErr: true, + }, + { + description: "boundary: id at the maximum valid length is accepted", + uris: []string{"spiffe://xtcp/operator/" + long}, + expected: Identity{Role: RoleOperator, ID: long}, + }, + { + description: "boundary: id one byte over the maximum is rejected", + uris: []string{"spiffe://xtcp/operator/" + long + "a"}, + wantErr: true, + }, + { + description: "boundary: empty id segment is rejected", + uris: []string{"spiffe://xtcp/operator/"}, + wantErr: true, + }, + { + // url.Parse lowercases the scheme, and x509 parses SANs with it, + // so this is accepted. Recorded so the behaviour is deliberate. + description: "corner: upper-case scheme is normalised by the URL parser and accepted", + uris: []string{"SPIFFE://xtcp/agent/dc1/host-1"}, + expected: Identity{Role: RoleAgent, Location: "dc1", ID: "host-1"}, + }, + { + description: "corner: escaped path separators are not segment separators", + uris: []string{"spiffe://xtcp/agent/dc/host%2Fextra"}, + wantErr: true, + }, + { + description: "corner: a trailing slash adds an empty segment and is rejected", + uris: []string{"spiffe://xtcp/agent/dc1/host-1/"}, + wantErr: true, + }, + { + description: "corner: an identical SAN repeated twice still counts as two", + uris: []string{"spiffe://xtcp/operator/alice", "spiffe://xtcp/operator/alice"}, + wantErr: true, + }, + } + for _, tc := range tests { + t.Run(tc.description, func(t *testing.T) { + got, err := FromCertificate(certWithURIs(t, tc.uris...)) + if tc.wantErr { + if err == nil { + t.Fatalf("expected error, got %+v", got) + } + if got != (Identity{}) { + t.Fatalf("identity must be zero on error, got %+v", got) + } + return + } + if err != nil { + t.Fatal(err) + } + if got != tc.expected { + t.Fatalf("got %+v, expected %+v", got, tc.expected) + } + }) + } +} + +// go test ./pkg/treatment/identity -run TestLeaf +func TestLeaf(t *testing.T) { + if _, e := Leaf(nil); e == nil { + t.Fatal("nil config must not yield a leaf") + } +} diff --git a/pkg/treatment/controller/auth_test.go b/pkg/treatment/identity/mtls_test.go similarity index 98% rename from pkg/treatment/controller/auth_test.go rename to pkg/treatment/identity/mtls_test.go index ba25169d7..63c57a8e1 100644 --- a/pkg/treatment/controller/auth_test.go +++ b/pkg/treatment/identity/mtls_test.go @@ -1,4 +1,4 @@ -package controller +package identity import ( "context" @@ -31,11 +31,11 @@ type authProbe struct { } func (authProbe) CheckIn(ctx context.Context, r *pb.CheckInRequest) (*pb.CheckInResponse, error) { - _, e := AgentIdentity(ctx, r.AgentId) + _, e := Agent(ctx, r.AgentId) return &pb.CheckInResponse{}, e } func (authProbe) Inspect(ctx context.Context, _ *pb.InspectRequest) (*pb.InspectResponse, error) { - _, e := OperatorIdentity(ctx) + _, e := Operator(ctx) return &pb.InspectResponse{}, e } diff --git a/pkg/treatment/identity/tls.go b/pkg/treatment/identity/tls.go new file mode 100644 index 000000000..5e87668c9 --- /dev/null +++ b/pkg/treatment/identity/tls.go @@ -0,0 +1,46 @@ +package identity + +import ( + "crypto/tls" + "crypto/x509" + "errors" + "os" +) + +// TLSConfig loads a leaf key pair and a private CA bundle. Both ends pin TLS +// 1.3 and trust only that CA; servers additionally require and verify a client +// certificate so every RPC arrives with a peer identity for FromPeer. +func TLSConfig(certFile, keyFile, caFile, serverName string, server bool) (*tls.Config, error) { + cert, e := tls.LoadX509KeyPair(certFile, keyFile) + if e != nil { + return nil, e + } + ca, e := os.ReadFile(caFile) + if e != nil { + return nil, e + } + roots := x509.NewCertPool() + if !roots.AppendCertsFromPEM(ca) { + return nil, errors.New("CA file has no certificates") + } + c := &tls.Config{ + MinVersion: tls.VersionTLS13, + Certificates: []tls.Certificate{cert}, + RootCAs: roots, + ServerName: serverName, + } + if server { + c.ClientCAs = roots + c.ClientAuth = tls.RequireAndVerifyClientCert + } + return c, nil +} + +// Leaf returns the parsed leaf certificate of a config built by TLSConfig, so +// a client can check its own identity before dialing anything. +func Leaf(c *tls.Config) (*x509.Certificate, error) { + if c == nil || len(c.Certificates) == 0 || len(c.Certificates[0].Certificate) == 0 { + return nil, errors.New("tls config has no leaf certificate") + } + return x509.ParseCertificate(c.Certificates[0].Certificate[0]) +} diff --git a/tools/tcp-controller-integration/main.go b/tools/tcp-controller-integration/main.go index 37a7599d5..eb22d4e29 100644 --- a/tools/tcp-controller-integration/main.go +++ b/tools/tcp-controller-integration/main.go @@ -20,6 +20,7 @@ import ( pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" "github.com/randomizedcoder/xtcp2/pkg/treatment/controller" + "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/protobuf/encoding/prototext" @@ -313,7 +314,7 @@ func runController(soak bool) { eventually(h+" fixture ready", 3*time.Minute, func() bool { act(h, "ready"); return true }) remote(h, action{Name: "setup", CA: read(pkiDir + "/ca.crt"), Certificate: read(pkiDir + "/" + h + ".crt"), Key: read(pkiDir + "/" + h + ".key")}, nil) } - tls := must(controller.TLSConfig(pkiDir+"/operator.crt", pkiDir+"/operator.key", pkiDir+"/ca.crt", "controller", false)) + tls := must(identity.TLSConfig(pkiDir+"/operator.crt", pkiDir+"/operator.key", pkiDir+"/ca.crt", "controller", false)) conn := must(grpc.NewClient("127.0.0.1:9443", grpc.WithTransportCredentials(credentials.NewTLS(tls)))) defer conn.Close() op := pb.NewOperatorServiceClient(conn) From 2ca932f866b016128c1c5878e200309a8bd2c822 Mon Sep 17 00:00:00 2001 From: "randomizedcoder dave.seddon.ca@gmail.com" Date: Sun, 25 Oct 2026 16:47:39 -0700 Subject: [PATCH 03/27] refactor(treatment): split the package by audience pkg/treatment mixed three audiences: the shared contract (validators, limits), controller-only statistics (Evaluate, Advance) and host-side actuation (Reconciler, Journal). The controller imported a Backend interface it never used and the agent imported the t-distribution code it never ran, and nothing stopped either from growing into the other. Move reconcile.go and journal.go to pkg/treatment/reconcile and experiment.go to pkg/treatment/experiment; the root keeps only the contract. In reconcile, replace the expiryOnly bool with a Mode type (ModeFull, ModeExpiryOnly) that Run rejects when out of range, and make BeginReconcile a declared member of Backend instead of a hidden type assertion. The store helper named experiment becomes loadExperiment to free the package name. Tests move with their code. The desired-state and experiment fixtures that three packages shared move into pkg/treatment/internal/fixtures, which imports only generated types so the contract package's own tests can use it without a cycle. The layering test gains rows for the two new packages and for "controller never reaches reconcile or goip"; two rows note the gRPC forbid that becomes possible once domain types replace the generated package. Co-Authored-By: Claude Fable 5.1 --- cmd/xtcp2-treatmentctl/main.go | 5 +- docs/tcp-controller.md | 18 +++++- pkg/treatment/agent/agent.go | 7 ++- pkg/treatment/agent/agent_test.go | 4 +- pkg/treatment/agent/benchmark_test.go | 4 +- pkg/treatment/boundaries_test.go | 24 +++++++ pkg/treatment/controller/analyze.go | 7 ++- pkg/treatment/controller/maintenance.go | 13 ++-- pkg/treatment/controller/server.go | 7 ++- pkg/treatment/controller/store.go | 2 +- .../{ => experiment}/benchmark_test.go | 8 ++- .../boundaries_test.go} | 12 ++-- pkg/treatment/{ => experiment}/experiment.go | 7 ++- .../{ => experiment}/experiment_test.go | 30 ++------- pkg/treatment/internal/fixtures/fixtures.go | 58 +++++++++++++++++ .../contracts_test.go} | 33 +++++----- pkg/treatment/{ => reconcile}/journal.go | 17 ++--- pkg/treatment/{ => reconcile}/journal_test.go | 8 ++- pkg/treatment/{ => reconcile}/reconcile.go | 49 ++++++++++++--- .../{ => reconcile}/reconcile_test.go | 63 +++++++++---------- pkg/treatment/validate_test.go | 9 +-- tools/tcp-controller-integration/soak.go | 3 +- 22 files changed, 258 insertions(+), 130 deletions(-) rename pkg/treatment/{ => experiment}/benchmark_test.go (85%) rename pkg/treatment/{experiment_boundaries_test.go => experiment/boundaries_test.go} (96%) rename pkg/treatment/{ => experiment}/experiment.go (96%) rename pkg/treatment/{ => experiment}/experiment_test.go (72%) create mode 100644 pkg/treatment/internal/fixtures/fixtures.go rename pkg/treatment/{reconcile_contracts_test.go => reconcile/contracts_test.go} (84%) rename pkg/treatment/{ => reconcile}/journal.go (86%) rename pkg/treatment/{ => reconcile}/journal_test.go (94%) rename pkg/treatment/{ => reconcile}/reconcile.go (85%) rename pkg/treatment/{ => reconcile}/reconcile_test.go (77%) diff --git a/cmd/xtcp2-treatmentctl/main.go b/cmd/xtcp2-treatmentctl/main.go index 884fb0ef6..b1d9fef26 100644 --- a/cmd/xtcp2-treatmentctl/main.go +++ b/cmd/xtcp2-treatmentctl/main.go @@ -13,6 +13,7 @@ import ( "time" "github.com/randomizedcoder/xtcp2/pkg/treatment/agent" + "github.com/randomizedcoder/xtcp2/pkg/treatment/reconcile" "google.golang.org/protobuf/encoding/prototext" ) @@ -56,7 +57,7 @@ func run() error { defer t.Stop() for { pass, cancelPass := context.WithTimeout(ctx, 25*time.Second) - _, e = a.Reconciler.Run(pass, nil, true) + _, e = a.Reconciler.Run(pass, nil, reconcile.ModeExpiryOnly) cancelPass() if e != nil { log.Printf("watchdog: %v", e) @@ -70,7 +71,7 @@ func run() error { default: return fmt.Errorf("unknown mode %q", *mode) } - s, e := a.Reconciler.Run(ctx, nil, false) + s, e := a.Reconciler.Run(ctx, nil, reconcile.ModeFull) if s != nil { fmt.Print(prototext.Format(s)) } diff --git a/docs/tcp-controller.md b/docs/tcp-controller.md index 362b88532..2b59d4fcc 100644 --- a/docs/tcp-controller.md +++ b/docs/tcp-controller.md @@ -22,8 +22,22 @@ flowchart LR W[Independent local lease watchdog] --> J ``` -The implementation lives in [pkg/treatment](../pkg/treatment), with a typed -[protobuf contract](../proto/xtcp/controller/v1/controller.proto). The binaries are: +The implementation lives under [pkg/treatment](../pkg/treatment), split by +audience so that each binary links only the half of the contract it needs, with +a typed [protobuf contract](../proto/xtcp/controller/v1/controller.proto): + +| Package | Used by | Contents | +| --- | --- | --- | +| `pkg/treatment` | everyone | identifiers, limits and validators: the shared contract | +| `pkg/treatment/identity` | agent, controller, tools | SPIFFE URI identity, mTLS configuration, gRPC peer resolution | +| `pkg/treatment/reconcile` | agent, watchdog | intent journal, reconciler, `Backend` interface, `Mode` (full or expiry-only) | +| `pkg/treatment/experiment` | controller | evaluation statistics, cohort allocation, phase advancement | +| `pkg/treatment/agent` | `xtcp2` | check-in loop, watch stream, capabilities, telemetry stamping | +| `pkg/treatment/controller` | `xtcp2-controller` | gRPC services, PostgreSQL store, ClickHouse evidence, maintenance, discovery | + +`pkg/treatment/boundaries_test.go` enforces the layering: `xtcp2` and the agent +never link the controller, a SQL driver or the ClickHouse client; the controller +never links the host-side reconcile package or goip. The binaries are: | Binary | Responsibility | | --- | --- | diff --git a/pkg/treatment/agent/agent.go b/pkg/treatment/agent/agent.go index 2419df99f..97574f199 100644 --- a/pkg/treatment/agent/agent.go +++ b/pkg/treatment/agent/agent.go @@ -24,6 +24,7 @@ import ( "github.com/randomizedcoder/xtcp2/internal/goip" "github.com/randomizedcoder/xtcp2/pkg/treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" + "github.com/randomizedcoder/xtcp2/pkg/treatment/reconcile" "golang.org/x/sys/unix" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -82,7 +83,7 @@ func LoadConfig(path string) (Config, error) { type Agent struct { Config Config - Reconciler *treatment.Reconciler + Reconciler *reconcile.Reconciler backend *routeBackend client pb.AgentControlServiceClient conn *grpc.ClientConn @@ -149,7 +150,7 @@ func newLocal(c Config, identity func() (string, uint64, error)) (*Agent, error) return nil, e } a := &Agent{Config: c, backend: b, wake: make(chan struct{}, 1), namespace: namespace, collectionID: hex.EncodeToString(nonce[:])} - a.Reconciler = &treatment.Reconciler{Journal: treatment.Journal{Dir: c.Directory}, Backend: b, AgentID: c.AgentID, BootID: boot} + a.Reconciler = &reconcile.Reconciler{Journal: reconcile.Journal{Dir: c.Directory}, Backend: b, AgentID: c.AgentID, BootID: boot} return a, nil } @@ -222,7 +223,7 @@ func (a *Agent) State() *pb.LocalState { func (a *Agent) reconcile(ctx context.Context, d *pb.DesiredState) { ctx, cancel := context.WithTimeout(ctx, 15*time.Second) defer cancel() - s, e := a.Reconciler.Run(ctx, d, false) + s, e := a.Reconciler.Run(ctx, d, reconcile.ModeFull) if s != nil { a.publish(s) } diff --git a/pkg/treatment/agent/agent_test.go b/pkg/treatment/agent/agent_test.go index 6a73a5c4b..d0916994e 100644 --- a/pkg/treatment/agent/agent_test.go +++ b/pkg/treatment/agent/agent_test.go @@ -6,13 +6,13 @@ import ( pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" flat "github.com/randomizedcoder/xtcp2/gen/go/xtcp_flat_record" - "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/reconcile" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" ) func TestStampDistinguishesAssignedAndActuallyExposed(t *testing.T) { - a := &Agent{Config: Config{AgentID: "host-1"}, namespace: 123, collectionID: "session", Reconciler: &treatment.Reconciler{BootID: "boot-1"}} + a := &Agent{Config: Config{AgentID: "host-1"}, namespace: 123, collectionID: "session", Reconciler: &reconcile.Reconciler{BootID: "boot-1"}} s := &pb.LocalState{Desired: &pb.DesiredState{Revision: 2}, Records: []*pb.ApplicationRecord{{Status: pb.ApplyStatus_APPLY_STATUS_VERIFIED, AppliedAt: timestamppb.Now(), Assignment: &pb.Assignment{Id: "exp-1", Generation: 1, Mode: pb.AssignmentMode_ASSIGNMENT_MODE_STABLE, Treatment: &pb.Treatment{Destination: "192.0.2.1/32", Settings: &pb.TCPSettings{CongestionControl: proto.String("cubic")}}}}}} a.publish(s) r := &flat.XtcpFlatRecord{NetnsInode: 123, InetDiagMsgFamily: 2, InetDiagMsgSocketDestination: []byte{192, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, InetDiagCong: "bbr"} diff --git a/pkg/treatment/agent/benchmark_test.go b/pkg/treatment/agent/benchmark_test.go index d60cccaf6..b0bb3ac43 100644 --- a/pkg/treatment/agent/benchmark_test.go +++ b/pkg/treatment/agent/benchmark_test.go @@ -8,13 +8,13 @@ import ( pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" flat "github.com/randomizedcoder/xtcp2/gen/go/xtcp_flat_record" - "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/reconcile" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" ) func stampFixture(n int) (*Agent, *pb.LocalState, *flat.XtcpFlatRecord) { - a := &Agent{Config: Config{AgentID: "host", Location: "dc"}, namespace: 123, collectionID: "session", Reconciler: &treatment.Reconciler{BootID: "boot"}, wake: make(chan struct{}, 1)} + a := &Agent{Config: Config{AgentID: "host", Location: "dc"}, namespace: 123, collectionID: "session", Reconciler: &reconcile.Reconciler{BootID: "boot"}, wake: make(chan struct{}, 1)} s := &pb.LocalState{Desired: &pb.DesiredState{Revision: 1}} for i := range n { ip := netip.AddrFrom4([4]byte{192, 0, byte(i >> 8), byte(i)}) diff --git a/pkg/treatment/boundaries_test.go b/pkg/treatment/boundaries_test.go index c0991703f..ee4425b7c 100644 --- a/pkg/treatment/boundaries_test.go +++ b/pkg/treatment/boundaries_test.go @@ -76,6 +76,30 @@ func treatmentBoundaryRules() []boundaryRule { forbidden3p: []string{"database/sql", "lib/pq", "jackc/pgx"}, expectAny: []string{tp("pkg/treatment")}, }, + { + // TODO(item 4): forbid google.golang.org/grpc once domain types + // replace the generated proto package, which bundles the stubs. + description: "positive: the reconcile package is host-side only: no identity, no experiment statistics", + root: tp("pkg/treatment/reconcile"), + forbidden: []string{tp("pkg/treatment/identity"), tp("pkg/treatment/experiment"), tp("pkg/treatment/agent"), tp("pkg/treatment/controller")}, + forbidden3p: []string{"database/sql"}, + expectAny: []string{tp("pkg/treatment")}, + }, + { + // TODO(item 4): forbid google.golang.org/grpc once domain types + // replace the generated proto package, which bundles the stubs. + description: "positive: the experiment package is pure: no journal, no identity, no syscalls", + root: tp("pkg/treatment/experiment"), + forbidden: []string{tp("pkg/treatment/reconcile"), tp("pkg/treatment/identity"), tp("pkg/treatment/agent"), tp("pkg/treatment/controller"), tp("internal/goip")}, + forbidden3p: []string{"database/sql", "golang.org/x/sys"}, + expectAny: []string{tp("pkg/treatment")}, + }, + { + description: "negative: the controller never reaches the host-side reconcile package or goip", + root: tp("pkg/treatment/controller"), + forbidden: []string{tp("pkg/treatment/reconcile"), tp("internal/goip")}, + expectAny: []string{tp("pkg/treatment/experiment")}, + }, { description: "corner: the controller never imports the agent (the dependency points one way)", root: tp("pkg/treatment/controller"), diff --git a/pkg/treatment/controller/analyze.go b/pkg/treatment/controller/analyze.go index c99da03f1..a199e7244 100644 --- a/pkg/treatment/controller/analyze.go +++ b/pkg/treatment/controller/analyze.go @@ -8,6 +8,7 @@ import ( pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/experiment" "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -32,7 +33,7 @@ func (s *Server) Analyze(ctx context.Context, r *pb.AnalyzeRequest) (*pb.Analyze var n *pb.Experiment e = s.Store.mutate(ctx, func(tx *sql.Tx) error { var e error - n, e = experiment(ctx, tx, r.ExperimentId) + n, e = loadExperiment(ctx, tx, r.ExperimentId) if e != nil { return e } @@ -60,14 +61,14 @@ func (s *Server) Analyze(ctx context.Context, r *pb.AnalyzeRequest) (*pb.Analyze } out := &pb.AnalyzeResponse{Evidence: ev, Outcome: "baseline"} if len(n.Phases) > 0 { - v, e := treatment.Evaluate(input, ev, s.now()) + v, e := experiment.Evaluate(input, ev, s.now()) if e != nil { return nil, failure(e) } out.Outcome = v.Outcome } e = s.Store.mutate(ctx, func(tx *sql.Tx) error { - current, e := experiment(ctx, tx, n.Id) + current, e := loadExperiment(ctx, tx, n.Id) if e != nil { return e } diff --git a/pkg/treatment/controller/maintenance.go b/pkg/treatment/controller/maintenance.go index b136b1464..ea87bd046 100644 --- a/pkg/treatment/controller/maintenance.go +++ b/pkg/treatment/controller/maintenance.go @@ -9,6 +9,7 @@ import ( pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/experiment" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" @@ -45,7 +46,7 @@ func (s *Server) Maintain(ctx context.Context, source EvidenceSource) error { for _, id := range ids { var snapshot *pb.Experiment e = s.Store.mutate(ctx, func(tx *sql.Tx) error { - n, e := experiment(ctx, tx, id) + n, e := loadExperiment(ctx, tx, id) if e != nil { return e } @@ -84,7 +85,7 @@ func (s *Server) Maintain(ctx context.Context, source EvidenceSource) error { } } e = s.Store.mutate(ctx, func(tx *sql.Tx) error { - n, e := experiment(ctx, tx, id) + n, e := loadExperiment(ctx, tx, id) if e != nil { return e } @@ -99,7 +100,7 @@ func (s *Server) Maintain(ctx context.Context, source EvidenceSource) error { if e = saveEvidence(ctx, tx, id, ev); e != nil { return e } - healthy, harmful := treatment.HealthyEvidence(n, ev, now) + healthy, harmful := experiment.HealthyEvidence(n, ev, now) n.LatestEvidence = ev n.LatestOutcome = "stable_observed" if !healthy { @@ -120,7 +121,7 @@ func (s *Server) Maintain(ctx context.Context, source EvidenceSource) error { if e = saveEvidence(ctx, tx, id, ev); e != nil { return e } - v, e := treatment.Evaluate(n, ev, now) + v, e := experiment.Evaluate(n, ev, now) if e != nil { return e } @@ -128,7 +129,7 @@ func (s *Server) Maintain(ctx context.Context, source EvidenceSource) error { // Keep analysis visible to reviewers without marking a phase as // approved/evaluated. Only Review writes reviewer and outcome. n.LatestEvidence = ev - if _, harmful := treatment.HealthyEvidence(n, ev, now); harmful { + if _, harmful := experiment.HealthyEvidence(n, ev, now); harmful { outcome = "harmful" } } @@ -147,7 +148,7 @@ func (s *Server) Maintain(ctx context.Context, source EvidenceSource) error { } else if ev != nil && n.Status == pb.ExperimentStatus_EXPERIMENT_STATUS_RUNNING { // Before the 30-minute decision window completes, fresh health // evidence can renew the existing phase but cannot expand it. - healthy, _ := treatment.HealthyEvidence(n, ev, now) + healthy, _ := experiment.HealthyEvidence(n, ev, now) if healthy && outcome != "stale" && outcome != "insufficient_data" { if e = s.renew(ctx, tx, n, now); e != nil { return e diff --git a/pkg/treatment/controller/server.go b/pkg/treatment/controller/server.go index fb8805756..77d5e1423 100644 --- a/pkg/treatment/controller/server.go +++ b/pkg/treatment/controller/server.go @@ -12,6 +12,7 @@ import ( pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/experiment" "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -326,7 +327,7 @@ func (s *Server) Review(ctx context.Context, r *pb.ReviewRequest) (out *pb.Revie if yes, e := replay(ctx, tx, r.RequestId, "review", r, out); yes || e != nil { return e } - n, e := experiment(ctx, tx, r.ExperimentId) + n, e := loadExperiment(ctx, tx, r.ExperimentId) if e != nil { return e } @@ -362,7 +363,7 @@ func (s *Server) Review(ctx context.Context, r *pb.ReviewRequest) (out *pb.Revie return e } } - n, e = treatment.Advance(n, r.Evidence, s.now(), actor, r.Reason) + n, e = experiment.Advance(n, r.Evidence, s.now(), actor, r.Reason) if e != nil { return e } @@ -589,7 +590,7 @@ func (s *Server) Control(ctx context.Context, r *pb.ControlRequest) (out *pb.Con return e } if r.ExperimentId != "" { - n, e := experiment(ctx, tx, r.ExperimentId) + n, e := loadExperiment(ctx, tx, r.ExperimentId) if e != nil { return e } diff --git a/pkg/treatment/controller/store.go b/pkg/treatment/controller/store.go index 72ceae8f2..033d16949 100644 --- a/pkg/treatment/controller/store.go +++ b/pkg/treatment/controller/store.go @@ -81,7 +81,7 @@ func (s *Store) mutate(ctx context.Context, fn func(*sql.Tx) error) error { return tx.Commit() } -func experiment(ctx context.Context, tx *sql.Tx, id string) (*pb.Experiment, error) { +func loadExperiment(ctx context.Context, tx *sql.Tx, id string) (*pb.Experiment, error) { var raw []byte if e := tx.QueryRowContext(ctx, "SELECT state FROM xtcp_control_experiments WHERE id=$1 FOR UPDATE", id).Scan(&raw); e != nil { return nil, e diff --git a/pkg/treatment/benchmark_test.go b/pkg/treatment/experiment/benchmark_test.go similarity index 85% rename from pkg/treatment/benchmark_test.go rename to pkg/treatment/experiment/benchmark_test.go index 2ce0fc54f..d6064cb99 100644 --- a/pkg/treatment/benchmark_test.go +++ b/pkg/treatment/experiment/benchmark_test.go @@ -1,4 +1,4 @@ -package treatment +package experiment import ( "fmt" @@ -6,12 +6,14 @@ import ( "time" pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" "google.golang.org/protobuf/proto" ) func fleetFixture(n int) (*pb.Experiment, *pb.Evidence, time.Time) { now := time.Unix(1700000000, 0) - e, v := experimentFixture(now) + e, v := fixtures.Experiment(now, treatment.DefaultPolicy()) e.CohortOrder, e.Baseline.Hosts, v.Hosts = nil, nil, nil e.Phases[0].TargetHosts = uint32(n / 2) for i := range n { @@ -35,7 +37,7 @@ func BenchmarkFleetEvidence(b *testing.B) { name string run func() error }{ - {"Validate", func() error { return ValidateEvidence(v) }}, + {"Validate", func() error { return treatment.ValidateEvidence(v) }}, {"Safety", func() error { h, bad := HealthyEvidence(e, v, now) if !h || bad { diff --git a/pkg/treatment/experiment_boundaries_test.go b/pkg/treatment/experiment/boundaries_test.go similarity index 96% rename from pkg/treatment/experiment_boundaries_test.go rename to pkg/treatment/experiment/boundaries_test.go index 88ce079a8..6718013ac 100644 --- a/pkg/treatment/experiment_boundaries_test.go +++ b/pkg/treatment/experiment/boundaries_test.go @@ -1,10 +1,12 @@ -package treatment +package experiment import ( "testing" "time" pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -65,7 +67,7 @@ func TestEvaluationBoundaries(t *testing.T) { {"invalid-evidence", "malformed evidence fails before decision", "", true, func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts[0] = nil }}, } { t.Run(tc.name, func(t *testing.T) { - e, v := experimentFixture(now) + e, v := fixtures.Experiment(now, treatment.DefaultPolicy()) tc.change(e, v) before := proto.Clone(e) evidenceBefore := proto.Clone(v) @@ -106,7 +108,7 @@ func TestSafetyGateBoundaries(t *testing.T) { {"partial-harm", "missing data does not conceal observed harm", func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts = v.Hosts[1:]; v.Hosts[0].RttP95Ms = 10 }, false, true}, } { t.Run(tc.name, func(t *testing.T) { - e, v := experimentFixture(now) + e, v := fixtures.Experiment(now, treatment.DefaultPolicy()) tc.change(e, v) h, bad := HealthyEvidence(e, v, now) if h != tc.healthy || bad != tc.harmful { @@ -135,7 +137,7 @@ func TestAdvanceTransitions(t *testing.T) { {"harm", "harm cannot be promoted", func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts[0].RttP95Ms = 100 }, 0, 0, true}, } { t.Run(tc.name, func(t *testing.T) { - e, v := experimentFixture(now) + e, v := fixtures.Experiment(now, treatment.DefaultPolicy()) tc.change(e, v) before := proto.Clone(e) got, err := Advance(e, v, now, "reviewer", "measured benefit") @@ -185,7 +187,7 @@ func TestDefaultRTTGuardExactBoundary(t *testing.T) { } { t.Run(tc.description, func(t *testing.T) { now := time.Unix(1700000000, 0) - e, v := experimentFixture(now) + e, v := fixtures.Experiment(now, treatment.DefaultPolicy()) for _, h := range e.Baseline.Hosts { h.RttP95Ms = 5 } diff --git a/pkg/treatment/experiment.go b/pkg/treatment/experiment/experiment.go similarity index 96% rename from pkg/treatment/experiment.go rename to pkg/treatment/experiment/experiment.go index eb27aa120..083155756 100644 --- a/pkg/treatment/experiment.go +++ b/pkg/treatment/experiment/experiment.go @@ -1,4 +1,4 @@ -package treatment +package experiment import ( "errors" @@ -7,6 +7,7 @@ import ( "time" pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "github.com/randomizedcoder/xtcp2/pkg/treatment" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -21,7 +22,7 @@ type Evaluation struct { // A conservative Welch interval budgets minute-by-minute looks across all // planned phases. This interval is a review aid, not an automatic promotion. func Evaluate(e *pb.Experiment, ev *pb.Evidence, now time.Time) (Evaluation, error) { - if err := ValidateEvidence(ev); err != nil { + if err := treatment.ValidateEvidence(ev); err != nil { return Evaluation{}, err } if len(e.Phases) == 0 { @@ -116,7 +117,7 @@ func Evaluate(e *pb.Experiment, ev *pb.Evidence, now time.Time) (Evaluation, err // HealthyEvidence is the fast safety gate, independent of the minimum benefit // observation window. It also prevents missing hosts from renewing leases. func HealthyEvidence(e *pb.Experiment, v *pb.Evidence, now time.Time) (healthy, harmful bool) { - if ValidateEvidence(v) != nil || v.AnalyzedAt.AsTime().After(now.Add(30*time.Second)) || now.Sub(v.WindowEnd.AsTime()) > e.Policy.MaximumEvidenceAge.AsDuration() { + if treatment.ValidateEvidence(v) != nil || v.AnalyzedAt.AsTime().After(now.Add(30*time.Second)) || now.Sub(v.WindowEnd.AsTime()) > e.Policy.MaximumEvidenceAge.AsDuration() { return false, false } base := make(map[string]*pb.HostEvidence, len(e.Baseline.Hosts)) diff --git a/pkg/treatment/experiment_test.go b/pkg/treatment/experiment/experiment_test.go similarity index 72% rename from pkg/treatment/experiment_test.go rename to pkg/treatment/experiment/experiment_test.go index bc96a0706..ba9567044 100644 --- a/pkg/treatment/experiment_test.go +++ b/pkg/treatment/experiment/experiment_test.go @@ -1,38 +1,20 @@ -package treatment +package experiment import ( - "fmt" "math" - "strings" "testing" "time" pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" ) -func experimentFixture(now time.Time) (*pb.Experiment, *pb.Evidence) { - b := &pb.Evidence{Id: "baseline", AnalyzedAt: timestamppb.New(now.Add(-time.Hour)), WindowStart: timestamppb.New(now.Add(-2 * time.Hour)), WindowEnd: timestamppb.New(now.Add(-time.Hour)), QueryVersion: "v1", ArtifactSha256: strings.Repeat("a", 64)} - e := &pb.Experiment{Id: "exp", Location: "dc1", Hypothesis: "cubic improves throughput", Treatment: fixture(now).Assignments[0].Treatment, Policy: DefaultPolicy(), Baseline: b, Status: pb.ExperimentStatus_EXPERIMENT_STATUS_RUNNING, Revision: 2, Phases: []*pb.Phase{{TargetHosts: 4, IssuedAt: timestamppb.New(now.Add(-40 * time.Minute)), ObservationStartedAt: timestamppb.New(now.Add(-35 * time.Minute))}}} - for i := range 16 { - id := fmt.Sprintf("host-%d", i) - e.CohortOrder = append(e.CohortOrder, id) - b.Hosts = append(b.Hosts, &pb.HostEvidence{AgentId: id, Sockets: 100, ExposedSockets: 100, BytesAcked: 1 << 25, GoodputBps: 1e8, RttP95Ms: 1, CollectionHealthy: true}) - } - ev := proto.Clone(b).(*pb.Evidence) - ev.Id = "after" - ev.WindowStart = timestamppb.New(now.Add(-34 * time.Minute)) - ev.WindowEnd = timestamppb.New(now.Add(-time.Second)) - ev.AnalyzedAt = timestamppb.New(now) - for _, h := range ev.Hosts[:4] { - h.GoodputBps *= 1.3 - } - return e, ev -} func TestEvaluationBenefitHarmAndEvidenceGates(t *testing.T) { now := time.Now() - e, v := experimentFixture(now) + e, v := fixtures.Experiment(now, treatment.DefaultPolicy()) for _, tc := range []struct { name, want string change func(*pb.Experiment, *pb.Evidence) @@ -81,7 +63,7 @@ func TestHealthyEvidenceSoakRTTRegression(t *testing.T) { } { t.Run(tc.name, func(t *testing.T) { now := time.Now() - e, v := experimentFixture(now) + e, v := fixtures.Experiment(now, treatment.DefaultPolicy()) e.Policy.MaximumRttIncrease = tc.limit for _, h := range e.Baseline.Hosts { h.RttP95Ms = 5.305 @@ -99,7 +81,7 @@ func TestHealthyEvidenceSoakRTTRegression(t *testing.T) { } func TestAdvancePreservesInputAndRequiresReviewableEvidence(t *testing.T) { now := time.Now() - e, v := experimentFixture(now) + e, v := fixtures.Experiment(now, treatment.DefaultPolicy()) next, err := Advance(e, v, now, "operator", "reviewed") if err != nil { t.Fatal(err) diff --git a/pkg/treatment/internal/fixtures/fixtures.go b/pkg/treatment/internal/fixtures/fixtures.go new file mode 100644 index 000000000..39e95a25c --- /dev/null +++ b/pkg/treatment/internal/fixtures/fixtures.go @@ -0,0 +1,58 @@ +// Package fixtures holds protobuf test fixtures shared by the treatment +// packages' tests. It imports only generated types so that the contract +// package's own tests can use it without an import cycle. +package fixtures + +import ( + "fmt" + "strings" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" +) + +// Desired is a minimal valid desired state for agent host-1: one trial +// assignment of CUBIC to a single /32 with a one-hour lease. +func Desired(now time.Time) *pb.DesiredState { + return &pb.DesiredState{ + AgentId: "host-1", + Revision: 1, + IssuedAt: timestamppb.New(now), + Assignments: []*pb.Assignment{{ + Id: "trial-1", + ExperimentId: "trial-1", + Generation: 1, + Mode: pb.AssignmentMode_ASSIGNMENT_MODE_TRIAL, + LeaseExpiresAt: timestamppb.New(now.Add(time.Hour)), + Treatment: &pb.Treatment{ + Id: "cubic", + Revision: 1, + Destination: "192.0.2.1/32", + Settings: &pb.TCPSettings{CongestionControl: proto.String("cubic")}, + }, + }}, + } +} + +// Experiment is a running 16-host experiment at its first 4-host phase with +// baseline and a current evidence window in which the treated hosts gained 30%. +func Experiment(now time.Time, policy *pb.ExperimentPolicy) (*pb.Experiment, *pb.Evidence) { + b := &pb.Evidence{Id: "baseline", AnalyzedAt: timestamppb.New(now.Add(-time.Hour)), WindowStart: timestamppb.New(now.Add(-2 * time.Hour)), WindowEnd: timestamppb.New(now.Add(-time.Hour)), QueryVersion: "v1", ArtifactSha256: strings.Repeat("a", 64)} + e := &pb.Experiment{Id: "exp", Location: "dc1", Hypothesis: "cubic improves throughput", Treatment: Desired(now).Assignments[0].Treatment, Policy: policy, Baseline: b, Status: pb.ExperimentStatus_EXPERIMENT_STATUS_RUNNING, Revision: 2, Phases: []*pb.Phase{{TargetHosts: 4, IssuedAt: timestamppb.New(now.Add(-40 * time.Minute)), ObservationStartedAt: timestamppb.New(now.Add(-35 * time.Minute))}}} + for i := range 16 { + id := fmt.Sprintf("host-%d", i) + e.CohortOrder = append(e.CohortOrder, id) + b.Hosts = append(b.Hosts, &pb.HostEvidence{AgentId: id, Sockets: 100, ExposedSockets: 100, BytesAcked: 1 << 25, GoodputBps: 1e8, RttP95Ms: 1, CollectionHealthy: true}) + } + ev := proto.Clone(b).(*pb.Evidence) + ev.Id = "after" + ev.WindowStart = timestamppb.New(now.Add(-34 * time.Minute)) + ev.WindowEnd = timestamppb.New(now.Add(-time.Second)) + ev.AnalyzedAt = timestamppb.New(now) + for _, h := range ev.Hosts[:4] { + h.GoodputBps *= 1.3 + } + return e, ev +} diff --git a/pkg/treatment/reconcile_contracts_test.go b/pkg/treatment/reconcile/contracts_test.go similarity index 84% rename from pkg/treatment/reconcile_contracts_test.go rename to pkg/treatment/reconcile/contracts_test.go index 54aa0c3d4..44a858c6a 100644 --- a/pkg/treatment/reconcile_contracts_test.go +++ b/pkg/treatment/reconcile/contracts_test.go @@ -1,4 +1,4 @@ -package treatment +package reconcile import ( "context" @@ -8,6 +8,7 @@ import ( "time" pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -17,6 +18,8 @@ type faultBackend struct { fail string } +func (b *faultBackend) BeginReconcile() {} + func (b *faultBackend) Plan(ctx context.Context, t *pb.Treatment) ([]byte, error) { if b.fail == "plan" { return nil, errors.New("plan failed") @@ -49,19 +52,19 @@ func TestReconcileFailureRecovery(t *testing.T) { now := time.Unix(1700000000, 0) b := &faultBackend{fakeBackend: &fakeBackend{routes: map[string]bool{}}} r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot", Now: func() time.Time { return now }} - d := fixture(now) + d := fixtures.Desired(now) if tc.name == "plan" { - if _, err := r.Run(context.Background(), d, false); err != nil { + if _, err := r.Run(context.Background(), d, ModeFull); err != nil { t.Fatal(err) } } b.fail = tc.name - s, err := r.Run(context.Background(), d, false) + s, err := r.Run(context.Background(), d, ModeFull) if err == nil || !strings.Contains(err.Error(), tc.name+" failed") || s.Records[0].Status != pb.ApplyStatus_APPLY_STATUS_FAILED || b.enabled || len(b.routes) != tc.wantRoutes { t.Fatalf("%s: err=%v enabled=%t routes=%d state=%v", tc.description, err, b.enabled, len(b.routes), s) } b.fail = "" - s, err = r.Run(context.Background(), nil, false) + s, err = r.Run(context.Background(), nil, ModeFull) if err != nil || !b.enabled || len(b.routes) != 1 || s.Records[0].Status != pb.ApplyStatus_APPLY_STATUS_VERIFIED { t.Fatalf("retry did not recover: %v %v", s, err) } @@ -73,26 +76,26 @@ func TestReconcileRevisionContracts(t *testing.T) { for _, tc := range []struct { name, description string change func(*pb.DesiredState) - watchdog bool + mode Mode wantError string }{ - {"idempotent", "identical state verifies without reinstall", func(*pb.DesiredState) {}, false, ""}, - {"conflict", "same revision cannot carry different contents", func(d *pb.DesiredState) { d.Suspended = true }, false, "conflicting"}, - {"wrong-host", "another agent's state is rejected", func(d *pb.DesiredState) { d.AgentId = "other" }, false, "another agent"}, - {"watchdog", "watchdog cannot accept controller instructions", func(*pb.DesiredState) {}, true, "watchdog"}, + {"idempotent", "identical state verifies without reinstall", func(*pb.DesiredState) {}, ModeFull, ""}, + {"conflict", "same revision cannot carry different contents", func(d *pb.DesiredState) { d.Suspended = true }, ModeFull, "conflicting"}, + {"wrong-host", "another agent's state is rejected", func(d *pb.DesiredState) { d.AgentId = "other" }, ModeFull, "another agent"}, + {"watchdog", "watchdog cannot accept controller instructions", func(*pb.DesiredState) {}, ModeExpiryOnly, "watchdog"}, } { t.Run(tc.name, func(t *testing.T) { now := time.Unix(1700000000, 0) b := &fakeBackend{routes: map[string]bool{}} r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot", Now: func() time.Time { return now }} - d := fixture(now) - s, err := r.Run(context.Background(), d, false) + d := fixtures.Desired(now) + s, err := r.Run(context.Background(), d, ModeFull) if err != nil { t.Fatal(err) } original := proto.Clone(s.Desired) tc.change(d) - _, err = r.Run(context.Background(), d, tc.watchdog) + _, err = r.Run(context.Background(), d, tc.mode) if tc.wantError == "" { if err != nil { t.Fatal(err) @@ -130,9 +133,9 @@ func TestReconcileLeaseBoundary(t *testing.T) { now := time.Unix(1700000000, 0) b := &fakeBackend{routes: map[string]bool{}} r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot", Now: func() time.Time { return now }} - d := fixture(now) + d := fixtures.Desired(now) d.Assignments[0].LeaseExpiresAt = timestamppb.New(now.Add(tc.offset)) - s, err := r.Run(context.Background(), d, false) + s, err := r.Run(context.Background(), d, ModeFull) if err != nil || s.Records[0].Status != tc.want || len(b.routes) != tc.routes { t.Fatalf("got %v %v routes=%d; want %v routes=%d", s, err, len(b.routes), tc.want, tc.routes) } diff --git a/pkg/treatment/journal.go b/pkg/treatment/reconcile/journal.go similarity index 86% rename from pkg/treatment/journal.go rename to pkg/treatment/reconcile/journal.go index 3f96db3d2..a3a5c3574 100644 --- a/pkg/treatment/journal.go +++ b/pkg/treatment/reconcile/journal.go @@ -1,4 +1,4 @@ -package treatment +package reconcile import ( "bytes" @@ -9,6 +9,7 @@ import ( "path/filepath" pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "github.com/randomizedcoder/xtcp2/pkg/treatment" "golang.org/x/sys/unix" "google.golang.org/protobuf/encoding/prototext" ) @@ -34,7 +35,7 @@ func (j Journal) WithLock(fn func(*pb.LocalState, func() error) error) error { s := new(pb.LocalState) f, err := os.OpenFile(filepath.Join(j.Dir, "state.textproto"), os.O_RDONLY|unix.O_NOFOLLOW, 0) if err == nil { - b, rerr := io.ReadAll(io.LimitReader(f, MaxStateBytes+1)) + b, rerr := io.ReadAll(io.LimitReader(f, treatment.MaxStateBytes+1)) cerr := f.Close() if rerr != nil { return rerr @@ -42,30 +43,30 @@ func (j Journal) WithLock(fn func(*pb.LocalState, func() error) error) error { if cerr != nil { return cerr } - if len(b) > MaxStateBytes { + if len(b) > treatment.MaxStateBytes { return errors.New("journal too large") } if err = prototext.Unmarshal(b, s); err != nil { return fmt.Errorf("invalid treatment journal: %w", err) } if s.Desired != nil { - if err = ValidateDesired(s.Desired); err != nil { + if err = treatment.ValidateDesired(s.Desired); err != nil { return fmt.Errorf("invalid persisted desired state: %w", err) } } - if len(s.Records) > MaxAssignments*2 { + if len(s.Records) > treatment.MaxAssignments*2 { return errors.New("too many journal records") } seen := map[string]bool{} for _, r := range s.Records { - if r.Assignment == nil || !ValidID(r.Assignment.Id) { + if r.Assignment == nil || !treatment.ValidID(r.Assignment.Id) { return errors.New("invalid journal record") } if seen[r.Assignment.Id] { return errors.New("duplicate journal record") } seen[r.Assignment.Id] = true - if err = ValidateTreatment(r.Assignment.Treatment); err != nil { + if err = treatment.ValidateTreatment(r.Assignment.Treatment); err != nil { return fmt.Errorf("invalid journal treatment: %w", err) } } @@ -78,7 +79,7 @@ func (j Journal) WithLock(fn func(*pb.LocalState, func() error) error) error { if err != nil { return err } - if len(b) > MaxStateBytes { + if len(b) > treatment.MaxStateBytes { return errors.New("journal exceeds size limit") } if err = atomicFile(j.Dir, "state.textproto", b); err != nil { diff --git a/pkg/treatment/journal_test.go b/pkg/treatment/reconcile/journal_test.go similarity index 94% rename from pkg/treatment/journal_test.go rename to pkg/treatment/reconcile/journal_test.go index 3be1987bf..b29ecde02 100644 --- a/pkg/treatment/journal_test.go +++ b/pkg/treatment/reconcile/journal_test.go @@ -1,4 +1,4 @@ -package treatment +package reconcile import ( "errors" @@ -9,6 +9,8 @@ import ( "time" pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" "golang.org/x/sys/unix" "google.golang.org/protobuf/encoding/prototext" ) @@ -28,7 +30,7 @@ func TestJournalRejectsInvalidSnapshots(t *testing.T) { } { t.Run(tc.name, func(t *testing.T) { j := Journal{Dir: t.TempDir()} - d := fixture(time.Unix(1700000000, 0)) + d := fixtures.Desired(time.Unix(1700000000, 0)) s := &pb.LocalState{Desired: d, Records: []*pb.ApplicationRecord{{Assignment: d.Assignments[0]}}} tc.change(s) raw, err := prototext.Marshal(s) @@ -112,7 +114,7 @@ func TestJournalSizeLimit(t *testing.T) { if err != nil { t.Fatal(err) } - if err = f.Truncate(MaxStateBytes + 1); err != nil { + if err = f.Truncate(treatment.MaxStateBytes + 1); err != nil { t.Fatal(err) } if err = f.Close(); err != nil { diff --git a/pkg/treatment/reconcile.go b/pkg/treatment/reconcile/reconcile.go similarity index 85% rename from pkg/treatment/reconcile.go rename to pkg/treatment/reconcile/reconcile.go index e456f5c3f..68a7c0b82 100644 --- a/pkg/treatment/reconcile.go +++ b/pkg/treatment/reconcile/reconcile.go @@ -1,4 +1,4 @@ -package treatment +package reconcile import ( "bytes" @@ -8,14 +8,46 @@ import ( "time" pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "github.com/randomizedcoder/xtcp2/pkg/treatment" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" ) +// Package reconcile owns the host-side half of the treatment contract: the +// intent journal and the reconciler that drives a Backend from it. It is +// shared by the in-daemon/standalone agent and by the independent watchdog; +// it never speaks gRPC and never imports the controller. + +// Mode selects how much a reconciliation pass may do. +type Mode uint8 + +const ( + // ModeFull installs, verifies, removes and toggles owned rules. Used by + // the agent and by the local reconcile command. + ModeFull Mode = iota + // ModeExpiryOnly may only withdraw: expired trials, suspended or retired + // assignments and tombstones. It cannot accept desired state, install a + // new treatment or clear a suspension. Used by the independent watchdog. + ModeExpiryOnly +) + +func (m Mode) String() string { + switch m { + case ModeFull: + return "full" + case ModeExpiryOnly: + return "expiry-only" + } + return "unknown" +} + // Backend only accepts locally planned routes. Install must be create-exclusive // or an idempotent match; Remove must refuse changed/foreign routes. Enable and // Disable only touch the deployment's exact owned lookup rules. type Backend interface { + // BeginReconcile marks the start of one pass so a backend may drop any + // per-pass caches (kernel route snapshots) before Plan/Verify read them. + BeginReconcile() Plan(context.Context, *pb.Treatment) ([]byte, error) Install(context.Context, []byte) error Verify(context.Context, []byte) error @@ -40,22 +72,23 @@ func (r *Reconciler) now() time.Time { } // Run saves intent before touching the kernel and verifies before reporting -// success. expiryOnly is used by the independent watchdog and cannot install a -// new treatment or clear a suspension. -func (r *Reconciler) Run(ctx context.Context, in *pb.DesiredState, expiryOnly bool) (*pb.LocalState, error) { +// success. See Mode for what ModeExpiryOnly may and may not do. +func (r *Reconciler) Run(ctx context.Context, in *pb.DesiredState, mode Mode) (*pb.LocalState, error) { + if mode != ModeFull && mode != ModeExpiryOnly { + return nil, fmt.Errorf("unknown reconcile mode %d", mode) + } + expiryOnly := mode == ModeExpiryOnly var result *pb.LocalState err := r.Journal.WithLock(func(s *pb.LocalState, save func() error) error { defer func() { result = proto.Clone(s).(*pb.LocalState) }() - if b, ok := r.Backend.(interface{ BeginReconcile() }); ok { - b.BeginReconcile() - } + r.Backend.BeginReconcile() now := r.now() stamp := func() *timestamppb.Timestamp { return timestamppb.New(now) } if in != nil { if expiryOnly { return errors.New("watchdog cannot accept desired state") } - if err := ValidateDesired(in); err != nil { + if err := treatment.ValidateDesired(in); err != nil { return err } if in.AgentId != r.AgentID { diff --git a/pkg/treatment/reconcile_test.go b/pkg/treatment/reconcile/reconcile_test.go similarity index 77% rename from pkg/treatment/reconcile_test.go rename to pkg/treatment/reconcile/reconcile_test.go index a9b1e4d07..cfe4b5e6c 100644 --- a/pkg/treatment/reconcile_test.go +++ b/pkg/treatment/reconcile/reconcile_test.go @@ -1,4 +1,4 @@ -package treatment +package reconcile import ( "context" @@ -9,6 +9,7 @@ import ( "time" pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -22,6 +23,8 @@ type fakeBackend struct { onInstall func() } +func (f *fakeBackend) BeginReconcile() {} + func (f *fakeBackend) Plan(_ context.Context, t *pb.Treatment) ([]byte, error) { return []byte(t.Destination + ":" + t.Settings.GetCongestionControl()), nil } @@ -51,7 +54,7 @@ func TestFailedWithdrawalDisablesOwnedLookup(t *testing.T) { now := time.Now() b := &fakeBackend{routes: map[string]bool{}} r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot-1", Now: func() time.Time { return now }} - if _, e := r.Run(context.Background(), fixture(now), false); e != nil { + if _, e := r.Run(context.Background(), fixtures.Desired(now), ModeFull); e != nil { t.Fatal(e) } if !b.enabled { @@ -59,14 +62,14 @@ func TestFailedWithdrawalDisablesOwnedLookup(t *testing.T) { } now = now.Add(61 * time.Minute) b.failRemove = true - if _, e := r.Run(context.Background(), nil, true); e == nil { + if _, e := r.Run(context.Background(), nil, ModeExpiryOnly); e == nil { t.Fatal("withdrawal failure hidden") } if b.enabled { t.Fatal("expired route still selected after failed withdrawal") } b.failRemove = false - if _, e := r.Run(context.Background(), nil, false); e != nil { + if _, e := r.Run(context.Background(), nil, ModeFull); e != nil { t.Fatal(e) } if len(b.routes) != 0 || b.enabled { @@ -78,17 +81,17 @@ func TestStableRouteSurvivesOfflineReboot(t *testing.T) { now := time.Now() b := &fakeBackend{routes: map[string]bool{}} r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot-1", Now: func() time.Time { return now }} - d := fixture(now) + d := fixtures.Desired(now) d.Assignments[0].Mode = pb.AssignmentMode_ASSIGNMENT_MODE_STABLE d.Assignments[0].LeaseExpiresAt = nil - if _, e := r.Run(context.Background(), d, false); e != nil { + if _, e := r.Run(context.Background(), d, ModeFull); e != nil { t.Fatal(e) } now = now.Add(48 * time.Hour) r.BootID = "boot-2" b.routes = map[string]bool{} b.enabled = false - s, e := r.Run(context.Background(), nil, false) + s, e := r.Run(context.Background(), nil, ModeFull) if e != nil || len(b.routes) != 1 || !b.enabled || s.Records[0].InstalledBootId != "boot-2" { t.Fatal("offline reboot did not restore stable treatment", e) } @@ -96,10 +99,6 @@ func TestStableRouteSurvivesOfflineReboot(t *testing.T) { func (f *fakeBackend) Enable(context.Context) error { f.enabled = true; return nil } func (f *fakeBackend) Disable(context.Context) error { f.enabled = false; return nil } -func fixture(now time.Time) *pb.DesiredState { - return &pb.DesiredState{AgentId: "host-1", Revision: 1, IssuedAt: timestamppb.New(now), Assignments: []*pb.Assignment{{Id: "trial-1", ExperimentId: "trial-1", Generation: 1, Mode: pb.AssignmentMode_ASSIGNMENT_MODE_TRIAL, LeaseExpiresAt: timestamppb.New(now.Add(time.Hour)), Treatment: &pb.Treatment{Id: "cubic", Revision: 1, Destination: "192.0.2.1/32", Settings: &pb.TCPSettings{CongestionControl: proto.String("cubic")}}}}} -} - func TestJournalIntentPrecedesKernelAndCrashRecovery(t *testing.T) { now := time.Now() dir := t.TempDir() @@ -111,7 +110,7 @@ func TestJournalIntentPrecedesKernelAndCrashRecovery(t *testing.T) { t.Fatal("intent not durable before install", e) } } - s, e := r.Run(context.Background(), fixture(now), false) + s, e := r.Run(context.Background(), fixtures.Desired(now), ModeFull) if e == nil || s.Records[0].Status != pb.ApplyStatus_APPLY_STATUS_FAILED { t.Fatal("must report readback failure", s, e) } @@ -119,7 +118,7 @@ func TestJournalIntentPrecedesKernelAndCrashRecovery(t *testing.T) { // the journal to recover without needing a controller or another command. b.failVerify = false r2 := *r - s, e = r2.Run(context.Background(), nil, false) + s, e = r2.Run(context.Background(), nil, ModeFull) if e != nil || s.Records[0].Status != pb.ApplyStatus_APPLY_STATUS_VERIFIED || len(b.routes) != 1 { t.Fatal("restart did not converge", s, e) } @@ -129,18 +128,18 @@ func TestTrialExpiryCannotBeResurrectedByRenewalOrRestart(t *testing.T) { now := time.Now() b := &fakeBackend{routes: map[string]bool{}} r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot-1", Now: func() time.Time { return now }} - d := fixture(now) - if _, e := r.Run(context.Background(), d, false); e != nil { + d := fixtures.Desired(now) + if _, e := r.Run(context.Background(), d, ModeFull); e != nil { t.Fatal(e) } now = now.Add(61 * time.Minute) - s, e := r.Run(context.Background(), nil, true) + s, e := r.Run(context.Background(), nil, ModeExpiryOnly) if e != nil || len(b.routes) != 0 || s.Records[0].Status != pb.ApplyStatus_APPLY_STATUS_EXPIRED { t.Fatal("watchdog expiry", s, e) } d.Revision++ d.Assignments[0].LeaseExpiresAt = timestamppb.New(now.Add(time.Hour)) - if _, e = r.Run(context.Background(), d, false); e != nil { + if _, e = r.Run(context.Background(), d, ModeFull); e != nil { t.Fatal(e) } if len(b.routes) != 0 { @@ -148,7 +147,7 @@ func TestTrialExpiryCannotBeResurrectedByRenewalOrRestart(t *testing.T) { } d.Revision++ d.Assignments[0].Generation++ - if _, e = r.Run(context.Background(), d, false); e != nil { + if _, e = r.Run(context.Background(), d, ModeFull); e != nil { t.Fatal(e) } if len(b.routes) != 1 { @@ -160,22 +159,22 @@ func TestPersistentHoldAndTombstones(t *testing.T) { now := time.Now() b := &fakeBackend{routes: map[string]bool{}} r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot-1"} - d := fixture(now) + d := fixtures.Desired(now) d.Assignments[0].Mode = pb.AssignmentMode_ASSIGNMENT_MODE_STABLE - if _, e := r.Run(context.Background(), d, false); e != nil { + if _, e := r.Run(context.Background(), d, ModeFull); e != nil { t.Fatal(e) } if e := r.SuspendLocal(true, "incident test"); e != nil { t.Fatal(e) } - if _, e := r.Run(context.Background(), nil, false); e != nil { + if _, e := r.Run(context.Background(), nil, ModeFull); e != nil { t.Fatal(e) } if b.enabled || len(b.routes) != 0 { t.Fatal("hold did not restore baseline") } d.Revision++ - if _, e := r.Run(context.Background(), d, false); e != nil { + if _, e := r.Run(context.Background(), d, ModeFull); e != nil { t.Fatal(e) } if len(b.routes) != 0 { @@ -186,17 +185,17 @@ func TestPersistentHoldAndTombstones(t *testing.T) { } d.Revision++ d.Assignments = nil - if _, e := r.Run(context.Background(), d, false); e != nil { + if _, e := r.Run(context.Background(), d, ModeFull); e != nil { t.Fatal(e) } - if _, e := r.Run(context.Background(), nil, false); e != nil { + if _, e := r.Run(context.Background(), nil, ModeFull); e != nil { t.Fatal(e) } if len(b.routes) != 0 { t.Fatal("orphan projection resurrected treatment") } d.Revision-- - if _, e := r.Run(context.Background(), d, false); e == nil { + if _, e := r.Run(context.Background(), d, ModeFull); e == nil { t.Fatal("accepted stale desired state") } } @@ -208,7 +207,7 @@ func TestCorruptJournalDoesNotInstall(t *testing.T) { } b := &fakeBackend{routes: map[string]bool{}} r := &Reconciler{Journal: Journal{Dir: dir}, Backend: b, AgentID: "host-1"} - if _, e := r.Run(context.Background(), fixture(time.Now()), false); e == nil { + if _, e := r.Run(context.Background(), fixtures.Desired(time.Now()), ModeFull); e == nil { t.Fatal("corrupt journal overwritten") } if b.installs != 0 { @@ -220,22 +219,22 @@ func TestPredecessorExpiryAndGenerationRejectionPreserveJournal(t *testing.T) { now := time.Now() b := &fakeBackend{routes: map[string]bool{}} r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot-1", Now: func() time.Time { return now }} - d := fixture(now) + d := fixtures.Desired(now) a := d.Assignments[0] a.Id = "state" a.Generation = 2 a.Predecessor = proto.Clone(a.Treatment).(*pb.Treatment) a.Predecessor.Settings.CongestionControl = proto.String("bbr") - if _, e := r.Run(context.Background(), d, false); e != nil { + if _, e := r.Run(context.Background(), d, ModeFull); e != nil { t.Fatal(e) } now = now.Add(61 * time.Minute) - s, e := r.Run(context.Background(), nil, true) + s, e := r.Run(context.Background(), nil, ModeExpiryOnly) if e != nil || !b.routes["192.0.2.1/32:bbr"] || s.Records[0].Status != pb.ApplyStatus_APPLY_STATUS_EXPIRED { t.Fatal("predecessor not restored", e) } installs := b.installs - if _, e = r.Run(context.Background(), nil, true); e != nil { + if _, e = r.Run(context.Background(), nil, ModeExpiryOnly); e != nil { t.Fatal(e) } if b.installs != installs { @@ -244,7 +243,7 @@ func TestPredecessorExpiryAndGenerationRejectionPreserveJournal(t *testing.T) { bad := proto.Clone(d).(*pb.DesiredState) bad.Revision++ bad.Assignments[0].Generation-- - if _, e = r.Run(context.Background(), bad, false); e == nil { + if _, e = r.Run(context.Background(), bad, ModeFull); e == nil { t.Fatal("stale generation accepted") } if e = r.Journal.WithLock(func(s *pb.LocalState, _ func() error) error { @@ -257,7 +256,7 @@ func TestPredecessorExpiryAndGenerationRejectionPreserveJournal(t *testing.T) { } d.Revision++ a.Mode = pb.AssignmentMode_ASSIGNMENT_MODE_RETIRED - if _, e = r.Run(context.Background(), d, false); e != nil { + if _, e = r.Run(context.Background(), d, ModeFull); e != nil { t.Fatal(e) } if len(b.routes) != 0 { diff --git a/pkg/treatment/validate_test.go b/pkg/treatment/validate_test.go index a6a63cc4d..d9498b16e 100644 --- a/pkg/treatment/validate_test.go +++ b/pkg/treatment/validate_test.go @@ -8,6 +8,7 @@ import ( "time" pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/timestamppb" @@ -80,7 +81,7 @@ func TestTreatmentValidation(t *testing.T) { } cases = append(cases, validationCase[*pb.Treatment]{tc.destination, "only unicast host destinations may be treated", func(p **pb.Treatment) { (*p).Destination = tc.destination }, want}) } - checkValidation(t, func() *pb.Treatment { return fixture(time.Unix(1700000000, 0)).Assignments[0].Treatment }, ValidateTreatment, cases) + checkValidation(t, func() *pb.Treatment { return fixtures.Desired(time.Unix(1700000000, 0)).Assignments[0].Treatment }, ValidateTreatment, cases) } func TestTCPSettingsValidation(t *testing.T) { @@ -238,7 +239,7 @@ func TestEvidenceValidation(t *testing.T) { } }, want}) } - checkValidation(t, func() *pb.Evidence { _, v := experimentFixture(now); return v }, ValidateEvidence, cases) + checkValidation(t, func() *pb.Evidence { _, v := fixtures.Experiment(now, DefaultPolicy()); return v }, ValidateEvidence, cases) } func TestDesiredValidation(t *testing.T) { @@ -298,12 +299,12 @@ func TestDesiredValidation(t *testing.T) { } }, want}) } - checkValidation(t, func() *pb.DesiredState { return fixture(now) }, ValidateDesired, cases) + checkValidation(t, func() *pb.DesiredState { return fixtures.Desired(now) }, ValidateDesired, cases) } func TestProposalValidation(t *testing.T) { now := time.Unix(1700000000, 0) - checkValidation(t, func() *pb.Experiment { e, _ := experimentFixture(now); return e }, ValidateProposal, []validationCase[*pb.Experiment]{ + checkValidation(t, func() *pb.Experiment { e, _ := fixtures.Experiment(now, DefaultPolicy()); return e }, ValidateProposal, []validationCase[*pb.Experiment]{ {"valid", "complete proposal is reviewable", func(**pb.Experiment) {}, ""}, {"nil", "proposal is required", func(p **pb.Experiment) { *p = nil }, "identity"}, {"no-hypothesis", "proposal must state a hypothesis", func(p **pb.Experiment) { (*p).Hypothesis = "" }, "identity"}, diff --git a/tools/tcp-controller-integration/soak.go b/tools/tcp-controller-integration/soak.go index 149a82482..2d67f43c3 100644 --- a/tools/tcp-controller-integration/soak.go +++ b/tools/tcp-controller-integration/soak.go @@ -11,6 +11,7 @@ import ( pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/experiment" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" ) @@ -178,7 +179,7 @@ func validateSoakRollback(e *pb.Experiment, injectedLatency bool) error { if err := treatment.ValidatePolicy(e.Policy); err != nil { return err } - _, harmful := treatment.HealthyEvidence(e, e.LatestEvidence, e.LatestEvidence.AnalyzedAt.AsTime()) + _, harmful := experiment.HealthyEvidence(e, e.LatestEvidence, e.LatestEvidence.AnalyzedAt.AsTime()) if !harmful { return fmt.Errorf("rollback evidence does not cross a safety guard") } From 460164b1819ec0cf26d964710565c5aec5ea4f8e Mon Sep 17 00:00:00 2001 From: "randomizedcoder dave.seddon.ca@gmail.com" Date: Sun, 25 Oct 2026 16:54:49 -0700 Subject: [PATCH 04/27] refactor(treatment): move the proto to xtcp_treatment/v1 with validation rules proto/xtcp/controller/v1/controller.proto nested the path unlike every other proto in the repo, generated a Go package whose name no longer matched the path, and carried no buf.validate rules: all input checking was hand-written. Move it to proto/xtcp_treatment/v1 as package xtcp_treatment.v1, split into treatment.proto (types), agent.proto (AgentControlService) and operator.proto (OperatorService), generated as gen/go/xtcp_treatment. The 37 Go importers keep their pb alias. Every client-controlled field now carries a rule that mirrors the bound in pkg/treatment/validate.go: identifier pattern, revision and generation > 0, TCP parameter ranges, host-prefix destinations, evidence interval ordering and digest shape, policy ranges with the observation-within-phase relation, cohort size and uniqueness, reason and request_id limits, exactly-one control selector, and the reserved ".result" request_id suffix. Contextual checks that span messages stay in Go. A new per-RPC option (xtcp_treatment.v1.role) declares which certificate class each method requires; enforcement moves into an interceptor in a later commit. pkg/treatment/proto_rules_test.go pins the mirror with positive, negative, boundary and corner rows through protovalidate, and TestEveryRPCDeclaresARole fails if a method is added without a role. Generated outputs for the other protos were left untouched: the local toolchain is one protoc-gen-go patch version newer than what produced them, and that drift is not this change's business. Co-Authored-By: Claude Fable 5.1 --- cmd/xtcp2-controller/main.go | 2 +- cmd/xtcp2-controllerctl/main.go | 2 +- docs/tcp-controller.md | 2 +- gen/cpp/xtcp_treatment/v1/agent.grpc.pb.cc | 167 + gen/cpp/xtcp_treatment/v1/agent.grpc.pb.h | 566 + gen/cpp/xtcp_treatment/v1/agent.pb.cc | 2576 +++++ gen/cpp/xtcp_treatment/v1/agent.pb.h | 2167 ++++ gen/cpp/xtcp_treatment/v1/operator.grpc.pb.cc | 258 + gen/cpp/xtcp_treatment/v1/operator.grpc.pb.h | 881 ++ gen/cpp/xtcp_treatment/v1/operator.pb.cc | 4651 ++++++++ gen/cpp/xtcp_treatment/v1/operator.pb.h | 4202 ++++++++ .../xtcp_treatment/v1/treatment.grpc.pb.cc | 29 + gen/cpp/xtcp_treatment/v1/treatment.grpc.pb.h | 56 + gen/cpp/xtcp_treatment/v1/treatment.pb.cc | 8186 ++++++++++++++ gen/cpp/xtcp_treatment/v1/treatment.pb.h | 9572 +++++++++++++++++ gen/dart/xtcp_treatment/v1/agent.pb.dart | 466 + gen/dart/xtcp_treatment/v1/agent.pbenum.dart | 11 + gen/dart/xtcp_treatment/v1/agent.pbgrpc.dart | 134 + gen/dart/xtcp_treatment/v1/agent.pbjson.dart | 146 + gen/dart/xtcp_treatment/v1/operator.pb.dart | 820 ++ .../xtcp_treatment/v1/operator.pbenum.dart | 49 + .../xtcp_treatment/v1/operator.pbgrpc.dart | 180 + .../xtcp_treatment/v1/operator.pbjson.dart | 320 + gen/dart/xtcp_treatment/v1/treatment.pb.dart | 1799 ++++ .../xtcp_treatment/v1/treatment.pbenum.dart | 145 + .../xtcp_treatment/v1/treatment.pbjson.dart | 911 ++ gen/go/xtcp_controller/controller.pb.go | 2712 ----- gen/go/xtcp_treatment/agent.pb.go | 440 + gen/go/xtcp_treatment/agent.pb.gw.go | 272 + gen/go/xtcp_treatment/agent_grpc.pb.go | 209 + gen/go/xtcp_treatment/agent_vtproto.pb.go | 1072 ++ gen/go/xtcp_treatment/operator.pb.go | 824 ++ gen/go/xtcp_treatment/operator.pb.gw.go | 421 + .../operator_grpc.pb.go} | 218 +- gen/go/xtcp_treatment/operator_vtproto.pb.go | 2099 ++++ gen/go/xtcp_treatment/treatment.pb.go | 1772 +++ .../treatment_vtproto.pb.go} | 6399 +++-------- .../xtcp_treatment/v1/agent.swagger.json | 49 + .../xtcp_treatment/v1/operator.swagger.json | 49 + .../xtcp_treatment/v1/treatment.swagger.json | 44 + gen/python/xtcp_treatment/v1/agent_pb2.py | 69 + gen/python/xtcp_treatment/v1/agent_pb2.pyi | 57 + .../xtcp_treatment/v1/agent_pb2_grpc.py | 163 + gen/python/xtcp_treatment/v1/operator_pb2.py | 106 + gen/python/xtcp_treatment/v1/operator_pb2.pyi | 120 + .../xtcp_treatment/v1/operator_pb2_grpc.py | 249 + gen/python/xtcp_treatment/v1/treatment_pb2.py | 207 + .../xtcp_treatment/v1/treatment_pb2.pyi | 304 + .../xtcp_treatment/v1/treatment_pb2_grpc.py | 4 + pkg/treatment/agent/agent.go | 2 +- pkg/treatment/agent/agent_test.go | 2 +- pkg/treatment/agent/backend_linux.go | 2 +- pkg/treatment/agent/benchmark_test.go | 2 +- pkg/treatment/agent/contracts_test.go | 2 +- pkg/treatment/agent/lifecycle_test.go | 2 +- pkg/treatment/boundaries_test.go | 2 +- pkg/treatment/controller/analyze.go | 2 +- pkg/treatment/controller/clickhouse.go | 2 +- pkg/treatment/controller/clickhouse_test.go | 2 +- pkg/treatment/controller/discovery.go | 2 +- .../controller/discovery_contracts_test.go | 2 +- pkg/treatment/controller/discovery_test.go | 2 +- pkg/treatment/controller/maintenance.go | 2 +- .../controller/postgres_indexes_test.go | 2 +- .../controller/rpc_contracts_test.go | 2 +- pkg/treatment/controller/server.go | 2 +- pkg/treatment/controller/server_test.go | 2 +- pkg/treatment/controller/store.go | 2 +- pkg/treatment/experiment/benchmark_test.go | 2 +- pkg/treatment/experiment/boundaries_test.go | 2 +- pkg/treatment/experiment/experiment.go | 2 +- pkg/treatment/experiment/experiment_test.go | 2 +- pkg/treatment/identity/mtls_test.go | 2 +- pkg/treatment/internal/fixtures/fixtures.go | 2 +- pkg/treatment/proto_rules_test.go | 188 + pkg/treatment/reconcile/contracts_test.go | 2 +- pkg/treatment/reconcile/journal.go | 2 +- pkg/treatment/reconcile/journal_test.go | 2 +- pkg/treatment/reconcile/reconcile.go | 2 +- pkg/treatment/reconcile/reconcile_test.go | 2 +- pkg/treatment/validate.go | 2 +- pkg/treatment/validate_test.go | 2 +- proto/xtcp/controller/v1/controller.proto | 240 - proto/xtcp_treatment/v1/agent.proto | 58 + proto/xtcp_treatment/v1/operator.proto | 122 + proto/xtcp_treatment/v1/treatment.proto | 281 + tools/tcp-controller-integration/main.go | 2 +- tools/tcp-controller-integration/nearby.go | 2 +- tools/tcp-controller-integration/soak.go | 2 +- tools/tcp-controller-integration/soak_test.go | 2 +- 90 files changed, 49162 insertions(+), 7956 deletions(-) create mode 100644 gen/cpp/xtcp_treatment/v1/agent.grpc.pb.cc create mode 100644 gen/cpp/xtcp_treatment/v1/agent.grpc.pb.h create mode 100644 gen/cpp/xtcp_treatment/v1/agent.pb.cc create mode 100644 gen/cpp/xtcp_treatment/v1/agent.pb.h create mode 100644 gen/cpp/xtcp_treatment/v1/operator.grpc.pb.cc create mode 100644 gen/cpp/xtcp_treatment/v1/operator.grpc.pb.h create mode 100644 gen/cpp/xtcp_treatment/v1/operator.pb.cc create mode 100644 gen/cpp/xtcp_treatment/v1/operator.pb.h create mode 100644 gen/cpp/xtcp_treatment/v1/treatment.grpc.pb.cc create mode 100644 gen/cpp/xtcp_treatment/v1/treatment.grpc.pb.h create mode 100644 gen/cpp/xtcp_treatment/v1/treatment.pb.cc create mode 100644 gen/cpp/xtcp_treatment/v1/treatment.pb.h create mode 100644 gen/dart/xtcp_treatment/v1/agent.pb.dart create mode 100644 gen/dart/xtcp_treatment/v1/agent.pbenum.dart create mode 100644 gen/dart/xtcp_treatment/v1/agent.pbgrpc.dart create mode 100644 gen/dart/xtcp_treatment/v1/agent.pbjson.dart create mode 100644 gen/dart/xtcp_treatment/v1/operator.pb.dart create mode 100644 gen/dart/xtcp_treatment/v1/operator.pbenum.dart create mode 100644 gen/dart/xtcp_treatment/v1/operator.pbgrpc.dart create mode 100644 gen/dart/xtcp_treatment/v1/operator.pbjson.dart create mode 100644 gen/dart/xtcp_treatment/v1/treatment.pb.dart create mode 100644 gen/dart/xtcp_treatment/v1/treatment.pbenum.dart create mode 100644 gen/dart/xtcp_treatment/v1/treatment.pbjson.dart delete mode 100644 gen/go/xtcp_controller/controller.pb.go create mode 100644 gen/go/xtcp_treatment/agent.pb.go create mode 100644 gen/go/xtcp_treatment/agent.pb.gw.go create mode 100644 gen/go/xtcp_treatment/agent_grpc.pb.go create mode 100644 gen/go/xtcp_treatment/agent_vtproto.pb.go create mode 100644 gen/go/xtcp_treatment/operator.pb.go create mode 100644 gen/go/xtcp_treatment/operator.pb.gw.go rename gen/go/{xtcp_controller/controller_grpc.pb.go => xtcp_treatment/operator_grpc.pb.go} (51%) create mode 100644 gen/go/xtcp_treatment/operator_vtproto.pb.go create mode 100644 gen/go/xtcp_treatment/treatment.pb.go rename gen/go/{xtcp_controller/controller_vtproto.pb.go => xtcp_treatment/treatment_vtproto.pb.go} (61%) create mode 100644 gen/openapi/xtcp_treatment/v1/agent.swagger.json create mode 100644 gen/openapi/xtcp_treatment/v1/operator.swagger.json create mode 100644 gen/openapi/xtcp_treatment/v1/treatment.swagger.json create mode 100644 gen/python/xtcp_treatment/v1/agent_pb2.py create mode 100644 gen/python/xtcp_treatment/v1/agent_pb2.pyi create mode 100644 gen/python/xtcp_treatment/v1/agent_pb2_grpc.py create mode 100644 gen/python/xtcp_treatment/v1/operator_pb2.py create mode 100644 gen/python/xtcp_treatment/v1/operator_pb2.pyi create mode 100644 gen/python/xtcp_treatment/v1/operator_pb2_grpc.py create mode 100644 gen/python/xtcp_treatment/v1/treatment_pb2.py create mode 100644 gen/python/xtcp_treatment/v1/treatment_pb2.pyi create mode 100644 gen/python/xtcp_treatment/v1/treatment_pb2_grpc.py create mode 100644 pkg/treatment/proto_rules_test.go delete mode 100644 proto/xtcp/controller/v1/controller.proto create mode 100644 proto/xtcp_treatment/v1/agent.proto create mode 100644 proto/xtcp_treatment/v1/operator.proto create mode 100644 proto/xtcp_treatment/v1/treatment.proto diff --git a/cmd/xtcp2-controller/main.go b/cmd/xtcp2-controller/main.go index 0b626c216..7403df5e0 100644 --- a/cmd/xtcp2-controller/main.go +++ b/cmd/xtcp2-controller/main.go @@ -16,7 +16,7 @@ import ( "syscall" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/controller" "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" diff --git a/cmd/xtcp2-controllerctl/main.go b/cmd/xtcp2-controllerctl/main.go index 5e1904a87..2bbbb3b41 100644 --- a/cmd/xtcp2-controllerctl/main.go +++ b/cmd/xtcp2-controllerctl/main.go @@ -8,7 +8,7 @@ import ( "os" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" "google.golang.org/grpc" diff --git a/docs/tcp-controller.md b/docs/tcp-controller.md index 2b59d4fcc..b36533f28 100644 --- a/docs/tcp-controller.md +++ b/docs/tcp-controller.md @@ -24,7 +24,7 @@ flowchart LR The implementation lives under [pkg/treatment](../pkg/treatment), split by audience so that each binary links only the half of the contract it needs, with -a typed [protobuf contract](../proto/xtcp/controller/v1/controller.proto): +a typed [protobuf contract](../proto/xtcp_treatment/v1/) (`xtcp_treatment.v1`: `treatment.proto` types, `agent.proto`, `operator.proto`): | Package | Used by | Contents | | --- | --- | --- | diff --git a/gen/cpp/xtcp_treatment/v1/agent.grpc.pb.cc b/gen/cpp/xtcp_treatment/v1/agent.grpc.pb.cc new file mode 100644 index 000000000..75c16bd26 --- /dev/null +++ b/gen/cpp/xtcp_treatment/v1/agent.grpc.pb.cc @@ -0,0 +1,167 @@ +// Generated by the gRPC C++ plugin. +// If you make any local change, they will be lost. +// source: xtcp_treatment/v1/agent.proto + +#include "xtcp_treatment/v1/agent.pb.h" +#include "xtcp_treatment/v1/agent.grpc.pb.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +namespace xtcp_treatment { +namespace v1 { + +static const char* AgentControlService_method_names[] = { + "/xtcp_treatment.v1.AgentControlService/CheckIn", + "/xtcp_treatment.v1.AgentControlService/GetDesiredState", + "/xtcp_treatment.v1.AgentControlService/WatchDesiredState", +}; + +std::unique_ptr< AgentControlService::Stub> AgentControlService::NewStub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options) { + (void)options; + std::unique_ptr< AgentControlService::Stub> stub(new AgentControlService::Stub(channel, options)); + return stub; +} + +AgentControlService::Stub::Stub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options) + : channel_(channel), rpcmethod_CheckIn_(AgentControlService_method_names[0], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel) + , rpcmethod_GetDesiredState_(AgentControlService_method_names[1], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel) + , rpcmethod_WatchDesiredState_(AgentControlService_method_names[2], options.suffix_for_stats(),::grpc::internal::RpcMethod::SERVER_STREAMING, channel) + {} + +::grpc::Status AgentControlService::Stub::CheckIn(::grpc::ClientContext* context, const ::xtcp_treatment::v1::CheckInRequest& request, ::xtcp_treatment::v1::CheckInResponse* response) { + return ::grpc::internal::BlockingUnaryCall< ::xtcp_treatment::v1::CheckInRequest, ::xtcp_treatment::v1::CheckInResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_CheckIn_, context, request, response); +} + +void AgentControlService::Stub::async::CheckIn(::grpc::ClientContext* context, const ::xtcp_treatment::v1::CheckInRequest* request, ::xtcp_treatment::v1::CheckInResponse* response, std::function f) { + ::grpc::internal::CallbackUnaryCall< ::xtcp_treatment::v1::CheckInRequest, ::xtcp_treatment::v1::CheckInResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_CheckIn_, context, request, response, std::move(f)); +} + +void AgentControlService::Stub::async::CheckIn(::grpc::ClientContext* context, const ::xtcp_treatment::v1::CheckInRequest* request, ::xtcp_treatment::v1::CheckInResponse* response, ::grpc::ClientUnaryReactor* reactor) { + ::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_CheckIn_, context, request, response, reactor); +} + +::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::CheckInResponse>* AgentControlService::Stub::PrepareAsyncCheckInRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::CheckInRequest& request, ::grpc::CompletionQueue* cq) { + return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::xtcp_treatment::v1::CheckInResponse, ::xtcp_treatment::v1::CheckInRequest, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_CheckIn_, context, request); +} + +::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::CheckInResponse>* AgentControlService::Stub::AsyncCheckInRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::CheckInRequest& request, ::grpc::CompletionQueue* cq) { + auto* result = + this->PrepareAsyncCheckInRaw(context, request, cq); + result->StartCall(); + return result; +} + +::grpc::Status AgentControlService::Stub::GetDesiredState(::grpc::ClientContext* context, const ::xtcp_treatment::v1::GetDesiredStateRequest& request, ::xtcp_treatment::v1::GetDesiredStateResponse* response) { + return ::grpc::internal::BlockingUnaryCall< ::xtcp_treatment::v1::GetDesiredStateRequest, ::xtcp_treatment::v1::GetDesiredStateResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_GetDesiredState_, context, request, response); +} + +void AgentControlService::Stub::async::GetDesiredState(::grpc::ClientContext* context, const ::xtcp_treatment::v1::GetDesiredStateRequest* request, ::xtcp_treatment::v1::GetDesiredStateResponse* response, std::function f) { + ::grpc::internal::CallbackUnaryCall< ::xtcp_treatment::v1::GetDesiredStateRequest, ::xtcp_treatment::v1::GetDesiredStateResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_GetDesiredState_, context, request, response, std::move(f)); +} + +void AgentControlService::Stub::async::GetDesiredState(::grpc::ClientContext* context, const ::xtcp_treatment::v1::GetDesiredStateRequest* request, ::xtcp_treatment::v1::GetDesiredStateResponse* response, ::grpc::ClientUnaryReactor* reactor) { + ::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_GetDesiredState_, context, request, response, reactor); +} + +::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::GetDesiredStateResponse>* AgentControlService::Stub::PrepareAsyncGetDesiredStateRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::GetDesiredStateRequest& request, ::grpc::CompletionQueue* cq) { + return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::xtcp_treatment::v1::GetDesiredStateResponse, ::xtcp_treatment::v1::GetDesiredStateRequest, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_GetDesiredState_, context, request); +} + +::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::GetDesiredStateResponse>* AgentControlService::Stub::AsyncGetDesiredStateRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::GetDesiredStateRequest& request, ::grpc::CompletionQueue* cq) { + auto* result = + this->PrepareAsyncGetDesiredStateRaw(context, request, cq); + result->StartCall(); + return result; +} + +::grpc::ClientReader< ::xtcp_treatment::v1::WatchDesiredStateResponse>* AgentControlService::Stub::WatchDesiredStateRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::WatchDesiredStateRequest& request) { + return ::grpc::internal::ClientReaderFactory< ::xtcp_treatment::v1::WatchDesiredStateResponse>::Create(channel_.get(), rpcmethod_WatchDesiredState_, context, request); +} + +void AgentControlService::Stub::async::WatchDesiredState(::grpc::ClientContext* context, const ::xtcp_treatment::v1::WatchDesiredStateRequest* request, ::grpc::ClientReadReactor< ::xtcp_treatment::v1::WatchDesiredStateResponse>* reactor) { + ::grpc::internal::ClientCallbackReaderFactory< ::xtcp_treatment::v1::WatchDesiredStateResponse>::Create(stub_->channel_.get(), stub_->rpcmethod_WatchDesiredState_, context, request, reactor); +} + +::grpc::ClientAsyncReader< ::xtcp_treatment::v1::WatchDesiredStateResponse>* AgentControlService::Stub::AsyncWatchDesiredStateRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::WatchDesiredStateRequest& request, ::grpc::CompletionQueue* cq, void* tag) { + return ::grpc::internal::ClientAsyncReaderFactory< ::xtcp_treatment::v1::WatchDesiredStateResponse>::Create(channel_.get(), cq, rpcmethod_WatchDesiredState_, context, request, true, tag); +} + +::grpc::ClientAsyncReader< ::xtcp_treatment::v1::WatchDesiredStateResponse>* AgentControlService::Stub::PrepareAsyncWatchDesiredStateRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::WatchDesiredStateRequest& request, ::grpc::CompletionQueue* cq) { + return ::grpc::internal::ClientAsyncReaderFactory< ::xtcp_treatment::v1::WatchDesiredStateResponse>::Create(channel_.get(), cq, rpcmethod_WatchDesiredState_, context, request, false, nullptr); +} + +AgentControlService::Service::Service() { + AddMethod(new ::grpc::internal::RpcServiceMethod( + AgentControlService_method_names[0], + ::grpc::internal::RpcMethod::NORMAL_RPC, + new ::grpc::internal::RpcMethodHandler< AgentControlService::Service, ::xtcp_treatment::v1::CheckInRequest, ::xtcp_treatment::v1::CheckInResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>( + [](AgentControlService::Service* service, + ::grpc::ServerContext* ctx, + const ::xtcp_treatment::v1::CheckInRequest* req, + ::xtcp_treatment::v1::CheckInResponse* resp) { + return service->CheckIn(ctx, req, resp); + }, this))); + AddMethod(new ::grpc::internal::RpcServiceMethod( + AgentControlService_method_names[1], + ::grpc::internal::RpcMethod::NORMAL_RPC, + new ::grpc::internal::RpcMethodHandler< AgentControlService::Service, ::xtcp_treatment::v1::GetDesiredStateRequest, ::xtcp_treatment::v1::GetDesiredStateResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>( + [](AgentControlService::Service* service, + ::grpc::ServerContext* ctx, + const ::xtcp_treatment::v1::GetDesiredStateRequest* req, + ::xtcp_treatment::v1::GetDesiredStateResponse* resp) { + return service->GetDesiredState(ctx, req, resp); + }, this))); + AddMethod(new ::grpc::internal::RpcServiceMethod( + AgentControlService_method_names[2], + ::grpc::internal::RpcMethod::SERVER_STREAMING, + new ::grpc::internal::ServerStreamingHandler< AgentControlService::Service, ::xtcp_treatment::v1::WatchDesiredStateRequest, ::xtcp_treatment::v1::WatchDesiredStateResponse>( + [](AgentControlService::Service* service, + ::grpc::ServerContext* ctx, + const ::xtcp_treatment::v1::WatchDesiredStateRequest* req, + ::grpc::ServerWriter<::xtcp_treatment::v1::WatchDesiredStateResponse>* writer) { + return service->WatchDesiredState(ctx, req, writer); + }, this))); +} + +AgentControlService::Service::~Service() { +} + +::grpc::Status AgentControlService::Service::CheckIn(::grpc::ServerContext* context, const ::xtcp_treatment::v1::CheckInRequest* request, ::xtcp_treatment::v1::CheckInResponse* response) { + (void) context; + (void) request; + (void) response; + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); +} + +::grpc::Status AgentControlService::Service::GetDesiredState(::grpc::ServerContext* context, const ::xtcp_treatment::v1::GetDesiredStateRequest* request, ::xtcp_treatment::v1::GetDesiredStateResponse* response) { + (void) context; + (void) request; + (void) response; + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); +} + +::grpc::Status AgentControlService::Service::WatchDesiredState(::grpc::ServerContext* context, const ::xtcp_treatment::v1::WatchDesiredStateRequest* request, ::grpc::ServerWriter< ::xtcp_treatment::v1::WatchDesiredStateResponse>* writer) { + (void) context; + (void) request; + (void) writer; + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); +} + + +} // namespace xtcp_treatment +} // namespace v1 +#include + diff --git a/gen/cpp/xtcp_treatment/v1/agent.grpc.pb.h b/gen/cpp/xtcp_treatment/v1/agent.grpc.pb.h new file mode 100644 index 000000000..6a60c3880 --- /dev/null +++ b/gen/cpp/xtcp_treatment/v1/agent.grpc.pb.h @@ -0,0 +1,566 @@ +// Generated by the gRPC C++ plugin. +// If you make any local change, they will be lost. +// source: xtcp_treatment/v1/agent.proto +// Original file comments: +// +// xTCP - treatment agent service +// +// The outbound-only RPC surface an agent uses: check in with observed state, +// fetch the authoritative desired state, and watch for revision hints. Every +// method requires an agent certificate whose SPIFFE id matches agent_id and +// whose location matches the request; see pkg/treatment/identity. +// +#ifndef GRPC_xtcp_5ftreatment_2fv1_2fagent_2eproto__INCLUDED +#define GRPC_xtcp_5ftreatment_2fv1_2fagent_2eproto__INCLUDED + +#include "xtcp_treatment/v1/agent.pb.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xtcp_treatment { +namespace v1 { + +class AgentControlService final { + public: + static constexpr char const* service_full_name() { + return "xtcp_treatment.v1.AgentControlService"; + } + class StubInterface { + public: + virtual ~StubInterface() {} + virtual ::grpc::Status CheckIn(::grpc::ClientContext* context, const ::xtcp_treatment::v1::CheckInRequest& request, ::xtcp_treatment::v1::CheckInResponse* response) = 0; + std::unique_ptr< ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::CheckInResponse>> AsyncCheckIn(::grpc::ClientContext* context, const ::xtcp_treatment::v1::CheckInRequest& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::CheckInResponse>>(AsyncCheckInRaw(context, request, cq)); + } + std::unique_ptr< ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::CheckInResponse>> PrepareAsyncCheckIn(::grpc::ClientContext* context, const ::xtcp_treatment::v1::CheckInRequest& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::CheckInResponse>>(PrepareAsyncCheckInRaw(context, request, cq)); + } + virtual ::grpc::Status GetDesiredState(::grpc::ClientContext* context, const ::xtcp_treatment::v1::GetDesiredStateRequest& request, ::xtcp_treatment::v1::GetDesiredStateResponse* response) = 0; + std::unique_ptr< ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::GetDesiredStateResponse>> AsyncGetDesiredState(::grpc::ClientContext* context, const ::xtcp_treatment::v1::GetDesiredStateRequest& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::GetDesiredStateResponse>>(AsyncGetDesiredStateRaw(context, request, cq)); + } + std::unique_ptr< ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::GetDesiredStateResponse>> PrepareAsyncGetDesiredState(::grpc::ClientContext* context, const ::xtcp_treatment::v1::GetDesiredStateRequest& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::GetDesiredStateResponse>>(PrepareAsyncGetDesiredStateRaw(context, request, cq)); + } + std::unique_ptr< ::grpc::ClientReaderInterface< ::xtcp_treatment::v1::WatchDesiredStateResponse>> WatchDesiredState(::grpc::ClientContext* context, const ::xtcp_treatment::v1::WatchDesiredStateRequest& request) { + return std::unique_ptr< ::grpc::ClientReaderInterface< ::xtcp_treatment::v1::WatchDesiredStateResponse>>(WatchDesiredStateRaw(context, request)); + } + std::unique_ptr< ::grpc::ClientAsyncReaderInterface< ::xtcp_treatment::v1::WatchDesiredStateResponse>> AsyncWatchDesiredState(::grpc::ClientContext* context, const ::xtcp_treatment::v1::WatchDesiredStateRequest& request, ::grpc::CompletionQueue* cq, void* tag) { + return std::unique_ptr< ::grpc::ClientAsyncReaderInterface< ::xtcp_treatment::v1::WatchDesiredStateResponse>>(AsyncWatchDesiredStateRaw(context, request, cq, tag)); + } + std::unique_ptr< ::grpc::ClientAsyncReaderInterface< ::xtcp_treatment::v1::WatchDesiredStateResponse>> PrepareAsyncWatchDesiredState(::grpc::ClientContext* context, const ::xtcp_treatment::v1::WatchDesiredStateRequest& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncReaderInterface< ::xtcp_treatment::v1::WatchDesiredStateResponse>>(PrepareAsyncWatchDesiredStateRaw(context, request, cq)); + } + class async_interface { + public: + virtual ~async_interface() {} + virtual void CheckIn(::grpc::ClientContext* context, const ::xtcp_treatment::v1::CheckInRequest* request, ::xtcp_treatment::v1::CheckInResponse* response, std::function) = 0; + virtual void CheckIn(::grpc::ClientContext* context, const ::xtcp_treatment::v1::CheckInRequest* request, ::xtcp_treatment::v1::CheckInResponse* response, ::grpc::ClientUnaryReactor* reactor) = 0; + virtual void GetDesiredState(::grpc::ClientContext* context, const ::xtcp_treatment::v1::GetDesiredStateRequest* request, ::xtcp_treatment::v1::GetDesiredStateResponse* response, std::function) = 0; + virtual void GetDesiredState(::grpc::ClientContext* context, const ::xtcp_treatment::v1::GetDesiredStateRequest* request, ::xtcp_treatment::v1::GetDesiredStateResponse* response, ::grpc::ClientUnaryReactor* reactor) = 0; + virtual void WatchDesiredState(::grpc::ClientContext* context, const ::xtcp_treatment::v1::WatchDesiredStateRequest* request, ::grpc::ClientReadReactor< ::xtcp_treatment::v1::WatchDesiredStateResponse>* reactor) = 0; + }; + typedef class async_interface experimental_async_interface; + virtual class async_interface* async() { return nullptr; } + class async_interface* experimental_async() { return async(); } + private: + virtual ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::CheckInResponse>* AsyncCheckInRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::CheckInRequest& request, ::grpc::CompletionQueue* cq) = 0; + virtual ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::CheckInResponse>* PrepareAsyncCheckInRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::CheckInRequest& request, ::grpc::CompletionQueue* cq) = 0; + virtual ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::GetDesiredStateResponse>* AsyncGetDesiredStateRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::GetDesiredStateRequest& request, ::grpc::CompletionQueue* cq) = 0; + virtual ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::GetDesiredStateResponse>* PrepareAsyncGetDesiredStateRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::GetDesiredStateRequest& request, ::grpc::CompletionQueue* cq) = 0; + virtual ::grpc::ClientReaderInterface< ::xtcp_treatment::v1::WatchDesiredStateResponse>* WatchDesiredStateRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::WatchDesiredStateRequest& request) = 0; + virtual ::grpc::ClientAsyncReaderInterface< ::xtcp_treatment::v1::WatchDesiredStateResponse>* AsyncWatchDesiredStateRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::WatchDesiredStateRequest& request, ::grpc::CompletionQueue* cq, void* tag) = 0; + virtual ::grpc::ClientAsyncReaderInterface< ::xtcp_treatment::v1::WatchDesiredStateResponse>* PrepareAsyncWatchDesiredStateRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::WatchDesiredStateRequest& request, ::grpc::CompletionQueue* cq) = 0; + }; + class Stub final : public StubInterface { + public: + Stub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options = ::grpc::StubOptions()); + ::grpc::Status CheckIn(::grpc::ClientContext* context, const ::xtcp_treatment::v1::CheckInRequest& request, ::xtcp_treatment::v1::CheckInResponse* response) override; + std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::CheckInResponse>> AsyncCheckIn(::grpc::ClientContext* context, const ::xtcp_treatment::v1::CheckInRequest& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::CheckInResponse>>(AsyncCheckInRaw(context, request, cq)); + } + std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::CheckInResponse>> PrepareAsyncCheckIn(::grpc::ClientContext* context, const ::xtcp_treatment::v1::CheckInRequest& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::CheckInResponse>>(PrepareAsyncCheckInRaw(context, request, cq)); + } + ::grpc::Status GetDesiredState(::grpc::ClientContext* context, const ::xtcp_treatment::v1::GetDesiredStateRequest& request, ::xtcp_treatment::v1::GetDesiredStateResponse* response) override; + std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::GetDesiredStateResponse>> AsyncGetDesiredState(::grpc::ClientContext* context, const ::xtcp_treatment::v1::GetDesiredStateRequest& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::GetDesiredStateResponse>>(AsyncGetDesiredStateRaw(context, request, cq)); + } + std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::GetDesiredStateResponse>> PrepareAsyncGetDesiredState(::grpc::ClientContext* context, const ::xtcp_treatment::v1::GetDesiredStateRequest& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::GetDesiredStateResponse>>(PrepareAsyncGetDesiredStateRaw(context, request, cq)); + } + std::unique_ptr< ::grpc::ClientReader< ::xtcp_treatment::v1::WatchDesiredStateResponse>> WatchDesiredState(::grpc::ClientContext* context, const ::xtcp_treatment::v1::WatchDesiredStateRequest& request) { + return std::unique_ptr< ::grpc::ClientReader< ::xtcp_treatment::v1::WatchDesiredStateResponse>>(WatchDesiredStateRaw(context, request)); + } + std::unique_ptr< ::grpc::ClientAsyncReader< ::xtcp_treatment::v1::WatchDesiredStateResponse>> AsyncWatchDesiredState(::grpc::ClientContext* context, const ::xtcp_treatment::v1::WatchDesiredStateRequest& request, ::grpc::CompletionQueue* cq, void* tag) { + return std::unique_ptr< ::grpc::ClientAsyncReader< ::xtcp_treatment::v1::WatchDesiredStateResponse>>(AsyncWatchDesiredStateRaw(context, request, cq, tag)); + } + std::unique_ptr< ::grpc::ClientAsyncReader< ::xtcp_treatment::v1::WatchDesiredStateResponse>> PrepareAsyncWatchDesiredState(::grpc::ClientContext* context, const ::xtcp_treatment::v1::WatchDesiredStateRequest& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncReader< ::xtcp_treatment::v1::WatchDesiredStateResponse>>(PrepareAsyncWatchDesiredStateRaw(context, request, cq)); + } + class async final : + public StubInterface::async_interface { + public: + void CheckIn(::grpc::ClientContext* context, const ::xtcp_treatment::v1::CheckInRequest* request, ::xtcp_treatment::v1::CheckInResponse* response, std::function) override; + void CheckIn(::grpc::ClientContext* context, const ::xtcp_treatment::v1::CheckInRequest* request, ::xtcp_treatment::v1::CheckInResponse* response, ::grpc::ClientUnaryReactor* reactor) override; + void GetDesiredState(::grpc::ClientContext* context, const ::xtcp_treatment::v1::GetDesiredStateRequest* request, ::xtcp_treatment::v1::GetDesiredStateResponse* response, std::function) override; + void GetDesiredState(::grpc::ClientContext* context, const ::xtcp_treatment::v1::GetDesiredStateRequest* request, ::xtcp_treatment::v1::GetDesiredStateResponse* response, ::grpc::ClientUnaryReactor* reactor) override; + void WatchDesiredState(::grpc::ClientContext* context, const ::xtcp_treatment::v1::WatchDesiredStateRequest* request, ::grpc::ClientReadReactor< ::xtcp_treatment::v1::WatchDesiredStateResponse>* reactor) override; + private: + friend class Stub; + explicit async(Stub* stub): stub_(stub) { } + Stub* stub() { return stub_; } + Stub* stub_; + }; + class async* async() override { return &async_stub_; } + + private: + std::shared_ptr< ::grpc::ChannelInterface> channel_; + class async async_stub_{this}; + ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::CheckInResponse>* AsyncCheckInRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::CheckInRequest& request, ::grpc::CompletionQueue* cq) override; + ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::CheckInResponse>* PrepareAsyncCheckInRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::CheckInRequest& request, ::grpc::CompletionQueue* cq) override; + ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::GetDesiredStateResponse>* AsyncGetDesiredStateRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::GetDesiredStateRequest& request, ::grpc::CompletionQueue* cq) override; + ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::GetDesiredStateResponse>* PrepareAsyncGetDesiredStateRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::GetDesiredStateRequest& request, ::grpc::CompletionQueue* cq) override; + ::grpc::ClientReader< ::xtcp_treatment::v1::WatchDesiredStateResponse>* WatchDesiredStateRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::WatchDesiredStateRequest& request) override; + ::grpc::ClientAsyncReader< ::xtcp_treatment::v1::WatchDesiredStateResponse>* AsyncWatchDesiredStateRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::WatchDesiredStateRequest& request, ::grpc::CompletionQueue* cq, void* tag) override; + ::grpc::ClientAsyncReader< ::xtcp_treatment::v1::WatchDesiredStateResponse>* PrepareAsyncWatchDesiredStateRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::WatchDesiredStateRequest& request, ::grpc::CompletionQueue* cq) override; + const ::grpc::internal::RpcMethod rpcmethod_CheckIn_; + const ::grpc::internal::RpcMethod rpcmethod_GetDesiredState_; + const ::grpc::internal::RpcMethod rpcmethod_WatchDesiredState_; + }; + static std::unique_ptr NewStub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options = ::grpc::StubOptions()); + + class Service : public ::grpc::Service { + public: + Service(); + virtual ~Service(); + virtual ::grpc::Status CheckIn(::grpc::ServerContext* context, const ::xtcp_treatment::v1::CheckInRequest* request, ::xtcp_treatment::v1::CheckInResponse* response); + virtual ::grpc::Status GetDesiredState(::grpc::ServerContext* context, const ::xtcp_treatment::v1::GetDesiredStateRequest* request, ::xtcp_treatment::v1::GetDesiredStateResponse* response); + virtual ::grpc::Status WatchDesiredState(::grpc::ServerContext* context, const ::xtcp_treatment::v1::WatchDesiredStateRequest* request, ::grpc::ServerWriter< ::xtcp_treatment::v1::WatchDesiredStateResponse>* writer); + }; + template + class WithAsyncMethod_CheckIn : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithAsyncMethod_CheckIn() { + ::grpc::Service::MarkMethodAsync(0); + } + ~WithAsyncMethod_CheckIn() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status CheckIn(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::CheckInRequest* /*request*/, ::xtcp_treatment::v1::CheckInResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + void RequestCheckIn(::grpc::ServerContext* context, ::xtcp_treatment::v1::CheckInRequest* request, ::grpc::ServerAsyncResponseWriter< ::xtcp_treatment::v1::CheckInResponse>* response, ::grpc::CompletionQueue* new_call_cq, ::grpc::ServerCompletionQueue* notification_cq, void *tag) { + ::grpc::Service::RequestAsyncUnary(0, context, request, response, new_call_cq, notification_cq, tag); + } + }; + template + class WithAsyncMethod_GetDesiredState : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithAsyncMethod_GetDesiredState() { + ::grpc::Service::MarkMethodAsync(1); + } + ~WithAsyncMethod_GetDesiredState() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status GetDesiredState(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::GetDesiredStateRequest* /*request*/, ::xtcp_treatment::v1::GetDesiredStateResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + void RequestGetDesiredState(::grpc::ServerContext* context, ::xtcp_treatment::v1::GetDesiredStateRequest* request, ::grpc::ServerAsyncResponseWriter< ::xtcp_treatment::v1::GetDesiredStateResponse>* response, ::grpc::CompletionQueue* new_call_cq, ::grpc::ServerCompletionQueue* notification_cq, void *tag) { + ::grpc::Service::RequestAsyncUnary(1, context, request, response, new_call_cq, notification_cq, tag); + } + }; + template + class WithAsyncMethod_WatchDesiredState : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithAsyncMethod_WatchDesiredState() { + ::grpc::Service::MarkMethodAsync(2); + } + ~WithAsyncMethod_WatchDesiredState() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status WatchDesiredState(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::WatchDesiredStateRequest* /*request*/, ::grpc::ServerWriter< ::xtcp_treatment::v1::WatchDesiredStateResponse>* /*writer*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + void RequestWatchDesiredState(::grpc::ServerContext* context, ::xtcp_treatment::v1::WatchDesiredStateRequest* request, ::grpc::ServerAsyncWriter< ::xtcp_treatment::v1::WatchDesiredStateResponse>* writer, ::grpc::CompletionQueue* new_call_cq, ::grpc::ServerCompletionQueue* notification_cq, void *tag) { + ::grpc::Service::RequestAsyncServerStreaming(2, context, request, writer, new_call_cq, notification_cq, tag); + } + }; + typedef WithAsyncMethod_CheckIn > > AsyncService; + template + class WithCallbackMethod_CheckIn : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithCallbackMethod_CheckIn() { + ::grpc::Service::MarkMethodCallback(0, + new ::grpc::internal::CallbackUnaryHandler< ::xtcp_treatment::v1::CheckInRequest, ::xtcp_treatment::v1::CheckInResponse>( + [this]( + ::grpc::CallbackServerContext* context, const ::xtcp_treatment::v1::CheckInRequest* request, ::xtcp_treatment::v1::CheckInResponse* response) { return this->CheckIn(context, request, response); }));} + void SetMessageAllocatorFor_CheckIn( + ::grpc::MessageAllocator< ::xtcp_treatment::v1::CheckInRequest, ::xtcp_treatment::v1::CheckInResponse>* allocator) { + ::grpc::internal::MethodHandler* const handler = ::grpc::Service::GetHandler(0); + static_cast<::grpc::internal::CallbackUnaryHandler< ::xtcp_treatment::v1::CheckInRequest, ::xtcp_treatment::v1::CheckInResponse>*>(handler) + ->SetMessageAllocator(allocator); + } + ~WithCallbackMethod_CheckIn() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status CheckIn(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::CheckInRequest* /*request*/, ::xtcp_treatment::v1::CheckInResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + virtual ::grpc::ServerUnaryReactor* CheckIn( + ::grpc::CallbackServerContext* /*context*/, const ::xtcp_treatment::v1::CheckInRequest* /*request*/, ::xtcp_treatment::v1::CheckInResponse* /*response*/) { return nullptr; } + }; + template + class WithCallbackMethod_GetDesiredState : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithCallbackMethod_GetDesiredState() { + ::grpc::Service::MarkMethodCallback(1, + new ::grpc::internal::CallbackUnaryHandler< ::xtcp_treatment::v1::GetDesiredStateRequest, ::xtcp_treatment::v1::GetDesiredStateResponse>( + [this]( + ::grpc::CallbackServerContext* context, const ::xtcp_treatment::v1::GetDesiredStateRequest* request, ::xtcp_treatment::v1::GetDesiredStateResponse* response) { return this->GetDesiredState(context, request, response); }));} + void SetMessageAllocatorFor_GetDesiredState( + ::grpc::MessageAllocator< ::xtcp_treatment::v1::GetDesiredStateRequest, ::xtcp_treatment::v1::GetDesiredStateResponse>* allocator) { + ::grpc::internal::MethodHandler* const handler = ::grpc::Service::GetHandler(1); + static_cast<::grpc::internal::CallbackUnaryHandler< ::xtcp_treatment::v1::GetDesiredStateRequest, ::xtcp_treatment::v1::GetDesiredStateResponse>*>(handler) + ->SetMessageAllocator(allocator); + } + ~WithCallbackMethod_GetDesiredState() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status GetDesiredState(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::GetDesiredStateRequest* /*request*/, ::xtcp_treatment::v1::GetDesiredStateResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + virtual ::grpc::ServerUnaryReactor* GetDesiredState( + ::grpc::CallbackServerContext* /*context*/, const ::xtcp_treatment::v1::GetDesiredStateRequest* /*request*/, ::xtcp_treatment::v1::GetDesiredStateResponse* /*response*/) { return nullptr; } + }; + template + class WithCallbackMethod_WatchDesiredState : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithCallbackMethod_WatchDesiredState() { + ::grpc::Service::MarkMethodCallback(2, + new ::grpc::internal::CallbackServerStreamingHandler< ::xtcp_treatment::v1::WatchDesiredStateRequest, ::xtcp_treatment::v1::WatchDesiredStateResponse>( + [this]( + ::grpc::CallbackServerContext* context, const ::xtcp_treatment::v1::WatchDesiredStateRequest* request) { return this->WatchDesiredState(context, request); })); + } + ~WithCallbackMethod_WatchDesiredState() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status WatchDesiredState(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::WatchDesiredStateRequest* /*request*/, ::grpc::ServerWriter< ::xtcp_treatment::v1::WatchDesiredStateResponse>* /*writer*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + virtual ::grpc::ServerWriteReactor< ::xtcp_treatment::v1::WatchDesiredStateResponse>* WatchDesiredState( + ::grpc::CallbackServerContext* /*context*/, const ::xtcp_treatment::v1::WatchDesiredStateRequest* /*request*/) { return nullptr; } + }; + typedef WithCallbackMethod_CheckIn > > CallbackService; + typedef CallbackService ExperimentalCallbackService; + template + class WithGenericMethod_CheckIn : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithGenericMethod_CheckIn() { + ::grpc::Service::MarkMethodGeneric(0); + } + ~WithGenericMethod_CheckIn() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status CheckIn(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::CheckInRequest* /*request*/, ::xtcp_treatment::v1::CheckInResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + }; + template + class WithGenericMethod_GetDesiredState : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithGenericMethod_GetDesiredState() { + ::grpc::Service::MarkMethodGeneric(1); + } + ~WithGenericMethod_GetDesiredState() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status GetDesiredState(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::GetDesiredStateRequest* /*request*/, ::xtcp_treatment::v1::GetDesiredStateResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + }; + template + class WithGenericMethod_WatchDesiredState : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithGenericMethod_WatchDesiredState() { + ::grpc::Service::MarkMethodGeneric(2); + } + ~WithGenericMethod_WatchDesiredState() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status WatchDesiredState(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::WatchDesiredStateRequest* /*request*/, ::grpc::ServerWriter< ::xtcp_treatment::v1::WatchDesiredStateResponse>* /*writer*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + }; + template + class WithRawMethod_CheckIn : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithRawMethod_CheckIn() { + ::grpc::Service::MarkMethodRaw(0); + } + ~WithRawMethod_CheckIn() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status CheckIn(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::CheckInRequest* /*request*/, ::xtcp_treatment::v1::CheckInResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + void RequestCheckIn(::grpc::ServerContext* context, ::grpc::ByteBuffer* request, ::grpc::ServerAsyncResponseWriter< ::grpc::ByteBuffer>* response, ::grpc::CompletionQueue* new_call_cq, ::grpc::ServerCompletionQueue* notification_cq, void *tag) { + ::grpc::Service::RequestAsyncUnary(0, context, request, response, new_call_cq, notification_cq, tag); + } + }; + template + class WithRawMethod_GetDesiredState : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithRawMethod_GetDesiredState() { + ::grpc::Service::MarkMethodRaw(1); + } + ~WithRawMethod_GetDesiredState() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status GetDesiredState(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::GetDesiredStateRequest* /*request*/, ::xtcp_treatment::v1::GetDesiredStateResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + void RequestGetDesiredState(::grpc::ServerContext* context, ::grpc::ByteBuffer* request, ::grpc::ServerAsyncResponseWriter< ::grpc::ByteBuffer>* response, ::grpc::CompletionQueue* new_call_cq, ::grpc::ServerCompletionQueue* notification_cq, void *tag) { + ::grpc::Service::RequestAsyncUnary(1, context, request, response, new_call_cq, notification_cq, tag); + } + }; + template + class WithRawMethod_WatchDesiredState : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithRawMethod_WatchDesiredState() { + ::grpc::Service::MarkMethodRaw(2); + } + ~WithRawMethod_WatchDesiredState() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status WatchDesiredState(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::WatchDesiredStateRequest* /*request*/, ::grpc::ServerWriter< ::xtcp_treatment::v1::WatchDesiredStateResponse>* /*writer*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + void RequestWatchDesiredState(::grpc::ServerContext* context, ::grpc::ByteBuffer* request, ::grpc::ServerAsyncWriter< ::grpc::ByteBuffer>* writer, ::grpc::CompletionQueue* new_call_cq, ::grpc::ServerCompletionQueue* notification_cq, void *tag) { + ::grpc::Service::RequestAsyncServerStreaming(2, context, request, writer, new_call_cq, notification_cq, tag); + } + }; + template + class WithRawCallbackMethod_CheckIn : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithRawCallbackMethod_CheckIn() { + ::grpc::Service::MarkMethodRawCallback(0, + new ::grpc::internal::CallbackUnaryHandler< ::grpc::ByteBuffer, ::grpc::ByteBuffer>( + [this]( + ::grpc::CallbackServerContext* context, const ::grpc::ByteBuffer* request, ::grpc::ByteBuffer* response) { return this->CheckIn(context, request, response); })); + } + ~WithRawCallbackMethod_CheckIn() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status CheckIn(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::CheckInRequest* /*request*/, ::xtcp_treatment::v1::CheckInResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + virtual ::grpc::ServerUnaryReactor* CheckIn( + ::grpc::CallbackServerContext* /*context*/, const ::grpc::ByteBuffer* /*request*/, ::grpc::ByteBuffer* /*response*/) { return nullptr; } + }; + template + class WithRawCallbackMethod_GetDesiredState : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithRawCallbackMethod_GetDesiredState() { + ::grpc::Service::MarkMethodRawCallback(1, + new ::grpc::internal::CallbackUnaryHandler< ::grpc::ByteBuffer, ::grpc::ByteBuffer>( + [this]( + ::grpc::CallbackServerContext* context, const ::grpc::ByteBuffer* request, ::grpc::ByteBuffer* response) { return this->GetDesiredState(context, request, response); })); + } + ~WithRawCallbackMethod_GetDesiredState() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status GetDesiredState(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::GetDesiredStateRequest* /*request*/, ::xtcp_treatment::v1::GetDesiredStateResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + virtual ::grpc::ServerUnaryReactor* GetDesiredState( + ::grpc::CallbackServerContext* /*context*/, const ::grpc::ByteBuffer* /*request*/, ::grpc::ByteBuffer* /*response*/) { return nullptr; } + }; + template + class WithRawCallbackMethod_WatchDesiredState : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithRawCallbackMethod_WatchDesiredState() { + ::grpc::Service::MarkMethodRawCallback(2, + new ::grpc::internal::CallbackServerStreamingHandler< ::grpc::ByteBuffer, ::grpc::ByteBuffer>( + [this]( + ::grpc::CallbackServerContext* context, const::grpc::ByteBuffer* request) { return this->WatchDesiredState(context, request); })); + } + ~WithRawCallbackMethod_WatchDesiredState() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status WatchDesiredState(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::WatchDesiredStateRequest* /*request*/, ::grpc::ServerWriter< ::xtcp_treatment::v1::WatchDesiredStateResponse>* /*writer*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + virtual ::grpc::ServerWriteReactor< ::grpc::ByteBuffer>* WatchDesiredState( + ::grpc::CallbackServerContext* /*context*/, const ::grpc::ByteBuffer* /*request*/) { return nullptr; } + }; + template + class WithStreamedUnaryMethod_CheckIn : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithStreamedUnaryMethod_CheckIn() { + ::grpc::Service::MarkMethodStreamed(0, + new ::grpc::internal::StreamedUnaryHandler< + ::xtcp_treatment::v1::CheckInRequest, ::xtcp_treatment::v1::CheckInResponse>( + [this](::grpc::ServerContext* context, + ::grpc::ServerUnaryStreamer< + ::xtcp_treatment::v1::CheckInRequest, ::xtcp_treatment::v1::CheckInResponse>* streamer) { + return this->StreamedCheckIn(context, + streamer); + })); + } + ~WithStreamedUnaryMethod_CheckIn() override { + BaseClassMustBeDerivedFromService(this); + } + // disable regular version of this method + ::grpc::Status CheckIn(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::CheckInRequest* /*request*/, ::xtcp_treatment::v1::CheckInResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + // replace default version of method with streamed unary + virtual ::grpc::Status StreamedCheckIn(::grpc::ServerContext* context, ::grpc::ServerUnaryStreamer< ::xtcp_treatment::v1::CheckInRequest,::xtcp_treatment::v1::CheckInResponse>* server_unary_streamer) = 0; + }; + template + class WithStreamedUnaryMethod_GetDesiredState : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithStreamedUnaryMethod_GetDesiredState() { + ::grpc::Service::MarkMethodStreamed(1, + new ::grpc::internal::StreamedUnaryHandler< + ::xtcp_treatment::v1::GetDesiredStateRequest, ::xtcp_treatment::v1::GetDesiredStateResponse>( + [this](::grpc::ServerContext* context, + ::grpc::ServerUnaryStreamer< + ::xtcp_treatment::v1::GetDesiredStateRequest, ::xtcp_treatment::v1::GetDesiredStateResponse>* streamer) { + return this->StreamedGetDesiredState(context, + streamer); + })); + } + ~WithStreamedUnaryMethod_GetDesiredState() override { + BaseClassMustBeDerivedFromService(this); + } + // disable regular version of this method + ::grpc::Status GetDesiredState(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::GetDesiredStateRequest* /*request*/, ::xtcp_treatment::v1::GetDesiredStateResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + // replace default version of method with streamed unary + virtual ::grpc::Status StreamedGetDesiredState(::grpc::ServerContext* context, ::grpc::ServerUnaryStreamer< ::xtcp_treatment::v1::GetDesiredStateRequest,::xtcp_treatment::v1::GetDesiredStateResponse>* server_unary_streamer) = 0; + }; + typedef WithStreamedUnaryMethod_CheckIn > StreamedUnaryService; + template + class WithSplitStreamingMethod_WatchDesiredState : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithSplitStreamingMethod_WatchDesiredState() { + ::grpc::Service::MarkMethodStreamed(2, + new ::grpc::internal::SplitServerStreamingHandler< + ::xtcp_treatment::v1::WatchDesiredStateRequest, ::xtcp_treatment::v1::WatchDesiredStateResponse>( + [this](::grpc::ServerContext* context, + ::grpc::ServerSplitStreamer< + ::xtcp_treatment::v1::WatchDesiredStateRequest, ::xtcp_treatment::v1::WatchDesiredStateResponse>* streamer) { + return this->StreamedWatchDesiredState(context, + streamer); + })); + } + ~WithSplitStreamingMethod_WatchDesiredState() override { + BaseClassMustBeDerivedFromService(this); + } + // disable regular version of this method + ::grpc::Status WatchDesiredState(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::WatchDesiredStateRequest* /*request*/, ::grpc::ServerWriter< ::xtcp_treatment::v1::WatchDesiredStateResponse>* /*writer*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + // replace default version of method with split streamed + virtual ::grpc::Status StreamedWatchDesiredState(::grpc::ServerContext* context, ::grpc::ServerSplitStreamer< ::xtcp_treatment::v1::WatchDesiredStateRequest,::xtcp_treatment::v1::WatchDesiredStateResponse>* server_split_streamer) = 0; + }; + typedef WithSplitStreamingMethod_WatchDesiredState SplitStreamedService; + typedef WithStreamedUnaryMethod_CheckIn > > StreamedService; +}; + +} // namespace v1 +} // namespace xtcp_treatment + + +#include +#endif // GRPC_xtcp_5ftreatment_2fv1_2fagent_2eproto__INCLUDED diff --git a/gen/cpp/xtcp_treatment/v1/agent.pb.cc b/gen/cpp/xtcp_treatment/v1/agent.pb.cc new file mode 100644 index 000000000..61f3a1def --- /dev/null +++ b/gen/cpp/xtcp_treatment/v1/agent.pb.cc @@ -0,0 +1,2576 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE +// source: xtcp_treatment/v1/agent.proto +// Protobuf C++ Version: 7.36.2 + +#include "xtcp_treatment/v1/agent.pb.h" + +#include +#include +#include "google/protobuf/io/coded_stream.h" +#include "google/protobuf/generated_message_tctable_impl.h" +#include "google/protobuf/internal_visibility.h" +#include "google/protobuf/extension_set.h" +#include "google/protobuf/generated_message_util.h" +#include "google/protobuf/wire_format_lite.h" +#include "google/protobuf/descriptor.h" +#include "google/protobuf/generated_message_reflection.h" +#include "google/protobuf/reflection_ops.h" +#include "google/protobuf/wire_format.h" +// @@protoc_insertion_point(includes) + +// Must be included last. +#include "google/protobuf/port_def.inc" +PROTOBUF_PRAGMA_INIT_SEG +namespace _pb = ::google::protobuf; +namespace _pbi = ::google::protobuf::internal; +namespace _fl = ::google::protobuf::internal::field_layout; +#ifdef PROTOBUF_MESSAGE_GLOBALS +namespace { +PROTOBUF_CONSTINIT ::google::protobuf::internal::ReflectionData + file_reflection_data[] = { + // ::xtcp_treatment::v1::CheckInRequest + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_2fv1_2fagent_2eproto, /* tracker*/ nullptr,}, + // ::xtcp_treatment::v1::CheckInResponse + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_2fv1_2fagent_2eproto, /* tracker*/ nullptr,}, + // ::xtcp_treatment::v1::GetDesiredStateRequest + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_2fv1_2fagent_2eproto, /* tracker*/ nullptr,}, + // ::xtcp_treatment::v1::GetDesiredStateResponse + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_2fv1_2fagent_2eproto, /* tracker*/ nullptr,}, + // ::xtcp_treatment::v1::WatchDesiredStateRequest + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_2fv1_2fagent_2eproto, /* tracker*/ nullptr,}, + // ::xtcp_treatment::v1::WatchDesiredStateResponse + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_2fv1_2fagent_2eproto, /* tracker*/ nullptr,}, +}; +} // namespace +#endif +namespace xtcp_treatment { +namespace v1 { +class WatchDesiredStateResponse::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(WatchDesiredStateResponse, _impl_._has_bits_); +}; + +constexpr WatchDesiredStateResponse::ParseTableT_ WatchDesiredStateResponse::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(WatchDesiredStateResponse, _impl_._has_bits_), + 0, // no _extensions_ + 1, 0, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294967294, // skipmap + offsetof(ParseTableT_, field_entries), + 1, // num_field_entries + 0, // num_aux_entries + offsetof(ParseTableT_, field_names), // no aux_entries + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment::v1::WatchDesiredStateResponse>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + // uint64 revision = 1 [json_name = "revision"]; + {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(WatchDesiredStateResponse, _impl_.revision_), 0>(), + {8, 0, 0, + PROTOBUF_FIELD_OFFSET(WatchDesiredStateResponse, _impl_.revision_)}}, + }}, {{ + 65535, 65535 + }}, {{ + // uint64 revision = 1 [json_name = "revision"]; + {PROTOBUF_FIELD_OFFSET(WatchDesiredStateResponse, _impl_.revision_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt64)}, + }}, + // no aux_entries + {{ + }}, + }; +} + + +inline constexpr WatchDesiredStateResponse::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : revision_{::uint64_t{0u}} {} + +template +constexpr WatchDesiredStateResponse::WatchDesiredStateResponse(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL WatchDesiredStateResponse::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) WatchDesiredStateResponse(arena); +} +constexpr auto WatchDesiredStateResponse::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::ZeroInit(sizeof(WatchDesiredStateResponse), alignof(WatchDesiredStateResponse)); +} +constexpr auto WatchDesiredStateResponse::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &WatchDesiredStateResponse::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &WatchDesiredStateResponse::SharedDtor, + Super_::GetClearImpl(), &WatchDesiredStateResponse::ByteSizeLong, + &WatchDesiredStateResponse::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(WatchDesiredStateResponse, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[5], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_2fv1_2fagent_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct WatchDesiredStateResponseGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr WatchDesiredStateResponseGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + WatchDesiredStateResponse_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(WatchDesiredStateResponse::InternalGenerateClassData_( + _default, &WatchDesiredStateResponse_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~WatchDesiredStateResponseGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) WatchDesiredStateResponse _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(WatchDesiredStateResponseGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST WatchDesiredStateResponseGlobalsTypeInternal WatchDesiredStateResponse_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* WatchDesiredStateResponse_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return WatchDesiredStateResponse_globals_.GetClassData(); +#else + return WatchDesiredStateResponse_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +class WatchDesiredStateRequest::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(WatchDesiredStateRequest, _impl_._has_bits_); +}; + +constexpr WatchDesiredStateRequest::ParseTableT_ WatchDesiredStateRequest::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(WatchDesiredStateRequest, _impl_._has_bits_), + 0, // no _extensions_ + 2, 8, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294967292, // skipmap + offsetof(ParseTableT_, field_entries), + 2, // num_field_entries + 0, // num_aux_entries + offsetof(ParseTableT_, field_names), // no aux_entries + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment::v1::WatchDesiredStateRequest>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + // uint64 after_revision = 2 [json_name = "afterRevision"]; + {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(WatchDesiredStateRequest, _impl_.after_revision_), 1>(), + {16, 1, 0, + PROTOBUF_FIELD_OFFSET(WatchDesiredStateRequest, _impl_.after_revision_)}}, + // string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {10, 0, 0, + PROTOBUF_FIELD_OFFSET(WatchDesiredStateRequest, _impl_.agent_id_)}}, + }}, {{ + 65535, 65535 + }}, {{ + // string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(WatchDesiredStateRequest, _impl_.agent_id_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // uint64 after_revision = 2 [json_name = "afterRevision"]; + {PROTOBUF_FIELD_OFFSET(WatchDesiredStateRequest, _impl_.after_revision_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt64)}, + }}, + // no aux_entries + {{ + "\52\10\0\0\0\0\0\0" + "xtcp_treatment.v1.WatchDesiredStateRequest" + "agent_id" + }}, + }; +} + + +inline constexpr WatchDesiredStateRequest::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : agent_id_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + after_revision_{::uint64_t{0u}} {} + +template +constexpr WatchDesiredStateRequest::WatchDesiredStateRequest(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL WatchDesiredStateRequest::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) WatchDesiredStateRequest(arena); +} +constexpr auto WatchDesiredStateRequest::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::CopyInit(sizeof(WatchDesiredStateRequest), alignof(WatchDesiredStateRequest)); +} +constexpr auto WatchDesiredStateRequest::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &WatchDesiredStateRequest::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &WatchDesiredStateRequest::SharedDtor, + Super_::GetClearImpl(), &WatchDesiredStateRequest::ByteSizeLong, + &WatchDesiredStateRequest::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(WatchDesiredStateRequest, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[4], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_2fv1_2fagent_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct WatchDesiredStateRequestGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr WatchDesiredStateRequestGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + WatchDesiredStateRequest_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(WatchDesiredStateRequest::InternalGenerateClassData_( + _default, &WatchDesiredStateRequest_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~WatchDesiredStateRequestGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) WatchDesiredStateRequest _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(WatchDesiredStateRequestGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST WatchDesiredStateRequestGlobalsTypeInternal WatchDesiredStateRequest_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* WatchDesiredStateRequest_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return WatchDesiredStateRequest_globals_.GetClassData(); +#else + return WatchDesiredStateRequest_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +class GetDesiredStateRequest::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(GetDesiredStateRequest, _impl_._has_bits_); +}; + +constexpr GetDesiredStateRequest::ParseTableT_ GetDesiredStateRequest::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(GetDesiredStateRequest, _impl_._has_bits_), + 0, // no _extensions_ + 1, 0, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294967294, // skipmap + offsetof(ParseTableT_, field_entries), + 1, // num_field_entries + 0, // num_aux_entries + offsetof(ParseTableT_, field_names), // no aux_entries + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment::v1::GetDesiredStateRequest>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + // string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {10, 0, 0, + PROTOBUF_FIELD_OFFSET(GetDesiredStateRequest, _impl_.agent_id_)}}, + }}, {{ + 65535, 65535 + }}, {{ + // string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(GetDesiredStateRequest, _impl_.agent_id_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + }}, + // no aux_entries + {{ + "\50\10\0\0\0\0\0\0" + "xtcp_treatment.v1.GetDesiredStateRequest" + "agent_id" + }}, + }; +} + + +inline constexpr GetDesiredStateRequest::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : agent_id_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()) {} + +template +constexpr GetDesiredStateRequest::GetDesiredStateRequest(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL GetDesiredStateRequest::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) GetDesiredStateRequest(arena); +} +constexpr auto GetDesiredStateRequest::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::CopyInit(sizeof(GetDesiredStateRequest), alignof(GetDesiredStateRequest)); +} +constexpr auto GetDesiredStateRequest::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &GetDesiredStateRequest::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &GetDesiredStateRequest::SharedDtor, + Super_::GetClearImpl(), &GetDesiredStateRequest::ByteSizeLong, + &GetDesiredStateRequest::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(GetDesiredStateRequest, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[2], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_2fv1_2fagent_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct GetDesiredStateRequestGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr GetDesiredStateRequestGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + GetDesiredStateRequest_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(GetDesiredStateRequest::InternalGenerateClassData_( + _default, &GetDesiredStateRequest_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~GetDesiredStateRequestGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) GetDesiredStateRequest _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(GetDesiredStateRequestGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST GetDesiredStateRequestGlobalsTypeInternal GetDesiredStateRequest_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* GetDesiredStateRequest_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return GetDesiredStateRequest_globals_.GetClassData(); +#else + return GetDesiredStateRequest_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +class GetDesiredStateResponse::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(GetDesiredStateResponse, _impl_._has_bits_); +}; + +constexpr GetDesiredStateResponse::ParseTableT_ GetDesiredStateResponse::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(GetDesiredStateResponse, _impl_._has_bits_), + 0, // no _extensions_ + 1, 0, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294967294, // skipmap + offsetof(ParseTableT_, field_entries), + 1, // num_field_entries + 1, // num_aux_entries + offsetof(ParseTableT_, aux_entries), + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment::v1::GetDesiredStateResponse>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + // .xtcp_treatment.v1.DesiredState desired = 1 [json_name = "desired"]; + {::_pbi::TcParser::FastMtS1, + {10, 0, 0, + PROTOBUF_FIELD_OFFSET(GetDesiredStateResponse, _impl_.desired_)}}, + }}, {{ + 65535, 65535 + }}, {{ + // .xtcp_treatment.v1.DesiredState desired = 1 [json_name = "desired"]; + {PROTOBUF_FIELD_OFFSET(GetDesiredStateResponse, _impl_.desired_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + }}, + {{ + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment::v1::DesiredState>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment::v1::DesiredState_globals_}, + #endif + }}, + {{ + }}, + }; +} + + +inline constexpr GetDesiredStateResponse::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : desired_{nullptr} {} + +template +constexpr GetDesiredStateResponse::GetDesiredStateResponse(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL GetDesiredStateResponse::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) GetDesiredStateResponse(arena); +} +constexpr auto GetDesiredStateResponse::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::ZeroInit(sizeof(GetDesiredStateResponse), alignof(GetDesiredStateResponse)); +} +constexpr auto GetDesiredStateResponse::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &GetDesiredStateResponse::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &GetDesiredStateResponse::SharedDtor, + Super_::GetClearImpl(), &GetDesiredStateResponse::ByteSizeLong, + &GetDesiredStateResponse::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(GetDesiredStateResponse, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[3], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_2fv1_2fagent_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct GetDesiredStateResponseGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr GetDesiredStateResponseGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + GetDesiredStateResponse_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(GetDesiredStateResponse::InternalGenerateClassData_( + _default, &GetDesiredStateResponse_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~GetDesiredStateResponseGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) GetDesiredStateResponse _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(GetDesiredStateResponseGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST GetDesiredStateResponseGlobalsTypeInternal GetDesiredStateResponse_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* GetDesiredStateResponse_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return GetDesiredStateResponse_globals_.GetClassData(); +#else + return GetDesiredStateResponse_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +class CheckInResponse::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(CheckInResponse, _impl_._has_bits_); +}; + +constexpr CheckInResponse::ParseTableT_ CheckInResponse::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(CheckInResponse, _impl_._has_bits_), + 0, // no _extensions_ + 1, 0, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294967294, // skipmap + offsetof(ParseTableT_, field_entries), + 1, // num_field_entries + 1, // num_aux_entries + offsetof(ParseTableT_, aux_entries), + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment::v1::CheckInResponse>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + // .xtcp_treatment.v1.DesiredState desired = 1 [json_name = "desired"]; + {::_pbi::TcParser::FastMtS1, + {10, 0, 0, + PROTOBUF_FIELD_OFFSET(CheckInResponse, _impl_.desired_)}}, + }}, {{ + 65535, 65535 + }}, {{ + // .xtcp_treatment.v1.DesiredState desired = 1 [json_name = "desired"]; + {PROTOBUF_FIELD_OFFSET(CheckInResponse, _impl_.desired_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + }}, + {{ + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment::v1::DesiredState>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment::v1::DesiredState_globals_}, + #endif + }}, + {{ + }}, + }; +} + + +inline constexpr CheckInResponse::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : desired_{nullptr} {} + +template +constexpr CheckInResponse::CheckInResponse(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL CheckInResponse::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) CheckInResponse(arena); +} +constexpr auto CheckInResponse::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::ZeroInit(sizeof(CheckInResponse), alignof(CheckInResponse)); +} +constexpr auto CheckInResponse::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &CheckInResponse::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &CheckInResponse::SharedDtor, + Super_::GetClearImpl(), &CheckInResponse::ByteSizeLong, + &CheckInResponse::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(CheckInResponse, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[1], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_2fv1_2fagent_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct CheckInResponseGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr CheckInResponseGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + CheckInResponse_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(CheckInResponse::InternalGenerateClassData_( + _default, &CheckInResponse_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~CheckInResponseGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) CheckInResponse _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(CheckInResponseGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST CheckInResponseGlobalsTypeInternal CheckInResponse_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* CheckInResponse_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return CheckInResponse_globals_.GetClassData(); +#else + return CheckInResponse_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +class CheckInRequest::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(CheckInRequest, _impl_._has_bits_); +}; + +constexpr CheckInRequest::ParseTableT_ CheckInRequest::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(CheckInRequest, _impl_._has_bits_), + 0, // no _extensions_ + 6, 56, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294967232, // skipmap + offsetof(ParseTableT_, field_entries), + 6, // num_field_entries + 2, // num_aux_entries + offsetof(ParseTableT_, aux_entries), + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment::v1::CheckInRequest>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + {::_pbi::TcParser::MiniParse, {}}, + // string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {10, 1, 0, + PROTOBUF_FIELD_OFFSET(CheckInRequest, _impl_.agent_id_)}}, + // string location = 2 [json_name = "location", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {18, 2, 0, + PROTOBUF_FIELD_OFFSET(CheckInRequest, _impl_.location_)}}, + // .xtcp_treatment.v1.Capabilities capabilities = 3 [json_name = "capabilities", (.buf.validate.field) = { + {::_pbi::TcParser::FastMtS1, + {26, 3, 0, + PROTOBUF_FIELD_OFFSET(CheckInRequest, _impl_.capabilities_)}}, + // uint64 desired_revision = 4 [json_name = "desiredRevision"]; + {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(CheckInRequest, _impl_.desired_revision_), 4>(), + {32, 4, 0, + PROTOBUF_FIELD_OFFSET(CheckInRequest, _impl_.desired_revision_)}}, + // repeated .xtcp_treatment.v1.ApplicationRecord records = 5 [json_name = "records", (.buf.validate.field) = { + {::_pbi::TcParser::FastMtR1, + {42, 0, 1, + PROTOBUF_FIELD_OFFSET(CheckInRequest, _impl_.records_)}}, + // bool locally_suspended = 6 [json_name = "locallySuspended"]; + {::_pbi::TcParser::SingularVarintNoZag1(), + {48, 5, 0, + PROTOBUF_FIELD_OFFSET(CheckInRequest, _impl_.locally_suspended_)}}, + {::_pbi::TcParser::MiniParse, {}}, + }}, {{ + 65535, 65535 + }}, {{ + // string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(CheckInRequest, _impl_.agent_id_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // string location = 2 [json_name = "location", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(CheckInRequest, _impl_.location_), _Internal::kHasBitsOffset + 2, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // .xtcp_treatment.v1.Capabilities capabilities = 3 [json_name = "capabilities", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(CheckInRequest, _impl_.capabilities_), _Internal::kHasBitsOffset + 3, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // uint64 desired_revision = 4 [json_name = "desiredRevision"]; + {PROTOBUF_FIELD_OFFSET(CheckInRequest, _impl_.desired_revision_), _Internal::kHasBitsOffset + 4, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt64)}, + // repeated .xtcp_treatment.v1.ApplicationRecord records = 5 [json_name = "records", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(CheckInRequest, _impl_.records_), _Internal::kHasBitsOffset + 0, 1, (0 | ::_fl::kFcRepeated | ::_fl::kMessage | ::_fl::kTvTable)}, + // bool locally_suspended = 6 [json_name = "locallySuspended"]; + {PROTOBUF_FIELD_OFFSET(CheckInRequest, _impl_.locally_suspended_), _Internal::kHasBitsOffset + 5, 0, (0 | ::_fl::kFcOptional | ::_fl::kBool)}, + }}, + {{ + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment::v1::Capabilities>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment::v1::Capabilities_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment::v1::ApplicationRecord>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment::v1::ApplicationRecord_globals_}, + #endif + }}, + {{ + "\40\10\10\0\0\0\0\0" + "xtcp_treatment.v1.CheckInRequest" + "agent_id" + "location" + }}, + }; +} + + +inline constexpr CheckInRequest::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : records_ { visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment::v1::CheckInRequest, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::CheckInRequest, _impl_.records_)>() + } + , + agent_id_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + location_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + capabilities_{nullptr}, + desired_revision_{::uint64_t{0u}}, + locally_suspended_{false} {} + +template +constexpr CheckInRequest::CheckInRequest(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL CheckInRequest::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) CheckInRequest(arena); +} +constexpr auto CheckInRequest::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::CopyInit(sizeof(CheckInRequest), alignof(CheckInRequest)); +} +constexpr auto CheckInRequest::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &CheckInRequest::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &CheckInRequest::SharedDtor, + Super_::GetClearImpl(), &CheckInRequest::ByteSizeLong, + &CheckInRequest::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(CheckInRequest, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[0], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_2fv1_2fagent_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct CheckInRequestGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr CheckInRequestGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + CheckInRequest_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(CheckInRequest::InternalGenerateClassData_( + _default, &CheckInRequest_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~CheckInRequestGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) CheckInRequest _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(CheckInRequestGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST CheckInRequestGlobalsTypeInternal CheckInRequest_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* CheckInRequest_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return CheckInRequest_globals_.GetClassData(); +#else + return CheckInRequest_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +} // namespace v1 +} // namespace xtcp_treatment +static constexpr const ::_pb::EnumDescriptor* PROTOBUF_NONNULL* PROTOBUF_NULLABLE + file_level_enum_descriptors_xtcp_5ftreatment_2fv1_2fagent_2eproto = nullptr; +static constexpr const ::_pb::ServiceDescriptor* PROTOBUF_NONNULL* PROTOBUF_NULLABLE + file_level_service_descriptors_xtcp_5ftreatment_2fv1_2fagent_2eproto = nullptr; +const ::uint32_t + TableStruct_xtcp_5ftreatment_2fv1_2fagent_2eproto::offsets[] ABSL_ATTRIBUTE_SECTION_VARIABLE( + protodesc_cold) = { + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::CheckInRequest, _impl_._has_bits_), + 9, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::CheckInRequest, _impl_.agent_id_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::CheckInRequest, _impl_.location_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::CheckInRequest, _impl_.capabilities_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::CheckInRequest, _impl_.desired_revision_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::CheckInRequest, _impl_.records_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::CheckInRequest, _impl_.locally_suspended_), + 1, + 2, + 3, + 4, + 0, + 5, + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::CheckInResponse, _impl_._has_bits_), + 4, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::CheckInResponse, _impl_.desired_), + 0, + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::GetDesiredStateRequest, _impl_._has_bits_), + 4, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::GetDesiredStateRequest, _impl_.agent_id_), + 0, + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::GetDesiredStateResponse, _impl_._has_bits_), + 4, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::GetDesiredStateResponse, _impl_.desired_), + 0, + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::WatchDesiredStateRequest, _impl_._has_bits_), + 5, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::WatchDesiredStateRequest, _impl_.agent_id_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::WatchDesiredStateRequest, _impl_.after_revision_), + 0, + 1, + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::WatchDesiredStateResponse, _impl_._has_bits_), + 4, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::WatchDesiredStateResponse, _impl_.revision_), + 0, +}; + +static const ::_pbi::MigrationSchema + schemas[] ABSL_ATTRIBUTE_SECTION_VARIABLE(protodesc_cold) = { + {0, sizeof(::xtcp_treatment::v1::CheckInRequest)}, + {15, sizeof(::xtcp_treatment::v1::CheckInResponse)}, + {20, sizeof(::xtcp_treatment::v1::GetDesiredStateRequest)}, + {25, sizeof(::xtcp_treatment::v1::GetDesiredStateResponse)}, + {30, sizeof(::xtcp_treatment::v1::WatchDesiredStateRequest)}, + {37, sizeof(::xtcp_treatment::v1::WatchDesiredStateResponse)}, +}; +static const ::_pbi::MessageGlobalsBase* PROTOBUF_NONNULL const + file_message_globals[] = { + &::xtcp_treatment::v1::CheckInRequest_globals_, + &::xtcp_treatment::v1::CheckInResponse_globals_, + &::xtcp_treatment::v1::GetDesiredStateRequest_globals_, + &::xtcp_treatment::v1::GetDesiredStateResponse_globals_, + &::xtcp_treatment::v1::WatchDesiredStateRequest_globals_, + &::xtcp_treatment::v1::WatchDesiredStateResponse_globals_, +}; +const char descriptor_table_protodef_xtcp_5ftreatment_2fv1_2fagent_2eproto[] ABSL_ATTRIBUTE_SECTION_VARIABLE( + protodesc_cold) = { + "\n\035xtcp_treatment/v1/agent.proto\022\021xtcp_tr" + "eatment.v1\032\033buf/validate/validate.proto\032" + "!xtcp_treatment/v1/treatment.proto\"\215\003\n\016C" + "heckInRequest\022D\n\010agent_id\030\001 \001(\tB)\272H&r$2\"" + "^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\007agen" + "tId\022E\n\010location\030\002 \001(\tB)\272H&r$2\"^[a-zA-Z0-" + "9][a-zA-Z0-9_.-]{0,127}$R\010location\022K\n\014ca" + "pabilities\030\003 \001(\0132\037.xtcp_treatment.v1.Cap" + "abilitiesB\006\272H\003\310\001\001R\014capabilities\022)\n\020desir" + "ed_revision\030\004 \001(\004R\017desiredRevision\022I\n\007re" + "cords\030\005 \003(\0132$.xtcp_treatment.v1.Applicat" + "ionRecordB\t\272H\006\222\001\003\020\200@R\007records\022+\n\021locally" + "_suspended\030\006 \001(\010R\020locallySuspended\"L\n\017Ch" + "eckInResponse\0229\n\007desired\030\001 \001(\0132\037.xtcp_tr" + "eatment.v1.DesiredStateR\007desired\"^\n\026GetD" + "esiredStateRequest\022D\n\010agent_id\030\001 \001(\tB)\272H" + "&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R" + "\007agentId\"T\n\027GetDesiredStateResponse\0229\n\007d" + "esired\030\001 \001(\0132\037.xtcp_treatment.v1.Desired" + "StateR\007desired\"\207\001\n\030WatchDesiredStateRequ" + "est\022D\n\010agent_id\030\001 \001(\tB)\272H&r$2\"^[a-zA-Z0-" + "9][a-zA-Z0-9_.-]{0,127}$R\007agentId\022%\n\016aft" + "er_revision\030\002 \001(\004R\rafterRevision\"7\n\031Watc" + "hDesiredStateResponse\022\032\n\010revision\030\001 \001(\004R" + "\010revision2\325\002\n\023AgentControlService\022V\n\007Che" + "ckIn\022!.xtcp_treatment.v1.CheckInRequest\032" + "\".xtcp_treatment.v1.CheckInResponse\"\004\320\363\030" + "\001\022n\n\017GetDesiredState\022).xtcp_treatment.v1" + ".GetDesiredStateRequest\032*.xtcp_treatment" + ".v1.GetDesiredStateResponse\"\004\320\363\030\001\022v\n\021Wat" + "chDesiredState\022+.xtcp_treatment.v1.Watch" + "DesiredStateRequest\032,.xtcp_treatment.v1." + "WatchDesiredStateResponse\"\004\320\363\030\0010\001B\274\001\n\025co" + "m.xtcp_treatment.v1B\nAgentProtoP\001Z6githu" + "b.com/randomizedcoder/xtcp2/gen/go/xtcp_" + "treatment\242\002\003XXX\252\002\020XtcpTreatment.V1\312\002\020Xtc" + "pTreatment\\V1\342\002\034XtcpTreatment\\V1\\GPBMeta" + "data\352\002\021XtcpTreatment::V1b\006proto3" +}; +static const ::_pbi::DescriptorTable* PROTOBUF_NONNULL const + descriptor_table_xtcp_5ftreatment_2fv1_2fagent_2eproto_deps[2] = { + &::descriptor_table_buf_2fvalidate_2fvalidate_2eproto, + &::descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, +}; +static ::absl::once_flag descriptor_table_xtcp_5ftreatment_2fv1_2fagent_2eproto_once; +PROTOBUF_CONSTINIT const ::_pbi::DescriptorTable descriptor_table_xtcp_5ftreatment_2fv1_2fagent_2eproto = { + false, + false, + 1512, + descriptor_table_protodef_xtcp_5ftreatment_2fv1_2fagent_2eproto, + "xtcp_treatment/v1/agent.proto", + &descriptor_table_xtcp_5ftreatment_2fv1_2fagent_2eproto_once, + descriptor_table_xtcp_5ftreatment_2fv1_2fagent_2eproto_deps, + 2, + 6, + schemas, + file_message_globals, + TableStruct_xtcp_5ftreatment_2fv1_2fagent_2eproto::offsets, + file_level_enum_descriptors_xtcp_5ftreatment_2fv1_2fagent_2eproto, + file_level_service_descriptors_xtcp_5ftreatment_2fv1_2fagent_2eproto, +}; +namespace xtcp_treatment { +namespace v1 { +// =================================================================== + +void CheckInRequest::clear_capabilities() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.capabilities_ != nullptr) _impl_.capabilities_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); +} +void CheckInRequest::clear_records() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.records_.Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +CheckInRequest::CheckInRequest(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, CheckInRequest_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment.v1.CheckInRequest) +} +PROTOBUF_NDEBUG_INLINE CheckInRequest::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment::v1::CheckInRequest& from_msg) + : _has_bits_{from._has_bits_}, + records_ { + visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment::v1::CheckInRequest, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::CheckInRequest, _impl_.records_)>() + , arena, from.records_ + } + , + agent_id_(arena, from.agent_id_), + location_(arena, from.location_) {} + +CheckInRequest::CheckInRequest( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const CheckInRequest& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, CheckInRequest_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + CheckInRequest* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + ::uint32_t cached_has_bits = _impl_._has_bits_[0]; + _impl_.capabilities_ = (CheckHasBit(cached_has_bits, 0x00000008U)) + ? Super_::CopyConstruct(arena, *from._impl_.capabilities_) + : nullptr; + ::memcpy(reinterpret_cast(&_impl_) + + offsetof(Impl_, desired_revision_), + reinterpret_cast(&from._impl_) + + offsetof(Impl_, desired_revision_), + offsetof(Impl_, locally_suspended_) - + offsetof(Impl_, desired_revision_) + + sizeof(Impl_::locally_suspended_)); + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment.v1.CheckInRequest) +} +PROTOBUF_NDEBUG_INLINE CheckInRequest::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + : records_ { visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment::v1::CheckInRequest, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::CheckInRequest, _impl_.records_)>() + } + , + agent_id_(arena), + location_(arena) {} + +inline void CheckInRequest::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + ::memset(reinterpret_cast(&_impl_) + + offsetof(Impl_, capabilities_), + 0, + offsetof(Impl_, locally_suspended_) - + offsetof(Impl_, capabilities_) + + sizeof(Impl_::locally_suspended_)); +} +CheckInRequest::~CheckInRequest() { + // @@protoc_insertion_point(destructor:xtcp_treatment.v1.CheckInRequest) + SharedDtor(*this); +} +inline void CheckInRequest::SharedDtor(MessageLite& self) { + CheckInRequest& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + this_._impl_.agent_id_.Destroy(); + this_._impl_.location_.Destroy(); + delete this_._impl_.capabilities_; + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull CheckInRequest_class_data_ = + CheckInRequest::InternalGenerateClassData_(CheckInRequest_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +CheckInRequest::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&CheckInRequest_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(CheckInRequest_class_data_.tc_table); + return CheckInRequest_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +CheckInRequest::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&CheckInRequest_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&CheckInRequest_globals_)); + return CheckInRequest_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const CheckInRequest::ParseTableT_ + CheckInRequest::_table_ = + CheckInRequest::InternalGenerateParseTable_(CheckInRequest_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void CheckInRequest::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment.v1.CheckInRequest) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000000fU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + _impl_.records_.Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + this_._impl_.agent_id_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + this_._impl_.location_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + ABSL_DCHECK(this_._impl_.capabilities_ != nullptr); + this_._impl_.capabilities_->Clear(); + } + } + if (BatchCheckHasBit(cached_has_bits, 0x00000030U)) { + ::memset(&this_._impl_.desired_revision_, 0, + static_cast<::size_t>( + reinterpret_cast(&this_._impl_.locally_suspended_) - + reinterpret_cast(&this_._impl_.desired_revision_)) + + sizeof(_impl_.locally_suspended_)); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL CheckInRequest::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const CheckInRequest& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL CheckInRequest::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const CheckInRequest& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment.v1.CheckInRequest) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!this_._internal_agent_id().empty()) { + const ::std::string& _s = this_._internal_agent_id(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.CheckInRequest.agent_id"); + target = stream->WriteStringMaybeAliased(1, _s, target); + } + } + + // string location = 2 [json_name = "location", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (!this_._internal_location().empty()) { + const ::std::string& _s = this_._internal_location(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.CheckInRequest.location"); + target = stream->WriteStringMaybeAliased(2, _s, target); + } + } + + // .xtcp_treatment.v1.Capabilities capabilities = 3 [json_name = "capabilities", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 3, *this_._impl_.capabilities_, this_._impl_.capabilities_->GetCachedSize(), target, + stream); + } + + // uint64 desired_revision = 4 [json_name = "desiredRevision"]; + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (this_._internal_desired_revision() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray( + 4, this_._internal_desired_revision(), target); + } + } + + // repeated .xtcp_treatment.v1.ApplicationRecord records = 5 [json_name = "records", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + for (unsigned i = 0, n = static_cast( + this_._internal_records_size()); + i < n; i++) { + const auto& repfield = this_._internal_records().Get(i); + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 5, repfield, repfield.GetCachedSize(), target, + stream); + } + } + + // bool locally_suspended = 6 [json_name = "locallySuspended"]; + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (this_._internal_locally_suspended() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray( + 6, this_._internal_locally_suspended(), target); + } + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment.v1.CheckInRequest) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t CheckInRequest::ByteSizeLong(const MessageLite& base) { + const CheckInRequest& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t CheckInRequest::ByteSizeLong() const { + const CheckInRequest& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment.v1.CheckInRequest) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + ::_pbi::Prefetch5LinesFrom7Lines(&this_); + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000003fU)) { + // repeated .xtcp_treatment.v1.ApplicationRecord records = 5 [json_name = "records", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + total_size += 1UL * this_._internal_records_size(); + for (const auto& msg : this_._internal_records()) { + total_size += ::google::protobuf::internal::WireFormatLite::MessageSize(msg); + } + } + // string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!this_._internal_agent_id().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_agent_id()); + } + } + // string location = 2 [json_name = "location", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (!this_._internal_location().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_location()); + } + } + // .xtcp_treatment.v1.Capabilities capabilities = 3 [json_name = "capabilities", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.capabilities_); + } + // uint64 desired_revision = 4 [json_name = "desiredRevision"]; + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (this_._internal_desired_revision() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne( + this_._internal_desired_revision()); + } + } + // bool locally_suspended = 6 [json_name = "locallySuspended"]; + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (this_._internal_locally_suspended() != 0) { + total_size += 2; + } + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void CheckInRequest::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + ::google::protobuf::Arena* arena = _this->GetArena(); + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment.v1.CheckInRequest) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000003fU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + _this->_internal_mutable_records()->InternalMergeFromWithArena( + ::google::protobuf::MessageLite::internal_visibility(), arena, + from._internal_records()); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!from._internal_agent_id().empty()) { + _this->_internal_set_agent_id(from._internal_agent_id()); + } else { + if (_this->_impl_.agent_id_.IsDefault()) { + _this->_internal_set_agent_id(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (!from._internal_location().empty()) { + _this->_internal_set_location(from._internal_location()); + } else { + if (_this->_impl_.location_.IsDefault()) { + _this->_internal_set_location(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + ABSL_DCHECK(from._impl_.capabilities_ != nullptr); + if (_this->_impl_.capabilities_ == nullptr) { + _this->_impl_.capabilities_ = Super_::CopyConstruct(arena, *from._impl_.capabilities_); + } else { + _this->_impl_.capabilities_->MergeFrom(*from._impl_.capabilities_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (from._internal_desired_revision() != 0) { + _this->_impl_.desired_revision_ = from._impl_.desired_revision_; + } + } + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (from._internal_locally_suspended() != 0) { + _this->_impl_.locally_suspended_ = from._impl_.locally_suspended_; + } + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void CheckInRequest::CopyFrom(const CheckInRequest& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment.v1.CheckInRequest) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void CheckInRequest::InternalSwap(CheckInRequest* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + auto* arena = GetArena(); + ABSL_DCHECK_EQ(arena, other->GetArena()); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + _impl_.records_.InternalSwap(&other->_impl_.records_); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.agent_id_, &other->_impl_.agent_id_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.location_, &other->_impl_.location_, arena); + ::google::protobuf::internal::memswap< + PROTOBUF_FIELD_OFFSET(CheckInRequest, _impl_.locally_suspended_) + + sizeof(CheckInRequest::_impl_.locally_suspended_) + - PROTOBUF_FIELD_OFFSET(CheckInRequest, _impl_.capabilities_)>( + reinterpret_cast(&_impl_.capabilities_), + reinterpret_cast(&other->_impl_.capabilities_)); +} + +::google::protobuf::Metadata CheckInRequest::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +void CheckInResponse::clear_desired() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.desired_ != nullptr) _impl_.desired_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +CheckInResponse::CheckInResponse(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, CheckInResponse_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment.v1.CheckInResponse) +} +PROTOBUF_NDEBUG_INLINE CheckInResponse::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment::v1::CheckInResponse& from_msg) + : _has_bits_{from._has_bits_} {} + +CheckInResponse::CheckInResponse( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const CheckInResponse& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, CheckInResponse_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + CheckInResponse* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + ::uint32_t cached_has_bits = _impl_._has_bits_[0]; + _impl_.desired_ = (CheckHasBit(cached_has_bits, 0x00000001U)) + ? Super_::CopyConstruct(arena, *from._impl_.desired_) + : nullptr; + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment.v1.CheckInResponse) +} +PROTOBUF_NDEBUG_INLINE CheckInResponse::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + {} + +inline void CheckInResponse::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + _impl_.desired_ = {}; +} +CheckInResponse::~CheckInResponse() { + // @@protoc_insertion_point(destructor:xtcp_treatment.v1.CheckInResponse) + SharedDtor(*this); +} +inline void CheckInResponse::SharedDtor(MessageLite& self) { + CheckInResponse& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + delete this_._impl_.desired_; + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull CheckInResponse_class_data_ = + CheckInResponse::InternalGenerateClassData_(CheckInResponse_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +CheckInResponse::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&CheckInResponse_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(CheckInResponse_class_data_.tc_table); + return CheckInResponse_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +CheckInResponse::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&CheckInResponse_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&CheckInResponse_globals_)); + return CheckInResponse_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const CheckInResponse::ParseTableT_ + CheckInResponse::_table_ = + CheckInResponse::InternalGenerateParseTable_(CheckInResponse_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void CheckInResponse::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment.v1.CheckInResponse) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + ABSL_DCHECK(this_._impl_.desired_ != nullptr); + this_._impl_.desired_->Clear(); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL CheckInResponse::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const CheckInResponse& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL CheckInResponse::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const CheckInResponse& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment.v1.CheckInResponse) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // .xtcp_treatment.v1.DesiredState desired = 1 [json_name = "desired"]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 1, *this_._impl_.desired_, this_._impl_.desired_->GetCachedSize(), target, + stream); + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment.v1.CheckInResponse) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t CheckInResponse::ByteSizeLong(const MessageLite& base) { + const CheckInResponse& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t CheckInResponse::ByteSizeLong() const { + const CheckInResponse& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment.v1.CheckInResponse) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + { + // .xtcp_treatment.v1.DesiredState desired = 1 [json_name = "desired"]; + cached_has_bits = this_._impl_._has_bits_[0]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.desired_); + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void CheckInResponse::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + ::google::protobuf::Arena* arena = _this->GetArena(); + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment.v1.CheckInResponse) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + ABSL_DCHECK(from._impl_.desired_ != nullptr); + if (_this->_impl_.desired_ == nullptr) { + _this->_impl_.desired_ = Super_::CopyConstruct(arena, *from._impl_.desired_); + } else { + _this->_impl_.desired_->MergeFrom(*from._impl_.desired_); + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void CheckInResponse::CopyFrom(const CheckInResponse& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment.v1.CheckInResponse) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void CheckInResponse::InternalSwap(CheckInResponse* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + swap(_impl_.desired_, other->_impl_.desired_); +} + +::google::protobuf::Metadata CheckInResponse::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +GetDesiredStateRequest::GetDesiredStateRequest(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, GetDesiredStateRequest_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment.v1.GetDesiredStateRequest) +} +PROTOBUF_NDEBUG_INLINE GetDesiredStateRequest::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment::v1::GetDesiredStateRequest& from_msg) + : _has_bits_{from._has_bits_}, + agent_id_(arena, from.agent_id_) {} + +GetDesiredStateRequest::GetDesiredStateRequest( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const GetDesiredStateRequest& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, GetDesiredStateRequest_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + GetDesiredStateRequest* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment.v1.GetDesiredStateRequest) +} +PROTOBUF_NDEBUG_INLINE GetDesiredStateRequest::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + : agent_id_(arena) {} + +inline void GetDesiredStateRequest::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); +} +GetDesiredStateRequest::~GetDesiredStateRequest() { + // @@protoc_insertion_point(destructor:xtcp_treatment.v1.GetDesiredStateRequest) + SharedDtor(*this); +} +inline void GetDesiredStateRequest::SharedDtor(MessageLite& self) { + GetDesiredStateRequest& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + this_._impl_.agent_id_.Destroy(); + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull GetDesiredStateRequest_class_data_ = + GetDesiredStateRequest::InternalGenerateClassData_(GetDesiredStateRequest_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +GetDesiredStateRequest::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&GetDesiredStateRequest_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(GetDesiredStateRequest_class_data_.tc_table); + return GetDesiredStateRequest_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +GetDesiredStateRequest::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&GetDesiredStateRequest_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&GetDesiredStateRequest_globals_)); + return GetDesiredStateRequest_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const GetDesiredStateRequest::ParseTableT_ + GetDesiredStateRequest::_table_ = + GetDesiredStateRequest::InternalGenerateParseTable_(GetDesiredStateRequest_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void GetDesiredStateRequest::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment.v1.GetDesiredStateRequest) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + this_._impl_.agent_id_.ClearNonDefaultToEmpty(); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL GetDesiredStateRequest::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const GetDesiredStateRequest& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL GetDesiredStateRequest::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const GetDesiredStateRequest& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment.v1.GetDesiredStateRequest) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_agent_id().empty()) { + const ::std::string& _s = this_._internal_agent_id(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.GetDesiredStateRequest.agent_id"); + target = stream->WriteStringMaybeAliased(1, _s, target); + } + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment.v1.GetDesiredStateRequest) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t GetDesiredStateRequest::ByteSizeLong(const MessageLite& base) { + const GetDesiredStateRequest& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t GetDesiredStateRequest::ByteSizeLong() const { + const GetDesiredStateRequest& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment.v1.GetDesiredStateRequest) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + { + // string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { + cached_has_bits = this_._impl_._has_bits_[0]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_agent_id().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_agent_id()); + } + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void GetDesiredStateRequest::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment.v1.GetDesiredStateRequest) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!from._internal_agent_id().empty()) { + _this->_internal_set_agent_id(from._internal_agent_id()); + } else { + if (_this->_impl_.agent_id_.IsDefault()) { + _this->_internal_set_agent_id(""); + } + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void GetDesiredStateRequest::CopyFrom(const GetDesiredStateRequest& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment.v1.GetDesiredStateRequest) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void GetDesiredStateRequest::InternalSwap(GetDesiredStateRequest* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + auto* arena = GetArena(); + ABSL_DCHECK_EQ(arena, other->GetArena()); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.agent_id_, &other->_impl_.agent_id_, arena); +} + +::google::protobuf::Metadata GetDesiredStateRequest::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +void GetDesiredStateResponse::clear_desired() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.desired_ != nullptr) _impl_.desired_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +GetDesiredStateResponse::GetDesiredStateResponse(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, GetDesiredStateResponse_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment.v1.GetDesiredStateResponse) +} +PROTOBUF_NDEBUG_INLINE GetDesiredStateResponse::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment::v1::GetDesiredStateResponse& from_msg) + : _has_bits_{from._has_bits_} {} + +GetDesiredStateResponse::GetDesiredStateResponse( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const GetDesiredStateResponse& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, GetDesiredStateResponse_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + GetDesiredStateResponse* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + ::uint32_t cached_has_bits = _impl_._has_bits_[0]; + _impl_.desired_ = (CheckHasBit(cached_has_bits, 0x00000001U)) + ? Super_::CopyConstruct(arena, *from._impl_.desired_) + : nullptr; + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment.v1.GetDesiredStateResponse) +} +PROTOBUF_NDEBUG_INLINE GetDesiredStateResponse::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + {} + +inline void GetDesiredStateResponse::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + _impl_.desired_ = {}; +} +GetDesiredStateResponse::~GetDesiredStateResponse() { + // @@protoc_insertion_point(destructor:xtcp_treatment.v1.GetDesiredStateResponse) + SharedDtor(*this); +} +inline void GetDesiredStateResponse::SharedDtor(MessageLite& self) { + GetDesiredStateResponse& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + delete this_._impl_.desired_; + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull GetDesiredStateResponse_class_data_ = + GetDesiredStateResponse::InternalGenerateClassData_(GetDesiredStateResponse_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +GetDesiredStateResponse::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&GetDesiredStateResponse_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(GetDesiredStateResponse_class_data_.tc_table); + return GetDesiredStateResponse_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +GetDesiredStateResponse::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&GetDesiredStateResponse_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&GetDesiredStateResponse_globals_)); + return GetDesiredStateResponse_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const GetDesiredStateResponse::ParseTableT_ + GetDesiredStateResponse::_table_ = + GetDesiredStateResponse::InternalGenerateParseTable_(GetDesiredStateResponse_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void GetDesiredStateResponse::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment.v1.GetDesiredStateResponse) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + ABSL_DCHECK(this_._impl_.desired_ != nullptr); + this_._impl_.desired_->Clear(); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL GetDesiredStateResponse::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const GetDesiredStateResponse& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL GetDesiredStateResponse::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const GetDesiredStateResponse& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment.v1.GetDesiredStateResponse) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // .xtcp_treatment.v1.DesiredState desired = 1 [json_name = "desired"]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 1, *this_._impl_.desired_, this_._impl_.desired_->GetCachedSize(), target, + stream); + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment.v1.GetDesiredStateResponse) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t GetDesiredStateResponse::ByteSizeLong(const MessageLite& base) { + const GetDesiredStateResponse& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t GetDesiredStateResponse::ByteSizeLong() const { + const GetDesiredStateResponse& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment.v1.GetDesiredStateResponse) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + { + // .xtcp_treatment.v1.DesiredState desired = 1 [json_name = "desired"]; + cached_has_bits = this_._impl_._has_bits_[0]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.desired_); + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void GetDesiredStateResponse::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + ::google::protobuf::Arena* arena = _this->GetArena(); + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment.v1.GetDesiredStateResponse) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + ABSL_DCHECK(from._impl_.desired_ != nullptr); + if (_this->_impl_.desired_ == nullptr) { + _this->_impl_.desired_ = Super_::CopyConstruct(arena, *from._impl_.desired_); + } else { + _this->_impl_.desired_->MergeFrom(*from._impl_.desired_); + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void GetDesiredStateResponse::CopyFrom(const GetDesiredStateResponse& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment.v1.GetDesiredStateResponse) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void GetDesiredStateResponse::InternalSwap(GetDesiredStateResponse* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + swap(_impl_.desired_, other->_impl_.desired_); +} + +::google::protobuf::Metadata GetDesiredStateResponse::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +WatchDesiredStateRequest::WatchDesiredStateRequest(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, WatchDesiredStateRequest_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment.v1.WatchDesiredStateRequest) +} +PROTOBUF_NDEBUG_INLINE WatchDesiredStateRequest::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment::v1::WatchDesiredStateRequest& from_msg) + : _has_bits_{from._has_bits_}, + agent_id_(arena, from.agent_id_) {} + +WatchDesiredStateRequest::WatchDesiredStateRequest( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const WatchDesiredStateRequest& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, WatchDesiredStateRequest_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + WatchDesiredStateRequest* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + _impl_.after_revision_ = from._impl_.after_revision_; + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment.v1.WatchDesiredStateRequest) +} +PROTOBUF_NDEBUG_INLINE WatchDesiredStateRequest::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + : agent_id_(arena) {} + +inline void WatchDesiredStateRequest::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + _impl_.after_revision_ = {}; +} +WatchDesiredStateRequest::~WatchDesiredStateRequest() { + // @@protoc_insertion_point(destructor:xtcp_treatment.v1.WatchDesiredStateRequest) + SharedDtor(*this); +} +inline void WatchDesiredStateRequest::SharedDtor(MessageLite& self) { + WatchDesiredStateRequest& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + this_._impl_.agent_id_.Destroy(); + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull WatchDesiredStateRequest_class_data_ = + WatchDesiredStateRequest::InternalGenerateClassData_(WatchDesiredStateRequest_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +WatchDesiredStateRequest::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&WatchDesiredStateRequest_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(WatchDesiredStateRequest_class_data_.tc_table); + return WatchDesiredStateRequest_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +WatchDesiredStateRequest::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&WatchDesiredStateRequest_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&WatchDesiredStateRequest_globals_)); + return WatchDesiredStateRequest_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const WatchDesiredStateRequest::ParseTableT_ + WatchDesiredStateRequest::_table_ = + WatchDesiredStateRequest::InternalGenerateParseTable_(WatchDesiredStateRequest_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void WatchDesiredStateRequest::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment.v1.WatchDesiredStateRequest) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + this_._impl_.agent_id_.ClearNonDefaultToEmpty(); + } + this_._impl_.after_revision_ = ::uint64_t{0u}; + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL WatchDesiredStateRequest::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const WatchDesiredStateRequest& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL WatchDesiredStateRequest::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const WatchDesiredStateRequest& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment.v1.WatchDesiredStateRequest) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_agent_id().empty()) { + const ::std::string& _s = this_._internal_agent_id(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.WatchDesiredStateRequest.agent_id"); + target = stream->WriteStringMaybeAliased(1, _s, target); + } + } + + // uint64 after_revision = 2 [json_name = "afterRevision"]; + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (this_._internal_after_revision() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray( + 2, this_._internal_after_revision(), target); + } + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment.v1.WatchDesiredStateRequest) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t WatchDesiredStateRequest::ByteSizeLong(const MessageLite& base) { + const WatchDesiredStateRequest& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t WatchDesiredStateRequest::ByteSizeLong() const { + const WatchDesiredStateRequest& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment.v1.WatchDesiredStateRequest) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + ::_pbi::Prefetch5LinesFrom7Lines(&this_); + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x00000003U)) { + // string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_agent_id().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_agent_id()); + } + } + // uint64 after_revision = 2 [json_name = "afterRevision"]; + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (this_._internal_after_revision() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne( + this_._internal_after_revision()); + } + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void WatchDesiredStateRequest::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment.v1.WatchDesiredStateRequest) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x00000003U)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!from._internal_agent_id().empty()) { + _this->_internal_set_agent_id(from._internal_agent_id()); + } else { + if (_this->_impl_.agent_id_.IsDefault()) { + _this->_internal_set_agent_id(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (from._internal_after_revision() != 0) { + _this->_impl_.after_revision_ = from._impl_.after_revision_; + } + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void WatchDesiredStateRequest::CopyFrom(const WatchDesiredStateRequest& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment.v1.WatchDesiredStateRequest) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void WatchDesiredStateRequest::InternalSwap(WatchDesiredStateRequest* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + auto* arena = GetArena(); + ABSL_DCHECK_EQ(arena, other->GetArena()); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.agent_id_, &other->_impl_.agent_id_, arena); + swap(_impl_.after_revision_, other->_impl_.after_revision_); +} + +::google::protobuf::Metadata WatchDesiredStateRequest::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +WatchDesiredStateResponse::WatchDesiredStateResponse(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, WatchDesiredStateResponse_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment.v1.WatchDesiredStateResponse) +} +WatchDesiredStateResponse::WatchDesiredStateResponse( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const WatchDesiredStateResponse& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, WatchDesiredStateResponse_get_class_data()), +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena), +#endif // PROTOBUF_CUSTOM_VTABLE + _impl_(from._impl_) { + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} +PROTOBUF_NDEBUG_INLINE WatchDesiredStateResponse::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + {} + +inline void WatchDesiredStateResponse::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + _impl_.revision_ = {}; +} +WatchDesiredStateResponse::~WatchDesiredStateResponse() { + // @@protoc_insertion_point(destructor:xtcp_treatment.v1.WatchDesiredStateResponse) + SharedDtor(*this); +} +inline void WatchDesiredStateResponse::SharedDtor(MessageLite& self) { + WatchDesiredStateResponse& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull WatchDesiredStateResponse_class_data_ = + WatchDesiredStateResponse::InternalGenerateClassData_(WatchDesiredStateResponse_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +WatchDesiredStateResponse::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&WatchDesiredStateResponse_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(WatchDesiredStateResponse_class_data_.tc_table); + return WatchDesiredStateResponse_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +WatchDesiredStateResponse::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&WatchDesiredStateResponse_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&WatchDesiredStateResponse_globals_)); + return WatchDesiredStateResponse_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const WatchDesiredStateResponse::ParseTableT_ + WatchDesiredStateResponse::_table_ = + WatchDesiredStateResponse::InternalGenerateParseTable_(WatchDesiredStateResponse_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void WatchDesiredStateResponse::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment.v1.WatchDesiredStateResponse) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + this_._impl_.revision_ = ::uint64_t{0u}; + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL WatchDesiredStateResponse::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const WatchDesiredStateResponse& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL WatchDesiredStateResponse::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const WatchDesiredStateResponse& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment.v1.WatchDesiredStateResponse) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // uint64 revision = 1 [json_name = "revision"]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (this_._internal_revision() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray( + 1, this_._internal_revision(), target); + } + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment.v1.WatchDesiredStateResponse) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t WatchDesiredStateResponse::ByteSizeLong(const MessageLite& base) { + const WatchDesiredStateResponse& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t WatchDesiredStateResponse::ByteSizeLong() const { + const WatchDesiredStateResponse& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment.v1.WatchDesiredStateResponse) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + { + // uint64 revision = 1 [json_name = "revision"]; + cached_has_bits = this_._impl_._has_bits_[0]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (this_._internal_revision() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne( + this_._internal_revision()); + } + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void WatchDesiredStateResponse::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment.v1.WatchDesiredStateResponse) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (from._internal_revision() != 0) { + _this->_impl_.revision_ = from._impl_.revision_; + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void WatchDesiredStateResponse::CopyFrom(const WatchDesiredStateResponse& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment.v1.WatchDesiredStateResponse) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void WatchDesiredStateResponse::InternalSwap(WatchDesiredStateResponse* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + swap(_impl_.revision_, other->_impl_.revision_); +} + +::google::protobuf::Metadata WatchDesiredStateResponse::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// @@protoc_insertion_point(namespace_scope) +} // namespace v1 +} // namespace xtcp_treatment +namespace google { +namespace protobuf { +} // namespace protobuf +} // namespace google +// @@protoc_insertion_point(global_scope) +PROTOBUF_ATTRIBUTE_INIT_PRIORITY2 static ::std::false_type + _static_init2_ [[maybe_unused]] = + (::_pbi::AddDescriptors(&descriptor_table_xtcp_5ftreatment_2fv1_2fagent_2eproto), + ::std::false_type{}); +#include "google/protobuf/port_undef.inc" diff --git a/gen/cpp/xtcp_treatment/v1/agent.pb.h b/gen/cpp/xtcp_treatment/v1/agent.pb.h new file mode 100644 index 000000000..0bdc0ce88 --- /dev/null +++ b/gen/cpp/xtcp_treatment/v1/agent.pb.h @@ -0,0 +1,2167 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE +// source: xtcp_treatment/v1/agent.proto +// Protobuf C++ Version: 7.36.2 + +#ifndef xtcp_5ftreatment_2fv1_2fagent_2eproto_2epb_2eh +#define xtcp_5ftreatment_2fv1_2fagent_2eproto_2epb_2eh + +#include +#include +#include +#include + +// clang-format off +#include "google/protobuf/runtime_version.h" +#if PROTOBUF_VERSION != 7036002 +#error "Protobuf C++ gencode is built with an incompatible version of" +#error "Protobuf C++ headers/runtime. See" +#error "https://protobuf.dev/support/cross-version-runtime-guarantee/#cpp" +#endif +#include "google/protobuf/io/coded_stream.h" +#include "google/protobuf/arena.h" +#include "google/protobuf/arenastring.h" +#include "google/protobuf/generated_message_tctable_decl.h" +#include "google/protobuf/generated_message_util.h" +#include "google/protobuf/metadata_lite.h" +#include "google/protobuf/generated_message_reflection.h" +#include "google/protobuf/message.h" +#include "google/protobuf/message_lite.h" +#include "google/protobuf/repeated_field.h" // IWYU pragma: export +#include "google/protobuf/extension_set.h" // IWYU pragma: export +#include "google/protobuf/unknown_field_set.h" +#include "buf/validate/validate.pb.h" +#include "xtcp_treatment/v1/treatment.pb.h" +// @@protoc_insertion_point(includes) + +// Must be included last. +#include "google/protobuf/port_def.inc" + +#define PROTOBUF_INTERNAL_EXPORT_xtcp_5ftreatment_2fv1_2fagent_2eproto + +// Internal implementation detail -- do not use these members. +struct TableStruct_xtcp_5ftreatment_2fv1_2fagent_2eproto { + static const ::uint32_t offsets[]; +}; +extern "C" { +extern const ::google::protobuf::internal::DescriptorTable descriptor_table_xtcp_5ftreatment_2fv1_2fagent_2eproto; +} // extern "C" +namespace xtcp_treatment { +namespace v1 { +class CheckInRequest; +struct CheckInRequestGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern CheckInRequestGlobalsTypeInternal CheckInRequest_globals_; +extern const ::google::protobuf::internal::ClassDataFull CheckInRequest_class_data_; +#else +extern const CheckInRequestGlobalsTypeInternal CheckInRequest_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +class CheckInResponse; +struct CheckInResponseGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern CheckInResponseGlobalsTypeInternal CheckInResponse_globals_; +extern const ::google::protobuf::internal::ClassDataFull CheckInResponse_class_data_; +#else +extern const CheckInResponseGlobalsTypeInternal CheckInResponse_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +class GetDesiredStateRequest; +struct GetDesiredStateRequestGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern GetDesiredStateRequestGlobalsTypeInternal GetDesiredStateRequest_globals_; +extern const ::google::protobuf::internal::ClassDataFull GetDesiredStateRequest_class_data_; +#else +extern const GetDesiredStateRequestGlobalsTypeInternal GetDesiredStateRequest_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +class GetDesiredStateResponse; +struct GetDesiredStateResponseGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern GetDesiredStateResponseGlobalsTypeInternal GetDesiredStateResponse_globals_; +extern const ::google::protobuf::internal::ClassDataFull GetDesiredStateResponse_class_data_; +#else +extern const GetDesiredStateResponseGlobalsTypeInternal GetDesiredStateResponse_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +class WatchDesiredStateRequest; +struct WatchDesiredStateRequestGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern WatchDesiredStateRequestGlobalsTypeInternal WatchDesiredStateRequest_globals_; +extern const ::google::protobuf::internal::ClassDataFull WatchDesiredStateRequest_class_data_; +#else +extern const WatchDesiredStateRequestGlobalsTypeInternal WatchDesiredStateRequest_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +class WatchDesiredStateResponse; +struct WatchDesiredStateResponseGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern WatchDesiredStateResponseGlobalsTypeInternal WatchDesiredStateResponse_globals_; +extern const ::google::protobuf::internal::ClassDataFull WatchDesiredStateResponse_class_data_; +#else +extern const WatchDesiredStateResponseGlobalsTypeInternal WatchDesiredStateResponse_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +} // namespace v1 +} // namespace xtcp_treatment +namespace google { +namespace protobuf { +} // namespace protobuf +} // namespace google + +namespace xtcp_treatment { +namespace v1 { + +// =================================================================== + + +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED WatchDesiredStateResponse final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment.v1.WatchDesiredStateResponse) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline WatchDesiredStateResponse() : WatchDesiredStateResponse(nullptr) {} + ~WatchDesiredStateResponse() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(WatchDesiredStateResponse* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(WatchDesiredStateResponse)); + } +#endif + + template + explicit constexpr WatchDesiredStateResponse(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline WatchDesiredStateResponse(const WatchDesiredStateResponse& from) : WatchDesiredStateResponse(nullptr, from) {} + inline WatchDesiredStateResponse(WatchDesiredStateResponse&& from) noexcept : WatchDesiredStateResponse(nullptr, ::std::move(from)) {} + inline WatchDesiredStateResponse& operator=(const WatchDesiredStateResponse& from) { + CopyFrom(from); + return *this; + } + inline WatchDesiredStateResponse& operator=(WatchDesiredStateResponse&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const WatchDesiredStateResponse& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&WatchDesiredStateResponse_globals_); + } + static constexpr int kIndexInFileMessages = 5; + friend void swap(WatchDesiredStateResponse& a, WatchDesiredStateResponse& b) { a.Swap(&b); } + inline void Swap(WatchDesiredStateResponse* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(WatchDesiredStateResponse* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] WatchDesiredStateResponse* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const WatchDesiredStateResponse& from); + using Super_::MergeFrom; + void MergeFrom(const WatchDesiredStateResponse& from) { WatchDesiredStateResponse::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(WatchDesiredStateResponse* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment.v1.WatchDesiredStateResponse"; } + + explicit WatchDesiredStateResponse(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + WatchDesiredStateResponse(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const WatchDesiredStateResponse& from); + WatchDesiredStateResponse( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, WatchDesiredStateResponse&& from) noexcept + : WatchDesiredStateResponse(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kRevisionFieldNumber = 1, + }; + // uint64 revision = 1 [json_name = "revision"]; + void clear_revision() ; + [[nodiscard]] ::uint64_t revision() const; + void set_revision(::uint64_t value); + + private: + ::uint64_t _internal_revision() const; + void _internal_set_revision(::uint64_t value); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment.v1.WatchDesiredStateResponse) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<0, 1, + 0, 0, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const WatchDesiredStateResponse& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::uint64_t revision_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_2fv1_2fagent_2eproto; +}; +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED WatchDesiredStateRequest final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment.v1.WatchDesiredStateRequest) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline WatchDesiredStateRequest() : WatchDesiredStateRequest(nullptr) {} + ~WatchDesiredStateRequest() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(WatchDesiredStateRequest* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(WatchDesiredStateRequest)); + } +#endif + + template + explicit constexpr WatchDesiredStateRequest(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline WatchDesiredStateRequest(const WatchDesiredStateRequest& from) : WatchDesiredStateRequest(nullptr, from) {} + inline WatchDesiredStateRequest(WatchDesiredStateRequest&& from) noexcept : WatchDesiredStateRequest(nullptr, ::std::move(from)) {} + inline WatchDesiredStateRequest& operator=(const WatchDesiredStateRequest& from) { + CopyFrom(from); + return *this; + } + inline WatchDesiredStateRequest& operator=(WatchDesiredStateRequest&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const WatchDesiredStateRequest& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&WatchDesiredStateRequest_globals_); + } + static constexpr int kIndexInFileMessages = 4; + friend void swap(WatchDesiredStateRequest& a, WatchDesiredStateRequest& b) { a.Swap(&b); } + inline void Swap(WatchDesiredStateRequest* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(WatchDesiredStateRequest* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] WatchDesiredStateRequest* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const WatchDesiredStateRequest& from); + using Super_::MergeFrom; + void MergeFrom(const WatchDesiredStateRequest& from) { WatchDesiredStateRequest::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(WatchDesiredStateRequest* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment.v1.WatchDesiredStateRequest"; } + + explicit WatchDesiredStateRequest(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + WatchDesiredStateRequest(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const WatchDesiredStateRequest& from); + WatchDesiredStateRequest( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, WatchDesiredStateRequest&& from) noexcept + : WatchDesiredStateRequest(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kAgentIdFieldNumber = 1, + kAfterRevisionFieldNumber = 2, + }; + // string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { + void clear_agent_id() ; + [[nodiscard]] const ::std::string& agent_id() const; + template + void set_agent_id(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_agent_id(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_agent_id(); + void set_allocated_agent_id(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_agent_id() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_agent_id(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_agent_id(); + + public: + // uint64 after_revision = 2 [json_name = "afterRevision"]; + void clear_after_revision() ; + [[nodiscard]] ::uint64_t after_revision() const; + void set_after_revision(::uint64_t value); + + private: + ::uint64_t _internal_after_revision() const; + void _internal_set_after_revision(::uint64_t value); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment.v1.WatchDesiredStateRequest) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<1, 2, + 0, 59, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const WatchDesiredStateRequest& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::google::protobuf::internal::ArenaStringPtr agent_id_; + ::uint64_t after_revision_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_2fv1_2fagent_2eproto; +}; +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED GetDesiredStateRequest final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment.v1.GetDesiredStateRequest) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline GetDesiredStateRequest() : GetDesiredStateRequest(nullptr) {} + ~GetDesiredStateRequest() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(GetDesiredStateRequest* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(GetDesiredStateRequest)); + } +#endif + + template + explicit constexpr GetDesiredStateRequest(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline GetDesiredStateRequest(const GetDesiredStateRequest& from) : GetDesiredStateRequest(nullptr, from) {} + inline GetDesiredStateRequest(GetDesiredStateRequest&& from) noexcept : GetDesiredStateRequest(nullptr, ::std::move(from)) {} + inline GetDesiredStateRequest& operator=(const GetDesiredStateRequest& from) { + CopyFrom(from); + return *this; + } + inline GetDesiredStateRequest& operator=(GetDesiredStateRequest&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const GetDesiredStateRequest& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&GetDesiredStateRequest_globals_); + } + static constexpr int kIndexInFileMessages = 2; + friend void swap(GetDesiredStateRequest& a, GetDesiredStateRequest& b) { a.Swap(&b); } + inline void Swap(GetDesiredStateRequest* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(GetDesiredStateRequest* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] GetDesiredStateRequest* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const GetDesiredStateRequest& from); + using Super_::MergeFrom; + void MergeFrom(const GetDesiredStateRequest& from) { GetDesiredStateRequest::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(GetDesiredStateRequest* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment.v1.GetDesiredStateRequest"; } + + explicit GetDesiredStateRequest(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + GetDesiredStateRequest(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const GetDesiredStateRequest& from); + GetDesiredStateRequest( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, GetDesiredStateRequest&& from) noexcept + : GetDesiredStateRequest(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kAgentIdFieldNumber = 1, + }; + // string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { + void clear_agent_id() ; + [[nodiscard]] const ::std::string& agent_id() const; + template + void set_agent_id(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_agent_id(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_agent_id(); + void set_allocated_agent_id(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_agent_id() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_agent_id(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_agent_id(); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment.v1.GetDesiredStateRequest) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<0, 1, + 0, 57, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const GetDesiredStateRequest& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::google::protobuf::internal::ArenaStringPtr agent_id_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_2fv1_2fagent_2eproto; +}; +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED GetDesiredStateResponse final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment.v1.GetDesiredStateResponse) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline GetDesiredStateResponse() : GetDesiredStateResponse(nullptr) {} + ~GetDesiredStateResponse() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(GetDesiredStateResponse* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(GetDesiredStateResponse)); + } +#endif + + template + explicit constexpr GetDesiredStateResponse(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline GetDesiredStateResponse(const GetDesiredStateResponse& from) : GetDesiredStateResponse(nullptr, from) {} + inline GetDesiredStateResponse(GetDesiredStateResponse&& from) noexcept : GetDesiredStateResponse(nullptr, ::std::move(from)) {} + inline GetDesiredStateResponse& operator=(const GetDesiredStateResponse& from) { + CopyFrom(from); + return *this; + } + inline GetDesiredStateResponse& operator=(GetDesiredStateResponse&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const GetDesiredStateResponse& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&GetDesiredStateResponse_globals_); + } + static constexpr int kIndexInFileMessages = 3; + friend void swap(GetDesiredStateResponse& a, GetDesiredStateResponse& b) { a.Swap(&b); } + inline void Swap(GetDesiredStateResponse* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(GetDesiredStateResponse* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] GetDesiredStateResponse* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const GetDesiredStateResponse& from); + using Super_::MergeFrom; + void MergeFrom(const GetDesiredStateResponse& from) { GetDesiredStateResponse::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(GetDesiredStateResponse* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment.v1.GetDesiredStateResponse"; } + + explicit GetDesiredStateResponse(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + GetDesiredStateResponse(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const GetDesiredStateResponse& from); + GetDesiredStateResponse( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, GetDesiredStateResponse&& from) noexcept + : GetDesiredStateResponse(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kDesiredFieldNumber = 1, + }; + // .xtcp_treatment.v1.DesiredState desired = 1 [json_name = "desired"]; + [[nodiscard]] bool has_desired() const; + void clear_desired() ; + [[nodiscard]] const ::xtcp_treatment::v1::DesiredState& desired() const; + [[nodiscard]] ::xtcp_treatment::v1::DesiredState* PROTOBUF_NULLABLE release_desired(); + ::xtcp_treatment::v1::DesiredState* PROTOBUF_NONNULL mutable_desired(); + void set_allocated_desired(::xtcp_treatment::v1::DesiredState* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_desired(::xtcp_treatment::v1::DesiredState* PROTOBUF_NULLABLE value); + ::xtcp_treatment::v1::DesiredState* PROTOBUF_NULLABLE unsafe_arena_release_desired(); + + private: + const ::xtcp_treatment::v1::DesiredState& _internal_desired() const; + ::xtcp_treatment::v1::DesiredState* PROTOBUF_NONNULL _internal_mutable_desired(); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment.v1.GetDesiredStateResponse) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<0, 1, + 1, 0, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const GetDesiredStateResponse& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::xtcp_treatment::v1::DesiredState* PROTOBUF_NULLABLE desired_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_2fv1_2fagent_2eproto; +}; +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED CheckInResponse final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment.v1.CheckInResponse) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline CheckInResponse() : CheckInResponse(nullptr) {} + ~CheckInResponse() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(CheckInResponse* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(CheckInResponse)); + } +#endif + + template + explicit constexpr CheckInResponse(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline CheckInResponse(const CheckInResponse& from) : CheckInResponse(nullptr, from) {} + inline CheckInResponse(CheckInResponse&& from) noexcept : CheckInResponse(nullptr, ::std::move(from)) {} + inline CheckInResponse& operator=(const CheckInResponse& from) { + CopyFrom(from); + return *this; + } + inline CheckInResponse& operator=(CheckInResponse&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const CheckInResponse& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&CheckInResponse_globals_); + } + static constexpr int kIndexInFileMessages = 1; + friend void swap(CheckInResponse& a, CheckInResponse& b) { a.Swap(&b); } + inline void Swap(CheckInResponse* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(CheckInResponse* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] CheckInResponse* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const CheckInResponse& from); + using Super_::MergeFrom; + void MergeFrom(const CheckInResponse& from) { CheckInResponse::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(CheckInResponse* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment.v1.CheckInResponse"; } + + explicit CheckInResponse(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + CheckInResponse(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const CheckInResponse& from); + CheckInResponse( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, CheckInResponse&& from) noexcept + : CheckInResponse(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kDesiredFieldNumber = 1, + }; + // .xtcp_treatment.v1.DesiredState desired = 1 [json_name = "desired"]; + [[nodiscard]] bool has_desired() const; + void clear_desired() ; + [[nodiscard]] const ::xtcp_treatment::v1::DesiredState& desired() const; + [[nodiscard]] ::xtcp_treatment::v1::DesiredState* PROTOBUF_NULLABLE release_desired(); + ::xtcp_treatment::v1::DesiredState* PROTOBUF_NONNULL mutable_desired(); + void set_allocated_desired(::xtcp_treatment::v1::DesiredState* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_desired(::xtcp_treatment::v1::DesiredState* PROTOBUF_NULLABLE value); + ::xtcp_treatment::v1::DesiredState* PROTOBUF_NULLABLE unsafe_arena_release_desired(); + + private: + const ::xtcp_treatment::v1::DesiredState& _internal_desired() const; + ::xtcp_treatment::v1::DesiredState* PROTOBUF_NONNULL _internal_mutable_desired(); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment.v1.CheckInResponse) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<0, 1, + 1, 0, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const CheckInResponse& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::xtcp_treatment::v1::DesiredState* PROTOBUF_NULLABLE desired_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_2fv1_2fagent_2eproto; +}; +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED CheckInRequest final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment.v1.CheckInRequest) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline CheckInRequest() : CheckInRequest(nullptr) {} + ~CheckInRequest() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(CheckInRequest* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(CheckInRequest)); + } +#endif + + template + explicit constexpr CheckInRequest(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline CheckInRequest(const CheckInRequest& from) : CheckInRequest(nullptr, from) {} + inline CheckInRequest(CheckInRequest&& from) noexcept : CheckInRequest(nullptr, ::std::move(from)) {} + inline CheckInRequest& operator=(const CheckInRequest& from) { + CopyFrom(from); + return *this; + } + inline CheckInRequest& operator=(CheckInRequest&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const CheckInRequest& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&CheckInRequest_globals_); + } + static constexpr int kIndexInFileMessages = 0; + friend void swap(CheckInRequest& a, CheckInRequest& b) { a.Swap(&b); } + inline void Swap(CheckInRequest* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(CheckInRequest* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] CheckInRequest* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const CheckInRequest& from); + using Super_::MergeFrom; + void MergeFrom(const CheckInRequest& from) { CheckInRequest::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(CheckInRequest* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment.v1.CheckInRequest"; } + + explicit CheckInRequest(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + CheckInRequest(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const CheckInRequest& from); + CheckInRequest( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, CheckInRequest&& from) noexcept + : CheckInRequest(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kRecordsFieldNumber = 5, + kAgentIdFieldNumber = 1, + kLocationFieldNumber = 2, + kCapabilitiesFieldNumber = 3, + kDesiredRevisionFieldNumber = 4, + kLocallySuspendedFieldNumber = 6, + }; + // repeated .xtcp_treatment.v1.ApplicationRecord records = 5 [json_name = "records", (.buf.validate.field) = { + [[nodiscard]] int records_size() const; + private: + int _internal_records_size() const; + + public: + void clear_records() ; + [[nodiscard]] const ::xtcp_treatment::v1::ApplicationRecord& records(int index) const; + [[nodiscard]] ::xtcp_treatment::v1::ApplicationRecord* PROTOBUF_NONNULL mutable_records(int index); + ::xtcp_treatment::v1::ApplicationRecord* PROTOBUF_NONNULL add_records(); + [[nodiscard]] const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::ApplicationRecord>& + records() const; + [[nodiscard]] ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::ApplicationRecord>* PROTOBUF_NONNULL + mutable_records(); + + private: + const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::ApplicationRecord>& _internal_records() const; + ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::ApplicationRecord>* PROTOBUF_NONNULL _internal_mutable_records(); + + public: + // string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { + void clear_agent_id() ; + [[nodiscard]] const ::std::string& agent_id() const; + template + void set_agent_id(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_agent_id(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_agent_id(); + void set_allocated_agent_id(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_agent_id() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_agent_id(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_agent_id(); + + public: + // string location = 2 [json_name = "location", (.buf.validate.field) = { + void clear_location() ; + [[nodiscard]] const ::std::string& location() const; + template + void set_location(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_location(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_location(); + void set_allocated_location(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_location() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_location(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_location(); + + public: + // .xtcp_treatment.v1.Capabilities capabilities = 3 [json_name = "capabilities", (.buf.validate.field) = { + [[nodiscard]] bool has_capabilities() const; + void clear_capabilities() ; + [[nodiscard]] const ::xtcp_treatment::v1::Capabilities& capabilities() const; + [[nodiscard]] ::xtcp_treatment::v1::Capabilities* PROTOBUF_NULLABLE release_capabilities(); + ::xtcp_treatment::v1::Capabilities* PROTOBUF_NONNULL mutable_capabilities(); + void set_allocated_capabilities(::xtcp_treatment::v1::Capabilities* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_capabilities(::xtcp_treatment::v1::Capabilities* PROTOBUF_NULLABLE value); + ::xtcp_treatment::v1::Capabilities* PROTOBUF_NULLABLE unsafe_arena_release_capabilities(); + + private: + const ::xtcp_treatment::v1::Capabilities& _internal_capabilities() const; + ::xtcp_treatment::v1::Capabilities* PROTOBUF_NONNULL _internal_mutable_capabilities(); + + public: + // uint64 desired_revision = 4 [json_name = "desiredRevision"]; + void clear_desired_revision() ; + [[nodiscard]] ::uint64_t desired_revision() const; + void set_desired_revision(::uint64_t value); + + private: + ::uint64_t _internal_desired_revision() const; + void _internal_set_desired_revision(::uint64_t value); + + public: + // bool locally_suspended = 6 [json_name = "locallySuspended"]; + void clear_locally_suspended() ; + [[nodiscard]] bool locally_suspended() const; + void set_locally_suspended(bool value); + + private: + bool _internal_locally_suspended() const; + void _internal_set_locally_suspended(bool value); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment.v1.CheckInRequest) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<3, 6, + 2, 57, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const CheckInRequest& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::google::protobuf::RepeatedPtrField< ::xtcp_treatment::v1::ApplicationRecord > records_; + ::google::protobuf::internal::ArenaStringPtr agent_id_; + ::google::protobuf::internal::ArenaStringPtr location_; + ::xtcp_treatment::v1::Capabilities* PROTOBUF_NULLABLE capabilities_; + ::uint64_t desired_revision_; + bool locally_suspended_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_2fv1_2fagent_2eproto; +}; + +// =================================================================== + + + + +// =================================================================== + + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif // __GNUC__ +// ------------------------------------------------------------------- + +// CheckInRequest + +// string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { +inline void CheckInRequest::clear_agent_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.agent_id_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); +} +inline const ::std::string& CheckInRequest::agent_id() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.CheckInRequest.agent_id) + return _internal_agent_id(); +} +template +PROTOBUF_ALWAYS_INLINE void CheckInRequest::set_agent_id(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + _impl_.agent_id_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.CheckInRequest.agent_id) +} +inline ::std::string* PROTOBUF_NONNULL CheckInRequest::mutable_agent_id() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + ::std::string* _s = _internal_mutable_agent_id(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.CheckInRequest.agent_id) + return _s; +} +inline const ::std::string& CheckInRequest::_internal_agent_id() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.agent_id_.Get(); +} +inline void CheckInRequest::_internal_set_agent_id(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.agent_id_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL CheckInRequest::_internal_mutable_agent_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.agent_id_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE CheckInRequest::release_agent_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.CheckInRequest.agent_id) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000002U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + auto* released = _impl_.agent_id_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.agent_id_.Set("", GetArena()); + } + return released; +} +inline void CheckInRequest::set_allocated_agent_id(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + } + _impl_.agent_id_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.agent_id_.IsDefault()) { + _impl_.agent_id_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.CheckInRequest.agent_id) +} + +// string location = 2 [json_name = "location", (.buf.validate.field) = { +inline void CheckInRequest::clear_location() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.location_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); +} +inline const ::std::string& CheckInRequest::location() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.CheckInRequest.location) + return _internal_location(); +} +template +PROTOBUF_ALWAYS_INLINE void CheckInRequest::set_location(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + _impl_.location_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.CheckInRequest.location) +} +inline ::std::string* PROTOBUF_NONNULL CheckInRequest::mutable_location() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + ::std::string* _s = _internal_mutable_location(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.CheckInRequest.location) + return _s; +} +inline const ::std::string& CheckInRequest::_internal_location() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.location_.Get(); +} +inline void CheckInRequest::_internal_set_location(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.location_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL CheckInRequest::_internal_mutable_location() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.location_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE CheckInRequest::release_location() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.CheckInRequest.location) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000004U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + auto* released = _impl_.location_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.location_.Set("", GetArena()); + } + return released; +} +inline void CheckInRequest::set_allocated_location(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + } + _impl_.location_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.location_.IsDefault()) { + _impl_.location_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.CheckInRequest.location) +} + +// .xtcp_treatment.v1.Capabilities capabilities = 3 [json_name = "capabilities", (.buf.validate.field) = { +inline bool CheckInRequest::has_capabilities() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000008U); + PROTOBUF_ASSUME(!value || _impl_.capabilities_ != nullptr); + return value; +} +inline const ::xtcp_treatment::v1::Capabilities& CheckInRequest::_internal_capabilities() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::xtcp_treatment::v1::Capabilities* p = _impl_.capabilities_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::xtcp_treatment::v1::Capabilities>(&::xtcp_treatment::v1::Capabilities_globals_); +} +inline const ::xtcp_treatment::v1::Capabilities& CheckInRequest::capabilities() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.CheckInRequest.capabilities) + return _internal_capabilities(); +} +inline void CheckInRequest::unsafe_arena_set_allocated_capabilities( + ::xtcp_treatment::v1::Capabilities* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.capabilities_); + } + _impl_.capabilities_ = reinterpret_cast<::xtcp_treatment::v1::Capabilities*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.CheckInRequest.capabilities) +} +inline ::xtcp_treatment::v1::Capabilities* PROTOBUF_NULLABLE CheckInRequest::release_capabilities() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + ::xtcp_treatment::v1::Capabilities* released = _impl_.capabilities_; + _impl_.capabilities_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::xtcp_treatment::v1::Capabilities* PROTOBUF_NULLABLE CheckInRequest::unsafe_arena_release_capabilities() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.CheckInRequest.capabilities) + + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + ::xtcp_treatment::v1::Capabilities* temp = _impl_.capabilities_; + _impl_.capabilities_ = nullptr; + return temp; +} +inline ::xtcp_treatment::v1::Capabilities* PROTOBUF_NONNULL CheckInRequest::_internal_mutable_capabilities() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.capabilities_ == nullptr) { + auto* p = Super_::DefaultConstruct<::xtcp_treatment::v1::Capabilities>(GetArena()); + _impl_.capabilities_ = reinterpret_cast<::xtcp_treatment::v1::Capabilities*>(p); + } + return _impl_.capabilities_; +} +inline ::xtcp_treatment::v1::Capabilities* PROTOBUF_NONNULL CheckInRequest::mutable_capabilities() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + ::xtcp_treatment::v1::Capabilities* _msg = _internal_mutable_capabilities(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.CheckInRequest.capabilities) + return _msg; +} +inline void CheckInRequest::set_allocated_capabilities(::xtcp_treatment::v1::Capabilities* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.capabilities_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + } + + _impl_.capabilities_ = reinterpret_cast<::xtcp_treatment::v1::Capabilities*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.CheckInRequest.capabilities) +} + +// uint64 desired_revision = 4 [json_name = "desiredRevision"]; +inline void CheckInRequest::clear_desired_revision() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.desired_revision_ = ::uint64_t{0u}; + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); +} +inline ::uint64_t CheckInRequest::desired_revision() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.CheckInRequest.desired_revision) + return _internal_desired_revision(); +} +inline void CheckInRequest::set_desired_revision(::uint64_t value) { + _internal_set_desired_revision(value); + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.CheckInRequest.desired_revision) +} +inline ::uint64_t CheckInRequest::_internal_desired_revision() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.desired_revision_; +} +inline void CheckInRequest::_internal_set_desired_revision(::uint64_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.desired_revision_ = value; +} + +// repeated .xtcp_treatment.v1.ApplicationRecord records = 5 [json_name = "records", (.buf.validate.field) = { +inline int CheckInRequest::_internal_records_size() const { + return _internal_records().size(); +} +inline int CheckInRequest::records_size() const { + return _internal_records_size(); +} +inline const ::xtcp_treatment::v1::ApplicationRecord& CheckInRequest::records(int index) const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.CheckInRequest.records) + return _internal_records().Get(index); +} +inline ::xtcp_treatment::v1::ApplicationRecord* PROTOBUF_NONNULL CheckInRequest::mutable_records(int index) + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.CheckInRequest.records) + return _internal_mutable_records()->Mutable(index); +} +inline ::xtcp_treatment::v1::ApplicationRecord* PROTOBUF_NONNULL CheckInRequest::add_records() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + ::google::protobuf::internal::TSanWrite(&_impl_); + ::xtcp_treatment::v1::ApplicationRecord* _add = + _internal_mutable_records()->InternalAddWithArena( + ::google::protobuf::MessageLite::internal_visibility(), GetArena()); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + // @@protoc_insertion_point(field_add:xtcp_treatment.v1.CheckInRequest.records) + return _add; +} +inline const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::ApplicationRecord>& CheckInRequest::records() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_list:xtcp_treatment.v1.CheckInRequest.records) + return _internal_records(); +} +inline ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::ApplicationRecord>* PROTOBUF_NONNULL +CheckInRequest::mutable_records() ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + // @@protoc_insertion_point(field_mutable_list:xtcp_treatment.v1.CheckInRequest.records) + ::google::protobuf::internal::TSanWrite(&_impl_); + return _internal_mutable_records(); +} +inline const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::ApplicationRecord>& +CheckInRequest::_internal_records() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.records_; +} +inline ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::ApplicationRecord>* PROTOBUF_NONNULL +CheckInRequest::_internal_mutable_records() { + ::google::protobuf::internal::TSanRead(&_impl_); + return &_impl_.records_; +} + +// bool locally_suspended = 6 [json_name = "locallySuspended"]; +inline void CheckInRequest::clear_locally_suspended() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.locally_suspended_ = false; + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); +} +inline bool CheckInRequest::locally_suspended() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.CheckInRequest.locally_suspended) + return _internal_locally_suspended(); +} +inline void CheckInRequest::set_locally_suspended(bool value) { + _internal_set_locally_suspended(value); + SetHasBit(_impl_._has_bits_[0], 0x00000020U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.CheckInRequest.locally_suspended) +} +inline bool CheckInRequest::_internal_locally_suspended() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.locally_suspended_; +} +inline void CheckInRequest::_internal_set_locally_suspended(bool value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.locally_suspended_ = value; +} + +// ------------------------------------------------------------------- + +// CheckInResponse + +// .xtcp_treatment.v1.DesiredState desired = 1 [json_name = "desired"]; +inline bool CheckInResponse::has_desired() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000001U); + PROTOBUF_ASSUME(!value || _impl_.desired_ != nullptr); + return value; +} +inline const ::xtcp_treatment::v1::DesiredState& CheckInResponse::_internal_desired() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::xtcp_treatment::v1::DesiredState* p = _impl_.desired_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::xtcp_treatment::v1::DesiredState>(&::xtcp_treatment::v1::DesiredState_globals_); +} +inline const ::xtcp_treatment::v1::DesiredState& CheckInResponse::desired() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.CheckInResponse.desired) + return _internal_desired(); +} +inline void CheckInResponse::unsafe_arena_set_allocated_desired( + ::xtcp_treatment::v1::DesiredState* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.desired_); + } + _impl_.desired_ = reinterpret_cast<::xtcp_treatment::v1::DesiredState*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.CheckInResponse.desired) +} +inline ::xtcp_treatment::v1::DesiredState* PROTOBUF_NULLABLE CheckInResponse::release_desired() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + ::xtcp_treatment::v1::DesiredState* released = _impl_.desired_; + _impl_.desired_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::xtcp_treatment::v1::DesiredState* PROTOBUF_NULLABLE CheckInResponse::unsafe_arena_release_desired() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.CheckInResponse.desired) + + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + ::xtcp_treatment::v1::DesiredState* temp = _impl_.desired_; + _impl_.desired_ = nullptr; + return temp; +} +inline ::xtcp_treatment::v1::DesiredState* PROTOBUF_NONNULL CheckInResponse::_internal_mutable_desired() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.desired_ == nullptr) { + auto* p = Super_::DefaultConstruct<::xtcp_treatment::v1::DesiredState>(GetArena()); + _impl_.desired_ = reinterpret_cast<::xtcp_treatment::v1::DesiredState*>(p); + } + return _impl_.desired_; +} +inline ::xtcp_treatment::v1::DesiredState* PROTOBUF_NONNULL CheckInResponse::mutable_desired() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + ::xtcp_treatment::v1::DesiredState* _msg = _internal_mutable_desired(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.CheckInResponse.desired) + return _msg; +} +inline void CheckInResponse::set_allocated_desired(::xtcp_treatment::v1::DesiredState* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.desired_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + } + + _impl_.desired_ = reinterpret_cast<::xtcp_treatment::v1::DesiredState*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.CheckInResponse.desired) +} + +// ------------------------------------------------------------------- + +// GetDesiredStateRequest + +// string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { +inline void GetDesiredStateRequest::clear_agent_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.agent_id_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +inline const ::std::string& GetDesiredStateRequest::agent_id() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.GetDesiredStateRequest.agent_id) + return _internal_agent_id(); +} +template +PROTOBUF_ALWAYS_INLINE void GetDesiredStateRequest::set_agent_id(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + _impl_.agent_id_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.GetDesiredStateRequest.agent_id) +} +inline ::std::string* PROTOBUF_NONNULL GetDesiredStateRequest::mutable_agent_id() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + ::std::string* _s = _internal_mutable_agent_id(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.GetDesiredStateRequest.agent_id) + return _s; +} +inline const ::std::string& GetDesiredStateRequest::_internal_agent_id() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.agent_id_.Get(); +} +inline void GetDesiredStateRequest::_internal_set_agent_id(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.agent_id_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL GetDesiredStateRequest::_internal_mutable_agent_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.agent_id_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE GetDesiredStateRequest::release_agent_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.GetDesiredStateRequest.agent_id) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000001U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + auto* released = _impl_.agent_id_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.agent_id_.Set("", GetArena()); + } + return released; +} +inline void GetDesiredStateRequest::set_allocated_agent_id(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + } + _impl_.agent_id_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.agent_id_.IsDefault()) { + _impl_.agent_id_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.GetDesiredStateRequest.agent_id) +} + +// ------------------------------------------------------------------- + +// GetDesiredStateResponse + +// .xtcp_treatment.v1.DesiredState desired = 1 [json_name = "desired"]; +inline bool GetDesiredStateResponse::has_desired() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000001U); + PROTOBUF_ASSUME(!value || _impl_.desired_ != nullptr); + return value; +} +inline const ::xtcp_treatment::v1::DesiredState& GetDesiredStateResponse::_internal_desired() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::xtcp_treatment::v1::DesiredState* p = _impl_.desired_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::xtcp_treatment::v1::DesiredState>(&::xtcp_treatment::v1::DesiredState_globals_); +} +inline const ::xtcp_treatment::v1::DesiredState& GetDesiredStateResponse::desired() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.GetDesiredStateResponse.desired) + return _internal_desired(); +} +inline void GetDesiredStateResponse::unsafe_arena_set_allocated_desired( + ::xtcp_treatment::v1::DesiredState* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.desired_); + } + _impl_.desired_ = reinterpret_cast<::xtcp_treatment::v1::DesiredState*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.GetDesiredStateResponse.desired) +} +inline ::xtcp_treatment::v1::DesiredState* PROTOBUF_NULLABLE GetDesiredStateResponse::release_desired() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + ::xtcp_treatment::v1::DesiredState* released = _impl_.desired_; + _impl_.desired_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::xtcp_treatment::v1::DesiredState* PROTOBUF_NULLABLE GetDesiredStateResponse::unsafe_arena_release_desired() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.GetDesiredStateResponse.desired) + + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + ::xtcp_treatment::v1::DesiredState* temp = _impl_.desired_; + _impl_.desired_ = nullptr; + return temp; +} +inline ::xtcp_treatment::v1::DesiredState* PROTOBUF_NONNULL GetDesiredStateResponse::_internal_mutable_desired() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.desired_ == nullptr) { + auto* p = Super_::DefaultConstruct<::xtcp_treatment::v1::DesiredState>(GetArena()); + _impl_.desired_ = reinterpret_cast<::xtcp_treatment::v1::DesiredState*>(p); + } + return _impl_.desired_; +} +inline ::xtcp_treatment::v1::DesiredState* PROTOBUF_NONNULL GetDesiredStateResponse::mutable_desired() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + ::xtcp_treatment::v1::DesiredState* _msg = _internal_mutable_desired(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.GetDesiredStateResponse.desired) + return _msg; +} +inline void GetDesiredStateResponse::set_allocated_desired(::xtcp_treatment::v1::DesiredState* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.desired_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + } + + _impl_.desired_ = reinterpret_cast<::xtcp_treatment::v1::DesiredState*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.GetDesiredStateResponse.desired) +} + +// ------------------------------------------------------------------- + +// WatchDesiredStateRequest + +// string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { +inline void WatchDesiredStateRequest::clear_agent_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.agent_id_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +inline const ::std::string& WatchDesiredStateRequest::agent_id() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.WatchDesiredStateRequest.agent_id) + return _internal_agent_id(); +} +template +PROTOBUF_ALWAYS_INLINE void WatchDesiredStateRequest::set_agent_id(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + _impl_.agent_id_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.WatchDesiredStateRequest.agent_id) +} +inline ::std::string* PROTOBUF_NONNULL WatchDesiredStateRequest::mutable_agent_id() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + ::std::string* _s = _internal_mutable_agent_id(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.WatchDesiredStateRequest.agent_id) + return _s; +} +inline const ::std::string& WatchDesiredStateRequest::_internal_agent_id() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.agent_id_.Get(); +} +inline void WatchDesiredStateRequest::_internal_set_agent_id(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.agent_id_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL WatchDesiredStateRequest::_internal_mutable_agent_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.agent_id_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE WatchDesiredStateRequest::release_agent_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.WatchDesiredStateRequest.agent_id) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000001U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + auto* released = _impl_.agent_id_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.agent_id_.Set("", GetArena()); + } + return released; +} +inline void WatchDesiredStateRequest::set_allocated_agent_id(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + } + _impl_.agent_id_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.agent_id_.IsDefault()) { + _impl_.agent_id_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.WatchDesiredStateRequest.agent_id) +} + +// uint64 after_revision = 2 [json_name = "afterRevision"]; +inline void WatchDesiredStateRequest::clear_after_revision() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.after_revision_ = ::uint64_t{0u}; + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); +} +inline ::uint64_t WatchDesiredStateRequest::after_revision() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.WatchDesiredStateRequest.after_revision) + return _internal_after_revision(); +} +inline void WatchDesiredStateRequest::set_after_revision(::uint64_t value) { + _internal_set_after_revision(value); + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.WatchDesiredStateRequest.after_revision) +} +inline ::uint64_t WatchDesiredStateRequest::_internal_after_revision() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.after_revision_; +} +inline void WatchDesiredStateRequest::_internal_set_after_revision(::uint64_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.after_revision_ = value; +} + +// ------------------------------------------------------------------- + +// WatchDesiredStateResponse + +// uint64 revision = 1 [json_name = "revision"]; +inline void WatchDesiredStateResponse::clear_revision() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.revision_ = ::uint64_t{0u}; + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +inline ::uint64_t WatchDesiredStateResponse::revision() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.WatchDesiredStateResponse.revision) + return _internal_revision(); +} +inline void WatchDesiredStateResponse::set_revision(::uint64_t value) { + _internal_set_revision(value); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.WatchDesiredStateResponse.revision) +} +inline ::uint64_t WatchDesiredStateResponse::_internal_revision() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.revision_; +} +inline void WatchDesiredStateResponse::_internal_set_revision(::uint64_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.revision_ = value; +} + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif // __GNUC__ + +// @@protoc_insertion_point(namespace_scope) +} // namespace v1 +} // namespace xtcp_treatment + + +// @@protoc_insertion_point(global_scope) + +#include "google/protobuf/port_undef.inc" +// clang-format on + +#endif // xtcp_5ftreatment_2fv1_2fagent_2eproto_2epb_2eh diff --git a/gen/cpp/xtcp_treatment/v1/operator.grpc.pb.cc b/gen/cpp/xtcp_treatment/v1/operator.grpc.pb.cc new file mode 100644 index 000000000..8d58c1a0a --- /dev/null +++ b/gen/cpp/xtcp_treatment/v1/operator.grpc.pb.cc @@ -0,0 +1,258 @@ +// Generated by the gRPC C++ plugin. +// If you make any local change, they will be lost. +// source: xtcp_treatment/v1/operator.proto + +#include "xtcp_treatment/v1/operator.pb.h" +#include "xtcp_treatment/v1/operator.grpc.pb.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +namespace xtcp_treatment { +namespace v1 { + +static const char* OperatorService_method_names[] = { + "/xtcp_treatment.v1.OperatorService/Propose", + "/xtcp_treatment.v1.OperatorService/Analyze", + "/xtcp_treatment.v1.OperatorService/Review", + "/xtcp_treatment.v1.OperatorService/Control", + "/xtcp_treatment.v1.OperatorService/Inspect", +}; + +std::unique_ptr< OperatorService::Stub> OperatorService::NewStub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options) { + (void)options; + std::unique_ptr< OperatorService::Stub> stub(new OperatorService::Stub(channel, options)); + return stub; +} + +OperatorService::Stub::Stub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options) + : channel_(channel), rpcmethod_Propose_(OperatorService_method_names[0], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel) + , rpcmethod_Analyze_(OperatorService_method_names[1], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel) + , rpcmethod_Review_(OperatorService_method_names[2], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel) + , rpcmethod_Control_(OperatorService_method_names[3], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel) + , rpcmethod_Inspect_(OperatorService_method_names[4], options.suffix_for_stats(),::grpc::internal::RpcMethod::NORMAL_RPC, channel) + {} + +::grpc::Status OperatorService::Stub::Propose(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ProposeRequest& request, ::xtcp_treatment::v1::ProposeResponse* response) { + return ::grpc::internal::BlockingUnaryCall< ::xtcp_treatment::v1::ProposeRequest, ::xtcp_treatment::v1::ProposeResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_Propose_, context, request, response); +} + +void OperatorService::Stub::async::Propose(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ProposeRequest* request, ::xtcp_treatment::v1::ProposeResponse* response, std::function f) { + ::grpc::internal::CallbackUnaryCall< ::xtcp_treatment::v1::ProposeRequest, ::xtcp_treatment::v1::ProposeResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_Propose_, context, request, response, std::move(f)); +} + +void OperatorService::Stub::async::Propose(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ProposeRequest* request, ::xtcp_treatment::v1::ProposeResponse* response, ::grpc::ClientUnaryReactor* reactor) { + ::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_Propose_, context, request, response, reactor); +} + +::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::ProposeResponse>* OperatorService::Stub::PrepareAsyncProposeRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ProposeRequest& request, ::grpc::CompletionQueue* cq) { + return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::xtcp_treatment::v1::ProposeResponse, ::xtcp_treatment::v1::ProposeRequest, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_Propose_, context, request); +} + +::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::ProposeResponse>* OperatorService::Stub::AsyncProposeRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ProposeRequest& request, ::grpc::CompletionQueue* cq) { + auto* result = + this->PrepareAsyncProposeRaw(context, request, cq); + result->StartCall(); + return result; +} + +::grpc::Status OperatorService::Stub::Analyze(::grpc::ClientContext* context, const ::xtcp_treatment::v1::AnalyzeRequest& request, ::xtcp_treatment::v1::AnalyzeResponse* response) { + return ::grpc::internal::BlockingUnaryCall< ::xtcp_treatment::v1::AnalyzeRequest, ::xtcp_treatment::v1::AnalyzeResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_Analyze_, context, request, response); +} + +void OperatorService::Stub::async::Analyze(::grpc::ClientContext* context, const ::xtcp_treatment::v1::AnalyzeRequest* request, ::xtcp_treatment::v1::AnalyzeResponse* response, std::function f) { + ::grpc::internal::CallbackUnaryCall< ::xtcp_treatment::v1::AnalyzeRequest, ::xtcp_treatment::v1::AnalyzeResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_Analyze_, context, request, response, std::move(f)); +} + +void OperatorService::Stub::async::Analyze(::grpc::ClientContext* context, const ::xtcp_treatment::v1::AnalyzeRequest* request, ::xtcp_treatment::v1::AnalyzeResponse* response, ::grpc::ClientUnaryReactor* reactor) { + ::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_Analyze_, context, request, response, reactor); +} + +::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::AnalyzeResponse>* OperatorService::Stub::PrepareAsyncAnalyzeRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::AnalyzeRequest& request, ::grpc::CompletionQueue* cq) { + return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::xtcp_treatment::v1::AnalyzeResponse, ::xtcp_treatment::v1::AnalyzeRequest, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_Analyze_, context, request); +} + +::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::AnalyzeResponse>* OperatorService::Stub::AsyncAnalyzeRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::AnalyzeRequest& request, ::grpc::CompletionQueue* cq) { + auto* result = + this->PrepareAsyncAnalyzeRaw(context, request, cq); + result->StartCall(); + return result; +} + +::grpc::Status OperatorService::Stub::Review(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ReviewRequest& request, ::xtcp_treatment::v1::ReviewResponse* response) { + return ::grpc::internal::BlockingUnaryCall< ::xtcp_treatment::v1::ReviewRequest, ::xtcp_treatment::v1::ReviewResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_Review_, context, request, response); +} + +void OperatorService::Stub::async::Review(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ReviewRequest* request, ::xtcp_treatment::v1::ReviewResponse* response, std::function f) { + ::grpc::internal::CallbackUnaryCall< ::xtcp_treatment::v1::ReviewRequest, ::xtcp_treatment::v1::ReviewResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_Review_, context, request, response, std::move(f)); +} + +void OperatorService::Stub::async::Review(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ReviewRequest* request, ::xtcp_treatment::v1::ReviewResponse* response, ::grpc::ClientUnaryReactor* reactor) { + ::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_Review_, context, request, response, reactor); +} + +::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::ReviewResponse>* OperatorService::Stub::PrepareAsyncReviewRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ReviewRequest& request, ::grpc::CompletionQueue* cq) { + return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::xtcp_treatment::v1::ReviewResponse, ::xtcp_treatment::v1::ReviewRequest, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_Review_, context, request); +} + +::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::ReviewResponse>* OperatorService::Stub::AsyncReviewRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ReviewRequest& request, ::grpc::CompletionQueue* cq) { + auto* result = + this->PrepareAsyncReviewRaw(context, request, cq); + result->StartCall(); + return result; +} + +::grpc::Status OperatorService::Stub::Control(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ControlRequest& request, ::xtcp_treatment::v1::ControlResponse* response) { + return ::grpc::internal::BlockingUnaryCall< ::xtcp_treatment::v1::ControlRequest, ::xtcp_treatment::v1::ControlResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_Control_, context, request, response); +} + +void OperatorService::Stub::async::Control(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ControlRequest* request, ::xtcp_treatment::v1::ControlResponse* response, std::function f) { + ::grpc::internal::CallbackUnaryCall< ::xtcp_treatment::v1::ControlRequest, ::xtcp_treatment::v1::ControlResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_Control_, context, request, response, std::move(f)); +} + +void OperatorService::Stub::async::Control(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ControlRequest* request, ::xtcp_treatment::v1::ControlResponse* response, ::grpc::ClientUnaryReactor* reactor) { + ::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_Control_, context, request, response, reactor); +} + +::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::ControlResponse>* OperatorService::Stub::PrepareAsyncControlRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ControlRequest& request, ::grpc::CompletionQueue* cq) { + return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::xtcp_treatment::v1::ControlResponse, ::xtcp_treatment::v1::ControlRequest, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_Control_, context, request); +} + +::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::ControlResponse>* OperatorService::Stub::AsyncControlRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ControlRequest& request, ::grpc::CompletionQueue* cq) { + auto* result = + this->PrepareAsyncControlRaw(context, request, cq); + result->StartCall(); + return result; +} + +::grpc::Status OperatorService::Stub::Inspect(::grpc::ClientContext* context, const ::xtcp_treatment::v1::InspectRequest& request, ::xtcp_treatment::v1::InspectResponse* response) { + return ::grpc::internal::BlockingUnaryCall< ::xtcp_treatment::v1::InspectRequest, ::xtcp_treatment::v1::InspectResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), rpcmethod_Inspect_, context, request, response); +} + +void OperatorService::Stub::async::Inspect(::grpc::ClientContext* context, const ::xtcp_treatment::v1::InspectRequest* request, ::xtcp_treatment::v1::InspectResponse* response, std::function f) { + ::grpc::internal::CallbackUnaryCall< ::xtcp_treatment::v1::InspectRequest, ::xtcp_treatment::v1::InspectResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_Inspect_, context, request, response, std::move(f)); +} + +void OperatorService::Stub::async::Inspect(::grpc::ClientContext* context, const ::xtcp_treatment::v1::InspectRequest* request, ::xtcp_treatment::v1::InspectResponse* response, ::grpc::ClientUnaryReactor* reactor) { + ::grpc::internal::ClientCallbackUnaryFactory::Create< ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(stub_->channel_.get(), stub_->rpcmethod_Inspect_, context, request, response, reactor); +} + +::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::InspectResponse>* OperatorService::Stub::PrepareAsyncInspectRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::InspectRequest& request, ::grpc::CompletionQueue* cq) { + return ::grpc::internal::ClientAsyncResponseReaderHelper::Create< ::xtcp_treatment::v1::InspectResponse, ::xtcp_treatment::v1::InspectRequest, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>(channel_.get(), cq, rpcmethod_Inspect_, context, request); +} + +::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::InspectResponse>* OperatorService::Stub::AsyncInspectRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::InspectRequest& request, ::grpc::CompletionQueue* cq) { + auto* result = + this->PrepareAsyncInspectRaw(context, request, cq); + result->StartCall(); + return result; +} + +OperatorService::Service::Service() { + AddMethod(new ::grpc::internal::RpcServiceMethod( + OperatorService_method_names[0], + ::grpc::internal::RpcMethod::NORMAL_RPC, + new ::grpc::internal::RpcMethodHandler< OperatorService::Service, ::xtcp_treatment::v1::ProposeRequest, ::xtcp_treatment::v1::ProposeResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>( + [](OperatorService::Service* service, + ::grpc::ServerContext* ctx, + const ::xtcp_treatment::v1::ProposeRequest* req, + ::xtcp_treatment::v1::ProposeResponse* resp) { + return service->Propose(ctx, req, resp); + }, this))); + AddMethod(new ::grpc::internal::RpcServiceMethod( + OperatorService_method_names[1], + ::grpc::internal::RpcMethod::NORMAL_RPC, + new ::grpc::internal::RpcMethodHandler< OperatorService::Service, ::xtcp_treatment::v1::AnalyzeRequest, ::xtcp_treatment::v1::AnalyzeResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>( + [](OperatorService::Service* service, + ::grpc::ServerContext* ctx, + const ::xtcp_treatment::v1::AnalyzeRequest* req, + ::xtcp_treatment::v1::AnalyzeResponse* resp) { + return service->Analyze(ctx, req, resp); + }, this))); + AddMethod(new ::grpc::internal::RpcServiceMethod( + OperatorService_method_names[2], + ::grpc::internal::RpcMethod::NORMAL_RPC, + new ::grpc::internal::RpcMethodHandler< OperatorService::Service, ::xtcp_treatment::v1::ReviewRequest, ::xtcp_treatment::v1::ReviewResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>( + [](OperatorService::Service* service, + ::grpc::ServerContext* ctx, + const ::xtcp_treatment::v1::ReviewRequest* req, + ::xtcp_treatment::v1::ReviewResponse* resp) { + return service->Review(ctx, req, resp); + }, this))); + AddMethod(new ::grpc::internal::RpcServiceMethod( + OperatorService_method_names[3], + ::grpc::internal::RpcMethod::NORMAL_RPC, + new ::grpc::internal::RpcMethodHandler< OperatorService::Service, ::xtcp_treatment::v1::ControlRequest, ::xtcp_treatment::v1::ControlResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>( + [](OperatorService::Service* service, + ::grpc::ServerContext* ctx, + const ::xtcp_treatment::v1::ControlRequest* req, + ::xtcp_treatment::v1::ControlResponse* resp) { + return service->Control(ctx, req, resp); + }, this))); + AddMethod(new ::grpc::internal::RpcServiceMethod( + OperatorService_method_names[4], + ::grpc::internal::RpcMethod::NORMAL_RPC, + new ::grpc::internal::RpcMethodHandler< OperatorService::Service, ::xtcp_treatment::v1::InspectRequest, ::xtcp_treatment::v1::InspectResponse, ::grpc::protobuf::MessageLite, ::grpc::protobuf::MessageLite>( + [](OperatorService::Service* service, + ::grpc::ServerContext* ctx, + const ::xtcp_treatment::v1::InspectRequest* req, + ::xtcp_treatment::v1::InspectResponse* resp) { + return service->Inspect(ctx, req, resp); + }, this))); +} + +OperatorService::Service::~Service() { +} + +::grpc::Status OperatorService::Service::Propose(::grpc::ServerContext* context, const ::xtcp_treatment::v1::ProposeRequest* request, ::xtcp_treatment::v1::ProposeResponse* response) { + (void) context; + (void) request; + (void) response; + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); +} + +::grpc::Status OperatorService::Service::Analyze(::grpc::ServerContext* context, const ::xtcp_treatment::v1::AnalyzeRequest* request, ::xtcp_treatment::v1::AnalyzeResponse* response) { + (void) context; + (void) request; + (void) response; + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); +} + +::grpc::Status OperatorService::Service::Review(::grpc::ServerContext* context, const ::xtcp_treatment::v1::ReviewRequest* request, ::xtcp_treatment::v1::ReviewResponse* response) { + (void) context; + (void) request; + (void) response; + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); +} + +::grpc::Status OperatorService::Service::Control(::grpc::ServerContext* context, const ::xtcp_treatment::v1::ControlRequest* request, ::xtcp_treatment::v1::ControlResponse* response) { + (void) context; + (void) request; + (void) response; + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); +} + +::grpc::Status OperatorService::Service::Inspect(::grpc::ServerContext* context, const ::xtcp_treatment::v1::InspectRequest* request, ::xtcp_treatment::v1::InspectResponse* response) { + (void) context; + (void) request; + (void) response; + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); +} + + +} // namespace xtcp_treatment +} // namespace v1 +#include + diff --git a/gen/cpp/xtcp_treatment/v1/operator.grpc.pb.h b/gen/cpp/xtcp_treatment/v1/operator.grpc.pb.h new file mode 100644 index 000000000..72df21c03 --- /dev/null +++ b/gen/cpp/xtcp_treatment/v1/operator.grpc.pb.h @@ -0,0 +1,881 @@ +// Generated by the gRPC C++ plugin. +// If you make any local change, they will be lost. +// source: xtcp_treatment/v1/operator.proto +// Original file comments: +// +// xTCP - treatment operator service +// +// The authenticated review surface. Every mutation carries a request_id; +// repeating one with the same body replays the original response, repeating +// it with a different body is refused. Reviews also fence on the experiment +// revision the operator last saw. +// +#ifndef GRPC_xtcp_5ftreatment_2fv1_2foperator_2eproto__INCLUDED +#define GRPC_xtcp_5ftreatment_2fv1_2foperator_2eproto__INCLUDED + +#include "xtcp_treatment/v1/operator.pb.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xtcp_treatment { +namespace v1 { + +class OperatorService final { + public: + static constexpr char const* service_full_name() { + return "xtcp_treatment.v1.OperatorService"; + } + class StubInterface { + public: + virtual ~StubInterface() {} + virtual ::grpc::Status Propose(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ProposeRequest& request, ::xtcp_treatment::v1::ProposeResponse* response) = 0; + std::unique_ptr< ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::ProposeResponse>> AsyncPropose(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ProposeRequest& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::ProposeResponse>>(AsyncProposeRaw(context, request, cq)); + } + std::unique_ptr< ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::ProposeResponse>> PrepareAsyncPropose(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ProposeRequest& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::ProposeResponse>>(PrepareAsyncProposeRaw(context, request, cq)); + } + virtual ::grpc::Status Analyze(::grpc::ClientContext* context, const ::xtcp_treatment::v1::AnalyzeRequest& request, ::xtcp_treatment::v1::AnalyzeResponse* response) = 0; + std::unique_ptr< ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::AnalyzeResponse>> AsyncAnalyze(::grpc::ClientContext* context, const ::xtcp_treatment::v1::AnalyzeRequest& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::AnalyzeResponse>>(AsyncAnalyzeRaw(context, request, cq)); + } + std::unique_ptr< ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::AnalyzeResponse>> PrepareAsyncAnalyze(::grpc::ClientContext* context, const ::xtcp_treatment::v1::AnalyzeRequest& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::AnalyzeResponse>>(PrepareAsyncAnalyzeRaw(context, request, cq)); + } + virtual ::grpc::Status Review(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ReviewRequest& request, ::xtcp_treatment::v1::ReviewResponse* response) = 0; + std::unique_ptr< ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::ReviewResponse>> AsyncReview(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ReviewRequest& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::ReviewResponse>>(AsyncReviewRaw(context, request, cq)); + } + std::unique_ptr< ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::ReviewResponse>> PrepareAsyncReview(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ReviewRequest& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::ReviewResponse>>(PrepareAsyncReviewRaw(context, request, cq)); + } + virtual ::grpc::Status Control(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ControlRequest& request, ::xtcp_treatment::v1::ControlResponse* response) = 0; + std::unique_ptr< ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::ControlResponse>> AsyncControl(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ControlRequest& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::ControlResponse>>(AsyncControlRaw(context, request, cq)); + } + std::unique_ptr< ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::ControlResponse>> PrepareAsyncControl(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ControlRequest& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::ControlResponse>>(PrepareAsyncControlRaw(context, request, cq)); + } + virtual ::grpc::Status Inspect(::grpc::ClientContext* context, const ::xtcp_treatment::v1::InspectRequest& request, ::xtcp_treatment::v1::InspectResponse* response) = 0; + std::unique_ptr< ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::InspectResponse>> AsyncInspect(::grpc::ClientContext* context, const ::xtcp_treatment::v1::InspectRequest& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::InspectResponse>>(AsyncInspectRaw(context, request, cq)); + } + std::unique_ptr< ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::InspectResponse>> PrepareAsyncInspect(::grpc::ClientContext* context, const ::xtcp_treatment::v1::InspectRequest& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::InspectResponse>>(PrepareAsyncInspectRaw(context, request, cq)); + } + class async_interface { + public: + virtual ~async_interface() {} + virtual void Propose(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ProposeRequest* request, ::xtcp_treatment::v1::ProposeResponse* response, std::function) = 0; + virtual void Propose(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ProposeRequest* request, ::xtcp_treatment::v1::ProposeResponse* response, ::grpc::ClientUnaryReactor* reactor) = 0; + virtual void Analyze(::grpc::ClientContext* context, const ::xtcp_treatment::v1::AnalyzeRequest* request, ::xtcp_treatment::v1::AnalyzeResponse* response, std::function) = 0; + virtual void Analyze(::grpc::ClientContext* context, const ::xtcp_treatment::v1::AnalyzeRequest* request, ::xtcp_treatment::v1::AnalyzeResponse* response, ::grpc::ClientUnaryReactor* reactor) = 0; + virtual void Review(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ReviewRequest* request, ::xtcp_treatment::v1::ReviewResponse* response, std::function) = 0; + virtual void Review(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ReviewRequest* request, ::xtcp_treatment::v1::ReviewResponse* response, ::grpc::ClientUnaryReactor* reactor) = 0; + virtual void Control(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ControlRequest* request, ::xtcp_treatment::v1::ControlResponse* response, std::function) = 0; + virtual void Control(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ControlRequest* request, ::xtcp_treatment::v1::ControlResponse* response, ::grpc::ClientUnaryReactor* reactor) = 0; + virtual void Inspect(::grpc::ClientContext* context, const ::xtcp_treatment::v1::InspectRequest* request, ::xtcp_treatment::v1::InspectResponse* response, std::function) = 0; + virtual void Inspect(::grpc::ClientContext* context, const ::xtcp_treatment::v1::InspectRequest* request, ::xtcp_treatment::v1::InspectResponse* response, ::grpc::ClientUnaryReactor* reactor) = 0; + }; + typedef class async_interface experimental_async_interface; + virtual class async_interface* async() { return nullptr; } + class async_interface* experimental_async() { return async(); } + private: + virtual ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::ProposeResponse>* AsyncProposeRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ProposeRequest& request, ::grpc::CompletionQueue* cq) = 0; + virtual ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::ProposeResponse>* PrepareAsyncProposeRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ProposeRequest& request, ::grpc::CompletionQueue* cq) = 0; + virtual ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::AnalyzeResponse>* AsyncAnalyzeRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::AnalyzeRequest& request, ::grpc::CompletionQueue* cq) = 0; + virtual ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::AnalyzeResponse>* PrepareAsyncAnalyzeRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::AnalyzeRequest& request, ::grpc::CompletionQueue* cq) = 0; + virtual ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::ReviewResponse>* AsyncReviewRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ReviewRequest& request, ::grpc::CompletionQueue* cq) = 0; + virtual ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::ReviewResponse>* PrepareAsyncReviewRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ReviewRequest& request, ::grpc::CompletionQueue* cq) = 0; + virtual ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::ControlResponse>* AsyncControlRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ControlRequest& request, ::grpc::CompletionQueue* cq) = 0; + virtual ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::ControlResponse>* PrepareAsyncControlRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ControlRequest& request, ::grpc::CompletionQueue* cq) = 0; + virtual ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::InspectResponse>* AsyncInspectRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::InspectRequest& request, ::grpc::CompletionQueue* cq) = 0; + virtual ::grpc::ClientAsyncResponseReaderInterface< ::xtcp_treatment::v1::InspectResponse>* PrepareAsyncInspectRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::InspectRequest& request, ::grpc::CompletionQueue* cq) = 0; + }; + class Stub final : public StubInterface { + public: + Stub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options = ::grpc::StubOptions()); + ::grpc::Status Propose(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ProposeRequest& request, ::xtcp_treatment::v1::ProposeResponse* response) override; + std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::ProposeResponse>> AsyncPropose(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ProposeRequest& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::ProposeResponse>>(AsyncProposeRaw(context, request, cq)); + } + std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::ProposeResponse>> PrepareAsyncPropose(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ProposeRequest& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::ProposeResponse>>(PrepareAsyncProposeRaw(context, request, cq)); + } + ::grpc::Status Analyze(::grpc::ClientContext* context, const ::xtcp_treatment::v1::AnalyzeRequest& request, ::xtcp_treatment::v1::AnalyzeResponse* response) override; + std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::AnalyzeResponse>> AsyncAnalyze(::grpc::ClientContext* context, const ::xtcp_treatment::v1::AnalyzeRequest& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::AnalyzeResponse>>(AsyncAnalyzeRaw(context, request, cq)); + } + std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::AnalyzeResponse>> PrepareAsyncAnalyze(::grpc::ClientContext* context, const ::xtcp_treatment::v1::AnalyzeRequest& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::AnalyzeResponse>>(PrepareAsyncAnalyzeRaw(context, request, cq)); + } + ::grpc::Status Review(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ReviewRequest& request, ::xtcp_treatment::v1::ReviewResponse* response) override; + std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::ReviewResponse>> AsyncReview(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ReviewRequest& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::ReviewResponse>>(AsyncReviewRaw(context, request, cq)); + } + std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::ReviewResponse>> PrepareAsyncReview(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ReviewRequest& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::ReviewResponse>>(PrepareAsyncReviewRaw(context, request, cq)); + } + ::grpc::Status Control(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ControlRequest& request, ::xtcp_treatment::v1::ControlResponse* response) override; + std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::ControlResponse>> AsyncControl(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ControlRequest& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::ControlResponse>>(AsyncControlRaw(context, request, cq)); + } + std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::ControlResponse>> PrepareAsyncControl(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ControlRequest& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::ControlResponse>>(PrepareAsyncControlRaw(context, request, cq)); + } + ::grpc::Status Inspect(::grpc::ClientContext* context, const ::xtcp_treatment::v1::InspectRequest& request, ::xtcp_treatment::v1::InspectResponse* response) override; + std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::InspectResponse>> AsyncInspect(::grpc::ClientContext* context, const ::xtcp_treatment::v1::InspectRequest& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::InspectResponse>>(AsyncInspectRaw(context, request, cq)); + } + std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::InspectResponse>> PrepareAsyncInspect(::grpc::ClientContext* context, const ::xtcp_treatment::v1::InspectRequest& request, ::grpc::CompletionQueue* cq) { + return std::unique_ptr< ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::InspectResponse>>(PrepareAsyncInspectRaw(context, request, cq)); + } + class async final : + public StubInterface::async_interface { + public: + void Propose(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ProposeRequest* request, ::xtcp_treatment::v1::ProposeResponse* response, std::function) override; + void Propose(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ProposeRequest* request, ::xtcp_treatment::v1::ProposeResponse* response, ::grpc::ClientUnaryReactor* reactor) override; + void Analyze(::grpc::ClientContext* context, const ::xtcp_treatment::v1::AnalyzeRequest* request, ::xtcp_treatment::v1::AnalyzeResponse* response, std::function) override; + void Analyze(::grpc::ClientContext* context, const ::xtcp_treatment::v1::AnalyzeRequest* request, ::xtcp_treatment::v1::AnalyzeResponse* response, ::grpc::ClientUnaryReactor* reactor) override; + void Review(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ReviewRequest* request, ::xtcp_treatment::v1::ReviewResponse* response, std::function) override; + void Review(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ReviewRequest* request, ::xtcp_treatment::v1::ReviewResponse* response, ::grpc::ClientUnaryReactor* reactor) override; + void Control(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ControlRequest* request, ::xtcp_treatment::v1::ControlResponse* response, std::function) override; + void Control(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ControlRequest* request, ::xtcp_treatment::v1::ControlResponse* response, ::grpc::ClientUnaryReactor* reactor) override; + void Inspect(::grpc::ClientContext* context, const ::xtcp_treatment::v1::InspectRequest* request, ::xtcp_treatment::v1::InspectResponse* response, std::function) override; + void Inspect(::grpc::ClientContext* context, const ::xtcp_treatment::v1::InspectRequest* request, ::xtcp_treatment::v1::InspectResponse* response, ::grpc::ClientUnaryReactor* reactor) override; + private: + friend class Stub; + explicit async(Stub* stub): stub_(stub) { } + Stub* stub() { return stub_; } + Stub* stub_; + }; + class async* async() override { return &async_stub_; } + + private: + std::shared_ptr< ::grpc::ChannelInterface> channel_; + class async async_stub_{this}; + ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::ProposeResponse>* AsyncProposeRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ProposeRequest& request, ::grpc::CompletionQueue* cq) override; + ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::ProposeResponse>* PrepareAsyncProposeRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ProposeRequest& request, ::grpc::CompletionQueue* cq) override; + ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::AnalyzeResponse>* AsyncAnalyzeRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::AnalyzeRequest& request, ::grpc::CompletionQueue* cq) override; + ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::AnalyzeResponse>* PrepareAsyncAnalyzeRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::AnalyzeRequest& request, ::grpc::CompletionQueue* cq) override; + ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::ReviewResponse>* AsyncReviewRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ReviewRequest& request, ::grpc::CompletionQueue* cq) override; + ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::ReviewResponse>* PrepareAsyncReviewRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ReviewRequest& request, ::grpc::CompletionQueue* cq) override; + ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::ControlResponse>* AsyncControlRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ControlRequest& request, ::grpc::CompletionQueue* cq) override; + ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::ControlResponse>* PrepareAsyncControlRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::ControlRequest& request, ::grpc::CompletionQueue* cq) override; + ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::InspectResponse>* AsyncInspectRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::InspectRequest& request, ::grpc::CompletionQueue* cq) override; + ::grpc::ClientAsyncResponseReader< ::xtcp_treatment::v1::InspectResponse>* PrepareAsyncInspectRaw(::grpc::ClientContext* context, const ::xtcp_treatment::v1::InspectRequest& request, ::grpc::CompletionQueue* cq) override; + const ::grpc::internal::RpcMethod rpcmethod_Propose_; + const ::grpc::internal::RpcMethod rpcmethod_Analyze_; + const ::grpc::internal::RpcMethod rpcmethod_Review_; + const ::grpc::internal::RpcMethod rpcmethod_Control_; + const ::grpc::internal::RpcMethod rpcmethod_Inspect_; + }; + static std::unique_ptr NewStub(const std::shared_ptr< ::grpc::ChannelInterface>& channel, const ::grpc::StubOptions& options = ::grpc::StubOptions()); + + class Service : public ::grpc::Service { + public: + Service(); + virtual ~Service(); + virtual ::grpc::Status Propose(::grpc::ServerContext* context, const ::xtcp_treatment::v1::ProposeRequest* request, ::xtcp_treatment::v1::ProposeResponse* response); + virtual ::grpc::Status Analyze(::grpc::ServerContext* context, const ::xtcp_treatment::v1::AnalyzeRequest* request, ::xtcp_treatment::v1::AnalyzeResponse* response); + virtual ::grpc::Status Review(::grpc::ServerContext* context, const ::xtcp_treatment::v1::ReviewRequest* request, ::xtcp_treatment::v1::ReviewResponse* response); + virtual ::grpc::Status Control(::grpc::ServerContext* context, const ::xtcp_treatment::v1::ControlRequest* request, ::xtcp_treatment::v1::ControlResponse* response); + virtual ::grpc::Status Inspect(::grpc::ServerContext* context, const ::xtcp_treatment::v1::InspectRequest* request, ::xtcp_treatment::v1::InspectResponse* response); + }; + template + class WithAsyncMethod_Propose : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithAsyncMethod_Propose() { + ::grpc::Service::MarkMethodAsync(0); + } + ~WithAsyncMethod_Propose() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status Propose(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::ProposeRequest* /*request*/, ::xtcp_treatment::v1::ProposeResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + void RequestPropose(::grpc::ServerContext* context, ::xtcp_treatment::v1::ProposeRequest* request, ::grpc::ServerAsyncResponseWriter< ::xtcp_treatment::v1::ProposeResponse>* response, ::grpc::CompletionQueue* new_call_cq, ::grpc::ServerCompletionQueue* notification_cq, void *tag) { + ::grpc::Service::RequestAsyncUnary(0, context, request, response, new_call_cq, notification_cq, tag); + } + }; + template + class WithAsyncMethod_Analyze : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithAsyncMethod_Analyze() { + ::grpc::Service::MarkMethodAsync(1); + } + ~WithAsyncMethod_Analyze() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status Analyze(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::AnalyzeRequest* /*request*/, ::xtcp_treatment::v1::AnalyzeResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + void RequestAnalyze(::grpc::ServerContext* context, ::xtcp_treatment::v1::AnalyzeRequest* request, ::grpc::ServerAsyncResponseWriter< ::xtcp_treatment::v1::AnalyzeResponse>* response, ::grpc::CompletionQueue* new_call_cq, ::grpc::ServerCompletionQueue* notification_cq, void *tag) { + ::grpc::Service::RequestAsyncUnary(1, context, request, response, new_call_cq, notification_cq, tag); + } + }; + template + class WithAsyncMethod_Review : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithAsyncMethod_Review() { + ::grpc::Service::MarkMethodAsync(2); + } + ~WithAsyncMethod_Review() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status Review(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::ReviewRequest* /*request*/, ::xtcp_treatment::v1::ReviewResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + void RequestReview(::grpc::ServerContext* context, ::xtcp_treatment::v1::ReviewRequest* request, ::grpc::ServerAsyncResponseWriter< ::xtcp_treatment::v1::ReviewResponse>* response, ::grpc::CompletionQueue* new_call_cq, ::grpc::ServerCompletionQueue* notification_cq, void *tag) { + ::grpc::Service::RequestAsyncUnary(2, context, request, response, new_call_cq, notification_cq, tag); + } + }; + template + class WithAsyncMethod_Control : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithAsyncMethod_Control() { + ::grpc::Service::MarkMethodAsync(3); + } + ~WithAsyncMethod_Control() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status Control(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::ControlRequest* /*request*/, ::xtcp_treatment::v1::ControlResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + void RequestControl(::grpc::ServerContext* context, ::xtcp_treatment::v1::ControlRequest* request, ::grpc::ServerAsyncResponseWriter< ::xtcp_treatment::v1::ControlResponse>* response, ::grpc::CompletionQueue* new_call_cq, ::grpc::ServerCompletionQueue* notification_cq, void *tag) { + ::grpc::Service::RequestAsyncUnary(3, context, request, response, new_call_cq, notification_cq, tag); + } + }; + template + class WithAsyncMethod_Inspect : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithAsyncMethod_Inspect() { + ::grpc::Service::MarkMethodAsync(4); + } + ~WithAsyncMethod_Inspect() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status Inspect(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::InspectRequest* /*request*/, ::xtcp_treatment::v1::InspectResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + void RequestInspect(::grpc::ServerContext* context, ::xtcp_treatment::v1::InspectRequest* request, ::grpc::ServerAsyncResponseWriter< ::xtcp_treatment::v1::InspectResponse>* response, ::grpc::CompletionQueue* new_call_cq, ::grpc::ServerCompletionQueue* notification_cq, void *tag) { + ::grpc::Service::RequestAsyncUnary(4, context, request, response, new_call_cq, notification_cq, tag); + } + }; + typedef WithAsyncMethod_Propose > > > > AsyncService; + template + class WithCallbackMethod_Propose : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithCallbackMethod_Propose() { + ::grpc::Service::MarkMethodCallback(0, + new ::grpc::internal::CallbackUnaryHandler< ::xtcp_treatment::v1::ProposeRequest, ::xtcp_treatment::v1::ProposeResponse>( + [this]( + ::grpc::CallbackServerContext* context, const ::xtcp_treatment::v1::ProposeRequest* request, ::xtcp_treatment::v1::ProposeResponse* response) { return this->Propose(context, request, response); }));} + void SetMessageAllocatorFor_Propose( + ::grpc::MessageAllocator< ::xtcp_treatment::v1::ProposeRequest, ::xtcp_treatment::v1::ProposeResponse>* allocator) { + ::grpc::internal::MethodHandler* const handler = ::grpc::Service::GetHandler(0); + static_cast<::grpc::internal::CallbackUnaryHandler< ::xtcp_treatment::v1::ProposeRequest, ::xtcp_treatment::v1::ProposeResponse>*>(handler) + ->SetMessageAllocator(allocator); + } + ~WithCallbackMethod_Propose() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status Propose(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::ProposeRequest* /*request*/, ::xtcp_treatment::v1::ProposeResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + virtual ::grpc::ServerUnaryReactor* Propose( + ::grpc::CallbackServerContext* /*context*/, const ::xtcp_treatment::v1::ProposeRequest* /*request*/, ::xtcp_treatment::v1::ProposeResponse* /*response*/) { return nullptr; } + }; + template + class WithCallbackMethod_Analyze : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithCallbackMethod_Analyze() { + ::grpc::Service::MarkMethodCallback(1, + new ::grpc::internal::CallbackUnaryHandler< ::xtcp_treatment::v1::AnalyzeRequest, ::xtcp_treatment::v1::AnalyzeResponse>( + [this]( + ::grpc::CallbackServerContext* context, const ::xtcp_treatment::v1::AnalyzeRequest* request, ::xtcp_treatment::v1::AnalyzeResponse* response) { return this->Analyze(context, request, response); }));} + void SetMessageAllocatorFor_Analyze( + ::grpc::MessageAllocator< ::xtcp_treatment::v1::AnalyzeRequest, ::xtcp_treatment::v1::AnalyzeResponse>* allocator) { + ::grpc::internal::MethodHandler* const handler = ::grpc::Service::GetHandler(1); + static_cast<::grpc::internal::CallbackUnaryHandler< ::xtcp_treatment::v1::AnalyzeRequest, ::xtcp_treatment::v1::AnalyzeResponse>*>(handler) + ->SetMessageAllocator(allocator); + } + ~WithCallbackMethod_Analyze() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status Analyze(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::AnalyzeRequest* /*request*/, ::xtcp_treatment::v1::AnalyzeResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + virtual ::grpc::ServerUnaryReactor* Analyze( + ::grpc::CallbackServerContext* /*context*/, const ::xtcp_treatment::v1::AnalyzeRequest* /*request*/, ::xtcp_treatment::v1::AnalyzeResponse* /*response*/) { return nullptr; } + }; + template + class WithCallbackMethod_Review : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithCallbackMethod_Review() { + ::grpc::Service::MarkMethodCallback(2, + new ::grpc::internal::CallbackUnaryHandler< ::xtcp_treatment::v1::ReviewRequest, ::xtcp_treatment::v1::ReviewResponse>( + [this]( + ::grpc::CallbackServerContext* context, const ::xtcp_treatment::v1::ReviewRequest* request, ::xtcp_treatment::v1::ReviewResponse* response) { return this->Review(context, request, response); }));} + void SetMessageAllocatorFor_Review( + ::grpc::MessageAllocator< ::xtcp_treatment::v1::ReviewRequest, ::xtcp_treatment::v1::ReviewResponse>* allocator) { + ::grpc::internal::MethodHandler* const handler = ::grpc::Service::GetHandler(2); + static_cast<::grpc::internal::CallbackUnaryHandler< ::xtcp_treatment::v1::ReviewRequest, ::xtcp_treatment::v1::ReviewResponse>*>(handler) + ->SetMessageAllocator(allocator); + } + ~WithCallbackMethod_Review() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status Review(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::ReviewRequest* /*request*/, ::xtcp_treatment::v1::ReviewResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + virtual ::grpc::ServerUnaryReactor* Review( + ::grpc::CallbackServerContext* /*context*/, const ::xtcp_treatment::v1::ReviewRequest* /*request*/, ::xtcp_treatment::v1::ReviewResponse* /*response*/) { return nullptr; } + }; + template + class WithCallbackMethod_Control : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithCallbackMethod_Control() { + ::grpc::Service::MarkMethodCallback(3, + new ::grpc::internal::CallbackUnaryHandler< ::xtcp_treatment::v1::ControlRequest, ::xtcp_treatment::v1::ControlResponse>( + [this]( + ::grpc::CallbackServerContext* context, const ::xtcp_treatment::v1::ControlRequest* request, ::xtcp_treatment::v1::ControlResponse* response) { return this->Control(context, request, response); }));} + void SetMessageAllocatorFor_Control( + ::grpc::MessageAllocator< ::xtcp_treatment::v1::ControlRequest, ::xtcp_treatment::v1::ControlResponse>* allocator) { + ::grpc::internal::MethodHandler* const handler = ::grpc::Service::GetHandler(3); + static_cast<::grpc::internal::CallbackUnaryHandler< ::xtcp_treatment::v1::ControlRequest, ::xtcp_treatment::v1::ControlResponse>*>(handler) + ->SetMessageAllocator(allocator); + } + ~WithCallbackMethod_Control() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status Control(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::ControlRequest* /*request*/, ::xtcp_treatment::v1::ControlResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + virtual ::grpc::ServerUnaryReactor* Control( + ::grpc::CallbackServerContext* /*context*/, const ::xtcp_treatment::v1::ControlRequest* /*request*/, ::xtcp_treatment::v1::ControlResponse* /*response*/) { return nullptr; } + }; + template + class WithCallbackMethod_Inspect : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithCallbackMethod_Inspect() { + ::grpc::Service::MarkMethodCallback(4, + new ::grpc::internal::CallbackUnaryHandler< ::xtcp_treatment::v1::InspectRequest, ::xtcp_treatment::v1::InspectResponse>( + [this]( + ::grpc::CallbackServerContext* context, const ::xtcp_treatment::v1::InspectRequest* request, ::xtcp_treatment::v1::InspectResponse* response) { return this->Inspect(context, request, response); }));} + void SetMessageAllocatorFor_Inspect( + ::grpc::MessageAllocator< ::xtcp_treatment::v1::InspectRequest, ::xtcp_treatment::v1::InspectResponse>* allocator) { + ::grpc::internal::MethodHandler* const handler = ::grpc::Service::GetHandler(4); + static_cast<::grpc::internal::CallbackUnaryHandler< ::xtcp_treatment::v1::InspectRequest, ::xtcp_treatment::v1::InspectResponse>*>(handler) + ->SetMessageAllocator(allocator); + } + ~WithCallbackMethod_Inspect() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status Inspect(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::InspectRequest* /*request*/, ::xtcp_treatment::v1::InspectResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + virtual ::grpc::ServerUnaryReactor* Inspect( + ::grpc::CallbackServerContext* /*context*/, const ::xtcp_treatment::v1::InspectRequest* /*request*/, ::xtcp_treatment::v1::InspectResponse* /*response*/) { return nullptr; } + }; + typedef WithCallbackMethod_Propose > > > > CallbackService; + typedef CallbackService ExperimentalCallbackService; + template + class WithGenericMethod_Propose : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithGenericMethod_Propose() { + ::grpc::Service::MarkMethodGeneric(0); + } + ~WithGenericMethod_Propose() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status Propose(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::ProposeRequest* /*request*/, ::xtcp_treatment::v1::ProposeResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + }; + template + class WithGenericMethod_Analyze : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithGenericMethod_Analyze() { + ::grpc::Service::MarkMethodGeneric(1); + } + ~WithGenericMethod_Analyze() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status Analyze(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::AnalyzeRequest* /*request*/, ::xtcp_treatment::v1::AnalyzeResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + }; + template + class WithGenericMethod_Review : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithGenericMethod_Review() { + ::grpc::Service::MarkMethodGeneric(2); + } + ~WithGenericMethod_Review() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status Review(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::ReviewRequest* /*request*/, ::xtcp_treatment::v1::ReviewResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + }; + template + class WithGenericMethod_Control : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithGenericMethod_Control() { + ::grpc::Service::MarkMethodGeneric(3); + } + ~WithGenericMethod_Control() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status Control(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::ControlRequest* /*request*/, ::xtcp_treatment::v1::ControlResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + }; + template + class WithGenericMethod_Inspect : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithGenericMethod_Inspect() { + ::grpc::Service::MarkMethodGeneric(4); + } + ~WithGenericMethod_Inspect() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status Inspect(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::InspectRequest* /*request*/, ::xtcp_treatment::v1::InspectResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + }; + template + class WithRawMethod_Propose : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithRawMethod_Propose() { + ::grpc::Service::MarkMethodRaw(0); + } + ~WithRawMethod_Propose() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status Propose(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::ProposeRequest* /*request*/, ::xtcp_treatment::v1::ProposeResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + void RequestPropose(::grpc::ServerContext* context, ::grpc::ByteBuffer* request, ::grpc::ServerAsyncResponseWriter< ::grpc::ByteBuffer>* response, ::grpc::CompletionQueue* new_call_cq, ::grpc::ServerCompletionQueue* notification_cq, void *tag) { + ::grpc::Service::RequestAsyncUnary(0, context, request, response, new_call_cq, notification_cq, tag); + } + }; + template + class WithRawMethod_Analyze : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithRawMethod_Analyze() { + ::grpc::Service::MarkMethodRaw(1); + } + ~WithRawMethod_Analyze() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status Analyze(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::AnalyzeRequest* /*request*/, ::xtcp_treatment::v1::AnalyzeResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + void RequestAnalyze(::grpc::ServerContext* context, ::grpc::ByteBuffer* request, ::grpc::ServerAsyncResponseWriter< ::grpc::ByteBuffer>* response, ::grpc::CompletionQueue* new_call_cq, ::grpc::ServerCompletionQueue* notification_cq, void *tag) { + ::grpc::Service::RequestAsyncUnary(1, context, request, response, new_call_cq, notification_cq, tag); + } + }; + template + class WithRawMethod_Review : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithRawMethod_Review() { + ::grpc::Service::MarkMethodRaw(2); + } + ~WithRawMethod_Review() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status Review(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::ReviewRequest* /*request*/, ::xtcp_treatment::v1::ReviewResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + void RequestReview(::grpc::ServerContext* context, ::grpc::ByteBuffer* request, ::grpc::ServerAsyncResponseWriter< ::grpc::ByteBuffer>* response, ::grpc::CompletionQueue* new_call_cq, ::grpc::ServerCompletionQueue* notification_cq, void *tag) { + ::grpc::Service::RequestAsyncUnary(2, context, request, response, new_call_cq, notification_cq, tag); + } + }; + template + class WithRawMethod_Control : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithRawMethod_Control() { + ::grpc::Service::MarkMethodRaw(3); + } + ~WithRawMethod_Control() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status Control(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::ControlRequest* /*request*/, ::xtcp_treatment::v1::ControlResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + void RequestControl(::grpc::ServerContext* context, ::grpc::ByteBuffer* request, ::grpc::ServerAsyncResponseWriter< ::grpc::ByteBuffer>* response, ::grpc::CompletionQueue* new_call_cq, ::grpc::ServerCompletionQueue* notification_cq, void *tag) { + ::grpc::Service::RequestAsyncUnary(3, context, request, response, new_call_cq, notification_cq, tag); + } + }; + template + class WithRawMethod_Inspect : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithRawMethod_Inspect() { + ::grpc::Service::MarkMethodRaw(4); + } + ~WithRawMethod_Inspect() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status Inspect(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::InspectRequest* /*request*/, ::xtcp_treatment::v1::InspectResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + void RequestInspect(::grpc::ServerContext* context, ::grpc::ByteBuffer* request, ::grpc::ServerAsyncResponseWriter< ::grpc::ByteBuffer>* response, ::grpc::CompletionQueue* new_call_cq, ::grpc::ServerCompletionQueue* notification_cq, void *tag) { + ::grpc::Service::RequestAsyncUnary(4, context, request, response, new_call_cq, notification_cq, tag); + } + }; + template + class WithRawCallbackMethod_Propose : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithRawCallbackMethod_Propose() { + ::grpc::Service::MarkMethodRawCallback(0, + new ::grpc::internal::CallbackUnaryHandler< ::grpc::ByteBuffer, ::grpc::ByteBuffer>( + [this]( + ::grpc::CallbackServerContext* context, const ::grpc::ByteBuffer* request, ::grpc::ByteBuffer* response) { return this->Propose(context, request, response); })); + } + ~WithRawCallbackMethod_Propose() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status Propose(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::ProposeRequest* /*request*/, ::xtcp_treatment::v1::ProposeResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + virtual ::grpc::ServerUnaryReactor* Propose( + ::grpc::CallbackServerContext* /*context*/, const ::grpc::ByteBuffer* /*request*/, ::grpc::ByteBuffer* /*response*/) { return nullptr; } + }; + template + class WithRawCallbackMethod_Analyze : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithRawCallbackMethod_Analyze() { + ::grpc::Service::MarkMethodRawCallback(1, + new ::grpc::internal::CallbackUnaryHandler< ::grpc::ByteBuffer, ::grpc::ByteBuffer>( + [this]( + ::grpc::CallbackServerContext* context, const ::grpc::ByteBuffer* request, ::grpc::ByteBuffer* response) { return this->Analyze(context, request, response); })); + } + ~WithRawCallbackMethod_Analyze() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status Analyze(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::AnalyzeRequest* /*request*/, ::xtcp_treatment::v1::AnalyzeResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + virtual ::grpc::ServerUnaryReactor* Analyze( + ::grpc::CallbackServerContext* /*context*/, const ::grpc::ByteBuffer* /*request*/, ::grpc::ByteBuffer* /*response*/) { return nullptr; } + }; + template + class WithRawCallbackMethod_Review : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithRawCallbackMethod_Review() { + ::grpc::Service::MarkMethodRawCallback(2, + new ::grpc::internal::CallbackUnaryHandler< ::grpc::ByteBuffer, ::grpc::ByteBuffer>( + [this]( + ::grpc::CallbackServerContext* context, const ::grpc::ByteBuffer* request, ::grpc::ByteBuffer* response) { return this->Review(context, request, response); })); + } + ~WithRawCallbackMethod_Review() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status Review(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::ReviewRequest* /*request*/, ::xtcp_treatment::v1::ReviewResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + virtual ::grpc::ServerUnaryReactor* Review( + ::grpc::CallbackServerContext* /*context*/, const ::grpc::ByteBuffer* /*request*/, ::grpc::ByteBuffer* /*response*/) { return nullptr; } + }; + template + class WithRawCallbackMethod_Control : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithRawCallbackMethod_Control() { + ::grpc::Service::MarkMethodRawCallback(3, + new ::grpc::internal::CallbackUnaryHandler< ::grpc::ByteBuffer, ::grpc::ByteBuffer>( + [this]( + ::grpc::CallbackServerContext* context, const ::grpc::ByteBuffer* request, ::grpc::ByteBuffer* response) { return this->Control(context, request, response); })); + } + ~WithRawCallbackMethod_Control() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status Control(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::ControlRequest* /*request*/, ::xtcp_treatment::v1::ControlResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + virtual ::grpc::ServerUnaryReactor* Control( + ::grpc::CallbackServerContext* /*context*/, const ::grpc::ByteBuffer* /*request*/, ::grpc::ByteBuffer* /*response*/) { return nullptr; } + }; + template + class WithRawCallbackMethod_Inspect : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithRawCallbackMethod_Inspect() { + ::grpc::Service::MarkMethodRawCallback(4, + new ::grpc::internal::CallbackUnaryHandler< ::grpc::ByteBuffer, ::grpc::ByteBuffer>( + [this]( + ::grpc::CallbackServerContext* context, const ::grpc::ByteBuffer* request, ::grpc::ByteBuffer* response) { return this->Inspect(context, request, response); })); + } + ~WithRawCallbackMethod_Inspect() override { + BaseClassMustBeDerivedFromService(this); + } + // disable synchronous version of this method + ::grpc::Status Inspect(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::InspectRequest* /*request*/, ::xtcp_treatment::v1::InspectResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + virtual ::grpc::ServerUnaryReactor* Inspect( + ::grpc::CallbackServerContext* /*context*/, const ::grpc::ByteBuffer* /*request*/, ::grpc::ByteBuffer* /*response*/) { return nullptr; } + }; + template + class WithStreamedUnaryMethod_Propose : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithStreamedUnaryMethod_Propose() { + ::grpc::Service::MarkMethodStreamed(0, + new ::grpc::internal::StreamedUnaryHandler< + ::xtcp_treatment::v1::ProposeRequest, ::xtcp_treatment::v1::ProposeResponse>( + [this](::grpc::ServerContext* context, + ::grpc::ServerUnaryStreamer< + ::xtcp_treatment::v1::ProposeRequest, ::xtcp_treatment::v1::ProposeResponse>* streamer) { + return this->StreamedPropose(context, + streamer); + })); + } + ~WithStreamedUnaryMethod_Propose() override { + BaseClassMustBeDerivedFromService(this); + } + // disable regular version of this method + ::grpc::Status Propose(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::ProposeRequest* /*request*/, ::xtcp_treatment::v1::ProposeResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + // replace default version of method with streamed unary + virtual ::grpc::Status StreamedPropose(::grpc::ServerContext* context, ::grpc::ServerUnaryStreamer< ::xtcp_treatment::v1::ProposeRequest,::xtcp_treatment::v1::ProposeResponse>* server_unary_streamer) = 0; + }; + template + class WithStreamedUnaryMethod_Analyze : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithStreamedUnaryMethod_Analyze() { + ::grpc::Service::MarkMethodStreamed(1, + new ::grpc::internal::StreamedUnaryHandler< + ::xtcp_treatment::v1::AnalyzeRequest, ::xtcp_treatment::v1::AnalyzeResponse>( + [this](::grpc::ServerContext* context, + ::grpc::ServerUnaryStreamer< + ::xtcp_treatment::v1::AnalyzeRequest, ::xtcp_treatment::v1::AnalyzeResponse>* streamer) { + return this->StreamedAnalyze(context, + streamer); + })); + } + ~WithStreamedUnaryMethod_Analyze() override { + BaseClassMustBeDerivedFromService(this); + } + // disable regular version of this method + ::grpc::Status Analyze(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::AnalyzeRequest* /*request*/, ::xtcp_treatment::v1::AnalyzeResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + // replace default version of method with streamed unary + virtual ::grpc::Status StreamedAnalyze(::grpc::ServerContext* context, ::grpc::ServerUnaryStreamer< ::xtcp_treatment::v1::AnalyzeRequest,::xtcp_treatment::v1::AnalyzeResponse>* server_unary_streamer) = 0; + }; + template + class WithStreamedUnaryMethod_Review : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithStreamedUnaryMethod_Review() { + ::grpc::Service::MarkMethodStreamed(2, + new ::grpc::internal::StreamedUnaryHandler< + ::xtcp_treatment::v1::ReviewRequest, ::xtcp_treatment::v1::ReviewResponse>( + [this](::grpc::ServerContext* context, + ::grpc::ServerUnaryStreamer< + ::xtcp_treatment::v1::ReviewRequest, ::xtcp_treatment::v1::ReviewResponse>* streamer) { + return this->StreamedReview(context, + streamer); + })); + } + ~WithStreamedUnaryMethod_Review() override { + BaseClassMustBeDerivedFromService(this); + } + // disable regular version of this method + ::grpc::Status Review(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::ReviewRequest* /*request*/, ::xtcp_treatment::v1::ReviewResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + // replace default version of method with streamed unary + virtual ::grpc::Status StreamedReview(::grpc::ServerContext* context, ::grpc::ServerUnaryStreamer< ::xtcp_treatment::v1::ReviewRequest,::xtcp_treatment::v1::ReviewResponse>* server_unary_streamer) = 0; + }; + template + class WithStreamedUnaryMethod_Control : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithStreamedUnaryMethod_Control() { + ::grpc::Service::MarkMethodStreamed(3, + new ::grpc::internal::StreamedUnaryHandler< + ::xtcp_treatment::v1::ControlRequest, ::xtcp_treatment::v1::ControlResponse>( + [this](::grpc::ServerContext* context, + ::grpc::ServerUnaryStreamer< + ::xtcp_treatment::v1::ControlRequest, ::xtcp_treatment::v1::ControlResponse>* streamer) { + return this->StreamedControl(context, + streamer); + })); + } + ~WithStreamedUnaryMethod_Control() override { + BaseClassMustBeDerivedFromService(this); + } + // disable regular version of this method + ::grpc::Status Control(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::ControlRequest* /*request*/, ::xtcp_treatment::v1::ControlResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + // replace default version of method with streamed unary + virtual ::grpc::Status StreamedControl(::grpc::ServerContext* context, ::grpc::ServerUnaryStreamer< ::xtcp_treatment::v1::ControlRequest,::xtcp_treatment::v1::ControlResponse>* server_unary_streamer) = 0; + }; + template + class WithStreamedUnaryMethod_Inspect : public BaseClass { + private: + void BaseClassMustBeDerivedFromService(const Service* /*service*/) {} + public: + WithStreamedUnaryMethod_Inspect() { + ::grpc::Service::MarkMethodStreamed(4, + new ::grpc::internal::StreamedUnaryHandler< + ::xtcp_treatment::v1::InspectRequest, ::xtcp_treatment::v1::InspectResponse>( + [this](::grpc::ServerContext* context, + ::grpc::ServerUnaryStreamer< + ::xtcp_treatment::v1::InspectRequest, ::xtcp_treatment::v1::InspectResponse>* streamer) { + return this->StreamedInspect(context, + streamer); + })); + } + ~WithStreamedUnaryMethod_Inspect() override { + BaseClassMustBeDerivedFromService(this); + } + // disable regular version of this method + ::grpc::Status Inspect(::grpc::ServerContext* /*context*/, const ::xtcp_treatment::v1::InspectRequest* /*request*/, ::xtcp_treatment::v1::InspectResponse* /*response*/) override { + abort(); + return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, ""); + } + // replace default version of method with streamed unary + virtual ::grpc::Status StreamedInspect(::grpc::ServerContext* context, ::grpc::ServerUnaryStreamer< ::xtcp_treatment::v1::InspectRequest,::xtcp_treatment::v1::InspectResponse>* server_unary_streamer) = 0; + }; + typedef WithStreamedUnaryMethod_Propose > > > > StreamedUnaryService; + typedef Service SplitStreamedService; + typedef WithStreamedUnaryMethod_Propose > > > > StreamedService; +}; + +} // namespace v1 +} // namespace xtcp_treatment + + +#include +#endif // GRPC_xtcp_5ftreatment_2fv1_2foperator_2eproto__INCLUDED diff --git a/gen/cpp/xtcp_treatment/v1/operator.pb.cc b/gen/cpp/xtcp_treatment/v1/operator.pb.cc new file mode 100644 index 000000000..608f20fd8 --- /dev/null +++ b/gen/cpp/xtcp_treatment/v1/operator.pb.cc @@ -0,0 +1,4651 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE +// source: xtcp_treatment/v1/operator.proto +// Protobuf C++ Version: 7.36.2 + +#include "xtcp_treatment/v1/operator.pb.h" + +#include +#include +#include "google/protobuf/io/coded_stream.h" +#include "google/protobuf/generated_message_tctable_impl.h" +#include "google/protobuf/internal_visibility.h" +#include "google/protobuf/extension_set.h" +#include "google/protobuf/generated_message_util.h" +#include "google/protobuf/wire_format_lite.h" +#include "google/protobuf/descriptor.h" +#include "google/protobuf/generated_message_reflection.h" +#include "google/protobuf/reflection_ops.h" +#include "google/protobuf/wire_format.h" +// @@protoc_insertion_point(includes) + +// Must be included last. +#include "google/protobuf/port_def.inc" +PROTOBUF_PRAGMA_INIT_SEG +namespace _pb = ::google::protobuf; +namespace _pbi = ::google::protobuf::internal; +namespace _fl = ::google::protobuf::internal::field_layout; +#ifdef PROTOBUF_MESSAGE_GLOBALS +namespace { +PROTOBUF_CONSTINIT ::google::protobuf::internal::ReflectionData + file_reflection_data[] = { + // ::xtcp_treatment::v1::ProposeRequest + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2eproto, /* tracker*/ nullptr,}, + // ::xtcp_treatment::v1::ProposeResponse + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2eproto, /* tracker*/ nullptr,}, + // ::xtcp_treatment::v1::AnalyzeRequest + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2eproto, /* tracker*/ nullptr,}, + // ::xtcp_treatment::v1::AnalyzeResponse + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2eproto, /* tracker*/ nullptr,}, + // ::xtcp_treatment::v1::ReviewRequest + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2eproto, /* tracker*/ nullptr,}, + // ::xtcp_treatment::v1::ReviewResponse + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2eproto, /* tracker*/ nullptr,}, + // ::xtcp_treatment::v1::ControlRequest + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2eproto, /* tracker*/ nullptr,}, + // ::xtcp_treatment::v1::ControlResponse + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2eproto, /* tracker*/ nullptr,}, + // ::xtcp_treatment::v1::InspectRequest + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2eproto, /* tracker*/ nullptr,}, + // ::xtcp_treatment::v1::InspectResponse + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2eproto, /* tracker*/ nullptr,}, +}; +} // namespace +#endif +namespace xtcp_treatment { +namespace v1 { +class InspectRequest::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(InspectRequest, _impl_._has_bits_); +}; + +constexpr InspectRequest::ParseTableT_ InspectRequest::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(InspectRequest, _impl_._has_bits_), + 0, // no _extensions_ + 2, 8, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294967292, // skipmap + offsetof(ParseTableT_, field_entries), + 2, // num_field_entries + 0, // num_aux_entries + offsetof(ParseTableT_, field_names), // no aux_entries + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment::v1::InspectRequest>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + // string agent_id = 2 [json_name = "agentId", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {18, 1, 0, + PROTOBUF_FIELD_OFFSET(InspectRequest, _impl_.agent_id_)}}, + // string experiment_id = 1 [json_name = "experimentId", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {10, 0, 0, + PROTOBUF_FIELD_OFFSET(InspectRequest, _impl_.experiment_id_)}}, + }}, {{ + 65535, 65535 + }}, {{ + // string experiment_id = 1 [json_name = "experimentId", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(InspectRequest, _impl_.experiment_id_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // string agent_id = 2 [json_name = "agentId", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(InspectRequest, _impl_.agent_id_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + }}, + // no aux_entries + {{ + "\40\15\10\0\0\0\0\0" + "xtcp_treatment.v1.InspectRequest" + "experiment_id" + "agent_id" + }}, + }; +} + + +inline constexpr InspectRequest::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : experiment_id_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + agent_id_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()) {} + +template +constexpr InspectRequest::InspectRequest(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL InspectRequest::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) InspectRequest(arena); +} +constexpr auto InspectRequest::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::CopyInit(sizeof(InspectRequest), alignof(InspectRequest)); +} +constexpr auto InspectRequest::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &InspectRequest::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &InspectRequest::SharedDtor, + Super_::GetClearImpl(), &InspectRequest::ByteSizeLong, + &InspectRequest::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(InspectRequest, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[8], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct InspectRequestGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr InspectRequestGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + InspectRequest_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(InspectRequest::InternalGenerateClassData_( + _default, &InspectRequest_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~InspectRequestGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) InspectRequest _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(InspectRequestGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST InspectRequestGlobalsTypeInternal InspectRequest_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* InspectRequest_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return InspectRequest_globals_.GetClassData(); +#else + return InspectRequest_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +class ControlResponse::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(ControlResponse, _impl_._has_bits_); +}; + +constexpr ControlResponse::ParseTableT_ ControlResponse::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(ControlResponse, _impl_._has_bits_), + 0, // no _extensions_ + 1, 0, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294967294, // skipmap + offsetof(ParseTableT_, field_entries), + 1, // num_field_entries + 0, // num_aux_entries + offsetof(ParseTableT_, field_names), // no aux_entries + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment::v1::ControlResponse>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + // uint32 affected_agents = 1 [json_name = "affectedAgents"]; + {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(ControlResponse, _impl_.affected_agents_), 0>(), + {8, 0, 0, + PROTOBUF_FIELD_OFFSET(ControlResponse, _impl_.affected_agents_)}}, + }}, {{ + 65535, 65535 + }}, {{ + // uint32 affected_agents = 1 [json_name = "affectedAgents"]; + {PROTOBUF_FIELD_OFFSET(ControlResponse, _impl_.affected_agents_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt32)}, + }}, + // no aux_entries + {{ + }}, + }; +} + + +inline constexpr ControlResponse::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : affected_agents_{0u} {} + +template +constexpr ControlResponse::ControlResponse(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL ControlResponse::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) ControlResponse(arena); +} +constexpr auto ControlResponse::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::ZeroInit(sizeof(ControlResponse), alignof(ControlResponse)); +} +constexpr auto ControlResponse::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &ControlResponse::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &ControlResponse::SharedDtor, + Super_::GetClearImpl(), &ControlResponse::ByteSizeLong, + &ControlResponse::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(ControlResponse, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[7], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct ControlResponseGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr ControlResponseGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + ControlResponse_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(ControlResponse::InternalGenerateClassData_( + _default, &ControlResponse_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~ControlResponseGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) ControlResponse _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(ControlResponseGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST ControlResponseGlobalsTypeInternal ControlResponse_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* ControlResponse_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return ControlResponse_globals_.GetClassData(); +#else + return ControlResponse_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +class ControlRequest::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(ControlRequest, _impl_._has_bits_); +}; + +constexpr ControlRequest::ParseTableT_ ControlRequest::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(ControlRequest, _impl_._has_bits_), + 0, // no _extensions_ + 7, 56, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294967168, // skipmap + offsetof(ParseTableT_, field_entries), + 7, // num_field_entries + 0, // num_aux_entries + offsetof(ParseTableT_, field_names), // no aux_entries + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment::v1::ControlRequest>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + {::_pbi::TcParser::MiniParse, {}}, + // string experiment_id = 1 [json_name = "experimentId", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {10, 0, 0, + PROTOBUF_FIELD_OFFSET(ControlRequest, _impl_.experiment_id_)}}, + // string agent_id = 2 [json_name = "agentId", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {18, 1, 0, + PROTOBUF_FIELD_OFFSET(ControlRequest, _impl_.agent_id_)}}, + // string location = 3 [json_name = "location", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {26, 2, 0, + PROTOBUF_FIELD_OFFSET(ControlRequest, _impl_.location_)}}, + // bool fleet = 4 [json_name = "fleet"]; + {::_pbi::TcParser::SingularVarintNoZag1(), + {32, 5, 0, + PROTOBUF_FIELD_OFFSET(ControlRequest, _impl_.fleet_)}}, + // .xtcp_treatment.v1.ControlAction action = 5 [json_name = "action", (.buf.validate.field) = { + {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(ControlRequest, _impl_.action_), 6>(), + {40, 6, 0, + PROTOBUF_FIELD_OFFSET(ControlRequest, _impl_.action_)}}, + // string reason = 6 [json_name = "reason", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {50, 3, 0, + PROTOBUF_FIELD_OFFSET(ControlRequest, _impl_.reason_)}}, + // string request_id = 7 [json_name = "requestId", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {58, 4, 0, + PROTOBUF_FIELD_OFFSET(ControlRequest, _impl_.request_id_)}}, + }}, {{ + 65535, 65535 + }}, {{ + // string experiment_id = 1 [json_name = "experimentId", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(ControlRequest, _impl_.experiment_id_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // string agent_id = 2 [json_name = "agentId", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(ControlRequest, _impl_.agent_id_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // string location = 3 [json_name = "location", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(ControlRequest, _impl_.location_), _Internal::kHasBitsOffset + 2, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // bool fleet = 4 [json_name = "fleet"]; + {PROTOBUF_FIELD_OFFSET(ControlRequest, _impl_.fleet_), _Internal::kHasBitsOffset + 5, 0, (0 | ::_fl::kFcOptional | ::_fl::kBool)}, + // .xtcp_treatment.v1.ControlAction action = 5 [json_name = "action", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(ControlRequest, _impl_.action_), _Internal::kHasBitsOffset + 6, 0, (0 | ::_fl::kFcOptional | ::_fl::kOpenEnum)}, + // string reason = 6 [json_name = "reason", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(ControlRequest, _impl_.reason_), _Internal::kHasBitsOffset + 3, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // string request_id = 7 [json_name = "requestId", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(ControlRequest, _impl_.request_id_), _Internal::kHasBitsOffset + 4, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + }}, + // no aux_entries + {{ + "\40\15\10\10\0\0\6\12" + "xtcp_treatment.v1.ControlRequest" + "experiment_id" + "agent_id" + "location" + "reason" + "request_id" + }}, + }; +} + + +inline constexpr ControlRequest::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : experiment_id_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + agent_id_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + location_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + reason_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + request_id_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + fleet_{false}, + action_{static_cast< ::xtcp_treatment::v1::ControlAction >(0)} {} + +template +constexpr ControlRequest::ControlRequest(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL ControlRequest::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) ControlRequest(arena); +} +constexpr auto ControlRequest::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::CopyInit(sizeof(ControlRequest), alignof(ControlRequest)); +} +constexpr auto ControlRequest::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &ControlRequest::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &ControlRequest::SharedDtor, + Super_::GetClearImpl(), &ControlRequest::ByteSizeLong, + &ControlRequest::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(ControlRequest, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[6], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct ControlRequestGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr ControlRequestGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + ControlRequest_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(ControlRequest::InternalGenerateClassData_( + _default, &ControlRequest_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~ControlRequestGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) ControlRequest _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(ControlRequestGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST ControlRequestGlobalsTypeInternal ControlRequest_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* ControlRequest_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return ControlRequest_globals_.GetClassData(); +#else + return ControlRequest_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +class AnalyzeRequest::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(AnalyzeRequest, _impl_._has_bits_); +}; + +constexpr AnalyzeRequest::ParseTableT_ AnalyzeRequest::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(AnalyzeRequest, _impl_._has_bits_), + 0, // no _extensions_ + 2, 8, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294967292, // skipmap + offsetof(ParseTableT_, field_entries), + 2, // num_field_entries + 0, // num_aux_entries + offsetof(ParseTableT_, field_names), // no aux_entries + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment::v1::AnalyzeRequest>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + // uint64 expected_revision = 2 [json_name = "expectedRevision"]; + {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(AnalyzeRequest, _impl_.expected_revision_), 1>(), + {16, 1, 0, + PROTOBUF_FIELD_OFFSET(AnalyzeRequest, _impl_.expected_revision_)}}, + // string experiment_id = 1 [json_name = "experimentId", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {10, 0, 0, + PROTOBUF_FIELD_OFFSET(AnalyzeRequest, _impl_.experiment_id_)}}, + }}, {{ + 65535, 65535 + }}, {{ + // string experiment_id = 1 [json_name = "experimentId", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(AnalyzeRequest, _impl_.experiment_id_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // uint64 expected_revision = 2 [json_name = "expectedRevision"]; + {PROTOBUF_FIELD_OFFSET(AnalyzeRequest, _impl_.expected_revision_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt64)}, + }}, + // no aux_entries + {{ + "\40\15\0\0\0\0\0\0" + "xtcp_treatment.v1.AnalyzeRequest" + "experiment_id" + }}, + }; +} + + +inline constexpr AnalyzeRequest::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : experiment_id_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + expected_revision_{::uint64_t{0u}} {} + +template +constexpr AnalyzeRequest::AnalyzeRequest(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL AnalyzeRequest::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) AnalyzeRequest(arena); +} +constexpr auto AnalyzeRequest::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::CopyInit(sizeof(AnalyzeRequest), alignof(AnalyzeRequest)); +} +constexpr auto AnalyzeRequest::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &AnalyzeRequest::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &AnalyzeRequest::SharedDtor, + Super_::GetClearImpl(), &AnalyzeRequest::ByteSizeLong, + &AnalyzeRequest::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(AnalyzeRequest, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[2], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct AnalyzeRequestGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr AnalyzeRequestGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + AnalyzeRequest_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(AnalyzeRequest::InternalGenerateClassData_( + _default, &AnalyzeRequest_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~AnalyzeRequestGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) AnalyzeRequest _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(AnalyzeRequestGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST AnalyzeRequestGlobalsTypeInternal AnalyzeRequest_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* AnalyzeRequest_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return AnalyzeRequest_globals_.GetClassData(); +#else + return AnalyzeRequest_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +class ReviewRequest::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(ReviewRequest, _impl_._has_bits_); +}; + +constexpr ReviewRequest::ParseTableT_ ReviewRequest::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(ReviewRequest, _impl_._has_bits_), + 0, // no _extensions_ + 6, 56, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294967232, // skipmap + offsetof(ParseTableT_, field_entries), + 6, // num_field_entries + 1, // num_aux_entries + offsetof(ParseTableT_, aux_entries), + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment::v1::ReviewRequest>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + {::_pbi::TcParser::MiniParse, {}}, + // string experiment_id = 1 [json_name = "experimentId", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {10, 0, 0, + PROTOBUF_FIELD_OFFSET(ReviewRequest, _impl_.experiment_id_)}}, + // uint64 expected_revision = 2 [json_name = "expectedRevision"]; + {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(ReviewRequest, _impl_.expected_revision_), 4>(), + {16, 4, 0, + PROTOBUF_FIELD_OFFSET(ReviewRequest, _impl_.expected_revision_)}}, + // bool approve = 3 [json_name = "approve"]; + {::_pbi::TcParser::SingularVarintNoZag1(), + {24, 5, 0, + PROTOBUF_FIELD_OFFSET(ReviewRequest, _impl_.approve_)}}, + // string reason = 4 [json_name = "reason", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {34, 1, 0, + PROTOBUF_FIELD_OFFSET(ReviewRequest, _impl_.reason_)}}, + // string request_id = 5 [json_name = "requestId", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {42, 2, 0, + PROTOBUF_FIELD_OFFSET(ReviewRequest, _impl_.request_id_)}}, + // .xtcp_treatment.v1.Evidence evidence = 6 [json_name = "evidence"]; + {::_pbi::TcParser::FastMtS1, + {50, 3, 0, + PROTOBUF_FIELD_OFFSET(ReviewRequest, _impl_.evidence_)}}, + {::_pbi::TcParser::MiniParse, {}}, + }}, {{ + 65535, 65535 + }}, {{ + // string experiment_id = 1 [json_name = "experimentId", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(ReviewRequest, _impl_.experiment_id_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // uint64 expected_revision = 2 [json_name = "expectedRevision"]; + {PROTOBUF_FIELD_OFFSET(ReviewRequest, _impl_.expected_revision_), _Internal::kHasBitsOffset + 4, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt64)}, + // bool approve = 3 [json_name = "approve"]; + {PROTOBUF_FIELD_OFFSET(ReviewRequest, _impl_.approve_), _Internal::kHasBitsOffset + 5, 0, (0 | ::_fl::kFcOptional | ::_fl::kBool)}, + // string reason = 4 [json_name = "reason", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(ReviewRequest, _impl_.reason_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // string request_id = 5 [json_name = "requestId", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(ReviewRequest, _impl_.request_id_), _Internal::kHasBitsOffset + 2, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // .xtcp_treatment.v1.Evidence evidence = 6 [json_name = "evidence"]; + {PROTOBUF_FIELD_OFFSET(ReviewRequest, _impl_.evidence_), _Internal::kHasBitsOffset + 3, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + }}, + {{ + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment::v1::Evidence>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment::v1::Evidence_globals_}, + #endif + }}, + {{ + "\37\15\0\0\6\12\0\0" + "xtcp_treatment.v1.ReviewRequest" + "experiment_id" + "reason" + "request_id" + }}, + }; +} + + +inline constexpr ReviewRequest::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : experiment_id_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + reason_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + request_id_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + evidence_{nullptr}, + expected_revision_{::uint64_t{0u}}, + approve_{false} {} + +template +constexpr ReviewRequest::ReviewRequest(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL ReviewRequest::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) ReviewRequest(arena); +} +constexpr auto ReviewRequest::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::CopyInit(sizeof(ReviewRequest), alignof(ReviewRequest)); +} +constexpr auto ReviewRequest::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &ReviewRequest::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &ReviewRequest::SharedDtor, + Super_::GetClearImpl(), &ReviewRequest::ByteSizeLong, + &ReviewRequest::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(ReviewRequest, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[4], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct ReviewRequestGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr ReviewRequestGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + ReviewRequest_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(ReviewRequest::InternalGenerateClassData_( + _default, &ReviewRequest_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~ReviewRequestGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) ReviewRequest _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(ReviewRequestGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST ReviewRequestGlobalsTypeInternal ReviewRequest_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* ReviewRequest_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return ReviewRequest_globals_.GetClassData(); +#else + return ReviewRequest_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +class AnalyzeResponse::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(AnalyzeResponse, _impl_._has_bits_); +}; + +constexpr AnalyzeResponse::ParseTableT_ AnalyzeResponse::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(AnalyzeResponse, _impl_._has_bits_), + 0, // no _extensions_ + 2, 8, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294967292, // skipmap + offsetof(ParseTableT_, field_entries), + 2, // num_field_entries + 1, // num_aux_entries + offsetof(ParseTableT_, aux_entries), + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment::v1::AnalyzeResponse>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + // string outcome = 2 [json_name = "outcome"]; + {::_pbi::TcParser::FastUS1, + {18, 0, 0, + PROTOBUF_FIELD_OFFSET(AnalyzeResponse, _impl_.outcome_)}}, + // .xtcp_treatment.v1.Evidence evidence = 1 [json_name = "evidence"]; + {::_pbi::TcParser::FastMtS1, + {10, 1, 0, + PROTOBUF_FIELD_OFFSET(AnalyzeResponse, _impl_.evidence_)}}, + }}, {{ + 65535, 65535 + }}, {{ + // .xtcp_treatment.v1.Evidence evidence = 1 [json_name = "evidence"]; + {PROTOBUF_FIELD_OFFSET(AnalyzeResponse, _impl_.evidence_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // string outcome = 2 [json_name = "outcome"]; + {PROTOBUF_FIELD_OFFSET(AnalyzeResponse, _impl_.outcome_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + }}, + {{ + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment::v1::Evidence>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment::v1::Evidence_globals_}, + #endif + }}, + {{ + "\41\0\7\0\0\0\0\0" + "xtcp_treatment.v1.AnalyzeResponse" + "outcome" + }}, + }; +} + + +inline constexpr AnalyzeResponse::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : outcome_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + evidence_{nullptr} {} + +template +constexpr AnalyzeResponse::AnalyzeResponse(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL AnalyzeResponse::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) AnalyzeResponse(arena); +} +constexpr auto AnalyzeResponse::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::CopyInit(sizeof(AnalyzeResponse), alignof(AnalyzeResponse)); +} +constexpr auto AnalyzeResponse::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &AnalyzeResponse::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &AnalyzeResponse::SharedDtor, + Super_::GetClearImpl(), &AnalyzeResponse::ByteSizeLong, + &AnalyzeResponse::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(AnalyzeResponse, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[3], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct AnalyzeResponseGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr AnalyzeResponseGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + AnalyzeResponse_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(AnalyzeResponse::InternalGenerateClassData_( + _default, &AnalyzeResponse_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~AnalyzeResponseGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) AnalyzeResponse _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(AnalyzeResponseGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST AnalyzeResponseGlobalsTypeInternal AnalyzeResponse_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* AnalyzeResponse_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return AnalyzeResponse_globals_.GetClassData(); +#else + return AnalyzeResponse_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +class ReviewResponse::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(ReviewResponse, _impl_._has_bits_); +}; + +constexpr ReviewResponse::ParseTableT_ ReviewResponse::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(ReviewResponse, _impl_._has_bits_), + 0, // no _extensions_ + 1, 0, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294967294, // skipmap + offsetof(ParseTableT_, field_entries), + 1, // num_field_entries + 1, // num_aux_entries + offsetof(ParseTableT_, aux_entries), + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment::v1::ReviewResponse>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + // .xtcp_treatment.v1.Experiment experiment = 1 [json_name = "experiment"]; + {::_pbi::TcParser::FastMtS1, + {10, 0, 0, + PROTOBUF_FIELD_OFFSET(ReviewResponse, _impl_.experiment_)}}, + }}, {{ + 65535, 65535 + }}, {{ + // .xtcp_treatment.v1.Experiment experiment = 1 [json_name = "experiment"]; + {PROTOBUF_FIELD_OFFSET(ReviewResponse, _impl_.experiment_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + }}, + {{ + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment::v1::Experiment>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment::v1::Experiment_globals_}, + #endif + }}, + {{ + }}, + }; +} + + +inline constexpr ReviewResponse::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : experiment_{nullptr} {} + +template +constexpr ReviewResponse::ReviewResponse(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL ReviewResponse::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) ReviewResponse(arena); +} +constexpr auto ReviewResponse::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::ZeroInit(sizeof(ReviewResponse), alignof(ReviewResponse)); +} +constexpr auto ReviewResponse::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &ReviewResponse::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &ReviewResponse::SharedDtor, + Super_::GetClearImpl(), &ReviewResponse::ByteSizeLong, + &ReviewResponse::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(ReviewResponse, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[5], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct ReviewResponseGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr ReviewResponseGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + ReviewResponse_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(ReviewResponse::InternalGenerateClassData_( + _default, &ReviewResponse_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~ReviewResponseGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) ReviewResponse _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(ReviewResponseGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST ReviewResponseGlobalsTypeInternal ReviewResponse_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* ReviewResponse_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return ReviewResponse_globals_.GetClassData(); +#else + return ReviewResponse_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +class ProposeResponse::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(ProposeResponse, _impl_._has_bits_); +}; + +constexpr ProposeResponse::ParseTableT_ ProposeResponse::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(ProposeResponse, _impl_._has_bits_), + 0, // no _extensions_ + 1, 0, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294967294, // skipmap + offsetof(ParseTableT_, field_entries), + 1, // num_field_entries + 1, // num_aux_entries + offsetof(ParseTableT_, aux_entries), + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment::v1::ProposeResponse>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + // .xtcp_treatment.v1.Experiment experiment = 1 [json_name = "experiment"]; + {::_pbi::TcParser::FastMtS1, + {10, 0, 0, + PROTOBUF_FIELD_OFFSET(ProposeResponse, _impl_.experiment_)}}, + }}, {{ + 65535, 65535 + }}, {{ + // .xtcp_treatment.v1.Experiment experiment = 1 [json_name = "experiment"]; + {PROTOBUF_FIELD_OFFSET(ProposeResponse, _impl_.experiment_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + }}, + {{ + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment::v1::Experiment>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment::v1::Experiment_globals_}, + #endif + }}, + {{ + }}, + }; +} + + +inline constexpr ProposeResponse::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : experiment_{nullptr} {} + +template +constexpr ProposeResponse::ProposeResponse(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL ProposeResponse::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) ProposeResponse(arena); +} +constexpr auto ProposeResponse::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::ZeroInit(sizeof(ProposeResponse), alignof(ProposeResponse)); +} +constexpr auto ProposeResponse::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &ProposeResponse::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &ProposeResponse::SharedDtor, + Super_::GetClearImpl(), &ProposeResponse::ByteSizeLong, + &ProposeResponse::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(ProposeResponse, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[1], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct ProposeResponseGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr ProposeResponseGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + ProposeResponse_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(ProposeResponse::InternalGenerateClassData_( + _default, &ProposeResponse_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~ProposeResponseGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) ProposeResponse _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(ProposeResponseGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST ProposeResponseGlobalsTypeInternal ProposeResponse_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* ProposeResponse_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return ProposeResponse_globals_.GetClassData(); +#else + return ProposeResponse_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +class ProposeRequest::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(ProposeRequest, _impl_._has_bits_); +}; + +constexpr ProposeRequest::ParseTableT_ ProposeRequest::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(ProposeRequest, _impl_._has_bits_), + 0, // no _extensions_ + 2, 8, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294967292, // skipmap + offsetof(ParseTableT_, field_entries), + 2, // num_field_entries + 1, // num_aux_entries + offsetof(ParseTableT_, aux_entries), + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment::v1::ProposeRequest>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + // string request_id = 2 [json_name = "requestId", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {18, 0, 0, + PROTOBUF_FIELD_OFFSET(ProposeRequest, _impl_.request_id_)}}, + // .xtcp_treatment.v1.Experiment experiment = 1 [json_name = "experiment", (.buf.validate.field) = { + {::_pbi::TcParser::FastMtS1, + {10, 1, 0, + PROTOBUF_FIELD_OFFSET(ProposeRequest, _impl_.experiment_)}}, + }}, {{ + 65535, 65535 + }}, {{ + // .xtcp_treatment.v1.Experiment experiment = 1 [json_name = "experiment", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(ProposeRequest, _impl_.experiment_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // string request_id = 2 [json_name = "requestId", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(ProposeRequest, _impl_.request_id_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + }}, + {{ + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment::v1::Experiment>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment::v1::Experiment_globals_}, + #endif + }}, + {{ + "\40\0\12\0\0\0\0\0" + "xtcp_treatment.v1.ProposeRequest" + "request_id" + }}, + }; +} + + +inline constexpr ProposeRequest::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : request_id_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + experiment_{nullptr} {} + +template +constexpr ProposeRequest::ProposeRequest(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL ProposeRequest::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) ProposeRequest(arena); +} +constexpr auto ProposeRequest::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::CopyInit(sizeof(ProposeRequest), alignof(ProposeRequest)); +} +constexpr auto ProposeRequest::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &ProposeRequest::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &ProposeRequest::SharedDtor, + Super_::GetClearImpl(), &ProposeRequest::ByteSizeLong, + &ProposeRequest::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(ProposeRequest, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[0], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct ProposeRequestGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr ProposeRequestGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + ProposeRequest_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(ProposeRequest::InternalGenerateClassData_( + _default, &ProposeRequest_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~ProposeRequestGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) ProposeRequest _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(ProposeRequestGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST ProposeRequestGlobalsTypeInternal ProposeRequest_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* ProposeRequest_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return ProposeRequest_globals_.GetClassData(); +#else + return ProposeRequest_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +class InspectResponse::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(InspectResponse, _impl_._has_bits_); +}; + +constexpr InspectResponse::ParseTableT_ InspectResponse::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(InspectResponse, _impl_._has_bits_), + 0, // no _extensions_ + 3, 24, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294967288, // skipmap + offsetof(ParseTableT_, field_entries), + 3, // num_field_entries + 3, // num_aux_entries + offsetof(ParseTableT_, aux_entries), + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment::v1::InspectResponse>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + {::_pbi::TcParser::MiniParse, {}}, + // repeated .xtcp_treatment.v1.Experiment experiments = 1 [json_name = "experiments"]; + {::_pbi::TcParser::FastMtR1, + {10, 0, 0, + PROTOBUF_FIELD_OFFSET(InspectResponse, _impl_.experiments_)}}, + // repeated .xtcp_treatment.v1.DesiredState desired = 2 [json_name = "desired"]; + {::_pbi::TcParser::FastMtR1, + {18, 1, 1, + PROTOBUF_FIELD_OFFSET(InspectResponse, _impl_.desired_)}}, + // repeated .xtcp_treatment.v1.CheckInRequest observed = 3 [json_name = "observed"]; + {::_pbi::TcParser::FastMtR1, + {26, 2, 2, + PROTOBUF_FIELD_OFFSET(InspectResponse, _impl_.observed_)}}, + }}, {{ + 65535, 65535 + }}, {{ + // repeated .xtcp_treatment.v1.Experiment experiments = 1 [json_name = "experiments"]; + {PROTOBUF_FIELD_OFFSET(InspectResponse, _impl_.experiments_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcRepeated | ::_fl::kMessage | ::_fl::kTvTable)}, + // repeated .xtcp_treatment.v1.DesiredState desired = 2 [json_name = "desired"]; + {PROTOBUF_FIELD_OFFSET(InspectResponse, _impl_.desired_), _Internal::kHasBitsOffset + 1, 1, (0 | ::_fl::kFcRepeated | ::_fl::kMessage | ::_fl::kTvTable)}, + // repeated .xtcp_treatment.v1.CheckInRequest observed = 3 [json_name = "observed"]; + {PROTOBUF_FIELD_OFFSET(InspectResponse, _impl_.observed_), _Internal::kHasBitsOffset + 2, 2, (0 | ::_fl::kFcRepeated | ::_fl::kMessage | ::_fl::kTvTable)}, + }}, + {{ + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment::v1::Experiment>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment::v1::Experiment_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment::v1::DesiredState>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment::v1::DesiredState_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment::v1::CheckInRequest>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment::v1::CheckInRequest_globals_}, + #endif + }}, + {{ + }}, + }; +} + + +inline constexpr InspectResponse::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : experiments_ { visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment::v1::InspectResponse, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::InspectResponse, _impl_.experiments_)>() + } + , + desired_ { visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment::v1::InspectResponse, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::InspectResponse, _impl_.desired_)>() + } + , + observed_ { visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment::v1::InspectResponse, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::InspectResponse, _impl_.observed_)>() + } + {} + +template +constexpr InspectResponse::InspectResponse(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL InspectResponse::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) InspectResponse(arena); +} +constexpr auto InspectResponse::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::CopyInit(sizeof(InspectResponse), alignof(InspectResponse)); +} +constexpr auto InspectResponse::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &InspectResponse::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &InspectResponse::SharedDtor, + Super_::GetClearImpl(), &InspectResponse::ByteSizeLong, + &InspectResponse::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(InspectResponse, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[9], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct InspectResponseGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr InspectResponseGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + InspectResponse_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(InspectResponse::InternalGenerateClassData_( + _default, &InspectResponse_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~InspectResponseGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) InspectResponse _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(InspectResponseGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST InspectResponseGlobalsTypeInternal InspectResponse_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* InspectResponse_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return InspectResponse_globals_.GetClassData(); +#else + return InspectResponse_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +} // namespace v1 +} // namespace xtcp_treatment +static const ::_pb::EnumDescriptor* PROTOBUF_NONNULL + file_level_enum_descriptors_xtcp_5ftreatment_2fv1_2foperator_2eproto[1]; +static constexpr const ::_pb::ServiceDescriptor* PROTOBUF_NONNULL* PROTOBUF_NULLABLE + file_level_service_descriptors_xtcp_5ftreatment_2fv1_2foperator_2eproto = nullptr; +const ::uint32_t + TableStruct_xtcp_5ftreatment_2fv1_2foperator_2eproto::offsets[] ABSL_ATTRIBUTE_SECTION_VARIABLE( + protodesc_cold) = { + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ProposeRequest, _impl_._has_bits_), + 5, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ProposeRequest, _impl_.experiment_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ProposeRequest, _impl_.request_id_), + 1, + 0, + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ProposeResponse, _impl_._has_bits_), + 4, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ProposeResponse, _impl_.experiment_), + 0, + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::AnalyzeRequest, _impl_._has_bits_), + 5, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::AnalyzeRequest, _impl_.experiment_id_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::AnalyzeRequest, _impl_.expected_revision_), + 0, + 1, + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::AnalyzeResponse, _impl_._has_bits_), + 5, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::AnalyzeResponse, _impl_.evidence_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::AnalyzeResponse, _impl_.outcome_), + 1, + 0, + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ReviewRequest, _impl_._has_bits_), + 9, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ReviewRequest, _impl_.experiment_id_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ReviewRequest, _impl_.expected_revision_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ReviewRequest, _impl_.approve_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ReviewRequest, _impl_.reason_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ReviewRequest, _impl_.request_id_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ReviewRequest, _impl_.evidence_), + 0, + 4, + 5, + 1, + 2, + 3, + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ReviewResponse, _impl_._has_bits_), + 4, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ReviewResponse, _impl_.experiment_), + 0, + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ControlRequest, _impl_._has_bits_), + 10, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ControlRequest, _impl_.experiment_id_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ControlRequest, _impl_.agent_id_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ControlRequest, _impl_.location_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ControlRequest, _impl_.fleet_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ControlRequest, _impl_.action_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ControlRequest, _impl_.reason_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ControlRequest, _impl_.request_id_), + 0, + 1, + 2, + 5, + 6, + 3, + 4, + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ControlResponse, _impl_._has_bits_), + 4, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ControlResponse, _impl_.affected_agents_), + 0, + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::InspectRequest, _impl_._has_bits_), + 5, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::InspectRequest, _impl_.experiment_id_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::InspectRequest, _impl_.agent_id_), + 0, + 1, + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::InspectResponse, _impl_._has_bits_), + 6, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::InspectResponse, _impl_.experiments_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::InspectResponse, _impl_.desired_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::InspectResponse, _impl_.observed_), + 0, + 1, + 2, +}; + +static const ::_pbi::MigrationSchema + schemas[] ABSL_ATTRIBUTE_SECTION_VARIABLE(protodesc_cold) = { + {0, sizeof(::xtcp_treatment::v1::ProposeRequest)}, + {7, sizeof(::xtcp_treatment::v1::ProposeResponse)}, + {12, sizeof(::xtcp_treatment::v1::AnalyzeRequest)}, + {19, sizeof(::xtcp_treatment::v1::AnalyzeResponse)}, + {26, sizeof(::xtcp_treatment::v1::ReviewRequest)}, + {41, sizeof(::xtcp_treatment::v1::ReviewResponse)}, + {46, sizeof(::xtcp_treatment::v1::ControlRequest)}, + {63, sizeof(::xtcp_treatment::v1::ControlResponse)}, + {68, sizeof(::xtcp_treatment::v1::InspectRequest)}, + {75, sizeof(::xtcp_treatment::v1::InspectResponse)}, +}; +static const ::_pbi::MessageGlobalsBase* PROTOBUF_NONNULL const + file_message_globals[] = { + &::xtcp_treatment::v1::ProposeRequest_globals_, + &::xtcp_treatment::v1::ProposeResponse_globals_, + &::xtcp_treatment::v1::AnalyzeRequest_globals_, + &::xtcp_treatment::v1::AnalyzeResponse_globals_, + &::xtcp_treatment::v1::ReviewRequest_globals_, + &::xtcp_treatment::v1::ReviewResponse_globals_, + &::xtcp_treatment::v1::ControlRequest_globals_, + &::xtcp_treatment::v1::ControlResponse_globals_, + &::xtcp_treatment::v1::InspectRequest_globals_, + &::xtcp_treatment::v1::InspectResponse_globals_, +}; +const char descriptor_table_protodef_xtcp_5ftreatment_2fv1_2foperator_2eproto[] ABSL_ATTRIBUTE_SECTION_VARIABLE( + protodesc_cold) = { + "\n xtcp_treatment/v1/operator.proto\022\021xtcp" + "_treatment.v1\032\033buf/validate/validate.pro" + "to\032\035xtcp_treatment/v1/agent.proto\032!xtcp_" + "treatment/v1/treatment.proto\"\273\002\n\016Propose" + "Request\022E\n\nexperiment\030\001 \001(\0132\035.xtcp_treat" + "ment.v1.ExperimentB\006\272H\003\310\001\001R\nexperiment\022H" + "\n\nrequest_id\030\002 \001(\tB)\272H&r$2\"^[a-zA-Z0-9][" + "a-zA-Z0-9_.-]{0,127}$R\trequestId:\227\001\272H\223\001\032" + "\220\001\n\031propose.request_id_suffix\022Mrequest_i" + "d must not end with .result (reserved fo" + "r the paired response event)\032$!this.requ" + "est_id.endsWith(\'.result\')\"P\n\017ProposeRes" + "ponse\022=\n\nexperiment\030\001 \001(\0132\035.xtcp_treatme" + "nt.v1.ExperimentR\nexperiment\"\215\001\n\016Analyze" + "Request\022N\n\rexperiment_id\030\001 \001(\tB)\272H&r$2\"^" + "[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\014exper" + "imentId\022+\n\021expected_revision\030\002 \001(\004R\020expe" + "ctedRevision\"d\n\017AnalyzeResponse\0227\n\010evide" + "nce\030\001 \001(\0132\033.xtcp_treatment.v1.EvidenceR\010" + "evidence\022\030\n\007outcome\030\002 \001(\tR\007outcome\"\346\003\n\rR" + "eviewRequest\022N\n\rexperiment_id\030\001 \001(\tB)\272H&" + "r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\014" + "experimentId\022+\n\021expected_revision\030\002 \001(\004R" + "\020expectedRevision\022\030\n\007approve\030\003 \001(\010R\007appr" + "ove\022\"\n\006reason\030\004 \001(\tB\n\272H\007r\005\020\001\030\200 R\006reason\022" + "H\n\nrequest_id\030\005 \001(\tB)\272H&r$2\"^[a-zA-Z0-9]" + "[a-zA-Z0-9_.-]{0,127}$R\trequestId\0227\n\010evi" + "dence\030\006 \001(\0132\033.xtcp_treatment.v1.Evidence" + "R\010evidence:\226\001\272H\222\001\032\217\001\n\030review.request_id_" + "suffix\022Mrequest_id must not end with .re" + "sult (reserved for the paired response e" + "vent)\032$!this.request_id.endsWith(\'.resul" + "t\')\"O\n\016ReviewResponse\022=\n\nexperiment\030\001 \001(" + "\0132\035.xtcp_treatment.v1.ExperimentR\nexperi" + "ment\"\272\006\n\016ControlRequest\022Q\n\rexperiment_id" + "\030\001 \001(\tB,\272H)r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-" + "]{0,127}$\330\001\001R\014experimentId\022G\n\010agent_id\030\002" + " \001(\tB,\272H)r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{" + "0,127}$\330\001\001R\007agentId\022H\n\010location\030\003 \001(\tB,\272" + "H)r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$" + "\330\001\001R\010location\022\024\n\005fleet\030\004 \001(\010R\005fleet\022D\n\006a" + "ction\030\005 \001(\0162 .xtcp_treatment.v1.ControlA" + "ctionB\n\272H\007\202\001\004\020\001 \000R\006action\022\"\n\006reason\030\006 \001(" + "\tB\n\272H\007r\005\020\001\030\200 R\006reason\022H\n\nrequest_id\030\007 \001(" + "\tB)\272H&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,1" + "27}$R\trequestId:\367\002\272H\363\002\032\220\001\n\031control.reque" + "st_id_suffix\022Mrequest_id must not end wi" + "th .result (reserved for the paired resp" + "onse event)\032$!this.request_id.endsWith(\'" + ".result\')\032\335\001\n\024control.one_selector\022Eexac" + "tly one of experiment_id, agent_id, loca" + "tion or fleet is required\032~(this.fleet \?" + " 1 : 0) + (this.experiment_id != \'\' \? 1 " + ": 0) + (this.agent_id != \'\' \? 1 : 0) + (" + "this.location != \'\' \? 1 : 0) == 1\":\n\017Con" + "trolResponse\022\'\n\017affected_agents\030\001 \001(\rR\016a" + "ffectedAgents\"\254\001\n\016InspectRequest\022Q\n\rexpe" + "riment_id\030\001 \001(\tB,\272H)r$2\"^[a-zA-Z0-9][a-z" + "A-Z0-9_.-]{0,127}$\330\001\001R\014experimentId\022G\n\010a" + "gent_id\030\002 \001(\tB,\272H)r$2\"^[a-zA-Z0-9][a-zA-" + "Z0-9_.-]{0,127}$\330\001\001R\007agentId\"\314\001\n\017Inspect" + "Response\022\?\n\013experiments\030\001 \003(\0132\035.xtcp_tre" + "atment.v1.ExperimentR\013experiments\0229\n\007des" + "ired\030\002 \003(\0132\037.xtcp_treatment.v1.DesiredSt" + "ateR\007desired\022=\n\010observed\030\003 \003(\0132!.xtcp_tr" + "eatment.v1.CheckInRequestR\010observed*\270\001\n\r" + "ControlAction\022\036\n\032CONTROL_ACTION_UNSPECIF" + "IED\020\000\022\030\n\024CONTROL_ACTION_PAUSE\020\001\022\032\n\026CONTR" + "OL_ACTION_SUSPEND\020\002\022\031\n\025CONTROL_ACTION_RE" + "SUME\020\003\022\033\n\027CONTROL_ACTION_ROLLBACK\020\004\022\031\n\025C" + "ONTROL_ACTION_RETIRE\020\0052\306\003\n\017OperatorServi" + "ce\022V\n\007Propose\022!.xtcp_treatment.v1.Propos" + "eRequest\032\".xtcp_treatment.v1.ProposeResp" + "onse\"\004\320\363\030\002\022V\n\007Analyze\022!.xtcp_treatment.v" + "1.AnalyzeRequest\032\".xtcp_treatment.v1.Ana" + "lyzeResponse\"\004\320\363\030\002\022S\n\006Review\022 .xtcp_trea" + "tment.v1.ReviewRequest\032!.xtcp_treatment." + "v1.ReviewResponse\"\004\320\363\030\002\022V\n\007Control\022!.xtc" + "p_treatment.v1.ControlRequest\032\".xtcp_tre" + "atment.v1.ControlResponse\"\004\320\363\030\002\022V\n\007Inspe" + "ct\022!.xtcp_treatment.v1.InspectRequest\032\"." + "xtcp_treatment.v1.InspectResponse\"\004\320\363\030\002B" + "\277\001\n\025com.xtcp_treatment.v1B\rOperatorProto" + "P\001Z6github.com/randomizedcoder/xtcp2/gen" + "/go/xtcp_treatment\242\002\003XXX\252\002\020XtcpTreatment" + ".V1\312\002\020XtcpTreatment\\V1\342\002\034XtcpTreatment\\V" + "1\\GPBMetadata\352\002\021XtcpTreatment::V1b\006proto" + "3" +}; +static const ::_pbi::DescriptorTable* PROTOBUF_NONNULL const + descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2eproto_deps[3] = { + &::descriptor_table_buf_2fvalidate_2fvalidate_2eproto, + &::descriptor_table_xtcp_5ftreatment_2fv1_2fagent_2eproto, + &::descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, +}; +static ::absl::once_flag descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2eproto_once; +PROTOBUF_CONSTINIT const ::_pbi::DescriptorTable descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2eproto = { + false, + false, + 3481, + descriptor_table_protodef_xtcp_5ftreatment_2fv1_2foperator_2eproto, + "xtcp_treatment/v1/operator.proto", + &descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2eproto_once, + descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2eproto_deps, + 3, + 10, + schemas, + file_message_globals, + TableStruct_xtcp_5ftreatment_2fv1_2foperator_2eproto::offsets, + file_level_enum_descriptors_xtcp_5ftreatment_2fv1_2foperator_2eproto, + file_level_service_descriptors_xtcp_5ftreatment_2fv1_2foperator_2eproto, +}; +namespace xtcp_treatment { +namespace v1 { +[[nodiscard]] const ::google::protobuf::EnumDescriptor* PROTOBUF_NONNULL +ControlAction_descriptor() { + ::google::protobuf::internal::AssignDescriptors(&descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2eproto); + return file_level_enum_descriptors_xtcp_5ftreatment_2fv1_2foperator_2eproto[0]; +} +PROTOBUF_CONSTINIT const uint32_t ControlAction_internal_data_[] = { + 393216u, 0u, }; +// =================================================================== + +void ProposeRequest::clear_experiment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.experiment_ != nullptr) _impl_.experiment_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); +} +ProposeRequest::ProposeRequest(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, ProposeRequest_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment.v1.ProposeRequest) +} +PROTOBUF_NDEBUG_INLINE ProposeRequest::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment::v1::ProposeRequest& from_msg) + : _has_bits_{from._has_bits_}, + request_id_(arena, from.request_id_) {} + +ProposeRequest::ProposeRequest( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const ProposeRequest& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, ProposeRequest_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + ProposeRequest* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + ::uint32_t cached_has_bits = _impl_._has_bits_[0]; + _impl_.experiment_ = (CheckHasBit(cached_has_bits, 0x00000002U)) + ? Super_::CopyConstruct(arena, *from._impl_.experiment_) + : nullptr; + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment.v1.ProposeRequest) +} +PROTOBUF_NDEBUG_INLINE ProposeRequest::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + : request_id_(arena) {} + +inline void ProposeRequest::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + _impl_.experiment_ = {}; +} +ProposeRequest::~ProposeRequest() { + // @@protoc_insertion_point(destructor:xtcp_treatment.v1.ProposeRequest) + SharedDtor(*this); +} +inline void ProposeRequest::SharedDtor(MessageLite& self) { + ProposeRequest& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + this_._impl_.request_id_.Destroy(); + delete this_._impl_.experiment_; + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull ProposeRequest_class_data_ = + ProposeRequest::InternalGenerateClassData_(ProposeRequest_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +ProposeRequest::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&ProposeRequest_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(ProposeRequest_class_data_.tc_table); + return ProposeRequest_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +ProposeRequest::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&ProposeRequest_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&ProposeRequest_globals_)); + return ProposeRequest_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const ProposeRequest::ParseTableT_ + ProposeRequest::_table_ = + ProposeRequest::InternalGenerateParseTable_(ProposeRequest_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void ProposeRequest::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment.v1.ProposeRequest) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x00000003U)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + this_._impl_.request_id_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + ABSL_DCHECK(this_._impl_.experiment_ != nullptr); + this_._impl_.experiment_->Clear(); + } + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL ProposeRequest::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const ProposeRequest& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL ProposeRequest::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const ProposeRequest& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment.v1.ProposeRequest) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // .xtcp_treatment.v1.Experiment experiment = 1 [json_name = "experiment", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 1, *this_._impl_.experiment_, this_._impl_.experiment_->GetCachedSize(), target, + stream); + } + + // string request_id = 2 [json_name = "requestId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_request_id().empty()) { + const ::std::string& _s = this_._internal_request_id(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.ProposeRequest.request_id"); + target = stream->WriteStringMaybeAliased(2, _s, target); + } + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment.v1.ProposeRequest) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t ProposeRequest::ByteSizeLong(const MessageLite& base) { + const ProposeRequest& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t ProposeRequest::ByteSizeLong() const { + const ProposeRequest& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment.v1.ProposeRequest) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + ::_pbi::Prefetch5LinesFrom7Lines(&this_); + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x00000003U)) { + // string request_id = 2 [json_name = "requestId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_request_id().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_request_id()); + } + } + // .xtcp_treatment.v1.Experiment experiment = 1 [json_name = "experiment", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.experiment_); + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void ProposeRequest::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + ::google::protobuf::Arena* arena = _this->GetArena(); + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment.v1.ProposeRequest) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x00000003U)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!from._internal_request_id().empty()) { + _this->_internal_set_request_id(from._internal_request_id()); + } else { + if (_this->_impl_.request_id_.IsDefault()) { + _this->_internal_set_request_id(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + ABSL_DCHECK(from._impl_.experiment_ != nullptr); + if (_this->_impl_.experiment_ == nullptr) { + _this->_impl_.experiment_ = Super_::CopyConstruct(arena, *from._impl_.experiment_); + } else { + _this->_impl_.experiment_->MergeFrom(*from._impl_.experiment_); + } + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void ProposeRequest::CopyFrom(const ProposeRequest& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment.v1.ProposeRequest) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void ProposeRequest::InternalSwap(ProposeRequest* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + auto* arena = GetArena(); + ABSL_DCHECK_EQ(arena, other->GetArena()); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.request_id_, &other->_impl_.request_id_, arena); + swap(_impl_.experiment_, other->_impl_.experiment_); +} + +::google::protobuf::Metadata ProposeRequest::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +void ProposeResponse::clear_experiment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.experiment_ != nullptr) _impl_.experiment_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +ProposeResponse::ProposeResponse(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, ProposeResponse_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment.v1.ProposeResponse) +} +PROTOBUF_NDEBUG_INLINE ProposeResponse::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment::v1::ProposeResponse& from_msg) + : _has_bits_{from._has_bits_} {} + +ProposeResponse::ProposeResponse( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const ProposeResponse& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, ProposeResponse_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + ProposeResponse* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + ::uint32_t cached_has_bits = _impl_._has_bits_[0]; + _impl_.experiment_ = (CheckHasBit(cached_has_bits, 0x00000001U)) + ? Super_::CopyConstruct(arena, *from._impl_.experiment_) + : nullptr; + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment.v1.ProposeResponse) +} +PROTOBUF_NDEBUG_INLINE ProposeResponse::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + {} + +inline void ProposeResponse::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + _impl_.experiment_ = {}; +} +ProposeResponse::~ProposeResponse() { + // @@protoc_insertion_point(destructor:xtcp_treatment.v1.ProposeResponse) + SharedDtor(*this); +} +inline void ProposeResponse::SharedDtor(MessageLite& self) { + ProposeResponse& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + delete this_._impl_.experiment_; + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull ProposeResponse_class_data_ = + ProposeResponse::InternalGenerateClassData_(ProposeResponse_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +ProposeResponse::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&ProposeResponse_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(ProposeResponse_class_data_.tc_table); + return ProposeResponse_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +ProposeResponse::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&ProposeResponse_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&ProposeResponse_globals_)); + return ProposeResponse_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const ProposeResponse::ParseTableT_ + ProposeResponse::_table_ = + ProposeResponse::InternalGenerateParseTable_(ProposeResponse_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void ProposeResponse::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment.v1.ProposeResponse) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + ABSL_DCHECK(this_._impl_.experiment_ != nullptr); + this_._impl_.experiment_->Clear(); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL ProposeResponse::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const ProposeResponse& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL ProposeResponse::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const ProposeResponse& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment.v1.ProposeResponse) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // .xtcp_treatment.v1.Experiment experiment = 1 [json_name = "experiment"]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 1, *this_._impl_.experiment_, this_._impl_.experiment_->GetCachedSize(), target, + stream); + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment.v1.ProposeResponse) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t ProposeResponse::ByteSizeLong(const MessageLite& base) { + const ProposeResponse& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t ProposeResponse::ByteSizeLong() const { + const ProposeResponse& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment.v1.ProposeResponse) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + { + // .xtcp_treatment.v1.Experiment experiment = 1 [json_name = "experiment"]; + cached_has_bits = this_._impl_._has_bits_[0]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.experiment_); + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void ProposeResponse::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + ::google::protobuf::Arena* arena = _this->GetArena(); + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment.v1.ProposeResponse) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + ABSL_DCHECK(from._impl_.experiment_ != nullptr); + if (_this->_impl_.experiment_ == nullptr) { + _this->_impl_.experiment_ = Super_::CopyConstruct(arena, *from._impl_.experiment_); + } else { + _this->_impl_.experiment_->MergeFrom(*from._impl_.experiment_); + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void ProposeResponse::CopyFrom(const ProposeResponse& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment.v1.ProposeResponse) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void ProposeResponse::InternalSwap(ProposeResponse* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + swap(_impl_.experiment_, other->_impl_.experiment_); +} + +::google::protobuf::Metadata ProposeResponse::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +AnalyzeRequest::AnalyzeRequest(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, AnalyzeRequest_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment.v1.AnalyzeRequest) +} +PROTOBUF_NDEBUG_INLINE AnalyzeRequest::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment::v1::AnalyzeRequest& from_msg) + : _has_bits_{from._has_bits_}, + experiment_id_(arena, from.experiment_id_) {} + +AnalyzeRequest::AnalyzeRequest( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const AnalyzeRequest& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, AnalyzeRequest_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + AnalyzeRequest* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + _impl_.expected_revision_ = from._impl_.expected_revision_; + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment.v1.AnalyzeRequest) +} +PROTOBUF_NDEBUG_INLINE AnalyzeRequest::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + : experiment_id_(arena) {} + +inline void AnalyzeRequest::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + _impl_.expected_revision_ = {}; +} +AnalyzeRequest::~AnalyzeRequest() { + // @@protoc_insertion_point(destructor:xtcp_treatment.v1.AnalyzeRequest) + SharedDtor(*this); +} +inline void AnalyzeRequest::SharedDtor(MessageLite& self) { + AnalyzeRequest& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + this_._impl_.experiment_id_.Destroy(); + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull AnalyzeRequest_class_data_ = + AnalyzeRequest::InternalGenerateClassData_(AnalyzeRequest_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +AnalyzeRequest::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&AnalyzeRequest_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(AnalyzeRequest_class_data_.tc_table); + return AnalyzeRequest_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +AnalyzeRequest::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&AnalyzeRequest_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&AnalyzeRequest_globals_)); + return AnalyzeRequest_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const AnalyzeRequest::ParseTableT_ + AnalyzeRequest::_table_ = + AnalyzeRequest::InternalGenerateParseTable_(AnalyzeRequest_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void AnalyzeRequest::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment.v1.AnalyzeRequest) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + this_._impl_.experiment_id_.ClearNonDefaultToEmpty(); + } + this_._impl_.expected_revision_ = ::uint64_t{0u}; + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL AnalyzeRequest::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const AnalyzeRequest& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL AnalyzeRequest::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const AnalyzeRequest& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment.v1.AnalyzeRequest) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // string experiment_id = 1 [json_name = "experimentId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_experiment_id().empty()) { + const ::std::string& _s = this_._internal_experiment_id(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.AnalyzeRequest.experiment_id"); + target = stream->WriteStringMaybeAliased(1, _s, target); + } + } + + // uint64 expected_revision = 2 [json_name = "expectedRevision"]; + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (this_._internal_expected_revision() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray( + 2, this_._internal_expected_revision(), target); + } + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment.v1.AnalyzeRequest) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t AnalyzeRequest::ByteSizeLong(const MessageLite& base) { + const AnalyzeRequest& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t AnalyzeRequest::ByteSizeLong() const { + const AnalyzeRequest& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment.v1.AnalyzeRequest) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + ::_pbi::Prefetch5LinesFrom7Lines(&this_); + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x00000003U)) { + // string experiment_id = 1 [json_name = "experimentId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_experiment_id().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_experiment_id()); + } + } + // uint64 expected_revision = 2 [json_name = "expectedRevision"]; + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (this_._internal_expected_revision() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne( + this_._internal_expected_revision()); + } + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void AnalyzeRequest::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment.v1.AnalyzeRequest) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x00000003U)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!from._internal_experiment_id().empty()) { + _this->_internal_set_experiment_id(from._internal_experiment_id()); + } else { + if (_this->_impl_.experiment_id_.IsDefault()) { + _this->_internal_set_experiment_id(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (from._internal_expected_revision() != 0) { + _this->_impl_.expected_revision_ = from._impl_.expected_revision_; + } + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void AnalyzeRequest::CopyFrom(const AnalyzeRequest& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment.v1.AnalyzeRequest) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void AnalyzeRequest::InternalSwap(AnalyzeRequest* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + auto* arena = GetArena(); + ABSL_DCHECK_EQ(arena, other->GetArena()); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.experiment_id_, &other->_impl_.experiment_id_, arena); + swap(_impl_.expected_revision_, other->_impl_.expected_revision_); +} + +::google::protobuf::Metadata AnalyzeRequest::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +void AnalyzeResponse::clear_evidence() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.evidence_ != nullptr) _impl_.evidence_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); +} +AnalyzeResponse::AnalyzeResponse(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, AnalyzeResponse_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment.v1.AnalyzeResponse) +} +PROTOBUF_NDEBUG_INLINE AnalyzeResponse::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment::v1::AnalyzeResponse& from_msg) + : _has_bits_{from._has_bits_}, + outcome_(arena, from.outcome_) {} + +AnalyzeResponse::AnalyzeResponse( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const AnalyzeResponse& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, AnalyzeResponse_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + AnalyzeResponse* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + ::uint32_t cached_has_bits = _impl_._has_bits_[0]; + _impl_.evidence_ = (CheckHasBit(cached_has_bits, 0x00000002U)) + ? Super_::CopyConstruct(arena, *from._impl_.evidence_) + : nullptr; + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment.v1.AnalyzeResponse) +} +PROTOBUF_NDEBUG_INLINE AnalyzeResponse::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + : outcome_(arena) {} + +inline void AnalyzeResponse::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + _impl_.evidence_ = {}; +} +AnalyzeResponse::~AnalyzeResponse() { + // @@protoc_insertion_point(destructor:xtcp_treatment.v1.AnalyzeResponse) + SharedDtor(*this); +} +inline void AnalyzeResponse::SharedDtor(MessageLite& self) { + AnalyzeResponse& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + this_._impl_.outcome_.Destroy(); + delete this_._impl_.evidence_; + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull AnalyzeResponse_class_data_ = + AnalyzeResponse::InternalGenerateClassData_(AnalyzeResponse_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +AnalyzeResponse::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&AnalyzeResponse_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(AnalyzeResponse_class_data_.tc_table); + return AnalyzeResponse_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +AnalyzeResponse::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&AnalyzeResponse_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&AnalyzeResponse_globals_)); + return AnalyzeResponse_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const AnalyzeResponse::ParseTableT_ + AnalyzeResponse::_table_ = + AnalyzeResponse::InternalGenerateParseTable_(AnalyzeResponse_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void AnalyzeResponse::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment.v1.AnalyzeResponse) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x00000003U)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + this_._impl_.outcome_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + ABSL_DCHECK(this_._impl_.evidence_ != nullptr); + this_._impl_.evidence_->Clear(); + } + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL AnalyzeResponse::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const AnalyzeResponse& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL AnalyzeResponse::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const AnalyzeResponse& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment.v1.AnalyzeResponse) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // .xtcp_treatment.v1.Evidence evidence = 1 [json_name = "evidence"]; + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 1, *this_._impl_.evidence_, this_._impl_.evidence_->GetCachedSize(), target, + stream); + } + + // string outcome = 2 [json_name = "outcome"]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_outcome().empty()) { + const ::std::string& _s = this_._internal_outcome(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.AnalyzeResponse.outcome"); + target = stream->WriteStringMaybeAliased(2, _s, target); + } + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment.v1.AnalyzeResponse) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t AnalyzeResponse::ByteSizeLong(const MessageLite& base) { + const AnalyzeResponse& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t AnalyzeResponse::ByteSizeLong() const { + const AnalyzeResponse& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment.v1.AnalyzeResponse) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + ::_pbi::Prefetch5LinesFrom7Lines(&this_); + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x00000003U)) { + // string outcome = 2 [json_name = "outcome"]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_outcome().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_outcome()); + } + } + // .xtcp_treatment.v1.Evidence evidence = 1 [json_name = "evidence"]; + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.evidence_); + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void AnalyzeResponse::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + ::google::protobuf::Arena* arena = _this->GetArena(); + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment.v1.AnalyzeResponse) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x00000003U)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!from._internal_outcome().empty()) { + _this->_internal_set_outcome(from._internal_outcome()); + } else { + if (_this->_impl_.outcome_.IsDefault()) { + _this->_internal_set_outcome(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + ABSL_DCHECK(from._impl_.evidence_ != nullptr); + if (_this->_impl_.evidence_ == nullptr) { + _this->_impl_.evidence_ = Super_::CopyConstruct(arena, *from._impl_.evidence_); + } else { + _this->_impl_.evidence_->MergeFrom(*from._impl_.evidence_); + } + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void AnalyzeResponse::CopyFrom(const AnalyzeResponse& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment.v1.AnalyzeResponse) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void AnalyzeResponse::InternalSwap(AnalyzeResponse* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + auto* arena = GetArena(); + ABSL_DCHECK_EQ(arena, other->GetArena()); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.outcome_, &other->_impl_.outcome_, arena); + swap(_impl_.evidence_, other->_impl_.evidence_); +} + +::google::protobuf::Metadata AnalyzeResponse::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +void ReviewRequest::clear_evidence() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.evidence_ != nullptr) _impl_.evidence_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); +} +ReviewRequest::ReviewRequest(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, ReviewRequest_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment.v1.ReviewRequest) +} +PROTOBUF_NDEBUG_INLINE ReviewRequest::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment::v1::ReviewRequest& from_msg) + : _has_bits_{from._has_bits_}, + experiment_id_(arena, from.experiment_id_), + reason_(arena, from.reason_), + request_id_(arena, from.request_id_) {} + +ReviewRequest::ReviewRequest( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const ReviewRequest& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, ReviewRequest_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + ReviewRequest* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + ::uint32_t cached_has_bits = _impl_._has_bits_[0]; + _impl_.evidence_ = (CheckHasBit(cached_has_bits, 0x00000008U)) + ? Super_::CopyConstruct(arena, *from._impl_.evidence_) + : nullptr; + ::memcpy(reinterpret_cast(&_impl_) + + offsetof(Impl_, expected_revision_), + reinterpret_cast(&from._impl_) + + offsetof(Impl_, expected_revision_), + offsetof(Impl_, approve_) - + offsetof(Impl_, expected_revision_) + + sizeof(Impl_::approve_)); + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment.v1.ReviewRequest) +} +PROTOBUF_NDEBUG_INLINE ReviewRequest::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + : experiment_id_(arena), + reason_(arena), + request_id_(arena) {} + +inline void ReviewRequest::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + ::memset(reinterpret_cast(&_impl_) + + offsetof(Impl_, evidence_), + 0, + offsetof(Impl_, approve_) - + offsetof(Impl_, evidence_) + + sizeof(Impl_::approve_)); +} +ReviewRequest::~ReviewRequest() { + // @@protoc_insertion_point(destructor:xtcp_treatment.v1.ReviewRequest) + SharedDtor(*this); +} +inline void ReviewRequest::SharedDtor(MessageLite& self) { + ReviewRequest& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + this_._impl_.experiment_id_.Destroy(); + this_._impl_.reason_.Destroy(); + this_._impl_.request_id_.Destroy(); + delete this_._impl_.evidence_; + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull ReviewRequest_class_data_ = + ReviewRequest::InternalGenerateClassData_(ReviewRequest_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +ReviewRequest::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&ReviewRequest_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(ReviewRequest_class_data_.tc_table); + return ReviewRequest_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +ReviewRequest::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&ReviewRequest_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&ReviewRequest_globals_)); + return ReviewRequest_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const ReviewRequest::ParseTableT_ + ReviewRequest::_table_ = + ReviewRequest::InternalGenerateParseTable_(ReviewRequest_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void ReviewRequest::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment.v1.ReviewRequest) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000000fU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + this_._impl_.experiment_id_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + this_._impl_.reason_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + this_._impl_.request_id_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + ABSL_DCHECK(this_._impl_.evidence_ != nullptr); + this_._impl_.evidence_->Clear(); + } + } + if (BatchCheckHasBit(cached_has_bits, 0x00000030U)) { + ::memset(&this_._impl_.expected_revision_, 0, + static_cast<::size_t>( + reinterpret_cast(&this_._impl_.approve_) - + reinterpret_cast(&this_._impl_.expected_revision_)) + + sizeof(_impl_.approve_)); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL ReviewRequest::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const ReviewRequest& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL ReviewRequest::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const ReviewRequest& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment.v1.ReviewRequest) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // string experiment_id = 1 [json_name = "experimentId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_experiment_id().empty()) { + const ::std::string& _s = this_._internal_experiment_id(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.ReviewRequest.experiment_id"); + target = stream->WriteStringMaybeAliased(1, _s, target); + } + } + + // uint64 expected_revision = 2 [json_name = "expectedRevision"]; + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (this_._internal_expected_revision() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray( + 2, this_._internal_expected_revision(), target); + } + } + + // bool approve = 3 [json_name = "approve"]; + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (this_._internal_approve() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray( + 3, this_._internal_approve(), target); + } + } + + // string reason = 4 [json_name = "reason", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!this_._internal_reason().empty()) { + const ::std::string& _s = this_._internal_reason(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.ReviewRequest.reason"); + target = stream->WriteStringMaybeAliased(4, _s, target); + } + } + + // string request_id = 5 [json_name = "requestId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (!this_._internal_request_id().empty()) { + const ::std::string& _s = this_._internal_request_id(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.ReviewRequest.request_id"); + target = stream->WriteStringMaybeAliased(5, _s, target); + } + } + + // .xtcp_treatment.v1.Evidence evidence = 6 [json_name = "evidence"]; + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 6, *this_._impl_.evidence_, this_._impl_.evidence_->GetCachedSize(), target, + stream); + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment.v1.ReviewRequest) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t ReviewRequest::ByteSizeLong(const MessageLite& base) { + const ReviewRequest& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t ReviewRequest::ByteSizeLong() const { + const ReviewRequest& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment.v1.ReviewRequest) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + ::_pbi::Prefetch5LinesFrom7Lines(&this_); + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000003fU)) { + // string experiment_id = 1 [json_name = "experimentId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_experiment_id().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_experiment_id()); + } + } + // string reason = 4 [json_name = "reason", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!this_._internal_reason().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_reason()); + } + } + // string request_id = 5 [json_name = "requestId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (!this_._internal_request_id().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_request_id()); + } + } + // .xtcp_treatment.v1.Evidence evidence = 6 [json_name = "evidence"]; + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.evidence_); + } + // uint64 expected_revision = 2 [json_name = "expectedRevision"]; + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (this_._internal_expected_revision() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne( + this_._internal_expected_revision()); + } + } + // bool approve = 3 [json_name = "approve"]; + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (this_._internal_approve() != 0) { + total_size += 2; + } + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void ReviewRequest::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + ::google::protobuf::Arena* arena = _this->GetArena(); + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment.v1.ReviewRequest) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000003fU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!from._internal_experiment_id().empty()) { + _this->_internal_set_experiment_id(from._internal_experiment_id()); + } else { + if (_this->_impl_.experiment_id_.IsDefault()) { + _this->_internal_set_experiment_id(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!from._internal_reason().empty()) { + _this->_internal_set_reason(from._internal_reason()); + } else { + if (_this->_impl_.reason_.IsDefault()) { + _this->_internal_set_reason(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (!from._internal_request_id().empty()) { + _this->_internal_set_request_id(from._internal_request_id()); + } else { + if (_this->_impl_.request_id_.IsDefault()) { + _this->_internal_set_request_id(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + ABSL_DCHECK(from._impl_.evidence_ != nullptr); + if (_this->_impl_.evidence_ == nullptr) { + _this->_impl_.evidence_ = Super_::CopyConstruct(arena, *from._impl_.evidence_); + } else { + _this->_impl_.evidence_->MergeFrom(*from._impl_.evidence_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (from._internal_expected_revision() != 0) { + _this->_impl_.expected_revision_ = from._impl_.expected_revision_; + } + } + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (from._internal_approve() != 0) { + _this->_impl_.approve_ = from._impl_.approve_; + } + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void ReviewRequest::CopyFrom(const ReviewRequest& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment.v1.ReviewRequest) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void ReviewRequest::InternalSwap(ReviewRequest* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + auto* arena = GetArena(); + ABSL_DCHECK_EQ(arena, other->GetArena()); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.experiment_id_, &other->_impl_.experiment_id_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.reason_, &other->_impl_.reason_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.request_id_, &other->_impl_.request_id_, arena); + ::google::protobuf::internal::memswap< + PROTOBUF_FIELD_OFFSET(ReviewRequest, _impl_.approve_) + + sizeof(ReviewRequest::_impl_.approve_) + - PROTOBUF_FIELD_OFFSET(ReviewRequest, _impl_.evidence_)>( + reinterpret_cast(&_impl_.evidence_), + reinterpret_cast(&other->_impl_.evidence_)); +} + +::google::protobuf::Metadata ReviewRequest::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +void ReviewResponse::clear_experiment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.experiment_ != nullptr) _impl_.experiment_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +ReviewResponse::ReviewResponse(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, ReviewResponse_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment.v1.ReviewResponse) +} +PROTOBUF_NDEBUG_INLINE ReviewResponse::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment::v1::ReviewResponse& from_msg) + : _has_bits_{from._has_bits_} {} + +ReviewResponse::ReviewResponse( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const ReviewResponse& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, ReviewResponse_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + ReviewResponse* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + ::uint32_t cached_has_bits = _impl_._has_bits_[0]; + _impl_.experiment_ = (CheckHasBit(cached_has_bits, 0x00000001U)) + ? Super_::CopyConstruct(arena, *from._impl_.experiment_) + : nullptr; + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment.v1.ReviewResponse) +} +PROTOBUF_NDEBUG_INLINE ReviewResponse::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + {} + +inline void ReviewResponse::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + _impl_.experiment_ = {}; +} +ReviewResponse::~ReviewResponse() { + // @@protoc_insertion_point(destructor:xtcp_treatment.v1.ReviewResponse) + SharedDtor(*this); +} +inline void ReviewResponse::SharedDtor(MessageLite& self) { + ReviewResponse& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + delete this_._impl_.experiment_; + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull ReviewResponse_class_data_ = + ReviewResponse::InternalGenerateClassData_(ReviewResponse_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +ReviewResponse::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&ReviewResponse_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(ReviewResponse_class_data_.tc_table); + return ReviewResponse_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +ReviewResponse::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&ReviewResponse_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&ReviewResponse_globals_)); + return ReviewResponse_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const ReviewResponse::ParseTableT_ + ReviewResponse::_table_ = + ReviewResponse::InternalGenerateParseTable_(ReviewResponse_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void ReviewResponse::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment.v1.ReviewResponse) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + ABSL_DCHECK(this_._impl_.experiment_ != nullptr); + this_._impl_.experiment_->Clear(); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL ReviewResponse::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const ReviewResponse& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL ReviewResponse::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const ReviewResponse& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment.v1.ReviewResponse) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // .xtcp_treatment.v1.Experiment experiment = 1 [json_name = "experiment"]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 1, *this_._impl_.experiment_, this_._impl_.experiment_->GetCachedSize(), target, + stream); + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment.v1.ReviewResponse) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t ReviewResponse::ByteSizeLong(const MessageLite& base) { + const ReviewResponse& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t ReviewResponse::ByteSizeLong() const { + const ReviewResponse& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment.v1.ReviewResponse) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + { + // .xtcp_treatment.v1.Experiment experiment = 1 [json_name = "experiment"]; + cached_has_bits = this_._impl_._has_bits_[0]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.experiment_); + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void ReviewResponse::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + ::google::protobuf::Arena* arena = _this->GetArena(); + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment.v1.ReviewResponse) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + ABSL_DCHECK(from._impl_.experiment_ != nullptr); + if (_this->_impl_.experiment_ == nullptr) { + _this->_impl_.experiment_ = Super_::CopyConstruct(arena, *from._impl_.experiment_); + } else { + _this->_impl_.experiment_->MergeFrom(*from._impl_.experiment_); + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void ReviewResponse::CopyFrom(const ReviewResponse& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment.v1.ReviewResponse) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void ReviewResponse::InternalSwap(ReviewResponse* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + swap(_impl_.experiment_, other->_impl_.experiment_); +} + +::google::protobuf::Metadata ReviewResponse::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +ControlRequest::ControlRequest(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, ControlRequest_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment.v1.ControlRequest) +} +PROTOBUF_NDEBUG_INLINE ControlRequest::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment::v1::ControlRequest& from_msg) + : _has_bits_{from._has_bits_}, + experiment_id_(arena, from.experiment_id_), + agent_id_(arena, from.agent_id_), + location_(arena, from.location_), + reason_(arena, from.reason_), + request_id_(arena, from.request_id_) {} + +ControlRequest::ControlRequest( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const ControlRequest& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, ControlRequest_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + ControlRequest* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + ::memcpy(reinterpret_cast(&_impl_) + + offsetof(Impl_, fleet_), + reinterpret_cast(&from._impl_) + + offsetof(Impl_, fleet_), + offsetof(Impl_, action_) - + offsetof(Impl_, fleet_) + + sizeof(Impl_::action_)); + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment.v1.ControlRequest) +} +PROTOBUF_NDEBUG_INLINE ControlRequest::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + : experiment_id_(arena), + agent_id_(arena), + location_(arena), + reason_(arena), + request_id_(arena) {} + +inline void ControlRequest::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + ::memset(reinterpret_cast(&_impl_) + + offsetof(Impl_, fleet_), + 0, + offsetof(Impl_, action_) - + offsetof(Impl_, fleet_) + + sizeof(Impl_::action_)); +} +ControlRequest::~ControlRequest() { + // @@protoc_insertion_point(destructor:xtcp_treatment.v1.ControlRequest) + SharedDtor(*this); +} +inline void ControlRequest::SharedDtor(MessageLite& self) { + ControlRequest& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + this_._impl_.experiment_id_.Destroy(); + this_._impl_.agent_id_.Destroy(); + this_._impl_.location_.Destroy(); + this_._impl_.reason_.Destroy(); + this_._impl_.request_id_.Destroy(); + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull ControlRequest_class_data_ = + ControlRequest::InternalGenerateClassData_(ControlRequest_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +ControlRequest::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&ControlRequest_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(ControlRequest_class_data_.tc_table); + return ControlRequest_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +ControlRequest::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&ControlRequest_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&ControlRequest_globals_)); + return ControlRequest_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const ControlRequest::ParseTableT_ + ControlRequest::_table_ = + ControlRequest::InternalGenerateParseTable_(ControlRequest_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void ControlRequest::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment.v1.ControlRequest) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000001fU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + this_._impl_.experiment_id_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + this_._impl_.agent_id_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + this_._impl_.location_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + this_._impl_.reason_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + this_._impl_.request_id_.ClearNonDefaultToEmpty(); + } + } + if (BatchCheckHasBit(cached_has_bits, 0x00000060U)) { + ::memset(&this_._impl_.fleet_, 0, + static_cast<::size_t>( + reinterpret_cast(&this_._impl_.action_) - + reinterpret_cast(&this_._impl_.fleet_)) + + sizeof(_impl_.action_)); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL ControlRequest::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const ControlRequest& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL ControlRequest::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const ControlRequest& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment.v1.ControlRequest) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // string experiment_id = 1 [json_name = "experimentId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_experiment_id().empty()) { + const ::std::string& _s = this_._internal_experiment_id(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.ControlRequest.experiment_id"); + target = stream->WriteStringMaybeAliased(1, _s, target); + } + } + + // string agent_id = 2 [json_name = "agentId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!this_._internal_agent_id().empty()) { + const ::std::string& _s = this_._internal_agent_id(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.ControlRequest.agent_id"); + target = stream->WriteStringMaybeAliased(2, _s, target); + } + } + + // string location = 3 [json_name = "location", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (!this_._internal_location().empty()) { + const ::std::string& _s = this_._internal_location(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.ControlRequest.location"); + target = stream->WriteStringMaybeAliased(3, _s, target); + } + } + + // bool fleet = 4 [json_name = "fleet"]; + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (this_._internal_fleet() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray( + 4, this_._internal_fleet(), target); + } + } + + // .xtcp_treatment.v1.ControlAction action = 5 [json_name = "action", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + if (this_._internal_action() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 5, this_._internal_action(), target); + } + } + + // string reason = 6 [json_name = "reason", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (!this_._internal_reason().empty()) { + const ::std::string& _s = this_._internal_reason(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.ControlRequest.reason"); + target = stream->WriteStringMaybeAliased(6, _s, target); + } + } + + // string request_id = 7 [json_name = "requestId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (!this_._internal_request_id().empty()) { + const ::std::string& _s = this_._internal_request_id(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.ControlRequest.request_id"); + target = stream->WriteStringMaybeAliased(7, _s, target); + } + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment.v1.ControlRequest) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t ControlRequest::ByteSizeLong(const MessageLite& base) { + const ControlRequest& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t ControlRequest::ByteSizeLong() const { + const ControlRequest& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment.v1.ControlRequest) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + ::_pbi::Prefetch5LinesFrom7Lines(&this_); + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000007fU)) { + // string experiment_id = 1 [json_name = "experimentId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_experiment_id().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_experiment_id()); + } + } + // string agent_id = 2 [json_name = "agentId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!this_._internal_agent_id().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_agent_id()); + } + } + // string location = 3 [json_name = "location", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (!this_._internal_location().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_location()); + } + } + // string reason = 6 [json_name = "reason", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (!this_._internal_reason().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_reason()); + } + } + // string request_id = 7 [json_name = "requestId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (!this_._internal_request_id().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_request_id()); + } + } + // bool fleet = 4 [json_name = "fleet"]; + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (this_._internal_fleet() != 0) { + total_size += 2; + } + } + // .xtcp_treatment.v1.ControlAction action = 5 [json_name = "action", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + if (this_._internal_action() != 0) { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this_._internal_action()); + } + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void ControlRequest::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment.v1.ControlRequest) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000007fU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!from._internal_experiment_id().empty()) { + _this->_internal_set_experiment_id(from._internal_experiment_id()); + } else { + if (_this->_impl_.experiment_id_.IsDefault()) { + _this->_internal_set_experiment_id(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!from._internal_agent_id().empty()) { + _this->_internal_set_agent_id(from._internal_agent_id()); + } else { + if (_this->_impl_.agent_id_.IsDefault()) { + _this->_internal_set_agent_id(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (!from._internal_location().empty()) { + _this->_internal_set_location(from._internal_location()); + } else { + if (_this->_impl_.location_.IsDefault()) { + _this->_internal_set_location(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (!from._internal_reason().empty()) { + _this->_internal_set_reason(from._internal_reason()); + } else { + if (_this->_impl_.reason_.IsDefault()) { + _this->_internal_set_reason(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (!from._internal_request_id().empty()) { + _this->_internal_set_request_id(from._internal_request_id()); + } else { + if (_this->_impl_.request_id_.IsDefault()) { + _this->_internal_set_request_id(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (from._internal_fleet() != 0) { + _this->_impl_.fleet_ = from._impl_.fleet_; + } + } + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + if (from._internal_action() != 0) { + _this->_impl_.action_ = from._impl_.action_; + } + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void ControlRequest::CopyFrom(const ControlRequest& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment.v1.ControlRequest) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void ControlRequest::InternalSwap(ControlRequest* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + auto* arena = GetArena(); + ABSL_DCHECK_EQ(arena, other->GetArena()); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.experiment_id_, &other->_impl_.experiment_id_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.agent_id_, &other->_impl_.agent_id_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.location_, &other->_impl_.location_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.reason_, &other->_impl_.reason_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.request_id_, &other->_impl_.request_id_, arena); + ::google::protobuf::internal::memswap< + PROTOBUF_FIELD_OFFSET(ControlRequest, _impl_.action_) + + sizeof(ControlRequest::_impl_.action_) + - PROTOBUF_FIELD_OFFSET(ControlRequest, _impl_.fleet_)>( + reinterpret_cast(&_impl_.fleet_), + reinterpret_cast(&other->_impl_.fleet_)); +} + +::google::protobuf::Metadata ControlRequest::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +ControlResponse::ControlResponse(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, ControlResponse_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment.v1.ControlResponse) +} +ControlResponse::ControlResponse( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const ControlResponse& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, ControlResponse_get_class_data()), +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena), +#endif // PROTOBUF_CUSTOM_VTABLE + _impl_(from._impl_) { + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} +PROTOBUF_NDEBUG_INLINE ControlResponse::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + {} + +inline void ControlResponse::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + _impl_.affected_agents_ = {}; +} +ControlResponse::~ControlResponse() { + // @@protoc_insertion_point(destructor:xtcp_treatment.v1.ControlResponse) + SharedDtor(*this); +} +inline void ControlResponse::SharedDtor(MessageLite& self) { + ControlResponse& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull ControlResponse_class_data_ = + ControlResponse::InternalGenerateClassData_(ControlResponse_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +ControlResponse::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&ControlResponse_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(ControlResponse_class_data_.tc_table); + return ControlResponse_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +ControlResponse::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&ControlResponse_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&ControlResponse_globals_)); + return ControlResponse_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const ControlResponse::ParseTableT_ + ControlResponse::_table_ = + ControlResponse::InternalGenerateParseTable_(ControlResponse_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void ControlResponse::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment.v1.ControlResponse) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + this_._impl_.affected_agents_ = 0u; + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL ControlResponse::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const ControlResponse& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL ControlResponse::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const ControlResponse& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment.v1.ControlResponse) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // uint32 affected_agents = 1 [json_name = "affectedAgents"]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (this_._internal_affected_agents() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray( + 1, this_._internal_affected_agents(), target); + } + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment.v1.ControlResponse) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t ControlResponse::ByteSizeLong(const MessageLite& base) { + const ControlResponse& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t ControlResponse::ByteSizeLong() const { + const ControlResponse& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment.v1.ControlResponse) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + { + // uint32 affected_agents = 1 [json_name = "affectedAgents"]; + cached_has_bits = this_._impl_._has_bits_[0]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (this_._internal_affected_agents() != 0) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne( + this_._internal_affected_agents()); + } + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void ControlResponse::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment.v1.ControlResponse) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (from._internal_affected_agents() != 0) { + _this->_impl_.affected_agents_ = from._impl_.affected_agents_; + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void ControlResponse::CopyFrom(const ControlResponse& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment.v1.ControlResponse) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void ControlResponse::InternalSwap(ControlResponse* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + swap(_impl_.affected_agents_, other->_impl_.affected_agents_); +} + +::google::protobuf::Metadata ControlResponse::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +InspectRequest::InspectRequest(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, InspectRequest_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment.v1.InspectRequest) +} +PROTOBUF_NDEBUG_INLINE InspectRequest::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment::v1::InspectRequest& from_msg) + : _has_bits_{from._has_bits_}, + experiment_id_(arena, from.experiment_id_), + agent_id_(arena, from.agent_id_) {} + +InspectRequest::InspectRequest( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const InspectRequest& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, InspectRequest_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + InspectRequest* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment.v1.InspectRequest) +} +PROTOBUF_NDEBUG_INLINE InspectRequest::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + : experiment_id_(arena), + agent_id_(arena) {} + +inline void InspectRequest::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); +} +InspectRequest::~InspectRequest() { + // @@protoc_insertion_point(destructor:xtcp_treatment.v1.InspectRequest) + SharedDtor(*this); +} +inline void InspectRequest::SharedDtor(MessageLite& self) { + InspectRequest& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + this_._impl_.experiment_id_.Destroy(); + this_._impl_.agent_id_.Destroy(); + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull InspectRequest_class_data_ = + InspectRequest::InternalGenerateClassData_(InspectRequest_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +InspectRequest::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&InspectRequest_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(InspectRequest_class_data_.tc_table); + return InspectRequest_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +InspectRequest::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&InspectRequest_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&InspectRequest_globals_)); + return InspectRequest_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const InspectRequest::ParseTableT_ + InspectRequest::_table_ = + InspectRequest::InternalGenerateParseTable_(InspectRequest_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void InspectRequest::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment.v1.InspectRequest) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x00000003U)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + this_._impl_.experiment_id_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + this_._impl_.agent_id_.ClearNonDefaultToEmpty(); + } + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL InspectRequest::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const InspectRequest& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL InspectRequest::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const InspectRequest& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment.v1.InspectRequest) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // string experiment_id = 1 [json_name = "experimentId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_experiment_id().empty()) { + const ::std::string& _s = this_._internal_experiment_id(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.InspectRequest.experiment_id"); + target = stream->WriteStringMaybeAliased(1, _s, target); + } + } + + // string agent_id = 2 [json_name = "agentId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!this_._internal_agent_id().empty()) { + const ::std::string& _s = this_._internal_agent_id(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.InspectRequest.agent_id"); + target = stream->WriteStringMaybeAliased(2, _s, target); + } + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment.v1.InspectRequest) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t InspectRequest::ByteSizeLong(const MessageLite& base) { + const InspectRequest& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t InspectRequest::ByteSizeLong() const { + const InspectRequest& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment.v1.InspectRequest) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + ::_pbi::Prefetch5LinesFrom7Lines(&this_); + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x00000003U)) { + // string experiment_id = 1 [json_name = "experimentId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_experiment_id().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_experiment_id()); + } + } + // string agent_id = 2 [json_name = "agentId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!this_._internal_agent_id().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_agent_id()); + } + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void InspectRequest::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment.v1.InspectRequest) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x00000003U)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!from._internal_experiment_id().empty()) { + _this->_internal_set_experiment_id(from._internal_experiment_id()); + } else { + if (_this->_impl_.experiment_id_.IsDefault()) { + _this->_internal_set_experiment_id(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!from._internal_agent_id().empty()) { + _this->_internal_set_agent_id(from._internal_agent_id()); + } else { + if (_this->_impl_.agent_id_.IsDefault()) { + _this->_internal_set_agent_id(""); + } + } + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void InspectRequest::CopyFrom(const InspectRequest& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment.v1.InspectRequest) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void InspectRequest::InternalSwap(InspectRequest* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + auto* arena = GetArena(); + ABSL_DCHECK_EQ(arena, other->GetArena()); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.experiment_id_, &other->_impl_.experiment_id_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.agent_id_, &other->_impl_.agent_id_, arena); +} + +::google::protobuf::Metadata InspectRequest::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +void InspectResponse::clear_experiments() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.experiments_.Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +void InspectResponse::clear_desired() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.desired_.Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); +} +void InspectResponse::clear_observed() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.observed_.Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); +} +InspectResponse::InspectResponse(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, InspectResponse_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment.v1.InspectResponse) +} +PROTOBUF_NDEBUG_INLINE InspectResponse::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment::v1::InspectResponse& from_msg) + : _has_bits_{from._has_bits_}, + experiments_ { + visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment::v1::InspectResponse, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::InspectResponse, _impl_.experiments_)>() + , arena, from.experiments_ + } + , + desired_ { + visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment::v1::InspectResponse, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::InspectResponse, _impl_.desired_)>() + , arena, from.desired_ + } + , + observed_ { + visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment::v1::InspectResponse, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::InspectResponse, _impl_.observed_)>() + , arena, from.observed_ + } + {} + +InspectResponse::InspectResponse( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const InspectResponse& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, InspectResponse_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + InspectResponse* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment.v1.InspectResponse) +} +PROTOBUF_NDEBUG_INLINE InspectResponse::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + : experiments_ { visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment::v1::InspectResponse, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::InspectResponse, _impl_.experiments_)>() + } + , + desired_ { visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment::v1::InspectResponse, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::InspectResponse, _impl_.desired_)>() + } + , + observed_ { visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment::v1::InspectResponse, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::InspectResponse, _impl_.observed_)>() + } + {} + +inline void InspectResponse::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); +} +InspectResponse::~InspectResponse() { + // @@protoc_insertion_point(destructor:xtcp_treatment.v1.InspectResponse) + SharedDtor(*this); +} +inline void InspectResponse::SharedDtor(MessageLite& self) { + InspectResponse& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull InspectResponse_class_data_ = + InspectResponse::InternalGenerateClassData_(InspectResponse_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +InspectResponse::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&InspectResponse_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(InspectResponse_class_data_.tc_table); + return InspectResponse_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +InspectResponse::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&InspectResponse_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&InspectResponse_globals_)); + return InspectResponse_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const InspectResponse::ParseTableT_ + InspectResponse::_table_ = + InspectResponse::InternalGenerateParseTable_(InspectResponse_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void InspectResponse::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment.v1.InspectResponse) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x00000007U)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + _impl_.experiments_.Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + _impl_.desired_.Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + _impl_.observed_.Clear(); + } + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL InspectResponse::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const InspectResponse& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL InspectResponse::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const InspectResponse& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment.v1.InspectResponse) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // repeated .xtcp_treatment.v1.Experiment experiments = 1 [json_name = "experiments"]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + for (unsigned i = 0, n = static_cast( + this_._internal_experiments_size()); + i < n; i++) { + const auto& repfield = this_._internal_experiments().Get(i); + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 1, repfield, repfield.GetCachedSize(), target, + stream); + } + } + + // repeated .xtcp_treatment.v1.DesiredState desired = 2 [json_name = "desired"]; + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + for (unsigned i = 0, n = static_cast( + this_._internal_desired_size()); + i < n; i++) { + const auto& repfield = this_._internal_desired().Get(i); + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 2, repfield, repfield.GetCachedSize(), target, + stream); + } + } + + // repeated .xtcp_treatment.v1.CheckInRequest observed = 3 [json_name = "observed"]; + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + for (unsigned i = 0, n = static_cast( + this_._internal_observed_size()); + i < n; i++) { + const auto& repfield = this_._internal_observed().Get(i); + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 3, repfield, repfield.GetCachedSize(), target, + stream); + } + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment.v1.InspectResponse) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t InspectResponse::ByteSizeLong(const MessageLite& base) { + const InspectResponse& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t InspectResponse::ByteSizeLong() const { + const InspectResponse& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment.v1.InspectResponse) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + ::_pbi::Prefetch5LinesFrom7Lines(&this_); + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x00000007U)) { + // repeated .xtcp_treatment.v1.Experiment experiments = 1 [json_name = "experiments"]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + total_size += 1UL * this_._internal_experiments_size(); + for (const auto& msg : this_._internal_experiments()) { + total_size += ::google::protobuf::internal::WireFormatLite::MessageSize(msg); + } + } + // repeated .xtcp_treatment.v1.DesiredState desired = 2 [json_name = "desired"]; + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + total_size += 1UL * this_._internal_desired_size(); + for (const auto& msg : this_._internal_desired()) { + total_size += ::google::protobuf::internal::WireFormatLite::MessageSize(msg); + } + } + // repeated .xtcp_treatment.v1.CheckInRequest observed = 3 [json_name = "observed"]; + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + total_size += 1UL * this_._internal_observed_size(); + for (const auto& msg : this_._internal_observed()) { + total_size += ::google::protobuf::internal::WireFormatLite::MessageSize(msg); + } + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void InspectResponse::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + ::google::protobuf::Arena* arena = _this->GetArena(); + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment.v1.InspectResponse) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x00000007U)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + _this->_internal_mutable_experiments()->InternalMergeFromWithArena( + ::google::protobuf::MessageLite::internal_visibility(), arena, + from._internal_experiments()); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + _this->_internal_mutable_desired()->InternalMergeFromWithArena( + ::google::protobuf::MessageLite::internal_visibility(), arena, + from._internal_desired()); + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + _this->_internal_mutable_observed()->InternalMergeFromWithArena( + ::google::protobuf::MessageLite::internal_visibility(), arena, + from._internal_observed()); + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void InspectResponse::CopyFrom(const InspectResponse& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment.v1.InspectResponse) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void InspectResponse::InternalSwap(InspectResponse* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + _impl_.experiments_.InternalSwap(&other->_impl_.experiments_); + _impl_.desired_.InternalSwap(&other->_impl_.desired_); + _impl_.observed_.InternalSwap(&other->_impl_.observed_); +} + +::google::protobuf::Metadata InspectResponse::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// @@protoc_insertion_point(namespace_scope) +} // namespace v1 +} // namespace xtcp_treatment +namespace google { +namespace protobuf { +} // namespace protobuf +} // namespace google +// @@protoc_insertion_point(global_scope) +PROTOBUF_ATTRIBUTE_INIT_PRIORITY2 static ::std::false_type + _static_init2_ [[maybe_unused]] = + (::_pbi::AddDescriptors(&descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2eproto), + ::std::false_type{}); +#include "google/protobuf/port_undef.inc" diff --git a/gen/cpp/xtcp_treatment/v1/operator.pb.h b/gen/cpp/xtcp_treatment/v1/operator.pb.h new file mode 100644 index 000000000..645536689 --- /dev/null +++ b/gen/cpp/xtcp_treatment/v1/operator.pb.h @@ -0,0 +1,4202 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE +// source: xtcp_treatment/v1/operator.proto +// Protobuf C++ Version: 7.36.2 + +#ifndef xtcp_5ftreatment_2fv1_2foperator_2eproto_2epb_2eh +#define xtcp_5ftreatment_2fv1_2foperator_2eproto_2epb_2eh + +#include +#include +#include +#include + +// clang-format off +#include "google/protobuf/runtime_version.h" +#if PROTOBUF_VERSION != 7036002 +#error "Protobuf C++ gencode is built with an incompatible version of" +#error "Protobuf C++ headers/runtime. See" +#error "https://protobuf.dev/support/cross-version-runtime-guarantee/#cpp" +#endif +#include "google/protobuf/io/coded_stream.h" +#include "google/protobuf/arena.h" +#include "google/protobuf/arenastring.h" +#include "google/protobuf/generated_message_tctable_decl.h" +#include "google/protobuf/generated_message_util.h" +#include "google/protobuf/metadata_lite.h" +#include "google/protobuf/generated_message_reflection.h" +#include "google/protobuf/message.h" +#include "google/protobuf/message_lite.h" +#include "google/protobuf/repeated_field.h" // IWYU pragma: export +#include "google/protobuf/extension_set.h" // IWYU pragma: export +#include "google/protobuf/generated_enum_reflection.h" +#include "google/protobuf/unknown_field_set.h" +#include "buf/validate/validate.pb.h" +#include "xtcp_treatment/v1/agent.pb.h" +#include "xtcp_treatment/v1/treatment.pb.h" +// @@protoc_insertion_point(includes) + +// Must be included last. +#include "google/protobuf/port_def.inc" + +#define PROTOBUF_INTERNAL_EXPORT_xtcp_5ftreatment_2fv1_2foperator_2eproto + +// Internal implementation detail -- do not use these members. +struct TableStruct_xtcp_5ftreatment_2fv1_2foperator_2eproto { + static const ::uint32_t offsets[]; +}; +extern "C" { +extern const ::google::protobuf::internal::DescriptorTable descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2eproto; +} // extern "C" +namespace xtcp_treatment { +namespace v1 { +enum ControlAction : int; +extern const uint32_t ControlAction_internal_data_[]; +class AnalyzeRequest; +struct AnalyzeRequestGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern AnalyzeRequestGlobalsTypeInternal AnalyzeRequest_globals_; +extern const ::google::protobuf::internal::ClassDataFull AnalyzeRequest_class_data_; +#else +extern const AnalyzeRequestGlobalsTypeInternal AnalyzeRequest_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +class AnalyzeResponse; +struct AnalyzeResponseGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern AnalyzeResponseGlobalsTypeInternal AnalyzeResponse_globals_; +extern const ::google::protobuf::internal::ClassDataFull AnalyzeResponse_class_data_; +#else +extern const AnalyzeResponseGlobalsTypeInternal AnalyzeResponse_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +class ControlRequest; +struct ControlRequestGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern ControlRequestGlobalsTypeInternal ControlRequest_globals_; +extern const ::google::protobuf::internal::ClassDataFull ControlRequest_class_data_; +#else +extern const ControlRequestGlobalsTypeInternal ControlRequest_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +class ControlResponse; +struct ControlResponseGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern ControlResponseGlobalsTypeInternal ControlResponse_globals_; +extern const ::google::protobuf::internal::ClassDataFull ControlResponse_class_data_; +#else +extern const ControlResponseGlobalsTypeInternal ControlResponse_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +class InspectRequest; +struct InspectRequestGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern InspectRequestGlobalsTypeInternal InspectRequest_globals_; +extern const ::google::protobuf::internal::ClassDataFull InspectRequest_class_data_; +#else +extern const InspectRequestGlobalsTypeInternal InspectRequest_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +class InspectResponse; +struct InspectResponseGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern InspectResponseGlobalsTypeInternal InspectResponse_globals_; +extern const ::google::protobuf::internal::ClassDataFull InspectResponse_class_data_; +#else +extern const InspectResponseGlobalsTypeInternal InspectResponse_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +class ProposeRequest; +struct ProposeRequestGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern ProposeRequestGlobalsTypeInternal ProposeRequest_globals_; +extern const ::google::protobuf::internal::ClassDataFull ProposeRequest_class_data_; +#else +extern const ProposeRequestGlobalsTypeInternal ProposeRequest_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +class ProposeResponse; +struct ProposeResponseGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern ProposeResponseGlobalsTypeInternal ProposeResponse_globals_; +extern const ::google::protobuf::internal::ClassDataFull ProposeResponse_class_data_; +#else +extern const ProposeResponseGlobalsTypeInternal ProposeResponse_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +class ReviewRequest; +struct ReviewRequestGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern ReviewRequestGlobalsTypeInternal ReviewRequest_globals_; +extern const ::google::protobuf::internal::ClassDataFull ReviewRequest_class_data_; +#else +extern const ReviewRequestGlobalsTypeInternal ReviewRequest_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +class ReviewResponse; +struct ReviewResponseGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern ReviewResponseGlobalsTypeInternal ReviewResponse_globals_; +extern const ::google::protobuf::internal::ClassDataFull ReviewResponse_class_data_; +#else +extern const ReviewResponseGlobalsTypeInternal ReviewResponse_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +} // namespace v1 +} // namespace xtcp_treatment +namespace google { +namespace protobuf { +template <> +internal::EnumTraitsT<::xtcp_treatment::v1::ControlAction_internal_data_> + internal::EnumTraitsImpl::value<::xtcp_treatment::v1::ControlAction>; +} // namespace protobuf +} // namespace google + +namespace xtcp_treatment { +namespace v1 { +enum ControlAction : int { + CONTROL_ACTION_UNSPECIFIED = 0, + CONTROL_ACTION_PAUSE = 1, + CONTROL_ACTION_SUSPEND = 2, + CONTROL_ACTION_RESUME = 3, + CONTROL_ACTION_ROLLBACK = 4, + CONTROL_ACTION_RETIRE = 5, + ControlAction_INT_MIN_SENTINEL_DO_NOT_USE_ = + ::std::numeric_limits<::int32_t>::min(), + ControlAction_INT_MAX_SENTINEL_DO_NOT_USE_ = + ::std::numeric_limits<::int32_t>::max(), +}; + +extern const uint32_t ControlAction_internal_data_[]; +inline constexpr ControlAction ControlAction_MIN = + static_cast(0); +inline constexpr ControlAction ControlAction_MAX = + static_cast(5); +[[nodiscard]] inline bool ControlAction_IsValid(int value) { + return 0 <= value && value <= 5; +} +inline constexpr int ControlAction_ARRAYSIZE = 5 + 1; +[[nodiscard]] const ::google::protobuf::EnumDescriptor* PROTOBUF_NONNULL +ControlAction_descriptor(); +[[nodiscard]] inline auto ProtobufInternalGetEnumDescriptor(ControlAction) { + return ControlAction_descriptor(); +} +template +[[nodiscard]] const ::std::string& ControlAction_Name(T value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to ControlAction_Name()."); + return ControlAction_Name(static_cast(value)); +} +template <> +[[nodiscard]] inline const ::std::string& ControlAction_Name(ControlAction value) { + return ::google::protobuf::internal::NameOfDenseEnum( + static_cast(value)); +} +[[nodiscard]] inline bool ControlAction_Parse( + ::absl::string_view name, ControlAction* PROTOBUF_NONNULL value) { + return ::google::protobuf::internal::ParseNamedEnum(ControlAction_descriptor(), name, + value); +} +using ::google::protobuf::internal::generated_enum::AbslParseFlag; +using ::google::protobuf::internal::generated_enum::AbslUnparseFlag; + +// =================================================================== + + +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED InspectRequest final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment.v1.InspectRequest) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline InspectRequest() : InspectRequest(nullptr) {} + ~InspectRequest() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(InspectRequest* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(InspectRequest)); + } +#endif + + template + explicit constexpr InspectRequest(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline InspectRequest(const InspectRequest& from) : InspectRequest(nullptr, from) {} + inline InspectRequest(InspectRequest&& from) noexcept : InspectRequest(nullptr, ::std::move(from)) {} + inline InspectRequest& operator=(const InspectRequest& from) { + CopyFrom(from); + return *this; + } + inline InspectRequest& operator=(InspectRequest&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const InspectRequest& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&InspectRequest_globals_); + } + static constexpr int kIndexInFileMessages = 8; + friend void swap(InspectRequest& a, InspectRequest& b) { a.Swap(&b); } + inline void Swap(InspectRequest* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(InspectRequest* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] InspectRequest* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const InspectRequest& from); + using Super_::MergeFrom; + void MergeFrom(const InspectRequest& from) { InspectRequest::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(InspectRequest* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment.v1.InspectRequest"; } + + explicit InspectRequest(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + InspectRequest(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const InspectRequest& from); + InspectRequest( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, InspectRequest&& from) noexcept + : InspectRequest(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kExperimentIdFieldNumber = 1, + kAgentIdFieldNumber = 2, + }; + // string experiment_id = 1 [json_name = "experimentId", (.buf.validate.field) = { + void clear_experiment_id() ; + [[nodiscard]] const ::std::string& experiment_id() const; + template + void set_experiment_id(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_experiment_id(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_experiment_id(); + void set_allocated_experiment_id(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_experiment_id() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_experiment_id(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_experiment_id(); + + public: + // string agent_id = 2 [json_name = "agentId", (.buf.validate.field) = { + void clear_agent_id() ; + [[nodiscard]] const ::std::string& agent_id() const; + template + void set_agent_id(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_agent_id(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_agent_id(); + void set_allocated_agent_id(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_agent_id() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_agent_id(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_agent_id(); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment.v1.InspectRequest) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<1, 2, + 0, 62, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const InspectRequest& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::google::protobuf::internal::ArenaStringPtr experiment_id_; + ::google::protobuf::internal::ArenaStringPtr agent_id_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_2fv1_2foperator_2eproto; +}; +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED ControlResponse final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment.v1.ControlResponse) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline ControlResponse() : ControlResponse(nullptr) {} + ~ControlResponse() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(ControlResponse* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(ControlResponse)); + } +#endif + + template + explicit constexpr ControlResponse(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline ControlResponse(const ControlResponse& from) : ControlResponse(nullptr, from) {} + inline ControlResponse(ControlResponse&& from) noexcept : ControlResponse(nullptr, ::std::move(from)) {} + inline ControlResponse& operator=(const ControlResponse& from) { + CopyFrom(from); + return *this; + } + inline ControlResponse& operator=(ControlResponse&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const ControlResponse& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&ControlResponse_globals_); + } + static constexpr int kIndexInFileMessages = 7; + friend void swap(ControlResponse& a, ControlResponse& b) { a.Swap(&b); } + inline void Swap(ControlResponse* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ControlResponse* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] ControlResponse* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const ControlResponse& from); + using Super_::MergeFrom; + void MergeFrom(const ControlResponse& from) { ControlResponse::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(ControlResponse* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment.v1.ControlResponse"; } + + explicit ControlResponse(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + ControlResponse(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const ControlResponse& from); + ControlResponse( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, ControlResponse&& from) noexcept + : ControlResponse(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kAffectedAgentsFieldNumber = 1, + }; + // uint32 affected_agents = 1 [json_name = "affectedAgents"]; + void clear_affected_agents() ; + [[nodiscard]] ::uint32_t affected_agents() const; + void set_affected_agents(::uint32_t value); + + private: + ::uint32_t _internal_affected_agents() const; + void _internal_set_affected_agents(::uint32_t value); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment.v1.ControlResponse) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<0, 1, + 0, 0, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const ControlResponse& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::uint32_t affected_agents_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_2fv1_2foperator_2eproto; +}; +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED ControlRequest final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment.v1.ControlRequest) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline ControlRequest() : ControlRequest(nullptr) {} + ~ControlRequest() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(ControlRequest* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(ControlRequest)); + } +#endif + + template + explicit constexpr ControlRequest(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline ControlRequest(const ControlRequest& from) : ControlRequest(nullptr, from) {} + inline ControlRequest(ControlRequest&& from) noexcept : ControlRequest(nullptr, ::std::move(from)) {} + inline ControlRequest& operator=(const ControlRequest& from) { + CopyFrom(from); + return *this; + } + inline ControlRequest& operator=(ControlRequest&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const ControlRequest& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&ControlRequest_globals_); + } + static constexpr int kIndexInFileMessages = 6; + friend void swap(ControlRequest& a, ControlRequest& b) { a.Swap(&b); } + inline void Swap(ControlRequest* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ControlRequest* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] ControlRequest* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const ControlRequest& from); + using Super_::MergeFrom; + void MergeFrom(const ControlRequest& from) { ControlRequest::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(ControlRequest* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment.v1.ControlRequest"; } + + explicit ControlRequest(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + ControlRequest(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const ControlRequest& from); + ControlRequest( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, ControlRequest&& from) noexcept + : ControlRequest(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kExperimentIdFieldNumber = 1, + kAgentIdFieldNumber = 2, + kLocationFieldNumber = 3, + kReasonFieldNumber = 6, + kRequestIdFieldNumber = 7, + kFleetFieldNumber = 4, + kActionFieldNumber = 5, + }; + // string experiment_id = 1 [json_name = "experimentId", (.buf.validate.field) = { + void clear_experiment_id() ; + [[nodiscard]] const ::std::string& experiment_id() const; + template + void set_experiment_id(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_experiment_id(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_experiment_id(); + void set_allocated_experiment_id(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_experiment_id() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_experiment_id(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_experiment_id(); + + public: + // string agent_id = 2 [json_name = "agentId", (.buf.validate.field) = { + void clear_agent_id() ; + [[nodiscard]] const ::std::string& agent_id() const; + template + void set_agent_id(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_agent_id(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_agent_id(); + void set_allocated_agent_id(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_agent_id() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_agent_id(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_agent_id(); + + public: + // string location = 3 [json_name = "location", (.buf.validate.field) = { + void clear_location() ; + [[nodiscard]] const ::std::string& location() const; + template + void set_location(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_location(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_location(); + void set_allocated_location(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_location() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_location(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_location(); + + public: + // string reason = 6 [json_name = "reason", (.buf.validate.field) = { + void clear_reason() ; + [[nodiscard]] const ::std::string& reason() const; + template + void set_reason(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_reason(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_reason(); + void set_allocated_reason(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_reason() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_reason(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_reason(); + + public: + // string request_id = 7 [json_name = "requestId", (.buf.validate.field) = { + void clear_request_id() ; + [[nodiscard]] const ::std::string& request_id() const; + template + void set_request_id(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_request_id(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_request_id(); + void set_allocated_request_id(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_request_id() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_request_id(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_request_id(); + + public: + // bool fleet = 4 [json_name = "fleet"]; + void clear_fleet() ; + [[nodiscard]] bool fleet() const; + void set_fleet(bool value); + + private: + bool _internal_fleet() const; + void _internal_set_fleet(bool value); + + public: + // .xtcp_treatment.v1.ControlAction action = 5 [json_name = "action", (.buf.validate.field) = { + void clear_action() ; + [[nodiscard]] ::xtcp_treatment::v1::ControlAction action() const; + void set_action(::xtcp_treatment::v1::ControlAction value); + + private: + ::xtcp_treatment::v1::ControlAction _internal_action() const; + void _internal_set_action(::xtcp_treatment::v1::ControlAction value); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment.v1.ControlRequest) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<3, 7, + 0, 86, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const ControlRequest& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::google::protobuf::internal::ArenaStringPtr experiment_id_; + ::google::protobuf::internal::ArenaStringPtr agent_id_; + ::google::protobuf::internal::ArenaStringPtr location_; + ::google::protobuf::internal::ArenaStringPtr reason_; + ::google::protobuf::internal::ArenaStringPtr request_id_; + bool fleet_; + int action_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_2fv1_2foperator_2eproto; +}; +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED AnalyzeRequest final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment.v1.AnalyzeRequest) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline AnalyzeRequest() : AnalyzeRequest(nullptr) {} + ~AnalyzeRequest() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(AnalyzeRequest* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(AnalyzeRequest)); + } +#endif + + template + explicit constexpr AnalyzeRequest(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline AnalyzeRequest(const AnalyzeRequest& from) : AnalyzeRequest(nullptr, from) {} + inline AnalyzeRequest(AnalyzeRequest&& from) noexcept : AnalyzeRequest(nullptr, ::std::move(from)) {} + inline AnalyzeRequest& operator=(const AnalyzeRequest& from) { + CopyFrom(from); + return *this; + } + inline AnalyzeRequest& operator=(AnalyzeRequest&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const AnalyzeRequest& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&AnalyzeRequest_globals_); + } + static constexpr int kIndexInFileMessages = 2; + friend void swap(AnalyzeRequest& a, AnalyzeRequest& b) { a.Swap(&b); } + inline void Swap(AnalyzeRequest* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(AnalyzeRequest* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] AnalyzeRequest* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const AnalyzeRequest& from); + using Super_::MergeFrom; + void MergeFrom(const AnalyzeRequest& from) { AnalyzeRequest::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(AnalyzeRequest* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment.v1.AnalyzeRequest"; } + + explicit AnalyzeRequest(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + AnalyzeRequest(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const AnalyzeRequest& from); + AnalyzeRequest( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, AnalyzeRequest&& from) noexcept + : AnalyzeRequest(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kExperimentIdFieldNumber = 1, + kExpectedRevisionFieldNumber = 2, + }; + // string experiment_id = 1 [json_name = "experimentId", (.buf.validate.field) = { + void clear_experiment_id() ; + [[nodiscard]] const ::std::string& experiment_id() const; + template + void set_experiment_id(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_experiment_id(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_experiment_id(); + void set_allocated_experiment_id(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_experiment_id() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_experiment_id(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_experiment_id(); + + public: + // uint64 expected_revision = 2 [json_name = "expectedRevision"]; + void clear_expected_revision() ; + [[nodiscard]] ::uint64_t expected_revision() const; + void set_expected_revision(::uint64_t value); + + private: + ::uint64_t _internal_expected_revision() const; + void _internal_set_expected_revision(::uint64_t value); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment.v1.AnalyzeRequest) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<1, 2, + 0, 54, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const AnalyzeRequest& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::google::protobuf::internal::ArenaStringPtr experiment_id_; + ::uint64_t expected_revision_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_2fv1_2foperator_2eproto; +}; +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED ReviewRequest final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment.v1.ReviewRequest) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline ReviewRequest() : ReviewRequest(nullptr) {} + ~ReviewRequest() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(ReviewRequest* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(ReviewRequest)); + } +#endif + + template + explicit constexpr ReviewRequest(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline ReviewRequest(const ReviewRequest& from) : ReviewRequest(nullptr, from) {} + inline ReviewRequest(ReviewRequest&& from) noexcept : ReviewRequest(nullptr, ::std::move(from)) {} + inline ReviewRequest& operator=(const ReviewRequest& from) { + CopyFrom(from); + return *this; + } + inline ReviewRequest& operator=(ReviewRequest&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const ReviewRequest& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&ReviewRequest_globals_); + } + static constexpr int kIndexInFileMessages = 4; + friend void swap(ReviewRequest& a, ReviewRequest& b) { a.Swap(&b); } + inline void Swap(ReviewRequest* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ReviewRequest* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] ReviewRequest* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const ReviewRequest& from); + using Super_::MergeFrom; + void MergeFrom(const ReviewRequest& from) { ReviewRequest::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(ReviewRequest* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment.v1.ReviewRequest"; } + + explicit ReviewRequest(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + ReviewRequest(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const ReviewRequest& from); + ReviewRequest( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, ReviewRequest&& from) noexcept + : ReviewRequest(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kExperimentIdFieldNumber = 1, + kReasonFieldNumber = 4, + kRequestIdFieldNumber = 5, + kEvidenceFieldNumber = 6, + kExpectedRevisionFieldNumber = 2, + kApproveFieldNumber = 3, + }; + // string experiment_id = 1 [json_name = "experimentId", (.buf.validate.field) = { + void clear_experiment_id() ; + [[nodiscard]] const ::std::string& experiment_id() const; + template + void set_experiment_id(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_experiment_id(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_experiment_id(); + void set_allocated_experiment_id(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_experiment_id() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_experiment_id(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_experiment_id(); + + public: + // string reason = 4 [json_name = "reason", (.buf.validate.field) = { + void clear_reason() ; + [[nodiscard]] const ::std::string& reason() const; + template + void set_reason(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_reason(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_reason(); + void set_allocated_reason(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_reason() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_reason(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_reason(); + + public: + // string request_id = 5 [json_name = "requestId", (.buf.validate.field) = { + void clear_request_id() ; + [[nodiscard]] const ::std::string& request_id() const; + template + void set_request_id(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_request_id(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_request_id(); + void set_allocated_request_id(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_request_id() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_request_id(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_request_id(); + + public: + // .xtcp_treatment.v1.Evidence evidence = 6 [json_name = "evidence"]; + [[nodiscard]] bool has_evidence() const; + void clear_evidence() ; + [[nodiscard]] const ::xtcp_treatment::v1::Evidence& evidence() const; + [[nodiscard]] ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE release_evidence(); + ::xtcp_treatment::v1::Evidence* PROTOBUF_NONNULL mutable_evidence(); + void set_allocated_evidence(::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_evidence(::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE value); + ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE unsafe_arena_release_evidence(); + + private: + const ::xtcp_treatment::v1::Evidence& _internal_evidence() const; + ::xtcp_treatment::v1::Evidence* PROTOBUF_NONNULL _internal_mutable_evidence(); + + public: + // uint64 expected_revision = 2 [json_name = "expectedRevision"]; + void clear_expected_revision() ; + [[nodiscard]] ::uint64_t expected_revision() const; + void set_expected_revision(::uint64_t value); + + private: + ::uint64_t _internal_expected_revision() const; + void _internal_set_expected_revision(::uint64_t value); + + public: + // bool approve = 3 [json_name = "approve"]; + void clear_approve() ; + [[nodiscard]] bool approve() const; + void set_approve(bool value); + + private: + bool _internal_approve() const; + void _internal_set_approve(bool value); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment.v1.ReviewRequest) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<3, 6, + 1, 69, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const ReviewRequest& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::google::protobuf::internal::ArenaStringPtr experiment_id_; + ::google::protobuf::internal::ArenaStringPtr reason_; + ::google::protobuf::internal::ArenaStringPtr request_id_; + ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE evidence_; + ::uint64_t expected_revision_; + bool approve_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_2fv1_2foperator_2eproto; +}; +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED AnalyzeResponse final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment.v1.AnalyzeResponse) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline AnalyzeResponse() : AnalyzeResponse(nullptr) {} + ~AnalyzeResponse() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(AnalyzeResponse* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(AnalyzeResponse)); + } +#endif + + template + explicit constexpr AnalyzeResponse(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline AnalyzeResponse(const AnalyzeResponse& from) : AnalyzeResponse(nullptr, from) {} + inline AnalyzeResponse(AnalyzeResponse&& from) noexcept : AnalyzeResponse(nullptr, ::std::move(from)) {} + inline AnalyzeResponse& operator=(const AnalyzeResponse& from) { + CopyFrom(from); + return *this; + } + inline AnalyzeResponse& operator=(AnalyzeResponse&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const AnalyzeResponse& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&AnalyzeResponse_globals_); + } + static constexpr int kIndexInFileMessages = 3; + friend void swap(AnalyzeResponse& a, AnalyzeResponse& b) { a.Swap(&b); } + inline void Swap(AnalyzeResponse* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(AnalyzeResponse* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] AnalyzeResponse* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const AnalyzeResponse& from); + using Super_::MergeFrom; + void MergeFrom(const AnalyzeResponse& from) { AnalyzeResponse::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(AnalyzeResponse* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment.v1.AnalyzeResponse"; } + + explicit AnalyzeResponse(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + AnalyzeResponse(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const AnalyzeResponse& from); + AnalyzeResponse( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, AnalyzeResponse&& from) noexcept + : AnalyzeResponse(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kOutcomeFieldNumber = 2, + kEvidenceFieldNumber = 1, + }; + // string outcome = 2 [json_name = "outcome"]; + void clear_outcome() ; + [[nodiscard]] const ::std::string& outcome() const; + template + void set_outcome(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_outcome(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_outcome(); + void set_allocated_outcome(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_outcome() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_outcome(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_outcome(); + + public: + // .xtcp_treatment.v1.Evidence evidence = 1 [json_name = "evidence"]; + [[nodiscard]] bool has_evidence() const; + void clear_evidence() ; + [[nodiscard]] const ::xtcp_treatment::v1::Evidence& evidence() const; + [[nodiscard]] ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE release_evidence(); + ::xtcp_treatment::v1::Evidence* PROTOBUF_NONNULL mutable_evidence(); + void set_allocated_evidence(::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_evidence(::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE value); + ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE unsafe_arena_release_evidence(); + + private: + const ::xtcp_treatment::v1::Evidence& _internal_evidence() const; + ::xtcp_treatment::v1::Evidence* PROTOBUF_NONNULL _internal_mutable_evidence(); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment.v1.AnalyzeResponse) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<1, 2, + 1, 49, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const AnalyzeResponse& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::google::protobuf::internal::ArenaStringPtr outcome_; + ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE evidence_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_2fv1_2foperator_2eproto; +}; +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED ReviewResponse final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment.v1.ReviewResponse) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline ReviewResponse() : ReviewResponse(nullptr) {} + ~ReviewResponse() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(ReviewResponse* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(ReviewResponse)); + } +#endif + + template + explicit constexpr ReviewResponse(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline ReviewResponse(const ReviewResponse& from) : ReviewResponse(nullptr, from) {} + inline ReviewResponse(ReviewResponse&& from) noexcept : ReviewResponse(nullptr, ::std::move(from)) {} + inline ReviewResponse& operator=(const ReviewResponse& from) { + CopyFrom(from); + return *this; + } + inline ReviewResponse& operator=(ReviewResponse&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const ReviewResponse& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&ReviewResponse_globals_); + } + static constexpr int kIndexInFileMessages = 5; + friend void swap(ReviewResponse& a, ReviewResponse& b) { a.Swap(&b); } + inline void Swap(ReviewResponse* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ReviewResponse* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] ReviewResponse* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const ReviewResponse& from); + using Super_::MergeFrom; + void MergeFrom(const ReviewResponse& from) { ReviewResponse::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(ReviewResponse* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment.v1.ReviewResponse"; } + + explicit ReviewResponse(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + ReviewResponse(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const ReviewResponse& from); + ReviewResponse( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, ReviewResponse&& from) noexcept + : ReviewResponse(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kExperimentFieldNumber = 1, + }; + // .xtcp_treatment.v1.Experiment experiment = 1 [json_name = "experiment"]; + [[nodiscard]] bool has_experiment() const; + void clear_experiment() ; + [[nodiscard]] const ::xtcp_treatment::v1::Experiment& experiment() const; + [[nodiscard]] ::xtcp_treatment::v1::Experiment* PROTOBUF_NULLABLE release_experiment(); + ::xtcp_treatment::v1::Experiment* PROTOBUF_NONNULL mutable_experiment(); + void set_allocated_experiment(::xtcp_treatment::v1::Experiment* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_experiment(::xtcp_treatment::v1::Experiment* PROTOBUF_NULLABLE value); + ::xtcp_treatment::v1::Experiment* PROTOBUF_NULLABLE unsafe_arena_release_experiment(); + + private: + const ::xtcp_treatment::v1::Experiment& _internal_experiment() const; + ::xtcp_treatment::v1::Experiment* PROTOBUF_NONNULL _internal_mutable_experiment(); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment.v1.ReviewResponse) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<0, 1, + 1, 0, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const ReviewResponse& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::xtcp_treatment::v1::Experiment* PROTOBUF_NULLABLE experiment_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_2fv1_2foperator_2eproto; +}; +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED ProposeResponse final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment.v1.ProposeResponse) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline ProposeResponse() : ProposeResponse(nullptr) {} + ~ProposeResponse() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(ProposeResponse* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(ProposeResponse)); + } +#endif + + template + explicit constexpr ProposeResponse(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline ProposeResponse(const ProposeResponse& from) : ProposeResponse(nullptr, from) {} + inline ProposeResponse(ProposeResponse&& from) noexcept : ProposeResponse(nullptr, ::std::move(from)) {} + inline ProposeResponse& operator=(const ProposeResponse& from) { + CopyFrom(from); + return *this; + } + inline ProposeResponse& operator=(ProposeResponse&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const ProposeResponse& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&ProposeResponse_globals_); + } + static constexpr int kIndexInFileMessages = 1; + friend void swap(ProposeResponse& a, ProposeResponse& b) { a.Swap(&b); } + inline void Swap(ProposeResponse* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ProposeResponse* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] ProposeResponse* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const ProposeResponse& from); + using Super_::MergeFrom; + void MergeFrom(const ProposeResponse& from) { ProposeResponse::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(ProposeResponse* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment.v1.ProposeResponse"; } + + explicit ProposeResponse(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + ProposeResponse(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const ProposeResponse& from); + ProposeResponse( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, ProposeResponse&& from) noexcept + : ProposeResponse(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kExperimentFieldNumber = 1, + }; + // .xtcp_treatment.v1.Experiment experiment = 1 [json_name = "experiment"]; + [[nodiscard]] bool has_experiment() const; + void clear_experiment() ; + [[nodiscard]] const ::xtcp_treatment::v1::Experiment& experiment() const; + [[nodiscard]] ::xtcp_treatment::v1::Experiment* PROTOBUF_NULLABLE release_experiment(); + ::xtcp_treatment::v1::Experiment* PROTOBUF_NONNULL mutable_experiment(); + void set_allocated_experiment(::xtcp_treatment::v1::Experiment* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_experiment(::xtcp_treatment::v1::Experiment* PROTOBUF_NULLABLE value); + ::xtcp_treatment::v1::Experiment* PROTOBUF_NULLABLE unsafe_arena_release_experiment(); + + private: + const ::xtcp_treatment::v1::Experiment& _internal_experiment() const; + ::xtcp_treatment::v1::Experiment* PROTOBUF_NONNULL _internal_mutable_experiment(); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment.v1.ProposeResponse) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<0, 1, + 1, 0, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const ProposeResponse& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::xtcp_treatment::v1::Experiment* PROTOBUF_NULLABLE experiment_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_2fv1_2foperator_2eproto; +}; +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED ProposeRequest final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment.v1.ProposeRequest) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline ProposeRequest() : ProposeRequest(nullptr) {} + ~ProposeRequest() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(ProposeRequest* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(ProposeRequest)); + } +#endif + + template + explicit constexpr ProposeRequest(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline ProposeRequest(const ProposeRequest& from) : ProposeRequest(nullptr, from) {} + inline ProposeRequest(ProposeRequest&& from) noexcept : ProposeRequest(nullptr, ::std::move(from)) {} + inline ProposeRequest& operator=(const ProposeRequest& from) { + CopyFrom(from); + return *this; + } + inline ProposeRequest& operator=(ProposeRequest&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const ProposeRequest& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&ProposeRequest_globals_); + } + static constexpr int kIndexInFileMessages = 0; + friend void swap(ProposeRequest& a, ProposeRequest& b) { a.Swap(&b); } + inline void Swap(ProposeRequest* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ProposeRequest* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] ProposeRequest* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const ProposeRequest& from); + using Super_::MergeFrom; + void MergeFrom(const ProposeRequest& from) { ProposeRequest::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(ProposeRequest* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment.v1.ProposeRequest"; } + + explicit ProposeRequest(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + ProposeRequest(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const ProposeRequest& from); + ProposeRequest( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, ProposeRequest&& from) noexcept + : ProposeRequest(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kRequestIdFieldNumber = 2, + kExperimentFieldNumber = 1, + }; + // string request_id = 2 [json_name = "requestId", (.buf.validate.field) = { + void clear_request_id() ; + [[nodiscard]] const ::std::string& request_id() const; + template + void set_request_id(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_request_id(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_request_id(); + void set_allocated_request_id(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_request_id() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_request_id(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_request_id(); + + public: + // .xtcp_treatment.v1.Experiment experiment = 1 [json_name = "experiment", (.buf.validate.field) = { + [[nodiscard]] bool has_experiment() const; + void clear_experiment() ; + [[nodiscard]] const ::xtcp_treatment::v1::Experiment& experiment() const; + [[nodiscard]] ::xtcp_treatment::v1::Experiment* PROTOBUF_NULLABLE release_experiment(); + ::xtcp_treatment::v1::Experiment* PROTOBUF_NONNULL mutable_experiment(); + void set_allocated_experiment(::xtcp_treatment::v1::Experiment* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_experiment(::xtcp_treatment::v1::Experiment* PROTOBUF_NULLABLE value); + ::xtcp_treatment::v1::Experiment* PROTOBUF_NULLABLE unsafe_arena_release_experiment(); + + private: + const ::xtcp_treatment::v1::Experiment& _internal_experiment() const; + ::xtcp_treatment::v1::Experiment* PROTOBUF_NONNULL _internal_mutable_experiment(); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment.v1.ProposeRequest) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<1, 2, + 1, 51, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const ProposeRequest& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::google::protobuf::internal::ArenaStringPtr request_id_; + ::xtcp_treatment::v1::Experiment* PROTOBUF_NULLABLE experiment_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_2fv1_2foperator_2eproto; +}; +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED InspectResponse final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment.v1.InspectResponse) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline InspectResponse() : InspectResponse(nullptr) {} + ~InspectResponse() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(InspectResponse* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(InspectResponse)); + } +#endif + + template + explicit constexpr InspectResponse(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline InspectResponse(const InspectResponse& from) : InspectResponse(nullptr, from) {} + inline InspectResponse(InspectResponse&& from) noexcept : InspectResponse(nullptr, ::std::move(from)) {} + inline InspectResponse& operator=(const InspectResponse& from) { + CopyFrom(from); + return *this; + } + inline InspectResponse& operator=(InspectResponse&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const InspectResponse& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&InspectResponse_globals_); + } + static constexpr int kIndexInFileMessages = 9; + friend void swap(InspectResponse& a, InspectResponse& b) { a.Swap(&b); } + inline void Swap(InspectResponse* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(InspectResponse* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] InspectResponse* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const InspectResponse& from); + using Super_::MergeFrom; + void MergeFrom(const InspectResponse& from) { InspectResponse::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(InspectResponse* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment.v1.InspectResponse"; } + + explicit InspectResponse(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + InspectResponse(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const InspectResponse& from); + InspectResponse( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, InspectResponse&& from) noexcept + : InspectResponse(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kExperimentsFieldNumber = 1, + kDesiredFieldNumber = 2, + kObservedFieldNumber = 3, + }; + // repeated .xtcp_treatment.v1.Experiment experiments = 1 [json_name = "experiments"]; + [[nodiscard]] int experiments_size() const; + private: + int _internal_experiments_size() const; + + public: + void clear_experiments() ; + [[nodiscard]] const ::xtcp_treatment::v1::Experiment& experiments(int index) const; + [[nodiscard]] ::xtcp_treatment::v1::Experiment* PROTOBUF_NONNULL mutable_experiments(int index); + ::xtcp_treatment::v1::Experiment* PROTOBUF_NONNULL add_experiments(); + [[nodiscard]] const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::Experiment>& + experiments() const; + [[nodiscard]] ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::Experiment>* PROTOBUF_NONNULL + mutable_experiments(); + + private: + const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::Experiment>& _internal_experiments() const; + ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::Experiment>* PROTOBUF_NONNULL _internal_mutable_experiments(); + + public: + // repeated .xtcp_treatment.v1.DesiredState desired = 2 [json_name = "desired"]; + [[nodiscard]] int desired_size() const; + private: + int _internal_desired_size() const; + + public: + void clear_desired() ; + [[nodiscard]] const ::xtcp_treatment::v1::DesiredState& desired(int index) const; + [[nodiscard]] ::xtcp_treatment::v1::DesiredState* PROTOBUF_NONNULL mutable_desired(int index); + ::xtcp_treatment::v1::DesiredState* PROTOBUF_NONNULL add_desired(); + [[nodiscard]] const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::DesiredState>& + desired() const; + [[nodiscard]] ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::DesiredState>* PROTOBUF_NONNULL + mutable_desired(); + + private: + const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::DesiredState>& _internal_desired() const; + ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::DesiredState>* PROTOBUF_NONNULL _internal_mutable_desired(); + + public: + // repeated .xtcp_treatment.v1.CheckInRequest observed = 3 [json_name = "observed"]; + [[nodiscard]] int observed_size() const; + private: + int _internal_observed_size() const; + + public: + void clear_observed() ; + [[nodiscard]] const ::xtcp_treatment::v1::CheckInRequest& observed(int index) const; + [[nodiscard]] ::xtcp_treatment::v1::CheckInRequest* PROTOBUF_NONNULL mutable_observed(int index); + ::xtcp_treatment::v1::CheckInRequest* PROTOBUF_NONNULL add_observed(); + [[nodiscard]] const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::CheckInRequest>& + observed() const; + [[nodiscard]] ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::CheckInRequest>* PROTOBUF_NONNULL + mutable_observed(); + + private: + const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::CheckInRequest>& _internal_observed() const; + ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::CheckInRequest>* PROTOBUF_NONNULL _internal_mutable_observed(); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment.v1.InspectResponse) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<2, 3, + 3, 0, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const InspectResponse& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::google::protobuf::RepeatedPtrField< ::xtcp_treatment::v1::Experiment > experiments_; + ::google::protobuf::RepeatedPtrField< ::xtcp_treatment::v1::DesiredState > desired_; + ::google::protobuf::RepeatedPtrField< ::xtcp_treatment::v1::CheckInRequest > observed_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_2fv1_2foperator_2eproto; +}; + +// =================================================================== + + + + +// =================================================================== + + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif // __GNUC__ +// ------------------------------------------------------------------- + +// ProposeRequest + +// .xtcp_treatment.v1.Experiment experiment = 1 [json_name = "experiment", (.buf.validate.field) = { +inline bool ProposeRequest::has_experiment() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000002U); + PROTOBUF_ASSUME(!value || _impl_.experiment_ != nullptr); + return value; +} +inline const ::xtcp_treatment::v1::Experiment& ProposeRequest::_internal_experiment() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::xtcp_treatment::v1::Experiment* p = _impl_.experiment_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::xtcp_treatment::v1::Experiment>(&::xtcp_treatment::v1::Experiment_globals_); +} +inline const ::xtcp_treatment::v1::Experiment& ProposeRequest::experiment() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ProposeRequest.experiment) + return _internal_experiment(); +} +inline void ProposeRequest::unsafe_arena_set_allocated_experiment( + ::xtcp_treatment::v1::Experiment* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.experiment_); + } + _impl_.experiment_ = reinterpret_cast<::xtcp_treatment::v1::Experiment*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.ProposeRequest.experiment) +} +inline ::xtcp_treatment::v1::Experiment* PROTOBUF_NULLABLE ProposeRequest::release_experiment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + ::xtcp_treatment::v1::Experiment* released = _impl_.experiment_; + _impl_.experiment_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::xtcp_treatment::v1::Experiment* PROTOBUF_NULLABLE ProposeRequest::unsafe_arena_release_experiment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.ProposeRequest.experiment) + + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + ::xtcp_treatment::v1::Experiment* temp = _impl_.experiment_; + _impl_.experiment_ = nullptr; + return temp; +} +inline ::xtcp_treatment::v1::Experiment* PROTOBUF_NONNULL ProposeRequest::_internal_mutable_experiment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.experiment_ == nullptr) { + auto* p = Super_::DefaultConstruct<::xtcp_treatment::v1::Experiment>(GetArena()); + _impl_.experiment_ = reinterpret_cast<::xtcp_treatment::v1::Experiment*>(p); + } + return _impl_.experiment_; +} +inline ::xtcp_treatment::v1::Experiment* PROTOBUF_NONNULL ProposeRequest::mutable_experiment() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + ::xtcp_treatment::v1::Experiment* _msg = _internal_mutable_experiment(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.ProposeRequest.experiment) + return _msg; +} +inline void ProposeRequest::set_allocated_experiment(::xtcp_treatment::v1::Experiment* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.experiment_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + } + + _impl_.experiment_ = reinterpret_cast<::xtcp_treatment::v1::Experiment*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.ProposeRequest.experiment) +} + +// string request_id = 2 [json_name = "requestId", (.buf.validate.field) = { +inline void ProposeRequest::clear_request_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.request_id_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +inline const ::std::string& ProposeRequest::request_id() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ProposeRequest.request_id) + return _internal_request_id(); +} +template +PROTOBUF_ALWAYS_INLINE void ProposeRequest::set_request_id(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + _impl_.request_id_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.ProposeRequest.request_id) +} +inline ::std::string* PROTOBUF_NONNULL ProposeRequest::mutable_request_id() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + ::std::string* _s = _internal_mutable_request_id(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.ProposeRequest.request_id) + return _s; +} +inline const ::std::string& ProposeRequest::_internal_request_id() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.request_id_.Get(); +} +inline void ProposeRequest::_internal_set_request_id(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.request_id_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL ProposeRequest::_internal_mutable_request_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.request_id_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE ProposeRequest::release_request_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.ProposeRequest.request_id) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000001U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + auto* released = _impl_.request_id_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.request_id_.Set("", GetArena()); + } + return released; +} +inline void ProposeRequest::set_allocated_request_id(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + } + _impl_.request_id_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.request_id_.IsDefault()) { + _impl_.request_id_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.ProposeRequest.request_id) +} + +// ------------------------------------------------------------------- + +// ProposeResponse + +// .xtcp_treatment.v1.Experiment experiment = 1 [json_name = "experiment"]; +inline bool ProposeResponse::has_experiment() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000001U); + PROTOBUF_ASSUME(!value || _impl_.experiment_ != nullptr); + return value; +} +inline const ::xtcp_treatment::v1::Experiment& ProposeResponse::_internal_experiment() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::xtcp_treatment::v1::Experiment* p = _impl_.experiment_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::xtcp_treatment::v1::Experiment>(&::xtcp_treatment::v1::Experiment_globals_); +} +inline const ::xtcp_treatment::v1::Experiment& ProposeResponse::experiment() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ProposeResponse.experiment) + return _internal_experiment(); +} +inline void ProposeResponse::unsafe_arena_set_allocated_experiment( + ::xtcp_treatment::v1::Experiment* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.experiment_); + } + _impl_.experiment_ = reinterpret_cast<::xtcp_treatment::v1::Experiment*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.ProposeResponse.experiment) +} +inline ::xtcp_treatment::v1::Experiment* PROTOBUF_NULLABLE ProposeResponse::release_experiment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + ::xtcp_treatment::v1::Experiment* released = _impl_.experiment_; + _impl_.experiment_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::xtcp_treatment::v1::Experiment* PROTOBUF_NULLABLE ProposeResponse::unsafe_arena_release_experiment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.ProposeResponse.experiment) + + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + ::xtcp_treatment::v1::Experiment* temp = _impl_.experiment_; + _impl_.experiment_ = nullptr; + return temp; +} +inline ::xtcp_treatment::v1::Experiment* PROTOBUF_NONNULL ProposeResponse::_internal_mutable_experiment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.experiment_ == nullptr) { + auto* p = Super_::DefaultConstruct<::xtcp_treatment::v1::Experiment>(GetArena()); + _impl_.experiment_ = reinterpret_cast<::xtcp_treatment::v1::Experiment*>(p); + } + return _impl_.experiment_; +} +inline ::xtcp_treatment::v1::Experiment* PROTOBUF_NONNULL ProposeResponse::mutable_experiment() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + ::xtcp_treatment::v1::Experiment* _msg = _internal_mutable_experiment(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.ProposeResponse.experiment) + return _msg; +} +inline void ProposeResponse::set_allocated_experiment(::xtcp_treatment::v1::Experiment* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.experiment_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + } + + _impl_.experiment_ = reinterpret_cast<::xtcp_treatment::v1::Experiment*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.ProposeResponse.experiment) +} + +// ------------------------------------------------------------------- + +// AnalyzeRequest + +// string experiment_id = 1 [json_name = "experimentId", (.buf.validate.field) = { +inline void AnalyzeRequest::clear_experiment_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.experiment_id_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +inline const ::std::string& AnalyzeRequest::experiment_id() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.AnalyzeRequest.experiment_id) + return _internal_experiment_id(); +} +template +PROTOBUF_ALWAYS_INLINE void AnalyzeRequest::set_experiment_id(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + _impl_.experiment_id_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.AnalyzeRequest.experiment_id) +} +inline ::std::string* PROTOBUF_NONNULL AnalyzeRequest::mutable_experiment_id() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + ::std::string* _s = _internal_mutable_experiment_id(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.AnalyzeRequest.experiment_id) + return _s; +} +inline const ::std::string& AnalyzeRequest::_internal_experiment_id() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.experiment_id_.Get(); +} +inline void AnalyzeRequest::_internal_set_experiment_id(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.experiment_id_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL AnalyzeRequest::_internal_mutable_experiment_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.experiment_id_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE AnalyzeRequest::release_experiment_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.AnalyzeRequest.experiment_id) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000001U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + auto* released = _impl_.experiment_id_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.experiment_id_.Set("", GetArena()); + } + return released; +} +inline void AnalyzeRequest::set_allocated_experiment_id(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + } + _impl_.experiment_id_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.experiment_id_.IsDefault()) { + _impl_.experiment_id_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.AnalyzeRequest.experiment_id) +} + +// uint64 expected_revision = 2 [json_name = "expectedRevision"]; +inline void AnalyzeRequest::clear_expected_revision() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.expected_revision_ = ::uint64_t{0u}; + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); +} +inline ::uint64_t AnalyzeRequest::expected_revision() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.AnalyzeRequest.expected_revision) + return _internal_expected_revision(); +} +inline void AnalyzeRequest::set_expected_revision(::uint64_t value) { + _internal_set_expected_revision(value); + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.AnalyzeRequest.expected_revision) +} +inline ::uint64_t AnalyzeRequest::_internal_expected_revision() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.expected_revision_; +} +inline void AnalyzeRequest::_internal_set_expected_revision(::uint64_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.expected_revision_ = value; +} + +// ------------------------------------------------------------------- + +// AnalyzeResponse + +// .xtcp_treatment.v1.Evidence evidence = 1 [json_name = "evidence"]; +inline bool AnalyzeResponse::has_evidence() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000002U); + PROTOBUF_ASSUME(!value || _impl_.evidence_ != nullptr); + return value; +} +inline const ::xtcp_treatment::v1::Evidence& AnalyzeResponse::_internal_evidence() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::xtcp_treatment::v1::Evidence* p = _impl_.evidence_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::xtcp_treatment::v1::Evidence>(&::xtcp_treatment::v1::Evidence_globals_); +} +inline const ::xtcp_treatment::v1::Evidence& AnalyzeResponse::evidence() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.AnalyzeResponse.evidence) + return _internal_evidence(); +} +inline void AnalyzeResponse::unsafe_arena_set_allocated_evidence( + ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.evidence_); + } + _impl_.evidence_ = reinterpret_cast<::xtcp_treatment::v1::Evidence*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.AnalyzeResponse.evidence) +} +inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE AnalyzeResponse::release_evidence() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + ::xtcp_treatment::v1::Evidence* released = _impl_.evidence_; + _impl_.evidence_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE AnalyzeResponse::unsafe_arena_release_evidence() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.AnalyzeResponse.evidence) + + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + ::xtcp_treatment::v1::Evidence* temp = _impl_.evidence_; + _impl_.evidence_ = nullptr; + return temp; +} +inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NONNULL AnalyzeResponse::_internal_mutable_evidence() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.evidence_ == nullptr) { + auto* p = Super_::DefaultConstruct<::xtcp_treatment::v1::Evidence>(GetArena()); + _impl_.evidence_ = reinterpret_cast<::xtcp_treatment::v1::Evidence*>(p); + } + return _impl_.evidence_; +} +inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NONNULL AnalyzeResponse::mutable_evidence() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + ::xtcp_treatment::v1::Evidence* _msg = _internal_mutable_evidence(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.AnalyzeResponse.evidence) + return _msg; +} +inline void AnalyzeResponse::set_allocated_evidence(::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.evidence_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + } + + _impl_.evidence_ = reinterpret_cast<::xtcp_treatment::v1::Evidence*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.AnalyzeResponse.evidence) +} + +// string outcome = 2 [json_name = "outcome"]; +inline void AnalyzeResponse::clear_outcome() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.outcome_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +inline const ::std::string& AnalyzeResponse::outcome() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.AnalyzeResponse.outcome) + return _internal_outcome(); +} +template +PROTOBUF_ALWAYS_INLINE void AnalyzeResponse::set_outcome(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + _impl_.outcome_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.AnalyzeResponse.outcome) +} +inline ::std::string* PROTOBUF_NONNULL AnalyzeResponse::mutable_outcome() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + ::std::string* _s = _internal_mutable_outcome(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.AnalyzeResponse.outcome) + return _s; +} +inline const ::std::string& AnalyzeResponse::_internal_outcome() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.outcome_.Get(); +} +inline void AnalyzeResponse::_internal_set_outcome(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.outcome_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL AnalyzeResponse::_internal_mutable_outcome() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.outcome_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE AnalyzeResponse::release_outcome() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.AnalyzeResponse.outcome) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000001U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + auto* released = _impl_.outcome_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.outcome_.Set("", GetArena()); + } + return released; +} +inline void AnalyzeResponse::set_allocated_outcome(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + } + _impl_.outcome_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.outcome_.IsDefault()) { + _impl_.outcome_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.AnalyzeResponse.outcome) +} + +// ------------------------------------------------------------------- + +// ReviewRequest + +// string experiment_id = 1 [json_name = "experimentId", (.buf.validate.field) = { +inline void ReviewRequest::clear_experiment_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.experiment_id_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +inline const ::std::string& ReviewRequest::experiment_id() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ReviewRequest.experiment_id) + return _internal_experiment_id(); +} +template +PROTOBUF_ALWAYS_INLINE void ReviewRequest::set_experiment_id(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + _impl_.experiment_id_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.ReviewRequest.experiment_id) +} +inline ::std::string* PROTOBUF_NONNULL ReviewRequest::mutable_experiment_id() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + ::std::string* _s = _internal_mutable_experiment_id(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.ReviewRequest.experiment_id) + return _s; +} +inline const ::std::string& ReviewRequest::_internal_experiment_id() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.experiment_id_.Get(); +} +inline void ReviewRequest::_internal_set_experiment_id(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.experiment_id_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL ReviewRequest::_internal_mutable_experiment_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.experiment_id_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE ReviewRequest::release_experiment_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.ReviewRequest.experiment_id) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000001U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + auto* released = _impl_.experiment_id_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.experiment_id_.Set("", GetArena()); + } + return released; +} +inline void ReviewRequest::set_allocated_experiment_id(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + } + _impl_.experiment_id_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.experiment_id_.IsDefault()) { + _impl_.experiment_id_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.ReviewRequest.experiment_id) +} + +// uint64 expected_revision = 2 [json_name = "expectedRevision"]; +inline void ReviewRequest::clear_expected_revision() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.expected_revision_ = ::uint64_t{0u}; + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); +} +inline ::uint64_t ReviewRequest::expected_revision() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ReviewRequest.expected_revision) + return _internal_expected_revision(); +} +inline void ReviewRequest::set_expected_revision(::uint64_t value) { + _internal_set_expected_revision(value); + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.ReviewRequest.expected_revision) +} +inline ::uint64_t ReviewRequest::_internal_expected_revision() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.expected_revision_; +} +inline void ReviewRequest::_internal_set_expected_revision(::uint64_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.expected_revision_ = value; +} + +// bool approve = 3 [json_name = "approve"]; +inline void ReviewRequest::clear_approve() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.approve_ = false; + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); +} +inline bool ReviewRequest::approve() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ReviewRequest.approve) + return _internal_approve(); +} +inline void ReviewRequest::set_approve(bool value) { + _internal_set_approve(value); + SetHasBit(_impl_._has_bits_[0], 0x00000020U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.ReviewRequest.approve) +} +inline bool ReviewRequest::_internal_approve() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.approve_; +} +inline void ReviewRequest::_internal_set_approve(bool value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.approve_ = value; +} + +// string reason = 4 [json_name = "reason", (.buf.validate.field) = { +inline void ReviewRequest::clear_reason() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.reason_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); +} +inline const ::std::string& ReviewRequest::reason() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ReviewRequest.reason) + return _internal_reason(); +} +template +PROTOBUF_ALWAYS_INLINE void ReviewRequest::set_reason(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + _impl_.reason_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.ReviewRequest.reason) +} +inline ::std::string* PROTOBUF_NONNULL ReviewRequest::mutable_reason() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + ::std::string* _s = _internal_mutable_reason(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.ReviewRequest.reason) + return _s; +} +inline const ::std::string& ReviewRequest::_internal_reason() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.reason_.Get(); +} +inline void ReviewRequest::_internal_set_reason(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.reason_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL ReviewRequest::_internal_mutable_reason() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.reason_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE ReviewRequest::release_reason() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.ReviewRequest.reason) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000002U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + auto* released = _impl_.reason_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.reason_.Set("", GetArena()); + } + return released; +} +inline void ReviewRequest::set_allocated_reason(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + } + _impl_.reason_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.reason_.IsDefault()) { + _impl_.reason_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.ReviewRequest.reason) +} + +// string request_id = 5 [json_name = "requestId", (.buf.validate.field) = { +inline void ReviewRequest::clear_request_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.request_id_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); +} +inline const ::std::string& ReviewRequest::request_id() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ReviewRequest.request_id) + return _internal_request_id(); +} +template +PROTOBUF_ALWAYS_INLINE void ReviewRequest::set_request_id(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + _impl_.request_id_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.ReviewRequest.request_id) +} +inline ::std::string* PROTOBUF_NONNULL ReviewRequest::mutable_request_id() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + ::std::string* _s = _internal_mutable_request_id(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.ReviewRequest.request_id) + return _s; +} +inline const ::std::string& ReviewRequest::_internal_request_id() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.request_id_.Get(); +} +inline void ReviewRequest::_internal_set_request_id(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.request_id_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL ReviewRequest::_internal_mutable_request_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.request_id_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE ReviewRequest::release_request_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.ReviewRequest.request_id) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000004U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + auto* released = _impl_.request_id_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.request_id_.Set("", GetArena()); + } + return released; +} +inline void ReviewRequest::set_allocated_request_id(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + } + _impl_.request_id_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.request_id_.IsDefault()) { + _impl_.request_id_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.ReviewRequest.request_id) +} + +// .xtcp_treatment.v1.Evidence evidence = 6 [json_name = "evidence"]; +inline bool ReviewRequest::has_evidence() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000008U); + PROTOBUF_ASSUME(!value || _impl_.evidence_ != nullptr); + return value; +} +inline const ::xtcp_treatment::v1::Evidence& ReviewRequest::_internal_evidence() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::xtcp_treatment::v1::Evidence* p = _impl_.evidence_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::xtcp_treatment::v1::Evidence>(&::xtcp_treatment::v1::Evidence_globals_); +} +inline const ::xtcp_treatment::v1::Evidence& ReviewRequest::evidence() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ReviewRequest.evidence) + return _internal_evidence(); +} +inline void ReviewRequest::unsafe_arena_set_allocated_evidence( + ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.evidence_); + } + _impl_.evidence_ = reinterpret_cast<::xtcp_treatment::v1::Evidence*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.ReviewRequest.evidence) +} +inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE ReviewRequest::release_evidence() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + ::xtcp_treatment::v1::Evidence* released = _impl_.evidence_; + _impl_.evidence_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE ReviewRequest::unsafe_arena_release_evidence() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.ReviewRequest.evidence) + + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + ::xtcp_treatment::v1::Evidence* temp = _impl_.evidence_; + _impl_.evidence_ = nullptr; + return temp; +} +inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NONNULL ReviewRequest::_internal_mutable_evidence() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.evidence_ == nullptr) { + auto* p = Super_::DefaultConstruct<::xtcp_treatment::v1::Evidence>(GetArena()); + _impl_.evidence_ = reinterpret_cast<::xtcp_treatment::v1::Evidence*>(p); + } + return _impl_.evidence_; +} +inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NONNULL ReviewRequest::mutable_evidence() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + ::xtcp_treatment::v1::Evidence* _msg = _internal_mutable_evidence(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.ReviewRequest.evidence) + return _msg; +} +inline void ReviewRequest::set_allocated_evidence(::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.evidence_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + } + + _impl_.evidence_ = reinterpret_cast<::xtcp_treatment::v1::Evidence*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.ReviewRequest.evidence) +} + +// ------------------------------------------------------------------- + +// ReviewResponse + +// .xtcp_treatment.v1.Experiment experiment = 1 [json_name = "experiment"]; +inline bool ReviewResponse::has_experiment() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000001U); + PROTOBUF_ASSUME(!value || _impl_.experiment_ != nullptr); + return value; +} +inline const ::xtcp_treatment::v1::Experiment& ReviewResponse::_internal_experiment() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::xtcp_treatment::v1::Experiment* p = _impl_.experiment_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::xtcp_treatment::v1::Experiment>(&::xtcp_treatment::v1::Experiment_globals_); +} +inline const ::xtcp_treatment::v1::Experiment& ReviewResponse::experiment() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ReviewResponse.experiment) + return _internal_experiment(); +} +inline void ReviewResponse::unsafe_arena_set_allocated_experiment( + ::xtcp_treatment::v1::Experiment* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.experiment_); + } + _impl_.experiment_ = reinterpret_cast<::xtcp_treatment::v1::Experiment*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.ReviewResponse.experiment) +} +inline ::xtcp_treatment::v1::Experiment* PROTOBUF_NULLABLE ReviewResponse::release_experiment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + ::xtcp_treatment::v1::Experiment* released = _impl_.experiment_; + _impl_.experiment_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::xtcp_treatment::v1::Experiment* PROTOBUF_NULLABLE ReviewResponse::unsafe_arena_release_experiment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.ReviewResponse.experiment) + + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + ::xtcp_treatment::v1::Experiment* temp = _impl_.experiment_; + _impl_.experiment_ = nullptr; + return temp; +} +inline ::xtcp_treatment::v1::Experiment* PROTOBUF_NONNULL ReviewResponse::_internal_mutable_experiment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.experiment_ == nullptr) { + auto* p = Super_::DefaultConstruct<::xtcp_treatment::v1::Experiment>(GetArena()); + _impl_.experiment_ = reinterpret_cast<::xtcp_treatment::v1::Experiment*>(p); + } + return _impl_.experiment_; +} +inline ::xtcp_treatment::v1::Experiment* PROTOBUF_NONNULL ReviewResponse::mutable_experiment() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + ::xtcp_treatment::v1::Experiment* _msg = _internal_mutable_experiment(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.ReviewResponse.experiment) + return _msg; +} +inline void ReviewResponse::set_allocated_experiment(::xtcp_treatment::v1::Experiment* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.experiment_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + } + + _impl_.experiment_ = reinterpret_cast<::xtcp_treatment::v1::Experiment*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.ReviewResponse.experiment) +} + +// ------------------------------------------------------------------- + +// ControlRequest + +// string experiment_id = 1 [json_name = "experimentId", (.buf.validate.field) = { +inline void ControlRequest::clear_experiment_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.experiment_id_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +inline const ::std::string& ControlRequest::experiment_id() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ControlRequest.experiment_id) + return _internal_experiment_id(); +} +template +PROTOBUF_ALWAYS_INLINE void ControlRequest::set_experiment_id(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + _impl_.experiment_id_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.ControlRequest.experiment_id) +} +inline ::std::string* PROTOBUF_NONNULL ControlRequest::mutable_experiment_id() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + ::std::string* _s = _internal_mutable_experiment_id(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.ControlRequest.experiment_id) + return _s; +} +inline const ::std::string& ControlRequest::_internal_experiment_id() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.experiment_id_.Get(); +} +inline void ControlRequest::_internal_set_experiment_id(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.experiment_id_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL ControlRequest::_internal_mutable_experiment_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.experiment_id_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE ControlRequest::release_experiment_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.ControlRequest.experiment_id) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000001U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + auto* released = _impl_.experiment_id_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.experiment_id_.Set("", GetArena()); + } + return released; +} +inline void ControlRequest::set_allocated_experiment_id(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + } + _impl_.experiment_id_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.experiment_id_.IsDefault()) { + _impl_.experiment_id_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.ControlRequest.experiment_id) +} + +// string agent_id = 2 [json_name = "agentId", (.buf.validate.field) = { +inline void ControlRequest::clear_agent_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.agent_id_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); +} +inline const ::std::string& ControlRequest::agent_id() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ControlRequest.agent_id) + return _internal_agent_id(); +} +template +PROTOBUF_ALWAYS_INLINE void ControlRequest::set_agent_id(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + _impl_.agent_id_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.ControlRequest.agent_id) +} +inline ::std::string* PROTOBUF_NONNULL ControlRequest::mutable_agent_id() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + ::std::string* _s = _internal_mutable_agent_id(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.ControlRequest.agent_id) + return _s; +} +inline const ::std::string& ControlRequest::_internal_agent_id() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.agent_id_.Get(); +} +inline void ControlRequest::_internal_set_agent_id(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.agent_id_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL ControlRequest::_internal_mutable_agent_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.agent_id_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE ControlRequest::release_agent_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.ControlRequest.agent_id) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000002U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + auto* released = _impl_.agent_id_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.agent_id_.Set("", GetArena()); + } + return released; +} +inline void ControlRequest::set_allocated_agent_id(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + } + _impl_.agent_id_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.agent_id_.IsDefault()) { + _impl_.agent_id_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.ControlRequest.agent_id) +} + +// string location = 3 [json_name = "location", (.buf.validate.field) = { +inline void ControlRequest::clear_location() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.location_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); +} +inline const ::std::string& ControlRequest::location() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ControlRequest.location) + return _internal_location(); +} +template +PROTOBUF_ALWAYS_INLINE void ControlRequest::set_location(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + _impl_.location_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.ControlRequest.location) +} +inline ::std::string* PROTOBUF_NONNULL ControlRequest::mutable_location() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + ::std::string* _s = _internal_mutable_location(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.ControlRequest.location) + return _s; +} +inline const ::std::string& ControlRequest::_internal_location() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.location_.Get(); +} +inline void ControlRequest::_internal_set_location(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.location_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL ControlRequest::_internal_mutable_location() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.location_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE ControlRequest::release_location() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.ControlRequest.location) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000004U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + auto* released = _impl_.location_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.location_.Set("", GetArena()); + } + return released; +} +inline void ControlRequest::set_allocated_location(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + } + _impl_.location_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.location_.IsDefault()) { + _impl_.location_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.ControlRequest.location) +} + +// bool fleet = 4 [json_name = "fleet"]; +inline void ControlRequest::clear_fleet() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.fleet_ = false; + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); +} +inline bool ControlRequest::fleet() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ControlRequest.fleet) + return _internal_fleet(); +} +inline void ControlRequest::set_fleet(bool value) { + _internal_set_fleet(value); + SetHasBit(_impl_._has_bits_[0], 0x00000020U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.ControlRequest.fleet) +} +inline bool ControlRequest::_internal_fleet() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.fleet_; +} +inline void ControlRequest::_internal_set_fleet(bool value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.fleet_ = value; +} + +// .xtcp_treatment.v1.ControlAction action = 5 [json_name = "action", (.buf.validate.field) = { +inline void ControlRequest::clear_action() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.action_ = 0; + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); +} +inline ::xtcp_treatment::v1::ControlAction ControlRequest::action() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ControlRequest.action) + return _internal_action(); +} +inline void ControlRequest::set_action(::xtcp_treatment::v1::ControlAction value) { + _internal_set_action(value); + SetHasBit(_impl_._has_bits_[0], 0x00000040U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.ControlRequest.action) +} +inline ::xtcp_treatment::v1::ControlAction ControlRequest::_internal_action() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return static_cast<::xtcp_treatment::v1::ControlAction>(_impl_.action_); +} +inline void ControlRequest::_internal_set_action(::xtcp_treatment::v1::ControlAction value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.action_ = value; +} + +// string reason = 6 [json_name = "reason", (.buf.validate.field) = { +inline void ControlRequest::clear_reason() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.reason_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); +} +inline const ::std::string& ControlRequest::reason() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ControlRequest.reason) + return _internal_reason(); +} +template +PROTOBUF_ALWAYS_INLINE void ControlRequest::set_reason(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + _impl_.reason_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.ControlRequest.reason) +} +inline ::std::string* PROTOBUF_NONNULL ControlRequest::mutable_reason() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + ::std::string* _s = _internal_mutable_reason(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.ControlRequest.reason) + return _s; +} +inline const ::std::string& ControlRequest::_internal_reason() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.reason_.Get(); +} +inline void ControlRequest::_internal_set_reason(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.reason_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL ControlRequest::_internal_mutable_reason() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.reason_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE ControlRequest::release_reason() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.ControlRequest.reason) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000008U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + auto* released = _impl_.reason_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.reason_.Set("", GetArena()); + } + return released; +} +inline void ControlRequest::set_allocated_reason(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + } + _impl_.reason_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.reason_.IsDefault()) { + _impl_.reason_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.ControlRequest.reason) +} + +// string request_id = 7 [json_name = "requestId", (.buf.validate.field) = { +inline void ControlRequest::clear_request_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.request_id_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); +} +inline const ::std::string& ControlRequest::request_id() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ControlRequest.request_id) + return _internal_request_id(); +} +template +PROTOBUF_ALWAYS_INLINE void ControlRequest::set_request_id(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + _impl_.request_id_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.ControlRequest.request_id) +} +inline ::std::string* PROTOBUF_NONNULL ControlRequest::mutable_request_id() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + ::std::string* _s = _internal_mutable_request_id(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.ControlRequest.request_id) + return _s; +} +inline const ::std::string& ControlRequest::_internal_request_id() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.request_id_.Get(); +} +inline void ControlRequest::_internal_set_request_id(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.request_id_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL ControlRequest::_internal_mutable_request_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.request_id_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE ControlRequest::release_request_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.ControlRequest.request_id) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000010U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + auto* released = _impl_.request_id_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.request_id_.Set("", GetArena()); + } + return released; +} +inline void ControlRequest::set_allocated_request_id(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + } + _impl_.request_id_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.request_id_.IsDefault()) { + _impl_.request_id_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.ControlRequest.request_id) +} + +// ------------------------------------------------------------------- + +// ControlResponse + +// uint32 affected_agents = 1 [json_name = "affectedAgents"]; +inline void ControlResponse::clear_affected_agents() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.affected_agents_ = 0u; + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +inline ::uint32_t ControlResponse::affected_agents() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ControlResponse.affected_agents) + return _internal_affected_agents(); +} +inline void ControlResponse::set_affected_agents(::uint32_t value) { + _internal_set_affected_agents(value); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.ControlResponse.affected_agents) +} +inline ::uint32_t ControlResponse::_internal_affected_agents() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.affected_agents_; +} +inline void ControlResponse::_internal_set_affected_agents(::uint32_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.affected_agents_ = value; +} + +// ------------------------------------------------------------------- + +// InspectRequest + +// string experiment_id = 1 [json_name = "experimentId", (.buf.validate.field) = { +inline void InspectRequest::clear_experiment_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.experiment_id_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +inline const ::std::string& InspectRequest::experiment_id() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.InspectRequest.experiment_id) + return _internal_experiment_id(); +} +template +PROTOBUF_ALWAYS_INLINE void InspectRequest::set_experiment_id(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + _impl_.experiment_id_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.InspectRequest.experiment_id) +} +inline ::std::string* PROTOBUF_NONNULL InspectRequest::mutable_experiment_id() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + ::std::string* _s = _internal_mutable_experiment_id(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.InspectRequest.experiment_id) + return _s; +} +inline const ::std::string& InspectRequest::_internal_experiment_id() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.experiment_id_.Get(); +} +inline void InspectRequest::_internal_set_experiment_id(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.experiment_id_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL InspectRequest::_internal_mutable_experiment_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.experiment_id_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE InspectRequest::release_experiment_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.InspectRequest.experiment_id) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000001U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + auto* released = _impl_.experiment_id_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.experiment_id_.Set("", GetArena()); + } + return released; +} +inline void InspectRequest::set_allocated_experiment_id(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + } + _impl_.experiment_id_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.experiment_id_.IsDefault()) { + _impl_.experiment_id_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.InspectRequest.experiment_id) +} + +// string agent_id = 2 [json_name = "agentId", (.buf.validate.field) = { +inline void InspectRequest::clear_agent_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.agent_id_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); +} +inline const ::std::string& InspectRequest::agent_id() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.InspectRequest.agent_id) + return _internal_agent_id(); +} +template +PROTOBUF_ALWAYS_INLINE void InspectRequest::set_agent_id(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + _impl_.agent_id_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.InspectRequest.agent_id) +} +inline ::std::string* PROTOBUF_NONNULL InspectRequest::mutable_agent_id() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + ::std::string* _s = _internal_mutable_agent_id(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.InspectRequest.agent_id) + return _s; +} +inline const ::std::string& InspectRequest::_internal_agent_id() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.agent_id_.Get(); +} +inline void InspectRequest::_internal_set_agent_id(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.agent_id_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL InspectRequest::_internal_mutable_agent_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.agent_id_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE InspectRequest::release_agent_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.InspectRequest.agent_id) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000002U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + auto* released = _impl_.agent_id_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.agent_id_.Set("", GetArena()); + } + return released; +} +inline void InspectRequest::set_allocated_agent_id(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + } + _impl_.agent_id_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.agent_id_.IsDefault()) { + _impl_.agent_id_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.InspectRequest.agent_id) +} + +// ------------------------------------------------------------------- + +// InspectResponse + +// repeated .xtcp_treatment.v1.Experiment experiments = 1 [json_name = "experiments"]; +inline int InspectResponse::_internal_experiments_size() const { + return _internal_experiments().size(); +} +inline int InspectResponse::experiments_size() const { + return _internal_experiments_size(); +} +inline const ::xtcp_treatment::v1::Experiment& InspectResponse::experiments(int index) const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.InspectResponse.experiments) + return _internal_experiments().Get(index); +} +inline ::xtcp_treatment::v1::Experiment* PROTOBUF_NONNULL InspectResponse::mutable_experiments(int index) + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.InspectResponse.experiments) + return _internal_mutable_experiments()->Mutable(index); +} +inline ::xtcp_treatment::v1::Experiment* PROTOBUF_NONNULL InspectResponse::add_experiments() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + ::google::protobuf::internal::TSanWrite(&_impl_); + ::xtcp_treatment::v1::Experiment* _add = + _internal_mutable_experiments()->InternalAddWithArena( + ::google::protobuf::MessageLite::internal_visibility(), GetArena()); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + // @@protoc_insertion_point(field_add:xtcp_treatment.v1.InspectResponse.experiments) + return _add; +} +inline const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::Experiment>& InspectResponse::experiments() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_list:xtcp_treatment.v1.InspectResponse.experiments) + return _internal_experiments(); +} +inline ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::Experiment>* PROTOBUF_NONNULL +InspectResponse::mutable_experiments() ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + // @@protoc_insertion_point(field_mutable_list:xtcp_treatment.v1.InspectResponse.experiments) + ::google::protobuf::internal::TSanWrite(&_impl_); + return _internal_mutable_experiments(); +} +inline const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::Experiment>& +InspectResponse::_internal_experiments() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.experiments_; +} +inline ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::Experiment>* PROTOBUF_NONNULL +InspectResponse::_internal_mutable_experiments() { + ::google::protobuf::internal::TSanRead(&_impl_); + return &_impl_.experiments_; +} + +// repeated .xtcp_treatment.v1.DesiredState desired = 2 [json_name = "desired"]; +inline int InspectResponse::_internal_desired_size() const { + return _internal_desired().size(); +} +inline int InspectResponse::desired_size() const { + return _internal_desired_size(); +} +inline const ::xtcp_treatment::v1::DesiredState& InspectResponse::desired(int index) const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.InspectResponse.desired) + return _internal_desired().Get(index); +} +inline ::xtcp_treatment::v1::DesiredState* PROTOBUF_NONNULL InspectResponse::mutable_desired(int index) + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.InspectResponse.desired) + return _internal_mutable_desired()->Mutable(index); +} +inline ::xtcp_treatment::v1::DesiredState* PROTOBUF_NONNULL InspectResponse::add_desired() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + ::google::protobuf::internal::TSanWrite(&_impl_); + ::xtcp_treatment::v1::DesiredState* _add = + _internal_mutable_desired()->InternalAddWithArena( + ::google::protobuf::MessageLite::internal_visibility(), GetArena()); + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + // @@protoc_insertion_point(field_add:xtcp_treatment.v1.InspectResponse.desired) + return _add; +} +inline const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::DesiredState>& InspectResponse::desired() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_list:xtcp_treatment.v1.InspectResponse.desired) + return _internal_desired(); +} +inline ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::DesiredState>* PROTOBUF_NONNULL +InspectResponse::mutable_desired() ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + // @@protoc_insertion_point(field_mutable_list:xtcp_treatment.v1.InspectResponse.desired) + ::google::protobuf::internal::TSanWrite(&_impl_); + return _internal_mutable_desired(); +} +inline const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::DesiredState>& +InspectResponse::_internal_desired() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.desired_; +} +inline ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::DesiredState>* PROTOBUF_NONNULL +InspectResponse::_internal_mutable_desired() { + ::google::protobuf::internal::TSanRead(&_impl_); + return &_impl_.desired_; +} + +// repeated .xtcp_treatment.v1.CheckInRequest observed = 3 [json_name = "observed"]; +inline int InspectResponse::_internal_observed_size() const { + return _internal_observed().size(); +} +inline int InspectResponse::observed_size() const { + return _internal_observed_size(); +} +inline const ::xtcp_treatment::v1::CheckInRequest& InspectResponse::observed(int index) const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.InspectResponse.observed) + return _internal_observed().Get(index); +} +inline ::xtcp_treatment::v1::CheckInRequest* PROTOBUF_NONNULL InspectResponse::mutable_observed(int index) + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.InspectResponse.observed) + return _internal_mutable_observed()->Mutable(index); +} +inline ::xtcp_treatment::v1::CheckInRequest* PROTOBUF_NONNULL InspectResponse::add_observed() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + ::google::protobuf::internal::TSanWrite(&_impl_); + ::xtcp_treatment::v1::CheckInRequest* _add = + _internal_mutable_observed()->InternalAddWithArena( + ::google::protobuf::MessageLite::internal_visibility(), GetArena()); + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + // @@protoc_insertion_point(field_add:xtcp_treatment.v1.InspectResponse.observed) + return _add; +} +inline const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::CheckInRequest>& InspectResponse::observed() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_list:xtcp_treatment.v1.InspectResponse.observed) + return _internal_observed(); +} +inline ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::CheckInRequest>* PROTOBUF_NONNULL +InspectResponse::mutable_observed() ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + // @@protoc_insertion_point(field_mutable_list:xtcp_treatment.v1.InspectResponse.observed) + ::google::protobuf::internal::TSanWrite(&_impl_); + return _internal_mutable_observed(); +} +inline const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::CheckInRequest>& +InspectResponse::_internal_observed() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.observed_; +} +inline ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::CheckInRequest>* PROTOBUF_NONNULL +InspectResponse::_internal_mutable_observed() { + ::google::protobuf::internal::TSanRead(&_impl_); + return &_impl_.observed_; +} + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif // __GNUC__ + +// @@protoc_insertion_point(namespace_scope) +} // namespace v1 +} // namespace xtcp_treatment + + +namespace google { +namespace protobuf { + +template <> +struct is_proto_enum<::xtcp_treatment::v1::ControlAction> : std::true_type {}; +template <> +inline const EnumDescriptor* PROTOBUF_NONNULL GetEnumDescriptor<::xtcp_treatment::v1::ControlAction>() { + return ::xtcp_treatment::v1::ControlAction_descriptor(); +} + +} // namespace protobuf +} // namespace google + +// @@protoc_insertion_point(global_scope) + +#include "google/protobuf/port_undef.inc" +// clang-format on + +#endif // xtcp_5ftreatment_2fv1_2foperator_2eproto_2epb_2eh diff --git a/gen/cpp/xtcp_treatment/v1/treatment.grpc.pb.cc b/gen/cpp/xtcp_treatment/v1/treatment.grpc.pb.cc new file mode 100644 index 000000000..fe99fc76f --- /dev/null +++ b/gen/cpp/xtcp_treatment/v1/treatment.grpc.pb.cc @@ -0,0 +1,29 @@ +// Generated by the gRPC C++ plugin. +// If you make any local change, they will be lost. +// source: xtcp_treatment/v1/treatment.proto + +#include "xtcp_treatment/v1/treatment.pb.h" +#include "xtcp_treatment/v1/treatment.grpc.pb.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +namespace xtcp_treatment { +namespace v1 { + +} // namespace xtcp_treatment +} // namespace v1 +#include + diff --git a/gen/cpp/xtcp_treatment/v1/treatment.grpc.pb.h b/gen/cpp/xtcp_treatment/v1/treatment.grpc.pb.h new file mode 100644 index 000000000..bc6412b0f --- /dev/null +++ b/gen/cpp/xtcp_treatment/v1/treatment.grpc.pb.h @@ -0,0 +1,56 @@ +// Generated by the gRPC C++ plugin. +// If you make any local change, they will be lost. +// source: xtcp_treatment/v1/treatment.proto +// Original file comments: +// +// xTCP - treatment +// +// Shared message types for the TCP route treatment control plane: what a +// treatment is, how it is assigned to an agent, what an agent reports back, +// and how an experiment over a cohort of agents is described and evidenced. +// +// Wire contract only. The controller and the agent work on Go domain types +// and convert at the gRPC boundary; nothing here is a storage schema. +// +// Validation: every client-controlled field carries a buf.validate rule that +// mirrors the bound in pkg/treatment/validate.go, so a malformed request is +// rejected before any handler runs. The Go validators remain authoritative +// for contextual rules that need more than one message (duplicate assignment +// IDs, overlapping destinations, cohort/baseline agreement). +// +// See docs/tcp-controller.md. +// +#ifndef GRPC_xtcp_5ftreatment_2fv1_2ftreatment_2eproto__INCLUDED +#define GRPC_xtcp_5ftreatment_2fv1_2ftreatment_2eproto__INCLUDED + +#include "xtcp_treatment/v1/treatment.pb.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xtcp_treatment { +namespace v1 { + +} // namespace v1 +} // namespace xtcp_treatment + + +#include +#endif // GRPC_xtcp_5ftreatment_2fv1_2ftreatment_2eproto__INCLUDED diff --git a/gen/cpp/xtcp_treatment/v1/treatment.pb.cc b/gen/cpp/xtcp_treatment/v1/treatment.pb.cc new file mode 100644 index 000000000..d9958320c --- /dev/null +++ b/gen/cpp/xtcp_treatment/v1/treatment.pb.cc @@ -0,0 +1,8186 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE +// source: xtcp_treatment/v1/treatment.proto +// Protobuf C++ Version: 7.36.2 + +#include "xtcp_treatment/v1/treatment.pb.h" + +#include +#include +#include "google/protobuf/io/coded_stream.h" +#include "google/protobuf/generated_message_tctable_impl.h" +#include "google/protobuf/internal_visibility.h" +#include "google/protobuf/extension_set.h" +#include "google/protobuf/generated_message_util.h" +#include "google/protobuf/wire_format_lite.h" +#include "google/protobuf/descriptor.h" +#include "google/protobuf/generated_message_reflection.h" +#include "google/protobuf/reflection_ops.h" +#include "google/protobuf/wire_format.h" +// @@protoc_insertion_point(includes) + +// Must be included last. +#include "google/protobuf/port_def.inc" +PROTOBUF_PRAGMA_INIT_SEG +namespace _pb = ::google::protobuf; +namespace _pbi = ::google::protobuf::internal; +namespace _fl = ::google::protobuf::internal::field_layout; +#ifdef PROTOBUF_MESSAGE_GLOBALS +namespace { +PROTOBUF_CONSTINIT ::google::protobuf::internal::ReflectionData + file_reflection_data[] = { + // ::xtcp_treatment::v1::TCPSettings + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, /* tracker*/ nullptr,}, + // ::xtcp_treatment::v1::Treatment + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, /* tracker*/ nullptr,}, + // ::xtcp_treatment::v1::Assignment + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, /* tracker*/ nullptr,}, + // ::xtcp_treatment::v1::DesiredState + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, /* tracker*/ nullptr,}, + // ::xtcp_treatment::v1::Capabilities + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, /* tracker*/ nullptr,}, + // ::xtcp_treatment::v1::ApplicationRecord + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, /* tracker*/ nullptr,}, + // ::xtcp_treatment::v1::LocalState + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, /* tracker*/ nullptr,}, + // ::xtcp_treatment::v1::HostEvidence + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, /* tracker*/ nullptr,}, + // ::xtcp_treatment::v1::Evidence + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, /* tracker*/ nullptr,}, + // ::xtcp_treatment::v1::ExperimentPolicy + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, /* tracker*/ nullptr,}, + // ::xtcp_treatment::v1::Phase + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, /* tracker*/ nullptr,}, + // ::xtcp_treatment::v1::Experiment + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, /* tracker*/ nullptr,}, +}; +} // namespace +#endif +namespace xtcp_treatment { +namespace v1 { +class TCPSettings::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_._has_bits_); +}; + +constexpr TCPSettings::ParseTableT_ TCPSettings::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_._has_bits_), + 0, // no _extensions_ + 7, 56, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294967168, // skipmap + offsetof(ParseTableT_, field_entries), + 7, // num_field_entries + 0, // num_aux_entries + offsetof(ParseTableT_, field_names), // no aux_entries + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment::v1::TCPSettings>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + {::_pbi::TcParser::MiniParse, {}}, + // optional string congestion_control = 1 [json_name = "congestionControl", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {10, 0, 0, + PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.congestion_control_)}}, + // bool congestion_lock = 2 [json_name = "congestionLock"]; + {::_pbi::TcParser::SingularVarintNoZag1(), + {16, 4, 0, + PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.congestion_lock_)}}, + // optional uint32 rto_min_ms = 3 [json_name = "rtoMinMs", (.buf.validate.field) = { + {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(TCPSettings, _impl_.rto_min_ms_), 1>(), + {24, 1, 0, + PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.rto_min_ms_)}}, + // optional uint32 initial_congestion_window = 4 [json_name = "initialCongestionWindow", (.buf.validate.field) = { + {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(TCPSettings, _impl_.initial_congestion_window_), 2>(), + {32, 2, 0, + PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.initial_congestion_window_)}}, + // optional uint32 initial_receive_window = 5 [json_name = "initialReceiveWindow", (.buf.validate.field) = { + {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(TCPSettings, _impl_.initial_receive_window_), 3>(), + {40, 3, 0, + PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.initial_receive_window_)}}, + // optional bool quick_ack = 6 [json_name = "quickAck"]; + {::_pbi::TcParser::SingularVarintNoZag1(), + {48, 5, 0, + PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.quick_ack_)}}, + // optional bool ecn = 7 [json_name = "ecn"]; + {::_pbi::TcParser::SingularVarintNoZag1(), + {56, 6, 0, + PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.ecn_)}}, + }}, {{ + 65535, 65535 + }}, {{ + // optional string congestion_control = 1 [json_name = "congestionControl", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.congestion_control_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // bool congestion_lock = 2 [json_name = "congestionLock"]; + {PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.congestion_lock_), _Internal::kHasBitsOffset + 4, 0, (0 | ::_fl::kFcOptional | ::_fl::kBool)}, + // optional uint32 rto_min_ms = 3 [json_name = "rtoMinMs", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.rto_min_ms_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt32)}, + // optional uint32 initial_congestion_window = 4 [json_name = "initialCongestionWindow", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.initial_congestion_window_), _Internal::kHasBitsOffset + 2, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt32)}, + // optional uint32 initial_receive_window = 5 [json_name = "initialReceiveWindow", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.initial_receive_window_), _Internal::kHasBitsOffset + 3, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt32)}, + // optional bool quick_ack = 6 [json_name = "quickAck"]; + {PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.quick_ack_), _Internal::kHasBitsOffset + 5, 0, (0 | ::_fl::kFcOptional | ::_fl::kBool)}, + // optional bool ecn = 7 [json_name = "ecn"]; + {PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.ecn_), _Internal::kHasBitsOffset + 6, 0, (0 | ::_fl::kFcOptional | ::_fl::kBool)}, + }}, + // no aux_entries + {{ + "\35\22\0\0\0\0\0\0" + "xtcp_treatment.v1.TCPSettings" + "congestion_control" + }}, + }; +} + + +inline constexpr TCPSettings::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : congestion_control_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + rto_min_ms_{0u}, + initial_congestion_window_{0u}, + initial_receive_window_{0u}, + congestion_lock_{false}, + quick_ack_{false}, + ecn_{false} {} + +template +constexpr TCPSettings::TCPSettings(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL TCPSettings::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) TCPSettings(arena); +} +constexpr auto TCPSettings::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::CopyInit(sizeof(TCPSettings), alignof(TCPSettings)); +} +constexpr auto TCPSettings::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &TCPSettings::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &TCPSettings::SharedDtor, + Super_::GetClearImpl(), &TCPSettings::ByteSizeLong, + &TCPSettings::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[0], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct TCPSettingsGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr TCPSettingsGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + TCPSettings_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(TCPSettings::InternalGenerateClassData_( + _default, &TCPSettings_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~TCPSettingsGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) TCPSettings _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(TCPSettingsGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST TCPSettingsGlobalsTypeInternal TCPSettings_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* TCPSettings_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return TCPSettings_globals_.GetClassData(); +#else + return TCPSettings_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +class HostEvidence::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(HostEvidence, _impl_._has_bits_); +}; + +constexpr HostEvidence::ParseTableT_ HostEvidence::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(HostEvidence, _impl_._has_bits_), + 0, // no _extensions_ + 8, 56, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294967040, // skipmap + offsetof(ParseTableT_, field_entries), + 8, // num_field_entries + 0, // num_aux_entries + offsetof(ParseTableT_, field_names), // no aux_entries + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment::v1::HostEvidence>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + // bool collection_healthy = 8 [json_name = "collectionHealthy"]; + {::_pbi::TcParser::SingularVarintNoZag1(), + {64, 7, 0, + PROTOBUF_FIELD_OFFSET(HostEvidence, _impl_.collection_healthy_)}}, + // string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {10, 0, 0, + PROTOBUF_FIELD_OFFSET(HostEvidence, _impl_.agent_id_)}}, + // uint64 sockets = 2 [json_name = "sockets"]; + {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(HostEvidence, _impl_.sockets_), 1>(), + {16, 1, 0, + PROTOBUF_FIELD_OFFSET(HostEvidence, _impl_.sockets_)}}, + // uint64 bytes_acked = 3 [json_name = "bytesAcked"]; + {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(HostEvidence, _impl_.bytes_acked_), 2>(), + {24, 2, 0, + PROTOBUF_FIELD_OFFSET(HostEvidence, _impl_.bytes_acked_)}}, + // double goodput_bps = 4 [json_name = "goodputBps", (.buf.validate.field) = { + {::_pbi::TcParser::FastF64S1, + {33, 3, 0, + PROTOBUF_FIELD_OFFSET(HostEvidence, _impl_.goodput_bps_)}}, + // double retransmission_ratio = 5 [json_name = "retransmissionRatio", (.buf.validate.field) = { + {::_pbi::TcParser::FastF64S1, + {41, 4, 0, + PROTOBUF_FIELD_OFFSET(HostEvidence, _impl_.retransmission_ratio_)}}, + // double rtt_p95_ms = 6 [json_name = "rttP95Ms", (.buf.validate.field) = { + {::_pbi::TcParser::FastF64S1, + {49, 5, 0, + PROTOBUF_FIELD_OFFSET(HostEvidence, _impl_.rtt_p95_ms_)}}, + // uint64 exposed_sockets = 7 [json_name = "exposedSockets"]; + {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(HostEvidence, _impl_.exposed_sockets_), 6>(), + {56, 6, 0, + PROTOBUF_FIELD_OFFSET(HostEvidence, _impl_.exposed_sockets_)}}, + }}, {{ + 65535, 65535 + }}, {{ + // string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(HostEvidence, _impl_.agent_id_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // uint64 sockets = 2 [json_name = "sockets"]; + {PROTOBUF_FIELD_OFFSET(HostEvidence, _impl_.sockets_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt64)}, + // uint64 bytes_acked = 3 [json_name = "bytesAcked"]; + {PROTOBUF_FIELD_OFFSET(HostEvidence, _impl_.bytes_acked_), _Internal::kHasBitsOffset + 2, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt64)}, + // double goodput_bps = 4 [json_name = "goodputBps", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(HostEvidence, _impl_.goodput_bps_), _Internal::kHasBitsOffset + 3, 0, (0 | ::_fl::kFcOptional | ::_fl::kDouble)}, + // double retransmission_ratio = 5 [json_name = "retransmissionRatio", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(HostEvidence, _impl_.retransmission_ratio_), _Internal::kHasBitsOffset + 4, 0, (0 | ::_fl::kFcOptional | ::_fl::kDouble)}, + // double rtt_p95_ms = 6 [json_name = "rttP95Ms", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(HostEvidence, _impl_.rtt_p95_ms_), _Internal::kHasBitsOffset + 5, 0, (0 | ::_fl::kFcOptional | ::_fl::kDouble)}, + // uint64 exposed_sockets = 7 [json_name = "exposedSockets"]; + {PROTOBUF_FIELD_OFFSET(HostEvidence, _impl_.exposed_sockets_), _Internal::kHasBitsOffset + 6, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt64)}, + // bool collection_healthy = 8 [json_name = "collectionHealthy"]; + {PROTOBUF_FIELD_OFFSET(HostEvidence, _impl_.collection_healthy_), _Internal::kHasBitsOffset + 7, 0, (0 | ::_fl::kFcOptional | ::_fl::kBool)}, + }}, + // no aux_entries + {{ + "\36\10\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "xtcp_treatment.v1.HostEvidence" + "agent_id" + }}, + }; +} + + +inline constexpr HostEvidence::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : agent_id_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + sockets_{::uint64_t{0u}}, + bytes_acked_{::uint64_t{0u}}, + goodput_bps_{0}, + retransmission_ratio_{0}, + rtt_p95_ms_{0}, + exposed_sockets_{::uint64_t{0u}}, + collection_healthy_{false} {} + +template +constexpr HostEvidence::HostEvidence(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL HostEvidence::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) HostEvidence(arena); +} +constexpr auto HostEvidence::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::CopyInit(sizeof(HostEvidence), alignof(HostEvidence)); +} +constexpr auto HostEvidence::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &HostEvidence::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &HostEvidence::SharedDtor, + Super_::GetClearImpl(), &HostEvidence::ByteSizeLong, + &HostEvidence::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(HostEvidence, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[7], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct HostEvidenceGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr HostEvidenceGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + HostEvidence_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(HostEvidence::InternalGenerateClassData_( + _default, &HostEvidence_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~HostEvidenceGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) HostEvidence _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(HostEvidenceGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST HostEvidenceGlobalsTypeInternal HostEvidence_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* HostEvidence_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return HostEvidence_globals_.GetClassData(); +#else + return HostEvidence_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +class Capabilities::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(Capabilities, _impl_._has_bits_); +}; + +constexpr Capabilities::ParseTableT_ Capabilities::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(Capabilities, _impl_._has_bits_), + 0, // no _extensions_ + 5, 56, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294967264, // skipmap + offsetof(ParseTableT_, field_entries), + 5, // num_field_entries + 0, // num_aux_entries + offsetof(ParseTableT_, field_names), // no aux_entries + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment::v1::Capabilities>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + {::_pbi::TcParser::MiniParse, {}}, + // string boot_id = 1 [json_name = "bootId", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {10, 1, 0, + PROTOBUF_FIELD_OFFSET(Capabilities, _impl_.boot_id_)}}, + // string kernel_release = 2 [json_name = "kernelRelease", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {18, 2, 0, + PROTOBUF_FIELD_OFFSET(Capabilities, _impl_.kernel_release_)}}, + // repeated string congestion_algorithms = 3 [json_name = "congestionAlgorithms", (.buf.validate.field) = { + {::_pbi::TcParser::FastUR1, + {26, 0, 0, + PROTOBUF_FIELD_OFFSET(Capabilities, _impl_.congestion_algorithms_)}}, + // bool simple_host_routing = 4 [json_name = "simpleHostRouting"]; + {::_pbi::TcParser::SingularVarintNoZag1(), + {32, 3, 0, + PROTOBUF_FIELD_OFFSET(Capabilities, _impl_.simple_host_routing_)}}, + // bool actuator_enabled = 5 [json_name = "actuatorEnabled"]; + {::_pbi::TcParser::SingularVarintNoZag1(), + {40, 4, 0, + PROTOBUF_FIELD_OFFSET(Capabilities, _impl_.actuator_enabled_)}}, + {::_pbi::TcParser::MiniParse, {}}, + {::_pbi::TcParser::MiniParse, {}}, + }}, {{ + 65535, 65535 + }}, {{ + // string boot_id = 1 [json_name = "bootId", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Capabilities, _impl_.boot_id_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // string kernel_release = 2 [json_name = "kernelRelease", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Capabilities, _impl_.kernel_release_), _Internal::kHasBitsOffset + 2, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // repeated string congestion_algorithms = 3 [json_name = "congestionAlgorithms", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Capabilities, _impl_.congestion_algorithms_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcRepeated | ::_fl::kUtf8String | ::_fl::kRepSString)}, + // bool simple_host_routing = 4 [json_name = "simpleHostRouting"]; + {PROTOBUF_FIELD_OFFSET(Capabilities, _impl_.simple_host_routing_), _Internal::kHasBitsOffset + 3, 0, (0 | ::_fl::kFcOptional | ::_fl::kBool)}, + // bool actuator_enabled = 5 [json_name = "actuatorEnabled"]; + {PROTOBUF_FIELD_OFFSET(Capabilities, _impl_.actuator_enabled_), _Internal::kHasBitsOffset + 4, 0, (0 | ::_fl::kFcOptional | ::_fl::kBool)}, + }}, + // no aux_entries + {{ + "\36\7\16\25\0\0\0\0" + "xtcp_treatment.v1.Capabilities" + "boot_id" + "kernel_release" + "congestion_algorithms" + }}, + }; +} + + +inline constexpr Capabilities::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : congestion_algorithms_ { visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment::v1::Capabilities, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Capabilities, _impl_.congestion_algorithms_)>() + } + , + boot_id_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + kernel_release_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + simple_host_routing_{false}, + actuator_enabled_{false} {} + +template +constexpr Capabilities::Capabilities(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL Capabilities::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) Capabilities(arena); +} +constexpr auto Capabilities::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::CopyInit(sizeof(Capabilities), alignof(Capabilities)); +} +constexpr auto Capabilities::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &Capabilities::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &Capabilities::SharedDtor, + Super_::GetClearImpl(), &Capabilities::ByteSizeLong, + &Capabilities::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(Capabilities, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[4], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct CapabilitiesGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr CapabilitiesGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + Capabilities_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(Capabilities::InternalGenerateClassData_( + _default, &Capabilities_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~CapabilitiesGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) Capabilities _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(CapabilitiesGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST CapabilitiesGlobalsTypeInternal Capabilities_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* Capabilities_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return Capabilities_globals_.GetClassData(); +#else + return Capabilities_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +class Treatment::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(Treatment, _impl_._has_bits_); +}; + +constexpr Treatment::ParseTableT_ Treatment::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(Treatment, _impl_._has_bits_), + 0, // no _extensions_ + 5, 56, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294967264, // skipmap + offsetof(ParseTableT_, field_entries), + 5, // num_field_entries + 1, // num_aux_entries + offsetof(ParseTableT_, aux_entries), + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment::v1::Treatment>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + {::_pbi::TcParser::MiniParse, {}}, + // string id = 1 [json_name = "id", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {10, 0, 0, + PROTOBUF_FIELD_OFFSET(Treatment, _impl_.id_)}}, + // uint64 revision = 2 [json_name = "revision", (.buf.validate.field) = { + {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(Treatment, _impl_.revision_), 3>(), + {16, 3, 0, + PROTOBUF_FIELD_OFFSET(Treatment, _impl_.revision_)}}, + // string destination = 3 [json_name = "destination", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {26, 1, 0, + PROTOBUF_FIELD_OFFSET(Treatment, _impl_.destination_)}}, + // .xtcp_treatment.v1.TCPSettings settings = 4 [json_name = "settings", (.buf.validate.field) = { + {::_pbi::TcParser::FastMtS1, + {34, 2, 0, + PROTOBUF_FIELD_OFFSET(Treatment, _impl_.settings_)}}, + // bool ecn_fabric_verified = 5 [json_name = "ecnFabricVerified"]; + {::_pbi::TcParser::SingularVarintNoZag1(), + {40, 4, 0, + PROTOBUF_FIELD_OFFSET(Treatment, _impl_.ecn_fabric_verified_)}}, + {::_pbi::TcParser::MiniParse, {}}, + {::_pbi::TcParser::MiniParse, {}}, + }}, {{ + 65535, 65535 + }}, {{ + // string id = 1 [json_name = "id", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Treatment, _impl_.id_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // uint64 revision = 2 [json_name = "revision", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Treatment, _impl_.revision_), _Internal::kHasBitsOffset + 3, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt64)}, + // string destination = 3 [json_name = "destination", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Treatment, _impl_.destination_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // .xtcp_treatment.v1.TCPSettings settings = 4 [json_name = "settings", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Treatment, _impl_.settings_), _Internal::kHasBitsOffset + 2, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // bool ecn_fabric_verified = 5 [json_name = "ecnFabricVerified"]; + {PROTOBUF_FIELD_OFFSET(Treatment, _impl_.ecn_fabric_verified_), _Internal::kHasBitsOffset + 4, 0, (0 | ::_fl::kFcOptional | ::_fl::kBool)}, + }}, + {{ + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment::v1::TCPSettings>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment::v1::TCPSettings_globals_}, + #endif + }}, + {{ + "\33\2\0\13\0\0\0\0" + "xtcp_treatment.v1.Treatment" + "id" + "destination" + }}, + }; +} + + +inline constexpr Treatment::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : id_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + destination_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + settings_{nullptr}, + revision_{::uint64_t{0u}}, + ecn_fabric_verified_{false} {} + +template +constexpr Treatment::Treatment(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL Treatment::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) Treatment(arena); +} +constexpr auto Treatment::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::CopyInit(sizeof(Treatment), alignof(Treatment)); +} +constexpr auto Treatment::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &Treatment::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &Treatment::SharedDtor, + Super_::GetClearImpl(), &Treatment::ByteSizeLong, + &Treatment::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(Treatment, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[1], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct TreatmentGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr TreatmentGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + Treatment_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(Treatment::InternalGenerateClassData_( + _default, &Treatment_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~TreatmentGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) Treatment _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(TreatmentGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST TreatmentGlobalsTypeInternal Treatment_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* Treatment_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return Treatment_globals_.GetClassData(); +#else + return Treatment_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +class ExperimentPolicy::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(ExperimentPolicy, _impl_._has_bits_); +}; + +constexpr ExperimentPolicy::ParseTableT_ ExperimentPolicy::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(ExperimentPolicy, _impl_._has_bits_), + 0, // no _extensions_ + 9, 120, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294966784, // skipmap + offsetof(ParseTableT_, field_entries), + 9, // num_field_entries + 3, // num_aux_entries + offsetof(ParseTableT_, aux_entries), + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment::v1::ExperimentPolicy>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + {::_pbi::TcParser::MiniParse, {}}, + // .google.protobuf.Duration minimum_observation = 1 [json_name = "minimumObservation", (.buf.validate.field) = { + {::_pbi::TcParser::FastMtS1, + {10, 0, 0, + PROTOBUF_FIELD_OFFSET(ExperimentPolicy, _impl_.minimum_observation_)}}, + // .google.protobuf.Duration maximum_phase = 2 [json_name = "maximumPhase", (.buf.validate.field) = { + {::_pbi::TcParser::FastMtS1, + {18, 1, 1, + PROTOBUF_FIELD_OFFSET(ExperimentPolicy, _impl_.maximum_phase_)}}, + // uint64 minimum_sockets_per_host = 3 [json_name = "minimumSocketsPerHost", (.buf.validate.field) = { + {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(ExperimentPolicy, _impl_.minimum_sockets_per_host_), 3>(), + {24, 3, 0, + PROTOBUF_FIELD_OFFSET(ExperimentPolicy, _impl_.minimum_sockets_per_host_)}}, + // uint64 minimum_bytes_per_host = 4 [json_name = "minimumBytesPerHost", (.buf.validate.field) = { + {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(ExperimentPolicy, _impl_.minimum_bytes_per_host_), 4>(), + {32, 4, 0, + PROTOBUF_FIELD_OFFSET(ExperimentPolicy, _impl_.minimum_bytes_per_host_)}}, + // uint32 minimum_treated_hosts = 5 [json_name = "minimumTreatedHosts", (.buf.validate.field) = { + {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(ExperimentPolicy, _impl_.minimum_treated_hosts_), 8>(), + {40, 8, 0, + PROTOBUF_FIELD_OFFSET(ExperimentPolicy, _impl_.minimum_treated_hosts_)}}, + // double minimum_improvement = 6 [json_name = "minimumImprovement", (.buf.validate.field) = { + {::_pbi::TcParser::FastF64S1, + {49, 5, 0, + PROTOBUF_FIELD_OFFSET(ExperimentPolicy, _impl_.minimum_improvement_)}}, + // double maximum_retransmission_increase = 7 [json_name = "maximumRetransmissionIncrease", (.buf.validate.field) = { + {::_pbi::TcParser::FastF64S1, + {57, 6, 0, + PROTOBUF_FIELD_OFFSET(ExperimentPolicy, _impl_.maximum_retransmission_increase_)}}, + // double maximum_rtt_increase = 8 [json_name = "maximumRttIncrease", (.buf.validate.field) = { + {::_pbi::TcParser::FastF64S1, + {65, 7, 0, + PROTOBUF_FIELD_OFFSET(ExperimentPolicy, _impl_.maximum_rtt_increase_)}}, + // .google.protobuf.Duration maximum_evidence_age = 9 [json_name = "maximumEvidenceAge", (.buf.validate.field) = { + {::_pbi::TcParser::FastMtS1, + {74, 2, 2, + PROTOBUF_FIELD_OFFSET(ExperimentPolicy, _impl_.maximum_evidence_age_)}}, + {::_pbi::TcParser::MiniParse, {}}, + {::_pbi::TcParser::MiniParse, {}}, + {::_pbi::TcParser::MiniParse, {}}, + {::_pbi::TcParser::MiniParse, {}}, + {::_pbi::TcParser::MiniParse, {}}, + {::_pbi::TcParser::MiniParse, {}}, + }}, {{ + 65535, 65535 + }}, {{ + // .google.protobuf.Duration minimum_observation = 1 [json_name = "minimumObservation", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(ExperimentPolicy, _impl_.minimum_observation_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // .google.protobuf.Duration maximum_phase = 2 [json_name = "maximumPhase", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(ExperimentPolicy, _impl_.maximum_phase_), _Internal::kHasBitsOffset + 1, 1, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // uint64 minimum_sockets_per_host = 3 [json_name = "minimumSocketsPerHost", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(ExperimentPolicy, _impl_.minimum_sockets_per_host_), _Internal::kHasBitsOffset + 3, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt64)}, + // uint64 minimum_bytes_per_host = 4 [json_name = "minimumBytesPerHost", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(ExperimentPolicy, _impl_.minimum_bytes_per_host_), _Internal::kHasBitsOffset + 4, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt64)}, + // uint32 minimum_treated_hosts = 5 [json_name = "minimumTreatedHosts", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(ExperimentPolicy, _impl_.minimum_treated_hosts_), _Internal::kHasBitsOffset + 8, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt32)}, + // double minimum_improvement = 6 [json_name = "minimumImprovement", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(ExperimentPolicy, _impl_.minimum_improvement_), _Internal::kHasBitsOffset + 5, 0, (0 | ::_fl::kFcOptional | ::_fl::kDouble)}, + // double maximum_retransmission_increase = 7 [json_name = "maximumRetransmissionIncrease", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(ExperimentPolicy, _impl_.maximum_retransmission_increase_), _Internal::kHasBitsOffset + 6, 0, (0 | ::_fl::kFcOptional | ::_fl::kDouble)}, + // double maximum_rtt_increase = 8 [json_name = "maximumRttIncrease", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(ExperimentPolicy, _impl_.maximum_rtt_increase_), _Internal::kHasBitsOffset + 7, 0, (0 | ::_fl::kFcOptional | ::_fl::kDouble)}, + // .google.protobuf.Duration maximum_evidence_age = 9 [json_name = "maximumEvidenceAge", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(ExperimentPolicy, _impl_.maximum_evidence_age_), _Internal::kHasBitsOffset + 2, 2, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + }}, + {{ + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::google::protobuf::Duration>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::google::protobuf::Duration_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::google::protobuf::Duration>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::google::protobuf::Duration_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::google::protobuf::Duration>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::google::protobuf::Duration_globals_}, + #endif + }}, + {{ + }}, + }; +} + + +inline constexpr ExperimentPolicy::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : minimum_observation_{nullptr}, + maximum_phase_{nullptr}, + maximum_evidence_age_{nullptr}, + minimum_sockets_per_host_{::uint64_t{0u}}, + minimum_bytes_per_host_{::uint64_t{0u}}, + minimum_improvement_{0}, + maximum_retransmission_increase_{0}, + maximum_rtt_increase_{0}, + minimum_treated_hosts_{0u} {} + +template +constexpr ExperimentPolicy::ExperimentPolicy(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL ExperimentPolicy::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) ExperimentPolicy(arena); +} +constexpr auto ExperimentPolicy::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::ZeroInit(sizeof(ExperimentPolicy), alignof(ExperimentPolicy)); +} +constexpr auto ExperimentPolicy::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &ExperimentPolicy::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &ExperimentPolicy::SharedDtor, + Super_::GetClearImpl(), &ExperimentPolicy::ByteSizeLong, + &ExperimentPolicy::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(ExperimentPolicy, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[9], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct ExperimentPolicyGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr ExperimentPolicyGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + ExperimentPolicy_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(ExperimentPolicy::InternalGenerateClassData_( + _default, &ExperimentPolicy_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~ExperimentPolicyGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) ExperimentPolicy _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(ExperimentPolicyGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST ExperimentPolicyGlobalsTypeInternal ExperimentPolicy_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* ExperimentPolicy_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return ExperimentPolicy_globals_.GetClassData(); +#else + return ExperimentPolicy_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +class Evidence::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(Evidence, _impl_._has_bits_); +}; + +constexpr Evidence::ParseTableT_ Evidence::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(Evidence, _impl_._has_bits_), + 0, // no _extensions_ + 8, 56, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294967040, // skipmap + offsetof(ParseTableT_, field_entries), + 8, // num_field_entries + 4, // num_aux_entries + offsetof(ParseTableT_, aux_entries), + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment::v1::Evidence>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + // string artifact_sha256 = 8 [json_name = "artifactSha256", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {66, 4, 0, + PROTOBUF_FIELD_OFFSET(Evidence, _impl_.artifact_sha256_)}}, + // string id = 1 [json_name = "id", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {10, 1, 0, + PROTOBUF_FIELD_OFFSET(Evidence, _impl_.id_)}}, + // .google.protobuf.Timestamp analyzed_at = 2 [json_name = "analyzedAt", (.buf.validate.field) = { + {::_pbi::TcParser::FastMtS1, + {18, 5, 0, + PROTOBUF_FIELD_OFFSET(Evidence, _impl_.analyzed_at_)}}, + // .google.protobuf.Timestamp window_start = 3 [json_name = "windowStart", (.buf.validate.field) = { + {::_pbi::TcParser::FastMtS1, + {26, 6, 1, + PROTOBUF_FIELD_OFFSET(Evidence, _impl_.window_start_)}}, + // .google.protobuf.Timestamp window_end = 4 [json_name = "windowEnd", (.buf.validate.field) = { + {::_pbi::TcParser::FastMtS1, + {34, 7, 2, + PROTOBUF_FIELD_OFFSET(Evidence, _impl_.window_end_)}}, + // string query_version = 5 [json_name = "queryVersion", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {42, 2, 0, + PROTOBUF_FIELD_OFFSET(Evidence, _impl_.query_version_)}}, + // string enrichment_version = 6 [json_name = "enrichmentVersion", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {50, 3, 0, + PROTOBUF_FIELD_OFFSET(Evidence, _impl_.enrichment_version_)}}, + // repeated .xtcp_treatment.v1.HostEvidence hosts = 7 [json_name = "hosts", (.buf.validate.field) = { + {::_pbi::TcParser::FastMtR1, + {58, 0, 3, + PROTOBUF_FIELD_OFFSET(Evidence, _impl_.hosts_)}}, + }}, {{ + 65535, 65535 + }}, {{ + // string id = 1 [json_name = "id", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Evidence, _impl_.id_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // .google.protobuf.Timestamp analyzed_at = 2 [json_name = "analyzedAt", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Evidence, _impl_.analyzed_at_), _Internal::kHasBitsOffset + 5, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // .google.protobuf.Timestamp window_start = 3 [json_name = "windowStart", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Evidence, _impl_.window_start_), _Internal::kHasBitsOffset + 6, 1, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // .google.protobuf.Timestamp window_end = 4 [json_name = "windowEnd", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Evidence, _impl_.window_end_), _Internal::kHasBitsOffset + 7, 2, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // string query_version = 5 [json_name = "queryVersion", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Evidence, _impl_.query_version_), _Internal::kHasBitsOffset + 2, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // string enrichment_version = 6 [json_name = "enrichmentVersion", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Evidence, _impl_.enrichment_version_), _Internal::kHasBitsOffset + 3, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // repeated .xtcp_treatment.v1.HostEvidence hosts = 7 [json_name = "hosts", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Evidence, _impl_.hosts_), _Internal::kHasBitsOffset + 0, 3, (0 | ::_fl::kFcRepeated | ::_fl::kMessage | ::_fl::kTvTable)}, + // string artifact_sha256 = 8 [json_name = "artifactSha256", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Evidence, _impl_.artifact_sha256_), _Internal::kHasBitsOffset + 4, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + }}, + {{ + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::google::protobuf::Timestamp>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::google::protobuf::Timestamp_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::google::protobuf::Timestamp>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::google::protobuf::Timestamp_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::google::protobuf::Timestamp>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::google::protobuf::Timestamp_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment::v1::HostEvidence>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment::v1::HostEvidence_globals_}, + #endif + }}, + {{ + "\32\2\0\0\0\15\22\0\17\0\0\0\0\0\0\0" + "xtcp_treatment.v1.Evidence" + "id" + "query_version" + "enrichment_version" + "artifact_sha256" + }}, + }; +} + + +inline constexpr Evidence::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : hosts_ { visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment::v1::Evidence, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Evidence, _impl_.hosts_)>() + } + , + id_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + query_version_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + enrichment_version_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + artifact_sha256_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + analyzed_at_{nullptr}, + window_start_{nullptr}, + window_end_{nullptr} {} + +template +constexpr Evidence::Evidence(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL Evidence::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) Evidence(arena); +} +constexpr auto Evidence::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::CopyInit(sizeof(Evidence), alignof(Evidence)); +} +constexpr auto Evidence::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &Evidence::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &Evidence::SharedDtor, + Super_::GetClearImpl(), &Evidence::ByteSizeLong, + &Evidence::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(Evidence, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[8], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct EvidenceGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr EvidenceGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + Evidence_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(Evidence::InternalGenerateClassData_( + _default, &Evidence_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~EvidenceGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) Evidence _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(EvidenceGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST EvidenceGlobalsTypeInternal Evidence_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* Evidence_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return Evidence_globals_.GetClassData(); +#else + return Evidence_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +class Phase::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(Phase, _impl_._has_bits_); +}; + +constexpr Phase::ParseTableT_ Phase::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(Phase, _impl_._has_bits_), + 0, // no _extensions_ + 8, 56, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294967040, // skipmap + offsetof(ParseTableT_, field_entries), + 8, // num_field_entries + 4, // num_aux_entries + offsetof(ParseTableT_, aux_entries), + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment::v1::Phase>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + // string reason = 8 [json_name = "reason", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {66, 2, 0, + PROTOBUF_FIELD_OFFSET(Phase, _impl_.reason_)}}, + // uint32 target_hosts = 1 [json_name = "targetHosts", (.buf.validate.field) = { + {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(Phase, _impl_.target_hosts_), 7>(), + {8, 7, 0, + PROTOBUF_FIELD_OFFSET(Phase, _impl_.target_hosts_)}}, + // .google.protobuf.Timestamp issued_at = 2 [json_name = "issuedAt"]; + {::_pbi::TcParser::FastMtS1, + {18, 3, 0, + PROTOBUF_FIELD_OFFSET(Phase, _impl_.issued_at_)}}, + // .google.protobuf.Timestamp observation_started_at = 3 [json_name = "observationStartedAt"]; + {::_pbi::TcParser::FastMtS1, + {26, 4, 1, + PROTOBUF_FIELD_OFFSET(Phase, _impl_.observation_started_at_)}}, + // .google.protobuf.Timestamp evaluated_at = 4 [json_name = "evaluatedAt"]; + {::_pbi::TcParser::FastMtS1, + {34, 5, 2, + PROTOBUF_FIELD_OFFSET(Phase, _impl_.evaluated_at_)}}, + // .xtcp_treatment.v1.Evidence evidence = 5 [json_name = "evidence"]; + {::_pbi::TcParser::FastMtS1, + {42, 6, 3, + PROTOBUF_FIELD_OFFSET(Phase, _impl_.evidence_)}}, + // string outcome = 6 [json_name = "outcome", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {50, 0, 0, + PROTOBUF_FIELD_OFFSET(Phase, _impl_.outcome_)}}, + // string reviewer = 7 [json_name = "reviewer", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {58, 1, 0, + PROTOBUF_FIELD_OFFSET(Phase, _impl_.reviewer_)}}, + }}, {{ + 65535, 65535 + }}, {{ + // uint32 target_hosts = 1 [json_name = "targetHosts", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Phase, _impl_.target_hosts_), _Internal::kHasBitsOffset + 7, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt32)}, + // .google.protobuf.Timestamp issued_at = 2 [json_name = "issuedAt"]; + {PROTOBUF_FIELD_OFFSET(Phase, _impl_.issued_at_), _Internal::kHasBitsOffset + 3, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // .google.protobuf.Timestamp observation_started_at = 3 [json_name = "observationStartedAt"]; + {PROTOBUF_FIELD_OFFSET(Phase, _impl_.observation_started_at_), _Internal::kHasBitsOffset + 4, 1, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // .google.protobuf.Timestamp evaluated_at = 4 [json_name = "evaluatedAt"]; + {PROTOBUF_FIELD_OFFSET(Phase, _impl_.evaluated_at_), _Internal::kHasBitsOffset + 5, 2, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // .xtcp_treatment.v1.Evidence evidence = 5 [json_name = "evidence"]; + {PROTOBUF_FIELD_OFFSET(Phase, _impl_.evidence_), _Internal::kHasBitsOffset + 6, 3, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // string outcome = 6 [json_name = "outcome", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Phase, _impl_.outcome_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // string reviewer = 7 [json_name = "reviewer", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Phase, _impl_.reviewer_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // string reason = 8 [json_name = "reason", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Phase, _impl_.reason_), _Internal::kHasBitsOffset + 2, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + }}, + {{ + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::google::protobuf::Timestamp>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::google::protobuf::Timestamp_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::google::protobuf::Timestamp>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::google::protobuf::Timestamp_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::google::protobuf::Timestamp>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::google::protobuf::Timestamp_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment::v1::Evidence>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment::v1::Evidence_globals_}, + #endif + }}, + {{ + "\27\0\0\0\0\0\7\10\6\0\0\0\0\0\0\0" + "xtcp_treatment.v1.Phase" + "outcome" + "reviewer" + "reason" + }}, + }; +} + + +inline constexpr Phase::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : outcome_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + reviewer_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + reason_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + issued_at_{nullptr}, + observation_started_at_{nullptr}, + evaluated_at_{nullptr}, + evidence_{nullptr}, + target_hosts_{0u} {} + +template +constexpr Phase::Phase(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL Phase::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) Phase(arena); +} +constexpr auto Phase::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::CopyInit(sizeof(Phase), alignof(Phase)); +} +constexpr auto Phase::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &Phase::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &Phase::SharedDtor, + Super_::GetClearImpl(), &Phase::ByteSizeLong, + &Phase::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(Phase, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[10], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct PhaseGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr PhaseGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + Phase_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(Phase::InternalGenerateClassData_( + _default, &Phase_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~PhaseGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) Phase _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(PhaseGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST PhaseGlobalsTypeInternal Phase_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* Phase_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return Phase_globals_.GetClassData(); +#else + return Phase_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +class Assignment::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(Assignment, _impl_._has_bits_); +}; + +constexpr Assignment::ParseTableT_ Assignment::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(Assignment, _impl_._has_bits_), + 0, // no _extensions_ + 7, 56, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294967168, // skipmap + offsetof(ParseTableT_, field_entries), + 7, // num_field_entries + 3, // num_aux_entries + offsetof(ParseTableT_, aux_entries), + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment::v1::Assignment>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + {::_pbi::TcParser::MiniParse, {}}, + // string id = 1 [json_name = "id", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {10, 0, 0, + PROTOBUF_FIELD_OFFSET(Assignment, _impl_.id_)}}, + // string experiment_id = 2 [json_name = "experimentId", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {18, 1, 0, + PROTOBUF_FIELD_OFFSET(Assignment, _impl_.experiment_id_)}}, + // .xtcp_treatment.v1.Treatment treatment = 3 [json_name = "treatment", (.buf.validate.field) = { + {::_pbi::TcParser::FastMtS1, + {26, 2, 0, + PROTOBUF_FIELD_OFFSET(Assignment, _impl_.treatment_)}}, + // .xtcp_treatment.v1.AssignmentMode mode = 4 [json_name = "mode", (.buf.validate.field) = { + {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(Assignment, _impl_.mode_), 6>(), + {32, 6, 0, + PROTOBUF_FIELD_OFFSET(Assignment, _impl_.mode_)}}, + // .google.protobuf.Timestamp lease_expires_at = 5 [json_name = "leaseExpiresAt"]; + {::_pbi::TcParser::FastMtS1, + {42, 3, 1, + PROTOBUF_FIELD_OFFSET(Assignment, _impl_.lease_expires_at_)}}, + // .xtcp_treatment.v1.Treatment predecessor = 6 [json_name = "predecessor"]; + {::_pbi::TcParser::FastMtS1, + {50, 4, 2, + PROTOBUF_FIELD_OFFSET(Assignment, _impl_.predecessor_)}}, + // uint64 generation = 7 [json_name = "generation", (.buf.validate.field) = { + {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(Assignment, _impl_.generation_), 5>(), + {56, 5, 0, + PROTOBUF_FIELD_OFFSET(Assignment, _impl_.generation_)}}, + }}, {{ + 65535, 65535 + }}, {{ + // string id = 1 [json_name = "id", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Assignment, _impl_.id_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // string experiment_id = 2 [json_name = "experimentId", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Assignment, _impl_.experiment_id_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // .xtcp_treatment.v1.Treatment treatment = 3 [json_name = "treatment", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Assignment, _impl_.treatment_), _Internal::kHasBitsOffset + 2, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // .xtcp_treatment.v1.AssignmentMode mode = 4 [json_name = "mode", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Assignment, _impl_.mode_), _Internal::kHasBitsOffset + 6, 0, (0 | ::_fl::kFcOptional | ::_fl::kOpenEnum)}, + // .google.protobuf.Timestamp lease_expires_at = 5 [json_name = "leaseExpiresAt"]; + {PROTOBUF_FIELD_OFFSET(Assignment, _impl_.lease_expires_at_), _Internal::kHasBitsOffset + 3, 1, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // .xtcp_treatment.v1.Treatment predecessor = 6 [json_name = "predecessor"]; + {PROTOBUF_FIELD_OFFSET(Assignment, _impl_.predecessor_), _Internal::kHasBitsOffset + 4, 2, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // uint64 generation = 7 [json_name = "generation", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Assignment, _impl_.generation_), _Internal::kHasBitsOffset + 5, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt64)}, + }}, + {{ + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment::v1::Treatment>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment::v1::Treatment_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::google::protobuf::Timestamp>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::google::protobuf::Timestamp_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment::v1::Treatment>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment::v1::Treatment_globals_}, + #endif + }}, + {{ + "\34\2\15\0\0\0\0\0" + "xtcp_treatment.v1.Assignment" + "id" + "experiment_id" + }}, + }; +} + + +inline constexpr Assignment::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : id_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + experiment_id_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + treatment_{nullptr}, + lease_expires_at_{nullptr}, + predecessor_{nullptr}, + generation_{::uint64_t{0u}}, + mode_{static_cast< ::xtcp_treatment::v1::AssignmentMode >(0)} {} + +template +constexpr Assignment::Assignment(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL Assignment::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) Assignment(arena); +} +constexpr auto Assignment::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::CopyInit(sizeof(Assignment), alignof(Assignment)); +} +constexpr auto Assignment::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &Assignment::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &Assignment::SharedDtor, + Super_::GetClearImpl(), &Assignment::ByteSizeLong, + &Assignment::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(Assignment, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[2], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct AssignmentGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr AssignmentGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + Assignment_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(Assignment::InternalGenerateClassData_( + _default, &Assignment_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~AssignmentGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) Assignment _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(AssignmentGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST AssignmentGlobalsTypeInternal Assignment_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* Assignment_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return Assignment_globals_.GetClassData(); +#else + return Assignment_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +class Experiment::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(Experiment, _impl_._has_bits_); +}; + +constexpr Experiment::ParseTableT_ Experiment::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(Experiment, _impl_._has_bits_), + 0, // no _extensions_ + 13, 120, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294959104, // skipmap + offsetof(ParseTableT_, field_entries), + 13, // num_field_entries + 6, // num_aux_entries + offsetof(ParseTableT_, aux_entries), + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment::v1::Experiment>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + {::_pbi::TcParser::MiniParse, {}}, + // string id = 1 [json_name = "id", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {10, 2, 0, + PROTOBUF_FIELD_OFFSET(Experiment, _impl_.id_)}}, + // string location = 2 [json_name = "location", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {18, 3, 0, + PROTOBUF_FIELD_OFFSET(Experiment, _impl_.location_)}}, + // string hypothesis = 3 [json_name = "hypothesis", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {26, 4, 0, + PROTOBUF_FIELD_OFFSET(Experiment, _impl_.hypothesis_)}}, + // .xtcp_treatment.v1.Treatment treatment = 4 [json_name = "treatment", (.buf.validate.field) = { + {::_pbi::TcParser::FastMtS1, + {34, 6, 0, + PROTOBUF_FIELD_OFFSET(Experiment, _impl_.treatment_)}}, + // .xtcp_treatment.v1.ExperimentPolicy policy = 5 [json_name = "policy", (.buf.validate.field) = { + {::_pbi::TcParser::FastMtS1, + {42, 7, 1, + PROTOBUF_FIELD_OFFSET(Experiment, _impl_.policy_)}}, + // .xtcp_treatment.v1.Evidence baseline = 6 [json_name = "baseline", (.buf.validate.field) = { + {::_pbi::TcParser::FastMtS1, + {50, 8, 2, + PROTOBUF_FIELD_OFFSET(Experiment, _impl_.baseline_)}}, + // repeated string cohort_order = 7 [json_name = "cohortOrder", (.buf.validate.field) = { + {::_pbi::TcParser::FastUR1, + {58, 0, 0, + PROTOBUF_FIELD_OFFSET(Experiment, _impl_.cohort_order_)}}, + // repeated .xtcp_treatment.v1.Phase phases = 8 [json_name = "phases", (.buf.validate.field) = { + {::_pbi::TcParser::FastMtR1, + {66, 1, 3, + PROTOBUF_FIELD_OFFSET(Experiment, _impl_.phases_)}}, + // .xtcp_treatment.v1.ExperimentStatus status = 9 [json_name = "status", (.buf.validate.field) = { + {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(Experiment, _impl_.status_), 12>(), + {72, 12, 0, + PROTOBUF_FIELD_OFFSET(Experiment, _impl_.status_)}}, + // uint64 revision = 10 [json_name = "revision"]; + {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(Experiment, _impl_.revision_), 11>(), + {80, 11, 0, + PROTOBUF_FIELD_OFFSET(Experiment, _impl_.revision_)}}, + // .google.protobuf.Timestamp proposed_at = 11 [json_name = "proposedAt"]; + {::_pbi::TcParser::FastMtS1, + {90, 9, 4, + PROTOBUF_FIELD_OFFSET(Experiment, _impl_.proposed_at_)}}, + // .xtcp_treatment.v1.Evidence latest_evidence = 12 [json_name = "latestEvidence"]; + {::_pbi::TcParser::FastMtS1, + {98, 10, 5, + PROTOBUF_FIELD_OFFSET(Experiment, _impl_.latest_evidence_)}}, + // string latest_outcome = 13 [json_name = "latestOutcome", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {106, 5, 0, + PROTOBUF_FIELD_OFFSET(Experiment, _impl_.latest_outcome_)}}, + {::_pbi::TcParser::MiniParse, {}}, + {::_pbi::TcParser::MiniParse, {}}, + }}, {{ + 65535, 65535 + }}, {{ + // string id = 1 [json_name = "id", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.id_), _Internal::kHasBitsOffset + 2, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // string location = 2 [json_name = "location", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.location_), _Internal::kHasBitsOffset + 3, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // string hypothesis = 3 [json_name = "hypothesis", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.hypothesis_), _Internal::kHasBitsOffset + 4, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // .xtcp_treatment.v1.Treatment treatment = 4 [json_name = "treatment", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.treatment_), _Internal::kHasBitsOffset + 6, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // .xtcp_treatment.v1.ExperimentPolicy policy = 5 [json_name = "policy", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.policy_), _Internal::kHasBitsOffset + 7, 1, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // .xtcp_treatment.v1.Evidence baseline = 6 [json_name = "baseline", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.baseline_), _Internal::kHasBitsOffset + 8, 2, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // repeated string cohort_order = 7 [json_name = "cohortOrder", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.cohort_order_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcRepeated | ::_fl::kUtf8String | ::_fl::kRepSString)}, + // repeated .xtcp_treatment.v1.Phase phases = 8 [json_name = "phases", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.phases_), _Internal::kHasBitsOffset + 1, 3, (0 | ::_fl::kFcRepeated | ::_fl::kMessage | ::_fl::kTvTable)}, + // .xtcp_treatment.v1.ExperimentStatus status = 9 [json_name = "status", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.status_), _Internal::kHasBitsOffset + 12, 0, (0 | ::_fl::kFcOptional | ::_fl::kOpenEnum)}, + // uint64 revision = 10 [json_name = "revision"]; + {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.revision_), _Internal::kHasBitsOffset + 11, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt64)}, + // .google.protobuf.Timestamp proposed_at = 11 [json_name = "proposedAt"]; + {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.proposed_at_), _Internal::kHasBitsOffset + 9, 4, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // .xtcp_treatment.v1.Evidence latest_evidence = 12 [json_name = "latestEvidence"]; + {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.latest_evidence_), _Internal::kHasBitsOffset + 10, 5, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // string latest_outcome = 13 [json_name = "latestOutcome", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.latest_outcome_), _Internal::kHasBitsOffset + 5, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + }}, + {{ + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment::v1::Treatment>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment::v1::Treatment_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment::v1::ExperimentPolicy>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment::v1::ExperimentPolicy_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment::v1::Evidence>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment::v1::Evidence_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment::v1::Phase>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment::v1::Phase_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::google::protobuf::Timestamp>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::google::protobuf::Timestamp_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment::v1::Evidence>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment::v1::Evidence_globals_}, + #endif + }}, + {{ + "\34\2\10\12\0\0\0\14\0\0\0\0\0\16\0\0" + "xtcp_treatment.v1.Experiment" + "id" + "location" + "hypothesis" + "cohort_order" + "latest_outcome" + }}, + }; +} + + +inline constexpr Experiment::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : cohort_order_ { visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment::v1::Experiment, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Experiment, _impl_.cohort_order_)>() + } + , + phases_ { visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment::v1::Experiment, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Experiment, _impl_.phases_)>() + } + , + id_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + location_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + hypothesis_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + latest_outcome_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + treatment_{nullptr}, + policy_{nullptr}, + baseline_{nullptr}, + proposed_at_{nullptr}, + latest_evidence_{nullptr}, + revision_{::uint64_t{0u}}, + status_{static_cast< ::xtcp_treatment::v1::ExperimentStatus >(0)} {} + +template +constexpr Experiment::Experiment(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL Experiment::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) Experiment(arena); +} +constexpr auto Experiment::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::CopyInit(sizeof(Experiment), alignof(Experiment)); +} +constexpr auto Experiment::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &Experiment::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &Experiment::SharedDtor, + Super_::GetClearImpl(), &Experiment::ByteSizeLong, + &Experiment::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(Experiment, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[11], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct ExperimentGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr ExperimentGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + Experiment_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(Experiment::InternalGenerateClassData_( + _default, &Experiment_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~ExperimentGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) Experiment _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(ExperimentGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST ExperimentGlobalsTypeInternal Experiment_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* Experiment_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return Experiment_globals_.GetClassData(); +#else + return Experiment_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +class DesiredState::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(DesiredState, _impl_._has_bits_); +}; + +constexpr DesiredState::ParseTableT_ DesiredState::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(DesiredState, _impl_._has_bits_), + 0, // no _extensions_ + 5, 56, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294967264, // skipmap + offsetof(ParseTableT_, field_entries), + 5, // num_field_entries + 2, // num_aux_entries + offsetof(ParseTableT_, aux_entries), + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment::v1::DesiredState>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + {::_pbi::TcParser::MiniParse, {}}, + // string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {10, 1, 0, + PROTOBUF_FIELD_OFFSET(DesiredState, _impl_.agent_id_)}}, + // uint64 revision = 2 [json_name = "revision", (.buf.validate.field) = { + {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(DesiredState, _impl_.revision_), 3>(), + {16, 3, 0, + PROTOBUF_FIELD_OFFSET(DesiredState, _impl_.revision_)}}, + // bool suspended = 3 [json_name = "suspended"]; + {::_pbi::TcParser::SingularVarintNoZag1(), + {24, 4, 0, + PROTOBUF_FIELD_OFFSET(DesiredState, _impl_.suspended_)}}, + // repeated .xtcp_treatment.v1.Assignment assignments = 4 [json_name = "assignments", (.buf.validate.field) = { + {::_pbi::TcParser::FastMtR1, + {34, 0, 0, + PROTOBUF_FIELD_OFFSET(DesiredState, _impl_.assignments_)}}, + // .google.protobuf.Timestamp issued_at = 5 [json_name = "issuedAt", (.buf.validate.field) = { + {::_pbi::TcParser::FastMtS1, + {42, 2, 1, + PROTOBUF_FIELD_OFFSET(DesiredState, _impl_.issued_at_)}}, + {::_pbi::TcParser::MiniParse, {}}, + {::_pbi::TcParser::MiniParse, {}}, + }}, {{ + 65535, 65535 + }}, {{ + // string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(DesiredState, _impl_.agent_id_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // uint64 revision = 2 [json_name = "revision", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(DesiredState, _impl_.revision_), _Internal::kHasBitsOffset + 3, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt64)}, + // bool suspended = 3 [json_name = "suspended"]; + {PROTOBUF_FIELD_OFFSET(DesiredState, _impl_.suspended_), _Internal::kHasBitsOffset + 4, 0, (0 | ::_fl::kFcOptional | ::_fl::kBool)}, + // repeated .xtcp_treatment.v1.Assignment assignments = 4 [json_name = "assignments", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(DesiredState, _impl_.assignments_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcRepeated | ::_fl::kMessage | ::_fl::kTvTable)}, + // .google.protobuf.Timestamp issued_at = 5 [json_name = "issuedAt", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(DesiredState, _impl_.issued_at_), _Internal::kHasBitsOffset + 2, 1, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + }}, + {{ + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment::v1::Assignment>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment::v1::Assignment_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::google::protobuf::Timestamp>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::google::protobuf::Timestamp_globals_}, + #endif + }}, + {{ + "\36\10\0\0\0\0\0\0" + "xtcp_treatment.v1.DesiredState" + "agent_id" + }}, + }; +} + + +inline constexpr DesiredState::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : assignments_ { visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment::v1::DesiredState, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::DesiredState, _impl_.assignments_)>() + } + , + agent_id_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + issued_at_{nullptr}, + revision_{::uint64_t{0u}}, + suspended_{false} {} + +template +constexpr DesiredState::DesiredState(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL DesiredState::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) DesiredState(arena); +} +constexpr auto DesiredState::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::CopyInit(sizeof(DesiredState), alignof(DesiredState)); +} +constexpr auto DesiredState::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &DesiredState::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &DesiredState::SharedDtor, + Super_::GetClearImpl(), &DesiredState::ByteSizeLong, + &DesiredState::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(DesiredState, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[3], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct DesiredStateGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr DesiredStateGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + DesiredState_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(DesiredState::InternalGenerateClassData_( + _default, &DesiredState_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~DesiredStateGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) DesiredState _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(DesiredStateGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST DesiredStateGlobalsTypeInternal DesiredState_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* DesiredState_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return DesiredState_globals_.GetClassData(); +#else + return DesiredState_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +class ApplicationRecord::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(ApplicationRecord, _impl_._has_bits_); +}; + +constexpr ApplicationRecord::ParseTableT_ ApplicationRecord::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(ApplicationRecord, _impl_._has_bits_), + 0, // no _extensions_ + 12, 120, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294963200, // skipmap + offsetof(ParseTableT_, field_entries), + 12, // num_field_entries + 6, // num_aux_entries + offsetof(ParseTableT_, aux_entries), + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment::v1::ApplicationRecord>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + {::_pbi::TcParser::MiniParse, {}}, + // .xtcp_treatment.v1.Assignment assignment = 1 [json_name = "assignment", (.buf.validate.field) = { + {::_pbi::TcParser::FastMtS1, + {10, 3, 0, + PROTOBUF_FIELD_OFFSET(ApplicationRecord, _impl_.assignment_)}}, + // uint64 desired_revision = 2 [json_name = "desiredRevision"]; + {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(ApplicationRecord, _impl_.desired_revision_), 9>(), + {16, 9, 0, + PROTOBUF_FIELD_OFFSET(ApplicationRecord, _impl_.desired_revision_)}}, + // .xtcp_treatment.v1.ApplyStatus status = 3 [json_name = "status", (.buf.validate.field) = { + {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(ApplicationRecord, _impl_.status_), 10>(), + {24, 10, 0, + PROTOBUF_FIELD_OFFSET(ApplicationRecord, _impl_.status_)}}, + // .google.protobuf.Timestamp received_at = 4 [json_name = "receivedAt"]; + {::_pbi::TcParser::FastMtS1, + {34, 4, 1, + PROTOBUF_FIELD_OFFSET(ApplicationRecord, _impl_.received_at_)}}, + // .google.protobuf.Timestamp attempted_at = 5 [json_name = "attemptedAt"]; + {::_pbi::TcParser::FastMtS1, + {42, 5, 2, + PROTOBUF_FIELD_OFFSET(ApplicationRecord, _impl_.attempted_at_)}}, + // .google.protobuf.Timestamp applied_at = 6 [json_name = "appliedAt"]; + {::_pbi::TcParser::FastMtS1, + {50, 6, 3, + PROTOBUF_FIELD_OFFSET(ApplicationRecord, _impl_.applied_at_)}}, + // .google.protobuf.Timestamp verified_at = 7 [json_name = "verifiedAt"]; + {::_pbi::TcParser::FastMtS1, + {58, 7, 4, + PROTOBUF_FIELD_OFFSET(ApplicationRecord, _impl_.verified_at_)}}, + // .google.protobuf.Timestamp removed_at = 8 [json_name = "removedAt"]; + {::_pbi::TcParser::FastMtS1, + {66, 8, 5, + PROTOBUF_FIELD_OFFSET(ApplicationRecord, _impl_.removed_at_)}}, + // string error = 9 [json_name = "error", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {74, 0, 0, + PROTOBUF_FIELD_OFFSET(ApplicationRecord, _impl_.error_)}}, + // bytes installed_route = 10 [json_name = "installedRoute", (.buf.validate.field) = { + {::_pbi::TcParser::FastBS1, + {82, 1, 0, + PROTOBUF_FIELD_OFFSET(ApplicationRecord, _impl_.installed_route_)}}, + // string installed_boot_id = 11 [json_name = "installedBootId", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {90, 2, 0, + PROTOBUF_FIELD_OFFSET(ApplicationRecord, _impl_.installed_boot_id_)}}, + // bool expired_latched = 12 [json_name = "expiredLatched"]; + {::_pbi::TcParser::SingularVarintNoZag1(), + {96, 11, 0, + PROTOBUF_FIELD_OFFSET(ApplicationRecord, _impl_.expired_latched_)}}, + {::_pbi::TcParser::MiniParse, {}}, + {::_pbi::TcParser::MiniParse, {}}, + {::_pbi::TcParser::MiniParse, {}}, + }}, {{ + 65535, 65535 + }}, {{ + // .xtcp_treatment.v1.Assignment assignment = 1 [json_name = "assignment", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(ApplicationRecord, _impl_.assignment_), _Internal::kHasBitsOffset + 3, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // uint64 desired_revision = 2 [json_name = "desiredRevision"]; + {PROTOBUF_FIELD_OFFSET(ApplicationRecord, _impl_.desired_revision_), _Internal::kHasBitsOffset + 9, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt64)}, + // .xtcp_treatment.v1.ApplyStatus status = 3 [json_name = "status", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(ApplicationRecord, _impl_.status_), _Internal::kHasBitsOffset + 10, 0, (0 | ::_fl::kFcOptional | ::_fl::kOpenEnum)}, + // .google.protobuf.Timestamp received_at = 4 [json_name = "receivedAt"]; + {PROTOBUF_FIELD_OFFSET(ApplicationRecord, _impl_.received_at_), _Internal::kHasBitsOffset + 4, 1, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // .google.protobuf.Timestamp attempted_at = 5 [json_name = "attemptedAt"]; + {PROTOBUF_FIELD_OFFSET(ApplicationRecord, _impl_.attempted_at_), _Internal::kHasBitsOffset + 5, 2, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // .google.protobuf.Timestamp applied_at = 6 [json_name = "appliedAt"]; + {PROTOBUF_FIELD_OFFSET(ApplicationRecord, _impl_.applied_at_), _Internal::kHasBitsOffset + 6, 3, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // .google.protobuf.Timestamp verified_at = 7 [json_name = "verifiedAt"]; + {PROTOBUF_FIELD_OFFSET(ApplicationRecord, _impl_.verified_at_), _Internal::kHasBitsOffset + 7, 4, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // .google.protobuf.Timestamp removed_at = 8 [json_name = "removedAt"]; + {PROTOBUF_FIELD_OFFSET(ApplicationRecord, _impl_.removed_at_), _Internal::kHasBitsOffset + 8, 5, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // string error = 9 [json_name = "error", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(ApplicationRecord, _impl_.error_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // bytes installed_route = 10 [json_name = "installedRoute", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(ApplicationRecord, _impl_.installed_route_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kBytes | ::_fl::kRepAString)}, + // string installed_boot_id = 11 [json_name = "installedBootId", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(ApplicationRecord, _impl_.installed_boot_id_), _Internal::kHasBitsOffset + 2, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // bool expired_latched = 12 [json_name = "expiredLatched"]; + {PROTOBUF_FIELD_OFFSET(ApplicationRecord, _impl_.expired_latched_), _Internal::kHasBitsOffset + 11, 0, (0 | ::_fl::kFcOptional | ::_fl::kBool)}, + }}, + {{ + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment::v1::Assignment>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment::v1::Assignment_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::google::protobuf::Timestamp>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::google::protobuf::Timestamp_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::google::protobuf::Timestamp>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::google::protobuf::Timestamp_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::google::protobuf::Timestamp>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::google::protobuf::Timestamp_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::google::protobuf::Timestamp>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::google::protobuf::Timestamp_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::google::protobuf::Timestamp>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::google::protobuf::Timestamp_globals_}, + #endif + }}, + {{ + "\43\0\0\0\0\0\0\0\0\5\0\21\0\0\0\0" + "xtcp_treatment.v1.ApplicationRecord" + "error" + "installed_boot_id" + }}, + }; +} + + +inline constexpr ApplicationRecord::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : error_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + installed_route_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + installed_boot_id_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + assignment_{nullptr}, + received_at_{nullptr}, + attempted_at_{nullptr}, + applied_at_{nullptr}, + verified_at_{nullptr}, + removed_at_{nullptr}, + desired_revision_{::uint64_t{0u}}, + status_{static_cast< ::xtcp_treatment::v1::ApplyStatus >(0)}, + expired_latched_{false} {} + +template +constexpr ApplicationRecord::ApplicationRecord(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL ApplicationRecord::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) ApplicationRecord(arena); +} +constexpr auto ApplicationRecord::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::CopyInit(sizeof(ApplicationRecord), alignof(ApplicationRecord)); +} +constexpr auto ApplicationRecord::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &ApplicationRecord::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &ApplicationRecord::SharedDtor, + Super_::GetClearImpl(), &ApplicationRecord::ByteSizeLong, + &ApplicationRecord::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(ApplicationRecord, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[5], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct ApplicationRecordGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr ApplicationRecordGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + ApplicationRecord_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(ApplicationRecord::InternalGenerateClassData_( + _default, &ApplicationRecord_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~ApplicationRecordGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) ApplicationRecord _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(ApplicationRecordGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST ApplicationRecordGlobalsTypeInternal ApplicationRecord_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* ApplicationRecord_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return ApplicationRecord_globals_.GetClassData(); +#else + return ApplicationRecord_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +class LocalState::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(LocalState, _impl_._has_bits_); +}; + +constexpr LocalState::ParseTableT_ LocalState::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(LocalState, _impl_._has_bits_), + 0, // no _extensions_ + 5, 56, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294967264, // skipmap + offsetof(ParseTableT_, field_entries), + 5, // num_field_entries + 3, // num_aux_entries + offsetof(ParseTableT_, aux_entries), + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment::v1::LocalState>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + {::_pbi::TcParser::MiniParse, {}}, + // .xtcp_treatment.v1.DesiredState desired = 1 [json_name = "desired"]; + {::_pbi::TcParser::FastMtS1, + {10, 2, 0, + PROTOBUF_FIELD_OFFSET(LocalState, _impl_.desired_)}}, + // bool locally_suspended = 2 [json_name = "locallySuspended"]; + {::_pbi::TcParser::SingularVarintNoZag1(), + {16, 4, 0, + PROTOBUF_FIELD_OFFSET(LocalState, _impl_.locally_suspended_)}}, + // string suspension_reason = 3 [json_name = "suspensionReason", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {26, 1, 0, + PROTOBUF_FIELD_OFFSET(LocalState, _impl_.suspension_reason_)}}, + // repeated .xtcp_treatment.v1.ApplicationRecord records = 4 [json_name = "records", (.buf.validate.field) = { + {::_pbi::TcParser::FastMtR1, + {34, 0, 1, + PROTOBUF_FIELD_OFFSET(LocalState, _impl_.records_)}}, + // .google.protobuf.Timestamp updated_at = 5 [json_name = "updatedAt"]; + {::_pbi::TcParser::FastMtS1, + {42, 3, 2, + PROTOBUF_FIELD_OFFSET(LocalState, _impl_.updated_at_)}}, + {::_pbi::TcParser::MiniParse, {}}, + {::_pbi::TcParser::MiniParse, {}}, + }}, {{ + 65535, 65535 + }}, {{ + // .xtcp_treatment.v1.DesiredState desired = 1 [json_name = "desired"]; + {PROTOBUF_FIELD_OFFSET(LocalState, _impl_.desired_), _Internal::kHasBitsOffset + 2, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // bool locally_suspended = 2 [json_name = "locallySuspended"]; + {PROTOBUF_FIELD_OFFSET(LocalState, _impl_.locally_suspended_), _Internal::kHasBitsOffset + 4, 0, (0 | ::_fl::kFcOptional | ::_fl::kBool)}, + // string suspension_reason = 3 [json_name = "suspensionReason", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(LocalState, _impl_.suspension_reason_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // repeated .xtcp_treatment.v1.ApplicationRecord records = 4 [json_name = "records", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(LocalState, _impl_.records_), _Internal::kHasBitsOffset + 0, 1, (0 | ::_fl::kFcRepeated | ::_fl::kMessage | ::_fl::kTvTable)}, + // .google.protobuf.Timestamp updated_at = 5 [json_name = "updatedAt"]; + {PROTOBUF_FIELD_OFFSET(LocalState, _impl_.updated_at_), _Internal::kHasBitsOffset + 3, 2, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + }}, + {{ + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment::v1::DesiredState>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment::v1::DesiredState_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment::v1::ApplicationRecord>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment::v1::ApplicationRecord_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::google::protobuf::Timestamp>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::google::protobuf::Timestamp_globals_}, + #endif + }}, + {{ + "\34\0\0\21\0\0\0\0" + "xtcp_treatment.v1.LocalState" + "suspension_reason" + }}, + }; +} + + +inline constexpr LocalState::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : records_ { visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment::v1::LocalState, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::LocalState, _impl_.records_)>() + } + , + suspension_reason_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + desired_{nullptr}, + updated_at_{nullptr}, + locally_suspended_{false} {} + +template +constexpr LocalState::LocalState(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL LocalState::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) LocalState(arena); +} +constexpr auto LocalState::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::CopyInit(sizeof(LocalState), alignof(LocalState)); +} +constexpr auto LocalState::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &LocalState::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &LocalState::SharedDtor, + Super_::GetClearImpl(), &LocalState::ByteSizeLong, + &LocalState::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(LocalState, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[6], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct LocalStateGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr LocalStateGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + LocalState_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(LocalState::InternalGenerateClassData_( + _default, &LocalState_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~LocalStateGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) LocalState _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(LocalStateGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST LocalStateGlobalsTypeInternal LocalState_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* LocalState_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return LocalState_globals_.GetClassData(); +#else + return LocalState_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +} // namespace v1 +} // namespace xtcp_treatment +static const ::_pb::EnumDescriptor* PROTOBUF_NONNULL + file_level_enum_descriptors_xtcp_5ftreatment_2fv1_2ftreatment_2eproto[4]; +static constexpr const ::_pb::ServiceDescriptor* PROTOBUF_NONNULL* PROTOBUF_NULLABLE + file_level_service_descriptors_xtcp_5ftreatment_2fv1_2ftreatment_2eproto = nullptr; +const ::uint32_t + TableStruct_xtcp_5ftreatment_2fv1_2ftreatment_2eproto::offsets[] ABSL_ATTRIBUTE_SECTION_VARIABLE( + protodesc_cold) = { + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::TCPSettings, _impl_._has_bits_), + 10, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::TCPSettings, _impl_.congestion_control_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::TCPSettings, _impl_.congestion_lock_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::TCPSettings, _impl_.rto_min_ms_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::TCPSettings, _impl_.initial_congestion_window_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::TCPSettings, _impl_.initial_receive_window_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::TCPSettings, _impl_.quick_ack_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::TCPSettings, _impl_.ecn_), + 0, + 4, + 1, + 2, + 3, + 5, + 6, + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Treatment, _impl_._has_bits_), + 8, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Treatment, _impl_.id_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Treatment, _impl_.revision_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Treatment, _impl_.destination_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Treatment, _impl_.settings_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Treatment, _impl_.ecn_fabric_verified_), + 0, + 3, + 1, + 2, + 4, + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Assignment, _impl_._has_bits_), + 10, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Assignment, _impl_.id_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Assignment, _impl_.experiment_id_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Assignment, _impl_.treatment_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Assignment, _impl_.mode_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Assignment, _impl_.lease_expires_at_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Assignment, _impl_.predecessor_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Assignment, _impl_.generation_), + 0, + 1, + 2, + 6, + 3, + 4, + 5, + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::DesiredState, _impl_._has_bits_), + 8, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::DesiredState, _impl_.agent_id_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::DesiredState, _impl_.revision_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::DesiredState, _impl_.suspended_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::DesiredState, _impl_.assignments_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::DesiredState, _impl_.issued_at_), + 1, + 3, + 4, + 0, + 2, + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Capabilities, _impl_._has_bits_), + 8, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Capabilities, _impl_.boot_id_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Capabilities, _impl_.kernel_release_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Capabilities, _impl_.congestion_algorithms_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Capabilities, _impl_.simple_host_routing_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Capabilities, _impl_.actuator_enabled_), + 1, + 2, + 0, + 3, + 4, + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ApplicationRecord, _impl_._has_bits_), + 15, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ApplicationRecord, _impl_.assignment_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ApplicationRecord, _impl_.desired_revision_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ApplicationRecord, _impl_.status_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ApplicationRecord, _impl_.received_at_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ApplicationRecord, _impl_.attempted_at_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ApplicationRecord, _impl_.applied_at_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ApplicationRecord, _impl_.verified_at_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ApplicationRecord, _impl_.removed_at_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ApplicationRecord, _impl_.error_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ApplicationRecord, _impl_.installed_route_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ApplicationRecord, _impl_.installed_boot_id_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ApplicationRecord, _impl_.expired_latched_), + 3, + 9, + 10, + 4, + 5, + 6, + 7, + 8, + 0, + 1, + 2, + 11, + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::LocalState, _impl_._has_bits_), + 8, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::LocalState, _impl_.desired_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::LocalState, _impl_.locally_suspended_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::LocalState, _impl_.suspension_reason_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::LocalState, _impl_.records_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::LocalState, _impl_.updated_at_), + 2, + 4, + 1, + 0, + 3, + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::HostEvidence, _impl_._has_bits_), + 11, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::HostEvidence, _impl_.agent_id_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::HostEvidence, _impl_.sockets_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::HostEvidence, _impl_.bytes_acked_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::HostEvidence, _impl_.goodput_bps_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::HostEvidence, _impl_.retransmission_ratio_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::HostEvidence, _impl_.rtt_p95_ms_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::HostEvidence, _impl_.exposed_sockets_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::HostEvidence, _impl_.collection_healthy_), + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Evidence, _impl_._has_bits_), + 11, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Evidence, _impl_.id_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Evidence, _impl_.analyzed_at_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Evidence, _impl_.window_start_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Evidence, _impl_.window_end_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Evidence, _impl_.query_version_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Evidence, _impl_.enrichment_version_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Evidence, _impl_.hosts_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Evidence, _impl_.artifact_sha256_), + 1, + 5, + 6, + 7, + 2, + 3, + 0, + 4, + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ExperimentPolicy, _impl_._has_bits_), + 12, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ExperimentPolicy, _impl_.minimum_observation_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ExperimentPolicy, _impl_.maximum_phase_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ExperimentPolicy, _impl_.minimum_sockets_per_host_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ExperimentPolicy, _impl_.minimum_bytes_per_host_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ExperimentPolicy, _impl_.minimum_treated_hosts_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ExperimentPolicy, _impl_.minimum_improvement_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ExperimentPolicy, _impl_.maximum_retransmission_increase_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ExperimentPolicy, _impl_.maximum_rtt_increase_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ExperimentPolicy, _impl_.maximum_evidence_age_), + 0, + 1, + 3, + 4, + 8, + 5, + 6, + 7, + 2, + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Phase, _impl_._has_bits_), + 11, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Phase, _impl_.target_hosts_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Phase, _impl_.issued_at_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Phase, _impl_.observation_started_at_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Phase, _impl_.evaluated_at_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Phase, _impl_.evidence_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Phase, _impl_.outcome_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Phase, _impl_.reviewer_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Phase, _impl_.reason_), + 7, + 3, + 4, + 5, + 6, + 0, + 1, + 2, + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Experiment, _impl_._has_bits_), + 16, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Experiment, _impl_.id_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Experiment, _impl_.location_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Experiment, _impl_.hypothesis_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Experiment, _impl_.treatment_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Experiment, _impl_.policy_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Experiment, _impl_.baseline_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Experiment, _impl_.cohort_order_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Experiment, _impl_.phases_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Experiment, _impl_.status_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Experiment, _impl_.revision_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Experiment, _impl_.proposed_at_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Experiment, _impl_.latest_evidence_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Experiment, _impl_.latest_outcome_), + 2, + 3, + 4, + 6, + 7, + 8, + 0, + 1, + 12, + 11, + 9, + 10, + 5, +}; + +static const ::_pbi::MigrationSchema + schemas[] ABSL_ATTRIBUTE_SECTION_VARIABLE(protodesc_cold) = { + {0, sizeof(::xtcp_treatment::v1::TCPSettings)}, + {17, sizeof(::xtcp_treatment::v1::Treatment)}, + {30, sizeof(::xtcp_treatment::v1::Assignment)}, + {47, sizeof(::xtcp_treatment::v1::DesiredState)}, + {60, sizeof(::xtcp_treatment::v1::Capabilities)}, + {73, sizeof(::xtcp_treatment::v1::ApplicationRecord)}, + {100, sizeof(::xtcp_treatment::v1::LocalState)}, + {113, sizeof(::xtcp_treatment::v1::HostEvidence)}, + {132, sizeof(::xtcp_treatment::v1::Evidence)}, + {151, sizeof(::xtcp_treatment::v1::ExperimentPolicy)}, + {172, sizeof(::xtcp_treatment::v1::Phase)}, + {191, sizeof(::xtcp_treatment::v1::Experiment)}, +}; +static const ::_pbi::MessageGlobalsBase* PROTOBUF_NONNULL const + file_message_globals[] = { + &::xtcp_treatment::v1::TCPSettings_globals_, + &::xtcp_treatment::v1::Treatment_globals_, + &::xtcp_treatment::v1::Assignment_globals_, + &::xtcp_treatment::v1::DesiredState_globals_, + &::xtcp_treatment::v1::Capabilities_globals_, + &::xtcp_treatment::v1::ApplicationRecord_globals_, + &::xtcp_treatment::v1::LocalState_globals_, + &::xtcp_treatment::v1::HostEvidence_globals_, + &::xtcp_treatment::v1::Evidence_globals_, + &::xtcp_treatment::v1::ExperimentPolicy_globals_, + &::xtcp_treatment::v1::Phase_globals_, + &::xtcp_treatment::v1::Experiment_globals_, +}; +const char descriptor_table_protodef_xtcp_5ftreatment_2fv1_2ftreatment_2eproto[] ABSL_ATTRIBUTE_SECTION_VARIABLE( + protodesc_cold) = { + "\n!xtcp_treatment/v1/treatment.proto\022\021xtc" + "p_treatment.v1\032\033buf/validate/validate.pr" + "oto\032 google/protobuf/descriptor.proto\032\036g" + "oogle/protobuf/duration.proto\032\037google/pr" + "otobuf/timestamp.proto\"\215\007\n\013TCPSettings\022Y" + "\n\022congestion_control\030\001 \001(\tB%\272H\"r \030\0172\034^[a" + "-zA-Z0-9][a-zA-Z0-9_.-]*$H\000R\021congestionC" + "ontrol\210\001\001\022\'\n\017congestion_lock\030\002 \001(\010R\016cong" + "estionLock\022.\n\nrto_min_ms\030\003 \001(\rB\013\272H\010*\006\030\300\251" + "\007(\001H\001R\010rtoMinMs\210\001\001\022K\n\031initial_congestion" + "_window\030\004 \001(\rB\n\272H\007*\005\030\200\010(\001H\002R\027initialCong" + "estionWindow\210\001\001\022E\n\026initial_receive_windo" + "w\030\005 \001(\rB\n\272H\007*\005\030\200\010(\001H\003R\024initialReceiveWin" + "dow\210\001\001\022 \n\tquick_ack\030\006 \001(\010H\004R\010quickAck\210\001\001" + "\022\025\n\003ecn\030\007 \001(\010H\005R\003ecn\210\001\001:\207\003\272H\203\003\032\363\001\n\026tcp_s" + "ettings.non_empty\022/a treatment must set " + "at least one TCP parameter\032\247\001has(this.co" + "ngestion_control) || has(this.rto_min_ms" + ") || has(this.initial_congestion_window)" + " || has(this.initial_receive_window) || " + "has(this.quick_ack) || has(this.ecn)\032\212\001\n" + "$tcp_settings.lock_requires_algorithm\022+c" + "ongestion_lock requires congestion_contr" + "ol\0325!this.congestion_lock || has(this.co" + "ngestion_control)B\025\n\023_congestion_control" + "B\r\n\013_rto_min_msB\034\n\032_initial_congestion_w" + "indowB\031\n\027_initial_receive_windowB\014\n\n_qui" + "ck_ackB\006\n\004_ecn\"\330\003\n\tTreatment\0229\n\002id\030\001 \001(\t" + "B)\272H&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,12" + "7}$R\002id\022#\n\010revision\030\002 \001(\004B\007\272H\0042\002 \000R\010revi" + "sion\022,\n\013destination\030\003 \001(\tB\n\272H\007r\005\0301\350\001\001R\013d" + "estination\022B\n\010settings\030\004 \001(\0132\036.xtcp_trea" + "tment.v1.TCPSettingsB\006\272H\003\310\001\001R\010settings\022." + "\n\023ecn_fabric_verified\030\005 \001(\010R\021ecnFabricVe" + "rified:\310\001\272H\304\001\032\301\001\n\034treatment.dctcp_requir" + "es_ecn\022ADCTCP requires independently ver" + "ified ECN fabric and explicit ECN\032^this." + "settings.congestion_control != \'dctcp\' |" + "| (this.ecn_fabric_verified && this.sett" + "ings.ecn)\"\375\005\n\nAssignment\0229\n\002id\030\001 \001(\tB)\272H" + "&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R" + "\002id\022N\n\rexperiment_id\030\002 \001(\tB)\272H&r$2\"^[a-z" + "A-Z0-9][a-zA-Z0-9_.-]{0,127}$R\014experimen" + "tId\022B\n\ttreatment\030\003 \001(\0132\034.xtcp_treatment." + "v1.TreatmentB\006\272H\003\310\001\001R\ttreatment\022A\n\004mode\030" + "\004 \001(\0162!.xtcp_treatment.v1.AssignmentMode" + "B\n\272H\007\202\001\004\020\001 \000R\004mode\022D\n\020lease_expires_at\030\005" + " \001(\0132\032.google.protobuf.TimestampR\016leaseE" + "xpiresAt\022>\n\013predecessor\030\006 \001(\0132\034.xtcp_tre" + "atment.v1.TreatmentR\013predecessor\022\'\n\ngene" + "ration\030\007 \001(\004B\007\272H\0042\002 \000R\ngeneration:\255\002\272H\251\002" + "\032}\n\037assignment.trial_requires_lease\022,a t" + "rial assignment requires lease_expires_a" + "t\032,this.mode != 1 || has(this.lease_expi" + "res_at)\032\247\001\n\"assignment.predecessor_desti" + "nation\022+predecessor must cover the same " + "destination\032T!has(this.predecessor) || t" + "his.predecessor.destination == this.trea" + "tment.destination\"\244\002\n\014DesiredState\022D\n\010ag" + "ent_id\030\001 \001(\tB)\272H&r$2\"^[a-zA-Z0-9][a-zA-Z" + "0-9_.-]{0,127}$R\007agentId\022#\n\010revision\030\002 \001" + "(\004B\007\272H\0042\002 \000R\010revision\022\034\n\tsuspended\030\003 \001(\010" + "R\tsuspended\022J\n\013assignments\030\004 \003(\0132\035.xtcp_" + "treatment.v1.AssignmentB\t\272H\006\222\001\003\020\200 R\013assi" + "gnments\022\?\n\tissued_at\030\005 \001(\0132\032.google.prot" + "obuf.TimestampB\006\272H\003\310\001\001R\010issuedAt\"\243\002\n\014Cap" + "abilities\022B\n\007boot_id\030\001 \001(\tB)\272H&r$2\"^[a-z" + "A-Z0-9][a-zA-Z0-9_.-]{0,127}$R\006bootId\022/\n" + "\016kernel_release\030\002 \001(\tB\010\272H\005r\003\030\200\002R\rkernelR" + "elease\022C\n\025congestion_algorithms\030\003 \003(\tB\016\272" + "H\013\222\001\010\020@\"\004r\002\030\017R\024congestionAlgorithms\022.\n\023s" + "imple_host_routing\030\004 \001(\010R\021simpleHostRout" + "ing\022)\n\020actuator_enabled\030\005 \001(\010R\017actuatorE" + "nabled\"\250\005\n\021ApplicationRecord\022E\n\nassignme" + "nt\030\001 \001(\0132\035.xtcp_treatment.v1.AssignmentB" + "\006\272H\003\310\001\001R\nassignment\022)\n\020desired_revision\030" + "\002 \001(\004R\017desiredRevision\022@\n\006status\030\003 \001(\0162\036" + ".xtcp_treatment.v1.ApplyStatusB\010\272H\005\202\001\002\020\001" + "R\006status\022;\n\013received_at\030\004 \001(\0132\032.google.p" + "rotobuf.TimestampR\nreceivedAt\022=\n\014attempt" + "ed_at\030\005 \001(\0132\032.google.protobuf.TimestampR" + "\013attemptedAt\0229\n\napplied_at\030\006 \001(\0132\032.googl" + "e.protobuf.TimestampR\tappliedAt\022;\n\013verif" + "ied_at\030\007 \001(\0132\032.google.protobuf.Timestamp" + "R\nverifiedAt\0229\n\nremoved_at\030\010 \001(\0132\032.googl" + "e.protobuf.TimestampR\tremovedAt\022\036\n\005error" + "\030\t \001(\tB\010\272H\005r\003\030\200 R\005error\0221\n\017installed_rou" + "te\030\n \001(\014B\010\272H\005z\003\030\200 R\016installedRoute\0224\n\021in" + "stalled_boot_id\030\013 \001(\tB\010\272H\005r\003\030\200\001R\017install" + "edBootId\022\'\n\017expired_latched\030\014 \001(\010R\016expir" + "edLatched\"\261\002\n\nLocalState\0229\n\007desired\030\001 \001(" + "\0132\037.xtcp_treatment.v1.DesiredStateR\007desi" + "red\022+\n\021locally_suspended\030\002 \001(\010R\020locallyS" + "uspended\0225\n\021suspension_reason\030\003 \001(\tB\010\272H\005" + "r\003\030\200\010R\020suspensionReason\022I\n\007records\030\004 \003(\013" + "2$.xtcp_treatment.v1.ApplicationRecordB\t" + "\272H\006\222\001\003\020\200@R\007records\0229\n\nupdated_at\030\005 \001(\0132\032" + ".google.protobuf.TimestampR\tupdatedAt\"\211\004" + "\n\014HostEvidence\022D\n\010agent_id\030\001 \001(\tB)\272H&r$2" + "\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\007age" + "ntId\022\030\n\007sockets\030\002 \001(\004R\007sockets\022\037\n\013bytes_" + "acked\030\003 \001(\004R\nbytesAcked\0221\n\013goodput_bps\030\004" + " \001(\001B\020\272H\r\022\013)\000\000\000\000\000\000\000\000@\001R\ngoodputBps\022C\n\024re" + "transmission_ratio\030\005 \001(\001B\020\272H\r\022\013)\000\000\000\000\000\000\000\000" + "@\001R\023retransmissionRatio\022.\n\nrtt_p95_ms\030\006 " + "\001(\001B\020\272H\r\022\013)\000\000\000\000\000\000\000\000@\001R\010rttP95Ms\022\'\n\017expos" + "ed_sockets\030\007 \001(\004R\016exposedSockets\022-\n\022coll" + "ection_healthy\030\010 \001(\010R\021collectionHealthy:" + "x\272Hu\032s\n$host_evidence.exposed_within_soc" + "kets\022%exposed_sockets cannot exceed sock" + "ets\032$this.exposed_sockets <= this.socket" + "s\"\274\005\n\010Evidence\0229\n\002id\030\001 \001(\tB)\272H&r$2\"^[a-z" + "A-Z0-9][a-zA-Z0-9_.-]{0,127}$R\002id\022C\n\013ana" + "lyzed_at\030\002 \001(\0132\032.google.protobuf.Timesta" + "mpB\006\272H\003\310\001\001R\nanalyzedAt\022E\n\014window_start\030\003" + " \001(\0132\032.google.protobuf.TimestampB\006\272H\003\310\001\001" + "R\013windowStart\022A\n\nwindow_end\030\004 \001(\0132\032.goog" + "le.protobuf.TimestampB\006\272H\003\310\001\001R\twindowEnd" + "\022/\n\rquery_version\030\005 \001(\tB\n\272H\007r\005\020\001\030\200\002R\014que" + "ryVersion\0227\n\022enrichment_version\030\006 \001(\tB\010\272" + "H\005r\003\030\200\002R\021enrichmentVersion\022B\n\005hosts\030\007 \003(" + "\0132\037.xtcp_treatment.v1.HostEvidenceB\013\272H\010\222" + "\001\005\010\001\020\220NR\005hosts\022>\n\017artifact_sha256\030\010 \001(\tB" + "\025\272H\022r\0202\016^[0-9a-f]{64}$R\016artifactSha256:\267" + "\001\272H\263\001\032\260\001\n\021evidence.interval\022Owindow_end " + "must follow window_start and analyzed_at" + " must not precede window_end\032Jthis.windo" + "w_end > this.window_start && this.analyz" + "ed_at >= this.window_end\"\354\006\n\020ExperimentP" + "olicy\022]\n\023minimum_observation\030\001 \001(\0132\031.goo" + "gle.protobuf.DurationB\021\272H\016\252\001\010\"\004\010\200\365$*\000\310\001\001" + "R\022minimumObservation\022Q\n\rmaximum_phase\030\002 " + "\001(\0132\031.google.protobuf.DurationB\021\272H\016\252\001\010\"\004" + "\010\200\365$*\000\310\001\001R\014maximumPhase\022@\n\030minimum_socke" + "ts_per_host\030\003 \001(\004B\007\272H\0042\002 \000R\025minimumSocke" + "tsPerHost\022<\n\026minimum_bytes_per_host\030\004 \001(" + "\004B\007\272H\0042\002 \000R\023minimumBytesPerHost\022>\n\025minim" + "um_treated_hosts\030\005 \001(\rB\n\272H\007*\005\030\220N(\002R\023mini" + "mumTreatedHosts\022J\n\023minimum_improvement\030\006" + " \001(\001B\031\272H\026\022\024\031\000\000\000\000\000\000\360\?)\000\000\000\000\000\000\000\000@\001R\022minimum" + "Improvement\022a\n\037maximum_retransmission_in" + "crease\030\007 \001(\001B\031\272H\026\022\024\031\000\000\000\000\000\000\360\?)\000\000\000\000\000\000\000\000@\001R" + "\035maximumRetransmissionIncrease\022K\n\024maximu" + "m_rtt_increase\030\010 \001(\001B\031\272H\026\022\024\031\000\000\000\000\000\000\360\?)\000\000\000" + "\000\000\000\000\000@\001R\022maximumRttIncrease\022^\n\024maximum_e" + "vidence_age\030\t \001(\0132\031.google.protobuf.Dura" + "tionB\021\272H\016\252\001\010\"\004\010\200\365$*\000\310\001\001R\022maximumEvidence" + "Age:\211\001\272H\205\001\032\202\001\n\037policy.observation_within" + "_phase\022/minimum_observation cannot excee" + "d maximum_phase\032.this.minimum_observatio" + "n <= this.maximum_phase\"\244\003\n\005Phase\022-\n\014tar" + "get_hosts\030\001 \001(\rB\n\272H\007*\005\030\220N(\001R\013targetHosts" + "\0227\n\tissued_at\030\002 \001(\0132\032.google.protobuf.Ti" + "mestampR\010issuedAt\022P\n\026observation_started" + "_at\030\003 \001(\0132\032.google.protobuf.TimestampR\024o" + "bservationStartedAt\022=\n\014evaluated_at\030\004 \001(" + "\0132\032.google.protobuf.TimestampR\013evaluated" + "At\0227\n\010evidence\030\005 \001(\0132\033.xtcp_treatment.v1" + ".EvidenceR\010evidence\022!\n\007outcome\030\006 \001(\tB\007\272H" + "\004r\002\030@R\007outcome\022$\n\010reviewer\030\007 \001(\tB\010\272H\005r\003\030" + "\200\001R\010reviewer\022 \n\006reason\030\010 \001(\tB\010\272H\005r\003\030\200 R\006" + "reason\"\260\006\n\nExperiment\0229\n\002id\030\001 \001(\tB)\272H&r$" + "2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\002id" + "\022E\n\010location\030\002 \001(\tB)\272H&r$2\"^[a-zA-Z0-9][" + "a-zA-Z0-9_.-]{0,127}$R\010location\022*\n\nhypot" + "hesis\030\003 \001(\tB\n\272H\007r\005\020\001\030\200@R\nhypothesis\022B\n\tt" + "reatment\030\004 \001(\0132\034.xtcp_treatment.v1.Treat" + "mentB\006\272H\003\310\001\001R\ttreatment\022C\n\006policy\030\005 \001(\0132" + "#.xtcp_treatment.v1.ExperimentPolicyB\006\272H" + "\003\310\001\001R\006policy\022\?\n\010baseline\030\006 \001(\0132\033.xtcp_tr" + "eatment.v1.EvidenceB\006\272H\003\310\001\001R\010baseline\022X\n" + "\014cohort_order\030\007 \003(\tB5\272H2\222\001/\010\003\020\220N\030\001\"&r$2\"" + "^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\013coho" + "rtOrder\022:\n\006phases\030\010 \003(\0132\030.xtcp_treatment" + ".v1.PhaseB\010\272H\005\222\001\002\020@R\006phases\022E\n\006status\030\t " + "\001(\0162#.xtcp_treatment.v1.ExperimentStatus" + "B\010\272H\005\202\001\002\020\001R\006status\022\032\n\010revision\030\n \001(\004R\010re" + "vision\022;\n\013proposed_at\030\013 \001(\0132\032.google.pro" + "tobuf.TimestampR\nproposedAt\022D\n\017latest_ev" + "idence\030\014 \001(\0132\033.xtcp_treatment.v1.Evidenc" + "eR\016latestEvidence\022.\n\016latest_outcome\030\r \001(" + "\tB\007\272H\004r\002\030@R\rlatestOutcome*\?\n\004Role\022\024\n\020ROL" + "E_UNSPECIFIED\020\000\022\016\n\nROLE_AGENT\020\001\022\021\n\rROLE_" + "OPERATOR\020\002*\244\001\n\016AssignmentMode\022\037\n\033ASSIGNM" + "ENT_MODE_UNSPECIFIED\020\000\022\031\n\025ASSIGNMENT_MOD" + "E_TRIAL\020\001\022\032\n\026ASSIGNMENT_MODE_STABLE\020\002\022\035\n" + "\031ASSIGNMENT_MODE_SUSPENDED\020\003\022\033\n\027ASSIGNME" + "NT_MODE_RETIRED\020\004*\255\001\n\013ApplyStatus\022\034\n\030APP" + "LY_STATUS_UNSPECIFIED\020\000\022\030\n\024APPLY_STATUS_" + "PENDING\020\001\022\031\n\025APPLY_STATUS_VERIFIED\020\002\022\030\n\024" + "APPLY_STATUS_REMOVED\020\003\022\030\n\024APPLY_STATUS_E" + "XPIRED\020\004\022\027\n\023APPLY_STATUS_FAILED\020\005*\225\002\n\020Ex" + "perimentStatus\022!\n\035EXPERIMENT_STATUS_UNSP" + "ECIFIED\020\000\022\036\n\032EXPERIMENT_STATUS_PROPOSED\020" + "\001\022\035\n\031EXPERIMENT_STATUS_RUNNING\020\002\022\034\n\030EXPE" + "RIMENT_STATUS_PAUSED\020\003\022\037\n\033EXPERIMENT_STA" + "TUS_SUSPENDED\020\004\022\036\n\032EXPERIMENT_STATUS_COM" + "PLETE\020\005\022\035\n\031EXPERIMENT_STATUS_RETIRED\020\006\022!" + "\n\035EXPERIMENT_STATUS_ROLLED_BACK\020\007:M\n\004rol" + "e\022\036.google.protobuf.MethodOptions\030\272\216\003 \001(" + "\0162\027.xtcp_treatment.v1.RoleR\004roleB\300\001\n\025com" + ".xtcp_treatment.v1B\016TreatmentProtoP\001Z6gi" + "thub.com/randomizedcoder/xtcp2/gen/go/xt" + "cp_treatment\242\002\003XXX\252\002\020XtcpTreatment.V1\312\002\020" + "XtcpTreatment\\V1\342\002\034XtcpTreatment\\V1\\GPBM" + "etadata\352\002\021XtcpTreatment::V1b\006proto3" +}; +static const ::_pbi::DescriptorTable* PROTOBUF_NONNULL const + descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto_deps[4] = { + &::descriptor_table_buf_2fvalidate_2fvalidate_2eproto, + &::descriptor_table_google_2fprotobuf_2fdescriptor_2eproto, + &::descriptor_table_google_2fprotobuf_2fduration_2eproto, + &::descriptor_table_google_2fprotobuf_2ftimestamp_2eproto, +}; +static ::absl::once_flag descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto_once; +PROTOBUF_CONSTINIT const ::_pbi::DescriptorTable descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto = { + false, + false, + 8235, + descriptor_table_protodef_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, + "xtcp_treatment/v1/treatment.proto", + &descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto_once, + descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto_deps, + 4, + 12, + schemas, + file_message_globals, + TableStruct_xtcp_5ftreatment_2fv1_2ftreatment_2eproto::offsets, + file_level_enum_descriptors_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, + file_level_service_descriptors_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, +}; +namespace xtcp_treatment { +namespace v1 { +[[nodiscard]] const ::google::protobuf::EnumDescriptor* PROTOBUF_NONNULL +Role_descriptor() { + ::google::protobuf::internal::AssignDescriptors(&descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto); + return file_level_enum_descriptors_xtcp_5ftreatment_2fv1_2ftreatment_2eproto[0]; +} +PROTOBUF_CONSTINIT const uint32_t Role_internal_data_[] = { + 196608u, 0u, }; +[[nodiscard]] const ::google::protobuf::EnumDescriptor* PROTOBUF_NONNULL +AssignmentMode_descriptor() { + ::google::protobuf::internal::AssignDescriptors(&descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto); + return file_level_enum_descriptors_xtcp_5ftreatment_2fv1_2ftreatment_2eproto[1]; +} +PROTOBUF_CONSTINIT const uint32_t AssignmentMode_internal_data_[] = { + 327680u, 0u, }; +[[nodiscard]] const ::google::protobuf::EnumDescriptor* PROTOBUF_NONNULL +ApplyStatus_descriptor() { + ::google::protobuf::internal::AssignDescriptors(&descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto); + return file_level_enum_descriptors_xtcp_5ftreatment_2fv1_2ftreatment_2eproto[2]; +} +PROTOBUF_CONSTINIT const uint32_t ApplyStatus_internal_data_[] = { + 393216u, 0u, }; +[[nodiscard]] const ::google::protobuf::EnumDescriptor* PROTOBUF_NONNULL +ExperimentStatus_descriptor() { + ::google::protobuf::internal::AssignDescriptors(&descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto); + return file_level_enum_descriptors_xtcp_5ftreatment_2fv1_2ftreatment_2eproto[3]; +} +PROTOBUF_CONSTINIT const uint32_t ExperimentStatus_internal_data_[] = { + 524288u, 0u, }; +// =================================================================== + +TCPSettings::TCPSettings(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, TCPSettings_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment.v1.TCPSettings) +} +PROTOBUF_NDEBUG_INLINE TCPSettings::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment::v1::TCPSettings& from_msg) + : _has_bits_{from._has_bits_}, + congestion_control_(arena, from.congestion_control_) {} + +TCPSettings::TCPSettings( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const TCPSettings& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, TCPSettings_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + TCPSettings* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + ::memcpy(reinterpret_cast(&_impl_) + + offsetof(Impl_, rto_min_ms_), + reinterpret_cast(&from._impl_) + + offsetof(Impl_, rto_min_ms_), + offsetof(Impl_, ecn_) - + offsetof(Impl_, rto_min_ms_) + + sizeof(Impl_::ecn_)); + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment.v1.TCPSettings) +} +PROTOBUF_NDEBUG_INLINE TCPSettings::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + : congestion_control_(arena) {} + +inline void TCPSettings::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + ::memset(reinterpret_cast(&_impl_) + + offsetof(Impl_, rto_min_ms_), + 0, + offsetof(Impl_, ecn_) - + offsetof(Impl_, rto_min_ms_) + + sizeof(Impl_::ecn_)); +} +TCPSettings::~TCPSettings() { + // @@protoc_insertion_point(destructor:xtcp_treatment.v1.TCPSettings) + SharedDtor(*this); +} +inline void TCPSettings::SharedDtor(MessageLite& self) { + TCPSettings& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + this_._impl_.congestion_control_.Destroy(); + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull TCPSettings_class_data_ = + TCPSettings::InternalGenerateClassData_(TCPSettings_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +TCPSettings::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&TCPSettings_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(TCPSettings_class_data_.tc_table); + return TCPSettings_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +TCPSettings::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&TCPSettings_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&TCPSettings_globals_)); + return TCPSettings_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const TCPSettings::ParseTableT_ + TCPSettings::_table_ = + TCPSettings::InternalGenerateParseTable_(TCPSettings_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void TCPSettings::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment.v1.TCPSettings) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + this_._impl_.congestion_control_.ClearNonDefaultToEmpty(); + } + if (BatchCheckHasBit(cached_has_bits, 0x0000007eU)) { + ::memset(&this_._impl_.rto_min_ms_, 0, + static_cast<::size_t>( + reinterpret_cast(&this_._impl_.ecn_) - + reinterpret_cast(&this_._impl_.rto_min_ms_)) + + sizeof(_impl_.ecn_)); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL TCPSettings::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const TCPSettings& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL TCPSettings::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const TCPSettings& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment.v1.TCPSettings) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // optional string congestion_control = 1 [json_name = "congestionControl", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + const ::std::string& _s = this_._internal_congestion_control(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.TCPSettings.congestion_control"); + target = stream->WriteStringMaybeAliased(1, _s, target); + } + + // bool congestion_lock = 2 [json_name = "congestionLock"]; + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (this_._internal_congestion_lock() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray( + 2, this_._internal_congestion_lock(), target); + } + } + + // optional uint32 rto_min_ms = 3 [json_name = "rtoMinMs", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray( + 3, this_._internal_rto_min_ms(), target); + } + + // optional uint32 initial_congestion_window = 4 [json_name = "initialCongestionWindow", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray( + 4, this_._internal_initial_congestion_window(), target); + } + + // optional uint32 initial_receive_window = 5 [json_name = "initialReceiveWindow", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray( + 5, this_._internal_initial_receive_window(), target); + } + + // optional bool quick_ack = 6 [json_name = "quickAck"]; + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray( + 6, this_._internal_quick_ack(), target); + } + + // optional bool ecn = 7 [json_name = "ecn"]; + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray( + 7, this_._internal_ecn(), target); + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment.v1.TCPSettings) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t TCPSettings::ByteSizeLong(const MessageLite& base) { + const TCPSettings& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t TCPSettings::ByteSizeLong() const { + const TCPSettings& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment.v1.TCPSettings) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + ::_pbi::Prefetch5LinesFrom7Lines(&this_); + cached_has_bits = this_._impl_._has_bits_[0]; + total_size += ::absl::popcount(0x00000060U & cached_has_bits) * 2; + if (BatchCheckHasBit(cached_has_bits, 0x0000001fU)) { + // optional string congestion_control = 1 [json_name = "congestionControl", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_congestion_control()); + } + // optional uint32 rto_min_ms = 3 [json_name = "rtoMinMs", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne( + this_._internal_rto_min_ms()); + } + // optional uint32 initial_congestion_window = 4 [json_name = "initialCongestionWindow", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne( + this_._internal_initial_congestion_window()); + } + // optional uint32 initial_receive_window = 5 [json_name = "initialReceiveWindow", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne( + this_._internal_initial_receive_window()); + } + // bool congestion_lock = 2 [json_name = "congestionLock"]; + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (this_._internal_congestion_lock() != 0) { + total_size += 2; + } + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void TCPSettings::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment.v1.TCPSettings) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000007fU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + _this->_internal_set_congestion_control(from._internal_congestion_control()); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + _this->_impl_.rto_min_ms_ = from._impl_.rto_min_ms_; + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + _this->_impl_.initial_congestion_window_ = from._impl_.initial_congestion_window_; + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + _this->_impl_.initial_receive_window_ = from._impl_.initial_receive_window_; + } + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (from._internal_congestion_lock() != 0) { + _this->_impl_.congestion_lock_ = from._impl_.congestion_lock_; + } + } + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + _this->_impl_.quick_ack_ = from._impl_.quick_ack_; + } + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + _this->_impl_.ecn_ = from._impl_.ecn_; + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void TCPSettings::CopyFrom(const TCPSettings& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment.v1.TCPSettings) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void TCPSettings::InternalSwap(TCPSettings* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + auto* arena = GetArena(); + ABSL_DCHECK_EQ(arena, other->GetArena()); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.congestion_control_, &other->_impl_.congestion_control_, arena); + ::google::protobuf::internal::memswap< + PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.ecn_) + + sizeof(TCPSettings::_impl_.ecn_) + - PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.rto_min_ms_)>( + reinterpret_cast(&_impl_.rto_min_ms_), + reinterpret_cast(&other->_impl_.rto_min_ms_)); +} + +::google::protobuf::Metadata TCPSettings::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +Treatment::Treatment(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, Treatment_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment.v1.Treatment) +} +PROTOBUF_NDEBUG_INLINE Treatment::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment::v1::Treatment& from_msg) + : _has_bits_{from._has_bits_}, + id_(arena, from.id_), + destination_(arena, from.destination_) {} + +Treatment::Treatment( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const Treatment& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, Treatment_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + Treatment* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + ::uint32_t cached_has_bits = _impl_._has_bits_[0]; + _impl_.settings_ = (CheckHasBit(cached_has_bits, 0x00000004U)) + ? Super_::CopyConstruct(arena, *from._impl_.settings_) + : nullptr; + ::memcpy(reinterpret_cast(&_impl_) + + offsetof(Impl_, revision_), + reinterpret_cast(&from._impl_) + + offsetof(Impl_, revision_), + offsetof(Impl_, ecn_fabric_verified_) - + offsetof(Impl_, revision_) + + sizeof(Impl_::ecn_fabric_verified_)); + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment.v1.Treatment) +} +PROTOBUF_NDEBUG_INLINE Treatment::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + : id_(arena), + destination_(arena) {} + +inline void Treatment::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + ::memset(reinterpret_cast(&_impl_) + + offsetof(Impl_, settings_), + 0, + offsetof(Impl_, ecn_fabric_verified_) - + offsetof(Impl_, settings_) + + sizeof(Impl_::ecn_fabric_verified_)); +} +Treatment::~Treatment() { + // @@protoc_insertion_point(destructor:xtcp_treatment.v1.Treatment) + SharedDtor(*this); +} +inline void Treatment::SharedDtor(MessageLite& self) { + Treatment& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + this_._impl_.id_.Destroy(); + this_._impl_.destination_.Destroy(); + delete this_._impl_.settings_; + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull Treatment_class_data_ = + Treatment::InternalGenerateClassData_(Treatment_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +Treatment::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&Treatment_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(Treatment_class_data_.tc_table); + return Treatment_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +Treatment::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&Treatment_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&Treatment_globals_)); + return Treatment_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const Treatment::ParseTableT_ + Treatment::_table_ = + Treatment::InternalGenerateParseTable_(Treatment_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void Treatment::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment.v1.Treatment) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x00000007U)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + this_._impl_.id_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + this_._impl_.destination_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + ABSL_DCHECK(this_._impl_.settings_ != nullptr); + this_._impl_.settings_->Clear(); + } + } + if (BatchCheckHasBit(cached_has_bits, 0x00000018U)) { + ::memset(&this_._impl_.revision_, 0, + static_cast<::size_t>( + reinterpret_cast(&this_._impl_.ecn_fabric_verified_) - + reinterpret_cast(&this_._impl_.revision_)) + + sizeof(_impl_.ecn_fabric_verified_)); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL Treatment::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const Treatment& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL Treatment::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const Treatment& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment.v1.Treatment) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // string id = 1 [json_name = "id", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_id().empty()) { + const ::std::string& _s = this_._internal_id(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.Treatment.id"); + target = stream->WriteStringMaybeAliased(1, _s, target); + } + } + + // uint64 revision = 2 [json_name = "revision", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (this_._internal_revision() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray( + 2, this_._internal_revision(), target); + } + } + + // string destination = 3 [json_name = "destination", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!this_._internal_destination().empty()) { + const ::std::string& _s = this_._internal_destination(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.Treatment.destination"); + target = stream->WriteStringMaybeAliased(3, _s, target); + } + } + + // .xtcp_treatment.v1.TCPSettings settings = 4 [json_name = "settings", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 4, *this_._impl_.settings_, this_._impl_.settings_->GetCachedSize(), target, + stream); + } + + // bool ecn_fabric_verified = 5 [json_name = "ecnFabricVerified"]; + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (this_._internal_ecn_fabric_verified() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray( + 5, this_._internal_ecn_fabric_verified(), target); + } + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment.v1.Treatment) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t Treatment::ByteSizeLong(const MessageLite& base) { + const Treatment& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t Treatment::ByteSizeLong() const { + const Treatment& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment.v1.Treatment) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + ::_pbi::Prefetch5LinesFrom7Lines(&this_); + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000001fU)) { + // string id = 1 [json_name = "id", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_id().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_id()); + } + } + // string destination = 3 [json_name = "destination", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!this_._internal_destination().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_destination()); + } + } + // .xtcp_treatment.v1.TCPSettings settings = 4 [json_name = "settings", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.settings_); + } + // uint64 revision = 2 [json_name = "revision", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (this_._internal_revision() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne( + this_._internal_revision()); + } + } + // bool ecn_fabric_verified = 5 [json_name = "ecnFabricVerified"]; + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (this_._internal_ecn_fabric_verified() != 0) { + total_size += 2; + } + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void Treatment::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + ::google::protobuf::Arena* arena = _this->GetArena(); + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment.v1.Treatment) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000001fU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!from._internal_id().empty()) { + _this->_internal_set_id(from._internal_id()); + } else { + if (_this->_impl_.id_.IsDefault()) { + _this->_internal_set_id(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!from._internal_destination().empty()) { + _this->_internal_set_destination(from._internal_destination()); + } else { + if (_this->_impl_.destination_.IsDefault()) { + _this->_internal_set_destination(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + ABSL_DCHECK(from._impl_.settings_ != nullptr); + if (_this->_impl_.settings_ == nullptr) { + _this->_impl_.settings_ = Super_::CopyConstruct(arena, *from._impl_.settings_); + } else { + _this->_impl_.settings_->MergeFrom(*from._impl_.settings_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (from._internal_revision() != 0) { + _this->_impl_.revision_ = from._impl_.revision_; + } + } + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (from._internal_ecn_fabric_verified() != 0) { + _this->_impl_.ecn_fabric_verified_ = from._impl_.ecn_fabric_verified_; + } + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void Treatment::CopyFrom(const Treatment& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment.v1.Treatment) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void Treatment::InternalSwap(Treatment* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + auto* arena = GetArena(); + ABSL_DCHECK_EQ(arena, other->GetArena()); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.id_, &other->_impl_.id_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.destination_, &other->_impl_.destination_, arena); + ::google::protobuf::internal::memswap< + PROTOBUF_FIELD_OFFSET(Treatment, _impl_.ecn_fabric_verified_) + + sizeof(Treatment::_impl_.ecn_fabric_verified_) + - PROTOBUF_FIELD_OFFSET(Treatment, _impl_.settings_)>( + reinterpret_cast(&_impl_.settings_), + reinterpret_cast(&other->_impl_.settings_)); +} + +::google::protobuf::Metadata Treatment::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +void Assignment::clear_lease_expires_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.lease_expires_at_ != nullptr) _impl_.lease_expires_at_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); +} +Assignment::Assignment(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, Assignment_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment.v1.Assignment) +} +PROTOBUF_NDEBUG_INLINE Assignment::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment::v1::Assignment& from_msg) + : _has_bits_{from._has_bits_}, + id_(arena, from.id_), + experiment_id_(arena, from.experiment_id_) {} + +Assignment::Assignment( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const Assignment& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, Assignment_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + Assignment* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + ::uint32_t cached_has_bits = _impl_._has_bits_[0]; + _impl_.treatment_ = (CheckHasBit(cached_has_bits, 0x00000004U)) + ? Super_::CopyConstruct(arena, *from._impl_.treatment_) + : nullptr; + _impl_.lease_expires_at_ = (CheckHasBit(cached_has_bits, 0x00000008U)) + ? Super_::CopyConstruct(arena, *from._impl_.lease_expires_at_) + : nullptr; + _impl_.predecessor_ = (CheckHasBit(cached_has_bits, 0x00000010U)) + ? Super_::CopyConstruct(arena, *from._impl_.predecessor_) + : nullptr; + ::memcpy(reinterpret_cast(&_impl_) + + offsetof(Impl_, generation_), + reinterpret_cast(&from._impl_) + + offsetof(Impl_, generation_), + offsetof(Impl_, mode_) - + offsetof(Impl_, generation_) + + sizeof(Impl_::mode_)); + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment.v1.Assignment) +} +PROTOBUF_NDEBUG_INLINE Assignment::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + : id_(arena), + experiment_id_(arena) {} + +inline void Assignment::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + ::memset(reinterpret_cast(&_impl_) + + offsetof(Impl_, treatment_), + 0, + offsetof(Impl_, mode_) - + offsetof(Impl_, treatment_) + + sizeof(Impl_::mode_)); +} +Assignment::~Assignment() { + // @@protoc_insertion_point(destructor:xtcp_treatment.v1.Assignment) + SharedDtor(*this); +} +inline void Assignment::SharedDtor(MessageLite& self) { + Assignment& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + this_._impl_.id_.Destroy(); + this_._impl_.experiment_id_.Destroy(); + delete this_._impl_.treatment_; + delete this_._impl_.lease_expires_at_; + delete this_._impl_.predecessor_; + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull Assignment_class_data_ = + Assignment::InternalGenerateClassData_(Assignment_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +Assignment::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&Assignment_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(Assignment_class_data_.tc_table); + return Assignment_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +Assignment::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&Assignment_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&Assignment_globals_)); + return Assignment_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const Assignment::ParseTableT_ + Assignment::_table_ = + Assignment::InternalGenerateParseTable_(Assignment_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void Assignment::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment.v1.Assignment) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000001fU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + this_._impl_.id_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + this_._impl_.experiment_id_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + ABSL_DCHECK(this_._impl_.treatment_ != nullptr); + this_._impl_.treatment_->Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + ABSL_DCHECK(this_._impl_.lease_expires_at_ != nullptr); + this_._impl_.lease_expires_at_->Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + ABSL_DCHECK(this_._impl_.predecessor_ != nullptr); + this_._impl_.predecessor_->Clear(); + } + } + if (BatchCheckHasBit(cached_has_bits, 0x00000060U)) { + ::memset(&this_._impl_.generation_, 0, + static_cast<::size_t>( + reinterpret_cast(&this_._impl_.mode_) - + reinterpret_cast(&this_._impl_.generation_)) + + sizeof(_impl_.mode_)); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL Assignment::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const Assignment& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL Assignment::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const Assignment& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment.v1.Assignment) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // string id = 1 [json_name = "id", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_id().empty()) { + const ::std::string& _s = this_._internal_id(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.Assignment.id"); + target = stream->WriteStringMaybeAliased(1, _s, target); + } + } + + // string experiment_id = 2 [json_name = "experimentId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!this_._internal_experiment_id().empty()) { + const ::std::string& _s = this_._internal_experiment_id(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.Assignment.experiment_id"); + target = stream->WriteStringMaybeAliased(2, _s, target); + } + } + + // .xtcp_treatment.v1.Treatment treatment = 3 [json_name = "treatment", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 3, *this_._impl_.treatment_, this_._impl_.treatment_->GetCachedSize(), target, + stream); + } + + // .xtcp_treatment.v1.AssignmentMode mode = 4 [json_name = "mode", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + if (this_._internal_mode() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 4, this_._internal_mode(), target); + } + } + + // .google.protobuf.Timestamp lease_expires_at = 5 [json_name = "leaseExpiresAt"]; + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 5, *this_._impl_.lease_expires_at_, this_._impl_.lease_expires_at_->GetCachedSize(), target, + stream); + } + + // .xtcp_treatment.v1.Treatment predecessor = 6 [json_name = "predecessor"]; + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 6, *this_._impl_.predecessor_, this_._impl_.predecessor_->GetCachedSize(), target, + stream); + } + + // uint64 generation = 7 [json_name = "generation", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (this_._internal_generation() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray( + 7, this_._internal_generation(), target); + } + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment.v1.Assignment) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t Assignment::ByteSizeLong(const MessageLite& base) { + const Assignment& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t Assignment::ByteSizeLong() const { + const Assignment& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment.v1.Assignment) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + ::_pbi::Prefetch5LinesFrom7Lines(&this_); + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000007fU)) { + // string id = 1 [json_name = "id", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_id().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_id()); + } + } + // string experiment_id = 2 [json_name = "experimentId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!this_._internal_experiment_id().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_experiment_id()); + } + } + // .xtcp_treatment.v1.Treatment treatment = 3 [json_name = "treatment", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.treatment_); + } + // .google.protobuf.Timestamp lease_expires_at = 5 [json_name = "leaseExpiresAt"]; + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.lease_expires_at_); + } + // .xtcp_treatment.v1.Treatment predecessor = 6 [json_name = "predecessor"]; + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.predecessor_); + } + // uint64 generation = 7 [json_name = "generation", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (this_._internal_generation() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne( + this_._internal_generation()); + } + } + // .xtcp_treatment.v1.AssignmentMode mode = 4 [json_name = "mode", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + if (this_._internal_mode() != 0) { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this_._internal_mode()); + } + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void Assignment::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + ::google::protobuf::Arena* arena = _this->GetArena(); + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment.v1.Assignment) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000007fU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!from._internal_id().empty()) { + _this->_internal_set_id(from._internal_id()); + } else { + if (_this->_impl_.id_.IsDefault()) { + _this->_internal_set_id(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!from._internal_experiment_id().empty()) { + _this->_internal_set_experiment_id(from._internal_experiment_id()); + } else { + if (_this->_impl_.experiment_id_.IsDefault()) { + _this->_internal_set_experiment_id(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + ABSL_DCHECK(from._impl_.treatment_ != nullptr); + if (_this->_impl_.treatment_ == nullptr) { + _this->_impl_.treatment_ = Super_::CopyConstruct(arena, *from._impl_.treatment_); + } else { + _this->_impl_.treatment_->MergeFrom(*from._impl_.treatment_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + ABSL_DCHECK(from._impl_.lease_expires_at_ != nullptr); + if (_this->_impl_.lease_expires_at_ == nullptr) { + _this->_impl_.lease_expires_at_ = Super_::CopyConstruct(arena, *from._impl_.lease_expires_at_); + } else { + _this->_impl_.lease_expires_at_->MergeFrom(*from._impl_.lease_expires_at_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + ABSL_DCHECK(from._impl_.predecessor_ != nullptr); + if (_this->_impl_.predecessor_ == nullptr) { + _this->_impl_.predecessor_ = Super_::CopyConstruct(arena, *from._impl_.predecessor_); + } else { + _this->_impl_.predecessor_->MergeFrom(*from._impl_.predecessor_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (from._internal_generation() != 0) { + _this->_impl_.generation_ = from._impl_.generation_; + } + } + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + if (from._internal_mode() != 0) { + _this->_impl_.mode_ = from._impl_.mode_; + } + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void Assignment::CopyFrom(const Assignment& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment.v1.Assignment) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void Assignment::InternalSwap(Assignment* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + auto* arena = GetArena(); + ABSL_DCHECK_EQ(arena, other->GetArena()); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.id_, &other->_impl_.id_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.experiment_id_, &other->_impl_.experiment_id_, arena); + ::google::protobuf::internal::memswap< + PROTOBUF_FIELD_OFFSET(Assignment, _impl_.mode_) + + sizeof(Assignment::_impl_.mode_) + - PROTOBUF_FIELD_OFFSET(Assignment, _impl_.treatment_)>( + reinterpret_cast(&_impl_.treatment_), + reinterpret_cast(&other->_impl_.treatment_)); +} + +::google::protobuf::Metadata Assignment::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +void DesiredState::clear_issued_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.issued_at_ != nullptr) _impl_.issued_at_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); +} +DesiredState::DesiredState(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, DesiredState_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment.v1.DesiredState) +} +PROTOBUF_NDEBUG_INLINE DesiredState::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment::v1::DesiredState& from_msg) + : _has_bits_{from._has_bits_}, + assignments_ { + visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment::v1::DesiredState, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::DesiredState, _impl_.assignments_)>() + , arena, from.assignments_ + } + , + agent_id_(arena, from.agent_id_) {} + +DesiredState::DesiredState( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const DesiredState& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, DesiredState_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + DesiredState* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + ::uint32_t cached_has_bits = _impl_._has_bits_[0]; + _impl_.issued_at_ = (CheckHasBit(cached_has_bits, 0x00000004U)) + ? Super_::CopyConstruct(arena, *from._impl_.issued_at_) + : nullptr; + ::memcpy(reinterpret_cast(&_impl_) + + offsetof(Impl_, revision_), + reinterpret_cast(&from._impl_) + + offsetof(Impl_, revision_), + offsetof(Impl_, suspended_) - + offsetof(Impl_, revision_) + + sizeof(Impl_::suspended_)); + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment.v1.DesiredState) +} +PROTOBUF_NDEBUG_INLINE DesiredState::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + : assignments_ { visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment::v1::DesiredState, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::DesiredState, _impl_.assignments_)>() + } + , + agent_id_(arena) {} + +inline void DesiredState::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + ::memset(reinterpret_cast(&_impl_) + + offsetof(Impl_, issued_at_), + 0, + offsetof(Impl_, suspended_) - + offsetof(Impl_, issued_at_) + + sizeof(Impl_::suspended_)); +} +DesiredState::~DesiredState() { + // @@protoc_insertion_point(destructor:xtcp_treatment.v1.DesiredState) + SharedDtor(*this); +} +inline void DesiredState::SharedDtor(MessageLite& self) { + DesiredState& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + this_._impl_.agent_id_.Destroy(); + delete this_._impl_.issued_at_; + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull DesiredState_class_data_ = + DesiredState::InternalGenerateClassData_(DesiredState_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +DesiredState::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&DesiredState_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(DesiredState_class_data_.tc_table); + return DesiredState_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +DesiredState::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&DesiredState_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&DesiredState_globals_)); + return DesiredState_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const DesiredState::ParseTableT_ + DesiredState::_table_ = + DesiredState::InternalGenerateParseTable_(DesiredState_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void DesiredState::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment.v1.DesiredState) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x00000007U)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + _impl_.assignments_.Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + this_._impl_.agent_id_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + ABSL_DCHECK(this_._impl_.issued_at_ != nullptr); + this_._impl_.issued_at_->Clear(); + } + } + if (BatchCheckHasBit(cached_has_bits, 0x00000018U)) { + ::memset(&this_._impl_.revision_, 0, + static_cast<::size_t>( + reinterpret_cast(&this_._impl_.suspended_) - + reinterpret_cast(&this_._impl_.revision_)) + + sizeof(_impl_.suspended_)); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL DesiredState::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const DesiredState& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL DesiredState::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const DesiredState& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment.v1.DesiredState) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!this_._internal_agent_id().empty()) { + const ::std::string& _s = this_._internal_agent_id(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.DesiredState.agent_id"); + target = stream->WriteStringMaybeAliased(1, _s, target); + } + } + + // uint64 revision = 2 [json_name = "revision", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (this_._internal_revision() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray( + 2, this_._internal_revision(), target); + } + } + + // bool suspended = 3 [json_name = "suspended"]; + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (this_._internal_suspended() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray( + 3, this_._internal_suspended(), target); + } + } + + // repeated .xtcp_treatment.v1.Assignment assignments = 4 [json_name = "assignments", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + for (unsigned i = 0, n = static_cast( + this_._internal_assignments_size()); + i < n; i++) { + const auto& repfield = this_._internal_assignments().Get(i); + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 4, repfield, repfield.GetCachedSize(), target, + stream); + } + } + + // .google.protobuf.Timestamp issued_at = 5 [json_name = "issuedAt", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 5, *this_._impl_.issued_at_, this_._impl_.issued_at_->GetCachedSize(), target, + stream); + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment.v1.DesiredState) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t DesiredState::ByteSizeLong(const MessageLite& base) { + const DesiredState& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t DesiredState::ByteSizeLong() const { + const DesiredState& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment.v1.DesiredState) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + ::_pbi::Prefetch5LinesFrom7Lines(&this_); + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000001fU)) { + // repeated .xtcp_treatment.v1.Assignment assignments = 4 [json_name = "assignments", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + total_size += 1UL * this_._internal_assignments_size(); + for (const auto& msg : this_._internal_assignments()) { + total_size += ::google::protobuf::internal::WireFormatLite::MessageSize(msg); + } + } + // string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!this_._internal_agent_id().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_agent_id()); + } + } + // .google.protobuf.Timestamp issued_at = 5 [json_name = "issuedAt", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.issued_at_); + } + // uint64 revision = 2 [json_name = "revision", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (this_._internal_revision() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne( + this_._internal_revision()); + } + } + // bool suspended = 3 [json_name = "suspended"]; + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (this_._internal_suspended() != 0) { + total_size += 2; + } + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void DesiredState::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + ::google::protobuf::Arena* arena = _this->GetArena(); + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment.v1.DesiredState) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000001fU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + _this->_internal_mutable_assignments()->InternalMergeFromWithArena( + ::google::protobuf::MessageLite::internal_visibility(), arena, + from._internal_assignments()); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!from._internal_agent_id().empty()) { + _this->_internal_set_agent_id(from._internal_agent_id()); + } else { + if (_this->_impl_.agent_id_.IsDefault()) { + _this->_internal_set_agent_id(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + ABSL_DCHECK(from._impl_.issued_at_ != nullptr); + if (_this->_impl_.issued_at_ == nullptr) { + _this->_impl_.issued_at_ = Super_::CopyConstruct(arena, *from._impl_.issued_at_); + } else { + _this->_impl_.issued_at_->MergeFrom(*from._impl_.issued_at_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (from._internal_revision() != 0) { + _this->_impl_.revision_ = from._impl_.revision_; + } + } + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (from._internal_suspended() != 0) { + _this->_impl_.suspended_ = from._impl_.suspended_; + } + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void DesiredState::CopyFrom(const DesiredState& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment.v1.DesiredState) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void DesiredState::InternalSwap(DesiredState* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + auto* arena = GetArena(); + ABSL_DCHECK_EQ(arena, other->GetArena()); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + _impl_.assignments_.InternalSwap(&other->_impl_.assignments_); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.agent_id_, &other->_impl_.agent_id_, arena); + ::google::protobuf::internal::memswap< + PROTOBUF_FIELD_OFFSET(DesiredState, _impl_.suspended_) + + sizeof(DesiredState::_impl_.suspended_) + - PROTOBUF_FIELD_OFFSET(DesiredState, _impl_.issued_at_)>( + reinterpret_cast(&_impl_.issued_at_), + reinterpret_cast(&other->_impl_.issued_at_)); +} + +::google::protobuf::Metadata DesiredState::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +Capabilities::Capabilities(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, Capabilities_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment.v1.Capabilities) +} +PROTOBUF_NDEBUG_INLINE Capabilities::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment::v1::Capabilities& from_msg) + : _has_bits_{from._has_bits_}, + congestion_algorithms_ { + visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment::v1::Capabilities, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Capabilities, _impl_.congestion_algorithms_)>() + , arena, from.congestion_algorithms_ + } + , + boot_id_(arena, from.boot_id_), + kernel_release_(arena, from.kernel_release_) {} + +Capabilities::Capabilities( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const Capabilities& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, Capabilities_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + Capabilities* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + ::memcpy(reinterpret_cast(&_impl_) + + offsetof(Impl_, simple_host_routing_), + reinterpret_cast(&from._impl_) + + offsetof(Impl_, simple_host_routing_), + offsetof(Impl_, actuator_enabled_) - + offsetof(Impl_, simple_host_routing_) + + sizeof(Impl_::actuator_enabled_)); + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment.v1.Capabilities) +} +PROTOBUF_NDEBUG_INLINE Capabilities::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + : congestion_algorithms_ { visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment::v1::Capabilities, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Capabilities, _impl_.congestion_algorithms_)>() + } + , + boot_id_(arena), + kernel_release_(arena) {} + +inline void Capabilities::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + ::memset(reinterpret_cast(&_impl_) + + offsetof(Impl_, simple_host_routing_), + 0, + offsetof(Impl_, actuator_enabled_) - + offsetof(Impl_, simple_host_routing_) + + sizeof(Impl_::actuator_enabled_)); +} +Capabilities::~Capabilities() { + // @@protoc_insertion_point(destructor:xtcp_treatment.v1.Capabilities) + SharedDtor(*this); +} +inline void Capabilities::SharedDtor(MessageLite& self) { + Capabilities& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + this_._impl_.boot_id_.Destroy(); + this_._impl_.kernel_release_.Destroy(); + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull Capabilities_class_data_ = + Capabilities::InternalGenerateClassData_(Capabilities_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +Capabilities::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&Capabilities_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(Capabilities_class_data_.tc_table); + return Capabilities_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +Capabilities::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&Capabilities_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&Capabilities_globals_)); + return Capabilities_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const Capabilities::ParseTableT_ + Capabilities::_table_ = + Capabilities::InternalGenerateParseTable_(Capabilities_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void Capabilities::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment.v1.Capabilities) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x00000007U)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + _impl_.congestion_algorithms_.Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + this_._impl_.boot_id_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + this_._impl_.kernel_release_.ClearNonDefaultToEmpty(); + } + } + ::memset(&this_._impl_.simple_host_routing_, 0, + static_cast<::size_t>( + reinterpret_cast(&this_._impl_.actuator_enabled_) - + reinterpret_cast(&this_._impl_.simple_host_routing_)) + + sizeof(_impl_.actuator_enabled_)); + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL Capabilities::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const Capabilities& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL Capabilities::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const Capabilities& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment.v1.Capabilities) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // string boot_id = 1 [json_name = "bootId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!this_._internal_boot_id().empty()) { + const ::std::string& _s = this_._internal_boot_id(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.Capabilities.boot_id"); + target = stream->WriteStringMaybeAliased(1, _s, target); + } + } + + // string kernel_release = 2 [json_name = "kernelRelease", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (!this_._internal_kernel_release().empty()) { + const ::std::string& _s = this_._internal_kernel_release(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.Capabilities.kernel_release"); + target = stream->WriteStringMaybeAliased(2, _s, target); + } + } + + // repeated string congestion_algorithms = 3 [json_name = "congestionAlgorithms", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + for (int i = 0, n = this_._internal_congestion_algorithms_size(); i < n; ++i) { + const auto& s = this_._internal_congestion_algorithms().Get(i); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + s.data(), static_cast(s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.Capabilities.congestion_algorithms"); + target = stream->WriteString(3, s, target); + } + } + + // bool simple_host_routing = 4 [json_name = "simpleHostRouting"]; + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (this_._internal_simple_host_routing() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray( + 4, this_._internal_simple_host_routing(), target); + } + } + + // bool actuator_enabled = 5 [json_name = "actuatorEnabled"]; + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (this_._internal_actuator_enabled() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray( + 5, this_._internal_actuator_enabled(), target); + } + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment.v1.Capabilities) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t Capabilities::ByteSizeLong(const MessageLite& base) { + const Capabilities& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t Capabilities::ByteSizeLong() const { + const Capabilities& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment.v1.Capabilities) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + ::_pbi::Prefetch5LinesFrom7Lines(&this_); + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000001fU)) { + // repeated string congestion_algorithms = 3 [json_name = "congestionAlgorithms", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + total_size += + 1 * ::google::protobuf::internal::FromIntSize(this_._internal_congestion_algorithms().size()); + for (int i = 0, n = this_._internal_congestion_algorithms().size(); i < n; ++i) { + total_size += ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_congestion_algorithms().Get(i)); + } + } + // string boot_id = 1 [json_name = "bootId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!this_._internal_boot_id().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_boot_id()); + } + } + // string kernel_release = 2 [json_name = "kernelRelease", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (!this_._internal_kernel_release().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_kernel_release()); + } + } + // bool simple_host_routing = 4 [json_name = "simpleHostRouting"]; + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (this_._internal_simple_host_routing() != 0) { + total_size += 2; + } + } + // bool actuator_enabled = 5 [json_name = "actuatorEnabled"]; + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (this_._internal_actuator_enabled() != 0) { + total_size += 2; + } + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void Capabilities::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + ::google::protobuf::Arena* arena = _this->GetArena(); + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment.v1.Capabilities) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000001fU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + _this->_internal_mutable_congestion_algorithms()->InternalMergeFromWithArena( + ::google::protobuf::MessageLite::internal_visibility(), arena, + from._internal_congestion_algorithms()); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!from._internal_boot_id().empty()) { + _this->_internal_set_boot_id(from._internal_boot_id()); + } else { + if (_this->_impl_.boot_id_.IsDefault()) { + _this->_internal_set_boot_id(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (!from._internal_kernel_release().empty()) { + _this->_internal_set_kernel_release(from._internal_kernel_release()); + } else { + if (_this->_impl_.kernel_release_.IsDefault()) { + _this->_internal_set_kernel_release(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (from._internal_simple_host_routing() != 0) { + _this->_impl_.simple_host_routing_ = from._impl_.simple_host_routing_; + } + } + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (from._internal_actuator_enabled() != 0) { + _this->_impl_.actuator_enabled_ = from._impl_.actuator_enabled_; + } + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void Capabilities::CopyFrom(const Capabilities& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment.v1.Capabilities) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void Capabilities::InternalSwap(Capabilities* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + auto* arena = GetArena(); + ABSL_DCHECK_EQ(arena, other->GetArena()); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + _impl_.congestion_algorithms_.InternalSwap(&other->_impl_.congestion_algorithms_); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.boot_id_, &other->_impl_.boot_id_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.kernel_release_, &other->_impl_.kernel_release_, arena); + ::google::protobuf::internal::memswap< + PROTOBUF_FIELD_OFFSET(Capabilities, _impl_.actuator_enabled_) + + sizeof(Capabilities::_impl_.actuator_enabled_) + - PROTOBUF_FIELD_OFFSET(Capabilities, _impl_.simple_host_routing_)>( + reinterpret_cast(&_impl_.simple_host_routing_), + reinterpret_cast(&other->_impl_.simple_host_routing_)); +} + +::google::protobuf::Metadata Capabilities::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +void ApplicationRecord::clear_received_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.received_at_ != nullptr) _impl_.received_at_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); +} +void ApplicationRecord::clear_attempted_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.attempted_at_ != nullptr) _impl_.attempted_at_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); +} +void ApplicationRecord::clear_applied_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.applied_at_ != nullptr) _impl_.applied_at_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); +} +void ApplicationRecord::clear_verified_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.verified_at_ != nullptr) _impl_.verified_at_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); +} +void ApplicationRecord::clear_removed_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.removed_at_ != nullptr) _impl_.removed_at_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000100U); +} +ApplicationRecord::ApplicationRecord(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, ApplicationRecord_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment.v1.ApplicationRecord) +} +PROTOBUF_NDEBUG_INLINE ApplicationRecord::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment::v1::ApplicationRecord& from_msg) + : _has_bits_{from._has_bits_}, + error_(arena, from.error_), + installed_route_(arena, from.installed_route_), + installed_boot_id_(arena, from.installed_boot_id_) {} + +ApplicationRecord::ApplicationRecord( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const ApplicationRecord& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, ApplicationRecord_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + ApplicationRecord* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + ::uint32_t cached_has_bits = _impl_._has_bits_[0]; + _impl_.assignment_ = (CheckHasBit(cached_has_bits, 0x00000008U)) + ? Super_::CopyConstruct(arena, *from._impl_.assignment_) + : nullptr; + _impl_.received_at_ = (CheckHasBit(cached_has_bits, 0x00000010U)) + ? Super_::CopyConstruct(arena, *from._impl_.received_at_) + : nullptr; + _impl_.attempted_at_ = (CheckHasBit(cached_has_bits, 0x00000020U)) + ? Super_::CopyConstruct(arena, *from._impl_.attempted_at_) + : nullptr; + _impl_.applied_at_ = (CheckHasBit(cached_has_bits, 0x00000040U)) + ? Super_::CopyConstruct(arena, *from._impl_.applied_at_) + : nullptr; + _impl_.verified_at_ = (CheckHasBit(cached_has_bits, 0x00000080U)) + ? Super_::CopyConstruct(arena, *from._impl_.verified_at_) + : nullptr; + _impl_.removed_at_ = (CheckHasBit(cached_has_bits, 0x00000100U)) + ? Super_::CopyConstruct(arena, *from._impl_.removed_at_) + : nullptr; + ::memcpy(reinterpret_cast(&_impl_) + + offsetof(Impl_, desired_revision_), + reinterpret_cast(&from._impl_) + + offsetof(Impl_, desired_revision_), + offsetof(Impl_, expired_latched_) - + offsetof(Impl_, desired_revision_) + + sizeof(Impl_::expired_latched_)); + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment.v1.ApplicationRecord) +} +PROTOBUF_NDEBUG_INLINE ApplicationRecord::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + : error_(arena), + installed_route_(arena), + installed_boot_id_(arena) {} + +inline void ApplicationRecord::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + ::memset(reinterpret_cast(&_impl_) + + offsetof(Impl_, assignment_), + 0, + offsetof(Impl_, expired_latched_) - + offsetof(Impl_, assignment_) + + sizeof(Impl_::expired_latched_)); +} +ApplicationRecord::~ApplicationRecord() { + // @@protoc_insertion_point(destructor:xtcp_treatment.v1.ApplicationRecord) + SharedDtor(*this); +} +inline void ApplicationRecord::SharedDtor(MessageLite& self) { + ApplicationRecord& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + this_._impl_.error_.Destroy(); + this_._impl_.installed_route_.Destroy(); + this_._impl_.installed_boot_id_.Destroy(); + delete this_._impl_.assignment_; + delete this_._impl_.received_at_; + delete this_._impl_.attempted_at_; + delete this_._impl_.applied_at_; + delete this_._impl_.verified_at_; + delete this_._impl_.removed_at_; + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull ApplicationRecord_class_data_ = + ApplicationRecord::InternalGenerateClassData_(ApplicationRecord_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +ApplicationRecord::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&ApplicationRecord_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(ApplicationRecord_class_data_.tc_table); + return ApplicationRecord_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +ApplicationRecord::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&ApplicationRecord_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&ApplicationRecord_globals_)); + return ApplicationRecord_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const ApplicationRecord::ParseTableT_ + ApplicationRecord::_table_ = + ApplicationRecord::InternalGenerateParseTable_(ApplicationRecord_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void ApplicationRecord::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment.v1.ApplicationRecord) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x000000ffU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + this_._impl_.error_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + this_._impl_.installed_route_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + this_._impl_.installed_boot_id_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + ABSL_DCHECK(this_._impl_.assignment_ != nullptr); + this_._impl_.assignment_->Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + ABSL_DCHECK(this_._impl_.received_at_ != nullptr); + this_._impl_.received_at_->Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + ABSL_DCHECK(this_._impl_.attempted_at_ != nullptr); + this_._impl_.attempted_at_->Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + ABSL_DCHECK(this_._impl_.applied_at_ != nullptr); + this_._impl_.applied_at_->Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + ABSL_DCHECK(this_._impl_.verified_at_ != nullptr); + this_._impl_.verified_at_->Clear(); + } + } + if (CheckHasBit(cached_has_bits, 0x00000100U)) { + ABSL_DCHECK(this_._impl_.removed_at_ != nullptr); + this_._impl_.removed_at_->Clear(); + } + if (BatchCheckHasBit(cached_has_bits, 0x00000e00U)) { + ::memset(&this_._impl_.desired_revision_, 0, + static_cast<::size_t>( + reinterpret_cast(&this_._impl_.expired_latched_) - + reinterpret_cast(&this_._impl_.desired_revision_)) + + sizeof(_impl_.expired_latched_)); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL ApplicationRecord::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const ApplicationRecord& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL ApplicationRecord::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const ApplicationRecord& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment.v1.ApplicationRecord) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // .xtcp_treatment.v1.Assignment assignment = 1 [json_name = "assignment", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 1, *this_._impl_.assignment_, this_._impl_.assignment_->GetCachedSize(), target, + stream); + } + + // uint64 desired_revision = 2 [json_name = "desiredRevision"]; + if (CheckHasBit(cached_has_bits, 0x00000200U)) { + if (this_._internal_desired_revision() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray( + 2, this_._internal_desired_revision(), target); + } + } + + // .xtcp_treatment.v1.ApplyStatus status = 3 [json_name = "status", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000400U)) { + if (this_._internal_status() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 3, this_._internal_status(), target); + } + } + + // .google.protobuf.Timestamp received_at = 4 [json_name = "receivedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 4, *this_._impl_.received_at_, this_._impl_.received_at_->GetCachedSize(), target, + stream); + } + + // .google.protobuf.Timestamp attempted_at = 5 [json_name = "attemptedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 5, *this_._impl_.attempted_at_, this_._impl_.attempted_at_->GetCachedSize(), target, + stream); + } + + // .google.protobuf.Timestamp applied_at = 6 [json_name = "appliedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 6, *this_._impl_.applied_at_, this_._impl_.applied_at_->GetCachedSize(), target, + stream); + } + + // .google.protobuf.Timestamp verified_at = 7 [json_name = "verifiedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 7, *this_._impl_.verified_at_, this_._impl_.verified_at_->GetCachedSize(), target, + stream); + } + + // .google.protobuf.Timestamp removed_at = 8 [json_name = "removedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000100U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 8, *this_._impl_.removed_at_, this_._impl_.removed_at_->GetCachedSize(), target, + stream); + } + + // string error = 9 [json_name = "error", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_error().empty()) { + const ::std::string& _s = this_._internal_error(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.ApplicationRecord.error"); + target = stream->WriteStringMaybeAliased(9, _s, target); + } + } + + // bytes installed_route = 10 [json_name = "installedRoute", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!this_._internal_installed_route().empty()) { + const ::std::string& _s = this_._internal_installed_route(); + target = stream->WriteBytesMaybeAliased(10, _s, target); + } + } + + // string installed_boot_id = 11 [json_name = "installedBootId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (!this_._internal_installed_boot_id().empty()) { + const ::std::string& _s = this_._internal_installed_boot_id(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.ApplicationRecord.installed_boot_id"); + target = stream->WriteStringMaybeAliased(11, _s, target); + } + } + + // bool expired_latched = 12 [json_name = "expiredLatched"]; + if (CheckHasBit(cached_has_bits, 0x00000800U)) { + if (this_._internal_expired_latched() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray( + 12, this_._internal_expired_latched(), target); + } + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment.v1.ApplicationRecord) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t ApplicationRecord::ByteSizeLong(const MessageLite& base) { + const ApplicationRecord& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t ApplicationRecord::ByteSizeLong() const { + const ApplicationRecord& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment.v1.ApplicationRecord) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + ::_pbi::Prefetch5LinesFrom7Lines(&this_); + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x000000ffU)) { + // string error = 9 [json_name = "error", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_error().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_error()); + } + } + // bytes installed_route = 10 [json_name = "installedRoute", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!this_._internal_installed_route().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::BytesSize( + this_._internal_installed_route()); + } + } + // string installed_boot_id = 11 [json_name = "installedBootId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (!this_._internal_installed_boot_id().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_installed_boot_id()); + } + } + // .xtcp_treatment.v1.Assignment assignment = 1 [json_name = "assignment", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.assignment_); + } + // .google.protobuf.Timestamp received_at = 4 [json_name = "receivedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.received_at_); + } + // .google.protobuf.Timestamp attempted_at = 5 [json_name = "attemptedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.attempted_at_); + } + // .google.protobuf.Timestamp applied_at = 6 [json_name = "appliedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.applied_at_); + } + // .google.protobuf.Timestamp verified_at = 7 [json_name = "verifiedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.verified_at_); + } + } + if (BatchCheckHasBit(cached_has_bits, 0x00000f00U)) { + // .google.protobuf.Timestamp removed_at = 8 [json_name = "removedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000100U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.removed_at_); + } + // uint64 desired_revision = 2 [json_name = "desiredRevision"]; + if (CheckHasBit(cached_has_bits, 0x00000200U)) { + if (this_._internal_desired_revision() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne( + this_._internal_desired_revision()); + } + } + // .xtcp_treatment.v1.ApplyStatus status = 3 [json_name = "status", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000400U)) { + if (this_._internal_status() != 0) { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this_._internal_status()); + } + } + // bool expired_latched = 12 [json_name = "expiredLatched"]; + if (CheckHasBit(cached_has_bits, 0x00000800U)) { + if (this_._internal_expired_latched() != 0) { + total_size += 2; + } + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void ApplicationRecord::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + ::google::protobuf::Arena* arena = _this->GetArena(); + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment.v1.ApplicationRecord) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x000000ffU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!from._internal_error().empty()) { + _this->_internal_set_error(from._internal_error()); + } else { + if (_this->_impl_.error_.IsDefault()) { + _this->_internal_set_error(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!from._internal_installed_route().empty()) { + _this->_internal_set_installed_route(from._internal_installed_route()); + } else { + if (_this->_impl_.installed_route_.IsDefault()) { + _this->_internal_set_installed_route(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (!from._internal_installed_boot_id().empty()) { + _this->_internal_set_installed_boot_id(from._internal_installed_boot_id()); + } else { + if (_this->_impl_.installed_boot_id_.IsDefault()) { + _this->_internal_set_installed_boot_id(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + ABSL_DCHECK(from._impl_.assignment_ != nullptr); + if (_this->_impl_.assignment_ == nullptr) { + _this->_impl_.assignment_ = Super_::CopyConstruct(arena, *from._impl_.assignment_); + } else { + _this->_impl_.assignment_->MergeFrom(*from._impl_.assignment_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + ABSL_DCHECK(from._impl_.received_at_ != nullptr); + if (_this->_impl_.received_at_ == nullptr) { + _this->_impl_.received_at_ = Super_::CopyConstruct(arena, *from._impl_.received_at_); + } else { + _this->_impl_.received_at_->MergeFrom(*from._impl_.received_at_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + ABSL_DCHECK(from._impl_.attempted_at_ != nullptr); + if (_this->_impl_.attempted_at_ == nullptr) { + _this->_impl_.attempted_at_ = Super_::CopyConstruct(arena, *from._impl_.attempted_at_); + } else { + _this->_impl_.attempted_at_->MergeFrom(*from._impl_.attempted_at_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + ABSL_DCHECK(from._impl_.applied_at_ != nullptr); + if (_this->_impl_.applied_at_ == nullptr) { + _this->_impl_.applied_at_ = Super_::CopyConstruct(arena, *from._impl_.applied_at_); + } else { + _this->_impl_.applied_at_->MergeFrom(*from._impl_.applied_at_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + ABSL_DCHECK(from._impl_.verified_at_ != nullptr); + if (_this->_impl_.verified_at_ == nullptr) { + _this->_impl_.verified_at_ = Super_::CopyConstruct(arena, *from._impl_.verified_at_); + } else { + _this->_impl_.verified_at_->MergeFrom(*from._impl_.verified_at_); + } + } + } + if (BatchCheckHasBit(cached_has_bits, 0x00000f00U)) { + if (CheckHasBit(cached_has_bits, 0x00000100U)) { + ABSL_DCHECK(from._impl_.removed_at_ != nullptr); + if (_this->_impl_.removed_at_ == nullptr) { + _this->_impl_.removed_at_ = Super_::CopyConstruct(arena, *from._impl_.removed_at_); + } else { + _this->_impl_.removed_at_->MergeFrom(*from._impl_.removed_at_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000200U)) { + if (from._internal_desired_revision() != 0) { + _this->_impl_.desired_revision_ = from._impl_.desired_revision_; + } + } + if (CheckHasBit(cached_has_bits, 0x00000400U)) { + if (from._internal_status() != 0) { + _this->_impl_.status_ = from._impl_.status_; + } + } + if (CheckHasBit(cached_has_bits, 0x00000800U)) { + if (from._internal_expired_latched() != 0) { + _this->_impl_.expired_latched_ = from._impl_.expired_latched_; + } + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void ApplicationRecord::CopyFrom(const ApplicationRecord& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment.v1.ApplicationRecord) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void ApplicationRecord::InternalSwap(ApplicationRecord* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + auto* arena = GetArena(); + ABSL_DCHECK_EQ(arena, other->GetArena()); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.error_, &other->_impl_.error_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.installed_route_, &other->_impl_.installed_route_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.installed_boot_id_, &other->_impl_.installed_boot_id_, arena); + ::google::protobuf::internal::memswap< + PROTOBUF_FIELD_OFFSET(ApplicationRecord, _impl_.expired_latched_) + + sizeof(ApplicationRecord::_impl_.expired_latched_) + - PROTOBUF_FIELD_OFFSET(ApplicationRecord, _impl_.assignment_)>( + reinterpret_cast(&_impl_.assignment_), + reinterpret_cast(&other->_impl_.assignment_)); +} + +::google::protobuf::Metadata ApplicationRecord::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +void LocalState::clear_updated_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.updated_at_ != nullptr) _impl_.updated_at_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); +} +LocalState::LocalState(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, LocalState_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment.v1.LocalState) +} +PROTOBUF_NDEBUG_INLINE LocalState::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment::v1::LocalState& from_msg) + : _has_bits_{from._has_bits_}, + records_ { + visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment::v1::LocalState, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::LocalState, _impl_.records_)>() + , arena, from.records_ + } + , + suspension_reason_(arena, from.suspension_reason_) {} + +LocalState::LocalState( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const LocalState& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, LocalState_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + LocalState* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + ::uint32_t cached_has_bits = _impl_._has_bits_[0]; + _impl_.desired_ = (CheckHasBit(cached_has_bits, 0x00000004U)) + ? Super_::CopyConstruct(arena, *from._impl_.desired_) + : nullptr; + _impl_.updated_at_ = (CheckHasBit(cached_has_bits, 0x00000008U)) + ? Super_::CopyConstruct(arena, *from._impl_.updated_at_) + : nullptr; + _impl_.locally_suspended_ = from._impl_.locally_suspended_; + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment.v1.LocalState) +} +PROTOBUF_NDEBUG_INLINE LocalState::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + : records_ { visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment::v1::LocalState, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::LocalState, _impl_.records_)>() + } + , + suspension_reason_(arena) {} + +inline void LocalState::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + ::memset(reinterpret_cast(&_impl_) + + offsetof(Impl_, desired_), + 0, + offsetof(Impl_, locally_suspended_) - + offsetof(Impl_, desired_) + + sizeof(Impl_::locally_suspended_)); +} +LocalState::~LocalState() { + // @@protoc_insertion_point(destructor:xtcp_treatment.v1.LocalState) + SharedDtor(*this); +} +inline void LocalState::SharedDtor(MessageLite& self) { + LocalState& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + this_._impl_.suspension_reason_.Destroy(); + delete this_._impl_.desired_; + delete this_._impl_.updated_at_; + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull LocalState_class_data_ = + LocalState::InternalGenerateClassData_(LocalState_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +LocalState::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&LocalState_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(LocalState_class_data_.tc_table); + return LocalState_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +LocalState::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&LocalState_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&LocalState_globals_)); + return LocalState_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const LocalState::ParseTableT_ + LocalState::_table_ = + LocalState::InternalGenerateParseTable_(LocalState_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void LocalState::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment.v1.LocalState) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000000fU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + _impl_.records_.Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + this_._impl_.suspension_reason_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + ABSL_DCHECK(this_._impl_.desired_ != nullptr); + this_._impl_.desired_->Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + ABSL_DCHECK(this_._impl_.updated_at_ != nullptr); + this_._impl_.updated_at_->Clear(); + } + } + this_._impl_.locally_suspended_ = false; + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL LocalState::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const LocalState& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL LocalState::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const LocalState& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment.v1.LocalState) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // .xtcp_treatment.v1.DesiredState desired = 1 [json_name = "desired"]; + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 1, *this_._impl_.desired_, this_._impl_.desired_->GetCachedSize(), target, + stream); + } + + // bool locally_suspended = 2 [json_name = "locallySuspended"]; + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (this_._internal_locally_suspended() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray( + 2, this_._internal_locally_suspended(), target); + } + } + + // string suspension_reason = 3 [json_name = "suspensionReason", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!this_._internal_suspension_reason().empty()) { + const ::std::string& _s = this_._internal_suspension_reason(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.LocalState.suspension_reason"); + target = stream->WriteStringMaybeAliased(3, _s, target); + } + } + + // repeated .xtcp_treatment.v1.ApplicationRecord records = 4 [json_name = "records", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + for (unsigned i = 0, n = static_cast( + this_._internal_records_size()); + i < n; i++) { + const auto& repfield = this_._internal_records().Get(i); + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 4, repfield, repfield.GetCachedSize(), target, + stream); + } + } + + // .google.protobuf.Timestamp updated_at = 5 [json_name = "updatedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 5, *this_._impl_.updated_at_, this_._impl_.updated_at_->GetCachedSize(), target, + stream); + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment.v1.LocalState) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t LocalState::ByteSizeLong(const MessageLite& base) { + const LocalState& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t LocalState::ByteSizeLong() const { + const LocalState& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment.v1.LocalState) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + ::_pbi::Prefetch5LinesFrom7Lines(&this_); + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000001fU)) { + // repeated .xtcp_treatment.v1.ApplicationRecord records = 4 [json_name = "records", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + total_size += 1UL * this_._internal_records_size(); + for (const auto& msg : this_._internal_records()) { + total_size += ::google::protobuf::internal::WireFormatLite::MessageSize(msg); + } + } + // string suspension_reason = 3 [json_name = "suspensionReason", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!this_._internal_suspension_reason().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_suspension_reason()); + } + } + // .xtcp_treatment.v1.DesiredState desired = 1 [json_name = "desired"]; + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.desired_); + } + // .google.protobuf.Timestamp updated_at = 5 [json_name = "updatedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.updated_at_); + } + // bool locally_suspended = 2 [json_name = "locallySuspended"]; + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (this_._internal_locally_suspended() != 0) { + total_size += 2; + } + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void LocalState::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + ::google::protobuf::Arena* arena = _this->GetArena(); + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment.v1.LocalState) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000001fU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + _this->_internal_mutable_records()->InternalMergeFromWithArena( + ::google::protobuf::MessageLite::internal_visibility(), arena, + from._internal_records()); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!from._internal_suspension_reason().empty()) { + _this->_internal_set_suspension_reason(from._internal_suspension_reason()); + } else { + if (_this->_impl_.suspension_reason_.IsDefault()) { + _this->_internal_set_suspension_reason(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + ABSL_DCHECK(from._impl_.desired_ != nullptr); + if (_this->_impl_.desired_ == nullptr) { + _this->_impl_.desired_ = Super_::CopyConstruct(arena, *from._impl_.desired_); + } else { + _this->_impl_.desired_->MergeFrom(*from._impl_.desired_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + ABSL_DCHECK(from._impl_.updated_at_ != nullptr); + if (_this->_impl_.updated_at_ == nullptr) { + _this->_impl_.updated_at_ = Super_::CopyConstruct(arena, *from._impl_.updated_at_); + } else { + _this->_impl_.updated_at_->MergeFrom(*from._impl_.updated_at_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (from._internal_locally_suspended() != 0) { + _this->_impl_.locally_suspended_ = from._impl_.locally_suspended_; + } + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void LocalState::CopyFrom(const LocalState& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment.v1.LocalState) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void LocalState::InternalSwap(LocalState* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + auto* arena = GetArena(); + ABSL_DCHECK_EQ(arena, other->GetArena()); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + _impl_.records_.InternalSwap(&other->_impl_.records_); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.suspension_reason_, &other->_impl_.suspension_reason_, arena); + ::google::protobuf::internal::memswap< + PROTOBUF_FIELD_OFFSET(LocalState, _impl_.locally_suspended_) + + sizeof(LocalState::_impl_.locally_suspended_) + - PROTOBUF_FIELD_OFFSET(LocalState, _impl_.desired_)>( + reinterpret_cast(&_impl_.desired_), + reinterpret_cast(&other->_impl_.desired_)); +} + +::google::protobuf::Metadata LocalState::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +HostEvidence::HostEvidence(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, HostEvidence_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment.v1.HostEvidence) +} +PROTOBUF_NDEBUG_INLINE HostEvidence::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment::v1::HostEvidence& from_msg) + : _has_bits_{from._has_bits_}, + agent_id_(arena, from.agent_id_) {} + +HostEvidence::HostEvidence( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const HostEvidence& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, HostEvidence_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + HostEvidence* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + ::memcpy(reinterpret_cast(&_impl_) + + offsetof(Impl_, sockets_), + reinterpret_cast(&from._impl_) + + offsetof(Impl_, sockets_), + offsetof(Impl_, collection_healthy_) - + offsetof(Impl_, sockets_) + + sizeof(Impl_::collection_healthy_)); + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment.v1.HostEvidence) +} +PROTOBUF_NDEBUG_INLINE HostEvidence::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + : agent_id_(arena) {} + +inline void HostEvidence::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + ::memset(reinterpret_cast(&_impl_) + + offsetof(Impl_, sockets_), + 0, + offsetof(Impl_, collection_healthy_) - + offsetof(Impl_, sockets_) + + sizeof(Impl_::collection_healthy_)); +} +HostEvidence::~HostEvidence() { + // @@protoc_insertion_point(destructor:xtcp_treatment.v1.HostEvidence) + SharedDtor(*this); +} +inline void HostEvidence::SharedDtor(MessageLite& self) { + HostEvidence& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + this_._impl_.agent_id_.Destroy(); + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull HostEvidence_class_data_ = + HostEvidence::InternalGenerateClassData_(HostEvidence_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +HostEvidence::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&HostEvidence_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(HostEvidence_class_data_.tc_table); + return HostEvidence_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +HostEvidence::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&HostEvidence_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&HostEvidence_globals_)); + return HostEvidence_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const HostEvidence::ParseTableT_ + HostEvidence::_table_ = + HostEvidence::InternalGenerateParseTable_(HostEvidence_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void HostEvidence::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment.v1.HostEvidence) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + this_._impl_.agent_id_.ClearNonDefaultToEmpty(); + } + if (BatchCheckHasBit(cached_has_bits, 0x000000feU)) { + ::memset(&this_._impl_.sockets_, 0, + static_cast<::size_t>( + reinterpret_cast(&this_._impl_.collection_healthy_) - + reinterpret_cast(&this_._impl_.sockets_)) + + sizeof(_impl_.collection_healthy_)); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL HostEvidence::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const HostEvidence& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL HostEvidence::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const HostEvidence& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment.v1.HostEvidence) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_agent_id().empty()) { + const ::std::string& _s = this_._internal_agent_id(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.HostEvidence.agent_id"); + target = stream->WriteStringMaybeAliased(1, _s, target); + } + } + + // uint64 sockets = 2 [json_name = "sockets"]; + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (this_._internal_sockets() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray( + 2, this_._internal_sockets(), target); + } + } + + // uint64 bytes_acked = 3 [json_name = "bytesAcked"]; + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (this_._internal_bytes_acked() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray( + 3, this_._internal_bytes_acked(), target); + } + } + + // double goodput_bps = 4 [json_name = "goodputBps", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (::absl::bit_cast<::uint64_t>(this_._internal_goodput_bps()) != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteDoubleToArray( + 4, this_._internal_goodput_bps(), target); + } + } + + // double retransmission_ratio = 5 [json_name = "retransmissionRatio", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (::absl::bit_cast<::uint64_t>(this_._internal_retransmission_ratio()) != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteDoubleToArray( + 5, this_._internal_retransmission_ratio(), target); + } + } + + // double rtt_p95_ms = 6 [json_name = "rttP95Ms", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (::absl::bit_cast<::uint64_t>(this_._internal_rtt_p95_ms()) != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteDoubleToArray( + 6, this_._internal_rtt_p95_ms(), target); + } + } + + // uint64 exposed_sockets = 7 [json_name = "exposedSockets"]; + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + if (this_._internal_exposed_sockets() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray( + 7, this_._internal_exposed_sockets(), target); + } + } + + // bool collection_healthy = 8 [json_name = "collectionHealthy"]; + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + if (this_._internal_collection_healthy() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray( + 8, this_._internal_collection_healthy(), target); + } + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment.v1.HostEvidence) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t HostEvidence::ByteSizeLong(const MessageLite& base) { + const HostEvidence& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t HostEvidence::ByteSizeLong() const { + const HostEvidence& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment.v1.HostEvidence) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + ::_pbi::Prefetch5LinesFrom7Lines(&this_); + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x000000ffU)) { + // string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_agent_id().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_agent_id()); + } + } + // uint64 sockets = 2 [json_name = "sockets"]; + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (this_._internal_sockets() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne( + this_._internal_sockets()); + } + } + // uint64 bytes_acked = 3 [json_name = "bytesAcked"]; + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (this_._internal_bytes_acked() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne( + this_._internal_bytes_acked()); + } + } + // double goodput_bps = 4 [json_name = "goodputBps", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (::absl::bit_cast<::uint64_t>(this_._internal_goodput_bps()) != 0) { + total_size += 9; + } + } + // double retransmission_ratio = 5 [json_name = "retransmissionRatio", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (::absl::bit_cast<::uint64_t>(this_._internal_retransmission_ratio()) != 0) { + total_size += 9; + } + } + // double rtt_p95_ms = 6 [json_name = "rttP95Ms", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (::absl::bit_cast<::uint64_t>(this_._internal_rtt_p95_ms()) != 0) { + total_size += 9; + } + } + // uint64 exposed_sockets = 7 [json_name = "exposedSockets"]; + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + if (this_._internal_exposed_sockets() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne( + this_._internal_exposed_sockets()); + } + } + // bool collection_healthy = 8 [json_name = "collectionHealthy"]; + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + if (this_._internal_collection_healthy() != 0) { + total_size += 2; + } + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void HostEvidence::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment.v1.HostEvidence) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x000000ffU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!from._internal_agent_id().empty()) { + _this->_internal_set_agent_id(from._internal_agent_id()); + } else { + if (_this->_impl_.agent_id_.IsDefault()) { + _this->_internal_set_agent_id(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (from._internal_sockets() != 0) { + _this->_impl_.sockets_ = from._impl_.sockets_; + } + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (from._internal_bytes_acked() != 0) { + _this->_impl_.bytes_acked_ = from._impl_.bytes_acked_; + } + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (::absl::bit_cast<::uint64_t>(from._internal_goodput_bps()) != 0) { + _this->_impl_.goodput_bps_ = from._impl_.goodput_bps_; + } + } + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (::absl::bit_cast<::uint64_t>(from._internal_retransmission_ratio()) != 0) { + _this->_impl_.retransmission_ratio_ = from._impl_.retransmission_ratio_; + } + } + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (::absl::bit_cast<::uint64_t>(from._internal_rtt_p95_ms()) != 0) { + _this->_impl_.rtt_p95_ms_ = from._impl_.rtt_p95_ms_; + } + } + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + if (from._internal_exposed_sockets() != 0) { + _this->_impl_.exposed_sockets_ = from._impl_.exposed_sockets_; + } + } + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + if (from._internal_collection_healthy() != 0) { + _this->_impl_.collection_healthy_ = from._impl_.collection_healthy_; + } + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void HostEvidence::CopyFrom(const HostEvidence& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment.v1.HostEvidence) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void HostEvidence::InternalSwap(HostEvidence* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + auto* arena = GetArena(); + ABSL_DCHECK_EQ(arena, other->GetArena()); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.agent_id_, &other->_impl_.agent_id_, arena); + ::google::protobuf::internal::memswap< + PROTOBUF_FIELD_OFFSET(HostEvidence, _impl_.collection_healthy_) + + sizeof(HostEvidence::_impl_.collection_healthy_) + - PROTOBUF_FIELD_OFFSET(HostEvidence, _impl_.sockets_)>( + reinterpret_cast(&_impl_.sockets_), + reinterpret_cast(&other->_impl_.sockets_)); +} + +::google::protobuf::Metadata HostEvidence::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +void Evidence::clear_analyzed_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.analyzed_at_ != nullptr) _impl_.analyzed_at_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); +} +void Evidence::clear_window_start() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.window_start_ != nullptr) _impl_.window_start_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); +} +void Evidence::clear_window_end() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.window_end_ != nullptr) _impl_.window_end_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); +} +Evidence::Evidence(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, Evidence_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment.v1.Evidence) +} +PROTOBUF_NDEBUG_INLINE Evidence::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment::v1::Evidence& from_msg) + : _has_bits_{from._has_bits_}, + hosts_ { + visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment::v1::Evidence, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Evidence, _impl_.hosts_)>() + , arena, from.hosts_ + } + , + id_(arena, from.id_), + query_version_(arena, from.query_version_), + enrichment_version_(arena, from.enrichment_version_), + artifact_sha256_(arena, from.artifact_sha256_) {} + +Evidence::Evidence( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const Evidence& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, Evidence_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + Evidence* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + ::uint32_t cached_has_bits = _impl_._has_bits_[0]; + _impl_.analyzed_at_ = (CheckHasBit(cached_has_bits, 0x00000020U)) + ? Super_::CopyConstruct(arena, *from._impl_.analyzed_at_) + : nullptr; + _impl_.window_start_ = (CheckHasBit(cached_has_bits, 0x00000040U)) + ? Super_::CopyConstruct(arena, *from._impl_.window_start_) + : nullptr; + _impl_.window_end_ = (CheckHasBit(cached_has_bits, 0x00000080U)) + ? Super_::CopyConstruct(arena, *from._impl_.window_end_) + : nullptr; + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment.v1.Evidence) +} +PROTOBUF_NDEBUG_INLINE Evidence::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + : hosts_ { visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment::v1::Evidence, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Evidence, _impl_.hosts_)>() + } + , + id_(arena), + query_version_(arena), + enrichment_version_(arena), + artifact_sha256_(arena) {} + +inline void Evidence::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + ::memset(reinterpret_cast(&_impl_) + + offsetof(Impl_, analyzed_at_), + 0, + offsetof(Impl_, window_end_) - + offsetof(Impl_, analyzed_at_) + + sizeof(Impl_::window_end_)); +} +Evidence::~Evidence() { + // @@protoc_insertion_point(destructor:xtcp_treatment.v1.Evidence) + SharedDtor(*this); +} +inline void Evidence::SharedDtor(MessageLite& self) { + Evidence& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + this_._impl_.id_.Destroy(); + this_._impl_.query_version_.Destroy(); + this_._impl_.enrichment_version_.Destroy(); + this_._impl_.artifact_sha256_.Destroy(); + delete this_._impl_.analyzed_at_; + delete this_._impl_.window_start_; + delete this_._impl_.window_end_; + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull Evidence_class_data_ = + Evidence::InternalGenerateClassData_(Evidence_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +Evidence::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&Evidence_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(Evidence_class_data_.tc_table); + return Evidence_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +Evidence::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&Evidence_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&Evidence_globals_)); + return Evidence_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const Evidence::ParseTableT_ + Evidence::_table_ = + Evidence::InternalGenerateParseTable_(Evidence_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void Evidence::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment.v1.Evidence) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x000000ffU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + _impl_.hosts_.Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + this_._impl_.id_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + this_._impl_.query_version_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + this_._impl_.enrichment_version_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + this_._impl_.artifact_sha256_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + ABSL_DCHECK(this_._impl_.analyzed_at_ != nullptr); + this_._impl_.analyzed_at_->Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + ABSL_DCHECK(this_._impl_.window_start_ != nullptr); + this_._impl_.window_start_->Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + ABSL_DCHECK(this_._impl_.window_end_ != nullptr); + this_._impl_.window_end_->Clear(); + } + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL Evidence::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const Evidence& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL Evidence::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const Evidence& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment.v1.Evidence) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // string id = 1 [json_name = "id", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!this_._internal_id().empty()) { + const ::std::string& _s = this_._internal_id(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.Evidence.id"); + target = stream->WriteStringMaybeAliased(1, _s, target); + } + } + + // .google.protobuf.Timestamp analyzed_at = 2 [json_name = "analyzedAt", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 2, *this_._impl_.analyzed_at_, this_._impl_.analyzed_at_->GetCachedSize(), target, + stream); + } + + // .google.protobuf.Timestamp window_start = 3 [json_name = "windowStart", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 3, *this_._impl_.window_start_, this_._impl_.window_start_->GetCachedSize(), target, + stream); + } + + // .google.protobuf.Timestamp window_end = 4 [json_name = "windowEnd", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 4, *this_._impl_.window_end_, this_._impl_.window_end_->GetCachedSize(), target, + stream); + } + + // string query_version = 5 [json_name = "queryVersion", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (!this_._internal_query_version().empty()) { + const ::std::string& _s = this_._internal_query_version(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.Evidence.query_version"); + target = stream->WriteStringMaybeAliased(5, _s, target); + } + } + + // string enrichment_version = 6 [json_name = "enrichmentVersion", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (!this_._internal_enrichment_version().empty()) { + const ::std::string& _s = this_._internal_enrichment_version(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.Evidence.enrichment_version"); + target = stream->WriteStringMaybeAliased(6, _s, target); + } + } + + // repeated .xtcp_treatment.v1.HostEvidence hosts = 7 [json_name = "hosts", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + for (unsigned i = 0, n = static_cast( + this_._internal_hosts_size()); + i < n; i++) { + const auto& repfield = this_._internal_hosts().Get(i); + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 7, repfield, repfield.GetCachedSize(), target, + stream); + } + } + + // string artifact_sha256 = 8 [json_name = "artifactSha256", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (!this_._internal_artifact_sha256().empty()) { + const ::std::string& _s = this_._internal_artifact_sha256(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.Evidence.artifact_sha256"); + target = stream->WriteStringMaybeAliased(8, _s, target); + } + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment.v1.Evidence) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t Evidence::ByteSizeLong(const MessageLite& base) { + const Evidence& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t Evidence::ByteSizeLong() const { + const Evidence& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment.v1.Evidence) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + ::_pbi::Prefetch5LinesFrom7Lines(&this_); + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x000000ffU)) { + // repeated .xtcp_treatment.v1.HostEvidence hosts = 7 [json_name = "hosts", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + total_size += 1UL * this_._internal_hosts_size(); + for (const auto& msg : this_._internal_hosts()) { + total_size += ::google::protobuf::internal::WireFormatLite::MessageSize(msg); + } + } + // string id = 1 [json_name = "id", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!this_._internal_id().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_id()); + } + } + // string query_version = 5 [json_name = "queryVersion", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (!this_._internal_query_version().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_query_version()); + } + } + // string enrichment_version = 6 [json_name = "enrichmentVersion", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (!this_._internal_enrichment_version().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_enrichment_version()); + } + } + // string artifact_sha256 = 8 [json_name = "artifactSha256", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (!this_._internal_artifact_sha256().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_artifact_sha256()); + } + } + // .google.protobuf.Timestamp analyzed_at = 2 [json_name = "analyzedAt", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.analyzed_at_); + } + // .google.protobuf.Timestamp window_start = 3 [json_name = "windowStart", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.window_start_); + } + // .google.protobuf.Timestamp window_end = 4 [json_name = "windowEnd", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.window_end_); + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void Evidence::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + ::google::protobuf::Arena* arena = _this->GetArena(); + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment.v1.Evidence) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x000000ffU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + _this->_internal_mutable_hosts()->InternalMergeFromWithArena( + ::google::protobuf::MessageLite::internal_visibility(), arena, + from._internal_hosts()); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!from._internal_id().empty()) { + _this->_internal_set_id(from._internal_id()); + } else { + if (_this->_impl_.id_.IsDefault()) { + _this->_internal_set_id(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (!from._internal_query_version().empty()) { + _this->_internal_set_query_version(from._internal_query_version()); + } else { + if (_this->_impl_.query_version_.IsDefault()) { + _this->_internal_set_query_version(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (!from._internal_enrichment_version().empty()) { + _this->_internal_set_enrichment_version(from._internal_enrichment_version()); + } else { + if (_this->_impl_.enrichment_version_.IsDefault()) { + _this->_internal_set_enrichment_version(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (!from._internal_artifact_sha256().empty()) { + _this->_internal_set_artifact_sha256(from._internal_artifact_sha256()); + } else { + if (_this->_impl_.artifact_sha256_.IsDefault()) { + _this->_internal_set_artifact_sha256(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + ABSL_DCHECK(from._impl_.analyzed_at_ != nullptr); + if (_this->_impl_.analyzed_at_ == nullptr) { + _this->_impl_.analyzed_at_ = Super_::CopyConstruct(arena, *from._impl_.analyzed_at_); + } else { + _this->_impl_.analyzed_at_->MergeFrom(*from._impl_.analyzed_at_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + ABSL_DCHECK(from._impl_.window_start_ != nullptr); + if (_this->_impl_.window_start_ == nullptr) { + _this->_impl_.window_start_ = Super_::CopyConstruct(arena, *from._impl_.window_start_); + } else { + _this->_impl_.window_start_->MergeFrom(*from._impl_.window_start_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + ABSL_DCHECK(from._impl_.window_end_ != nullptr); + if (_this->_impl_.window_end_ == nullptr) { + _this->_impl_.window_end_ = Super_::CopyConstruct(arena, *from._impl_.window_end_); + } else { + _this->_impl_.window_end_->MergeFrom(*from._impl_.window_end_); + } + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void Evidence::CopyFrom(const Evidence& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment.v1.Evidence) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void Evidence::InternalSwap(Evidence* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + auto* arena = GetArena(); + ABSL_DCHECK_EQ(arena, other->GetArena()); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + _impl_.hosts_.InternalSwap(&other->_impl_.hosts_); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.id_, &other->_impl_.id_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.query_version_, &other->_impl_.query_version_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.enrichment_version_, &other->_impl_.enrichment_version_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.artifact_sha256_, &other->_impl_.artifact_sha256_, arena); + ::google::protobuf::internal::memswap< + PROTOBUF_FIELD_OFFSET(Evidence, _impl_.window_end_) + + sizeof(Evidence::_impl_.window_end_) + - PROTOBUF_FIELD_OFFSET(Evidence, _impl_.analyzed_at_)>( + reinterpret_cast(&_impl_.analyzed_at_), + reinterpret_cast(&other->_impl_.analyzed_at_)); +} + +::google::protobuf::Metadata Evidence::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +void ExperimentPolicy::clear_minimum_observation() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.minimum_observation_ != nullptr) _impl_.minimum_observation_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +void ExperimentPolicy::clear_maximum_phase() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.maximum_phase_ != nullptr) _impl_.maximum_phase_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); +} +void ExperimentPolicy::clear_maximum_evidence_age() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.maximum_evidence_age_ != nullptr) _impl_.maximum_evidence_age_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); +} +ExperimentPolicy::ExperimentPolicy(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, ExperimentPolicy_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment.v1.ExperimentPolicy) +} +PROTOBUF_NDEBUG_INLINE ExperimentPolicy::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment::v1::ExperimentPolicy& from_msg) + : _has_bits_{from._has_bits_} {} + +ExperimentPolicy::ExperimentPolicy( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const ExperimentPolicy& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, ExperimentPolicy_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + ExperimentPolicy* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + ::uint32_t cached_has_bits = _impl_._has_bits_[0]; + _impl_.minimum_observation_ = (CheckHasBit(cached_has_bits, 0x00000001U)) + ? Super_::CopyConstruct(arena, *from._impl_.minimum_observation_) + : nullptr; + _impl_.maximum_phase_ = (CheckHasBit(cached_has_bits, 0x00000002U)) + ? Super_::CopyConstruct(arena, *from._impl_.maximum_phase_) + : nullptr; + _impl_.maximum_evidence_age_ = (CheckHasBit(cached_has_bits, 0x00000004U)) + ? Super_::CopyConstruct(arena, *from._impl_.maximum_evidence_age_) + : nullptr; + ::memcpy(reinterpret_cast(&_impl_) + + offsetof(Impl_, minimum_sockets_per_host_), + reinterpret_cast(&from._impl_) + + offsetof(Impl_, minimum_sockets_per_host_), + offsetof(Impl_, minimum_treated_hosts_) - + offsetof(Impl_, minimum_sockets_per_host_) + + sizeof(Impl_::minimum_treated_hosts_)); + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment.v1.ExperimentPolicy) +} +PROTOBUF_NDEBUG_INLINE ExperimentPolicy::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + {} + +inline void ExperimentPolicy::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + ::memset(reinterpret_cast(&_impl_) + + offsetof(Impl_, minimum_observation_), + 0, + offsetof(Impl_, minimum_treated_hosts_) - + offsetof(Impl_, minimum_observation_) + + sizeof(Impl_::minimum_treated_hosts_)); +} +ExperimentPolicy::~ExperimentPolicy() { + // @@protoc_insertion_point(destructor:xtcp_treatment.v1.ExperimentPolicy) + SharedDtor(*this); +} +inline void ExperimentPolicy::SharedDtor(MessageLite& self) { + ExperimentPolicy& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + delete this_._impl_.minimum_observation_; + delete this_._impl_.maximum_phase_; + delete this_._impl_.maximum_evidence_age_; + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull ExperimentPolicy_class_data_ = + ExperimentPolicy::InternalGenerateClassData_(ExperimentPolicy_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +ExperimentPolicy::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&ExperimentPolicy_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(ExperimentPolicy_class_data_.tc_table); + return ExperimentPolicy_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +ExperimentPolicy::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&ExperimentPolicy_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&ExperimentPolicy_globals_)); + return ExperimentPolicy_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const ExperimentPolicy::ParseTableT_ + ExperimentPolicy::_table_ = + ExperimentPolicy::InternalGenerateParseTable_(ExperimentPolicy_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void ExperimentPolicy::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment.v1.ExperimentPolicy) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x00000007U)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + ABSL_DCHECK(this_._impl_.minimum_observation_ != nullptr); + this_._impl_.minimum_observation_->Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + ABSL_DCHECK(this_._impl_.maximum_phase_ != nullptr); + this_._impl_.maximum_phase_->Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + ABSL_DCHECK(this_._impl_.maximum_evidence_age_ != nullptr); + this_._impl_.maximum_evidence_age_->Clear(); + } + } + if (BatchCheckHasBit(cached_has_bits, 0x000000f8U)) { + ::memset(&this_._impl_.minimum_sockets_per_host_, 0, + static_cast<::size_t>( + reinterpret_cast(&this_._impl_.maximum_rtt_increase_) - + reinterpret_cast(&this_._impl_.minimum_sockets_per_host_)) + + sizeof(_impl_.maximum_rtt_increase_)); + } + this_._impl_.minimum_treated_hosts_ = 0u; + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL ExperimentPolicy::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const ExperimentPolicy& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL ExperimentPolicy::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const ExperimentPolicy& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment.v1.ExperimentPolicy) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // .google.protobuf.Duration minimum_observation = 1 [json_name = "minimumObservation", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 1, *this_._impl_.minimum_observation_, this_._impl_.minimum_observation_->GetCachedSize(), target, + stream); + } + + // .google.protobuf.Duration maximum_phase = 2 [json_name = "maximumPhase", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 2, *this_._impl_.maximum_phase_, this_._impl_.maximum_phase_->GetCachedSize(), target, + stream); + } + + // uint64 minimum_sockets_per_host = 3 [json_name = "minimumSocketsPerHost", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (this_._internal_minimum_sockets_per_host() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray( + 3, this_._internal_minimum_sockets_per_host(), target); + } + } + + // uint64 minimum_bytes_per_host = 4 [json_name = "minimumBytesPerHost", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (this_._internal_minimum_bytes_per_host() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray( + 4, this_._internal_minimum_bytes_per_host(), target); + } + } + + // uint32 minimum_treated_hosts = 5 [json_name = "minimumTreatedHosts", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000100U)) { + if (this_._internal_minimum_treated_hosts() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray( + 5, this_._internal_minimum_treated_hosts(), target); + } + } + + // double minimum_improvement = 6 [json_name = "minimumImprovement", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (::absl::bit_cast<::uint64_t>(this_._internal_minimum_improvement()) != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteDoubleToArray( + 6, this_._internal_minimum_improvement(), target); + } + } + + // double maximum_retransmission_increase = 7 [json_name = "maximumRetransmissionIncrease", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + if (::absl::bit_cast<::uint64_t>(this_._internal_maximum_retransmission_increase()) != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteDoubleToArray( + 7, this_._internal_maximum_retransmission_increase(), target); + } + } + + // double maximum_rtt_increase = 8 [json_name = "maximumRttIncrease", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + if (::absl::bit_cast<::uint64_t>(this_._internal_maximum_rtt_increase()) != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteDoubleToArray( + 8, this_._internal_maximum_rtt_increase(), target); + } + } + + // .google.protobuf.Duration maximum_evidence_age = 9 [json_name = "maximumEvidenceAge", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 9, *this_._impl_.maximum_evidence_age_, this_._impl_.maximum_evidence_age_->GetCachedSize(), target, + stream); + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment.v1.ExperimentPolicy) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t ExperimentPolicy::ByteSizeLong(const MessageLite& base) { + const ExperimentPolicy& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t ExperimentPolicy::ByteSizeLong() const { + const ExperimentPolicy& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment.v1.ExperimentPolicy) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + ::_pbi::Prefetch5LinesFrom7Lines(&this_); + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x000000ffU)) { + // .google.protobuf.Duration minimum_observation = 1 [json_name = "minimumObservation", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.minimum_observation_); + } + // .google.protobuf.Duration maximum_phase = 2 [json_name = "maximumPhase", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.maximum_phase_); + } + // .google.protobuf.Duration maximum_evidence_age = 9 [json_name = "maximumEvidenceAge", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.maximum_evidence_age_); + } + // uint64 minimum_sockets_per_host = 3 [json_name = "minimumSocketsPerHost", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (this_._internal_minimum_sockets_per_host() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne( + this_._internal_minimum_sockets_per_host()); + } + } + // uint64 minimum_bytes_per_host = 4 [json_name = "minimumBytesPerHost", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (this_._internal_minimum_bytes_per_host() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne( + this_._internal_minimum_bytes_per_host()); + } + } + // double minimum_improvement = 6 [json_name = "minimumImprovement", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (::absl::bit_cast<::uint64_t>(this_._internal_minimum_improvement()) != 0) { + total_size += 9; + } + } + // double maximum_retransmission_increase = 7 [json_name = "maximumRetransmissionIncrease", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + if (::absl::bit_cast<::uint64_t>(this_._internal_maximum_retransmission_increase()) != 0) { + total_size += 9; + } + } + // double maximum_rtt_increase = 8 [json_name = "maximumRttIncrease", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + if (::absl::bit_cast<::uint64_t>(this_._internal_maximum_rtt_increase()) != 0) { + total_size += 9; + } + } + } + { + // uint32 minimum_treated_hosts = 5 [json_name = "minimumTreatedHosts", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000100U)) { + if (this_._internal_minimum_treated_hosts() != 0) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne( + this_._internal_minimum_treated_hosts()); + } + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void ExperimentPolicy::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + ::google::protobuf::Arena* arena = _this->GetArena(); + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment.v1.ExperimentPolicy) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x000000ffU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + ABSL_DCHECK(from._impl_.minimum_observation_ != nullptr); + if (_this->_impl_.minimum_observation_ == nullptr) { + _this->_impl_.minimum_observation_ = Super_::CopyConstruct(arena, *from._impl_.minimum_observation_); + } else { + _this->_impl_.minimum_observation_->MergeFrom(*from._impl_.minimum_observation_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + ABSL_DCHECK(from._impl_.maximum_phase_ != nullptr); + if (_this->_impl_.maximum_phase_ == nullptr) { + _this->_impl_.maximum_phase_ = Super_::CopyConstruct(arena, *from._impl_.maximum_phase_); + } else { + _this->_impl_.maximum_phase_->MergeFrom(*from._impl_.maximum_phase_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + ABSL_DCHECK(from._impl_.maximum_evidence_age_ != nullptr); + if (_this->_impl_.maximum_evidence_age_ == nullptr) { + _this->_impl_.maximum_evidence_age_ = Super_::CopyConstruct(arena, *from._impl_.maximum_evidence_age_); + } else { + _this->_impl_.maximum_evidence_age_->MergeFrom(*from._impl_.maximum_evidence_age_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (from._internal_minimum_sockets_per_host() != 0) { + _this->_impl_.minimum_sockets_per_host_ = from._impl_.minimum_sockets_per_host_; + } + } + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (from._internal_minimum_bytes_per_host() != 0) { + _this->_impl_.minimum_bytes_per_host_ = from._impl_.minimum_bytes_per_host_; + } + } + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (::absl::bit_cast<::uint64_t>(from._internal_minimum_improvement()) != 0) { + _this->_impl_.minimum_improvement_ = from._impl_.minimum_improvement_; + } + } + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + if (::absl::bit_cast<::uint64_t>(from._internal_maximum_retransmission_increase()) != 0) { + _this->_impl_.maximum_retransmission_increase_ = from._impl_.maximum_retransmission_increase_; + } + } + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + if (::absl::bit_cast<::uint64_t>(from._internal_maximum_rtt_increase()) != 0) { + _this->_impl_.maximum_rtt_increase_ = from._impl_.maximum_rtt_increase_; + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000100U)) { + if (from._internal_minimum_treated_hosts() != 0) { + _this->_impl_.minimum_treated_hosts_ = from._impl_.minimum_treated_hosts_; + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void ExperimentPolicy::CopyFrom(const ExperimentPolicy& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment.v1.ExperimentPolicy) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void ExperimentPolicy::InternalSwap(ExperimentPolicy* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::google::protobuf::internal::memswap< + PROTOBUF_FIELD_OFFSET(ExperimentPolicy, _impl_.minimum_treated_hosts_) + + sizeof(ExperimentPolicy::_impl_.minimum_treated_hosts_) + - PROTOBUF_FIELD_OFFSET(ExperimentPolicy, _impl_.minimum_observation_)>( + reinterpret_cast(&_impl_.minimum_observation_), + reinterpret_cast(&other->_impl_.minimum_observation_)); +} + +::google::protobuf::Metadata ExperimentPolicy::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +void Phase::clear_issued_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.issued_at_ != nullptr) _impl_.issued_at_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); +} +void Phase::clear_observation_started_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.observation_started_at_ != nullptr) _impl_.observation_started_at_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); +} +void Phase::clear_evaluated_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.evaluated_at_ != nullptr) _impl_.evaluated_at_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); +} +Phase::Phase(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, Phase_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment.v1.Phase) +} +PROTOBUF_NDEBUG_INLINE Phase::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment::v1::Phase& from_msg) + : _has_bits_{from._has_bits_}, + outcome_(arena, from.outcome_), + reviewer_(arena, from.reviewer_), + reason_(arena, from.reason_) {} + +Phase::Phase( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const Phase& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, Phase_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + Phase* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + ::uint32_t cached_has_bits = _impl_._has_bits_[0]; + _impl_.issued_at_ = (CheckHasBit(cached_has_bits, 0x00000008U)) + ? Super_::CopyConstruct(arena, *from._impl_.issued_at_) + : nullptr; + _impl_.observation_started_at_ = (CheckHasBit(cached_has_bits, 0x00000010U)) + ? Super_::CopyConstruct(arena, *from._impl_.observation_started_at_) + : nullptr; + _impl_.evaluated_at_ = (CheckHasBit(cached_has_bits, 0x00000020U)) + ? Super_::CopyConstruct(arena, *from._impl_.evaluated_at_) + : nullptr; + _impl_.evidence_ = (CheckHasBit(cached_has_bits, 0x00000040U)) + ? Super_::CopyConstruct(arena, *from._impl_.evidence_) + : nullptr; + _impl_.target_hosts_ = from._impl_.target_hosts_; + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment.v1.Phase) +} +PROTOBUF_NDEBUG_INLINE Phase::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + : outcome_(arena), + reviewer_(arena), + reason_(arena) {} + +inline void Phase::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + ::memset(reinterpret_cast(&_impl_) + + offsetof(Impl_, issued_at_), + 0, + offsetof(Impl_, target_hosts_) - + offsetof(Impl_, issued_at_) + + sizeof(Impl_::target_hosts_)); +} +Phase::~Phase() { + // @@protoc_insertion_point(destructor:xtcp_treatment.v1.Phase) + SharedDtor(*this); +} +inline void Phase::SharedDtor(MessageLite& self) { + Phase& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + this_._impl_.outcome_.Destroy(); + this_._impl_.reviewer_.Destroy(); + this_._impl_.reason_.Destroy(); + delete this_._impl_.issued_at_; + delete this_._impl_.observation_started_at_; + delete this_._impl_.evaluated_at_; + delete this_._impl_.evidence_; + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull Phase_class_data_ = + Phase::InternalGenerateClassData_(Phase_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +Phase::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&Phase_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(Phase_class_data_.tc_table); + return Phase_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +Phase::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&Phase_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&Phase_globals_)); + return Phase_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const Phase::ParseTableT_ + Phase::_table_ = + Phase::InternalGenerateParseTable_(Phase_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void Phase::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment.v1.Phase) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000007fU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + this_._impl_.outcome_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + this_._impl_.reviewer_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + this_._impl_.reason_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + ABSL_DCHECK(this_._impl_.issued_at_ != nullptr); + this_._impl_.issued_at_->Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + ABSL_DCHECK(this_._impl_.observation_started_at_ != nullptr); + this_._impl_.observation_started_at_->Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + ABSL_DCHECK(this_._impl_.evaluated_at_ != nullptr); + this_._impl_.evaluated_at_->Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + ABSL_DCHECK(this_._impl_.evidence_ != nullptr); + this_._impl_.evidence_->Clear(); + } + } + this_._impl_.target_hosts_ = 0u; + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL Phase::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const Phase& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL Phase::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const Phase& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment.v1.Phase) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // uint32 target_hosts = 1 [json_name = "targetHosts", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + if (this_._internal_target_hosts() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray( + 1, this_._internal_target_hosts(), target); + } + } + + // .google.protobuf.Timestamp issued_at = 2 [json_name = "issuedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 2, *this_._impl_.issued_at_, this_._impl_.issued_at_->GetCachedSize(), target, + stream); + } + + // .google.protobuf.Timestamp observation_started_at = 3 [json_name = "observationStartedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 3, *this_._impl_.observation_started_at_, this_._impl_.observation_started_at_->GetCachedSize(), target, + stream); + } + + // .google.protobuf.Timestamp evaluated_at = 4 [json_name = "evaluatedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 4, *this_._impl_.evaluated_at_, this_._impl_.evaluated_at_->GetCachedSize(), target, + stream); + } + + // .xtcp_treatment.v1.Evidence evidence = 5 [json_name = "evidence"]; + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 5, *this_._impl_.evidence_, this_._impl_.evidence_->GetCachedSize(), target, + stream); + } + + // string outcome = 6 [json_name = "outcome", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_outcome().empty()) { + const ::std::string& _s = this_._internal_outcome(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.Phase.outcome"); + target = stream->WriteStringMaybeAliased(6, _s, target); + } + } + + // string reviewer = 7 [json_name = "reviewer", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!this_._internal_reviewer().empty()) { + const ::std::string& _s = this_._internal_reviewer(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.Phase.reviewer"); + target = stream->WriteStringMaybeAliased(7, _s, target); + } + } + + // string reason = 8 [json_name = "reason", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (!this_._internal_reason().empty()) { + const ::std::string& _s = this_._internal_reason(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.Phase.reason"); + target = stream->WriteStringMaybeAliased(8, _s, target); + } + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment.v1.Phase) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t Phase::ByteSizeLong(const MessageLite& base) { + const Phase& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t Phase::ByteSizeLong() const { + const Phase& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment.v1.Phase) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + ::_pbi::Prefetch5LinesFrom7Lines(&this_); + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x000000ffU)) { + // string outcome = 6 [json_name = "outcome", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_outcome().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_outcome()); + } + } + // string reviewer = 7 [json_name = "reviewer", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!this_._internal_reviewer().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_reviewer()); + } + } + // string reason = 8 [json_name = "reason", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (!this_._internal_reason().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_reason()); + } + } + // .google.protobuf.Timestamp issued_at = 2 [json_name = "issuedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.issued_at_); + } + // .google.protobuf.Timestamp observation_started_at = 3 [json_name = "observationStartedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.observation_started_at_); + } + // .google.protobuf.Timestamp evaluated_at = 4 [json_name = "evaluatedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.evaluated_at_); + } + // .xtcp_treatment.v1.Evidence evidence = 5 [json_name = "evidence"]; + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.evidence_); + } + // uint32 target_hosts = 1 [json_name = "targetHosts", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + if (this_._internal_target_hosts() != 0) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne( + this_._internal_target_hosts()); + } + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void Phase::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + ::google::protobuf::Arena* arena = _this->GetArena(); + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment.v1.Phase) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x000000ffU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!from._internal_outcome().empty()) { + _this->_internal_set_outcome(from._internal_outcome()); + } else { + if (_this->_impl_.outcome_.IsDefault()) { + _this->_internal_set_outcome(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!from._internal_reviewer().empty()) { + _this->_internal_set_reviewer(from._internal_reviewer()); + } else { + if (_this->_impl_.reviewer_.IsDefault()) { + _this->_internal_set_reviewer(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (!from._internal_reason().empty()) { + _this->_internal_set_reason(from._internal_reason()); + } else { + if (_this->_impl_.reason_.IsDefault()) { + _this->_internal_set_reason(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + ABSL_DCHECK(from._impl_.issued_at_ != nullptr); + if (_this->_impl_.issued_at_ == nullptr) { + _this->_impl_.issued_at_ = Super_::CopyConstruct(arena, *from._impl_.issued_at_); + } else { + _this->_impl_.issued_at_->MergeFrom(*from._impl_.issued_at_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + ABSL_DCHECK(from._impl_.observation_started_at_ != nullptr); + if (_this->_impl_.observation_started_at_ == nullptr) { + _this->_impl_.observation_started_at_ = Super_::CopyConstruct(arena, *from._impl_.observation_started_at_); + } else { + _this->_impl_.observation_started_at_->MergeFrom(*from._impl_.observation_started_at_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + ABSL_DCHECK(from._impl_.evaluated_at_ != nullptr); + if (_this->_impl_.evaluated_at_ == nullptr) { + _this->_impl_.evaluated_at_ = Super_::CopyConstruct(arena, *from._impl_.evaluated_at_); + } else { + _this->_impl_.evaluated_at_->MergeFrom(*from._impl_.evaluated_at_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + ABSL_DCHECK(from._impl_.evidence_ != nullptr); + if (_this->_impl_.evidence_ == nullptr) { + _this->_impl_.evidence_ = Super_::CopyConstruct(arena, *from._impl_.evidence_); + } else { + _this->_impl_.evidence_->MergeFrom(*from._impl_.evidence_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + if (from._internal_target_hosts() != 0) { + _this->_impl_.target_hosts_ = from._impl_.target_hosts_; + } + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void Phase::CopyFrom(const Phase& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment.v1.Phase) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void Phase::InternalSwap(Phase* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + auto* arena = GetArena(); + ABSL_DCHECK_EQ(arena, other->GetArena()); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.outcome_, &other->_impl_.outcome_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.reviewer_, &other->_impl_.reviewer_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.reason_, &other->_impl_.reason_, arena); + ::google::protobuf::internal::memswap< + PROTOBUF_FIELD_OFFSET(Phase, _impl_.target_hosts_) + + sizeof(Phase::_impl_.target_hosts_) + - PROTOBUF_FIELD_OFFSET(Phase, _impl_.issued_at_)>( + reinterpret_cast(&_impl_.issued_at_), + reinterpret_cast(&other->_impl_.issued_at_)); +} + +::google::protobuf::Metadata Phase::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +void Experiment::clear_proposed_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.proposed_at_ != nullptr) _impl_.proposed_at_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000200U); +} +Experiment::Experiment(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, Experiment_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment.v1.Experiment) +} +PROTOBUF_NDEBUG_INLINE Experiment::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment::v1::Experiment& from_msg) + : _has_bits_{from._has_bits_}, + cohort_order_ { + visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment::v1::Experiment, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Experiment, _impl_.cohort_order_)>() + , arena, from.cohort_order_ + } + , + phases_ { + visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment::v1::Experiment, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Experiment, _impl_.phases_)>() + , arena, from.phases_ + } + , + id_(arena, from.id_), + location_(arena, from.location_), + hypothesis_(arena, from.hypothesis_), + latest_outcome_(arena, from.latest_outcome_) {} + +Experiment::Experiment( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const Experiment& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, Experiment_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + Experiment* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + ::uint32_t cached_has_bits = _impl_._has_bits_[0]; + _impl_.treatment_ = (CheckHasBit(cached_has_bits, 0x00000040U)) + ? Super_::CopyConstruct(arena, *from._impl_.treatment_) + : nullptr; + _impl_.policy_ = (CheckHasBit(cached_has_bits, 0x00000080U)) + ? Super_::CopyConstruct(arena, *from._impl_.policy_) + : nullptr; + _impl_.baseline_ = (CheckHasBit(cached_has_bits, 0x00000100U)) + ? Super_::CopyConstruct(arena, *from._impl_.baseline_) + : nullptr; + _impl_.proposed_at_ = (CheckHasBit(cached_has_bits, 0x00000200U)) + ? Super_::CopyConstruct(arena, *from._impl_.proposed_at_) + : nullptr; + _impl_.latest_evidence_ = (CheckHasBit(cached_has_bits, 0x00000400U)) + ? Super_::CopyConstruct(arena, *from._impl_.latest_evidence_) + : nullptr; + ::memcpy(reinterpret_cast(&_impl_) + + offsetof(Impl_, revision_), + reinterpret_cast(&from._impl_) + + offsetof(Impl_, revision_), + offsetof(Impl_, status_) - + offsetof(Impl_, revision_) + + sizeof(Impl_::status_)); + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment.v1.Experiment) +} +PROTOBUF_NDEBUG_INLINE Experiment::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + : cohort_order_ { visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment::v1::Experiment, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Experiment, _impl_.cohort_order_)>() + } + , + phases_ { visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment::v1::Experiment, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Experiment, _impl_.phases_)>() + } + , + id_(arena), + location_(arena), + hypothesis_(arena), + latest_outcome_(arena) {} + +inline void Experiment::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + ::memset(reinterpret_cast(&_impl_) + + offsetof(Impl_, treatment_), + 0, + offsetof(Impl_, status_) - + offsetof(Impl_, treatment_) + + sizeof(Impl_::status_)); +} +Experiment::~Experiment() { + // @@protoc_insertion_point(destructor:xtcp_treatment.v1.Experiment) + SharedDtor(*this); +} +inline void Experiment::SharedDtor(MessageLite& self) { + Experiment& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + this_._impl_.id_.Destroy(); + this_._impl_.location_.Destroy(); + this_._impl_.hypothesis_.Destroy(); + this_._impl_.latest_outcome_.Destroy(); + delete this_._impl_.treatment_; + delete this_._impl_.policy_; + delete this_._impl_.baseline_; + delete this_._impl_.proposed_at_; + delete this_._impl_.latest_evidence_; + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull Experiment_class_data_ = + Experiment::InternalGenerateClassData_(Experiment_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +Experiment::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&Experiment_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(Experiment_class_data_.tc_table); + return Experiment_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +Experiment::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&Experiment_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&Experiment_globals_)); + return Experiment_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const Experiment::ParseTableT_ + Experiment::_table_ = + Experiment::InternalGenerateParseTable_(Experiment_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void Experiment::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment.v1.Experiment) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x000000ffU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + _impl_.cohort_order_.Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + _impl_.phases_.Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + this_._impl_.id_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + this_._impl_.location_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + this_._impl_.hypothesis_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + this_._impl_.latest_outcome_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + ABSL_DCHECK(this_._impl_.treatment_ != nullptr); + this_._impl_.treatment_->Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + ABSL_DCHECK(this_._impl_.policy_ != nullptr); + this_._impl_.policy_->Clear(); + } + } + if (BatchCheckHasBit(cached_has_bits, 0x00000700U)) { + if (CheckHasBit(cached_has_bits, 0x00000100U)) { + ABSL_DCHECK(this_._impl_.baseline_ != nullptr); + this_._impl_.baseline_->Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000200U)) { + ABSL_DCHECK(this_._impl_.proposed_at_ != nullptr); + this_._impl_.proposed_at_->Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000400U)) { + ABSL_DCHECK(this_._impl_.latest_evidence_ != nullptr); + this_._impl_.latest_evidence_->Clear(); + } + } + if (BatchCheckHasBit(cached_has_bits, 0x00001800U)) { + ::memset(&this_._impl_.revision_, 0, + static_cast<::size_t>( + reinterpret_cast(&this_._impl_.status_) - + reinterpret_cast(&this_._impl_.revision_)) + + sizeof(_impl_.status_)); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL Experiment::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const Experiment& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL Experiment::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const Experiment& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment.v1.Experiment) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // string id = 1 [json_name = "id", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (!this_._internal_id().empty()) { + const ::std::string& _s = this_._internal_id(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.Experiment.id"); + target = stream->WriteStringMaybeAliased(1, _s, target); + } + } + + // string location = 2 [json_name = "location", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (!this_._internal_location().empty()) { + const ::std::string& _s = this_._internal_location(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.Experiment.location"); + target = stream->WriteStringMaybeAliased(2, _s, target); + } + } + + // string hypothesis = 3 [json_name = "hypothesis", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (!this_._internal_hypothesis().empty()) { + const ::std::string& _s = this_._internal_hypothesis(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.Experiment.hypothesis"); + target = stream->WriteStringMaybeAliased(3, _s, target); + } + } + + // .xtcp_treatment.v1.Treatment treatment = 4 [json_name = "treatment", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 4, *this_._impl_.treatment_, this_._impl_.treatment_->GetCachedSize(), target, + stream); + } + + // .xtcp_treatment.v1.ExperimentPolicy policy = 5 [json_name = "policy", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 5, *this_._impl_.policy_, this_._impl_.policy_->GetCachedSize(), target, + stream); + } + + // .xtcp_treatment.v1.Evidence baseline = 6 [json_name = "baseline", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000100U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 6, *this_._impl_.baseline_, this_._impl_.baseline_->GetCachedSize(), target, + stream); + } + + // repeated string cohort_order = 7 [json_name = "cohortOrder", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + for (int i = 0, n = this_._internal_cohort_order_size(); i < n; ++i) { + const auto& s = this_._internal_cohort_order().Get(i); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + s.data(), static_cast(s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.Experiment.cohort_order"); + target = stream->WriteString(7, s, target); + } + } + + // repeated .xtcp_treatment.v1.Phase phases = 8 [json_name = "phases", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + for (unsigned i = 0, n = static_cast( + this_._internal_phases_size()); + i < n; i++) { + const auto& repfield = this_._internal_phases().Get(i); + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 8, repfield, repfield.GetCachedSize(), target, + stream); + } + } + + // .xtcp_treatment.v1.ExperimentStatus status = 9 [json_name = "status", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00001000U)) { + if (this_._internal_status() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 9, this_._internal_status(), target); + } + } + + // uint64 revision = 10 [json_name = "revision"]; + if (CheckHasBit(cached_has_bits, 0x00000800U)) { + if (this_._internal_revision() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray( + 10, this_._internal_revision(), target); + } + } + + // .google.protobuf.Timestamp proposed_at = 11 [json_name = "proposedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000200U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 11, *this_._impl_.proposed_at_, this_._impl_.proposed_at_->GetCachedSize(), target, + stream); + } + + // .xtcp_treatment.v1.Evidence latest_evidence = 12 [json_name = "latestEvidence"]; + if (CheckHasBit(cached_has_bits, 0x00000400U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 12, *this_._impl_.latest_evidence_, this_._impl_.latest_evidence_->GetCachedSize(), target, + stream); + } + + // string latest_outcome = 13 [json_name = "latestOutcome", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (!this_._internal_latest_outcome().empty()) { + const ::std::string& _s = this_._internal_latest_outcome(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.Experiment.latest_outcome"); + target = stream->WriteStringMaybeAliased(13, _s, target); + } + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment.v1.Experiment) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t Experiment::ByteSizeLong(const MessageLite& base) { + const Experiment& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t Experiment::ByteSizeLong() const { + const Experiment& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment.v1.Experiment) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + ::_pbi::Prefetch5LinesFrom7Lines(&this_); + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x000000ffU)) { + // repeated string cohort_order = 7 [json_name = "cohortOrder", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + total_size += + 1 * ::google::protobuf::internal::FromIntSize(this_._internal_cohort_order().size()); + for (int i = 0, n = this_._internal_cohort_order().size(); i < n; ++i) { + total_size += ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_cohort_order().Get(i)); + } + } + // repeated .xtcp_treatment.v1.Phase phases = 8 [json_name = "phases", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + total_size += 1UL * this_._internal_phases_size(); + for (const auto& msg : this_._internal_phases()) { + total_size += ::google::protobuf::internal::WireFormatLite::MessageSize(msg); + } + } + // string id = 1 [json_name = "id", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (!this_._internal_id().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_id()); + } + } + // string location = 2 [json_name = "location", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (!this_._internal_location().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_location()); + } + } + // string hypothesis = 3 [json_name = "hypothesis", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (!this_._internal_hypothesis().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_hypothesis()); + } + } + // string latest_outcome = 13 [json_name = "latestOutcome", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (!this_._internal_latest_outcome().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_latest_outcome()); + } + } + // .xtcp_treatment.v1.Treatment treatment = 4 [json_name = "treatment", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.treatment_); + } + // .xtcp_treatment.v1.ExperimentPolicy policy = 5 [json_name = "policy", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.policy_); + } + } + if (BatchCheckHasBit(cached_has_bits, 0x00001f00U)) { + // .xtcp_treatment.v1.Evidence baseline = 6 [json_name = "baseline", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000100U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.baseline_); + } + // .google.protobuf.Timestamp proposed_at = 11 [json_name = "proposedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000200U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.proposed_at_); + } + // .xtcp_treatment.v1.Evidence latest_evidence = 12 [json_name = "latestEvidence"]; + if (CheckHasBit(cached_has_bits, 0x00000400U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.latest_evidence_); + } + // uint64 revision = 10 [json_name = "revision"]; + if (CheckHasBit(cached_has_bits, 0x00000800U)) { + if (this_._internal_revision() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne( + this_._internal_revision()); + } + } + // .xtcp_treatment.v1.ExperimentStatus status = 9 [json_name = "status", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00001000U)) { + if (this_._internal_status() != 0) { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this_._internal_status()); + } + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void Experiment::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + ::google::protobuf::Arena* arena = _this->GetArena(); + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment.v1.Experiment) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x000000ffU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + _this->_internal_mutable_cohort_order()->InternalMergeFromWithArena( + ::google::protobuf::MessageLite::internal_visibility(), arena, + from._internal_cohort_order()); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + _this->_internal_mutable_phases()->InternalMergeFromWithArena( + ::google::protobuf::MessageLite::internal_visibility(), arena, + from._internal_phases()); + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (!from._internal_id().empty()) { + _this->_internal_set_id(from._internal_id()); + } else { + if (_this->_impl_.id_.IsDefault()) { + _this->_internal_set_id(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (!from._internal_location().empty()) { + _this->_internal_set_location(from._internal_location()); + } else { + if (_this->_impl_.location_.IsDefault()) { + _this->_internal_set_location(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (!from._internal_hypothesis().empty()) { + _this->_internal_set_hypothesis(from._internal_hypothesis()); + } else { + if (_this->_impl_.hypothesis_.IsDefault()) { + _this->_internal_set_hypothesis(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (!from._internal_latest_outcome().empty()) { + _this->_internal_set_latest_outcome(from._internal_latest_outcome()); + } else { + if (_this->_impl_.latest_outcome_.IsDefault()) { + _this->_internal_set_latest_outcome(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + ABSL_DCHECK(from._impl_.treatment_ != nullptr); + if (_this->_impl_.treatment_ == nullptr) { + _this->_impl_.treatment_ = Super_::CopyConstruct(arena, *from._impl_.treatment_); + } else { + _this->_impl_.treatment_->MergeFrom(*from._impl_.treatment_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + ABSL_DCHECK(from._impl_.policy_ != nullptr); + if (_this->_impl_.policy_ == nullptr) { + _this->_impl_.policy_ = Super_::CopyConstruct(arena, *from._impl_.policy_); + } else { + _this->_impl_.policy_->MergeFrom(*from._impl_.policy_); + } + } + } + if (BatchCheckHasBit(cached_has_bits, 0x00001f00U)) { + if (CheckHasBit(cached_has_bits, 0x00000100U)) { + ABSL_DCHECK(from._impl_.baseline_ != nullptr); + if (_this->_impl_.baseline_ == nullptr) { + _this->_impl_.baseline_ = Super_::CopyConstruct(arena, *from._impl_.baseline_); + } else { + _this->_impl_.baseline_->MergeFrom(*from._impl_.baseline_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000200U)) { + ABSL_DCHECK(from._impl_.proposed_at_ != nullptr); + if (_this->_impl_.proposed_at_ == nullptr) { + _this->_impl_.proposed_at_ = Super_::CopyConstruct(arena, *from._impl_.proposed_at_); + } else { + _this->_impl_.proposed_at_->MergeFrom(*from._impl_.proposed_at_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000400U)) { + ABSL_DCHECK(from._impl_.latest_evidence_ != nullptr); + if (_this->_impl_.latest_evidence_ == nullptr) { + _this->_impl_.latest_evidence_ = Super_::CopyConstruct(arena, *from._impl_.latest_evidence_); + } else { + _this->_impl_.latest_evidence_->MergeFrom(*from._impl_.latest_evidence_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000800U)) { + if (from._internal_revision() != 0) { + _this->_impl_.revision_ = from._impl_.revision_; + } + } + if (CheckHasBit(cached_has_bits, 0x00001000U)) { + if (from._internal_status() != 0) { + _this->_impl_.status_ = from._impl_.status_; + } + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void Experiment::CopyFrom(const Experiment& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment.v1.Experiment) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void Experiment::InternalSwap(Experiment* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + auto* arena = GetArena(); + ABSL_DCHECK_EQ(arena, other->GetArena()); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + _impl_.cohort_order_.InternalSwap(&other->_impl_.cohort_order_); + _impl_.phases_.InternalSwap(&other->_impl_.phases_); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.id_, &other->_impl_.id_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.location_, &other->_impl_.location_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.hypothesis_, &other->_impl_.hypothesis_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.latest_outcome_, &other->_impl_.latest_outcome_, arena); + ::google::protobuf::internal::memswap< + PROTOBUF_FIELD_OFFSET(Experiment, _impl_.status_) + + sizeof(Experiment::_impl_.status_) + - PROTOBUF_FIELD_OFFSET(Experiment, _impl_.treatment_)>( + reinterpret_cast(&_impl_.treatment_), + reinterpret_cast(&other->_impl_.treatment_)); +} + +::google::protobuf::Metadata Experiment::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY2 ::_pbi::ExtensionIdentifier< + ::google::protobuf::MethodOptions, ::_pbi::EnumTypeTraits< ::xtcp_treatment::v1::Role>, 14, false> + role(kRoleFieldNumber, static_cast< ::xtcp_treatment::v1::Role >(0)); +// @@protoc_insertion_point(namespace_scope) +} // namespace v1 +} // namespace xtcp_treatment +namespace google { +namespace protobuf { +} // namespace protobuf +} // namespace google +// @@protoc_insertion_point(global_scope) +PROTOBUF_ATTRIBUTE_INIT_PRIORITY2 static ::std::false_type + _static_init2_ [[maybe_unused]] = + (::_pbi::AddDescriptors(&descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto), + ::_pbi::ExtensionSet::RegisterEnumExtension( + &::google::protobuf::MethodOptions::default_instance(), 51002, 14, + false, false, ::xtcp_treatment::v1::Role_internal_data_), + ::std::false_type{}); +#include "google/protobuf/port_undef.inc" diff --git a/gen/cpp/xtcp_treatment/v1/treatment.pb.h b/gen/cpp/xtcp_treatment/v1/treatment.pb.h new file mode 100644 index 000000000..2b074341e --- /dev/null +++ b/gen/cpp/xtcp_treatment/v1/treatment.pb.h @@ -0,0 +1,9572 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE +// source: xtcp_treatment/v1/treatment.proto +// Protobuf C++ Version: 7.36.2 + +#ifndef xtcp_5ftreatment_2fv1_2ftreatment_2eproto_2epb_2eh +#define xtcp_5ftreatment_2fv1_2ftreatment_2eproto_2epb_2eh + +#include +#include +#include +#include + +// clang-format off +#include "google/protobuf/runtime_version.h" +#if PROTOBUF_VERSION != 7036002 +#error "Protobuf C++ gencode is built with an incompatible version of" +#error "Protobuf C++ headers/runtime. See" +#error "https://protobuf.dev/support/cross-version-runtime-guarantee/#cpp" +#endif +#include "google/protobuf/io/coded_stream.h" +#include "google/protobuf/arena.h" +#include "google/protobuf/arenastring.h" +#include "google/protobuf/generated_message_tctable_decl.h" +#include "google/protobuf/generated_message_util.h" +#include "google/protobuf/metadata_lite.h" +#include "google/protobuf/generated_message_reflection.h" +#include "google/protobuf/message.h" +#include "google/protobuf/message_lite.h" +#include "google/protobuf/repeated_field.h" // IWYU pragma: export +#include "google/protobuf/extension_set.h" // IWYU pragma: export +#include "google/protobuf/generated_enum_reflection.h" +#include "google/protobuf/unknown_field_set.h" +#include "buf/validate/validate.pb.h" +#include "google/protobuf/descriptor.pb.h" +#include "google/protobuf/duration.pb.h" +#include "google/protobuf/timestamp.pb.h" +// @@protoc_insertion_point(includes) + +// Must be included last. +#include "google/protobuf/port_def.inc" + +#define PROTOBUF_INTERNAL_EXPORT_xtcp_5ftreatment_2fv1_2ftreatment_2eproto + +// Internal implementation detail -- do not use these members. +struct TableStruct_xtcp_5ftreatment_2fv1_2ftreatment_2eproto { + static const ::uint32_t offsets[]; +}; +extern "C" { +extern const ::google::protobuf::internal::DescriptorTable descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto; +} // extern "C" +namespace xtcp_treatment { +namespace v1 { +enum ApplyStatus : int; +extern const uint32_t ApplyStatus_internal_data_[]; +enum AssignmentMode : int; +extern const uint32_t AssignmentMode_internal_data_[]; +enum ExperimentStatus : int; +extern const uint32_t ExperimentStatus_internal_data_[]; +enum Role : int; +extern const uint32_t Role_internal_data_[]; +class ApplicationRecord; +struct ApplicationRecordGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern ApplicationRecordGlobalsTypeInternal ApplicationRecord_globals_; +extern const ::google::protobuf::internal::ClassDataFull ApplicationRecord_class_data_; +#else +extern const ApplicationRecordGlobalsTypeInternal ApplicationRecord_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +class Assignment; +struct AssignmentGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern AssignmentGlobalsTypeInternal Assignment_globals_; +extern const ::google::protobuf::internal::ClassDataFull Assignment_class_data_; +#else +extern const AssignmentGlobalsTypeInternal Assignment_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +class Capabilities; +struct CapabilitiesGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern CapabilitiesGlobalsTypeInternal Capabilities_globals_; +extern const ::google::protobuf::internal::ClassDataFull Capabilities_class_data_; +#else +extern const CapabilitiesGlobalsTypeInternal Capabilities_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +class DesiredState; +struct DesiredStateGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern DesiredStateGlobalsTypeInternal DesiredState_globals_; +extern const ::google::protobuf::internal::ClassDataFull DesiredState_class_data_; +#else +extern const DesiredStateGlobalsTypeInternal DesiredState_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +class Evidence; +struct EvidenceGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern EvidenceGlobalsTypeInternal Evidence_globals_; +extern const ::google::protobuf::internal::ClassDataFull Evidence_class_data_; +#else +extern const EvidenceGlobalsTypeInternal Evidence_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +class Experiment; +struct ExperimentGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern ExperimentGlobalsTypeInternal Experiment_globals_; +extern const ::google::protobuf::internal::ClassDataFull Experiment_class_data_; +#else +extern const ExperimentGlobalsTypeInternal Experiment_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +class ExperimentPolicy; +struct ExperimentPolicyGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern ExperimentPolicyGlobalsTypeInternal ExperimentPolicy_globals_; +extern const ::google::protobuf::internal::ClassDataFull ExperimentPolicy_class_data_; +#else +extern const ExperimentPolicyGlobalsTypeInternal ExperimentPolicy_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +class HostEvidence; +struct HostEvidenceGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern HostEvidenceGlobalsTypeInternal HostEvidence_globals_; +extern const ::google::protobuf::internal::ClassDataFull HostEvidence_class_data_; +#else +extern const HostEvidenceGlobalsTypeInternal HostEvidence_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +class LocalState; +struct LocalStateGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern LocalStateGlobalsTypeInternal LocalState_globals_; +extern const ::google::protobuf::internal::ClassDataFull LocalState_class_data_; +#else +extern const LocalStateGlobalsTypeInternal LocalState_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +class Phase; +struct PhaseGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern PhaseGlobalsTypeInternal Phase_globals_; +extern const ::google::protobuf::internal::ClassDataFull Phase_class_data_; +#else +extern const PhaseGlobalsTypeInternal Phase_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +class TCPSettings; +struct TCPSettingsGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern TCPSettingsGlobalsTypeInternal TCPSettings_globals_; +extern const ::google::protobuf::internal::ClassDataFull TCPSettings_class_data_; +#else +extern const TCPSettingsGlobalsTypeInternal TCPSettings_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +class Treatment; +struct TreatmentGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern TreatmentGlobalsTypeInternal Treatment_globals_; +extern const ::google::protobuf::internal::ClassDataFull Treatment_class_data_; +#else +extern const TreatmentGlobalsTypeInternal Treatment_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +} // namespace v1 +} // namespace xtcp_treatment +namespace google { +namespace protobuf { +template <> +internal::EnumTraitsT<::xtcp_treatment::v1::ApplyStatus_internal_data_> + internal::EnumTraitsImpl::value<::xtcp_treatment::v1::ApplyStatus>; +template <> +internal::EnumTraitsT<::xtcp_treatment::v1::AssignmentMode_internal_data_> + internal::EnumTraitsImpl::value<::xtcp_treatment::v1::AssignmentMode>; +template <> +internal::EnumTraitsT<::xtcp_treatment::v1::ExperimentStatus_internal_data_> + internal::EnumTraitsImpl::value<::xtcp_treatment::v1::ExperimentStatus>; +template <> +internal::EnumTraitsT<::xtcp_treatment::v1::Role_internal_data_> + internal::EnumTraitsImpl::value<::xtcp_treatment::v1::Role>; +} // namespace protobuf +} // namespace google + +namespace xtcp_treatment { +namespace v1 { +enum Role : int { + ROLE_UNSPECIFIED = 0, + ROLE_AGENT = 1, + ROLE_OPERATOR = 2, + Role_INT_MIN_SENTINEL_DO_NOT_USE_ = + ::std::numeric_limits<::int32_t>::min(), + Role_INT_MAX_SENTINEL_DO_NOT_USE_ = + ::std::numeric_limits<::int32_t>::max(), +}; + +extern const uint32_t Role_internal_data_[]; +inline constexpr Role Role_MIN = + static_cast(0); +inline constexpr Role Role_MAX = + static_cast(2); +[[nodiscard]] inline bool Role_IsValid(int value) { + return 0 <= value && value <= 2; +} +inline constexpr int Role_ARRAYSIZE = 2 + 1; +[[nodiscard]] const ::google::protobuf::EnumDescriptor* PROTOBUF_NONNULL +Role_descriptor(); +[[nodiscard]] inline auto ProtobufInternalGetEnumDescriptor(Role) { + return Role_descriptor(); +} +template +[[nodiscard]] const ::std::string& Role_Name(T value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to Role_Name()."); + return Role_Name(static_cast(value)); +} +template <> +[[nodiscard]] inline const ::std::string& Role_Name(Role value) { + return ::google::protobuf::internal::NameOfDenseEnum( + static_cast(value)); +} +[[nodiscard]] inline bool Role_Parse( + ::absl::string_view name, Role* PROTOBUF_NONNULL value) { + return ::google::protobuf::internal::ParseNamedEnum(Role_descriptor(), name, + value); +} +enum AssignmentMode : int { + ASSIGNMENT_MODE_UNSPECIFIED = 0, + ASSIGNMENT_MODE_TRIAL = 1, + ASSIGNMENT_MODE_STABLE = 2, + ASSIGNMENT_MODE_SUSPENDED = 3, + ASSIGNMENT_MODE_RETIRED = 4, + AssignmentMode_INT_MIN_SENTINEL_DO_NOT_USE_ = + ::std::numeric_limits<::int32_t>::min(), + AssignmentMode_INT_MAX_SENTINEL_DO_NOT_USE_ = + ::std::numeric_limits<::int32_t>::max(), +}; + +extern const uint32_t AssignmentMode_internal_data_[]; +inline constexpr AssignmentMode AssignmentMode_MIN = + static_cast(0); +inline constexpr AssignmentMode AssignmentMode_MAX = + static_cast(4); +[[nodiscard]] inline bool AssignmentMode_IsValid(int value) { + return 0 <= value && value <= 4; +} +inline constexpr int AssignmentMode_ARRAYSIZE = 4 + 1; +[[nodiscard]] const ::google::protobuf::EnumDescriptor* PROTOBUF_NONNULL +AssignmentMode_descriptor(); +[[nodiscard]] inline auto ProtobufInternalGetEnumDescriptor(AssignmentMode) { + return AssignmentMode_descriptor(); +} +template +[[nodiscard]] const ::std::string& AssignmentMode_Name(T value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to AssignmentMode_Name()."); + return AssignmentMode_Name(static_cast(value)); +} +template <> +[[nodiscard]] inline const ::std::string& AssignmentMode_Name(AssignmentMode value) { + return ::google::protobuf::internal::NameOfDenseEnum( + static_cast(value)); +} +[[nodiscard]] inline bool AssignmentMode_Parse( + ::absl::string_view name, AssignmentMode* PROTOBUF_NONNULL value) { + return ::google::protobuf::internal::ParseNamedEnum(AssignmentMode_descriptor(), name, + value); +} +enum ApplyStatus : int { + APPLY_STATUS_UNSPECIFIED = 0, + APPLY_STATUS_PENDING = 1, + APPLY_STATUS_VERIFIED = 2, + APPLY_STATUS_REMOVED = 3, + APPLY_STATUS_EXPIRED = 4, + APPLY_STATUS_FAILED = 5, + ApplyStatus_INT_MIN_SENTINEL_DO_NOT_USE_ = + ::std::numeric_limits<::int32_t>::min(), + ApplyStatus_INT_MAX_SENTINEL_DO_NOT_USE_ = + ::std::numeric_limits<::int32_t>::max(), +}; + +extern const uint32_t ApplyStatus_internal_data_[]; +inline constexpr ApplyStatus ApplyStatus_MIN = + static_cast(0); +inline constexpr ApplyStatus ApplyStatus_MAX = + static_cast(5); +[[nodiscard]] inline bool ApplyStatus_IsValid(int value) { + return 0 <= value && value <= 5; +} +inline constexpr int ApplyStatus_ARRAYSIZE = 5 + 1; +[[nodiscard]] const ::google::protobuf::EnumDescriptor* PROTOBUF_NONNULL +ApplyStatus_descriptor(); +[[nodiscard]] inline auto ProtobufInternalGetEnumDescriptor(ApplyStatus) { + return ApplyStatus_descriptor(); +} +template +[[nodiscard]] const ::std::string& ApplyStatus_Name(T value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to ApplyStatus_Name()."); + return ApplyStatus_Name(static_cast(value)); +} +template <> +[[nodiscard]] inline const ::std::string& ApplyStatus_Name(ApplyStatus value) { + return ::google::protobuf::internal::NameOfDenseEnum( + static_cast(value)); +} +[[nodiscard]] inline bool ApplyStatus_Parse( + ::absl::string_view name, ApplyStatus* PROTOBUF_NONNULL value) { + return ::google::protobuf::internal::ParseNamedEnum(ApplyStatus_descriptor(), name, + value); +} +enum ExperimentStatus : int { + EXPERIMENT_STATUS_UNSPECIFIED = 0, + EXPERIMENT_STATUS_PROPOSED = 1, + EXPERIMENT_STATUS_RUNNING = 2, + EXPERIMENT_STATUS_PAUSED = 3, + EXPERIMENT_STATUS_SUSPENDED = 4, + EXPERIMENT_STATUS_COMPLETE = 5, + EXPERIMENT_STATUS_RETIRED = 6, + EXPERIMENT_STATUS_ROLLED_BACK = 7, + ExperimentStatus_INT_MIN_SENTINEL_DO_NOT_USE_ = + ::std::numeric_limits<::int32_t>::min(), + ExperimentStatus_INT_MAX_SENTINEL_DO_NOT_USE_ = + ::std::numeric_limits<::int32_t>::max(), +}; + +extern const uint32_t ExperimentStatus_internal_data_[]; +inline constexpr ExperimentStatus ExperimentStatus_MIN = + static_cast(0); +inline constexpr ExperimentStatus ExperimentStatus_MAX = + static_cast(7); +[[nodiscard]] inline bool ExperimentStatus_IsValid(int value) { + return 0 <= value && value <= 7; +} +inline constexpr int ExperimentStatus_ARRAYSIZE = 7 + 1; +[[nodiscard]] const ::google::protobuf::EnumDescriptor* PROTOBUF_NONNULL +ExperimentStatus_descriptor(); +[[nodiscard]] inline auto ProtobufInternalGetEnumDescriptor(ExperimentStatus) { + return ExperimentStatus_descriptor(); +} +template +[[nodiscard]] const ::std::string& ExperimentStatus_Name(T value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to ExperimentStatus_Name()."); + return ExperimentStatus_Name(static_cast(value)); +} +template <> +[[nodiscard]] inline const ::std::string& ExperimentStatus_Name(ExperimentStatus value) { + return ::google::protobuf::internal::NameOfDenseEnum( + static_cast(value)); +} +[[nodiscard]] inline bool ExperimentStatus_Parse( + ::absl::string_view name, ExperimentStatus* PROTOBUF_NONNULL value) { + return ::google::protobuf::internal::ParseNamedEnum(ExperimentStatus_descriptor(), name, + value); +} +using ::google::protobuf::internal::generated_enum::AbslParseFlag; +using ::google::protobuf::internal::generated_enum::AbslUnparseFlag; + +// =================================================================== + + +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED TCPSettings final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment.v1.TCPSettings) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline TCPSettings() : TCPSettings(nullptr) {} + ~TCPSettings() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(TCPSettings* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(TCPSettings)); + } +#endif + + template + explicit constexpr TCPSettings(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline TCPSettings(const TCPSettings& from) : TCPSettings(nullptr, from) {} + inline TCPSettings(TCPSettings&& from) noexcept : TCPSettings(nullptr, ::std::move(from)) {} + inline TCPSettings& operator=(const TCPSettings& from) { + CopyFrom(from); + return *this; + } + inline TCPSettings& operator=(TCPSettings&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const TCPSettings& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&TCPSettings_globals_); + } + static constexpr int kIndexInFileMessages = 0; + friend void swap(TCPSettings& a, TCPSettings& b) { a.Swap(&b); } + inline void Swap(TCPSettings* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(TCPSettings* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] TCPSettings* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const TCPSettings& from); + using Super_::MergeFrom; + void MergeFrom(const TCPSettings& from) { TCPSettings::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(TCPSettings* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment.v1.TCPSettings"; } + + explicit TCPSettings(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + TCPSettings(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const TCPSettings& from); + TCPSettings( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, TCPSettings&& from) noexcept + : TCPSettings(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kCongestionControlFieldNumber = 1, + kRtoMinMsFieldNumber = 3, + kInitialCongestionWindowFieldNumber = 4, + kInitialReceiveWindowFieldNumber = 5, + kCongestionLockFieldNumber = 2, + kQuickAckFieldNumber = 6, + kEcnFieldNumber = 7, + }; + // optional string congestion_control = 1 [json_name = "congestionControl", (.buf.validate.field) = { + [[nodiscard]] bool has_congestion_control() const; + void clear_congestion_control() ; + [[nodiscard]] const ::std::string& congestion_control() const; + template + void set_congestion_control(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_congestion_control(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_congestion_control(); + void set_allocated_congestion_control(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_congestion_control() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_congestion_control(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_congestion_control(); + + public: + // optional uint32 rto_min_ms = 3 [json_name = "rtoMinMs", (.buf.validate.field) = { + [[nodiscard]] bool has_rto_min_ms() const; + void clear_rto_min_ms() ; + [[nodiscard]] ::uint32_t rto_min_ms() const; + void set_rto_min_ms(::uint32_t value); + + private: + ::uint32_t _internal_rto_min_ms() const; + void _internal_set_rto_min_ms(::uint32_t value); + + public: + // optional uint32 initial_congestion_window = 4 [json_name = "initialCongestionWindow", (.buf.validate.field) = { + [[nodiscard]] bool has_initial_congestion_window() const; + void clear_initial_congestion_window() ; + [[nodiscard]] ::uint32_t initial_congestion_window() const; + void set_initial_congestion_window(::uint32_t value); + + private: + ::uint32_t _internal_initial_congestion_window() const; + void _internal_set_initial_congestion_window(::uint32_t value); + + public: + // optional uint32 initial_receive_window = 5 [json_name = "initialReceiveWindow", (.buf.validate.field) = { + [[nodiscard]] bool has_initial_receive_window() const; + void clear_initial_receive_window() ; + [[nodiscard]] ::uint32_t initial_receive_window() const; + void set_initial_receive_window(::uint32_t value); + + private: + ::uint32_t _internal_initial_receive_window() const; + void _internal_set_initial_receive_window(::uint32_t value); + + public: + // bool congestion_lock = 2 [json_name = "congestionLock"]; + void clear_congestion_lock() ; + [[nodiscard]] bool congestion_lock() const; + void set_congestion_lock(bool value); + + private: + bool _internal_congestion_lock() const; + void _internal_set_congestion_lock(bool value); + + public: + // optional bool quick_ack = 6 [json_name = "quickAck"]; + [[nodiscard]] bool has_quick_ack() const; + void clear_quick_ack() ; + [[nodiscard]] bool quick_ack() const; + void set_quick_ack(bool value); + + private: + bool _internal_quick_ack() const; + void _internal_set_quick_ack(bool value); + + public: + // optional bool ecn = 7 [json_name = "ecn"]; + [[nodiscard]] bool has_ecn() const; + void clear_ecn() ; + [[nodiscard]] bool ecn() const; + void set_ecn(bool value); + + private: + bool _internal_ecn() const; + void _internal_set_ecn(bool value); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment.v1.TCPSettings) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<3, 7, + 0, 56, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const TCPSettings& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::google::protobuf::internal::ArenaStringPtr congestion_control_; + ::uint32_t rto_min_ms_; + ::uint32_t initial_congestion_window_; + ::uint32_t initial_receive_window_; + bool congestion_lock_; + bool quick_ack_; + bool ecn_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_2fv1_2ftreatment_2eproto; +}; +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED HostEvidence final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment.v1.HostEvidence) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline HostEvidence() : HostEvidence(nullptr) {} + ~HostEvidence() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(HostEvidence* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(HostEvidence)); + } +#endif + + template + explicit constexpr HostEvidence(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline HostEvidence(const HostEvidence& from) : HostEvidence(nullptr, from) {} + inline HostEvidence(HostEvidence&& from) noexcept : HostEvidence(nullptr, ::std::move(from)) {} + inline HostEvidence& operator=(const HostEvidence& from) { + CopyFrom(from); + return *this; + } + inline HostEvidence& operator=(HostEvidence&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const HostEvidence& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&HostEvidence_globals_); + } + static constexpr int kIndexInFileMessages = 7; + friend void swap(HostEvidence& a, HostEvidence& b) { a.Swap(&b); } + inline void Swap(HostEvidence* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(HostEvidence* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] HostEvidence* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const HostEvidence& from); + using Super_::MergeFrom; + void MergeFrom(const HostEvidence& from) { HostEvidence::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(HostEvidence* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment.v1.HostEvidence"; } + + explicit HostEvidence(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + HostEvidence(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const HostEvidence& from); + HostEvidence( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, HostEvidence&& from) noexcept + : HostEvidence(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kAgentIdFieldNumber = 1, + kSocketsFieldNumber = 2, + kBytesAckedFieldNumber = 3, + kGoodputBpsFieldNumber = 4, + kRetransmissionRatioFieldNumber = 5, + kRttP95MsFieldNumber = 6, + kExposedSocketsFieldNumber = 7, + kCollectionHealthyFieldNumber = 8, + }; + // string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { + void clear_agent_id() ; + [[nodiscard]] const ::std::string& agent_id() const; + template + void set_agent_id(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_agent_id(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_agent_id(); + void set_allocated_agent_id(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_agent_id() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_agent_id(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_agent_id(); + + public: + // uint64 sockets = 2 [json_name = "sockets"]; + void clear_sockets() ; + [[nodiscard]] ::uint64_t sockets() const; + void set_sockets(::uint64_t value); + + private: + ::uint64_t _internal_sockets() const; + void _internal_set_sockets(::uint64_t value); + + public: + // uint64 bytes_acked = 3 [json_name = "bytesAcked"]; + void clear_bytes_acked() ; + [[nodiscard]] ::uint64_t bytes_acked() const; + void set_bytes_acked(::uint64_t value); + + private: + ::uint64_t _internal_bytes_acked() const; + void _internal_set_bytes_acked(::uint64_t value); + + public: + // double goodput_bps = 4 [json_name = "goodputBps", (.buf.validate.field) = { + void clear_goodput_bps() ; + [[nodiscard]] double goodput_bps() const; + void set_goodput_bps(double value); + + private: + double _internal_goodput_bps() const; + void _internal_set_goodput_bps(double value); + + public: + // double retransmission_ratio = 5 [json_name = "retransmissionRatio", (.buf.validate.field) = { + void clear_retransmission_ratio() ; + [[nodiscard]] double retransmission_ratio() const; + void set_retransmission_ratio(double value); + + private: + double _internal_retransmission_ratio() const; + void _internal_set_retransmission_ratio(double value); + + public: + // double rtt_p95_ms = 6 [json_name = "rttP95Ms", (.buf.validate.field) = { + void clear_rtt_p95_ms() ; + [[nodiscard]] double rtt_p95_ms() const; + void set_rtt_p95_ms(double value); + + private: + double _internal_rtt_p95_ms() const; + void _internal_set_rtt_p95_ms(double value); + + public: + // uint64 exposed_sockets = 7 [json_name = "exposedSockets"]; + void clear_exposed_sockets() ; + [[nodiscard]] ::uint64_t exposed_sockets() const; + void set_exposed_sockets(::uint64_t value); + + private: + ::uint64_t _internal_exposed_sockets() const; + void _internal_set_exposed_sockets(::uint64_t value); + + public: + // bool collection_healthy = 8 [json_name = "collectionHealthy"]; + void clear_collection_healthy() ; + [[nodiscard]] bool collection_healthy() const; + void set_collection_healthy(bool value); + + private: + bool _internal_collection_healthy() const; + void _internal_set_collection_healthy(bool value); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment.v1.HostEvidence) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<3, 8, + 0, 55, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const HostEvidence& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::google::protobuf::internal::ArenaStringPtr agent_id_; + ::uint64_t sockets_; + ::uint64_t bytes_acked_; + double goodput_bps_; + double retransmission_ratio_; + double rtt_p95_ms_; + ::uint64_t exposed_sockets_; + bool collection_healthy_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_2fv1_2ftreatment_2eproto; +}; +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Capabilities final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment.v1.Capabilities) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline Capabilities() : Capabilities(nullptr) {} + ~Capabilities() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(Capabilities* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(Capabilities)); + } +#endif + + template + explicit constexpr Capabilities(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline Capabilities(const Capabilities& from) : Capabilities(nullptr, from) {} + inline Capabilities(Capabilities&& from) noexcept : Capabilities(nullptr, ::std::move(from)) {} + inline Capabilities& operator=(const Capabilities& from) { + CopyFrom(from); + return *this; + } + inline Capabilities& operator=(Capabilities&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const Capabilities& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&Capabilities_globals_); + } + static constexpr int kIndexInFileMessages = 4; + friend void swap(Capabilities& a, Capabilities& b) { a.Swap(&b); } + inline void Swap(Capabilities* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(Capabilities* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] Capabilities* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const Capabilities& from); + using Super_::MergeFrom; + void MergeFrom(const Capabilities& from) { Capabilities::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(Capabilities* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment.v1.Capabilities"; } + + explicit Capabilities(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + Capabilities(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Capabilities& from); + Capabilities( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, Capabilities&& from) noexcept + : Capabilities(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kCongestionAlgorithmsFieldNumber = 3, + kBootIdFieldNumber = 1, + kKernelReleaseFieldNumber = 2, + kSimpleHostRoutingFieldNumber = 4, + kActuatorEnabledFieldNumber = 5, + }; + // repeated string congestion_algorithms = 3 [json_name = "congestionAlgorithms", (.buf.validate.field) = { + [[nodiscard]] int congestion_algorithms_size() const; + private: + int _internal_congestion_algorithms_size() const; + + public: + void clear_congestion_algorithms() ; + [[nodiscard]] const ::std::string& congestion_algorithms(int index) const; + ::std::string* PROTOBUF_NONNULL mutable_congestion_algorithms(int index); + template + void set_congestion_algorithms(int index, Arg_&& value, Args_... args); + ::std::string* PROTOBUF_NONNULL add_congestion_algorithms(); + template + void add_congestion_algorithms(Arg_&& value, Args_... args); + [[nodiscard]] const + ::google::protobuf::RepeatedPtrField<::std::string>& + congestion_algorithms() const; + [[nodiscard]] ::google::protobuf::RepeatedPtrField<::std::string>* + PROTOBUF_NONNULL + mutable_congestion_algorithms(); + + private: + const ::google::protobuf::RepeatedPtrField<::std::string>& _internal_congestion_algorithms() const; + ::google::protobuf::RepeatedPtrField<::std::string>* PROTOBUF_NONNULL _internal_mutable_congestion_algorithms(); + + public: + // string boot_id = 1 [json_name = "bootId", (.buf.validate.field) = { + void clear_boot_id() ; + [[nodiscard]] const ::std::string& boot_id() const; + template + void set_boot_id(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_boot_id(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_boot_id(); + void set_allocated_boot_id(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_boot_id() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_boot_id(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_boot_id(); + + public: + // string kernel_release = 2 [json_name = "kernelRelease", (.buf.validate.field) = { + void clear_kernel_release() ; + [[nodiscard]] const ::std::string& kernel_release() const; + template + void set_kernel_release(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_kernel_release(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_kernel_release(); + void set_allocated_kernel_release(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_kernel_release() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_kernel_release(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_kernel_release(); + + public: + // bool simple_host_routing = 4 [json_name = "simpleHostRouting"]; + void clear_simple_host_routing() ; + [[nodiscard]] bool simple_host_routing() const; + void set_simple_host_routing(bool value); + + private: + bool _internal_simple_host_routing() const; + void _internal_set_simple_host_routing(bool value); + + public: + // bool actuator_enabled = 5 [json_name = "actuatorEnabled"]; + void clear_actuator_enabled() ; + [[nodiscard]] bool actuator_enabled() const; + void set_actuator_enabled(bool value); + + private: + bool _internal_actuator_enabled() const; + void _internal_set_actuator_enabled(bool value); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment.v1.Capabilities) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<3, 5, + 0, 81, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const Capabilities& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::google::protobuf::RepeatedPtrField<::std::string> congestion_algorithms_; + ::google::protobuf::internal::ArenaStringPtr boot_id_; + ::google::protobuf::internal::ArenaStringPtr kernel_release_; + bool simple_host_routing_; + bool actuator_enabled_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_2fv1_2ftreatment_2eproto; +}; +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Treatment final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment.v1.Treatment) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline Treatment() : Treatment(nullptr) {} + ~Treatment() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(Treatment* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(Treatment)); + } +#endif + + template + explicit constexpr Treatment(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline Treatment(const Treatment& from) : Treatment(nullptr, from) {} + inline Treatment(Treatment&& from) noexcept : Treatment(nullptr, ::std::move(from)) {} + inline Treatment& operator=(const Treatment& from) { + CopyFrom(from); + return *this; + } + inline Treatment& operator=(Treatment&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const Treatment& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&Treatment_globals_); + } + static constexpr int kIndexInFileMessages = 1; + friend void swap(Treatment& a, Treatment& b) { a.Swap(&b); } + inline void Swap(Treatment* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(Treatment* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] Treatment* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const Treatment& from); + using Super_::MergeFrom; + void MergeFrom(const Treatment& from) { Treatment::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(Treatment* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment.v1.Treatment"; } + + explicit Treatment(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + Treatment(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Treatment& from); + Treatment( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, Treatment&& from) noexcept + : Treatment(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kIdFieldNumber = 1, + kDestinationFieldNumber = 3, + kSettingsFieldNumber = 4, + kRevisionFieldNumber = 2, + kEcnFabricVerifiedFieldNumber = 5, + }; + // string id = 1 [json_name = "id", (.buf.validate.field) = { + void clear_id() ; + [[nodiscard]] const ::std::string& id() const; + template + void set_id(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_id(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_id(); + void set_allocated_id(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_id() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_id(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_id(); + + public: + // string destination = 3 [json_name = "destination", (.buf.validate.field) = { + void clear_destination() ; + [[nodiscard]] const ::std::string& destination() const; + template + void set_destination(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_destination(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_destination(); + void set_allocated_destination(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_destination() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_destination(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_destination(); + + public: + // .xtcp_treatment.v1.TCPSettings settings = 4 [json_name = "settings", (.buf.validate.field) = { + [[nodiscard]] bool has_settings() const; + void clear_settings() ; + [[nodiscard]] const ::xtcp_treatment::v1::TCPSettings& settings() const; + [[nodiscard]] ::xtcp_treatment::v1::TCPSettings* PROTOBUF_NULLABLE release_settings(); + ::xtcp_treatment::v1::TCPSettings* PROTOBUF_NONNULL mutable_settings(); + void set_allocated_settings(::xtcp_treatment::v1::TCPSettings* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_settings(::xtcp_treatment::v1::TCPSettings* PROTOBUF_NULLABLE value); + ::xtcp_treatment::v1::TCPSettings* PROTOBUF_NULLABLE unsafe_arena_release_settings(); + + private: + const ::xtcp_treatment::v1::TCPSettings& _internal_settings() const; + ::xtcp_treatment::v1::TCPSettings* PROTOBUF_NONNULL _internal_mutable_settings(); + + public: + // uint64 revision = 2 [json_name = "revision", (.buf.validate.field) = { + void clear_revision() ; + [[nodiscard]] ::uint64_t revision() const; + void set_revision(::uint64_t value); + + private: + ::uint64_t _internal_revision() const; + void _internal_set_revision(::uint64_t value); + + public: + // bool ecn_fabric_verified = 5 [json_name = "ecnFabricVerified"]; + void clear_ecn_fabric_verified() ; + [[nodiscard]] bool ecn_fabric_verified() const; + void set_ecn_fabric_verified(bool value); + + private: + bool _internal_ecn_fabric_verified() const; + void _internal_set_ecn_fabric_verified(bool value); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment.v1.Treatment) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<3, 5, + 1, 49, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const Treatment& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::google::protobuf::internal::ArenaStringPtr id_; + ::google::protobuf::internal::ArenaStringPtr destination_; + ::xtcp_treatment::v1::TCPSettings* PROTOBUF_NULLABLE settings_; + ::uint64_t revision_; + bool ecn_fabric_verified_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_2fv1_2ftreatment_2eproto; +}; +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED ExperimentPolicy final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment.v1.ExperimentPolicy) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline ExperimentPolicy() : ExperimentPolicy(nullptr) {} + ~ExperimentPolicy() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(ExperimentPolicy* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(ExperimentPolicy)); + } +#endif + + template + explicit constexpr ExperimentPolicy(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline ExperimentPolicy(const ExperimentPolicy& from) : ExperimentPolicy(nullptr, from) {} + inline ExperimentPolicy(ExperimentPolicy&& from) noexcept : ExperimentPolicy(nullptr, ::std::move(from)) {} + inline ExperimentPolicy& operator=(const ExperimentPolicy& from) { + CopyFrom(from); + return *this; + } + inline ExperimentPolicy& operator=(ExperimentPolicy&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const ExperimentPolicy& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&ExperimentPolicy_globals_); + } + static constexpr int kIndexInFileMessages = 9; + friend void swap(ExperimentPolicy& a, ExperimentPolicy& b) { a.Swap(&b); } + inline void Swap(ExperimentPolicy* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ExperimentPolicy* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] ExperimentPolicy* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const ExperimentPolicy& from); + using Super_::MergeFrom; + void MergeFrom(const ExperimentPolicy& from) { ExperimentPolicy::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(ExperimentPolicy* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment.v1.ExperimentPolicy"; } + + explicit ExperimentPolicy(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + ExperimentPolicy(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const ExperimentPolicy& from); + ExperimentPolicy( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, ExperimentPolicy&& from) noexcept + : ExperimentPolicy(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kMinimumObservationFieldNumber = 1, + kMaximumPhaseFieldNumber = 2, + kMaximumEvidenceAgeFieldNumber = 9, + kMinimumSocketsPerHostFieldNumber = 3, + kMinimumBytesPerHostFieldNumber = 4, + kMinimumImprovementFieldNumber = 6, + kMaximumRetransmissionIncreaseFieldNumber = 7, + kMaximumRttIncreaseFieldNumber = 8, + kMinimumTreatedHostsFieldNumber = 5, + }; + // .google.protobuf.Duration minimum_observation = 1 [json_name = "minimumObservation", (.buf.validate.field) = { + [[nodiscard]] bool has_minimum_observation() const; + void clear_minimum_observation() ; + [[nodiscard]] const ::google::protobuf::Duration& minimum_observation() const; + [[nodiscard]] ::google::protobuf::Duration* PROTOBUF_NULLABLE release_minimum_observation(); + ::google::protobuf::Duration* PROTOBUF_NONNULL mutable_minimum_observation(); + void set_allocated_minimum_observation(::google::protobuf::Duration* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_minimum_observation(::google::protobuf::Duration* PROTOBUF_NULLABLE value); + ::google::protobuf::Duration* PROTOBUF_NULLABLE unsafe_arena_release_minimum_observation(); + + private: + const ::google::protobuf::Duration& _internal_minimum_observation() const; + ::google::protobuf::Duration* PROTOBUF_NONNULL _internal_mutable_minimum_observation(); + + public: + // .google.protobuf.Duration maximum_phase = 2 [json_name = "maximumPhase", (.buf.validate.field) = { + [[nodiscard]] bool has_maximum_phase() const; + void clear_maximum_phase() ; + [[nodiscard]] const ::google::protobuf::Duration& maximum_phase() const; + [[nodiscard]] ::google::protobuf::Duration* PROTOBUF_NULLABLE release_maximum_phase(); + ::google::protobuf::Duration* PROTOBUF_NONNULL mutable_maximum_phase(); + void set_allocated_maximum_phase(::google::protobuf::Duration* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_maximum_phase(::google::protobuf::Duration* PROTOBUF_NULLABLE value); + ::google::protobuf::Duration* PROTOBUF_NULLABLE unsafe_arena_release_maximum_phase(); + + private: + const ::google::protobuf::Duration& _internal_maximum_phase() const; + ::google::protobuf::Duration* PROTOBUF_NONNULL _internal_mutable_maximum_phase(); + + public: + // .google.protobuf.Duration maximum_evidence_age = 9 [json_name = "maximumEvidenceAge", (.buf.validate.field) = { + [[nodiscard]] bool has_maximum_evidence_age() const; + void clear_maximum_evidence_age() ; + [[nodiscard]] const ::google::protobuf::Duration& maximum_evidence_age() const; + [[nodiscard]] ::google::protobuf::Duration* PROTOBUF_NULLABLE release_maximum_evidence_age(); + ::google::protobuf::Duration* PROTOBUF_NONNULL mutable_maximum_evidence_age(); + void set_allocated_maximum_evidence_age(::google::protobuf::Duration* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_maximum_evidence_age(::google::protobuf::Duration* PROTOBUF_NULLABLE value); + ::google::protobuf::Duration* PROTOBUF_NULLABLE unsafe_arena_release_maximum_evidence_age(); + + private: + const ::google::protobuf::Duration& _internal_maximum_evidence_age() const; + ::google::protobuf::Duration* PROTOBUF_NONNULL _internal_mutable_maximum_evidence_age(); + + public: + // uint64 minimum_sockets_per_host = 3 [json_name = "minimumSocketsPerHost", (.buf.validate.field) = { + void clear_minimum_sockets_per_host() ; + [[nodiscard]] ::uint64_t minimum_sockets_per_host() const; + void set_minimum_sockets_per_host(::uint64_t value); + + private: + ::uint64_t _internal_minimum_sockets_per_host() const; + void _internal_set_minimum_sockets_per_host(::uint64_t value); + + public: + // uint64 minimum_bytes_per_host = 4 [json_name = "minimumBytesPerHost", (.buf.validate.field) = { + void clear_minimum_bytes_per_host() ; + [[nodiscard]] ::uint64_t minimum_bytes_per_host() const; + void set_minimum_bytes_per_host(::uint64_t value); + + private: + ::uint64_t _internal_minimum_bytes_per_host() const; + void _internal_set_minimum_bytes_per_host(::uint64_t value); + + public: + // double minimum_improvement = 6 [json_name = "minimumImprovement", (.buf.validate.field) = { + void clear_minimum_improvement() ; + [[nodiscard]] double minimum_improvement() const; + void set_minimum_improvement(double value); + + private: + double _internal_minimum_improvement() const; + void _internal_set_minimum_improvement(double value); + + public: + // double maximum_retransmission_increase = 7 [json_name = "maximumRetransmissionIncrease", (.buf.validate.field) = { + void clear_maximum_retransmission_increase() ; + [[nodiscard]] double maximum_retransmission_increase() const; + void set_maximum_retransmission_increase(double value); + + private: + double _internal_maximum_retransmission_increase() const; + void _internal_set_maximum_retransmission_increase(double value); + + public: + // double maximum_rtt_increase = 8 [json_name = "maximumRttIncrease", (.buf.validate.field) = { + void clear_maximum_rtt_increase() ; + [[nodiscard]] double maximum_rtt_increase() const; + void set_maximum_rtt_increase(double value); + + private: + double _internal_maximum_rtt_increase() const; + void _internal_set_maximum_rtt_increase(double value); + + public: + // uint32 minimum_treated_hosts = 5 [json_name = "minimumTreatedHosts", (.buf.validate.field) = { + void clear_minimum_treated_hosts() ; + [[nodiscard]] ::uint32_t minimum_treated_hosts() const; + void set_minimum_treated_hosts(::uint32_t value); + + private: + ::uint32_t _internal_minimum_treated_hosts() const; + void _internal_set_minimum_treated_hosts(::uint32_t value); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment.v1.ExperimentPolicy) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<4, 9, + 3, 0, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const ExperimentPolicy& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::google::protobuf::Duration* PROTOBUF_NULLABLE minimum_observation_; + ::google::protobuf::Duration* PROTOBUF_NULLABLE maximum_phase_; + ::google::protobuf::Duration* PROTOBUF_NULLABLE maximum_evidence_age_; + ::uint64_t minimum_sockets_per_host_; + ::uint64_t minimum_bytes_per_host_; + double minimum_improvement_; + double maximum_retransmission_increase_; + double maximum_rtt_increase_; + ::uint32_t minimum_treated_hosts_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_2fv1_2ftreatment_2eproto; +}; +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Evidence final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment.v1.Evidence) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline Evidence() : Evidence(nullptr) {} + ~Evidence() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(Evidence* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(Evidence)); + } +#endif + + template + explicit constexpr Evidence(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline Evidence(const Evidence& from) : Evidence(nullptr, from) {} + inline Evidence(Evidence&& from) noexcept : Evidence(nullptr, ::std::move(from)) {} + inline Evidence& operator=(const Evidence& from) { + CopyFrom(from); + return *this; + } + inline Evidence& operator=(Evidence&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const Evidence& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&Evidence_globals_); + } + static constexpr int kIndexInFileMessages = 8; + friend void swap(Evidence& a, Evidence& b) { a.Swap(&b); } + inline void Swap(Evidence* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(Evidence* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] Evidence* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const Evidence& from); + using Super_::MergeFrom; + void MergeFrom(const Evidence& from) { Evidence::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(Evidence* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment.v1.Evidence"; } + + explicit Evidence(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + Evidence(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Evidence& from); + Evidence( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, Evidence&& from) noexcept + : Evidence(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kHostsFieldNumber = 7, + kIdFieldNumber = 1, + kQueryVersionFieldNumber = 5, + kEnrichmentVersionFieldNumber = 6, + kArtifactSha256FieldNumber = 8, + kAnalyzedAtFieldNumber = 2, + kWindowStartFieldNumber = 3, + kWindowEndFieldNumber = 4, + }; + // repeated .xtcp_treatment.v1.HostEvidence hosts = 7 [json_name = "hosts", (.buf.validate.field) = { + [[nodiscard]] int hosts_size() const; + private: + int _internal_hosts_size() const; + + public: + void clear_hosts() ; + [[nodiscard]] const ::xtcp_treatment::v1::HostEvidence& hosts(int index) const; + [[nodiscard]] ::xtcp_treatment::v1::HostEvidence* PROTOBUF_NONNULL mutable_hosts(int index); + ::xtcp_treatment::v1::HostEvidence* PROTOBUF_NONNULL add_hosts(); + [[nodiscard]] const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::HostEvidence>& + hosts() const; + [[nodiscard]] ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::HostEvidence>* PROTOBUF_NONNULL + mutable_hosts(); + + private: + const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::HostEvidence>& _internal_hosts() const; + ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::HostEvidence>* PROTOBUF_NONNULL _internal_mutable_hosts(); + + public: + // string id = 1 [json_name = "id", (.buf.validate.field) = { + void clear_id() ; + [[nodiscard]] const ::std::string& id() const; + template + void set_id(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_id(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_id(); + void set_allocated_id(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_id() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_id(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_id(); + + public: + // string query_version = 5 [json_name = "queryVersion", (.buf.validate.field) = { + void clear_query_version() ; + [[nodiscard]] const ::std::string& query_version() const; + template + void set_query_version(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_query_version(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_query_version(); + void set_allocated_query_version(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_query_version() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_query_version(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_query_version(); + + public: + // string enrichment_version = 6 [json_name = "enrichmentVersion", (.buf.validate.field) = { + void clear_enrichment_version() ; + [[nodiscard]] const ::std::string& enrichment_version() const; + template + void set_enrichment_version(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_enrichment_version(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_enrichment_version(); + void set_allocated_enrichment_version(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_enrichment_version() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_enrichment_version(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_enrichment_version(); + + public: + // string artifact_sha256 = 8 [json_name = "artifactSha256", (.buf.validate.field) = { + void clear_artifact_sha256() ; + [[nodiscard]] const ::std::string& artifact_sha256() const; + template + void set_artifact_sha256(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_artifact_sha256(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_artifact_sha256(); + void set_allocated_artifact_sha256(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_artifact_sha256() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_artifact_sha256(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_artifact_sha256(); + + public: + // .google.protobuf.Timestamp analyzed_at = 2 [json_name = "analyzedAt", (.buf.validate.field) = { + [[nodiscard]] bool has_analyzed_at() const; + void clear_analyzed_at() ; + [[nodiscard]] const ::google::protobuf::Timestamp& analyzed_at() const; + [[nodiscard]] ::google::protobuf::Timestamp* PROTOBUF_NULLABLE release_analyzed_at(); + ::google::protobuf::Timestamp* PROTOBUF_NONNULL mutable_analyzed_at(); + void set_allocated_analyzed_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_analyzed_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE unsafe_arena_release_analyzed_at(); + + private: + const ::google::protobuf::Timestamp& _internal_analyzed_at() const; + ::google::protobuf::Timestamp* PROTOBUF_NONNULL _internal_mutable_analyzed_at(); + + public: + // .google.protobuf.Timestamp window_start = 3 [json_name = "windowStart", (.buf.validate.field) = { + [[nodiscard]] bool has_window_start() const; + void clear_window_start() ; + [[nodiscard]] const ::google::protobuf::Timestamp& window_start() const; + [[nodiscard]] ::google::protobuf::Timestamp* PROTOBUF_NULLABLE release_window_start(); + ::google::protobuf::Timestamp* PROTOBUF_NONNULL mutable_window_start(); + void set_allocated_window_start(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_window_start(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE unsafe_arena_release_window_start(); + + private: + const ::google::protobuf::Timestamp& _internal_window_start() const; + ::google::protobuf::Timestamp* PROTOBUF_NONNULL _internal_mutable_window_start(); + + public: + // .google.protobuf.Timestamp window_end = 4 [json_name = "windowEnd", (.buf.validate.field) = { + [[nodiscard]] bool has_window_end() const; + void clear_window_end() ; + [[nodiscard]] const ::google::protobuf::Timestamp& window_end() const; + [[nodiscard]] ::google::protobuf::Timestamp* PROTOBUF_NULLABLE release_window_end(); + ::google::protobuf::Timestamp* PROTOBUF_NONNULL mutable_window_end(); + void set_allocated_window_end(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_window_end(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE unsafe_arena_release_window_end(); + + private: + const ::google::protobuf::Timestamp& _internal_window_end() const; + ::google::protobuf::Timestamp* PROTOBUF_NONNULL _internal_mutable_window_end(); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment.v1.Evidence) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<3, 8, + 4, 91, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const Evidence& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::google::protobuf::RepeatedPtrField< ::xtcp_treatment::v1::HostEvidence > hosts_; + ::google::protobuf::internal::ArenaStringPtr id_; + ::google::protobuf::internal::ArenaStringPtr query_version_; + ::google::protobuf::internal::ArenaStringPtr enrichment_version_; + ::google::protobuf::internal::ArenaStringPtr artifact_sha256_; + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE analyzed_at_; + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE window_start_; + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE window_end_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_2fv1_2ftreatment_2eproto; +}; +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Phase final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment.v1.Phase) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline Phase() : Phase(nullptr) {} + ~Phase() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(Phase* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(Phase)); + } +#endif + + template + explicit constexpr Phase(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline Phase(const Phase& from) : Phase(nullptr, from) {} + inline Phase(Phase&& from) noexcept : Phase(nullptr, ::std::move(from)) {} + inline Phase& operator=(const Phase& from) { + CopyFrom(from); + return *this; + } + inline Phase& operator=(Phase&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const Phase& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&Phase_globals_); + } + static constexpr int kIndexInFileMessages = 10; + friend void swap(Phase& a, Phase& b) { a.Swap(&b); } + inline void Swap(Phase* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(Phase* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] Phase* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const Phase& from); + using Super_::MergeFrom; + void MergeFrom(const Phase& from) { Phase::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(Phase* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment.v1.Phase"; } + + explicit Phase(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + Phase(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Phase& from); + Phase( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, Phase&& from) noexcept + : Phase(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kOutcomeFieldNumber = 6, + kReviewerFieldNumber = 7, + kReasonFieldNumber = 8, + kIssuedAtFieldNumber = 2, + kObservationStartedAtFieldNumber = 3, + kEvaluatedAtFieldNumber = 4, + kEvidenceFieldNumber = 5, + kTargetHostsFieldNumber = 1, + }; + // string outcome = 6 [json_name = "outcome", (.buf.validate.field) = { + void clear_outcome() ; + [[nodiscard]] const ::std::string& outcome() const; + template + void set_outcome(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_outcome(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_outcome(); + void set_allocated_outcome(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_outcome() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_outcome(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_outcome(); + + public: + // string reviewer = 7 [json_name = "reviewer", (.buf.validate.field) = { + void clear_reviewer() ; + [[nodiscard]] const ::std::string& reviewer() const; + template + void set_reviewer(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_reviewer(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_reviewer(); + void set_allocated_reviewer(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_reviewer() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_reviewer(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_reviewer(); + + public: + // string reason = 8 [json_name = "reason", (.buf.validate.field) = { + void clear_reason() ; + [[nodiscard]] const ::std::string& reason() const; + template + void set_reason(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_reason(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_reason(); + void set_allocated_reason(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_reason() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_reason(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_reason(); + + public: + // .google.protobuf.Timestamp issued_at = 2 [json_name = "issuedAt"]; + [[nodiscard]] bool has_issued_at() const; + void clear_issued_at() ; + [[nodiscard]] const ::google::protobuf::Timestamp& issued_at() const; + [[nodiscard]] ::google::protobuf::Timestamp* PROTOBUF_NULLABLE release_issued_at(); + ::google::protobuf::Timestamp* PROTOBUF_NONNULL mutable_issued_at(); + void set_allocated_issued_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_issued_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE unsafe_arena_release_issued_at(); + + private: + const ::google::protobuf::Timestamp& _internal_issued_at() const; + ::google::protobuf::Timestamp* PROTOBUF_NONNULL _internal_mutable_issued_at(); + + public: + // .google.protobuf.Timestamp observation_started_at = 3 [json_name = "observationStartedAt"]; + [[nodiscard]] bool has_observation_started_at() const; + void clear_observation_started_at() ; + [[nodiscard]] const ::google::protobuf::Timestamp& observation_started_at() const; + [[nodiscard]] ::google::protobuf::Timestamp* PROTOBUF_NULLABLE release_observation_started_at(); + ::google::protobuf::Timestamp* PROTOBUF_NONNULL mutable_observation_started_at(); + void set_allocated_observation_started_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_observation_started_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE unsafe_arena_release_observation_started_at(); + + private: + const ::google::protobuf::Timestamp& _internal_observation_started_at() const; + ::google::protobuf::Timestamp* PROTOBUF_NONNULL _internal_mutable_observation_started_at(); + + public: + // .google.protobuf.Timestamp evaluated_at = 4 [json_name = "evaluatedAt"]; + [[nodiscard]] bool has_evaluated_at() const; + void clear_evaluated_at() ; + [[nodiscard]] const ::google::protobuf::Timestamp& evaluated_at() const; + [[nodiscard]] ::google::protobuf::Timestamp* PROTOBUF_NULLABLE release_evaluated_at(); + ::google::protobuf::Timestamp* PROTOBUF_NONNULL mutable_evaluated_at(); + void set_allocated_evaluated_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_evaluated_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE unsafe_arena_release_evaluated_at(); + + private: + const ::google::protobuf::Timestamp& _internal_evaluated_at() const; + ::google::protobuf::Timestamp* PROTOBUF_NONNULL _internal_mutable_evaluated_at(); + + public: + // .xtcp_treatment.v1.Evidence evidence = 5 [json_name = "evidence"]; + [[nodiscard]] bool has_evidence() const; + void clear_evidence() ; + [[nodiscard]] const ::xtcp_treatment::v1::Evidence& evidence() const; + [[nodiscard]] ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE release_evidence(); + ::xtcp_treatment::v1::Evidence* PROTOBUF_NONNULL mutable_evidence(); + void set_allocated_evidence(::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_evidence(::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE value); + ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE unsafe_arena_release_evidence(); + + private: + const ::xtcp_treatment::v1::Evidence& _internal_evidence() const; + ::xtcp_treatment::v1::Evidence* PROTOBUF_NONNULL _internal_mutable_evidence(); + + public: + // uint32 target_hosts = 1 [json_name = "targetHosts", (.buf.validate.field) = { + void clear_target_hosts() ; + [[nodiscard]] ::uint32_t target_hosts() const; + void set_target_hosts(::uint32_t value); + + private: + ::uint32_t _internal_target_hosts() const; + void _internal_set_target_hosts(::uint32_t value); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment.v1.Phase) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<3, 8, + 4, 61, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const Phase& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::google::protobuf::internal::ArenaStringPtr outcome_; + ::google::protobuf::internal::ArenaStringPtr reviewer_; + ::google::protobuf::internal::ArenaStringPtr reason_; + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE issued_at_; + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE observation_started_at_; + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE evaluated_at_; + ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE evidence_; + ::uint32_t target_hosts_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_2fv1_2ftreatment_2eproto; +}; +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Assignment final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment.v1.Assignment) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline Assignment() : Assignment(nullptr) {} + ~Assignment() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(Assignment* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(Assignment)); + } +#endif + + template + explicit constexpr Assignment(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline Assignment(const Assignment& from) : Assignment(nullptr, from) {} + inline Assignment(Assignment&& from) noexcept : Assignment(nullptr, ::std::move(from)) {} + inline Assignment& operator=(const Assignment& from) { + CopyFrom(from); + return *this; + } + inline Assignment& operator=(Assignment&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const Assignment& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&Assignment_globals_); + } + static constexpr int kIndexInFileMessages = 2; + friend void swap(Assignment& a, Assignment& b) { a.Swap(&b); } + inline void Swap(Assignment* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(Assignment* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] Assignment* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const Assignment& from); + using Super_::MergeFrom; + void MergeFrom(const Assignment& from) { Assignment::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(Assignment* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment.v1.Assignment"; } + + explicit Assignment(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + Assignment(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Assignment& from); + Assignment( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, Assignment&& from) noexcept + : Assignment(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kIdFieldNumber = 1, + kExperimentIdFieldNumber = 2, + kTreatmentFieldNumber = 3, + kLeaseExpiresAtFieldNumber = 5, + kPredecessorFieldNumber = 6, + kGenerationFieldNumber = 7, + kModeFieldNumber = 4, + }; + // string id = 1 [json_name = "id", (.buf.validate.field) = { + void clear_id() ; + [[nodiscard]] const ::std::string& id() const; + template + void set_id(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_id(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_id(); + void set_allocated_id(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_id() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_id(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_id(); + + public: + // string experiment_id = 2 [json_name = "experimentId", (.buf.validate.field) = { + void clear_experiment_id() ; + [[nodiscard]] const ::std::string& experiment_id() const; + template + void set_experiment_id(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_experiment_id(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_experiment_id(); + void set_allocated_experiment_id(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_experiment_id() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_experiment_id(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_experiment_id(); + + public: + // .xtcp_treatment.v1.Treatment treatment = 3 [json_name = "treatment", (.buf.validate.field) = { + [[nodiscard]] bool has_treatment() const; + void clear_treatment() ; + [[nodiscard]] const ::xtcp_treatment::v1::Treatment& treatment() const; + [[nodiscard]] ::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE release_treatment(); + ::xtcp_treatment::v1::Treatment* PROTOBUF_NONNULL mutable_treatment(); + void set_allocated_treatment(::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_treatment(::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE value); + ::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE unsafe_arena_release_treatment(); + + private: + const ::xtcp_treatment::v1::Treatment& _internal_treatment() const; + ::xtcp_treatment::v1::Treatment* PROTOBUF_NONNULL _internal_mutable_treatment(); + + public: + // .google.protobuf.Timestamp lease_expires_at = 5 [json_name = "leaseExpiresAt"]; + [[nodiscard]] bool has_lease_expires_at() const; + void clear_lease_expires_at() ; + [[nodiscard]] const ::google::protobuf::Timestamp& lease_expires_at() const; + [[nodiscard]] ::google::protobuf::Timestamp* PROTOBUF_NULLABLE release_lease_expires_at(); + ::google::protobuf::Timestamp* PROTOBUF_NONNULL mutable_lease_expires_at(); + void set_allocated_lease_expires_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_lease_expires_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE unsafe_arena_release_lease_expires_at(); + + private: + const ::google::protobuf::Timestamp& _internal_lease_expires_at() const; + ::google::protobuf::Timestamp* PROTOBUF_NONNULL _internal_mutable_lease_expires_at(); + + public: + // .xtcp_treatment.v1.Treatment predecessor = 6 [json_name = "predecessor"]; + [[nodiscard]] bool has_predecessor() const; + void clear_predecessor() ; + [[nodiscard]] const ::xtcp_treatment::v1::Treatment& predecessor() const; + [[nodiscard]] ::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE release_predecessor(); + ::xtcp_treatment::v1::Treatment* PROTOBUF_NONNULL mutable_predecessor(); + void set_allocated_predecessor(::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_predecessor(::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE value); + ::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE unsafe_arena_release_predecessor(); + + private: + const ::xtcp_treatment::v1::Treatment& _internal_predecessor() const; + ::xtcp_treatment::v1::Treatment* PROTOBUF_NONNULL _internal_mutable_predecessor(); + + public: + // uint64 generation = 7 [json_name = "generation", (.buf.validate.field) = { + void clear_generation() ; + [[nodiscard]] ::uint64_t generation() const; + void set_generation(::uint64_t value); + + private: + ::uint64_t _internal_generation() const; + void _internal_set_generation(::uint64_t value); + + public: + // .xtcp_treatment.v1.AssignmentMode mode = 4 [json_name = "mode", (.buf.validate.field) = { + void clear_mode() ; + [[nodiscard]] ::xtcp_treatment::v1::AssignmentMode mode() const; + void set_mode(::xtcp_treatment::v1::AssignmentMode value); + + private: + ::xtcp_treatment::v1::AssignmentMode _internal_mode() const; + void _internal_set_mode(::xtcp_treatment::v1::AssignmentMode value); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment.v1.Assignment) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<3, 7, + 3, 52, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const Assignment& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::google::protobuf::internal::ArenaStringPtr id_; + ::google::protobuf::internal::ArenaStringPtr experiment_id_; + ::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE treatment_; + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE lease_expires_at_; + ::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE predecessor_; + ::uint64_t generation_; + int mode_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_2fv1_2ftreatment_2eproto; +}; +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Experiment final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment.v1.Experiment) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline Experiment() : Experiment(nullptr) {} + ~Experiment() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(Experiment* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(Experiment)); + } +#endif + + template + explicit constexpr Experiment(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline Experiment(const Experiment& from) : Experiment(nullptr, from) {} + inline Experiment(Experiment&& from) noexcept : Experiment(nullptr, ::std::move(from)) {} + inline Experiment& operator=(const Experiment& from) { + CopyFrom(from); + return *this; + } + inline Experiment& operator=(Experiment&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const Experiment& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&Experiment_globals_); + } + static constexpr int kIndexInFileMessages = 11; + friend void swap(Experiment& a, Experiment& b) { a.Swap(&b); } + inline void Swap(Experiment* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(Experiment* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] Experiment* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const Experiment& from); + using Super_::MergeFrom; + void MergeFrom(const Experiment& from) { Experiment::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(Experiment* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment.v1.Experiment"; } + + explicit Experiment(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + Experiment(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Experiment& from); + Experiment( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, Experiment&& from) noexcept + : Experiment(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kCohortOrderFieldNumber = 7, + kPhasesFieldNumber = 8, + kIdFieldNumber = 1, + kLocationFieldNumber = 2, + kHypothesisFieldNumber = 3, + kLatestOutcomeFieldNumber = 13, + kTreatmentFieldNumber = 4, + kPolicyFieldNumber = 5, + kBaselineFieldNumber = 6, + kProposedAtFieldNumber = 11, + kLatestEvidenceFieldNumber = 12, + kRevisionFieldNumber = 10, + kStatusFieldNumber = 9, + }; + // repeated string cohort_order = 7 [json_name = "cohortOrder", (.buf.validate.field) = { + [[nodiscard]] int cohort_order_size() const; + private: + int _internal_cohort_order_size() const; + + public: + void clear_cohort_order() ; + [[nodiscard]] const ::std::string& cohort_order(int index) const; + ::std::string* PROTOBUF_NONNULL mutable_cohort_order(int index); + template + void set_cohort_order(int index, Arg_&& value, Args_... args); + ::std::string* PROTOBUF_NONNULL add_cohort_order(); + template + void add_cohort_order(Arg_&& value, Args_... args); + [[nodiscard]] const + ::google::protobuf::RepeatedPtrField<::std::string>& + cohort_order() const; + [[nodiscard]] ::google::protobuf::RepeatedPtrField<::std::string>* + PROTOBUF_NONNULL + mutable_cohort_order(); + + private: + const ::google::protobuf::RepeatedPtrField<::std::string>& _internal_cohort_order() const; + ::google::protobuf::RepeatedPtrField<::std::string>* PROTOBUF_NONNULL _internal_mutable_cohort_order(); + + public: + // repeated .xtcp_treatment.v1.Phase phases = 8 [json_name = "phases", (.buf.validate.field) = { + [[nodiscard]] int phases_size() const; + private: + int _internal_phases_size() const; + + public: + void clear_phases() ; + [[nodiscard]] const ::xtcp_treatment::v1::Phase& phases(int index) const; + [[nodiscard]] ::xtcp_treatment::v1::Phase* PROTOBUF_NONNULL mutable_phases(int index); + ::xtcp_treatment::v1::Phase* PROTOBUF_NONNULL add_phases(); + [[nodiscard]] const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::Phase>& + phases() const; + [[nodiscard]] ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::Phase>* PROTOBUF_NONNULL + mutable_phases(); + + private: + const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::Phase>& _internal_phases() const; + ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::Phase>* PROTOBUF_NONNULL _internal_mutable_phases(); + + public: + // string id = 1 [json_name = "id", (.buf.validate.field) = { + void clear_id() ; + [[nodiscard]] const ::std::string& id() const; + template + void set_id(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_id(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_id(); + void set_allocated_id(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_id() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_id(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_id(); + + public: + // string location = 2 [json_name = "location", (.buf.validate.field) = { + void clear_location() ; + [[nodiscard]] const ::std::string& location() const; + template + void set_location(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_location(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_location(); + void set_allocated_location(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_location() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_location(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_location(); + + public: + // string hypothesis = 3 [json_name = "hypothesis", (.buf.validate.field) = { + void clear_hypothesis() ; + [[nodiscard]] const ::std::string& hypothesis() const; + template + void set_hypothesis(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_hypothesis(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_hypothesis(); + void set_allocated_hypothesis(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_hypothesis() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_hypothesis(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_hypothesis(); + + public: + // string latest_outcome = 13 [json_name = "latestOutcome", (.buf.validate.field) = { + void clear_latest_outcome() ; + [[nodiscard]] const ::std::string& latest_outcome() const; + template + void set_latest_outcome(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_latest_outcome(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_latest_outcome(); + void set_allocated_latest_outcome(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_latest_outcome() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_latest_outcome(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_latest_outcome(); + + public: + // .xtcp_treatment.v1.Treatment treatment = 4 [json_name = "treatment", (.buf.validate.field) = { + [[nodiscard]] bool has_treatment() const; + void clear_treatment() ; + [[nodiscard]] const ::xtcp_treatment::v1::Treatment& treatment() const; + [[nodiscard]] ::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE release_treatment(); + ::xtcp_treatment::v1::Treatment* PROTOBUF_NONNULL mutable_treatment(); + void set_allocated_treatment(::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_treatment(::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE value); + ::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE unsafe_arena_release_treatment(); + + private: + const ::xtcp_treatment::v1::Treatment& _internal_treatment() const; + ::xtcp_treatment::v1::Treatment* PROTOBUF_NONNULL _internal_mutable_treatment(); + + public: + // .xtcp_treatment.v1.ExperimentPolicy policy = 5 [json_name = "policy", (.buf.validate.field) = { + [[nodiscard]] bool has_policy() const; + void clear_policy() ; + [[nodiscard]] const ::xtcp_treatment::v1::ExperimentPolicy& policy() const; + [[nodiscard]] ::xtcp_treatment::v1::ExperimentPolicy* PROTOBUF_NULLABLE release_policy(); + ::xtcp_treatment::v1::ExperimentPolicy* PROTOBUF_NONNULL mutable_policy(); + void set_allocated_policy(::xtcp_treatment::v1::ExperimentPolicy* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_policy(::xtcp_treatment::v1::ExperimentPolicy* PROTOBUF_NULLABLE value); + ::xtcp_treatment::v1::ExperimentPolicy* PROTOBUF_NULLABLE unsafe_arena_release_policy(); + + private: + const ::xtcp_treatment::v1::ExperimentPolicy& _internal_policy() const; + ::xtcp_treatment::v1::ExperimentPolicy* PROTOBUF_NONNULL _internal_mutable_policy(); + + public: + // .xtcp_treatment.v1.Evidence baseline = 6 [json_name = "baseline", (.buf.validate.field) = { + [[nodiscard]] bool has_baseline() const; + void clear_baseline() ; + [[nodiscard]] const ::xtcp_treatment::v1::Evidence& baseline() const; + [[nodiscard]] ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE release_baseline(); + ::xtcp_treatment::v1::Evidence* PROTOBUF_NONNULL mutable_baseline(); + void set_allocated_baseline(::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_baseline(::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE value); + ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE unsafe_arena_release_baseline(); + + private: + const ::xtcp_treatment::v1::Evidence& _internal_baseline() const; + ::xtcp_treatment::v1::Evidence* PROTOBUF_NONNULL _internal_mutable_baseline(); + + public: + // .google.protobuf.Timestamp proposed_at = 11 [json_name = "proposedAt"]; + [[nodiscard]] bool has_proposed_at() const; + void clear_proposed_at() ; + [[nodiscard]] const ::google::protobuf::Timestamp& proposed_at() const; + [[nodiscard]] ::google::protobuf::Timestamp* PROTOBUF_NULLABLE release_proposed_at(); + ::google::protobuf::Timestamp* PROTOBUF_NONNULL mutable_proposed_at(); + void set_allocated_proposed_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_proposed_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE unsafe_arena_release_proposed_at(); + + private: + const ::google::protobuf::Timestamp& _internal_proposed_at() const; + ::google::protobuf::Timestamp* PROTOBUF_NONNULL _internal_mutable_proposed_at(); + + public: + // .xtcp_treatment.v1.Evidence latest_evidence = 12 [json_name = "latestEvidence"]; + [[nodiscard]] bool has_latest_evidence() const; + void clear_latest_evidence() ; + [[nodiscard]] const ::xtcp_treatment::v1::Evidence& latest_evidence() const; + [[nodiscard]] ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE release_latest_evidence(); + ::xtcp_treatment::v1::Evidence* PROTOBUF_NONNULL mutable_latest_evidence(); + void set_allocated_latest_evidence(::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_latest_evidence(::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE value); + ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE unsafe_arena_release_latest_evidence(); + + private: + const ::xtcp_treatment::v1::Evidence& _internal_latest_evidence() const; + ::xtcp_treatment::v1::Evidence* PROTOBUF_NONNULL _internal_mutable_latest_evidence(); + + public: + // uint64 revision = 10 [json_name = "revision"]; + void clear_revision() ; + [[nodiscard]] ::uint64_t revision() const; + void set_revision(::uint64_t value); + + private: + ::uint64_t _internal_revision() const; + void _internal_set_revision(::uint64_t value); + + public: + // .xtcp_treatment.v1.ExperimentStatus status = 9 [json_name = "status", (.buf.validate.field) = { + void clear_status() ; + [[nodiscard]] ::xtcp_treatment::v1::ExperimentStatus status() const; + void set_status(::xtcp_treatment::v1::ExperimentStatus value); + + private: + ::xtcp_treatment::v1::ExperimentStatus _internal_status() const; + void _internal_set_status(::xtcp_treatment::v1::ExperimentStatus value); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment.v1.Experiment) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<4, 13, + 6, 91, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const Experiment& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::google::protobuf::RepeatedPtrField<::std::string> cohort_order_; + ::google::protobuf::RepeatedPtrField< ::xtcp_treatment::v1::Phase > phases_; + ::google::protobuf::internal::ArenaStringPtr id_; + ::google::protobuf::internal::ArenaStringPtr location_; + ::google::protobuf::internal::ArenaStringPtr hypothesis_; + ::google::protobuf::internal::ArenaStringPtr latest_outcome_; + ::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE treatment_; + ::xtcp_treatment::v1::ExperimentPolicy* PROTOBUF_NULLABLE policy_; + ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE baseline_; + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE proposed_at_; + ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE latest_evidence_; + ::uint64_t revision_; + int status_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_2fv1_2ftreatment_2eproto; +}; +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED DesiredState final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment.v1.DesiredState) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline DesiredState() : DesiredState(nullptr) {} + ~DesiredState() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(DesiredState* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(DesiredState)); + } +#endif + + template + explicit constexpr DesiredState(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline DesiredState(const DesiredState& from) : DesiredState(nullptr, from) {} + inline DesiredState(DesiredState&& from) noexcept : DesiredState(nullptr, ::std::move(from)) {} + inline DesiredState& operator=(const DesiredState& from) { + CopyFrom(from); + return *this; + } + inline DesiredState& operator=(DesiredState&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const DesiredState& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&DesiredState_globals_); + } + static constexpr int kIndexInFileMessages = 3; + friend void swap(DesiredState& a, DesiredState& b) { a.Swap(&b); } + inline void Swap(DesiredState* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(DesiredState* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] DesiredState* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const DesiredState& from); + using Super_::MergeFrom; + void MergeFrom(const DesiredState& from) { DesiredState::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(DesiredState* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment.v1.DesiredState"; } + + explicit DesiredState(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + DesiredState(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const DesiredState& from); + DesiredState( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, DesiredState&& from) noexcept + : DesiredState(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kAssignmentsFieldNumber = 4, + kAgentIdFieldNumber = 1, + kIssuedAtFieldNumber = 5, + kRevisionFieldNumber = 2, + kSuspendedFieldNumber = 3, + }; + // repeated .xtcp_treatment.v1.Assignment assignments = 4 [json_name = "assignments", (.buf.validate.field) = { + [[nodiscard]] int assignments_size() const; + private: + int _internal_assignments_size() const; + + public: + void clear_assignments() ; + [[nodiscard]] const ::xtcp_treatment::v1::Assignment& assignments(int index) const; + [[nodiscard]] ::xtcp_treatment::v1::Assignment* PROTOBUF_NONNULL mutable_assignments(int index); + ::xtcp_treatment::v1::Assignment* PROTOBUF_NONNULL add_assignments(); + [[nodiscard]] const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::Assignment>& + assignments() const; + [[nodiscard]] ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::Assignment>* PROTOBUF_NONNULL + mutable_assignments(); + + private: + const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::Assignment>& _internal_assignments() const; + ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::Assignment>* PROTOBUF_NONNULL _internal_mutable_assignments(); + + public: + // string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { + void clear_agent_id() ; + [[nodiscard]] const ::std::string& agent_id() const; + template + void set_agent_id(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_agent_id(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_agent_id(); + void set_allocated_agent_id(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_agent_id() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_agent_id(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_agent_id(); + + public: + // .google.protobuf.Timestamp issued_at = 5 [json_name = "issuedAt", (.buf.validate.field) = { + [[nodiscard]] bool has_issued_at() const; + void clear_issued_at() ; + [[nodiscard]] const ::google::protobuf::Timestamp& issued_at() const; + [[nodiscard]] ::google::protobuf::Timestamp* PROTOBUF_NULLABLE release_issued_at(); + ::google::protobuf::Timestamp* PROTOBUF_NONNULL mutable_issued_at(); + void set_allocated_issued_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_issued_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE unsafe_arena_release_issued_at(); + + private: + const ::google::protobuf::Timestamp& _internal_issued_at() const; + ::google::protobuf::Timestamp* PROTOBUF_NONNULL _internal_mutable_issued_at(); + + public: + // uint64 revision = 2 [json_name = "revision", (.buf.validate.field) = { + void clear_revision() ; + [[nodiscard]] ::uint64_t revision() const; + void set_revision(::uint64_t value); + + private: + ::uint64_t _internal_revision() const; + void _internal_set_revision(::uint64_t value); + + public: + // bool suspended = 3 [json_name = "suspended"]; + void clear_suspended() ; + [[nodiscard]] bool suspended() const; + void set_suspended(bool value); + + private: + bool _internal_suspended() const; + void _internal_set_suspended(bool value); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment.v1.DesiredState) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<3, 5, + 2, 47, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const DesiredState& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::google::protobuf::RepeatedPtrField< ::xtcp_treatment::v1::Assignment > assignments_; + ::google::protobuf::internal::ArenaStringPtr agent_id_; + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE issued_at_; + ::uint64_t revision_; + bool suspended_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_2fv1_2ftreatment_2eproto; +}; +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED ApplicationRecord final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment.v1.ApplicationRecord) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline ApplicationRecord() : ApplicationRecord(nullptr) {} + ~ApplicationRecord() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(ApplicationRecord* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(ApplicationRecord)); + } +#endif + + template + explicit constexpr ApplicationRecord(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline ApplicationRecord(const ApplicationRecord& from) : ApplicationRecord(nullptr, from) {} + inline ApplicationRecord(ApplicationRecord&& from) noexcept : ApplicationRecord(nullptr, ::std::move(from)) {} + inline ApplicationRecord& operator=(const ApplicationRecord& from) { + CopyFrom(from); + return *this; + } + inline ApplicationRecord& operator=(ApplicationRecord&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const ApplicationRecord& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&ApplicationRecord_globals_); + } + static constexpr int kIndexInFileMessages = 5; + friend void swap(ApplicationRecord& a, ApplicationRecord& b) { a.Swap(&b); } + inline void Swap(ApplicationRecord* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ApplicationRecord* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] ApplicationRecord* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const ApplicationRecord& from); + using Super_::MergeFrom; + void MergeFrom(const ApplicationRecord& from) { ApplicationRecord::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(ApplicationRecord* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment.v1.ApplicationRecord"; } + + explicit ApplicationRecord(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + ApplicationRecord(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const ApplicationRecord& from); + ApplicationRecord( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, ApplicationRecord&& from) noexcept + : ApplicationRecord(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kErrorFieldNumber = 9, + kInstalledRouteFieldNumber = 10, + kInstalledBootIdFieldNumber = 11, + kAssignmentFieldNumber = 1, + kReceivedAtFieldNumber = 4, + kAttemptedAtFieldNumber = 5, + kAppliedAtFieldNumber = 6, + kVerifiedAtFieldNumber = 7, + kRemovedAtFieldNumber = 8, + kDesiredRevisionFieldNumber = 2, + kStatusFieldNumber = 3, + kExpiredLatchedFieldNumber = 12, + }; + // string error = 9 [json_name = "error", (.buf.validate.field) = { + void clear_error() ; + [[nodiscard]] const ::std::string& error() const; + template + void set_error(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_error(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_error(); + void set_allocated_error(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_error() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_error(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_error(); + + public: + // bytes installed_route = 10 [json_name = "installedRoute", (.buf.validate.field) = { + void clear_installed_route() ; + [[nodiscard]] const ::std::string& installed_route() const; + template + void set_installed_route(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_installed_route(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_installed_route(); + void set_allocated_installed_route(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_installed_route() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_installed_route(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_installed_route(); + + public: + // string installed_boot_id = 11 [json_name = "installedBootId", (.buf.validate.field) = { + void clear_installed_boot_id() ; + [[nodiscard]] const ::std::string& installed_boot_id() const; + template + void set_installed_boot_id(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_installed_boot_id(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_installed_boot_id(); + void set_allocated_installed_boot_id(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_installed_boot_id() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_installed_boot_id(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_installed_boot_id(); + + public: + // .xtcp_treatment.v1.Assignment assignment = 1 [json_name = "assignment", (.buf.validate.field) = { + [[nodiscard]] bool has_assignment() const; + void clear_assignment() ; + [[nodiscard]] const ::xtcp_treatment::v1::Assignment& assignment() const; + [[nodiscard]] ::xtcp_treatment::v1::Assignment* PROTOBUF_NULLABLE release_assignment(); + ::xtcp_treatment::v1::Assignment* PROTOBUF_NONNULL mutable_assignment(); + void set_allocated_assignment(::xtcp_treatment::v1::Assignment* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_assignment(::xtcp_treatment::v1::Assignment* PROTOBUF_NULLABLE value); + ::xtcp_treatment::v1::Assignment* PROTOBUF_NULLABLE unsafe_arena_release_assignment(); + + private: + const ::xtcp_treatment::v1::Assignment& _internal_assignment() const; + ::xtcp_treatment::v1::Assignment* PROTOBUF_NONNULL _internal_mutable_assignment(); + + public: + // .google.protobuf.Timestamp received_at = 4 [json_name = "receivedAt"]; + [[nodiscard]] bool has_received_at() const; + void clear_received_at() ; + [[nodiscard]] const ::google::protobuf::Timestamp& received_at() const; + [[nodiscard]] ::google::protobuf::Timestamp* PROTOBUF_NULLABLE release_received_at(); + ::google::protobuf::Timestamp* PROTOBUF_NONNULL mutable_received_at(); + void set_allocated_received_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_received_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE unsafe_arena_release_received_at(); + + private: + const ::google::protobuf::Timestamp& _internal_received_at() const; + ::google::protobuf::Timestamp* PROTOBUF_NONNULL _internal_mutable_received_at(); + + public: + // .google.protobuf.Timestamp attempted_at = 5 [json_name = "attemptedAt"]; + [[nodiscard]] bool has_attempted_at() const; + void clear_attempted_at() ; + [[nodiscard]] const ::google::protobuf::Timestamp& attempted_at() const; + [[nodiscard]] ::google::protobuf::Timestamp* PROTOBUF_NULLABLE release_attempted_at(); + ::google::protobuf::Timestamp* PROTOBUF_NONNULL mutable_attempted_at(); + void set_allocated_attempted_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_attempted_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE unsafe_arena_release_attempted_at(); + + private: + const ::google::protobuf::Timestamp& _internal_attempted_at() const; + ::google::protobuf::Timestamp* PROTOBUF_NONNULL _internal_mutable_attempted_at(); + + public: + // .google.protobuf.Timestamp applied_at = 6 [json_name = "appliedAt"]; + [[nodiscard]] bool has_applied_at() const; + void clear_applied_at() ; + [[nodiscard]] const ::google::protobuf::Timestamp& applied_at() const; + [[nodiscard]] ::google::protobuf::Timestamp* PROTOBUF_NULLABLE release_applied_at(); + ::google::protobuf::Timestamp* PROTOBUF_NONNULL mutable_applied_at(); + void set_allocated_applied_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_applied_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE unsafe_arena_release_applied_at(); + + private: + const ::google::protobuf::Timestamp& _internal_applied_at() const; + ::google::protobuf::Timestamp* PROTOBUF_NONNULL _internal_mutable_applied_at(); + + public: + // .google.protobuf.Timestamp verified_at = 7 [json_name = "verifiedAt"]; + [[nodiscard]] bool has_verified_at() const; + void clear_verified_at() ; + [[nodiscard]] const ::google::protobuf::Timestamp& verified_at() const; + [[nodiscard]] ::google::protobuf::Timestamp* PROTOBUF_NULLABLE release_verified_at(); + ::google::protobuf::Timestamp* PROTOBUF_NONNULL mutable_verified_at(); + void set_allocated_verified_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_verified_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE unsafe_arena_release_verified_at(); + + private: + const ::google::protobuf::Timestamp& _internal_verified_at() const; + ::google::protobuf::Timestamp* PROTOBUF_NONNULL _internal_mutable_verified_at(); + + public: + // .google.protobuf.Timestamp removed_at = 8 [json_name = "removedAt"]; + [[nodiscard]] bool has_removed_at() const; + void clear_removed_at() ; + [[nodiscard]] const ::google::protobuf::Timestamp& removed_at() const; + [[nodiscard]] ::google::protobuf::Timestamp* PROTOBUF_NULLABLE release_removed_at(); + ::google::protobuf::Timestamp* PROTOBUF_NONNULL mutable_removed_at(); + void set_allocated_removed_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_removed_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE unsafe_arena_release_removed_at(); + + private: + const ::google::protobuf::Timestamp& _internal_removed_at() const; + ::google::protobuf::Timestamp* PROTOBUF_NONNULL _internal_mutable_removed_at(); + + public: + // uint64 desired_revision = 2 [json_name = "desiredRevision"]; + void clear_desired_revision() ; + [[nodiscard]] ::uint64_t desired_revision() const; + void set_desired_revision(::uint64_t value); + + private: + ::uint64_t _internal_desired_revision() const; + void _internal_set_desired_revision(::uint64_t value); + + public: + // .xtcp_treatment.v1.ApplyStatus status = 3 [json_name = "status", (.buf.validate.field) = { + void clear_status() ; + [[nodiscard]] ::xtcp_treatment::v1::ApplyStatus status() const; + void set_status(::xtcp_treatment::v1::ApplyStatus value); + + private: + ::xtcp_treatment::v1::ApplyStatus _internal_status() const; + void _internal_set_status(::xtcp_treatment::v1::ApplyStatus value); + + public: + // bool expired_latched = 12 [json_name = "expiredLatched"]; + void clear_expired_latched() ; + [[nodiscard]] bool expired_latched() const; + void set_expired_latched(bool value); + + private: + bool _internal_expired_latched() const; + void _internal_set_expired_latched(bool value); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment.v1.ApplicationRecord) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<4, 12, + 6, 74, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const ApplicationRecord& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::google::protobuf::internal::ArenaStringPtr error_; + ::google::protobuf::internal::ArenaStringPtr installed_route_; + ::google::protobuf::internal::ArenaStringPtr installed_boot_id_; + ::xtcp_treatment::v1::Assignment* PROTOBUF_NULLABLE assignment_; + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE received_at_; + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE attempted_at_; + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE applied_at_; + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE verified_at_; + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE removed_at_; + ::uint64_t desired_revision_; + int status_; + bool expired_latched_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_2fv1_2ftreatment_2eproto; +}; +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED LocalState final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment.v1.LocalState) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline LocalState() : LocalState(nullptr) {} + ~LocalState() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(LocalState* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(LocalState)); + } +#endif + + template + explicit constexpr LocalState(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline LocalState(const LocalState& from) : LocalState(nullptr, from) {} + inline LocalState(LocalState&& from) noexcept : LocalState(nullptr, ::std::move(from)) {} + inline LocalState& operator=(const LocalState& from) { + CopyFrom(from); + return *this; + } + inline LocalState& operator=(LocalState&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const LocalState& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&LocalState_globals_); + } + static constexpr int kIndexInFileMessages = 6; + friend void swap(LocalState& a, LocalState& b) { a.Swap(&b); } + inline void Swap(LocalState* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(LocalState* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] LocalState* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const LocalState& from); + using Super_::MergeFrom; + void MergeFrom(const LocalState& from) { LocalState::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(LocalState* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment.v1.LocalState"; } + + explicit LocalState(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + LocalState(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const LocalState& from); + LocalState( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, LocalState&& from) noexcept + : LocalState(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kRecordsFieldNumber = 4, + kSuspensionReasonFieldNumber = 3, + kDesiredFieldNumber = 1, + kUpdatedAtFieldNumber = 5, + kLocallySuspendedFieldNumber = 2, + }; + // repeated .xtcp_treatment.v1.ApplicationRecord records = 4 [json_name = "records", (.buf.validate.field) = { + [[nodiscard]] int records_size() const; + private: + int _internal_records_size() const; + + public: + void clear_records() ; + [[nodiscard]] const ::xtcp_treatment::v1::ApplicationRecord& records(int index) const; + [[nodiscard]] ::xtcp_treatment::v1::ApplicationRecord* PROTOBUF_NONNULL mutable_records(int index); + ::xtcp_treatment::v1::ApplicationRecord* PROTOBUF_NONNULL add_records(); + [[nodiscard]] const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::ApplicationRecord>& + records() const; + [[nodiscard]] ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::ApplicationRecord>* PROTOBUF_NONNULL + mutable_records(); + + private: + const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::ApplicationRecord>& _internal_records() const; + ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::ApplicationRecord>* PROTOBUF_NONNULL _internal_mutable_records(); + + public: + // string suspension_reason = 3 [json_name = "suspensionReason", (.buf.validate.field) = { + void clear_suspension_reason() ; + [[nodiscard]] const ::std::string& suspension_reason() const; + template + void set_suspension_reason(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_suspension_reason(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_suspension_reason(); + void set_allocated_suspension_reason(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_suspension_reason() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_suspension_reason(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_suspension_reason(); + + public: + // .xtcp_treatment.v1.DesiredState desired = 1 [json_name = "desired"]; + [[nodiscard]] bool has_desired() const; + void clear_desired() ; + [[nodiscard]] const ::xtcp_treatment::v1::DesiredState& desired() const; + [[nodiscard]] ::xtcp_treatment::v1::DesiredState* PROTOBUF_NULLABLE release_desired(); + ::xtcp_treatment::v1::DesiredState* PROTOBUF_NONNULL mutable_desired(); + void set_allocated_desired(::xtcp_treatment::v1::DesiredState* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_desired(::xtcp_treatment::v1::DesiredState* PROTOBUF_NULLABLE value); + ::xtcp_treatment::v1::DesiredState* PROTOBUF_NULLABLE unsafe_arena_release_desired(); + + private: + const ::xtcp_treatment::v1::DesiredState& _internal_desired() const; + ::xtcp_treatment::v1::DesiredState* PROTOBUF_NONNULL _internal_mutable_desired(); + + public: + // .google.protobuf.Timestamp updated_at = 5 [json_name = "updatedAt"]; + [[nodiscard]] bool has_updated_at() const; + void clear_updated_at() ; + [[nodiscard]] const ::google::protobuf::Timestamp& updated_at() const; + [[nodiscard]] ::google::protobuf::Timestamp* PROTOBUF_NULLABLE release_updated_at(); + ::google::protobuf::Timestamp* PROTOBUF_NONNULL mutable_updated_at(); + void set_allocated_updated_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_updated_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE unsafe_arena_release_updated_at(); + + private: + const ::google::protobuf::Timestamp& _internal_updated_at() const; + ::google::protobuf::Timestamp* PROTOBUF_NONNULL _internal_mutable_updated_at(); + + public: + // bool locally_suspended = 2 [json_name = "locallySuspended"]; + void clear_locally_suspended() ; + [[nodiscard]] bool locally_suspended() const; + void set_locally_suspended(bool value); + + private: + bool _internal_locally_suspended() const; + void _internal_set_locally_suspended(bool value); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment.v1.LocalState) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<3, 5, + 3, 54, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const LocalState& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::google::protobuf::RepeatedPtrField< ::xtcp_treatment::v1::ApplicationRecord > records_; + ::google::protobuf::internal::ArenaStringPtr suspension_reason_; + ::xtcp_treatment::v1::DesiredState* PROTOBUF_NULLABLE desired_; + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE updated_at_; + bool locally_suspended_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_2fv1_2ftreatment_2eproto; +}; + +// =================================================================== + + + +inline constexpr int kRoleFieldNumber = 51002; +extern ::google::protobuf::internal::ExtensionIdentifier< + ::google::protobuf::MethodOptions, ::google::protobuf::internal::EnumTypeTraits< ::xtcp_treatment::v1::Role>, 14, false>(role); + +// =================================================================== + + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif // __GNUC__ +// ------------------------------------------------------------------- + +// TCPSettings + +// optional string congestion_control = 1 [json_name = "congestionControl", (.buf.validate.field) = { +inline bool TCPSettings::has_congestion_control() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000001U); + return value; +} +inline void TCPSettings::clear_congestion_control() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.congestion_control_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +inline const ::std::string& TCPSettings::congestion_control() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.TCPSettings.congestion_control) + return _internal_congestion_control(); +} +template +PROTOBUF_ALWAYS_INLINE void TCPSettings::set_congestion_control(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + _impl_.congestion_control_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.TCPSettings.congestion_control) +} +inline ::std::string* PROTOBUF_NONNULL TCPSettings::mutable_congestion_control() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + ::std::string* _s = _internal_mutable_congestion_control(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.TCPSettings.congestion_control) + return _s; +} +inline const ::std::string& TCPSettings::_internal_congestion_control() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.congestion_control_.Get(); +} +inline void TCPSettings::_internal_set_congestion_control(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.congestion_control_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL TCPSettings::_internal_mutable_congestion_control() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.congestion_control_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE TCPSettings::release_congestion_control() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.TCPSettings.congestion_control) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000001U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + auto* released = _impl_.congestion_control_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.congestion_control_.Set("", GetArena()); + } + return released; +} +inline void TCPSettings::set_allocated_congestion_control(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + } + _impl_.congestion_control_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.congestion_control_.IsDefault()) { + _impl_.congestion_control_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.TCPSettings.congestion_control) +} + +// bool congestion_lock = 2 [json_name = "congestionLock"]; +inline void TCPSettings::clear_congestion_lock() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.congestion_lock_ = false; + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); +} +inline bool TCPSettings::congestion_lock() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.TCPSettings.congestion_lock) + return _internal_congestion_lock(); +} +inline void TCPSettings::set_congestion_lock(bool value) { + _internal_set_congestion_lock(value); + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.TCPSettings.congestion_lock) +} +inline bool TCPSettings::_internal_congestion_lock() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.congestion_lock_; +} +inline void TCPSettings::_internal_set_congestion_lock(bool value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.congestion_lock_ = value; +} + +// optional uint32 rto_min_ms = 3 [json_name = "rtoMinMs", (.buf.validate.field) = { +inline bool TCPSettings::has_rto_min_ms() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000002U); + return value; +} +inline void TCPSettings::clear_rto_min_ms() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.rto_min_ms_ = 0u; + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); +} +inline ::uint32_t TCPSettings::rto_min_ms() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.TCPSettings.rto_min_ms) + return _internal_rto_min_ms(); +} +inline void TCPSettings::set_rto_min_ms(::uint32_t value) { + _internal_set_rto_min_ms(value); + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.TCPSettings.rto_min_ms) +} +inline ::uint32_t TCPSettings::_internal_rto_min_ms() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.rto_min_ms_; +} +inline void TCPSettings::_internal_set_rto_min_ms(::uint32_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.rto_min_ms_ = value; +} + +// optional uint32 initial_congestion_window = 4 [json_name = "initialCongestionWindow", (.buf.validate.field) = { +inline bool TCPSettings::has_initial_congestion_window() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000004U); + return value; +} +inline void TCPSettings::clear_initial_congestion_window() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.initial_congestion_window_ = 0u; + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); +} +inline ::uint32_t TCPSettings::initial_congestion_window() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.TCPSettings.initial_congestion_window) + return _internal_initial_congestion_window(); +} +inline void TCPSettings::set_initial_congestion_window(::uint32_t value) { + _internal_set_initial_congestion_window(value); + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.TCPSettings.initial_congestion_window) +} +inline ::uint32_t TCPSettings::_internal_initial_congestion_window() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.initial_congestion_window_; +} +inline void TCPSettings::_internal_set_initial_congestion_window(::uint32_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.initial_congestion_window_ = value; +} + +// optional uint32 initial_receive_window = 5 [json_name = "initialReceiveWindow", (.buf.validate.field) = { +inline bool TCPSettings::has_initial_receive_window() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000008U); + return value; +} +inline void TCPSettings::clear_initial_receive_window() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.initial_receive_window_ = 0u; + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); +} +inline ::uint32_t TCPSettings::initial_receive_window() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.TCPSettings.initial_receive_window) + return _internal_initial_receive_window(); +} +inline void TCPSettings::set_initial_receive_window(::uint32_t value) { + _internal_set_initial_receive_window(value); + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.TCPSettings.initial_receive_window) +} +inline ::uint32_t TCPSettings::_internal_initial_receive_window() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.initial_receive_window_; +} +inline void TCPSettings::_internal_set_initial_receive_window(::uint32_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.initial_receive_window_ = value; +} + +// optional bool quick_ack = 6 [json_name = "quickAck"]; +inline bool TCPSettings::has_quick_ack() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000020U); + return value; +} +inline void TCPSettings::clear_quick_ack() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.quick_ack_ = false; + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); +} +inline bool TCPSettings::quick_ack() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.TCPSettings.quick_ack) + return _internal_quick_ack(); +} +inline void TCPSettings::set_quick_ack(bool value) { + _internal_set_quick_ack(value); + SetHasBit(_impl_._has_bits_[0], 0x00000020U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.TCPSettings.quick_ack) +} +inline bool TCPSettings::_internal_quick_ack() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.quick_ack_; +} +inline void TCPSettings::_internal_set_quick_ack(bool value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.quick_ack_ = value; +} + +// optional bool ecn = 7 [json_name = "ecn"]; +inline bool TCPSettings::has_ecn() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000040U); + return value; +} +inline void TCPSettings::clear_ecn() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.ecn_ = false; + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); +} +inline bool TCPSettings::ecn() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.TCPSettings.ecn) + return _internal_ecn(); +} +inline void TCPSettings::set_ecn(bool value) { + _internal_set_ecn(value); + SetHasBit(_impl_._has_bits_[0], 0x00000040U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.TCPSettings.ecn) +} +inline bool TCPSettings::_internal_ecn() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.ecn_; +} +inline void TCPSettings::_internal_set_ecn(bool value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.ecn_ = value; +} + +// ------------------------------------------------------------------- + +// Treatment + +// string id = 1 [json_name = "id", (.buf.validate.field) = { +inline void Treatment::clear_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.id_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +inline const ::std::string& Treatment::id() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Treatment.id) + return _internal_id(); +} +template +PROTOBUF_ALWAYS_INLINE void Treatment::set_id(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + _impl_.id_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Treatment.id) +} +inline ::std::string* PROTOBUF_NONNULL Treatment::mutable_id() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + ::std::string* _s = _internal_mutable_id(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Treatment.id) + return _s; +} +inline const ::std::string& Treatment::_internal_id() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.id_.Get(); +} +inline void Treatment::_internal_set_id(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.id_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL Treatment::_internal_mutable_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.id_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE Treatment::release_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Treatment.id) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000001U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + auto* released = _impl_.id_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.id_.Set("", GetArena()); + } + return released; +} +inline void Treatment::set_allocated_id(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + } + _impl_.id_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.id_.IsDefault()) { + _impl_.id_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Treatment.id) +} + +// uint64 revision = 2 [json_name = "revision", (.buf.validate.field) = { +inline void Treatment::clear_revision() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.revision_ = ::uint64_t{0u}; + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); +} +inline ::uint64_t Treatment::revision() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Treatment.revision) + return _internal_revision(); +} +inline void Treatment::set_revision(::uint64_t value) { + _internal_set_revision(value); + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Treatment.revision) +} +inline ::uint64_t Treatment::_internal_revision() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.revision_; +} +inline void Treatment::_internal_set_revision(::uint64_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.revision_ = value; +} + +// string destination = 3 [json_name = "destination", (.buf.validate.field) = { +inline void Treatment::clear_destination() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.destination_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); +} +inline const ::std::string& Treatment::destination() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Treatment.destination) + return _internal_destination(); +} +template +PROTOBUF_ALWAYS_INLINE void Treatment::set_destination(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + _impl_.destination_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Treatment.destination) +} +inline ::std::string* PROTOBUF_NONNULL Treatment::mutable_destination() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + ::std::string* _s = _internal_mutable_destination(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Treatment.destination) + return _s; +} +inline const ::std::string& Treatment::_internal_destination() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.destination_.Get(); +} +inline void Treatment::_internal_set_destination(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.destination_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL Treatment::_internal_mutable_destination() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.destination_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE Treatment::release_destination() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Treatment.destination) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000002U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + auto* released = _impl_.destination_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.destination_.Set("", GetArena()); + } + return released; +} +inline void Treatment::set_allocated_destination(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + } + _impl_.destination_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.destination_.IsDefault()) { + _impl_.destination_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Treatment.destination) +} + +// .xtcp_treatment.v1.TCPSettings settings = 4 [json_name = "settings", (.buf.validate.field) = { +inline bool Treatment::has_settings() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000004U); + PROTOBUF_ASSUME(!value || _impl_.settings_ != nullptr); + return value; +} +inline void Treatment::clear_settings() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.settings_ != nullptr) _impl_.settings_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); +} +inline const ::xtcp_treatment::v1::TCPSettings& Treatment::_internal_settings() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::xtcp_treatment::v1::TCPSettings* p = _impl_.settings_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::xtcp_treatment::v1::TCPSettings>(&::xtcp_treatment::v1::TCPSettings_globals_); +} +inline const ::xtcp_treatment::v1::TCPSettings& Treatment::settings() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Treatment.settings) + return _internal_settings(); +} +inline void Treatment::unsafe_arena_set_allocated_settings( + ::xtcp_treatment::v1::TCPSettings* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.settings_); + } + _impl_.settings_ = reinterpret_cast<::xtcp_treatment::v1::TCPSettings*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.Treatment.settings) +} +inline ::xtcp_treatment::v1::TCPSettings* PROTOBUF_NULLABLE Treatment::release_settings() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + ::xtcp_treatment::v1::TCPSettings* released = _impl_.settings_; + _impl_.settings_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::xtcp_treatment::v1::TCPSettings* PROTOBUF_NULLABLE Treatment::unsafe_arena_release_settings() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Treatment.settings) + + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + ::xtcp_treatment::v1::TCPSettings* temp = _impl_.settings_; + _impl_.settings_ = nullptr; + return temp; +} +inline ::xtcp_treatment::v1::TCPSettings* PROTOBUF_NONNULL Treatment::_internal_mutable_settings() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.settings_ == nullptr) { + auto* p = Super_::DefaultConstruct<::xtcp_treatment::v1::TCPSettings>(GetArena()); + _impl_.settings_ = reinterpret_cast<::xtcp_treatment::v1::TCPSettings*>(p); + } + return _impl_.settings_; +} +inline ::xtcp_treatment::v1::TCPSettings* PROTOBUF_NONNULL Treatment::mutable_settings() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + ::xtcp_treatment::v1::TCPSettings* _msg = _internal_mutable_settings(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Treatment.settings) + return _msg; +} +inline void Treatment::set_allocated_settings(::xtcp_treatment::v1::TCPSettings* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.settings_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = value->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + } + + _impl_.settings_ = reinterpret_cast<::xtcp_treatment::v1::TCPSettings*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Treatment.settings) +} + +// bool ecn_fabric_verified = 5 [json_name = "ecnFabricVerified"]; +inline void Treatment::clear_ecn_fabric_verified() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.ecn_fabric_verified_ = false; + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); +} +inline bool Treatment::ecn_fabric_verified() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Treatment.ecn_fabric_verified) + return _internal_ecn_fabric_verified(); +} +inline void Treatment::set_ecn_fabric_verified(bool value) { + _internal_set_ecn_fabric_verified(value); + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Treatment.ecn_fabric_verified) +} +inline bool Treatment::_internal_ecn_fabric_verified() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.ecn_fabric_verified_; +} +inline void Treatment::_internal_set_ecn_fabric_verified(bool value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.ecn_fabric_verified_ = value; +} + +// ------------------------------------------------------------------- + +// Assignment + +// string id = 1 [json_name = "id", (.buf.validate.field) = { +inline void Assignment::clear_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.id_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +inline const ::std::string& Assignment::id() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Assignment.id) + return _internal_id(); +} +template +PROTOBUF_ALWAYS_INLINE void Assignment::set_id(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + _impl_.id_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Assignment.id) +} +inline ::std::string* PROTOBUF_NONNULL Assignment::mutable_id() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + ::std::string* _s = _internal_mutable_id(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Assignment.id) + return _s; +} +inline const ::std::string& Assignment::_internal_id() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.id_.Get(); +} +inline void Assignment::_internal_set_id(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.id_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL Assignment::_internal_mutable_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.id_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE Assignment::release_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Assignment.id) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000001U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + auto* released = _impl_.id_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.id_.Set("", GetArena()); + } + return released; +} +inline void Assignment::set_allocated_id(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + } + _impl_.id_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.id_.IsDefault()) { + _impl_.id_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Assignment.id) +} + +// string experiment_id = 2 [json_name = "experimentId", (.buf.validate.field) = { +inline void Assignment::clear_experiment_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.experiment_id_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); +} +inline const ::std::string& Assignment::experiment_id() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Assignment.experiment_id) + return _internal_experiment_id(); +} +template +PROTOBUF_ALWAYS_INLINE void Assignment::set_experiment_id(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + _impl_.experiment_id_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Assignment.experiment_id) +} +inline ::std::string* PROTOBUF_NONNULL Assignment::mutable_experiment_id() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + ::std::string* _s = _internal_mutable_experiment_id(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Assignment.experiment_id) + return _s; +} +inline const ::std::string& Assignment::_internal_experiment_id() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.experiment_id_.Get(); +} +inline void Assignment::_internal_set_experiment_id(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.experiment_id_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL Assignment::_internal_mutable_experiment_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.experiment_id_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE Assignment::release_experiment_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Assignment.experiment_id) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000002U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + auto* released = _impl_.experiment_id_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.experiment_id_.Set("", GetArena()); + } + return released; +} +inline void Assignment::set_allocated_experiment_id(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + } + _impl_.experiment_id_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.experiment_id_.IsDefault()) { + _impl_.experiment_id_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Assignment.experiment_id) +} + +// .xtcp_treatment.v1.Treatment treatment = 3 [json_name = "treatment", (.buf.validate.field) = { +inline bool Assignment::has_treatment() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000004U); + PROTOBUF_ASSUME(!value || _impl_.treatment_ != nullptr); + return value; +} +inline void Assignment::clear_treatment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.treatment_ != nullptr) _impl_.treatment_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); +} +inline const ::xtcp_treatment::v1::Treatment& Assignment::_internal_treatment() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::xtcp_treatment::v1::Treatment* p = _impl_.treatment_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::xtcp_treatment::v1::Treatment>(&::xtcp_treatment::v1::Treatment_globals_); +} +inline const ::xtcp_treatment::v1::Treatment& Assignment::treatment() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Assignment.treatment) + return _internal_treatment(); +} +inline void Assignment::unsafe_arena_set_allocated_treatment( + ::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.treatment_); + } + _impl_.treatment_ = reinterpret_cast<::xtcp_treatment::v1::Treatment*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.Assignment.treatment) +} +inline ::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE Assignment::release_treatment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + ::xtcp_treatment::v1::Treatment* released = _impl_.treatment_; + _impl_.treatment_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE Assignment::unsafe_arena_release_treatment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Assignment.treatment) + + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + ::xtcp_treatment::v1::Treatment* temp = _impl_.treatment_; + _impl_.treatment_ = nullptr; + return temp; +} +inline ::xtcp_treatment::v1::Treatment* PROTOBUF_NONNULL Assignment::_internal_mutable_treatment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.treatment_ == nullptr) { + auto* p = Super_::DefaultConstruct<::xtcp_treatment::v1::Treatment>(GetArena()); + _impl_.treatment_ = reinterpret_cast<::xtcp_treatment::v1::Treatment*>(p); + } + return _impl_.treatment_; +} +inline ::xtcp_treatment::v1::Treatment* PROTOBUF_NONNULL Assignment::mutable_treatment() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + ::xtcp_treatment::v1::Treatment* _msg = _internal_mutable_treatment(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Assignment.treatment) + return _msg; +} +inline void Assignment::set_allocated_treatment(::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.treatment_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = value->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + } + + _impl_.treatment_ = reinterpret_cast<::xtcp_treatment::v1::Treatment*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Assignment.treatment) +} + +// .xtcp_treatment.v1.AssignmentMode mode = 4 [json_name = "mode", (.buf.validate.field) = { +inline void Assignment::clear_mode() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.mode_ = 0; + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); +} +inline ::xtcp_treatment::v1::AssignmentMode Assignment::mode() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Assignment.mode) + return _internal_mode(); +} +inline void Assignment::set_mode(::xtcp_treatment::v1::AssignmentMode value) { + _internal_set_mode(value); + SetHasBit(_impl_._has_bits_[0], 0x00000040U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Assignment.mode) +} +inline ::xtcp_treatment::v1::AssignmentMode Assignment::_internal_mode() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return static_cast<::xtcp_treatment::v1::AssignmentMode>(_impl_.mode_); +} +inline void Assignment::_internal_set_mode(::xtcp_treatment::v1::AssignmentMode value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.mode_ = value; +} + +// .google.protobuf.Timestamp lease_expires_at = 5 [json_name = "leaseExpiresAt"]; +inline bool Assignment::has_lease_expires_at() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000008U); + PROTOBUF_ASSUME(!value || _impl_.lease_expires_at_ != nullptr); + return value; +} +inline const ::google::protobuf::Timestamp& Assignment::_internal_lease_expires_at() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::google::protobuf::Timestamp* p = _impl_.lease_expires_at_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::google::protobuf::Timestamp>(&::google::protobuf::Timestamp_globals_); +} +inline const ::google::protobuf::Timestamp& Assignment::lease_expires_at() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Assignment.lease_expires_at) + return _internal_lease_expires_at(); +} +inline void Assignment::unsafe_arena_set_allocated_lease_expires_at( + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.lease_expires_at_); + } + _impl_.lease_expires_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.Assignment.lease_expires_at) +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Assignment::release_lease_expires_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + ::google::protobuf::Timestamp* released = _impl_.lease_expires_at_; + _impl_.lease_expires_at_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Assignment::unsafe_arena_release_lease_expires_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Assignment.lease_expires_at) + + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + ::google::protobuf::Timestamp* temp = _impl_.lease_expires_at_; + _impl_.lease_expires_at_ = nullptr; + return temp; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Assignment::_internal_mutable_lease_expires_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.lease_expires_at_ == nullptr) { + auto* p = Super_::DefaultConstruct<::google::protobuf::Timestamp>(GetArena()); + _impl_.lease_expires_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(p); + } + return _impl_.lease_expires_at_; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Assignment::mutable_lease_expires_at() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + ::google::protobuf::Timestamp* _msg = _internal_mutable_lease_expires_at(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Assignment.lease_expires_at) + return _msg; +} +inline void Assignment::set_allocated_lease_expires_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.lease_expires_at_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + } + + _impl_.lease_expires_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Assignment.lease_expires_at) +} + +// .xtcp_treatment.v1.Treatment predecessor = 6 [json_name = "predecessor"]; +inline bool Assignment::has_predecessor() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000010U); + PROTOBUF_ASSUME(!value || _impl_.predecessor_ != nullptr); + return value; +} +inline void Assignment::clear_predecessor() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.predecessor_ != nullptr) _impl_.predecessor_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); +} +inline const ::xtcp_treatment::v1::Treatment& Assignment::_internal_predecessor() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::xtcp_treatment::v1::Treatment* p = _impl_.predecessor_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::xtcp_treatment::v1::Treatment>(&::xtcp_treatment::v1::Treatment_globals_); +} +inline const ::xtcp_treatment::v1::Treatment& Assignment::predecessor() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Assignment.predecessor) + return _internal_predecessor(); +} +inline void Assignment::unsafe_arena_set_allocated_predecessor( + ::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.predecessor_); + } + _impl_.predecessor_ = reinterpret_cast<::xtcp_treatment::v1::Treatment*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.Assignment.predecessor) +} +inline ::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE Assignment::release_predecessor() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + ::xtcp_treatment::v1::Treatment* released = _impl_.predecessor_; + _impl_.predecessor_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE Assignment::unsafe_arena_release_predecessor() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Assignment.predecessor) + + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + ::xtcp_treatment::v1::Treatment* temp = _impl_.predecessor_; + _impl_.predecessor_ = nullptr; + return temp; +} +inline ::xtcp_treatment::v1::Treatment* PROTOBUF_NONNULL Assignment::_internal_mutable_predecessor() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.predecessor_ == nullptr) { + auto* p = Super_::DefaultConstruct<::xtcp_treatment::v1::Treatment>(GetArena()); + _impl_.predecessor_ = reinterpret_cast<::xtcp_treatment::v1::Treatment*>(p); + } + return _impl_.predecessor_; +} +inline ::xtcp_treatment::v1::Treatment* PROTOBUF_NONNULL Assignment::mutable_predecessor() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + ::xtcp_treatment::v1::Treatment* _msg = _internal_mutable_predecessor(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Assignment.predecessor) + return _msg; +} +inline void Assignment::set_allocated_predecessor(::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.predecessor_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = value->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + } + + _impl_.predecessor_ = reinterpret_cast<::xtcp_treatment::v1::Treatment*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Assignment.predecessor) +} + +// uint64 generation = 7 [json_name = "generation", (.buf.validate.field) = { +inline void Assignment::clear_generation() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.generation_ = ::uint64_t{0u}; + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); +} +inline ::uint64_t Assignment::generation() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Assignment.generation) + return _internal_generation(); +} +inline void Assignment::set_generation(::uint64_t value) { + _internal_set_generation(value); + SetHasBit(_impl_._has_bits_[0], 0x00000020U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Assignment.generation) +} +inline ::uint64_t Assignment::_internal_generation() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.generation_; +} +inline void Assignment::_internal_set_generation(::uint64_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.generation_ = value; +} + +// ------------------------------------------------------------------- + +// DesiredState + +// string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { +inline void DesiredState::clear_agent_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.agent_id_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); +} +inline const ::std::string& DesiredState::agent_id() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.DesiredState.agent_id) + return _internal_agent_id(); +} +template +PROTOBUF_ALWAYS_INLINE void DesiredState::set_agent_id(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + _impl_.agent_id_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.DesiredState.agent_id) +} +inline ::std::string* PROTOBUF_NONNULL DesiredState::mutable_agent_id() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + ::std::string* _s = _internal_mutable_agent_id(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.DesiredState.agent_id) + return _s; +} +inline const ::std::string& DesiredState::_internal_agent_id() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.agent_id_.Get(); +} +inline void DesiredState::_internal_set_agent_id(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.agent_id_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL DesiredState::_internal_mutable_agent_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.agent_id_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE DesiredState::release_agent_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.DesiredState.agent_id) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000002U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + auto* released = _impl_.agent_id_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.agent_id_.Set("", GetArena()); + } + return released; +} +inline void DesiredState::set_allocated_agent_id(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + } + _impl_.agent_id_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.agent_id_.IsDefault()) { + _impl_.agent_id_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.DesiredState.agent_id) +} + +// uint64 revision = 2 [json_name = "revision", (.buf.validate.field) = { +inline void DesiredState::clear_revision() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.revision_ = ::uint64_t{0u}; + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); +} +inline ::uint64_t DesiredState::revision() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.DesiredState.revision) + return _internal_revision(); +} +inline void DesiredState::set_revision(::uint64_t value) { + _internal_set_revision(value); + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.DesiredState.revision) +} +inline ::uint64_t DesiredState::_internal_revision() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.revision_; +} +inline void DesiredState::_internal_set_revision(::uint64_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.revision_ = value; +} + +// bool suspended = 3 [json_name = "suspended"]; +inline void DesiredState::clear_suspended() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.suspended_ = false; + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); +} +inline bool DesiredState::suspended() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.DesiredState.suspended) + return _internal_suspended(); +} +inline void DesiredState::set_suspended(bool value) { + _internal_set_suspended(value); + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.DesiredState.suspended) +} +inline bool DesiredState::_internal_suspended() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.suspended_; +} +inline void DesiredState::_internal_set_suspended(bool value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.suspended_ = value; +} + +// repeated .xtcp_treatment.v1.Assignment assignments = 4 [json_name = "assignments", (.buf.validate.field) = { +inline int DesiredState::_internal_assignments_size() const { + return _internal_assignments().size(); +} +inline int DesiredState::assignments_size() const { + return _internal_assignments_size(); +} +inline void DesiredState::clear_assignments() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.assignments_.Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +inline const ::xtcp_treatment::v1::Assignment& DesiredState::assignments(int index) const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.DesiredState.assignments) + return _internal_assignments().Get(index); +} +inline ::xtcp_treatment::v1::Assignment* PROTOBUF_NONNULL DesiredState::mutable_assignments(int index) + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.DesiredState.assignments) + return _internal_mutable_assignments()->Mutable(index); +} +inline ::xtcp_treatment::v1::Assignment* PROTOBUF_NONNULL DesiredState::add_assignments() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + ::google::protobuf::internal::TSanWrite(&_impl_); + ::xtcp_treatment::v1::Assignment* _add = + _internal_mutable_assignments()->InternalAddWithArena( + ::google::protobuf::MessageLite::internal_visibility(), GetArena()); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + // @@protoc_insertion_point(field_add:xtcp_treatment.v1.DesiredState.assignments) + return _add; +} +inline const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::Assignment>& DesiredState::assignments() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_list:xtcp_treatment.v1.DesiredState.assignments) + return _internal_assignments(); +} +inline ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::Assignment>* PROTOBUF_NONNULL +DesiredState::mutable_assignments() ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + // @@protoc_insertion_point(field_mutable_list:xtcp_treatment.v1.DesiredState.assignments) + ::google::protobuf::internal::TSanWrite(&_impl_); + return _internal_mutable_assignments(); +} +inline const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::Assignment>& +DesiredState::_internal_assignments() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.assignments_; +} +inline ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::Assignment>* PROTOBUF_NONNULL +DesiredState::_internal_mutable_assignments() { + ::google::protobuf::internal::TSanRead(&_impl_); + return &_impl_.assignments_; +} + +// .google.protobuf.Timestamp issued_at = 5 [json_name = "issuedAt", (.buf.validate.field) = { +inline bool DesiredState::has_issued_at() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000004U); + PROTOBUF_ASSUME(!value || _impl_.issued_at_ != nullptr); + return value; +} +inline const ::google::protobuf::Timestamp& DesiredState::_internal_issued_at() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::google::protobuf::Timestamp* p = _impl_.issued_at_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::google::protobuf::Timestamp>(&::google::protobuf::Timestamp_globals_); +} +inline const ::google::protobuf::Timestamp& DesiredState::issued_at() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.DesiredState.issued_at) + return _internal_issued_at(); +} +inline void DesiredState::unsafe_arena_set_allocated_issued_at( + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.issued_at_); + } + _impl_.issued_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.DesiredState.issued_at) +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE DesiredState::release_issued_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + ::google::protobuf::Timestamp* released = _impl_.issued_at_; + _impl_.issued_at_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE DesiredState::unsafe_arena_release_issued_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.DesiredState.issued_at) + + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + ::google::protobuf::Timestamp* temp = _impl_.issued_at_; + _impl_.issued_at_ = nullptr; + return temp; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL DesiredState::_internal_mutable_issued_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.issued_at_ == nullptr) { + auto* p = Super_::DefaultConstruct<::google::protobuf::Timestamp>(GetArena()); + _impl_.issued_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(p); + } + return _impl_.issued_at_; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL DesiredState::mutable_issued_at() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + ::google::protobuf::Timestamp* _msg = _internal_mutable_issued_at(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.DesiredState.issued_at) + return _msg; +} +inline void DesiredState::set_allocated_issued_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.issued_at_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + } + + _impl_.issued_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.DesiredState.issued_at) +} + +// ------------------------------------------------------------------- + +// Capabilities + +// string boot_id = 1 [json_name = "bootId", (.buf.validate.field) = { +inline void Capabilities::clear_boot_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.boot_id_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); +} +inline const ::std::string& Capabilities::boot_id() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Capabilities.boot_id) + return _internal_boot_id(); +} +template +PROTOBUF_ALWAYS_INLINE void Capabilities::set_boot_id(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + _impl_.boot_id_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Capabilities.boot_id) +} +inline ::std::string* PROTOBUF_NONNULL Capabilities::mutable_boot_id() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + ::std::string* _s = _internal_mutable_boot_id(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Capabilities.boot_id) + return _s; +} +inline const ::std::string& Capabilities::_internal_boot_id() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.boot_id_.Get(); +} +inline void Capabilities::_internal_set_boot_id(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.boot_id_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL Capabilities::_internal_mutable_boot_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.boot_id_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE Capabilities::release_boot_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Capabilities.boot_id) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000002U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + auto* released = _impl_.boot_id_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.boot_id_.Set("", GetArena()); + } + return released; +} +inline void Capabilities::set_allocated_boot_id(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + } + _impl_.boot_id_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.boot_id_.IsDefault()) { + _impl_.boot_id_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Capabilities.boot_id) +} + +// string kernel_release = 2 [json_name = "kernelRelease", (.buf.validate.field) = { +inline void Capabilities::clear_kernel_release() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.kernel_release_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); +} +inline const ::std::string& Capabilities::kernel_release() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Capabilities.kernel_release) + return _internal_kernel_release(); +} +template +PROTOBUF_ALWAYS_INLINE void Capabilities::set_kernel_release(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + _impl_.kernel_release_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Capabilities.kernel_release) +} +inline ::std::string* PROTOBUF_NONNULL Capabilities::mutable_kernel_release() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + ::std::string* _s = _internal_mutable_kernel_release(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Capabilities.kernel_release) + return _s; +} +inline const ::std::string& Capabilities::_internal_kernel_release() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.kernel_release_.Get(); +} +inline void Capabilities::_internal_set_kernel_release(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.kernel_release_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL Capabilities::_internal_mutable_kernel_release() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.kernel_release_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE Capabilities::release_kernel_release() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Capabilities.kernel_release) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000004U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + auto* released = _impl_.kernel_release_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.kernel_release_.Set("", GetArena()); + } + return released; +} +inline void Capabilities::set_allocated_kernel_release(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + } + _impl_.kernel_release_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.kernel_release_.IsDefault()) { + _impl_.kernel_release_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Capabilities.kernel_release) +} + +// repeated string congestion_algorithms = 3 [json_name = "congestionAlgorithms", (.buf.validate.field) = { +inline int Capabilities::_internal_congestion_algorithms_size() const { + return _internal_congestion_algorithms().size(); +} +inline int Capabilities::congestion_algorithms_size() const { + return _internal_congestion_algorithms_size(); +} +inline void Capabilities::clear_congestion_algorithms() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.congestion_algorithms_.Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +inline ::std::string* PROTOBUF_NONNULL Capabilities::add_congestion_algorithms() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + ::google::protobuf::internal::TSanWrite(&_impl_); + ::std::string* _s = + _internal_mutable_congestion_algorithms()->InternalAddWithArena( + ::google::protobuf::MessageLite::internal_visibility(), GetArena()); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + // @@protoc_insertion_point(field_add_mutable:xtcp_treatment.v1.Capabilities.congestion_algorithms) + return _s; +} +inline const ::std::string& Capabilities::congestion_algorithms(int index) const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Capabilities.congestion_algorithms) + return _internal_congestion_algorithms().Get(index); +} +inline ::std::string* PROTOBUF_NONNULL Capabilities::mutable_congestion_algorithms(int index) + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Capabilities.congestion_algorithms) + return _internal_mutable_congestion_algorithms()->Mutable(index); +} +template +inline void Capabilities::set_congestion_algorithms(int index, Arg_&& value, Args_... args) { + ::google::protobuf::internal::AssignToString( + *_internal_mutable_congestion_algorithms()->Mutable(index), + ::std::forward(value), args... ); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Capabilities.congestion_algorithms) +} +template +inline void Capabilities::add_congestion_algorithms(Arg_&& value, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + ::google::protobuf::internal::AddToRepeatedPtrField( + ::google::protobuf::MessageLite::internal_visibility(), GetArena(), + *_internal_mutable_congestion_algorithms(), ::std::forward(value), + args... ); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + // @@protoc_insertion_point(field_add:xtcp_treatment.v1.Capabilities.congestion_algorithms) +} +inline const ::google::protobuf::RepeatedPtrField<::std::string>& Capabilities::congestion_algorithms() + const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_list:xtcp_treatment.v1.Capabilities.congestion_algorithms) + return _internal_congestion_algorithms(); +} +inline ::google::protobuf::RepeatedPtrField<::std::string>* PROTOBUF_NONNULL +Capabilities::mutable_congestion_algorithms() ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + // @@protoc_insertion_point(field_mutable_list:xtcp_treatment.v1.Capabilities.congestion_algorithms) + ::google::protobuf::internal::TSanWrite(&_impl_); + return _internal_mutable_congestion_algorithms(); +} +inline const ::google::protobuf::RepeatedPtrField<::std::string>& +Capabilities::_internal_congestion_algorithms() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.congestion_algorithms_; +} +inline ::google::protobuf::RepeatedPtrField<::std::string>* PROTOBUF_NONNULL +Capabilities::_internal_mutable_congestion_algorithms() { + ::google::protobuf::internal::TSanRead(&_impl_); + return &_impl_.congestion_algorithms_; +} + +// bool simple_host_routing = 4 [json_name = "simpleHostRouting"]; +inline void Capabilities::clear_simple_host_routing() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.simple_host_routing_ = false; + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); +} +inline bool Capabilities::simple_host_routing() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Capabilities.simple_host_routing) + return _internal_simple_host_routing(); +} +inline void Capabilities::set_simple_host_routing(bool value) { + _internal_set_simple_host_routing(value); + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Capabilities.simple_host_routing) +} +inline bool Capabilities::_internal_simple_host_routing() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.simple_host_routing_; +} +inline void Capabilities::_internal_set_simple_host_routing(bool value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.simple_host_routing_ = value; +} + +// bool actuator_enabled = 5 [json_name = "actuatorEnabled"]; +inline void Capabilities::clear_actuator_enabled() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.actuator_enabled_ = false; + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); +} +inline bool Capabilities::actuator_enabled() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Capabilities.actuator_enabled) + return _internal_actuator_enabled(); +} +inline void Capabilities::set_actuator_enabled(bool value) { + _internal_set_actuator_enabled(value); + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Capabilities.actuator_enabled) +} +inline bool Capabilities::_internal_actuator_enabled() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.actuator_enabled_; +} +inline void Capabilities::_internal_set_actuator_enabled(bool value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.actuator_enabled_ = value; +} + +// ------------------------------------------------------------------- + +// ApplicationRecord + +// .xtcp_treatment.v1.Assignment assignment = 1 [json_name = "assignment", (.buf.validate.field) = { +inline bool ApplicationRecord::has_assignment() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000008U); + PROTOBUF_ASSUME(!value || _impl_.assignment_ != nullptr); + return value; +} +inline void ApplicationRecord::clear_assignment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.assignment_ != nullptr) _impl_.assignment_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); +} +inline const ::xtcp_treatment::v1::Assignment& ApplicationRecord::_internal_assignment() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::xtcp_treatment::v1::Assignment* p = _impl_.assignment_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::xtcp_treatment::v1::Assignment>(&::xtcp_treatment::v1::Assignment_globals_); +} +inline const ::xtcp_treatment::v1::Assignment& ApplicationRecord::assignment() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ApplicationRecord.assignment) + return _internal_assignment(); +} +inline void ApplicationRecord::unsafe_arena_set_allocated_assignment( + ::xtcp_treatment::v1::Assignment* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.assignment_); + } + _impl_.assignment_ = reinterpret_cast<::xtcp_treatment::v1::Assignment*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.ApplicationRecord.assignment) +} +inline ::xtcp_treatment::v1::Assignment* PROTOBUF_NULLABLE ApplicationRecord::release_assignment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + ::xtcp_treatment::v1::Assignment* released = _impl_.assignment_; + _impl_.assignment_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::xtcp_treatment::v1::Assignment* PROTOBUF_NULLABLE ApplicationRecord::unsafe_arena_release_assignment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.ApplicationRecord.assignment) + + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + ::xtcp_treatment::v1::Assignment* temp = _impl_.assignment_; + _impl_.assignment_ = nullptr; + return temp; +} +inline ::xtcp_treatment::v1::Assignment* PROTOBUF_NONNULL ApplicationRecord::_internal_mutable_assignment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.assignment_ == nullptr) { + auto* p = Super_::DefaultConstruct<::xtcp_treatment::v1::Assignment>(GetArena()); + _impl_.assignment_ = reinterpret_cast<::xtcp_treatment::v1::Assignment*>(p); + } + return _impl_.assignment_; +} +inline ::xtcp_treatment::v1::Assignment* PROTOBUF_NONNULL ApplicationRecord::mutable_assignment() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + ::xtcp_treatment::v1::Assignment* _msg = _internal_mutable_assignment(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.ApplicationRecord.assignment) + return _msg; +} +inline void ApplicationRecord::set_allocated_assignment(::xtcp_treatment::v1::Assignment* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.assignment_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = value->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + } + + _impl_.assignment_ = reinterpret_cast<::xtcp_treatment::v1::Assignment*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.ApplicationRecord.assignment) +} + +// uint64 desired_revision = 2 [json_name = "desiredRevision"]; +inline void ApplicationRecord::clear_desired_revision() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.desired_revision_ = ::uint64_t{0u}; + ClearHasBit(_impl_._has_bits_[0], 0x00000200U); +} +inline ::uint64_t ApplicationRecord::desired_revision() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ApplicationRecord.desired_revision) + return _internal_desired_revision(); +} +inline void ApplicationRecord::set_desired_revision(::uint64_t value) { + _internal_set_desired_revision(value); + SetHasBit(_impl_._has_bits_[0], 0x00000200U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.ApplicationRecord.desired_revision) +} +inline ::uint64_t ApplicationRecord::_internal_desired_revision() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.desired_revision_; +} +inline void ApplicationRecord::_internal_set_desired_revision(::uint64_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.desired_revision_ = value; +} + +// .xtcp_treatment.v1.ApplyStatus status = 3 [json_name = "status", (.buf.validate.field) = { +inline void ApplicationRecord::clear_status() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.status_ = 0; + ClearHasBit(_impl_._has_bits_[0], 0x00000400U); +} +inline ::xtcp_treatment::v1::ApplyStatus ApplicationRecord::status() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ApplicationRecord.status) + return _internal_status(); +} +inline void ApplicationRecord::set_status(::xtcp_treatment::v1::ApplyStatus value) { + _internal_set_status(value); + SetHasBit(_impl_._has_bits_[0], 0x00000400U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.ApplicationRecord.status) +} +inline ::xtcp_treatment::v1::ApplyStatus ApplicationRecord::_internal_status() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return static_cast<::xtcp_treatment::v1::ApplyStatus>(_impl_.status_); +} +inline void ApplicationRecord::_internal_set_status(::xtcp_treatment::v1::ApplyStatus value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.status_ = value; +} + +// .google.protobuf.Timestamp received_at = 4 [json_name = "receivedAt"]; +inline bool ApplicationRecord::has_received_at() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000010U); + PROTOBUF_ASSUME(!value || _impl_.received_at_ != nullptr); + return value; +} +inline const ::google::protobuf::Timestamp& ApplicationRecord::_internal_received_at() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::google::protobuf::Timestamp* p = _impl_.received_at_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::google::protobuf::Timestamp>(&::google::protobuf::Timestamp_globals_); +} +inline const ::google::protobuf::Timestamp& ApplicationRecord::received_at() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ApplicationRecord.received_at) + return _internal_received_at(); +} +inline void ApplicationRecord::unsafe_arena_set_allocated_received_at( + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.received_at_); + } + _impl_.received_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.ApplicationRecord.received_at) +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE ApplicationRecord::release_received_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + ::google::protobuf::Timestamp* released = _impl_.received_at_; + _impl_.received_at_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE ApplicationRecord::unsafe_arena_release_received_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.ApplicationRecord.received_at) + + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + ::google::protobuf::Timestamp* temp = _impl_.received_at_; + _impl_.received_at_ = nullptr; + return temp; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL ApplicationRecord::_internal_mutable_received_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.received_at_ == nullptr) { + auto* p = Super_::DefaultConstruct<::google::protobuf::Timestamp>(GetArena()); + _impl_.received_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(p); + } + return _impl_.received_at_; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL ApplicationRecord::mutable_received_at() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + ::google::protobuf::Timestamp* _msg = _internal_mutable_received_at(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.ApplicationRecord.received_at) + return _msg; +} +inline void ApplicationRecord::set_allocated_received_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.received_at_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + } + + _impl_.received_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.ApplicationRecord.received_at) +} + +// .google.protobuf.Timestamp attempted_at = 5 [json_name = "attemptedAt"]; +inline bool ApplicationRecord::has_attempted_at() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000020U); + PROTOBUF_ASSUME(!value || _impl_.attempted_at_ != nullptr); + return value; +} +inline const ::google::protobuf::Timestamp& ApplicationRecord::_internal_attempted_at() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::google::protobuf::Timestamp* p = _impl_.attempted_at_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::google::protobuf::Timestamp>(&::google::protobuf::Timestamp_globals_); +} +inline const ::google::protobuf::Timestamp& ApplicationRecord::attempted_at() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ApplicationRecord.attempted_at) + return _internal_attempted_at(); +} +inline void ApplicationRecord::unsafe_arena_set_allocated_attempted_at( + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.attempted_at_); + } + _impl_.attempted_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000020U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.ApplicationRecord.attempted_at) +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE ApplicationRecord::release_attempted_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + ::google::protobuf::Timestamp* released = _impl_.attempted_at_; + _impl_.attempted_at_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE ApplicationRecord::unsafe_arena_release_attempted_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.ApplicationRecord.attempted_at) + + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + ::google::protobuf::Timestamp* temp = _impl_.attempted_at_; + _impl_.attempted_at_ = nullptr; + return temp; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL ApplicationRecord::_internal_mutable_attempted_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.attempted_at_ == nullptr) { + auto* p = Super_::DefaultConstruct<::google::protobuf::Timestamp>(GetArena()); + _impl_.attempted_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(p); + } + return _impl_.attempted_at_; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL ApplicationRecord::mutable_attempted_at() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000020U); + ::google::protobuf::Timestamp* _msg = _internal_mutable_attempted_at(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.ApplicationRecord.attempted_at) + return _msg; +} +inline void ApplicationRecord::set_allocated_attempted_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.attempted_at_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000020U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + } + + _impl_.attempted_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.ApplicationRecord.attempted_at) +} + +// .google.protobuf.Timestamp applied_at = 6 [json_name = "appliedAt"]; +inline bool ApplicationRecord::has_applied_at() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000040U); + PROTOBUF_ASSUME(!value || _impl_.applied_at_ != nullptr); + return value; +} +inline const ::google::protobuf::Timestamp& ApplicationRecord::_internal_applied_at() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::google::protobuf::Timestamp* p = _impl_.applied_at_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::google::protobuf::Timestamp>(&::google::protobuf::Timestamp_globals_); +} +inline const ::google::protobuf::Timestamp& ApplicationRecord::applied_at() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ApplicationRecord.applied_at) + return _internal_applied_at(); +} +inline void ApplicationRecord::unsafe_arena_set_allocated_applied_at( + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.applied_at_); + } + _impl_.applied_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000040U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.ApplicationRecord.applied_at) +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE ApplicationRecord::release_applied_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + ::google::protobuf::Timestamp* released = _impl_.applied_at_; + _impl_.applied_at_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE ApplicationRecord::unsafe_arena_release_applied_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.ApplicationRecord.applied_at) + + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + ::google::protobuf::Timestamp* temp = _impl_.applied_at_; + _impl_.applied_at_ = nullptr; + return temp; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL ApplicationRecord::_internal_mutable_applied_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.applied_at_ == nullptr) { + auto* p = Super_::DefaultConstruct<::google::protobuf::Timestamp>(GetArena()); + _impl_.applied_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(p); + } + return _impl_.applied_at_; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL ApplicationRecord::mutable_applied_at() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000040U); + ::google::protobuf::Timestamp* _msg = _internal_mutable_applied_at(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.ApplicationRecord.applied_at) + return _msg; +} +inline void ApplicationRecord::set_allocated_applied_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.applied_at_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000040U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + } + + _impl_.applied_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.ApplicationRecord.applied_at) +} + +// .google.protobuf.Timestamp verified_at = 7 [json_name = "verifiedAt"]; +inline bool ApplicationRecord::has_verified_at() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000080U); + PROTOBUF_ASSUME(!value || _impl_.verified_at_ != nullptr); + return value; +} +inline const ::google::protobuf::Timestamp& ApplicationRecord::_internal_verified_at() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::google::protobuf::Timestamp* p = _impl_.verified_at_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::google::protobuf::Timestamp>(&::google::protobuf::Timestamp_globals_); +} +inline const ::google::protobuf::Timestamp& ApplicationRecord::verified_at() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ApplicationRecord.verified_at) + return _internal_verified_at(); +} +inline void ApplicationRecord::unsafe_arena_set_allocated_verified_at( + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.verified_at_); + } + _impl_.verified_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000080U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.ApplicationRecord.verified_at) +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE ApplicationRecord::release_verified_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); + ::google::protobuf::Timestamp* released = _impl_.verified_at_; + _impl_.verified_at_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE ApplicationRecord::unsafe_arena_release_verified_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.ApplicationRecord.verified_at) + + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); + ::google::protobuf::Timestamp* temp = _impl_.verified_at_; + _impl_.verified_at_ = nullptr; + return temp; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL ApplicationRecord::_internal_mutable_verified_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.verified_at_ == nullptr) { + auto* p = Super_::DefaultConstruct<::google::protobuf::Timestamp>(GetArena()); + _impl_.verified_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(p); + } + return _impl_.verified_at_; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL ApplicationRecord::mutable_verified_at() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000080U); + ::google::protobuf::Timestamp* _msg = _internal_mutable_verified_at(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.ApplicationRecord.verified_at) + return _msg; +} +inline void ApplicationRecord::set_allocated_verified_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.verified_at_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000080U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); + } + + _impl_.verified_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.ApplicationRecord.verified_at) +} + +// .google.protobuf.Timestamp removed_at = 8 [json_name = "removedAt"]; +inline bool ApplicationRecord::has_removed_at() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000100U); + PROTOBUF_ASSUME(!value || _impl_.removed_at_ != nullptr); + return value; +} +inline const ::google::protobuf::Timestamp& ApplicationRecord::_internal_removed_at() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::google::protobuf::Timestamp* p = _impl_.removed_at_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::google::protobuf::Timestamp>(&::google::protobuf::Timestamp_globals_); +} +inline const ::google::protobuf::Timestamp& ApplicationRecord::removed_at() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ApplicationRecord.removed_at) + return _internal_removed_at(); +} +inline void ApplicationRecord::unsafe_arena_set_allocated_removed_at( + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.removed_at_); + } + _impl_.removed_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000100U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000100U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.ApplicationRecord.removed_at) +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE ApplicationRecord::release_removed_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000100U); + ::google::protobuf::Timestamp* released = _impl_.removed_at_; + _impl_.removed_at_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE ApplicationRecord::unsafe_arena_release_removed_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.ApplicationRecord.removed_at) + + ClearHasBit(_impl_._has_bits_[0], 0x00000100U); + ::google::protobuf::Timestamp* temp = _impl_.removed_at_; + _impl_.removed_at_ = nullptr; + return temp; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL ApplicationRecord::_internal_mutable_removed_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.removed_at_ == nullptr) { + auto* p = Super_::DefaultConstruct<::google::protobuf::Timestamp>(GetArena()); + _impl_.removed_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(p); + } + return _impl_.removed_at_; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL ApplicationRecord::mutable_removed_at() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000100U); + ::google::protobuf::Timestamp* _msg = _internal_mutable_removed_at(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.ApplicationRecord.removed_at) + return _msg; +} +inline void ApplicationRecord::set_allocated_removed_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.removed_at_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000100U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000100U); + } + + _impl_.removed_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.ApplicationRecord.removed_at) +} + +// string error = 9 [json_name = "error", (.buf.validate.field) = { +inline void ApplicationRecord::clear_error() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.error_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +inline const ::std::string& ApplicationRecord::error() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ApplicationRecord.error) + return _internal_error(); +} +template +PROTOBUF_ALWAYS_INLINE void ApplicationRecord::set_error(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + _impl_.error_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.ApplicationRecord.error) +} +inline ::std::string* PROTOBUF_NONNULL ApplicationRecord::mutable_error() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + ::std::string* _s = _internal_mutable_error(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.ApplicationRecord.error) + return _s; +} +inline const ::std::string& ApplicationRecord::_internal_error() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.error_.Get(); +} +inline void ApplicationRecord::_internal_set_error(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.error_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL ApplicationRecord::_internal_mutable_error() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.error_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE ApplicationRecord::release_error() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.ApplicationRecord.error) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000001U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + auto* released = _impl_.error_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.error_.Set("", GetArena()); + } + return released; +} +inline void ApplicationRecord::set_allocated_error(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + } + _impl_.error_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.error_.IsDefault()) { + _impl_.error_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.ApplicationRecord.error) +} + +// bytes installed_route = 10 [json_name = "installedRoute", (.buf.validate.field) = { +inline void ApplicationRecord::clear_installed_route() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.installed_route_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); +} +inline const ::std::string& ApplicationRecord::installed_route() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ApplicationRecord.installed_route) + return _internal_installed_route(); +} +template +PROTOBUF_ALWAYS_INLINE void ApplicationRecord::set_installed_route(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + _impl_.installed_route_.SetBytes(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.ApplicationRecord.installed_route) +} +inline ::std::string* PROTOBUF_NONNULL ApplicationRecord::mutable_installed_route() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + ::std::string* _s = _internal_mutable_installed_route(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.ApplicationRecord.installed_route) + return _s; +} +inline const ::std::string& ApplicationRecord::_internal_installed_route() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.installed_route_.Get(); +} +inline void ApplicationRecord::_internal_set_installed_route(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.installed_route_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL ApplicationRecord::_internal_mutable_installed_route() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.installed_route_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE ApplicationRecord::release_installed_route() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.ApplicationRecord.installed_route) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000002U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + auto* released = _impl_.installed_route_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.installed_route_.Set("", GetArena()); + } + return released; +} +inline void ApplicationRecord::set_allocated_installed_route(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + } + _impl_.installed_route_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.installed_route_.IsDefault()) { + _impl_.installed_route_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.ApplicationRecord.installed_route) +} + +// string installed_boot_id = 11 [json_name = "installedBootId", (.buf.validate.field) = { +inline void ApplicationRecord::clear_installed_boot_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.installed_boot_id_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); +} +inline const ::std::string& ApplicationRecord::installed_boot_id() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ApplicationRecord.installed_boot_id) + return _internal_installed_boot_id(); +} +template +PROTOBUF_ALWAYS_INLINE void ApplicationRecord::set_installed_boot_id(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + _impl_.installed_boot_id_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.ApplicationRecord.installed_boot_id) +} +inline ::std::string* PROTOBUF_NONNULL ApplicationRecord::mutable_installed_boot_id() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + ::std::string* _s = _internal_mutable_installed_boot_id(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.ApplicationRecord.installed_boot_id) + return _s; +} +inline const ::std::string& ApplicationRecord::_internal_installed_boot_id() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.installed_boot_id_.Get(); +} +inline void ApplicationRecord::_internal_set_installed_boot_id(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.installed_boot_id_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL ApplicationRecord::_internal_mutable_installed_boot_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.installed_boot_id_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE ApplicationRecord::release_installed_boot_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.ApplicationRecord.installed_boot_id) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000004U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + auto* released = _impl_.installed_boot_id_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.installed_boot_id_.Set("", GetArena()); + } + return released; +} +inline void ApplicationRecord::set_allocated_installed_boot_id(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + } + _impl_.installed_boot_id_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.installed_boot_id_.IsDefault()) { + _impl_.installed_boot_id_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.ApplicationRecord.installed_boot_id) +} + +// bool expired_latched = 12 [json_name = "expiredLatched"]; +inline void ApplicationRecord::clear_expired_latched() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.expired_latched_ = false; + ClearHasBit(_impl_._has_bits_[0], 0x00000800U); +} +inline bool ApplicationRecord::expired_latched() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ApplicationRecord.expired_latched) + return _internal_expired_latched(); +} +inline void ApplicationRecord::set_expired_latched(bool value) { + _internal_set_expired_latched(value); + SetHasBit(_impl_._has_bits_[0], 0x00000800U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.ApplicationRecord.expired_latched) +} +inline bool ApplicationRecord::_internal_expired_latched() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.expired_latched_; +} +inline void ApplicationRecord::_internal_set_expired_latched(bool value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.expired_latched_ = value; +} + +// ------------------------------------------------------------------- + +// LocalState + +// .xtcp_treatment.v1.DesiredState desired = 1 [json_name = "desired"]; +inline bool LocalState::has_desired() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000004U); + PROTOBUF_ASSUME(!value || _impl_.desired_ != nullptr); + return value; +} +inline void LocalState::clear_desired() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.desired_ != nullptr) _impl_.desired_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); +} +inline const ::xtcp_treatment::v1::DesiredState& LocalState::_internal_desired() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::xtcp_treatment::v1::DesiredState* p = _impl_.desired_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::xtcp_treatment::v1::DesiredState>(&::xtcp_treatment::v1::DesiredState_globals_); +} +inline const ::xtcp_treatment::v1::DesiredState& LocalState::desired() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.LocalState.desired) + return _internal_desired(); +} +inline void LocalState::unsafe_arena_set_allocated_desired( + ::xtcp_treatment::v1::DesiredState* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.desired_); + } + _impl_.desired_ = reinterpret_cast<::xtcp_treatment::v1::DesiredState*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.LocalState.desired) +} +inline ::xtcp_treatment::v1::DesiredState* PROTOBUF_NULLABLE LocalState::release_desired() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + ::xtcp_treatment::v1::DesiredState* released = _impl_.desired_; + _impl_.desired_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::xtcp_treatment::v1::DesiredState* PROTOBUF_NULLABLE LocalState::unsafe_arena_release_desired() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.LocalState.desired) + + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + ::xtcp_treatment::v1::DesiredState* temp = _impl_.desired_; + _impl_.desired_ = nullptr; + return temp; +} +inline ::xtcp_treatment::v1::DesiredState* PROTOBUF_NONNULL LocalState::_internal_mutable_desired() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.desired_ == nullptr) { + auto* p = Super_::DefaultConstruct<::xtcp_treatment::v1::DesiredState>(GetArena()); + _impl_.desired_ = reinterpret_cast<::xtcp_treatment::v1::DesiredState*>(p); + } + return _impl_.desired_; +} +inline ::xtcp_treatment::v1::DesiredState* PROTOBUF_NONNULL LocalState::mutable_desired() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + ::xtcp_treatment::v1::DesiredState* _msg = _internal_mutable_desired(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.LocalState.desired) + return _msg; +} +inline void LocalState::set_allocated_desired(::xtcp_treatment::v1::DesiredState* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.desired_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = value->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + } + + _impl_.desired_ = reinterpret_cast<::xtcp_treatment::v1::DesiredState*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.LocalState.desired) +} + +// bool locally_suspended = 2 [json_name = "locallySuspended"]; +inline void LocalState::clear_locally_suspended() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.locally_suspended_ = false; + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); +} +inline bool LocalState::locally_suspended() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.LocalState.locally_suspended) + return _internal_locally_suspended(); +} +inline void LocalState::set_locally_suspended(bool value) { + _internal_set_locally_suspended(value); + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.LocalState.locally_suspended) +} +inline bool LocalState::_internal_locally_suspended() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.locally_suspended_; +} +inline void LocalState::_internal_set_locally_suspended(bool value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.locally_suspended_ = value; +} + +// string suspension_reason = 3 [json_name = "suspensionReason", (.buf.validate.field) = { +inline void LocalState::clear_suspension_reason() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.suspension_reason_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); +} +inline const ::std::string& LocalState::suspension_reason() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.LocalState.suspension_reason) + return _internal_suspension_reason(); +} +template +PROTOBUF_ALWAYS_INLINE void LocalState::set_suspension_reason(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + _impl_.suspension_reason_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.LocalState.suspension_reason) +} +inline ::std::string* PROTOBUF_NONNULL LocalState::mutable_suspension_reason() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + ::std::string* _s = _internal_mutable_suspension_reason(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.LocalState.suspension_reason) + return _s; +} +inline const ::std::string& LocalState::_internal_suspension_reason() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.suspension_reason_.Get(); +} +inline void LocalState::_internal_set_suspension_reason(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.suspension_reason_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL LocalState::_internal_mutable_suspension_reason() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.suspension_reason_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE LocalState::release_suspension_reason() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.LocalState.suspension_reason) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000002U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + auto* released = _impl_.suspension_reason_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.suspension_reason_.Set("", GetArena()); + } + return released; +} +inline void LocalState::set_allocated_suspension_reason(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + } + _impl_.suspension_reason_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.suspension_reason_.IsDefault()) { + _impl_.suspension_reason_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.LocalState.suspension_reason) +} + +// repeated .xtcp_treatment.v1.ApplicationRecord records = 4 [json_name = "records", (.buf.validate.field) = { +inline int LocalState::_internal_records_size() const { + return _internal_records().size(); +} +inline int LocalState::records_size() const { + return _internal_records_size(); +} +inline void LocalState::clear_records() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.records_.Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +inline const ::xtcp_treatment::v1::ApplicationRecord& LocalState::records(int index) const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.LocalState.records) + return _internal_records().Get(index); +} +inline ::xtcp_treatment::v1::ApplicationRecord* PROTOBUF_NONNULL LocalState::mutable_records(int index) + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.LocalState.records) + return _internal_mutable_records()->Mutable(index); +} +inline ::xtcp_treatment::v1::ApplicationRecord* PROTOBUF_NONNULL LocalState::add_records() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + ::google::protobuf::internal::TSanWrite(&_impl_); + ::xtcp_treatment::v1::ApplicationRecord* _add = + _internal_mutable_records()->InternalAddWithArena( + ::google::protobuf::MessageLite::internal_visibility(), GetArena()); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + // @@protoc_insertion_point(field_add:xtcp_treatment.v1.LocalState.records) + return _add; +} +inline const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::ApplicationRecord>& LocalState::records() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_list:xtcp_treatment.v1.LocalState.records) + return _internal_records(); +} +inline ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::ApplicationRecord>* PROTOBUF_NONNULL +LocalState::mutable_records() ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + // @@protoc_insertion_point(field_mutable_list:xtcp_treatment.v1.LocalState.records) + ::google::protobuf::internal::TSanWrite(&_impl_); + return _internal_mutable_records(); +} +inline const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::ApplicationRecord>& +LocalState::_internal_records() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.records_; +} +inline ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::ApplicationRecord>* PROTOBUF_NONNULL +LocalState::_internal_mutable_records() { + ::google::protobuf::internal::TSanRead(&_impl_); + return &_impl_.records_; +} + +// .google.protobuf.Timestamp updated_at = 5 [json_name = "updatedAt"]; +inline bool LocalState::has_updated_at() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000008U); + PROTOBUF_ASSUME(!value || _impl_.updated_at_ != nullptr); + return value; +} +inline const ::google::protobuf::Timestamp& LocalState::_internal_updated_at() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::google::protobuf::Timestamp* p = _impl_.updated_at_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::google::protobuf::Timestamp>(&::google::protobuf::Timestamp_globals_); +} +inline const ::google::protobuf::Timestamp& LocalState::updated_at() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.LocalState.updated_at) + return _internal_updated_at(); +} +inline void LocalState::unsafe_arena_set_allocated_updated_at( + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.updated_at_); + } + _impl_.updated_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.LocalState.updated_at) +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE LocalState::release_updated_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + ::google::protobuf::Timestamp* released = _impl_.updated_at_; + _impl_.updated_at_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE LocalState::unsafe_arena_release_updated_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.LocalState.updated_at) + + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + ::google::protobuf::Timestamp* temp = _impl_.updated_at_; + _impl_.updated_at_ = nullptr; + return temp; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL LocalState::_internal_mutable_updated_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.updated_at_ == nullptr) { + auto* p = Super_::DefaultConstruct<::google::protobuf::Timestamp>(GetArena()); + _impl_.updated_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(p); + } + return _impl_.updated_at_; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL LocalState::mutable_updated_at() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + ::google::protobuf::Timestamp* _msg = _internal_mutable_updated_at(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.LocalState.updated_at) + return _msg; +} +inline void LocalState::set_allocated_updated_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.updated_at_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + } + + _impl_.updated_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.LocalState.updated_at) +} + +// ------------------------------------------------------------------- + +// HostEvidence + +// string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { +inline void HostEvidence::clear_agent_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.agent_id_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +inline const ::std::string& HostEvidence::agent_id() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.HostEvidence.agent_id) + return _internal_agent_id(); +} +template +PROTOBUF_ALWAYS_INLINE void HostEvidence::set_agent_id(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + _impl_.agent_id_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.HostEvidence.agent_id) +} +inline ::std::string* PROTOBUF_NONNULL HostEvidence::mutable_agent_id() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + ::std::string* _s = _internal_mutable_agent_id(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.HostEvidence.agent_id) + return _s; +} +inline const ::std::string& HostEvidence::_internal_agent_id() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.agent_id_.Get(); +} +inline void HostEvidence::_internal_set_agent_id(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.agent_id_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL HostEvidence::_internal_mutable_agent_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.agent_id_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE HostEvidence::release_agent_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.HostEvidence.agent_id) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000001U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + auto* released = _impl_.agent_id_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.agent_id_.Set("", GetArena()); + } + return released; +} +inline void HostEvidence::set_allocated_agent_id(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + } + _impl_.agent_id_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.agent_id_.IsDefault()) { + _impl_.agent_id_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.HostEvidence.agent_id) +} + +// uint64 sockets = 2 [json_name = "sockets"]; +inline void HostEvidence::clear_sockets() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.sockets_ = ::uint64_t{0u}; + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); +} +inline ::uint64_t HostEvidence::sockets() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.HostEvidence.sockets) + return _internal_sockets(); +} +inline void HostEvidence::set_sockets(::uint64_t value) { + _internal_set_sockets(value); + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.HostEvidence.sockets) +} +inline ::uint64_t HostEvidence::_internal_sockets() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.sockets_; +} +inline void HostEvidence::_internal_set_sockets(::uint64_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.sockets_ = value; +} + +// uint64 bytes_acked = 3 [json_name = "bytesAcked"]; +inline void HostEvidence::clear_bytes_acked() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.bytes_acked_ = ::uint64_t{0u}; + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); +} +inline ::uint64_t HostEvidence::bytes_acked() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.HostEvidence.bytes_acked) + return _internal_bytes_acked(); +} +inline void HostEvidence::set_bytes_acked(::uint64_t value) { + _internal_set_bytes_acked(value); + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.HostEvidence.bytes_acked) +} +inline ::uint64_t HostEvidence::_internal_bytes_acked() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.bytes_acked_; +} +inline void HostEvidence::_internal_set_bytes_acked(::uint64_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.bytes_acked_ = value; +} + +// double goodput_bps = 4 [json_name = "goodputBps", (.buf.validate.field) = { +inline void HostEvidence::clear_goodput_bps() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.goodput_bps_ = 0; + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); +} +inline double HostEvidence::goodput_bps() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.HostEvidence.goodput_bps) + return _internal_goodput_bps(); +} +inline void HostEvidence::set_goodput_bps(double value) { + _internal_set_goodput_bps(value); + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.HostEvidence.goodput_bps) +} +inline double HostEvidence::_internal_goodput_bps() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.goodput_bps_; +} +inline void HostEvidence::_internal_set_goodput_bps(double value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.goodput_bps_ = value; +} + +// double retransmission_ratio = 5 [json_name = "retransmissionRatio", (.buf.validate.field) = { +inline void HostEvidence::clear_retransmission_ratio() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.retransmission_ratio_ = 0; + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); +} +inline double HostEvidence::retransmission_ratio() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.HostEvidence.retransmission_ratio) + return _internal_retransmission_ratio(); +} +inline void HostEvidence::set_retransmission_ratio(double value) { + _internal_set_retransmission_ratio(value); + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.HostEvidence.retransmission_ratio) +} +inline double HostEvidence::_internal_retransmission_ratio() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.retransmission_ratio_; +} +inline void HostEvidence::_internal_set_retransmission_ratio(double value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.retransmission_ratio_ = value; +} + +// double rtt_p95_ms = 6 [json_name = "rttP95Ms", (.buf.validate.field) = { +inline void HostEvidence::clear_rtt_p95_ms() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.rtt_p95_ms_ = 0; + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); +} +inline double HostEvidence::rtt_p95_ms() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.HostEvidence.rtt_p95_ms) + return _internal_rtt_p95_ms(); +} +inline void HostEvidence::set_rtt_p95_ms(double value) { + _internal_set_rtt_p95_ms(value); + SetHasBit(_impl_._has_bits_[0], 0x00000020U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.HostEvidence.rtt_p95_ms) +} +inline double HostEvidence::_internal_rtt_p95_ms() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.rtt_p95_ms_; +} +inline void HostEvidence::_internal_set_rtt_p95_ms(double value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.rtt_p95_ms_ = value; +} + +// uint64 exposed_sockets = 7 [json_name = "exposedSockets"]; +inline void HostEvidence::clear_exposed_sockets() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.exposed_sockets_ = ::uint64_t{0u}; + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); +} +inline ::uint64_t HostEvidence::exposed_sockets() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.HostEvidence.exposed_sockets) + return _internal_exposed_sockets(); +} +inline void HostEvidence::set_exposed_sockets(::uint64_t value) { + _internal_set_exposed_sockets(value); + SetHasBit(_impl_._has_bits_[0], 0x00000040U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.HostEvidence.exposed_sockets) +} +inline ::uint64_t HostEvidence::_internal_exposed_sockets() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.exposed_sockets_; +} +inline void HostEvidence::_internal_set_exposed_sockets(::uint64_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.exposed_sockets_ = value; +} + +// bool collection_healthy = 8 [json_name = "collectionHealthy"]; +inline void HostEvidence::clear_collection_healthy() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.collection_healthy_ = false; + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); +} +inline bool HostEvidence::collection_healthy() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.HostEvidence.collection_healthy) + return _internal_collection_healthy(); +} +inline void HostEvidence::set_collection_healthy(bool value) { + _internal_set_collection_healthy(value); + SetHasBit(_impl_._has_bits_[0], 0x00000080U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.HostEvidence.collection_healthy) +} +inline bool HostEvidence::_internal_collection_healthy() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.collection_healthy_; +} +inline void HostEvidence::_internal_set_collection_healthy(bool value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.collection_healthy_ = value; +} + +// ------------------------------------------------------------------- + +// Evidence + +// string id = 1 [json_name = "id", (.buf.validate.field) = { +inline void Evidence::clear_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.id_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); +} +inline const ::std::string& Evidence::id() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Evidence.id) + return _internal_id(); +} +template +PROTOBUF_ALWAYS_INLINE void Evidence::set_id(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + _impl_.id_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Evidence.id) +} +inline ::std::string* PROTOBUF_NONNULL Evidence::mutable_id() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + ::std::string* _s = _internal_mutable_id(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Evidence.id) + return _s; +} +inline const ::std::string& Evidence::_internal_id() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.id_.Get(); +} +inline void Evidence::_internal_set_id(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.id_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL Evidence::_internal_mutable_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.id_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE Evidence::release_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Evidence.id) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000002U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + auto* released = _impl_.id_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.id_.Set("", GetArena()); + } + return released; +} +inline void Evidence::set_allocated_id(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + } + _impl_.id_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.id_.IsDefault()) { + _impl_.id_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Evidence.id) +} + +// .google.protobuf.Timestamp analyzed_at = 2 [json_name = "analyzedAt", (.buf.validate.field) = { +inline bool Evidence::has_analyzed_at() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000020U); + PROTOBUF_ASSUME(!value || _impl_.analyzed_at_ != nullptr); + return value; +} +inline const ::google::protobuf::Timestamp& Evidence::_internal_analyzed_at() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::google::protobuf::Timestamp* p = _impl_.analyzed_at_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::google::protobuf::Timestamp>(&::google::protobuf::Timestamp_globals_); +} +inline const ::google::protobuf::Timestamp& Evidence::analyzed_at() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Evidence.analyzed_at) + return _internal_analyzed_at(); +} +inline void Evidence::unsafe_arena_set_allocated_analyzed_at( + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.analyzed_at_); + } + _impl_.analyzed_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000020U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.Evidence.analyzed_at) +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Evidence::release_analyzed_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + ::google::protobuf::Timestamp* released = _impl_.analyzed_at_; + _impl_.analyzed_at_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Evidence::unsafe_arena_release_analyzed_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Evidence.analyzed_at) + + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + ::google::protobuf::Timestamp* temp = _impl_.analyzed_at_; + _impl_.analyzed_at_ = nullptr; + return temp; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Evidence::_internal_mutable_analyzed_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.analyzed_at_ == nullptr) { + auto* p = Super_::DefaultConstruct<::google::protobuf::Timestamp>(GetArena()); + _impl_.analyzed_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(p); + } + return _impl_.analyzed_at_; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Evidence::mutable_analyzed_at() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000020U); + ::google::protobuf::Timestamp* _msg = _internal_mutable_analyzed_at(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Evidence.analyzed_at) + return _msg; +} +inline void Evidence::set_allocated_analyzed_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.analyzed_at_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000020U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + } + + _impl_.analyzed_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Evidence.analyzed_at) +} + +// .google.protobuf.Timestamp window_start = 3 [json_name = "windowStart", (.buf.validate.field) = { +inline bool Evidence::has_window_start() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000040U); + PROTOBUF_ASSUME(!value || _impl_.window_start_ != nullptr); + return value; +} +inline const ::google::protobuf::Timestamp& Evidence::_internal_window_start() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::google::protobuf::Timestamp* p = _impl_.window_start_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::google::protobuf::Timestamp>(&::google::protobuf::Timestamp_globals_); +} +inline const ::google::protobuf::Timestamp& Evidence::window_start() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Evidence.window_start) + return _internal_window_start(); +} +inline void Evidence::unsafe_arena_set_allocated_window_start( + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.window_start_); + } + _impl_.window_start_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000040U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.Evidence.window_start) +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Evidence::release_window_start() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + ::google::protobuf::Timestamp* released = _impl_.window_start_; + _impl_.window_start_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Evidence::unsafe_arena_release_window_start() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Evidence.window_start) + + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + ::google::protobuf::Timestamp* temp = _impl_.window_start_; + _impl_.window_start_ = nullptr; + return temp; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Evidence::_internal_mutable_window_start() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.window_start_ == nullptr) { + auto* p = Super_::DefaultConstruct<::google::protobuf::Timestamp>(GetArena()); + _impl_.window_start_ = reinterpret_cast<::google::protobuf::Timestamp*>(p); + } + return _impl_.window_start_; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Evidence::mutable_window_start() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000040U); + ::google::protobuf::Timestamp* _msg = _internal_mutable_window_start(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Evidence.window_start) + return _msg; +} +inline void Evidence::set_allocated_window_start(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.window_start_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000040U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + } + + _impl_.window_start_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Evidence.window_start) +} + +// .google.protobuf.Timestamp window_end = 4 [json_name = "windowEnd", (.buf.validate.field) = { +inline bool Evidence::has_window_end() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000080U); + PROTOBUF_ASSUME(!value || _impl_.window_end_ != nullptr); + return value; +} +inline const ::google::protobuf::Timestamp& Evidence::_internal_window_end() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::google::protobuf::Timestamp* p = _impl_.window_end_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::google::protobuf::Timestamp>(&::google::protobuf::Timestamp_globals_); +} +inline const ::google::protobuf::Timestamp& Evidence::window_end() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Evidence.window_end) + return _internal_window_end(); +} +inline void Evidence::unsafe_arena_set_allocated_window_end( + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.window_end_); + } + _impl_.window_end_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000080U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.Evidence.window_end) +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Evidence::release_window_end() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); + ::google::protobuf::Timestamp* released = _impl_.window_end_; + _impl_.window_end_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Evidence::unsafe_arena_release_window_end() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Evidence.window_end) + + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); + ::google::protobuf::Timestamp* temp = _impl_.window_end_; + _impl_.window_end_ = nullptr; + return temp; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Evidence::_internal_mutable_window_end() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.window_end_ == nullptr) { + auto* p = Super_::DefaultConstruct<::google::protobuf::Timestamp>(GetArena()); + _impl_.window_end_ = reinterpret_cast<::google::protobuf::Timestamp*>(p); + } + return _impl_.window_end_; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Evidence::mutable_window_end() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000080U); + ::google::protobuf::Timestamp* _msg = _internal_mutable_window_end(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Evidence.window_end) + return _msg; +} +inline void Evidence::set_allocated_window_end(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.window_end_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000080U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); + } + + _impl_.window_end_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Evidence.window_end) +} + +// string query_version = 5 [json_name = "queryVersion", (.buf.validate.field) = { +inline void Evidence::clear_query_version() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.query_version_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); +} +inline const ::std::string& Evidence::query_version() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Evidence.query_version) + return _internal_query_version(); +} +template +PROTOBUF_ALWAYS_INLINE void Evidence::set_query_version(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + _impl_.query_version_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Evidence.query_version) +} +inline ::std::string* PROTOBUF_NONNULL Evidence::mutable_query_version() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + ::std::string* _s = _internal_mutable_query_version(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Evidence.query_version) + return _s; +} +inline const ::std::string& Evidence::_internal_query_version() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.query_version_.Get(); +} +inline void Evidence::_internal_set_query_version(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.query_version_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL Evidence::_internal_mutable_query_version() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.query_version_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE Evidence::release_query_version() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Evidence.query_version) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000004U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + auto* released = _impl_.query_version_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.query_version_.Set("", GetArena()); + } + return released; +} +inline void Evidence::set_allocated_query_version(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + } + _impl_.query_version_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.query_version_.IsDefault()) { + _impl_.query_version_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Evidence.query_version) +} + +// string enrichment_version = 6 [json_name = "enrichmentVersion", (.buf.validate.field) = { +inline void Evidence::clear_enrichment_version() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.enrichment_version_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); +} +inline const ::std::string& Evidence::enrichment_version() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Evidence.enrichment_version) + return _internal_enrichment_version(); +} +template +PROTOBUF_ALWAYS_INLINE void Evidence::set_enrichment_version(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + _impl_.enrichment_version_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Evidence.enrichment_version) +} +inline ::std::string* PROTOBUF_NONNULL Evidence::mutable_enrichment_version() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + ::std::string* _s = _internal_mutable_enrichment_version(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Evidence.enrichment_version) + return _s; +} +inline const ::std::string& Evidence::_internal_enrichment_version() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.enrichment_version_.Get(); +} +inline void Evidence::_internal_set_enrichment_version(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.enrichment_version_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL Evidence::_internal_mutable_enrichment_version() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.enrichment_version_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE Evidence::release_enrichment_version() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Evidence.enrichment_version) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000008U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + auto* released = _impl_.enrichment_version_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.enrichment_version_.Set("", GetArena()); + } + return released; +} +inline void Evidence::set_allocated_enrichment_version(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + } + _impl_.enrichment_version_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.enrichment_version_.IsDefault()) { + _impl_.enrichment_version_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Evidence.enrichment_version) +} + +// repeated .xtcp_treatment.v1.HostEvidence hosts = 7 [json_name = "hosts", (.buf.validate.field) = { +inline int Evidence::_internal_hosts_size() const { + return _internal_hosts().size(); +} +inline int Evidence::hosts_size() const { + return _internal_hosts_size(); +} +inline void Evidence::clear_hosts() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.hosts_.Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +inline const ::xtcp_treatment::v1::HostEvidence& Evidence::hosts(int index) const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Evidence.hosts) + return _internal_hosts().Get(index); +} +inline ::xtcp_treatment::v1::HostEvidence* PROTOBUF_NONNULL Evidence::mutable_hosts(int index) + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Evidence.hosts) + return _internal_mutable_hosts()->Mutable(index); +} +inline ::xtcp_treatment::v1::HostEvidence* PROTOBUF_NONNULL Evidence::add_hosts() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + ::google::protobuf::internal::TSanWrite(&_impl_); + ::xtcp_treatment::v1::HostEvidence* _add = + _internal_mutable_hosts()->InternalAddWithArena( + ::google::protobuf::MessageLite::internal_visibility(), GetArena()); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + // @@protoc_insertion_point(field_add:xtcp_treatment.v1.Evidence.hosts) + return _add; +} +inline const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::HostEvidence>& Evidence::hosts() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_list:xtcp_treatment.v1.Evidence.hosts) + return _internal_hosts(); +} +inline ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::HostEvidence>* PROTOBUF_NONNULL +Evidence::mutable_hosts() ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + // @@protoc_insertion_point(field_mutable_list:xtcp_treatment.v1.Evidence.hosts) + ::google::protobuf::internal::TSanWrite(&_impl_); + return _internal_mutable_hosts(); +} +inline const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::HostEvidence>& +Evidence::_internal_hosts() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.hosts_; +} +inline ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::HostEvidence>* PROTOBUF_NONNULL +Evidence::_internal_mutable_hosts() { + ::google::protobuf::internal::TSanRead(&_impl_); + return &_impl_.hosts_; +} + +// string artifact_sha256 = 8 [json_name = "artifactSha256", (.buf.validate.field) = { +inline void Evidence::clear_artifact_sha256() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.artifact_sha256_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); +} +inline const ::std::string& Evidence::artifact_sha256() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Evidence.artifact_sha256) + return _internal_artifact_sha256(); +} +template +PROTOBUF_ALWAYS_INLINE void Evidence::set_artifact_sha256(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + _impl_.artifact_sha256_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Evidence.artifact_sha256) +} +inline ::std::string* PROTOBUF_NONNULL Evidence::mutable_artifact_sha256() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + ::std::string* _s = _internal_mutable_artifact_sha256(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Evidence.artifact_sha256) + return _s; +} +inline const ::std::string& Evidence::_internal_artifact_sha256() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.artifact_sha256_.Get(); +} +inline void Evidence::_internal_set_artifact_sha256(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.artifact_sha256_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL Evidence::_internal_mutable_artifact_sha256() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.artifact_sha256_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE Evidence::release_artifact_sha256() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Evidence.artifact_sha256) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000010U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + auto* released = _impl_.artifact_sha256_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.artifact_sha256_.Set("", GetArena()); + } + return released; +} +inline void Evidence::set_allocated_artifact_sha256(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + } + _impl_.artifact_sha256_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.artifact_sha256_.IsDefault()) { + _impl_.artifact_sha256_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Evidence.artifact_sha256) +} + +// ------------------------------------------------------------------- + +// ExperimentPolicy + +// .google.protobuf.Duration minimum_observation = 1 [json_name = "minimumObservation", (.buf.validate.field) = { +inline bool ExperimentPolicy::has_minimum_observation() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000001U); + PROTOBUF_ASSUME(!value || _impl_.minimum_observation_ != nullptr); + return value; +} +inline const ::google::protobuf::Duration& ExperimentPolicy::_internal_minimum_observation() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::google::protobuf::Duration* p = _impl_.minimum_observation_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::google::protobuf::Duration>(&::google::protobuf::Duration_globals_); +} +inline const ::google::protobuf::Duration& ExperimentPolicy::minimum_observation() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ExperimentPolicy.minimum_observation) + return _internal_minimum_observation(); +} +inline void ExperimentPolicy::unsafe_arena_set_allocated_minimum_observation( + ::google::protobuf::Duration* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.minimum_observation_); + } + _impl_.minimum_observation_ = reinterpret_cast<::google::protobuf::Duration*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.ExperimentPolicy.minimum_observation) +} +inline ::google::protobuf::Duration* PROTOBUF_NULLABLE ExperimentPolicy::release_minimum_observation() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + ::google::protobuf::Duration* released = _impl_.minimum_observation_; + _impl_.minimum_observation_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::google::protobuf::Duration* PROTOBUF_NULLABLE ExperimentPolicy::unsafe_arena_release_minimum_observation() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.ExperimentPolicy.minimum_observation) + + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + ::google::protobuf::Duration* temp = _impl_.minimum_observation_; + _impl_.minimum_observation_ = nullptr; + return temp; +} +inline ::google::protobuf::Duration* PROTOBUF_NONNULL ExperimentPolicy::_internal_mutable_minimum_observation() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.minimum_observation_ == nullptr) { + auto* p = Super_::DefaultConstruct<::google::protobuf::Duration>(GetArena()); + _impl_.minimum_observation_ = reinterpret_cast<::google::protobuf::Duration*>(p); + } + return _impl_.minimum_observation_; +} +inline ::google::protobuf::Duration* PROTOBUF_NONNULL ExperimentPolicy::mutable_minimum_observation() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + ::google::protobuf::Duration* _msg = _internal_mutable_minimum_observation(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.ExperimentPolicy.minimum_observation) + return _msg; +} +inline void ExperimentPolicy::set_allocated_minimum_observation(::google::protobuf::Duration* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.minimum_observation_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + } + + _impl_.minimum_observation_ = reinterpret_cast<::google::protobuf::Duration*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.ExperimentPolicy.minimum_observation) +} + +// .google.protobuf.Duration maximum_phase = 2 [json_name = "maximumPhase", (.buf.validate.field) = { +inline bool ExperimentPolicy::has_maximum_phase() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000002U); + PROTOBUF_ASSUME(!value || _impl_.maximum_phase_ != nullptr); + return value; +} +inline const ::google::protobuf::Duration& ExperimentPolicy::_internal_maximum_phase() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::google::protobuf::Duration* p = _impl_.maximum_phase_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::google::protobuf::Duration>(&::google::protobuf::Duration_globals_); +} +inline const ::google::protobuf::Duration& ExperimentPolicy::maximum_phase() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ExperimentPolicy.maximum_phase) + return _internal_maximum_phase(); +} +inline void ExperimentPolicy::unsafe_arena_set_allocated_maximum_phase( + ::google::protobuf::Duration* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.maximum_phase_); + } + _impl_.maximum_phase_ = reinterpret_cast<::google::protobuf::Duration*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.ExperimentPolicy.maximum_phase) +} +inline ::google::protobuf::Duration* PROTOBUF_NULLABLE ExperimentPolicy::release_maximum_phase() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + ::google::protobuf::Duration* released = _impl_.maximum_phase_; + _impl_.maximum_phase_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::google::protobuf::Duration* PROTOBUF_NULLABLE ExperimentPolicy::unsafe_arena_release_maximum_phase() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.ExperimentPolicy.maximum_phase) + + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + ::google::protobuf::Duration* temp = _impl_.maximum_phase_; + _impl_.maximum_phase_ = nullptr; + return temp; +} +inline ::google::protobuf::Duration* PROTOBUF_NONNULL ExperimentPolicy::_internal_mutable_maximum_phase() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.maximum_phase_ == nullptr) { + auto* p = Super_::DefaultConstruct<::google::protobuf::Duration>(GetArena()); + _impl_.maximum_phase_ = reinterpret_cast<::google::protobuf::Duration*>(p); + } + return _impl_.maximum_phase_; +} +inline ::google::protobuf::Duration* PROTOBUF_NONNULL ExperimentPolicy::mutable_maximum_phase() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + ::google::protobuf::Duration* _msg = _internal_mutable_maximum_phase(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.ExperimentPolicy.maximum_phase) + return _msg; +} +inline void ExperimentPolicy::set_allocated_maximum_phase(::google::protobuf::Duration* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.maximum_phase_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + } + + _impl_.maximum_phase_ = reinterpret_cast<::google::protobuf::Duration*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.ExperimentPolicy.maximum_phase) +} + +// uint64 minimum_sockets_per_host = 3 [json_name = "minimumSocketsPerHost", (.buf.validate.field) = { +inline void ExperimentPolicy::clear_minimum_sockets_per_host() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.minimum_sockets_per_host_ = ::uint64_t{0u}; + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); +} +inline ::uint64_t ExperimentPolicy::minimum_sockets_per_host() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ExperimentPolicy.minimum_sockets_per_host) + return _internal_minimum_sockets_per_host(); +} +inline void ExperimentPolicy::set_minimum_sockets_per_host(::uint64_t value) { + _internal_set_minimum_sockets_per_host(value); + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.ExperimentPolicy.minimum_sockets_per_host) +} +inline ::uint64_t ExperimentPolicy::_internal_minimum_sockets_per_host() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.minimum_sockets_per_host_; +} +inline void ExperimentPolicy::_internal_set_minimum_sockets_per_host(::uint64_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.minimum_sockets_per_host_ = value; +} + +// uint64 minimum_bytes_per_host = 4 [json_name = "minimumBytesPerHost", (.buf.validate.field) = { +inline void ExperimentPolicy::clear_minimum_bytes_per_host() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.minimum_bytes_per_host_ = ::uint64_t{0u}; + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); +} +inline ::uint64_t ExperimentPolicy::minimum_bytes_per_host() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ExperimentPolicy.minimum_bytes_per_host) + return _internal_minimum_bytes_per_host(); +} +inline void ExperimentPolicy::set_minimum_bytes_per_host(::uint64_t value) { + _internal_set_minimum_bytes_per_host(value); + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.ExperimentPolicy.minimum_bytes_per_host) +} +inline ::uint64_t ExperimentPolicy::_internal_minimum_bytes_per_host() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.minimum_bytes_per_host_; +} +inline void ExperimentPolicy::_internal_set_minimum_bytes_per_host(::uint64_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.minimum_bytes_per_host_ = value; +} + +// uint32 minimum_treated_hosts = 5 [json_name = "minimumTreatedHosts", (.buf.validate.field) = { +inline void ExperimentPolicy::clear_minimum_treated_hosts() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.minimum_treated_hosts_ = 0u; + ClearHasBit(_impl_._has_bits_[0], 0x00000100U); +} +inline ::uint32_t ExperimentPolicy::minimum_treated_hosts() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ExperimentPolicy.minimum_treated_hosts) + return _internal_minimum_treated_hosts(); +} +inline void ExperimentPolicy::set_minimum_treated_hosts(::uint32_t value) { + _internal_set_minimum_treated_hosts(value); + SetHasBit(_impl_._has_bits_[0], 0x00000100U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.ExperimentPolicy.minimum_treated_hosts) +} +inline ::uint32_t ExperimentPolicy::_internal_minimum_treated_hosts() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.minimum_treated_hosts_; +} +inline void ExperimentPolicy::_internal_set_minimum_treated_hosts(::uint32_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.minimum_treated_hosts_ = value; +} + +// double minimum_improvement = 6 [json_name = "minimumImprovement", (.buf.validate.field) = { +inline void ExperimentPolicy::clear_minimum_improvement() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.minimum_improvement_ = 0; + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); +} +inline double ExperimentPolicy::minimum_improvement() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ExperimentPolicy.minimum_improvement) + return _internal_minimum_improvement(); +} +inline void ExperimentPolicy::set_minimum_improvement(double value) { + _internal_set_minimum_improvement(value); + SetHasBit(_impl_._has_bits_[0], 0x00000020U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.ExperimentPolicy.minimum_improvement) +} +inline double ExperimentPolicy::_internal_minimum_improvement() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.minimum_improvement_; +} +inline void ExperimentPolicy::_internal_set_minimum_improvement(double value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.minimum_improvement_ = value; +} + +// double maximum_retransmission_increase = 7 [json_name = "maximumRetransmissionIncrease", (.buf.validate.field) = { +inline void ExperimentPolicy::clear_maximum_retransmission_increase() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.maximum_retransmission_increase_ = 0; + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); +} +inline double ExperimentPolicy::maximum_retransmission_increase() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ExperimentPolicy.maximum_retransmission_increase) + return _internal_maximum_retransmission_increase(); +} +inline void ExperimentPolicy::set_maximum_retransmission_increase(double value) { + _internal_set_maximum_retransmission_increase(value); + SetHasBit(_impl_._has_bits_[0], 0x00000040U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.ExperimentPolicy.maximum_retransmission_increase) +} +inline double ExperimentPolicy::_internal_maximum_retransmission_increase() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.maximum_retransmission_increase_; +} +inline void ExperimentPolicy::_internal_set_maximum_retransmission_increase(double value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.maximum_retransmission_increase_ = value; +} + +// double maximum_rtt_increase = 8 [json_name = "maximumRttIncrease", (.buf.validate.field) = { +inline void ExperimentPolicy::clear_maximum_rtt_increase() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.maximum_rtt_increase_ = 0; + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); +} +inline double ExperimentPolicy::maximum_rtt_increase() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ExperimentPolicy.maximum_rtt_increase) + return _internal_maximum_rtt_increase(); +} +inline void ExperimentPolicy::set_maximum_rtt_increase(double value) { + _internal_set_maximum_rtt_increase(value); + SetHasBit(_impl_._has_bits_[0], 0x00000080U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.ExperimentPolicy.maximum_rtt_increase) +} +inline double ExperimentPolicy::_internal_maximum_rtt_increase() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.maximum_rtt_increase_; +} +inline void ExperimentPolicy::_internal_set_maximum_rtt_increase(double value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.maximum_rtt_increase_ = value; +} + +// .google.protobuf.Duration maximum_evidence_age = 9 [json_name = "maximumEvidenceAge", (.buf.validate.field) = { +inline bool ExperimentPolicy::has_maximum_evidence_age() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000004U); + PROTOBUF_ASSUME(!value || _impl_.maximum_evidence_age_ != nullptr); + return value; +} +inline const ::google::protobuf::Duration& ExperimentPolicy::_internal_maximum_evidence_age() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::google::protobuf::Duration* p = _impl_.maximum_evidence_age_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::google::protobuf::Duration>(&::google::protobuf::Duration_globals_); +} +inline const ::google::protobuf::Duration& ExperimentPolicy::maximum_evidence_age() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.ExperimentPolicy.maximum_evidence_age) + return _internal_maximum_evidence_age(); +} +inline void ExperimentPolicy::unsafe_arena_set_allocated_maximum_evidence_age( + ::google::protobuf::Duration* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.maximum_evidence_age_); + } + _impl_.maximum_evidence_age_ = reinterpret_cast<::google::protobuf::Duration*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.ExperimentPolicy.maximum_evidence_age) +} +inline ::google::protobuf::Duration* PROTOBUF_NULLABLE ExperimentPolicy::release_maximum_evidence_age() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + ::google::protobuf::Duration* released = _impl_.maximum_evidence_age_; + _impl_.maximum_evidence_age_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::google::protobuf::Duration* PROTOBUF_NULLABLE ExperimentPolicy::unsafe_arena_release_maximum_evidence_age() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.ExperimentPolicy.maximum_evidence_age) + + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + ::google::protobuf::Duration* temp = _impl_.maximum_evidence_age_; + _impl_.maximum_evidence_age_ = nullptr; + return temp; +} +inline ::google::protobuf::Duration* PROTOBUF_NONNULL ExperimentPolicy::_internal_mutable_maximum_evidence_age() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.maximum_evidence_age_ == nullptr) { + auto* p = Super_::DefaultConstruct<::google::protobuf::Duration>(GetArena()); + _impl_.maximum_evidence_age_ = reinterpret_cast<::google::protobuf::Duration*>(p); + } + return _impl_.maximum_evidence_age_; +} +inline ::google::protobuf::Duration* PROTOBUF_NONNULL ExperimentPolicy::mutable_maximum_evidence_age() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + ::google::protobuf::Duration* _msg = _internal_mutable_maximum_evidence_age(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.ExperimentPolicy.maximum_evidence_age) + return _msg; +} +inline void ExperimentPolicy::set_allocated_maximum_evidence_age(::google::protobuf::Duration* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.maximum_evidence_age_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + } + + _impl_.maximum_evidence_age_ = reinterpret_cast<::google::protobuf::Duration*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.ExperimentPolicy.maximum_evidence_age) +} + +// ------------------------------------------------------------------- + +// Phase + +// uint32 target_hosts = 1 [json_name = "targetHosts", (.buf.validate.field) = { +inline void Phase::clear_target_hosts() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.target_hosts_ = 0u; + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); +} +inline ::uint32_t Phase::target_hosts() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Phase.target_hosts) + return _internal_target_hosts(); +} +inline void Phase::set_target_hosts(::uint32_t value) { + _internal_set_target_hosts(value); + SetHasBit(_impl_._has_bits_[0], 0x00000080U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Phase.target_hosts) +} +inline ::uint32_t Phase::_internal_target_hosts() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.target_hosts_; +} +inline void Phase::_internal_set_target_hosts(::uint32_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.target_hosts_ = value; +} + +// .google.protobuf.Timestamp issued_at = 2 [json_name = "issuedAt"]; +inline bool Phase::has_issued_at() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000008U); + PROTOBUF_ASSUME(!value || _impl_.issued_at_ != nullptr); + return value; +} +inline const ::google::protobuf::Timestamp& Phase::_internal_issued_at() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::google::protobuf::Timestamp* p = _impl_.issued_at_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::google::protobuf::Timestamp>(&::google::protobuf::Timestamp_globals_); +} +inline const ::google::protobuf::Timestamp& Phase::issued_at() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Phase.issued_at) + return _internal_issued_at(); +} +inline void Phase::unsafe_arena_set_allocated_issued_at( + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.issued_at_); + } + _impl_.issued_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.Phase.issued_at) +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Phase::release_issued_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + ::google::protobuf::Timestamp* released = _impl_.issued_at_; + _impl_.issued_at_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Phase::unsafe_arena_release_issued_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Phase.issued_at) + + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + ::google::protobuf::Timestamp* temp = _impl_.issued_at_; + _impl_.issued_at_ = nullptr; + return temp; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Phase::_internal_mutable_issued_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.issued_at_ == nullptr) { + auto* p = Super_::DefaultConstruct<::google::protobuf::Timestamp>(GetArena()); + _impl_.issued_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(p); + } + return _impl_.issued_at_; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Phase::mutable_issued_at() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + ::google::protobuf::Timestamp* _msg = _internal_mutable_issued_at(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Phase.issued_at) + return _msg; +} +inline void Phase::set_allocated_issued_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.issued_at_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + } + + _impl_.issued_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Phase.issued_at) +} + +// .google.protobuf.Timestamp observation_started_at = 3 [json_name = "observationStartedAt"]; +inline bool Phase::has_observation_started_at() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000010U); + PROTOBUF_ASSUME(!value || _impl_.observation_started_at_ != nullptr); + return value; +} +inline const ::google::protobuf::Timestamp& Phase::_internal_observation_started_at() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::google::protobuf::Timestamp* p = _impl_.observation_started_at_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::google::protobuf::Timestamp>(&::google::protobuf::Timestamp_globals_); +} +inline const ::google::protobuf::Timestamp& Phase::observation_started_at() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Phase.observation_started_at) + return _internal_observation_started_at(); +} +inline void Phase::unsafe_arena_set_allocated_observation_started_at( + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.observation_started_at_); + } + _impl_.observation_started_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.Phase.observation_started_at) +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Phase::release_observation_started_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + ::google::protobuf::Timestamp* released = _impl_.observation_started_at_; + _impl_.observation_started_at_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Phase::unsafe_arena_release_observation_started_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Phase.observation_started_at) + + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + ::google::protobuf::Timestamp* temp = _impl_.observation_started_at_; + _impl_.observation_started_at_ = nullptr; + return temp; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Phase::_internal_mutable_observation_started_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.observation_started_at_ == nullptr) { + auto* p = Super_::DefaultConstruct<::google::protobuf::Timestamp>(GetArena()); + _impl_.observation_started_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(p); + } + return _impl_.observation_started_at_; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Phase::mutable_observation_started_at() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + ::google::protobuf::Timestamp* _msg = _internal_mutable_observation_started_at(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Phase.observation_started_at) + return _msg; +} +inline void Phase::set_allocated_observation_started_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.observation_started_at_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + } + + _impl_.observation_started_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Phase.observation_started_at) +} + +// .google.protobuf.Timestamp evaluated_at = 4 [json_name = "evaluatedAt"]; +inline bool Phase::has_evaluated_at() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000020U); + PROTOBUF_ASSUME(!value || _impl_.evaluated_at_ != nullptr); + return value; +} +inline const ::google::protobuf::Timestamp& Phase::_internal_evaluated_at() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::google::protobuf::Timestamp* p = _impl_.evaluated_at_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::google::protobuf::Timestamp>(&::google::protobuf::Timestamp_globals_); +} +inline const ::google::protobuf::Timestamp& Phase::evaluated_at() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Phase.evaluated_at) + return _internal_evaluated_at(); +} +inline void Phase::unsafe_arena_set_allocated_evaluated_at( + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.evaluated_at_); + } + _impl_.evaluated_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000020U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.Phase.evaluated_at) +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Phase::release_evaluated_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + ::google::protobuf::Timestamp* released = _impl_.evaluated_at_; + _impl_.evaluated_at_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Phase::unsafe_arena_release_evaluated_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Phase.evaluated_at) + + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + ::google::protobuf::Timestamp* temp = _impl_.evaluated_at_; + _impl_.evaluated_at_ = nullptr; + return temp; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Phase::_internal_mutable_evaluated_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.evaluated_at_ == nullptr) { + auto* p = Super_::DefaultConstruct<::google::protobuf::Timestamp>(GetArena()); + _impl_.evaluated_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(p); + } + return _impl_.evaluated_at_; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Phase::mutable_evaluated_at() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000020U); + ::google::protobuf::Timestamp* _msg = _internal_mutable_evaluated_at(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Phase.evaluated_at) + return _msg; +} +inline void Phase::set_allocated_evaluated_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.evaluated_at_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000020U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + } + + _impl_.evaluated_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Phase.evaluated_at) +} + +// .xtcp_treatment.v1.Evidence evidence = 5 [json_name = "evidence"]; +inline bool Phase::has_evidence() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000040U); + PROTOBUF_ASSUME(!value || _impl_.evidence_ != nullptr); + return value; +} +inline void Phase::clear_evidence() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.evidence_ != nullptr) _impl_.evidence_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); +} +inline const ::xtcp_treatment::v1::Evidence& Phase::_internal_evidence() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::xtcp_treatment::v1::Evidence* p = _impl_.evidence_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::xtcp_treatment::v1::Evidence>(&::xtcp_treatment::v1::Evidence_globals_); +} +inline const ::xtcp_treatment::v1::Evidence& Phase::evidence() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Phase.evidence) + return _internal_evidence(); +} +inline void Phase::unsafe_arena_set_allocated_evidence( + ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.evidence_); + } + _impl_.evidence_ = reinterpret_cast<::xtcp_treatment::v1::Evidence*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000040U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.Phase.evidence) +} +inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE Phase::release_evidence() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + ::xtcp_treatment::v1::Evidence* released = _impl_.evidence_; + _impl_.evidence_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE Phase::unsafe_arena_release_evidence() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Phase.evidence) + + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + ::xtcp_treatment::v1::Evidence* temp = _impl_.evidence_; + _impl_.evidence_ = nullptr; + return temp; +} +inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NONNULL Phase::_internal_mutable_evidence() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.evidence_ == nullptr) { + auto* p = Super_::DefaultConstruct<::xtcp_treatment::v1::Evidence>(GetArena()); + _impl_.evidence_ = reinterpret_cast<::xtcp_treatment::v1::Evidence*>(p); + } + return _impl_.evidence_; +} +inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NONNULL Phase::mutable_evidence() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000040U); + ::xtcp_treatment::v1::Evidence* _msg = _internal_mutable_evidence(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Phase.evidence) + return _msg; +} +inline void Phase::set_allocated_evidence(::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.evidence_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = value->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000040U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + } + + _impl_.evidence_ = reinterpret_cast<::xtcp_treatment::v1::Evidence*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Phase.evidence) +} + +// string outcome = 6 [json_name = "outcome", (.buf.validate.field) = { +inline void Phase::clear_outcome() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.outcome_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +inline const ::std::string& Phase::outcome() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Phase.outcome) + return _internal_outcome(); +} +template +PROTOBUF_ALWAYS_INLINE void Phase::set_outcome(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + _impl_.outcome_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Phase.outcome) +} +inline ::std::string* PROTOBUF_NONNULL Phase::mutable_outcome() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + ::std::string* _s = _internal_mutable_outcome(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Phase.outcome) + return _s; +} +inline const ::std::string& Phase::_internal_outcome() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.outcome_.Get(); +} +inline void Phase::_internal_set_outcome(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.outcome_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL Phase::_internal_mutable_outcome() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.outcome_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE Phase::release_outcome() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Phase.outcome) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000001U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + auto* released = _impl_.outcome_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.outcome_.Set("", GetArena()); + } + return released; +} +inline void Phase::set_allocated_outcome(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + } + _impl_.outcome_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.outcome_.IsDefault()) { + _impl_.outcome_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Phase.outcome) +} + +// string reviewer = 7 [json_name = "reviewer", (.buf.validate.field) = { +inline void Phase::clear_reviewer() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.reviewer_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); +} +inline const ::std::string& Phase::reviewer() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Phase.reviewer) + return _internal_reviewer(); +} +template +PROTOBUF_ALWAYS_INLINE void Phase::set_reviewer(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + _impl_.reviewer_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Phase.reviewer) +} +inline ::std::string* PROTOBUF_NONNULL Phase::mutable_reviewer() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + ::std::string* _s = _internal_mutable_reviewer(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Phase.reviewer) + return _s; +} +inline const ::std::string& Phase::_internal_reviewer() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.reviewer_.Get(); +} +inline void Phase::_internal_set_reviewer(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.reviewer_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL Phase::_internal_mutable_reviewer() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.reviewer_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE Phase::release_reviewer() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Phase.reviewer) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000002U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + auto* released = _impl_.reviewer_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.reviewer_.Set("", GetArena()); + } + return released; +} +inline void Phase::set_allocated_reviewer(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + } + _impl_.reviewer_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.reviewer_.IsDefault()) { + _impl_.reviewer_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Phase.reviewer) +} + +// string reason = 8 [json_name = "reason", (.buf.validate.field) = { +inline void Phase::clear_reason() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.reason_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); +} +inline const ::std::string& Phase::reason() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Phase.reason) + return _internal_reason(); +} +template +PROTOBUF_ALWAYS_INLINE void Phase::set_reason(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + _impl_.reason_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Phase.reason) +} +inline ::std::string* PROTOBUF_NONNULL Phase::mutable_reason() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + ::std::string* _s = _internal_mutable_reason(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Phase.reason) + return _s; +} +inline const ::std::string& Phase::_internal_reason() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.reason_.Get(); +} +inline void Phase::_internal_set_reason(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.reason_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL Phase::_internal_mutable_reason() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.reason_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE Phase::release_reason() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Phase.reason) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000004U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + auto* released = _impl_.reason_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.reason_.Set("", GetArena()); + } + return released; +} +inline void Phase::set_allocated_reason(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + } + _impl_.reason_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.reason_.IsDefault()) { + _impl_.reason_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Phase.reason) +} + +// ------------------------------------------------------------------- + +// Experiment + +// string id = 1 [json_name = "id", (.buf.validate.field) = { +inline void Experiment::clear_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.id_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); +} +inline const ::std::string& Experiment::id() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Experiment.id) + return _internal_id(); +} +template +PROTOBUF_ALWAYS_INLINE void Experiment::set_id(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + _impl_.id_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Experiment.id) +} +inline ::std::string* PROTOBUF_NONNULL Experiment::mutable_id() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + ::std::string* _s = _internal_mutable_id(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Experiment.id) + return _s; +} +inline const ::std::string& Experiment::_internal_id() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.id_.Get(); +} +inline void Experiment::_internal_set_id(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.id_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL Experiment::_internal_mutable_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.id_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE Experiment::release_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Experiment.id) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000004U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + auto* released = _impl_.id_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.id_.Set("", GetArena()); + } + return released; +} +inline void Experiment::set_allocated_id(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + } + _impl_.id_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.id_.IsDefault()) { + _impl_.id_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Experiment.id) +} + +// string location = 2 [json_name = "location", (.buf.validate.field) = { +inline void Experiment::clear_location() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.location_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); +} +inline const ::std::string& Experiment::location() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Experiment.location) + return _internal_location(); +} +template +PROTOBUF_ALWAYS_INLINE void Experiment::set_location(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + _impl_.location_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Experiment.location) +} +inline ::std::string* PROTOBUF_NONNULL Experiment::mutable_location() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + ::std::string* _s = _internal_mutable_location(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Experiment.location) + return _s; +} +inline const ::std::string& Experiment::_internal_location() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.location_.Get(); +} +inline void Experiment::_internal_set_location(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.location_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL Experiment::_internal_mutable_location() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.location_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE Experiment::release_location() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Experiment.location) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000008U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + auto* released = _impl_.location_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.location_.Set("", GetArena()); + } + return released; +} +inline void Experiment::set_allocated_location(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + } + _impl_.location_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.location_.IsDefault()) { + _impl_.location_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Experiment.location) +} + +// string hypothesis = 3 [json_name = "hypothesis", (.buf.validate.field) = { +inline void Experiment::clear_hypothesis() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.hypothesis_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); +} +inline const ::std::string& Experiment::hypothesis() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Experiment.hypothesis) + return _internal_hypothesis(); +} +template +PROTOBUF_ALWAYS_INLINE void Experiment::set_hypothesis(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + _impl_.hypothesis_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Experiment.hypothesis) +} +inline ::std::string* PROTOBUF_NONNULL Experiment::mutable_hypothesis() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + ::std::string* _s = _internal_mutable_hypothesis(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Experiment.hypothesis) + return _s; +} +inline const ::std::string& Experiment::_internal_hypothesis() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.hypothesis_.Get(); +} +inline void Experiment::_internal_set_hypothesis(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.hypothesis_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL Experiment::_internal_mutable_hypothesis() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.hypothesis_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE Experiment::release_hypothesis() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Experiment.hypothesis) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000010U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + auto* released = _impl_.hypothesis_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.hypothesis_.Set("", GetArena()); + } + return released; +} +inline void Experiment::set_allocated_hypothesis(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + } + _impl_.hypothesis_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.hypothesis_.IsDefault()) { + _impl_.hypothesis_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Experiment.hypothesis) +} + +// .xtcp_treatment.v1.Treatment treatment = 4 [json_name = "treatment", (.buf.validate.field) = { +inline bool Experiment::has_treatment() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000040U); + PROTOBUF_ASSUME(!value || _impl_.treatment_ != nullptr); + return value; +} +inline void Experiment::clear_treatment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.treatment_ != nullptr) _impl_.treatment_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); +} +inline const ::xtcp_treatment::v1::Treatment& Experiment::_internal_treatment() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::xtcp_treatment::v1::Treatment* p = _impl_.treatment_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::xtcp_treatment::v1::Treatment>(&::xtcp_treatment::v1::Treatment_globals_); +} +inline const ::xtcp_treatment::v1::Treatment& Experiment::treatment() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Experiment.treatment) + return _internal_treatment(); +} +inline void Experiment::unsafe_arena_set_allocated_treatment( + ::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.treatment_); + } + _impl_.treatment_ = reinterpret_cast<::xtcp_treatment::v1::Treatment*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000040U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.Experiment.treatment) +} +inline ::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE Experiment::release_treatment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + ::xtcp_treatment::v1::Treatment* released = _impl_.treatment_; + _impl_.treatment_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE Experiment::unsafe_arena_release_treatment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Experiment.treatment) + + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + ::xtcp_treatment::v1::Treatment* temp = _impl_.treatment_; + _impl_.treatment_ = nullptr; + return temp; +} +inline ::xtcp_treatment::v1::Treatment* PROTOBUF_NONNULL Experiment::_internal_mutable_treatment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.treatment_ == nullptr) { + auto* p = Super_::DefaultConstruct<::xtcp_treatment::v1::Treatment>(GetArena()); + _impl_.treatment_ = reinterpret_cast<::xtcp_treatment::v1::Treatment*>(p); + } + return _impl_.treatment_; +} +inline ::xtcp_treatment::v1::Treatment* PROTOBUF_NONNULL Experiment::mutable_treatment() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000040U); + ::xtcp_treatment::v1::Treatment* _msg = _internal_mutable_treatment(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Experiment.treatment) + return _msg; +} +inline void Experiment::set_allocated_treatment(::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.treatment_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = value->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000040U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + } + + _impl_.treatment_ = reinterpret_cast<::xtcp_treatment::v1::Treatment*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Experiment.treatment) +} + +// .xtcp_treatment.v1.ExperimentPolicy policy = 5 [json_name = "policy", (.buf.validate.field) = { +inline bool Experiment::has_policy() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000080U); + PROTOBUF_ASSUME(!value || _impl_.policy_ != nullptr); + return value; +} +inline void Experiment::clear_policy() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.policy_ != nullptr) _impl_.policy_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); +} +inline const ::xtcp_treatment::v1::ExperimentPolicy& Experiment::_internal_policy() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::xtcp_treatment::v1::ExperimentPolicy* p = _impl_.policy_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::xtcp_treatment::v1::ExperimentPolicy>(&::xtcp_treatment::v1::ExperimentPolicy_globals_); +} +inline const ::xtcp_treatment::v1::ExperimentPolicy& Experiment::policy() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Experiment.policy) + return _internal_policy(); +} +inline void Experiment::unsafe_arena_set_allocated_policy( + ::xtcp_treatment::v1::ExperimentPolicy* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.policy_); + } + _impl_.policy_ = reinterpret_cast<::xtcp_treatment::v1::ExperimentPolicy*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000080U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.Experiment.policy) +} +inline ::xtcp_treatment::v1::ExperimentPolicy* PROTOBUF_NULLABLE Experiment::release_policy() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); + ::xtcp_treatment::v1::ExperimentPolicy* released = _impl_.policy_; + _impl_.policy_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::xtcp_treatment::v1::ExperimentPolicy* PROTOBUF_NULLABLE Experiment::unsafe_arena_release_policy() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Experiment.policy) + + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); + ::xtcp_treatment::v1::ExperimentPolicy* temp = _impl_.policy_; + _impl_.policy_ = nullptr; + return temp; +} +inline ::xtcp_treatment::v1::ExperimentPolicy* PROTOBUF_NONNULL Experiment::_internal_mutable_policy() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.policy_ == nullptr) { + auto* p = Super_::DefaultConstruct<::xtcp_treatment::v1::ExperimentPolicy>(GetArena()); + _impl_.policy_ = reinterpret_cast<::xtcp_treatment::v1::ExperimentPolicy*>(p); + } + return _impl_.policy_; +} +inline ::xtcp_treatment::v1::ExperimentPolicy* PROTOBUF_NONNULL Experiment::mutable_policy() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000080U); + ::xtcp_treatment::v1::ExperimentPolicy* _msg = _internal_mutable_policy(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Experiment.policy) + return _msg; +} +inline void Experiment::set_allocated_policy(::xtcp_treatment::v1::ExperimentPolicy* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.policy_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = value->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000080U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); + } + + _impl_.policy_ = reinterpret_cast<::xtcp_treatment::v1::ExperimentPolicy*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Experiment.policy) +} + +// .xtcp_treatment.v1.Evidence baseline = 6 [json_name = "baseline", (.buf.validate.field) = { +inline bool Experiment::has_baseline() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000100U); + PROTOBUF_ASSUME(!value || _impl_.baseline_ != nullptr); + return value; +} +inline void Experiment::clear_baseline() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.baseline_ != nullptr) _impl_.baseline_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000100U); +} +inline const ::xtcp_treatment::v1::Evidence& Experiment::_internal_baseline() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::xtcp_treatment::v1::Evidence* p = _impl_.baseline_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::xtcp_treatment::v1::Evidence>(&::xtcp_treatment::v1::Evidence_globals_); +} +inline const ::xtcp_treatment::v1::Evidence& Experiment::baseline() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Experiment.baseline) + return _internal_baseline(); +} +inline void Experiment::unsafe_arena_set_allocated_baseline( + ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.baseline_); + } + _impl_.baseline_ = reinterpret_cast<::xtcp_treatment::v1::Evidence*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000100U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000100U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.Experiment.baseline) +} +inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE Experiment::release_baseline() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000100U); + ::xtcp_treatment::v1::Evidence* released = _impl_.baseline_; + _impl_.baseline_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE Experiment::unsafe_arena_release_baseline() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Experiment.baseline) + + ClearHasBit(_impl_._has_bits_[0], 0x00000100U); + ::xtcp_treatment::v1::Evidence* temp = _impl_.baseline_; + _impl_.baseline_ = nullptr; + return temp; +} +inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NONNULL Experiment::_internal_mutable_baseline() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.baseline_ == nullptr) { + auto* p = Super_::DefaultConstruct<::xtcp_treatment::v1::Evidence>(GetArena()); + _impl_.baseline_ = reinterpret_cast<::xtcp_treatment::v1::Evidence*>(p); + } + return _impl_.baseline_; +} +inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NONNULL Experiment::mutable_baseline() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000100U); + ::xtcp_treatment::v1::Evidence* _msg = _internal_mutable_baseline(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Experiment.baseline) + return _msg; +} +inline void Experiment::set_allocated_baseline(::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.baseline_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = value->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000100U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000100U); + } + + _impl_.baseline_ = reinterpret_cast<::xtcp_treatment::v1::Evidence*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Experiment.baseline) +} + +// repeated string cohort_order = 7 [json_name = "cohortOrder", (.buf.validate.field) = { +inline int Experiment::_internal_cohort_order_size() const { + return _internal_cohort_order().size(); +} +inline int Experiment::cohort_order_size() const { + return _internal_cohort_order_size(); +} +inline void Experiment::clear_cohort_order() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.cohort_order_.Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +inline ::std::string* PROTOBUF_NONNULL Experiment::add_cohort_order() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + ::google::protobuf::internal::TSanWrite(&_impl_); + ::std::string* _s = + _internal_mutable_cohort_order()->InternalAddWithArena( + ::google::protobuf::MessageLite::internal_visibility(), GetArena()); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + // @@protoc_insertion_point(field_add_mutable:xtcp_treatment.v1.Experiment.cohort_order) + return _s; +} +inline const ::std::string& Experiment::cohort_order(int index) const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Experiment.cohort_order) + return _internal_cohort_order().Get(index); +} +inline ::std::string* PROTOBUF_NONNULL Experiment::mutable_cohort_order(int index) + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Experiment.cohort_order) + return _internal_mutable_cohort_order()->Mutable(index); +} +template +inline void Experiment::set_cohort_order(int index, Arg_&& value, Args_... args) { + ::google::protobuf::internal::AssignToString( + *_internal_mutable_cohort_order()->Mutable(index), + ::std::forward(value), args... ); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Experiment.cohort_order) +} +template +inline void Experiment::add_cohort_order(Arg_&& value, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + ::google::protobuf::internal::AddToRepeatedPtrField( + ::google::protobuf::MessageLite::internal_visibility(), GetArena(), + *_internal_mutable_cohort_order(), ::std::forward(value), + args... ); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + // @@protoc_insertion_point(field_add:xtcp_treatment.v1.Experiment.cohort_order) +} +inline const ::google::protobuf::RepeatedPtrField<::std::string>& Experiment::cohort_order() + const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_list:xtcp_treatment.v1.Experiment.cohort_order) + return _internal_cohort_order(); +} +inline ::google::protobuf::RepeatedPtrField<::std::string>* PROTOBUF_NONNULL +Experiment::mutable_cohort_order() ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + // @@protoc_insertion_point(field_mutable_list:xtcp_treatment.v1.Experiment.cohort_order) + ::google::protobuf::internal::TSanWrite(&_impl_); + return _internal_mutable_cohort_order(); +} +inline const ::google::protobuf::RepeatedPtrField<::std::string>& +Experiment::_internal_cohort_order() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.cohort_order_; +} +inline ::google::protobuf::RepeatedPtrField<::std::string>* PROTOBUF_NONNULL +Experiment::_internal_mutable_cohort_order() { + ::google::protobuf::internal::TSanRead(&_impl_); + return &_impl_.cohort_order_; +} + +// repeated .xtcp_treatment.v1.Phase phases = 8 [json_name = "phases", (.buf.validate.field) = { +inline int Experiment::_internal_phases_size() const { + return _internal_phases().size(); +} +inline int Experiment::phases_size() const { + return _internal_phases_size(); +} +inline void Experiment::clear_phases() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.phases_.Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); +} +inline const ::xtcp_treatment::v1::Phase& Experiment::phases(int index) const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Experiment.phases) + return _internal_phases().Get(index); +} +inline ::xtcp_treatment::v1::Phase* PROTOBUF_NONNULL Experiment::mutable_phases(int index) + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Experiment.phases) + return _internal_mutable_phases()->Mutable(index); +} +inline ::xtcp_treatment::v1::Phase* PROTOBUF_NONNULL Experiment::add_phases() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + ::google::protobuf::internal::TSanWrite(&_impl_); + ::xtcp_treatment::v1::Phase* _add = + _internal_mutable_phases()->InternalAddWithArena( + ::google::protobuf::MessageLite::internal_visibility(), GetArena()); + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + // @@protoc_insertion_point(field_add:xtcp_treatment.v1.Experiment.phases) + return _add; +} +inline const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::Phase>& Experiment::phases() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_list:xtcp_treatment.v1.Experiment.phases) + return _internal_phases(); +} +inline ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::Phase>* PROTOBUF_NONNULL +Experiment::mutable_phases() ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + // @@protoc_insertion_point(field_mutable_list:xtcp_treatment.v1.Experiment.phases) + ::google::protobuf::internal::TSanWrite(&_impl_); + return _internal_mutable_phases(); +} +inline const ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::Phase>& +Experiment::_internal_phases() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.phases_; +} +inline ::google::protobuf::RepeatedPtrField<::xtcp_treatment::v1::Phase>* PROTOBUF_NONNULL +Experiment::_internal_mutable_phases() { + ::google::protobuf::internal::TSanRead(&_impl_); + return &_impl_.phases_; +} + +// .xtcp_treatment.v1.ExperimentStatus status = 9 [json_name = "status", (.buf.validate.field) = { +inline void Experiment::clear_status() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.status_ = 0; + ClearHasBit(_impl_._has_bits_[0], 0x00001000U); +} +inline ::xtcp_treatment::v1::ExperimentStatus Experiment::status() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Experiment.status) + return _internal_status(); +} +inline void Experiment::set_status(::xtcp_treatment::v1::ExperimentStatus value) { + _internal_set_status(value); + SetHasBit(_impl_._has_bits_[0], 0x00001000U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Experiment.status) +} +inline ::xtcp_treatment::v1::ExperimentStatus Experiment::_internal_status() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return static_cast<::xtcp_treatment::v1::ExperimentStatus>(_impl_.status_); +} +inline void Experiment::_internal_set_status(::xtcp_treatment::v1::ExperimentStatus value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.status_ = value; +} + +// uint64 revision = 10 [json_name = "revision"]; +inline void Experiment::clear_revision() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.revision_ = ::uint64_t{0u}; + ClearHasBit(_impl_._has_bits_[0], 0x00000800U); +} +inline ::uint64_t Experiment::revision() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Experiment.revision) + return _internal_revision(); +} +inline void Experiment::set_revision(::uint64_t value) { + _internal_set_revision(value); + SetHasBit(_impl_._has_bits_[0], 0x00000800U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Experiment.revision) +} +inline ::uint64_t Experiment::_internal_revision() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.revision_; +} +inline void Experiment::_internal_set_revision(::uint64_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.revision_ = value; +} + +// .google.protobuf.Timestamp proposed_at = 11 [json_name = "proposedAt"]; +inline bool Experiment::has_proposed_at() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000200U); + PROTOBUF_ASSUME(!value || _impl_.proposed_at_ != nullptr); + return value; +} +inline const ::google::protobuf::Timestamp& Experiment::_internal_proposed_at() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::google::protobuf::Timestamp* p = _impl_.proposed_at_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::google::protobuf::Timestamp>(&::google::protobuf::Timestamp_globals_); +} +inline const ::google::protobuf::Timestamp& Experiment::proposed_at() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Experiment.proposed_at) + return _internal_proposed_at(); +} +inline void Experiment::unsafe_arena_set_allocated_proposed_at( + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.proposed_at_); + } + _impl_.proposed_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000200U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000200U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.Experiment.proposed_at) +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Experiment::release_proposed_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000200U); + ::google::protobuf::Timestamp* released = _impl_.proposed_at_; + _impl_.proposed_at_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Experiment::unsafe_arena_release_proposed_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Experiment.proposed_at) + + ClearHasBit(_impl_._has_bits_[0], 0x00000200U); + ::google::protobuf::Timestamp* temp = _impl_.proposed_at_; + _impl_.proposed_at_ = nullptr; + return temp; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Experiment::_internal_mutable_proposed_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.proposed_at_ == nullptr) { + auto* p = Super_::DefaultConstruct<::google::protobuf::Timestamp>(GetArena()); + _impl_.proposed_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(p); + } + return _impl_.proposed_at_; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Experiment::mutable_proposed_at() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000200U); + ::google::protobuf::Timestamp* _msg = _internal_mutable_proposed_at(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Experiment.proposed_at) + return _msg; +} +inline void Experiment::set_allocated_proposed_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.proposed_at_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000200U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000200U); + } + + _impl_.proposed_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Experiment.proposed_at) +} + +// .xtcp_treatment.v1.Evidence latest_evidence = 12 [json_name = "latestEvidence"]; +inline bool Experiment::has_latest_evidence() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000400U); + PROTOBUF_ASSUME(!value || _impl_.latest_evidence_ != nullptr); + return value; +} +inline void Experiment::clear_latest_evidence() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.latest_evidence_ != nullptr) _impl_.latest_evidence_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000400U); +} +inline const ::xtcp_treatment::v1::Evidence& Experiment::_internal_latest_evidence() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::xtcp_treatment::v1::Evidence* p = _impl_.latest_evidence_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::xtcp_treatment::v1::Evidence>(&::xtcp_treatment::v1::Evidence_globals_); +} +inline const ::xtcp_treatment::v1::Evidence& Experiment::latest_evidence() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Experiment.latest_evidence) + return _internal_latest_evidence(); +} +inline void Experiment::unsafe_arena_set_allocated_latest_evidence( + ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.latest_evidence_); + } + _impl_.latest_evidence_ = reinterpret_cast<::xtcp_treatment::v1::Evidence*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000400U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000400U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.Experiment.latest_evidence) +} +inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE Experiment::release_latest_evidence() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000400U); + ::xtcp_treatment::v1::Evidence* released = _impl_.latest_evidence_; + _impl_.latest_evidence_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE Experiment::unsafe_arena_release_latest_evidence() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Experiment.latest_evidence) + + ClearHasBit(_impl_._has_bits_[0], 0x00000400U); + ::xtcp_treatment::v1::Evidence* temp = _impl_.latest_evidence_; + _impl_.latest_evidence_ = nullptr; + return temp; +} +inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NONNULL Experiment::_internal_mutable_latest_evidence() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.latest_evidence_ == nullptr) { + auto* p = Super_::DefaultConstruct<::xtcp_treatment::v1::Evidence>(GetArena()); + _impl_.latest_evidence_ = reinterpret_cast<::xtcp_treatment::v1::Evidence*>(p); + } + return _impl_.latest_evidence_; +} +inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NONNULL Experiment::mutable_latest_evidence() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000400U); + ::xtcp_treatment::v1::Evidence* _msg = _internal_mutable_latest_evidence(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Experiment.latest_evidence) + return _msg; +} +inline void Experiment::set_allocated_latest_evidence(::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.latest_evidence_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = value->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000400U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000400U); + } + + _impl_.latest_evidence_ = reinterpret_cast<::xtcp_treatment::v1::Evidence*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Experiment.latest_evidence) +} + +// string latest_outcome = 13 [json_name = "latestOutcome", (.buf.validate.field) = { +inline void Experiment::clear_latest_outcome() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.latest_outcome_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); +} +inline const ::std::string& Experiment::latest_outcome() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Experiment.latest_outcome) + return _internal_latest_outcome(); +} +template +PROTOBUF_ALWAYS_INLINE void Experiment::set_latest_outcome(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000020U); + _impl_.latest_outcome_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Experiment.latest_outcome) +} +inline ::std::string* PROTOBUF_NONNULL Experiment::mutable_latest_outcome() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000020U); + ::std::string* _s = _internal_mutable_latest_outcome(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Experiment.latest_outcome) + return _s; +} +inline const ::std::string& Experiment::_internal_latest_outcome() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.latest_outcome_.Get(); +} +inline void Experiment::_internal_set_latest_outcome(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.latest_outcome_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL Experiment::_internal_mutable_latest_outcome() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.latest_outcome_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE Experiment::release_latest_outcome() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Experiment.latest_outcome) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000020U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + auto* released = _impl_.latest_outcome_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.latest_outcome_.Set("", GetArena()); + } + return released; +} +inline void Experiment::set_allocated_latest_outcome(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000020U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + } + _impl_.latest_outcome_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.latest_outcome_.IsDefault()) { + _impl_.latest_outcome_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Experiment.latest_outcome) +} + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif // __GNUC__ + +// @@protoc_insertion_point(namespace_scope) +} // namespace v1 +} // namespace xtcp_treatment + + +namespace google { +namespace protobuf { + +template <> +struct is_proto_enum<::xtcp_treatment::v1::Role> : std::true_type {}; +template <> +inline const EnumDescriptor* PROTOBUF_NONNULL GetEnumDescriptor<::xtcp_treatment::v1::Role>() { + return ::xtcp_treatment::v1::Role_descriptor(); +} +template <> +struct is_proto_enum<::xtcp_treatment::v1::AssignmentMode> : std::true_type {}; +template <> +inline const EnumDescriptor* PROTOBUF_NONNULL GetEnumDescriptor<::xtcp_treatment::v1::AssignmentMode>() { + return ::xtcp_treatment::v1::AssignmentMode_descriptor(); +} +template <> +struct is_proto_enum<::xtcp_treatment::v1::ApplyStatus> : std::true_type {}; +template <> +inline const EnumDescriptor* PROTOBUF_NONNULL GetEnumDescriptor<::xtcp_treatment::v1::ApplyStatus>() { + return ::xtcp_treatment::v1::ApplyStatus_descriptor(); +} +template <> +struct is_proto_enum<::xtcp_treatment::v1::ExperimentStatus> : std::true_type {}; +template <> +inline const EnumDescriptor* PROTOBUF_NONNULL GetEnumDescriptor<::xtcp_treatment::v1::ExperimentStatus>() { + return ::xtcp_treatment::v1::ExperimentStatus_descriptor(); +} + +} // namespace protobuf +} // namespace google + +// @@protoc_insertion_point(global_scope) + +#include "google/protobuf/port_undef.inc" +// clang-format on + +#endif // xtcp_5ftreatment_2fv1_2ftreatment_2eproto_2epb_2eh diff --git a/gen/dart/xtcp_treatment/v1/agent.pb.dart b/gen/dart/xtcp_treatment/v1/agent.pb.dart new file mode 100644 index 000000000..74de84ecd --- /dev/null +++ b/gen/dart/xtcp_treatment/v1/agent.pb.dart @@ -0,0 +1,466 @@ +// This is a generated file - do not edit. +// +// Generated from xtcp_treatment/v1/agent.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports + +import 'dart:core' as $core; + +import 'package:fixnum/fixnum.dart' as $fixnum; +import 'package:protobuf/protobuf.dart' as $pb; + +import 'treatment.pb.dart' as $1; + +export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions; + +class CheckInRequest extends $pb.GeneratedMessage { + factory CheckInRequest({ + $core.String? agentId, + $core.String? location, + $1.Capabilities? capabilities, + $fixnum.Int64? desiredRevision, + $core.Iterable<$1.ApplicationRecord>? records, + $core.bool? locallySuspended, + }) { + final result = CheckInRequest._(); + if (agentId != null) result.agentId = agentId; + if (location != null) result.location = location; + if (capabilities != null) result.capabilities = capabilities; + if (desiredRevision != null) result.desiredRevision = desiredRevision; + if (records != null) result.records.addAll(records); + if (locallySuspended != null) result.locallySuspended = locallySuspended; + return result; + } + + CheckInRequest._(); + + factory CheckInRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + CheckInRequest()..mergeFromBuffer(data, registry); + factory CheckInRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + CheckInRequest()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'CheckInRequest', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'xtcp_treatment.v1'), + createEmptyInstance: CheckInRequest.$_createMessage) + ..aOS(1, _omitFieldNames ? '' : 'agentId') + ..aOS(2, _omitFieldNames ? '' : 'location') + ..aOM<$1.Capabilities>(3, _omitFieldNames ? '' : 'capabilities', + subBuilder: $1.Capabilities.$_createMessage) + ..a<$fixnum.Int64>( + 4, _omitFieldNames ? '' : 'desiredRevision', $pb.PbFieldType.OU6, + defaultOrMaker: $fixnum.Int64.ZERO) + ..pPM<$1.ApplicationRecord>(5, _omitFieldNames ? '' : 'records', + subBuilder: $1.ApplicationRecord.$_createMessage) + ..aOB(6, _omitFieldNames ? '' : 'locallySuspended') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + CheckInRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + CheckInRequest copyWith(void Function(CheckInRequest) updates) => + super.copyWith((message) => updates(message as CheckInRequest)) + as CheckInRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated('Use CheckInRequest() / CheckInRequest.new instead') + static CheckInRequest create() => CheckInRequest._(); + static $pb.GeneratedMessage $_createMessage() => CheckInRequest._(); + @$core.override + CheckInRequest createEmptyInstance() => CheckInRequest._(); + @$core.pragma('dart2js:noInline') + static CheckInRequest getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor( + CheckInRequest.$_createMessage); + static CheckInRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get agentId => $_getSZ(0); + @$pb.TagNumber(1) + set agentId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasAgentId() => $_has(0); + @$pb.TagNumber(1) + void clearAgentId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get location => $_getSZ(1); + @$pb.TagNumber(2) + set location($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasLocation() => $_has(1); + @$pb.TagNumber(2) + void clearLocation() => $_clearField(2); + + @$pb.TagNumber(3) + $1.Capabilities get capabilities => $_getN(2); + @$pb.TagNumber(3) + set capabilities($1.Capabilities value) => $_setField(3, value); + @$pb.TagNumber(3) + $core.bool hasCapabilities() => $_has(2); + @$pb.TagNumber(3) + void clearCapabilities() => $_clearField(3); + @$pb.TagNumber(3) + $1.Capabilities ensureCapabilities() => $_ensure(2); + + @$pb.TagNumber(4) + $fixnum.Int64 get desiredRevision => $_getI64(3); + @$pb.TagNumber(4) + set desiredRevision($fixnum.Int64 value) => $_setInt64(3, value); + @$pb.TagNumber(4) + $core.bool hasDesiredRevision() => $_has(3); + @$pb.TagNumber(4) + void clearDesiredRevision() => $_clearField(4); + + @$pb.TagNumber(5) + $pb.PbList<$1.ApplicationRecord> get records => $_getList(4); + + @$pb.TagNumber(6) + $core.bool get locallySuspended => $_getBF(5); + @$pb.TagNumber(6) + set locallySuspended($core.bool value) => $_setBool(5, value); + @$pb.TagNumber(6) + $core.bool hasLocallySuspended() => $_has(5); + @$pb.TagNumber(6) + void clearLocallySuspended() => $_clearField(6); +} + +class CheckInResponse extends $pb.GeneratedMessage { + factory CheckInResponse({ + $1.DesiredState? desired, + }) { + final result = CheckInResponse._(); + if (desired != null) result.desired = desired; + return result; + } + + CheckInResponse._(); + + factory CheckInResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + CheckInResponse()..mergeFromBuffer(data, registry); + factory CheckInResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + CheckInResponse()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'CheckInResponse', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'xtcp_treatment.v1'), + createEmptyInstance: CheckInResponse.$_createMessage) + ..aOM<$1.DesiredState>(1, _omitFieldNames ? '' : 'desired', + subBuilder: $1.DesiredState.$_createMessage) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + CheckInResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + CheckInResponse copyWith(void Function(CheckInResponse) updates) => + super.copyWith((message) => updates(message as CheckInResponse)) + as CheckInResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated('Use CheckInResponse() / CheckInResponse.new instead') + static CheckInResponse create() => CheckInResponse._(); + static $pb.GeneratedMessage $_createMessage() => CheckInResponse._(); + @$core.override + CheckInResponse createEmptyInstance() => CheckInResponse._(); + @$core.pragma('dart2js:noInline') + static CheckInResponse getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor( + CheckInResponse.$_createMessage); + static CheckInResponse? _defaultInstance; + + @$pb.TagNumber(1) + $1.DesiredState get desired => $_getN(0); + @$pb.TagNumber(1) + set desired($1.DesiredState value) => $_setField(1, value); + @$pb.TagNumber(1) + $core.bool hasDesired() => $_has(0); + @$pb.TagNumber(1) + void clearDesired() => $_clearField(1); + @$pb.TagNumber(1) + $1.DesiredState ensureDesired() => $_ensure(0); +} + +class GetDesiredStateRequest extends $pb.GeneratedMessage { + factory GetDesiredStateRequest({ + $core.String? agentId, + }) { + final result = GetDesiredStateRequest._(); + if (agentId != null) result.agentId = agentId; + return result; + } + + GetDesiredStateRequest._(); + + factory GetDesiredStateRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + GetDesiredStateRequest()..mergeFromBuffer(data, registry); + factory GetDesiredStateRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + GetDesiredStateRequest()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'GetDesiredStateRequest', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'xtcp_treatment.v1'), + createEmptyInstance: GetDesiredStateRequest.$_createMessage) + ..aOS(1, _omitFieldNames ? '' : 'agentId') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetDesiredStateRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetDesiredStateRequest copyWith( + void Function(GetDesiredStateRequest) updates) => + super.copyWith((message) => updates(message as GetDesiredStateRequest)) + as GetDesiredStateRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated( + 'Use GetDesiredStateRequest() / GetDesiredStateRequest.new instead') + static GetDesiredStateRequest create() => GetDesiredStateRequest._(); + static $pb.GeneratedMessage $_createMessage() => GetDesiredStateRequest._(); + @$core.override + GetDesiredStateRequest createEmptyInstance() => GetDesiredStateRequest._(); + @$core.pragma('dart2js:noInline') + static GetDesiredStateRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor( + GetDesiredStateRequest.$_createMessage); + static GetDesiredStateRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get agentId => $_getSZ(0); + @$pb.TagNumber(1) + set agentId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasAgentId() => $_has(0); + @$pb.TagNumber(1) + void clearAgentId() => $_clearField(1); +} + +class GetDesiredStateResponse extends $pb.GeneratedMessage { + factory GetDesiredStateResponse({ + $1.DesiredState? desired, + }) { + final result = GetDesiredStateResponse._(); + if (desired != null) result.desired = desired; + return result; + } + + GetDesiredStateResponse._(); + + factory GetDesiredStateResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + GetDesiredStateResponse()..mergeFromBuffer(data, registry); + factory GetDesiredStateResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + GetDesiredStateResponse()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'GetDesiredStateResponse', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'xtcp_treatment.v1'), + createEmptyInstance: GetDesiredStateResponse.$_createMessage) + ..aOM<$1.DesiredState>(1, _omitFieldNames ? '' : 'desired', + subBuilder: $1.DesiredState.$_createMessage) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetDesiredStateResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetDesiredStateResponse copyWith( + void Function(GetDesiredStateResponse) updates) => + super.copyWith((message) => updates(message as GetDesiredStateResponse)) + as GetDesiredStateResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated( + 'Use GetDesiredStateResponse() / GetDesiredStateResponse.new instead') + static GetDesiredStateResponse create() => GetDesiredStateResponse._(); + static $pb.GeneratedMessage $_createMessage() => GetDesiredStateResponse._(); + @$core.override + GetDesiredStateResponse createEmptyInstance() => GetDesiredStateResponse._(); + @$core.pragma('dart2js:noInline') + static GetDesiredStateResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor( + GetDesiredStateResponse.$_createMessage); + static GetDesiredStateResponse? _defaultInstance; + + @$pb.TagNumber(1) + $1.DesiredState get desired => $_getN(0); + @$pb.TagNumber(1) + set desired($1.DesiredState value) => $_setField(1, value); + @$pb.TagNumber(1) + $core.bool hasDesired() => $_has(0); + @$pb.TagNumber(1) + void clearDesired() => $_clearField(1); + @$pb.TagNumber(1) + $1.DesiredState ensureDesired() => $_ensure(0); +} + +class WatchDesiredStateRequest extends $pb.GeneratedMessage { + factory WatchDesiredStateRequest({ + $core.String? agentId, + $fixnum.Int64? afterRevision, + }) { + final result = WatchDesiredStateRequest._(); + if (agentId != null) result.agentId = agentId; + if (afterRevision != null) result.afterRevision = afterRevision; + return result; + } + + WatchDesiredStateRequest._(); + + factory WatchDesiredStateRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + WatchDesiredStateRequest()..mergeFromBuffer(data, registry); + factory WatchDesiredStateRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + WatchDesiredStateRequest()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'WatchDesiredStateRequest', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'xtcp_treatment.v1'), + createEmptyInstance: WatchDesiredStateRequest.$_createMessage) + ..aOS(1, _omitFieldNames ? '' : 'agentId') + ..a<$fixnum.Int64>( + 2, _omitFieldNames ? '' : 'afterRevision', $pb.PbFieldType.OU6, + defaultOrMaker: $fixnum.Int64.ZERO) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + WatchDesiredStateRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + WatchDesiredStateRequest copyWith( + void Function(WatchDesiredStateRequest) updates) => + super.copyWith((message) => updates(message as WatchDesiredStateRequest)) + as WatchDesiredStateRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated( + 'Use WatchDesiredStateRequest() / WatchDesiredStateRequest.new instead') + static WatchDesiredStateRequest create() => WatchDesiredStateRequest._(); + static $pb.GeneratedMessage $_createMessage() => WatchDesiredStateRequest._(); + @$core.override + WatchDesiredStateRequest createEmptyInstance() => + WatchDesiredStateRequest._(); + @$core.pragma('dart2js:noInline') + static WatchDesiredStateRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor( + WatchDesiredStateRequest.$_createMessage); + static WatchDesiredStateRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get agentId => $_getSZ(0); + @$pb.TagNumber(1) + set agentId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasAgentId() => $_has(0); + @$pb.TagNumber(1) + void clearAgentId() => $_clearField(1); + + @$pb.TagNumber(2) + $fixnum.Int64 get afterRevision => $_getI64(1); + @$pb.TagNumber(2) + set afterRevision($fixnum.Int64 value) => $_setInt64(1, value); + @$pb.TagNumber(2) + $core.bool hasAfterRevision() => $_has(1); + @$pb.TagNumber(2) + void clearAfterRevision() => $_clearField(2); +} + +class WatchDesiredStateResponse extends $pb.GeneratedMessage { + factory WatchDesiredStateResponse({ + $fixnum.Int64? revision, + }) { + final result = WatchDesiredStateResponse._(); + if (revision != null) result.revision = revision; + return result; + } + + WatchDesiredStateResponse._(); + + factory WatchDesiredStateResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + WatchDesiredStateResponse()..mergeFromBuffer(data, registry); + factory WatchDesiredStateResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + WatchDesiredStateResponse()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'WatchDesiredStateResponse', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'xtcp_treatment.v1'), + createEmptyInstance: WatchDesiredStateResponse.$_createMessage) + ..a<$fixnum.Int64>( + 1, _omitFieldNames ? '' : 'revision', $pb.PbFieldType.OU6, + defaultOrMaker: $fixnum.Int64.ZERO) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + WatchDesiredStateResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + WatchDesiredStateResponse copyWith( + void Function(WatchDesiredStateResponse) updates) => + super.copyWith((message) => updates(message as WatchDesiredStateResponse)) + as WatchDesiredStateResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated( + 'Use WatchDesiredStateResponse() / WatchDesiredStateResponse.new instead') + static WatchDesiredStateResponse create() => WatchDesiredStateResponse._(); + static $pb.GeneratedMessage $_createMessage() => + WatchDesiredStateResponse._(); + @$core.override + WatchDesiredStateResponse createEmptyInstance() => + WatchDesiredStateResponse._(); + @$core.pragma('dart2js:noInline') + static WatchDesiredStateResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor( + WatchDesiredStateResponse.$_createMessage); + static WatchDesiredStateResponse? _defaultInstance; + + @$pb.TagNumber(1) + $fixnum.Int64 get revision => $_getI64(0); + @$pb.TagNumber(1) + set revision($fixnum.Int64 value) => $_setInt64(0, value); + @$pb.TagNumber(1) + $core.bool hasRevision() => $_has(0); + @$pb.TagNumber(1) + void clearRevision() => $_clearField(1); +} + +const $core.bool _omitFieldNames = + $core.bool.fromEnvironment('protobuf.omit_field_names'); +const $core.bool _omitMessageNames = + $core.bool.fromEnvironment('protobuf.omit_message_names'); diff --git a/gen/dart/xtcp_treatment/v1/agent.pbenum.dart b/gen/dart/xtcp_treatment/v1/agent.pbenum.dart new file mode 100644 index 000000000..239d62c37 --- /dev/null +++ b/gen/dart/xtcp_treatment/v1/agent.pbenum.dart @@ -0,0 +1,11 @@ +// This is a generated file - do not edit. +// +// Generated from xtcp_treatment/v1/agent.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports diff --git a/gen/dart/xtcp_treatment/v1/agent.pbgrpc.dart b/gen/dart/xtcp_treatment/v1/agent.pbgrpc.dart new file mode 100644 index 000000000..9e1217668 --- /dev/null +++ b/gen/dart/xtcp_treatment/v1/agent.pbgrpc.dart @@ -0,0 +1,134 @@ +// This is a generated file - do not edit. +// +// Generated from xtcp_treatment/v1/agent.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports + +import 'dart:async' as $async; +import 'dart:core' as $core; + +import 'package:grpc/service_api.dart' as $grpc; +import 'package:protobuf/protobuf.dart' as $pb; + +import 'agent.pb.dart' as $0; + +export 'agent.pb.dart'; + +@$pb.GrpcServiceName('xtcp_treatment.v1.AgentControlService') +class AgentControlServiceClient extends $grpc.Client { + /// The hostname for this service. + static const $core.String defaultHost = ''; + + /// OAuth scopes needed for the client. + static const $core.List<$core.String> oauthScopes = [ + '', + ]; + + AgentControlServiceClient(super.channel, {super.options, super.interceptors}); + + $grpc.ResponseFuture<$0.CheckInResponse> checkIn( + $0.CheckInRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$checkIn, request, options: options); + } + + $grpc.ResponseFuture<$0.GetDesiredStateResponse> getDesiredState( + $0.GetDesiredStateRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$getDesiredState, request, options: options); + } + + $grpc.ResponseStream<$0.WatchDesiredStateResponse> watchDesiredState( + $0.WatchDesiredStateRequest request, { + $grpc.CallOptions? options, + }) { + return $createStreamingCall( + _$watchDesiredState, $async.Stream.fromIterable([request]), + options: options); + } + + // method descriptors + + static final _$checkIn = + $grpc.ClientMethod<$0.CheckInRequest, $0.CheckInResponse>( + '/xtcp_treatment.v1.AgentControlService/CheckIn', + ($0.CheckInRequest value) => value.writeToBuffer(), + $0.CheckInResponse.fromBuffer); + static final _$getDesiredState = + $grpc.ClientMethod<$0.GetDesiredStateRequest, $0.GetDesiredStateResponse>( + '/xtcp_treatment.v1.AgentControlService/GetDesiredState', + ($0.GetDesiredStateRequest value) => value.writeToBuffer(), + $0.GetDesiredStateResponse.fromBuffer); + static final _$watchDesiredState = $grpc.ClientMethod< + $0.WatchDesiredStateRequest, $0.WatchDesiredStateResponse>( + '/xtcp_treatment.v1.AgentControlService/WatchDesiredState', + ($0.WatchDesiredStateRequest value) => value.writeToBuffer(), + $0.WatchDesiredStateResponse.fromBuffer); +} + +@$pb.GrpcServiceName('xtcp_treatment.v1.AgentControlService') +abstract class AgentControlServiceBase extends $grpc.Service { + $core.String get $name => 'xtcp_treatment.v1.AgentControlService'; + + AgentControlServiceBase() { + $addMethod($grpc.ServiceMethod<$0.CheckInRequest, $0.CheckInResponse>( + 'CheckIn', + checkIn_Pre, + false, + false, + ($core.List<$core.int> value) => $0.CheckInRequest.fromBuffer(value), + ($0.CheckInResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.GetDesiredStateRequest, + $0.GetDesiredStateResponse>( + 'GetDesiredState', + getDesiredState_Pre, + false, + false, + ($core.List<$core.int> value) => + $0.GetDesiredStateRequest.fromBuffer(value), + ($0.GetDesiredStateResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.WatchDesiredStateRequest, + $0.WatchDesiredStateResponse>( + 'WatchDesiredState', + watchDesiredState_Pre, + false, + true, + ($core.List<$core.int> value) => + $0.WatchDesiredStateRequest.fromBuffer(value), + ($0.WatchDesiredStateResponse value) => value.writeToBuffer())); + } + + $async.Future<$0.CheckInResponse> checkIn_Pre($grpc.ServiceCall $call, + $async.Future<$0.CheckInRequest> $request) async { + return checkIn($call, await $request); + } + + $async.Future<$0.CheckInResponse> checkIn( + $grpc.ServiceCall call, $0.CheckInRequest request); + + $async.Future<$0.GetDesiredStateResponse> getDesiredState_Pre( + $grpc.ServiceCall $call, + $async.Future<$0.GetDesiredStateRequest> $request) async { + return getDesiredState($call, await $request); + } + + $async.Future<$0.GetDesiredStateResponse> getDesiredState( + $grpc.ServiceCall call, $0.GetDesiredStateRequest request); + + $async.Stream<$0.WatchDesiredStateResponse> watchDesiredState_Pre( + $grpc.ServiceCall $call, + $async.Future<$0.WatchDesiredStateRequest> $request) async* { + yield* watchDesiredState($call, await $request); + } + + $async.Stream<$0.WatchDesiredStateResponse> watchDesiredState( + $grpc.ServiceCall call, $0.WatchDesiredStateRequest request); +} diff --git a/gen/dart/xtcp_treatment/v1/agent.pbjson.dart b/gen/dart/xtcp_treatment/v1/agent.pbjson.dart new file mode 100644 index 000000000..f890d0f44 --- /dev/null +++ b/gen/dart/xtcp_treatment/v1/agent.pbjson.dart @@ -0,0 +1,146 @@ +// This is a generated file - do not edit. +// +// Generated from xtcp_treatment/v1/agent.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports +// ignore_for_file: unused_import + +import 'dart:convert' as $convert; +import 'dart:core' as $core; +import 'dart:typed_data' as $typed_data; + +@$core.Deprecated('Use checkInRequestDescriptor instead') +const CheckInRequest$json = { + '1': 'CheckInRequest', + '2': [ + {'1': 'agent_id', '3': 1, '4': 1, '5': 9, '8': {}, '10': 'agentId'}, + {'1': 'location', '3': 2, '4': 1, '5': 9, '8': {}, '10': 'location'}, + { + '1': 'capabilities', + '3': 3, + '4': 1, + '5': 11, + '6': '.xtcp_treatment.v1.Capabilities', + '8': {}, + '10': 'capabilities' + }, + {'1': 'desired_revision', '3': 4, '4': 1, '5': 4, '10': 'desiredRevision'}, + { + '1': 'records', + '3': 5, + '4': 3, + '5': 11, + '6': '.xtcp_treatment.v1.ApplicationRecord', + '8': {}, + '10': 'records' + }, + { + '1': 'locally_suspended', + '3': 6, + '4': 1, + '5': 8, + '10': 'locallySuspended' + }, + ], +}; + +/// Descriptor for `CheckInRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List checkInRequestDescriptor = $convert.base64Decode( + 'Cg5DaGVja0luUmVxdWVzdBJECghhZ2VudF9pZBgBIAEoCUIpukgmciQyIl5bYS16QS1aMC05XV' + 'thLXpBLVowLTlfLi1dezAsMTI3fSRSB2FnZW50SWQSRQoIbG9jYXRpb24YAiABKAlCKbpIJnIk' + 'MiJeW2EtekEtWjAtOV1bYS16QS1aMC05Xy4tXXswLDEyN30kUghsb2NhdGlvbhJLCgxjYXBhYm' + 'lsaXRpZXMYAyABKAsyHy54dGNwX3RyZWF0bWVudC52MS5DYXBhYmlsaXRpZXNCBrpIA8gBAVIM' + 'Y2FwYWJpbGl0aWVzEikKEGRlc2lyZWRfcmV2aXNpb24YBCABKARSD2Rlc2lyZWRSZXZpc2lvbh' + 'JJCgdyZWNvcmRzGAUgAygLMiQueHRjcF90cmVhdG1lbnQudjEuQXBwbGljYXRpb25SZWNvcmRC' + 'CbpIBpIBAxCAQFIHcmVjb3JkcxIrChFsb2NhbGx5X3N1c3BlbmRlZBgGIAEoCFIQbG9jYWxseV' + 'N1c3BlbmRlZA=='); + +@$core.Deprecated('Use checkInResponseDescriptor instead') +const CheckInResponse$json = { + '1': 'CheckInResponse', + '2': [ + { + '1': 'desired', + '3': 1, + '4': 1, + '5': 11, + '6': '.xtcp_treatment.v1.DesiredState', + '10': 'desired' + }, + ], +}; + +/// Descriptor for `CheckInResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List checkInResponseDescriptor = $convert.base64Decode( + 'Cg9DaGVja0luUmVzcG9uc2USOQoHZGVzaXJlZBgBIAEoCzIfLnh0Y3BfdHJlYXRtZW50LnYxLk' + 'Rlc2lyZWRTdGF0ZVIHZGVzaXJlZA=='); + +@$core.Deprecated('Use getDesiredStateRequestDescriptor instead') +const GetDesiredStateRequest$json = { + '1': 'GetDesiredStateRequest', + '2': [ + {'1': 'agent_id', '3': 1, '4': 1, '5': 9, '8': {}, '10': 'agentId'}, + ], +}; + +/// Descriptor for `GetDesiredStateRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List getDesiredStateRequestDescriptor = + $convert.base64Decode( + 'ChZHZXREZXNpcmVkU3RhdGVSZXF1ZXN0EkQKCGFnZW50X2lkGAEgASgJQim6SCZyJDIiXlthLX' + 'pBLVowLTldW2EtekEtWjAtOV8uLV17MCwxMjd9JFIHYWdlbnRJZA=='); + +@$core.Deprecated('Use getDesiredStateResponseDescriptor instead') +const GetDesiredStateResponse$json = { + '1': 'GetDesiredStateResponse', + '2': [ + { + '1': 'desired', + '3': 1, + '4': 1, + '5': 11, + '6': '.xtcp_treatment.v1.DesiredState', + '10': 'desired' + }, + ], +}; + +/// Descriptor for `GetDesiredStateResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List getDesiredStateResponseDescriptor = + $convert.base64Decode( + 'ChdHZXREZXNpcmVkU3RhdGVSZXNwb25zZRI5CgdkZXNpcmVkGAEgASgLMh8ueHRjcF90cmVhdG' + '1lbnQudjEuRGVzaXJlZFN0YXRlUgdkZXNpcmVk'); + +@$core.Deprecated('Use watchDesiredStateRequestDescriptor instead') +const WatchDesiredStateRequest$json = { + '1': 'WatchDesiredStateRequest', + '2': [ + {'1': 'agent_id', '3': 1, '4': 1, '5': 9, '8': {}, '10': 'agentId'}, + {'1': 'after_revision', '3': 2, '4': 1, '5': 4, '10': 'afterRevision'}, + ], +}; + +/// Descriptor for `WatchDesiredStateRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List watchDesiredStateRequestDescriptor = $convert.base64Decode( + 'ChhXYXRjaERlc2lyZWRTdGF0ZVJlcXVlc3QSRAoIYWdlbnRfaWQYASABKAlCKbpIJnIkMiJeW2' + 'EtekEtWjAtOV1bYS16QS1aMC05Xy4tXXswLDEyN30kUgdhZ2VudElkEiUKDmFmdGVyX3Jldmlz' + 'aW9uGAIgASgEUg1hZnRlclJldmlzaW9u'); + +@$core.Deprecated('Use watchDesiredStateResponseDescriptor instead') +const WatchDesiredStateResponse$json = { + '1': 'WatchDesiredStateResponse', + '2': [ + {'1': 'revision', '3': 1, '4': 1, '5': 4, '10': 'revision'}, + ], +}; + +/// Descriptor for `WatchDesiredStateResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List watchDesiredStateResponseDescriptor = + $convert.base64Decode( + 'ChlXYXRjaERlc2lyZWRTdGF0ZVJlc3BvbnNlEhoKCHJldmlzaW9uGAEgASgEUghyZXZpc2lvbg' + '=='); diff --git a/gen/dart/xtcp_treatment/v1/operator.pb.dart b/gen/dart/xtcp_treatment/v1/operator.pb.dart new file mode 100644 index 000000000..e8f0e5455 --- /dev/null +++ b/gen/dart/xtcp_treatment/v1/operator.pb.dart @@ -0,0 +1,820 @@ +// This is a generated file - do not edit. +// +// Generated from xtcp_treatment/v1/operator.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports + +import 'dart:core' as $core; + +import 'package:fixnum/fixnum.dart' as $fixnum; +import 'package:protobuf/protobuf.dart' as $pb; + +import 'agent.pb.dart' as $2; +import 'operator.pbenum.dart'; +import 'treatment.pb.dart' as $1; + +export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions; + +export 'operator.pbenum.dart'; + +class ProposeRequest extends $pb.GeneratedMessage { + factory ProposeRequest({ + $1.Experiment? experiment, + $core.String? requestId, + }) { + final result = ProposeRequest._(); + if (experiment != null) result.experiment = experiment; + if (requestId != null) result.requestId = requestId; + return result; + } + + ProposeRequest._(); + + factory ProposeRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + ProposeRequest()..mergeFromBuffer(data, registry); + factory ProposeRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + ProposeRequest()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'ProposeRequest', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'xtcp_treatment.v1'), + createEmptyInstance: ProposeRequest.$_createMessage) + ..aOM<$1.Experiment>(1, _omitFieldNames ? '' : 'experiment', + subBuilder: $1.Experiment.$_createMessage) + ..aOS(2, _omitFieldNames ? '' : 'requestId') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ProposeRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ProposeRequest copyWith(void Function(ProposeRequest) updates) => + super.copyWith((message) => updates(message as ProposeRequest)) + as ProposeRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated('Use ProposeRequest() / ProposeRequest.new instead') + static ProposeRequest create() => ProposeRequest._(); + static $pb.GeneratedMessage $_createMessage() => ProposeRequest._(); + @$core.override + ProposeRequest createEmptyInstance() => ProposeRequest._(); + @$core.pragma('dart2js:noInline') + static ProposeRequest getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor( + ProposeRequest.$_createMessage); + static ProposeRequest? _defaultInstance; + + @$pb.TagNumber(1) + $1.Experiment get experiment => $_getN(0); + @$pb.TagNumber(1) + set experiment($1.Experiment value) => $_setField(1, value); + @$pb.TagNumber(1) + $core.bool hasExperiment() => $_has(0); + @$pb.TagNumber(1) + void clearExperiment() => $_clearField(1); + @$pb.TagNumber(1) + $1.Experiment ensureExperiment() => $_ensure(0); + + @$pb.TagNumber(2) + $core.String get requestId => $_getSZ(1); + @$pb.TagNumber(2) + set requestId($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasRequestId() => $_has(1); + @$pb.TagNumber(2) + void clearRequestId() => $_clearField(2); +} + +class ProposeResponse extends $pb.GeneratedMessage { + factory ProposeResponse({ + $1.Experiment? experiment, + }) { + final result = ProposeResponse._(); + if (experiment != null) result.experiment = experiment; + return result; + } + + ProposeResponse._(); + + factory ProposeResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + ProposeResponse()..mergeFromBuffer(data, registry); + factory ProposeResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + ProposeResponse()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'ProposeResponse', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'xtcp_treatment.v1'), + createEmptyInstance: ProposeResponse.$_createMessage) + ..aOM<$1.Experiment>(1, _omitFieldNames ? '' : 'experiment', + subBuilder: $1.Experiment.$_createMessage) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ProposeResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ProposeResponse copyWith(void Function(ProposeResponse) updates) => + super.copyWith((message) => updates(message as ProposeResponse)) + as ProposeResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated('Use ProposeResponse() / ProposeResponse.new instead') + static ProposeResponse create() => ProposeResponse._(); + static $pb.GeneratedMessage $_createMessage() => ProposeResponse._(); + @$core.override + ProposeResponse createEmptyInstance() => ProposeResponse._(); + @$core.pragma('dart2js:noInline') + static ProposeResponse getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor( + ProposeResponse.$_createMessage); + static ProposeResponse? _defaultInstance; + + @$pb.TagNumber(1) + $1.Experiment get experiment => $_getN(0); + @$pb.TagNumber(1) + set experiment($1.Experiment value) => $_setField(1, value); + @$pb.TagNumber(1) + $core.bool hasExperiment() => $_has(0); + @$pb.TagNumber(1) + void clearExperiment() => $_clearField(1); + @$pb.TagNumber(1) + $1.Experiment ensureExperiment() => $_ensure(0); +} + +class AnalyzeRequest extends $pb.GeneratedMessage { + factory AnalyzeRequest({ + $core.String? experimentId, + $fixnum.Int64? expectedRevision, + }) { + final result = AnalyzeRequest._(); + if (experimentId != null) result.experimentId = experimentId; + if (expectedRevision != null) result.expectedRevision = expectedRevision; + return result; + } + + AnalyzeRequest._(); + + factory AnalyzeRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + AnalyzeRequest()..mergeFromBuffer(data, registry); + factory AnalyzeRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + AnalyzeRequest()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'AnalyzeRequest', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'xtcp_treatment.v1'), + createEmptyInstance: AnalyzeRequest.$_createMessage) + ..aOS(1, _omitFieldNames ? '' : 'experimentId') + ..a<$fixnum.Int64>( + 2, _omitFieldNames ? '' : 'expectedRevision', $pb.PbFieldType.OU6, + defaultOrMaker: $fixnum.Int64.ZERO) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + AnalyzeRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + AnalyzeRequest copyWith(void Function(AnalyzeRequest) updates) => + super.copyWith((message) => updates(message as AnalyzeRequest)) + as AnalyzeRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated('Use AnalyzeRequest() / AnalyzeRequest.new instead') + static AnalyzeRequest create() => AnalyzeRequest._(); + static $pb.GeneratedMessage $_createMessage() => AnalyzeRequest._(); + @$core.override + AnalyzeRequest createEmptyInstance() => AnalyzeRequest._(); + @$core.pragma('dart2js:noInline') + static AnalyzeRequest getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor( + AnalyzeRequest.$_createMessage); + static AnalyzeRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get experimentId => $_getSZ(0); + @$pb.TagNumber(1) + set experimentId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasExperimentId() => $_has(0); + @$pb.TagNumber(1) + void clearExperimentId() => $_clearField(1); + + @$pb.TagNumber(2) + $fixnum.Int64 get expectedRevision => $_getI64(1); + @$pb.TagNumber(2) + set expectedRevision($fixnum.Int64 value) => $_setInt64(1, value); + @$pb.TagNumber(2) + $core.bool hasExpectedRevision() => $_has(1); + @$pb.TagNumber(2) + void clearExpectedRevision() => $_clearField(2); +} + +class AnalyzeResponse extends $pb.GeneratedMessage { + factory AnalyzeResponse({ + $1.Evidence? evidence, + $core.String? outcome, + }) { + final result = AnalyzeResponse._(); + if (evidence != null) result.evidence = evidence; + if (outcome != null) result.outcome = outcome; + return result; + } + + AnalyzeResponse._(); + + factory AnalyzeResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + AnalyzeResponse()..mergeFromBuffer(data, registry); + factory AnalyzeResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + AnalyzeResponse()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'AnalyzeResponse', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'xtcp_treatment.v1'), + createEmptyInstance: AnalyzeResponse.$_createMessage) + ..aOM<$1.Evidence>(1, _omitFieldNames ? '' : 'evidence', + subBuilder: $1.Evidence.$_createMessage) + ..aOS(2, _omitFieldNames ? '' : 'outcome') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + AnalyzeResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + AnalyzeResponse copyWith(void Function(AnalyzeResponse) updates) => + super.copyWith((message) => updates(message as AnalyzeResponse)) + as AnalyzeResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated('Use AnalyzeResponse() / AnalyzeResponse.new instead') + static AnalyzeResponse create() => AnalyzeResponse._(); + static $pb.GeneratedMessage $_createMessage() => AnalyzeResponse._(); + @$core.override + AnalyzeResponse createEmptyInstance() => AnalyzeResponse._(); + @$core.pragma('dart2js:noInline') + static AnalyzeResponse getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor( + AnalyzeResponse.$_createMessage); + static AnalyzeResponse? _defaultInstance; + + @$pb.TagNumber(1) + $1.Evidence get evidence => $_getN(0); + @$pb.TagNumber(1) + set evidence($1.Evidence value) => $_setField(1, value); + @$pb.TagNumber(1) + $core.bool hasEvidence() => $_has(0); + @$pb.TagNumber(1) + void clearEvidence() => $_clearField(1); + @$pb.TagNumber(1) + $1.Evidence ensureEvidence() => $_ensure(0); + + @$pb.TagNumber(2) + $core.String get outcome => $_getSZ(1); + @$pb.TagNumber(2) + set outcome($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasOutcome() => $_has(1); + @$pb.TagNumber(2) + void clearOutcome() => $_clearField(2); +} + +class ReviewRequest extends $pb.GeneratedMessage { + factory ReviewRequest({ + $core.String? experimentId, + $fixnum.Int64? expectedRevision, + $core.bool? approve, + $core.String? reason, + $core.String? requestId, + $1.Evidence? evidence, + }) { + final result = ReviewRequest._(); + if (experimentId != null) result.experimentId = experimentId; + if (expectedRevision != null) result.expectedRevision = expectedRevision; + if (approve != null) result.approve = approve; + if (reason != null) result.reason = reason; + if (requestId != null) result.requestId = requestId; + if (evidence != null) result.evidence = evidence; + return result; + } + + ReviewRequest._(); + + factory ReviewRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + ReviewRequest()..mergeFromBuffer(data, registry); + factory ReviewRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + ReviewRequest()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'ReviewRequest', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'xtcp_treatment.v1'), + createEmptyInstance: ReviewRequest.$_createMessage) + ..aOS(1, _omitFieldNames ? '' : 'experimentId') + ..a<$fixnum.Int64>( + 2, _omitFieldNames ? '' : 'expectedRevision', $pb.PbFieldType.OU6, + defaultOrMaker: $fixnum.Int64.ZERO) + ..aOB(3, _omitFieldNames ? '' : 'approve') + ..aOS(4, _omitFieldNames ? '' : 'reason') + ..aOS(5, _omitFieldNames ? '' : 'requestId') + ..aOM<$1.Evidence>(6, _omitFieldNames ? '' : 'evidence', + subBuilder: $1.Evidence.$_createMessage) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ReviewRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ReviewRequest copyWith(void Function(ReviewRequest) updates) => + super.copyWith((message) => updates(message as ReviewRequest)) + as ReviewRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated('Use ReviewRequest() / ReviewRequest.new instead') + static ReviewRequest create() => ReviewRequest._(); + static $pb.GeneratedMessage $_createMessage() => ReviewRequest._(); + @$core.override + ReviewRequest createEmptyInstance() => ReviewRequest._(); + @$core.pragma('dart2js:noInline') + static ReviewRequest getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor( + ReviewRequest.$_createMessage); + static ReviewRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get experimentId => $_getSZ(0); + @$pb.TagNumber(1) + set experimentId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasExperimentId() => $_has(0); + @$pb.TagNumber(1) + void clearExperimentId() => $_clearField(1); + + @$pb.TagNumber(2) + $fixnum.Int64 get expectedRevision => $_getI64(1); + @$pb.TagNumber(2) + set expectedRevision($fixnum.Int64 value) => $_setInt64(1, value); + @$pb.TagNumber(2) + $core.bool hasExpectedRevision() => $_has(1); + @$pb.TagNumber(2) + void clearExpectedRevision() => $_clearField(2); + + @$pb.TagNumber(3) + $core.bool get approve => $_getBF(2); + @$pb.TagNumber(3) + set approve($core.bool value) => $_setBool(2, value); + @$pb.TagNumber(3) + $core.bool hasApprove() => $_has(2); + @$pb.TagNumber(3) + void clearApprove() => $_clearField(3); + + @$pb.TagNumber(4) + $core.String get reason => $_getSZ(3); + @$pb.TagNumber(4) + set reason($core.String value) => $_setString(3, value); + @$pb.TagNumber(4) + $core.bool hasReason() => $_has(3); + @$pb.TagNumber(4) + void clearReason() => $_clearField(4); + + @$pb.TagNumber(5) + $core.String get requestId => $_getSZ(4); + @$pb.TagNumber(5) + set requestId($core.String value) => $_setString(4, value); + @$pb.TagNumber(5) + $core.bool hasRequestId() => $_has(4); + @$pb.TagNumber(5) + void clearRequestId() => $_clearField(5); + + /// Initial review uses baseline; subsequent reviews require fresh evidence. + @$pb.TagNumber(6) + $1.Evidence get evidence => $_getN(5); + @$pb.TagNumber(6) + set evidence($1.Evidence value) => $_setField(6, value); + @$pb.TagNumber(6) + $core.bool hasEvidence() => $_has(5); + @$pb.TagNumber(6) + void clearEvidence() => $_clearField(6); + @$pb.TagNumber(6) + $1.Evidence ensureEvidence() => $_ensure(5); +} + +class ReviewResponse extends $pb.GeneratedMessage { + factory ReviewResponse({ + $1.Experiment? experiment, + }) { + final result = ReviewResponse._(); + if (experiment != null) result.experiment = experiment; + return result; + } + + ReviewResponse._(); + + factory ReviewResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + ReviewResponse()..mergeFromBuffer(data, registry); + factory ReviewResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + ReviewResponse()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'ReviewResponse', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'xtcp_treatment.v1'), + createEmptyInstance: ReviewResponse.$_createMessage) + ..aOM<$1.Experiment>(1, _omitFieldNames ? '' : 'experiment', + subBuilder: $1.Experiment.$_createMessage) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ReviewResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ReviewResponse copyWith(void Function(ReviewResponse) updates) => + super.copyWith((message) => updates(message as ReviewResponse)) + as ReviewResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated('Use ReviewResponse() / ReviewResponse.new instead') + static ReviewResponse create() => ReviewResponse._(); + static $pb.GeneratedMessage $_createMessage() => ReviewResponse._(); + @$core.override + ReviewResponse createEmptyInstance() => ReviewResponse._(); + @$core.pragma('dart2js:noInline') + static ReviewResponse getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor( + ReviewResponse.$_createMessage); + static ReviewResponse? _defaultInstance; + + @$pb.TagNumber(1) + $1.Experiment get experiment => $_getN(0); + @$pb.TagNumber(1) + set experiment($1.Experiment value) => $_setField(1, value); + @$pb.TagNumber(1) + $core.bool hasExperiment() => $_has(0); + @$pb.TagNumber(1) + void clearExperiment() => $_clearField(1); + @$pb.TagNumber(1) + $1.Experiment ensureExperiment() => $_ensure(0); +} + +class ControlRequest extends $pb.GeneratedMessage { + factory ControlRequest({ + $core.String? experimentId, + $core.String? agentId, + $core.String? location, + $core.bool? fleet, + ControlAction? action, + $core.String? reason, + $core.String? requestId, + }) { + final result = ControlRequest._(); + if (experimentId != null) result.experimentId = experimentId; + if (agentId != null) result.agentId = agentId; + if (location != null) result.location = location; + if (fleet != null) result.fleet = fleet; + if (action != null) result.action = action; + if (reason != null) result.reason = reason; + if (requestId != null) result.requestId = requestId; + return result; + } + + ControlRequest._(); + + factory ControlRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + ControlRequest()..mergeFromBuffer(data, registry); + factory ControlRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + ControlRequest()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'ControlRequest', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'xtcp_treatment.v1'), + createEmptyInstance: ControlRequest.$_createMessage) + ..aOS(1, _omitFieldNames ? '' : 'experimentId') + ..aOS(2, _omitFieldNames ? '' : 'agentId') + ..aOS(3, _omitFieldNames ? '' : 'location') + ..aOB(4, _omitFieldNames ? '' : 'fleet') + ..aE(5, _omitFieldNames ? '' : 'action', + enumValues: ControlAction.values) + ..aOS(6, _omitFieldNames ? '' : 'reason') + ..aOS(7, _omitFieldNames ? '' : 'requestId') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ControlRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ControlRequest copyWith(void Function(ControlRequest) updates) => + super.copyWith((message) => updates(message as ControlRequest)) + as ControlRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated('Use ControlRequest() / ControlRequest.new instead') + static ControlRequest create() => ControlRequest._(); + static $pb.GeneratedMessage $_createMessage() => ControlRequest._(); + @$core.override + ControlRequest createEmptyInstance() => ControlRequest._(); + @$core.pragma('dart2js:noInline') + static ControlRequest getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor( + ControlRequest.$_createMessage); + static ControlRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get experimentId => $_getSZ(0); + @$pb.TagNumber(1) + set experimentId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasExperimentId() => $_has(0); + @$pb.TagNumber(1) + void clearExperimentId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get agentId => $_getSZ(1); + @$pb.TagNumber(2) + set agentId($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasAgentId() => $_has(1); + @$pb.TagNumber(2) + void clearAgentId() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get location => $_getSZ(2); + @$pb.TagNumber(3) + set location($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasLocation() => $_has(2); + @$pb.TagNumber(3) + void clearLocation() => $_clearField(3); + + @$pb.TagNumber(4) + $core.bool get fleet => $_getBF(3); + @$pb.TagNumber(4) + set fleet($core.bool value) => $_setBool(3, value); + @$pb.TagNumber(4) + $core.bool hasFleet() => $_has(3); + @$pb.TagNumber(4) + void clearFleet() => $_clearField(4); + + @$pb.TagNumber(5) + ControlAction get action => $_getN(4); + @$pb.TagNumber(5) + set action(ControlAction value) => $_setField(5, value); + @$pb.TagNumber(5) + $core.bool hasAction() => $_has(4); + @$pb.TagNumber(5) + void clearAction() => $_clearField(5); + + @$pb.TagNumber(6) + $core.String get reason => $_getSZ(5); + @$pb.TagNumber(6) + set reason($core.String value) => $_setString(5, value); + @$pb.TagNumber(6) + $core.bool hasReason() => $_has(5); + @$pb.TagNumber(6) + void clearReason() => $_clearField(6); + + @$pb.TagNumber(7) + $core.String get requestId => $_getSZ(6); + @$pb.TagNumber(7) + set requestId($core.String value) => $_setString(6, value); + @$pb.TagNumber(7) + $core.bool hasRequestId() => $_has(6); + @$pb.TagNumber(7) + void clearRequestId() => $_clearField(7); +} + +class ControlResponse extends $pb.GeneratedMessage { + factory ControlResponse({ + $core.int? affectedAgents, + }) { + final result = ControlResponse._(); + if (affectedAgents != null) result.affectedAgents = affectedAgents; + return result; + } + + ControlResponse._(); + + factory ControlResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + ControlResponse()..mergeFromBuffer(data, registry); + factory ControlResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + ControlResponse()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'ControlResponse', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'xtcp_treatment.v1'), + createEmptyInstance: ControlResponse.$_createMessage) + ..aI(1, _omitFieldNames ? '' : 'affectedAgents', + fieldType: $pb.PbFieldType.OU3) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ControlResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ControlResponse copyWith(void Function(ControlResponse) updates) => + super.copyWith((message) => updates(message as ControlResponse)) + as ControlResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated('Use ControlResponse() / ControlResponse.new instead') + static ControlResponse create() => ControlResponse._(); + static $pb.GeneratedMessage $_createMessage() => ControlResponse._(); + @$core.override + ControlResponse createEmptyInstance() => ControlResponse._(); + @$core.pragma('dart2js:noInline') + static ControlResponse getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor( + ControlResponse.$_createMessage); + static ControlResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.int get affectedAgents => $_getIZ(0); + @$pb.TagNumber(1) + set affectedAgents($core.int value) => $_setUnsignedInt32(0, value); + @$pb.TagNumber(1) + $core.bool hasAffectedAgents() => $_has(0); + @$pb.TagNumber(1) + void clearAffectedAgents() => $_clearField(1); +} + +class InspectRequest extends $pb.GeneratedMessage { + factory InspectRequest({ + $core.String? experimentId, + $core.String? agentId, + }) { + final result = InspectRequest._(); + if (experimentId != null) result.experimentId = experimentId; + if (agentId != null) result.agentId = agentId; + return result; + } + + InspectRequest._(); + + factory InspectRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + InspectRequest()..mergeFromBuffer(data, registry); + factory InspectRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + InspectRequest()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'InspectRequest', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'xtcp_treatment.v1'), + createEmptyInstance: InspectRequest.$_createMessage) + ..aOS(1, _omitFieldNames ? '' : 'experimentId') + ..aOS(2, _omitFieldNames ? '' : 'agentId') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + InspectRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + InspectRequest copyWith(void Function(InspectRequest) updates) => + super.copyWith((message) => updates(message as InspectRequest)) + as InspectRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated('Use InspectRequest() / InspectRequest.new instead') + static InspectRequest create() => InspectRequest._(); + static $pb.GeneratedMessage $_createMessage() => InspectRequest._(); + @$core.override + InspectRequest createEmptyInstance() => InspectRequest._(); + @$core.pragma('dart2js:noInline') + static InspectRequest getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor( + InspectRequest.$_createMessage); + static InspectRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get experimentId => $_getSZ(0); + @$pb.TagNumber(1) + set experimentId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasExperimentId() => $_has(0); + @$pb.TagNumber(1) + void clearExperimentId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get agentId => $_getSZ(1); + @$pb.TagNumber(2) + set agentId($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasAgentId() => $_has(1); + @$pb.TagNumber(2) + void clearAgentId() => $_clearField(2); +} + +class InspectResponse extends $pb.GeneratedMessage { + factory InspectResponse({ + $core.Iterable<$1.Experiment>? experiments, + $core.Iterable<$1.DesiredState>? desired, + $core.Iterable<$2.CheckInRequest>? observed, + }) { + final result = InspectResponse._(); + if (experiments != null) result.experiments.addAll(experiments); + if (desired != null) result.desired.addAll(desired); + if (observed != null) result.observed.addAll(observed); + return result; + } + + InspectResponse._(); + + factory InspectResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + InspectResponse()..mergeFromBuffer(data, registry); + factory InspectResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + InspectResponse()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'InspectResponse', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'xtcp_treatment.v1'), + createEmptyInstance: InspectResponse.$_createMessage) + ..pPM<$1.Experiment>(1, _omitFieldNames ? '' : 'experiments', + subBuilder: $1.Experiment.$_createMessage) + ..pPM<$1.DesiredState>(2, _omitFieldNames ? '' : 'desired', + subBuilder: $1.DesiredState.$_createMessage) + ..pPM<$2.CheckInRequest>(3, _omitFieldNames ? '' : 'observed', + subBuilder: $2.CheckInRequest.$_createMessage) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + InspectResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + InspectResponse copyWith(void Function(InspectResponse) updates) => + super.copyWith((message) => updates(message as InspectResponse)) + as InspectResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated('Use InspectResponse() / InspectResponse.new instead') + static InspectResponse create() => InspectResponse._(); + static $pb.GeneratedMessage $_createMessage() => InspectResponse._(); + @$core.override + InspectResponse createEmptyInstance() => InspectResponse._(); + @$core.pragma('dart2js:noInline') + static InspectResponse getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor( + InspectResponse.$_createMessage); + static InspectResponse? _defaultInstance; + + @$pb.TagNumber(1) + $pb.PbList<$1.Experiment> get experiments => $_getList(0); + + @$pb.TagNumber(2) + $pb.PbList<$1.DesiredState> get desired => $_getList(1); + + @$pb.TagNumber(3) + $pb.PbList<$2.CheckInRequest> get observed => $_getList(2); +} + +const $core.bool _omitFieldNames = + $core.bool.fromEnvironment('protobuf.omit_field_names'); +const $core.bool _omitMessageNames = + $core.bool.fromEnvironment('protobuf.omit_message_names'); diff --git a/gen/dart/xtcp_treatment/v1/operator.pbenum.dart b/gen/dart/xtcp_treatment/v1/operator.pbenum.dart new file mode 100644 index 000000000..4580f6956 --- /dev/null +++ b/gen/dart/xtcp_treatment/v1/operator.pbenum.dart @@ -0,0 +1,49 @@ +// This is a generated file - do not edit. +// +// Generated from xtcp_treatment/v1/operator.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports + +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +class ControlAction extends $pb.ProtobufEnum { + static const ControlAction CONTROL_ACTION_UNSPECIFIED = + ControlAction._(0, _omitEnumNames ? '' : 'CONTROL_ACTION_UNSPECIFIED'); + static const ControlAction CONTROL_ACTION_PAUSE = + ControlAction._(1, _omitEnumNames ? '' : 'CONTROL_ACTION_PAUSE'); + static const ControlAction CONTROL_ACTION_SUSPEND = + ControlAction._(2, _omitEnumNames ? '' : 'CONTROL_ACTION_SUSPEND'); + static const ControlAction CONTROL_ACTION_RESUME = + ControlAction._(3, _omitEnumNames ? '' : 'CONTROL_ACTION_RESUME'); + static const ControlAction CONTROL_ACTION_ROLLBACK = + ControlAction._(4, _omitEnumNames ? '' : 'CONTROL_ACTION_ROLLBACK'); + static const ControlAction CONTROL_ACTION_RETIRE = + ControlAction._(5, _omitEnumNames ? '' : 'CONTROL_ACTION_RETIRE'); + + static const $core.List values = [ + CONTROL_ACTION_UNSPECIFIED, + CONTROL_ACTION_PAUSE, + CONTROL_ACTION_SUSPEND, + CONTROL_ACTION_RESUME, + CONTROL_ACTION_ROLLBACK, + CONTROL_ACTION_RETIRE, + ]; + + static final $core.List _byValue = + $pb.ProtobufEnum.$_initByValueList(values, 5); + static ControlAction? valueOf($core.int value) => + value < 0 || value >= _byValue.length ? null : _byValue[value]; + + const ControlAction._(super.value, super.name); +} + +const $core.bool _omitEnumNames = + $core.bool.fromEnvironment('protobuf.omit_enum_names'); diff --git a/gen/dart/xtcp_treatment/v1/operator.pbgrpc.dart b/gen/dart/xtcp_treatment/v1/operator.pbgrpc.dart new file mode 100644 index 000000000..0fcc2843d --- /dev/null +++ b/gen/dart/xtcp_treatment/v1/operator.pbgrpc.dart @@ -0,0 +1,180 @@ +// This is a generated file - do not edit. +// +// Generated from xtcp_treatment/v1/operator.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports + +import 'dart:async' as $async; +import 'dart:core' as $core; + +import 'package:grpc/service_api.dart' as $grpc; +import 'package:protobuf/protobuf.dart' as $pb; + +import 'operator.pb.dart' as $0; + +export 'operator.pb.dart'; + +@$pb.GrpcServiceName('xtcp_treatment.v1.OperatorService') +class OperatorServiceClient extends $grpc.Client { + /// The hostname for this service. + static const $core.String defaultHost = ''; + + /// OAuth scopes needed for the client. + static const $core.List<$core.String> oauthScopes = [ + '', + ]; + + OperatorServiceClient(super.channel, {super.options, super.interceptors}); + + $grpc.ResponseFuture<$0.ProposeResponse> propose( + $0.ProposeRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$propose, request, options: options); + } + + $grpc.ResponseFuture<$0.AnalyzeResponse> analyze( + $0.AnalyzeRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$analyze, request, options: options); + } + + $grpc.ResponseFuture<$0.ReviewResponse> review( + $0.ReviewRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$review, request, options: options); + } + + $grpc.ResponseFuture<$0.ControlResponse> control( + $0.ControlRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$control, request, options: options); + } + + $grpc.ResponseFuture<$0.InspectResponse> inspect( + $0.InspectRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$inspect, request, options: options); + } + + // method descriptors + + static final _$propose = + $grpc.ClientMethod<$0.ProposeRequest, $0.ProposeResponse>( + '/xtcp_treatment.v1.OperatorService/Propose', + ($0.ProposeRequest value) => value.writeToBuffer(), + $0.ProposeResponse.fromBuffer); + static final _$analyze = + $grpc.ClientMethod<$0.AnalyzeRequest, $0.AnalyzeResponse>( + '/xtcp_treatment.v1.OperatorService/Analyze', + ($0.AnalyzeRequest value) => value.writeToBuffer(), + $0.AnalyzeResponse.fromBuffer); + static final _$review = + $grpc.ClientMethod<$0.ReviewRequest, $0.ReviewResponse>( + '/xtcp_treatment.v1.OperatorService/Review', + ($0.ReviewRequest value) => value.writeToBuffer(), + $0.ReviewResponse.fromBuffer); + static final _$control = + $grpc.ClientMethod<$0.ControlRequest, $0.ControlResponse>( + '/xtcp_treatment.v1.OperatorService/Control', + ($0.ControlRequest value) => value.writeToBuffer(), + $0.ControlResponse.fromBuffer); + static final _$inspect = + $grpc.ClientMethod<$0.InspectRequest, $0.InspectResponse>( + '/xtcp_treatment.v1.OperatorService/Inspect', + ($0.InspectRequest value) => value.writeToBuffer(), + $0.InspectResponse.fromBuffer); +} + +@$pb.GrpcServiceName('xtcp_treatment.v1.OperatorService') +abstract class OperatorServiceBase extends $grpc.Service { + $core.String get $name => 'xtcp_treatment.v1.OperatorService'; + + OperatorServiceBase() { + $addMethod($grpc.ServiceMethod<$0.ProposeRequest, $0.ProposeResponse>( + 'Propose', + propose_Pre, + false, + false, + ($core.List<$core.int> value) => $0.ProposeRequest.fromBuffer(value), + ($0.ProposeResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.AnalyzeRequest, $0.AnalyzeResponse>( + 'Analyze', + analyze_Pre, + false, + false, + ($core.List<$core.int> value) => $0.AnalyzeRequest.fromBuffer(value), + ($0.AnalyzeResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.ReviewRequest, $0.ReviewResponse>( + 'Review', + review_Pre, + false, + false, + ($core.List<$core.int> value) => $0.ReviewRequest.fromBuffer(value), + ($0.ReviewResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.ControlRequest, $0.ControlResponse>( + 'Control', + control_Pre, + false, + false, + ($core.List<$core.int> value) => $0.ControlRequest.fromBuffer(value), + ($0.ControlResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.InspectRequest, $0.InspectResponse>( + 'Inspect', + inspect_Pre, + false, + false, + ($core.List<$core.int> value) => $0.InspectRequest.fromBuffer(value), + ($0.InspectResponse value) => value.writeToBuffer())); + } + + $async.Future<$0.ProposeResponse> propose_Pre($grpc.ServiceCall $call, + $async.Future<$0.ProposeRequest> $request) async { + return propose($call, await $request); + } + + $async.Future<$0.ProposeResponse> propose( + $grpc.ServiceCall call, $0.ProposeRequest request); + + $async.Future<$0.AnalyzeResponse> analyze_Pre($grpc.ServiceCall $call, + $async.Future<$0.AnalyzeRequest> $request) async { + return analyze($call, await $request); + } + + $async.Future<$0.AnalyzeResponse> analyze( + $grpc.ServiceCall call, $0.AnalyzeRequest request); + + $async.Future<$0.ReviewResponse> review_Pre( + $grpc.ServiceCall $call, $async.Future<$0.ReviewRequest> $request) async { + return review($call, await $request); + } + + $async.Future<$0.ReviewResponse> review( + $grpc.ServiceCall call, $0.ReviewRequest request); + + $async.Future<$0.ControlResponse> control_Pre($grpc.ServiceCall $call, + $async.Future<$0.ControlRequest> $request) async { + return control($call, await $request); + } + + $async.Future<$0.ControlResponse> control( + $grpc.ServiceCall call, $0.ControlRequest request); + + $async.Future<$0.InspectResponse> inspect_Pre($grpc.ServiceCall $call, + $async.Future<$0.InspectRequest> $request) async { + return inspect($call, await $request); + } + + $async.Future<$0.InspectResponse> inspect( + $grpc.ServiceCall call, $0.InspectRequest request); +} diff --git a/gen/dart/xtcp_treatment/v1/operator.pbjson.dart b/gen/dart/xtcp_treatment/v1/operator.pbjson.dart new file mode 100644 index 000000000..432ed42cf --- /dev/null +++ b/gen/dart/xtcp_treatment/v1/operator.pbjson.dart @@ -0,0 +1,320 @@ +// This is a generated file - do not edit. +// +// Generated from xtcp_treatment/v1/operator.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports +// ignore_for_file: unused_import + +import 'dart:convert' as $convert; +import 'dart:core' as $core; +import 'dart:typed_data' as $typed_data; + +@$core.Deprecated('Use controlActionDescriptor instead') +const ControlAction$json = { + '1': 'ControlAction', + '2': [ + {'1': 'CONTROL_ACTION_UNSPECIFIED', '2': 0}, + {'1': 'CONTROL_ACTION_PAUSE', '2': 1}, + {'1': 'CONTROL_ACTION_SUSPEND', '2': 2}, + {'1': 'CONTROL_ACTION_RESUME', '2': 3}, + {'1': 'CONTROL_ACTION_ROLLBACK', '2': 4}, + {'1': 'CONTROL_ACTION_RETIRE', '2': 5}, + ], +}; + +/// Descriptor for `ControlAction`. Decode as a `google.protobuf.EnumDescriptorProto`. +final $typed_data.Uint8List controlActionDescriptor = $convert.base64Decode( + 'Cg1Db250cm9sQWN0aW9uEh4KGkNPTlRST0xfQUNUSU9OX1VOU1BFQ0lGSUVEEAASGAoUQ09OVF' + 'JPTF9BQ1RJT05fUEFVU0UQARIaChZDT05UUk9MX0FDVElPTl9TVVNQRU5EEAISGQoVQ09OVFJP' + 'TF9BQ1RJT05fUkVTVU1FEAMSGwoXQ09OVFJPTF9BQ1RJT05fUk9MTEJBQ0sQBBIZChVDT05UUk' + '9MX0FDVElPTl9SRVRJUkUQBQ=='); + +@$core.Deprecated('Use proposeRequestDescriptor instead') +const ProposeRequest$json = { + '1': 'ProposeRequest', + '2': [ + { + '1': 'experiment', + '3': 1, + '4': 1, + '5': 11, + '6': '.xtcp_treatment.v1.Experiment', + '8': {}, + '10': 'experiment' + }, + {'1': 'request_id', '3': 2, '4': 1, '5': 9, '8': {}, '10': 'requestId'}, + ], + '7': {}, +}; + +/// Descriptor for `ProposeRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List proposeRequestDescriptor = $convert.base64Decode( + 'Cg5Qcm9wb3NlUmVxdWVzdBJFCgpleHBlcmltZW50GAEgASgLMh0ueHRjcF90cmVhdG1lbnQudj' + 'EuRXhwZXJpbWVudEIGukgDyAEBUgpleHBlcmltZW50EkgKCnJlcXVlc3RfaWQYAiABKAlCKbpI' + 'JnIkMiJeW2EtekEtWjAtOV1bYS16QS1aMC05Xy4tXXswLDEyN30kUglyZXF1ZXN0SWQ6lwG6SJ' + 'MBGpABChlwcm9wb3NlLnJlcXVlc3RfaWRfc3VmZml4Ek1yZXF1ZXN0X2lkIG11c3Qgbm90IGVu' + 'ZCB3aXRoIC5yZXN1bHQgKHJlc2VydmVkIGZvciB0aGUgcGFpcmVkIHJlc3BvbnNlIGV2ZW50KR' + 'okIXRoaXMucmVxdWVzdF9pZC5lbmRzV2l0aCgnLnJlc3VsdCcp'); + +@$core.Deprecated('Use proposeResponseDescriptor instead') +const ProposeResponse$json = { + '1': 'ProposeResponse', + '2': [ + { + '1': 'experiment', + '3': 1, + '4': 1, + '5': 11, + '6': '.xtcp_treatment.v1.Experiment', + '10': 'experiment' + }, + ], +}; + +/// Descriptor for `ProposeResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List proposeResponseDescriptor = $convert.base64Decode( + 'Cg9Qcm9wb3NlUmVzcG9uc2USPQoKZXhwZXJpbWVudBgBIAEoCzIdLnh0Y3BfdHJlYXRtZW50Ln' + 'YxLkV4cGVyaW1lbnRSCmV4cGVyaW1lbnQ='); + +@$core.Deprecated('Use analyzeRequestDescriptor instead') +const AnalyzeRequest$json = { + '1': 'AnalyzeRequest', + '2': [ + { + '1': 'experiment_id', + '3': 1, + '4': 1, + '5': 9, + '8': {}, + '10': 'experimentId' + }, + { + '1': 'expected_revision', + '3': 2, + '4': 1, + '5': 4, + '10': 'expectedRevision' + }, + ], +}; + +/// Descriptor for `AnalyzeRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List analyzeRequestDescriptor = $convert.base64Decode( + 'Cg5BbmFseXplUmVxdWVzdBJOCg1leHBlcmltZW50X2lkGAEgASgJQim6SCZyJDIiXlthLXpBLV' + 'owLTldW2EtekEtWjAtOV8uLV17MCwxMjd9JFIMZXhwZXJpbWVudElkEisKEWV4cGVjdGVkX3Jl' + 'dmlzaW9uGAIgASgEUhBleHBlY3RlZFJldmlzaW9u'); + +@$core.Deprecated('Use analyzeResponseDescriptor instead') +const AnalyzeResponse$json = { + '1': 'AnalyzeResponse', + '2': [ + { + '1': 'evidence', + '3': 1, + '4': 1, + '5': 11, + '6': '.xtcp_treatment.v1.Evidence', + '10': 'evidence' + }, + {'1': 'outcome', '3': 2, '4': 1, '5': 9, '10': 'outcome'}, + ], +}; + +/// Descriptor for `AnalyzeResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List analyzeResponseDescriptor = $convert.base64Decode( + 'Cg9BbmFseXplUmVzcG9uc2USNwoIZXZpZGVuY2UYASABKAsyGy54dGNwX3RyZWF0bWVudC52MS' + '5FdmlkZW5jZVIIZXZpZGVuY2USGAoHb3V0Y29tZRgCIAEoCVIHb3V0Y29tZQ=='); + +@$core.Deprecated('Use reviewRequestDescriptor instead') +const ReviewRequest$json = { + '1': 'ReviewRequest', + '2': [ + { + '1': 'experiment_id', + '3': 1, + '4': 1, + '5': 9, + '8': {}, + '10': 'experimentId' + }, + { + '1': 'expected_revision', + '3': 2, + '4': 1, + '5': 4, + '10': 'expectedRevision' + }, + {'1': 'approve', '3': 3, '4': 1, '5': 8, '10': 'approve'}, + {'1': 'reason', '3': 4, '4': 1, '5': 9, '8': {}, '10': 'reason'}, + {'1': 'request_id', '3': 5, '4': 1, '5': 9, '8': {}, '10': 'requestId'}, + { + '1': 'evidence', + '3': 6, + '4': 1, + '5': 11, + '6': '.xtcp_treatment.v1.Evidence', + '10': 'evidence' + }, + ], + '7': {}, +}; + +/// Descriptor for `ReviewRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List reviewRequestDescriptor = $convert.base64Decode( + 'Cg1SZXZpZXdSZXF1ZXN0Ek4KDWV4cGVyaW1lbnRfaWQYASABKAlCKbpIJnIkMiJeW2EtekEtWj' + 'AtOV1bYS16QS1aMC05Xy4tXXswLDEyN30kUgxleHBlcmltZW50SWQSKwoRZXhwZWN0ZWRfcmV2' + 'aXNpb24YAiABKARSEGV4cGVjdGVkUmV2aXNpb24SGAoHYXBwcm92ZRgDIAEoCFIHYXBwcm92ZR' + 'IiCgZyZWFzb24YBCABKAlCCrpIB3IFEAEYgCBSBnJlYXNvbhJICgpyZXF1ZXN0X2lkGAUgASgJ' + 'Qim6SCZyJDIiXlthLXpBLVowLTldW2EtekEtWjAtOV8uLV17MCwxMjd9JFIJcmVxdWVzdElkEj' + 'cKCGV2aWRlbmNlGAYgASgLMhsueHRjcF90cmVhdG1lbnQudjEuRXZpZGVuY2VSCGV2aWRlbmNl' + 'OpYBukiSARqPAQoYcmV2aWV3LnJlcXVlc3RfaWRfc3VmZml4Ek1yZXF1ZXN0X2lkIG11c3Qgbm' + '90IGVuZCB3aXRoIC5yZXN1bHQgKHJlc2VydmVkIGZvciB0aGUgcGFpcmVkIHJlc3BvbnNlIGV2' + 'ZW50KRokIXRoaXMucmVxdWVzdF9pZC5lbmRzV2l0aCgnLnJlc3VsdCcp'); + +@$core.Deprecated('Use reviewResponseDescriptor instead') +const ReviewResponse$json = { + '1': 'ReviewResponse', + '2': [ + { + '1': 'experiment', + '3': 1, + '4': 1, + '5': 11, + '6': '.xtcp_treatment.v1.Experiment', + '10': 'experiment' + }, + ], +}; + +/// Descriptor for `ReviewResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List reviewResponseDescriptor = $convert.base64Decode( + 'Cg5SZXZpZXdSZXNwb25zZRI9CgpleHBlcmltZW50GAEgASgLMh0ueHRjcF90cmVhdG1lbnQudj' + 'EuRXhwZXJpbWVudFIKZXhwZXJpbWVudA=='); + +@$core.Deprecated('Use controlRequestDescriptor instead') +const ControlRequest$json = { + '1': 'ControlRequest', + '2': [ + { + '1': 'experiment_id', + '3': 1, + '4': 1, + '5': 9, + '8': {}, + '10': 'experimentId' + }, + {'1': 'agent_id', '3': 2, '4': 1, '5': 9, '8': {}, '10': 'agentId'}, + {'1': 'location', '3': 3, '4': 1, '5': 9, '8': {}, '10': 'location'}, + {'1': 'fleet', '3': 4, '4': 1, '5': 8, '10': 'fleet'}, + { + '1': 'action', + '3': 5, + '4': 1, + '5': 14, + '6': '.xtcp_treatment.v1.ControlAction', + '8': {}, + '10': 'action' + }, + {'1': 'reason', '3': 6, '4': 1, '5': 9, '8': {}, '10': 'reason'}, + {'1': 'request_id', '3': 7, '4': 1, '5': 9, '8': {}, '10': 'requestId'}, + ], + '7': {}, +}; + +/// Descriptor for `ControlRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List controlRequestDescriptor = $convert.base64Decode( + 'Cg5Db250cm9sUmVxdWVzdBJRCg1leHBlcmltZW50X2lkGAEgASgJQiy6SCnYAQFyJDIiXlthLX' + 'pBLVowLTldW2EtekEtWjAtOV8uLV17MCwxMjd9JFIMZXhwZXJpbWVudElkEkcKCGFnZW50X2lk' + 'GAIgASgJQiy6SCnYAQFyJDIiXlthLXpBLVowLTldW2EtekEtWjAtOV8uLV17MCwxMjd9JFIHYW' + 'dlbnRJZBJICghsb2NhdGlvbhgDIAEoCUIsukgp2AEBciQyIl5bYS16QS1aMC05XVthLXpBLVow' + 'LTlfLi1dezAsMTI3fSRSCGxvY2F0aW9uEhQKBWZsZWV0GAQgASgIUgVmbGVldBJECgZhY3Rpb2' + '4YBSABKA4yIC54dGNwX3RyZWF0bWVudC52MS5Db250cm9sQWN0aW9uQgq6SAeCAQQQASAAUgZh' + 'Y3Rpb24SIgoGcmVhc29uGAYgASgJQgq6SAdyBRABGIAgUgZyZWFzb24SSAoKcmVxdWVzdF9pZB' + 'gHIAEoCUIpukgmciQyIl5bYS16QS1aMC05XVthLXpBLVowLTlfLi1dezAsMTI3fSRSCXJlcXVl' + 'c3RJZDr3ArpI8wIakAEKGWNvbnRyb2wucmVxdWVzdF9pZF9zdWZmaXgSTXJlcXVlc3RfaWQgbX' + 'VzdCBub3QgZW5kIHdpdGggLnJlc3VsdCAocmVzZXJ2ZWQgZm9yIHRoZSBwYWlyZWQgcmVzcG9u' + 'c2UgZXZlbnQpGiQhdGhpcy5yZXF1ZXN0X2lkLmVuZHNXaXRoKCcucmVzdWx0Jyka3QEKFGNvbn' + 'Ryb2wub25lX3NlbGVjdG9yEkVleGFjdGx5IG9uZSBvZiBleHBlcmltZW50X2lkLCBhZ2VudF9p' + 'ZCwgbG9jYXRpb24gb3IgZmxlZXQgaXMgcmVxdWlyZWQafih0aGlzLmZsZWV0ID8gMSA6IDApIC' + 'sgKHRoaXMuZXhwZXJpbWVudF9pZCAhPSAnJyA/IDEgOiAwKSArICh0aGlzLmFnZW50X2lkICE9' + 'ICcnID8gMSA6IDApICsgKHRoaXMubG9jYXRpb24gIT0gJycgPyAxIDogMCkgPT0gMQ=='); + +@$core.Deprecated('Use controlResponseDescriptor instead') +const ControlResponse$json = { + '1': 'ControlResponse', + '2': [ + {'1': 'affected_agents', '3': 1, '4': 1, '5': 13, '10': 'affectedAgents'}, + ], +}; + +/// Descriptor for `ControlResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List controlResponseDescriptor = $convert.base64Decode( + 'Cg9Db250cm9sUmVzcG9uc2USJwoPYWZmZWN0ZWRfYWdlbnRzGAEgASgNUg5hZmZlY3RlZEFnZW' + '50cw=='); + +@$core.Deprecated('Use inspectRequestDescriptor instead') +const InspectRequest$json = { + '1': 'InspectRequest', + '2': [ + { + '1': 'experiment_id', + '3': 1, + '4': 1, + '5': 9, + '8': {}, + '10': 'experimentId' + }, + {'1': 'agent_id', '3': 2, '4': 1, '5': 9, '8': {}, '10': 'agentId'}, + ], +}; + +/// Descriptor for `InspectRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List inspectRequestDescriptor = $convert.base64Decode( + 'Cg5JbnNwZWN0UmVxdWVzdBJRCg1leHBlcmltZW50X2lkGAEgASgJQiy6SCnYAQFyJDIiXlthLX' + 'pBLVowLTldW2EtekEtWjAtOV8uLV17MCwxMjd9JFIMZXhwZXJpbWVudElkEkcKCGFnZW50X2lk' + 'GAIgASgJQiy6SCnYAQFyJDIiXlthLXpBLVowLTldW2EtekEtWjAtOV8uLV17MCwxMjd9JFIHYW' + 'dlbnRJZA=='); + +@$core.Deprecated('Use inspectResponseDescriptor instead') +const InspectResponse$json = { + '1': 'InspectResponse', + '2': [ + { + '1': 'experiments', + '3': 1, + '4': 3, + '5': 11, + '6': '.xtcp_treatment.v1.Experiment', + '10': 'experiments' + }, + { + '1': 'desired', + '3': 2, + '4': 3, + '5': 11, + '6': '.xtcp_treatment.v1.DesiredState', + '10': 'desired' + }, + { + '1': 'observed', + '3': 3, + '4': 3, + '5': 11, + '6': '.xtcp_treatment.v1.CheckInRequest', + '10': 'observed' + }, + ], +}; + +/// Descriptor for `InspectResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List inspectResponseDescriptor = $convert.base64Decode( + 'Cg9JbnNwZWN0UmVzcG9uc2USPwoLZXhwZXJpbWVudHMYASADKAsyHS54dGNwX3RyZWF0bWVudC' + '52MS5FeHBlcmltZW50UgtleHBlcmltZW50cxI5CgdkZXNpcmVkGAIgAygLMh8ueHRjcF90cmVh' + 'dG1lbnQudjEuRGVzaXJlZFN0YXRlUgdkZXNpcmVkEj0KCG9ic2VydmVkGAMgAygLMiEueHRjcF' + '90cmVhdG1lbnQudjEuQ2hlY2tJblJlcXVlc3RSCG9ic2VydmVk'); diff --git a/gen/dart/xtcp_treatment/v1/treatment.pb.dart b/gen/dart/xtcp_treatment/v1/treatment.pb.dart new file mode 100644 index 000000000..fd2fc0426 --- /dev/null +++ b/gen/dart/xtcp_treatment/v1/treatment.pb.dart @@ -0,0 +1,1799 @@ +// This is a generated file - do not edit. +// +// Generated from xtcp_treatment/v1/treatment.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports + +import 'dart:core' as $core; + +import 'package:fixnum/fixnum.dart' as $fixnum; +import 'package:protobuf/protobuf.dart' as $pb; +import 'package:protobuf/well_known_types/google/protobuf/duration.pb.dart' + as $1; +import 'package:protobuf/well_known_types/google/protobuf/timestamp.pb.dart' + as $0; + +import 'treatment.pbenum.dart'; + +export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions; + +export 'treatment.pbenum.dart'; + +/// TCPSettings are the per-route TCP parameters a treatment installs. Every +/// field is optional; at least one must be present. +class TCPSettings extends $pb.GeneratedMessage { + factory TCPSettings({ + $core.String? congestionControl, + $core.bool? congestionLock, + $core.int? rtoMinMs, + $core.int? initialCongestionWindow, + $core.int? initialReceiveWindow, + $core.bool? quickAck, + $core.bool? ecn, + }) { + final result = TCPSettings._(); + if (congestionControl != null) result.congestionControl = congestionControl; + if (congestionLock != null) result.congestionLock = congestionLock; + if (rtoMinMs != null) result.rtoMinMs = rtoMinMs; + if (initialCongestionWindow != null) + result.initialCongestionWindow = initialCongestionWindow; + if (initialReceiveWindow != null) + result.initialReceiveWindow = initialReceiveWindow; + if (quickAck != null) result.quickAck = quickAck; + if (ecn != null) result.ecn = ecn; + return result; + } + + TCPSettings._(); + + factory TCPSettings.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + TCPSettings()..mergeFromBuffer(data, registry); + factory TCPSettings.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + TCPSettings()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'TCPSettings', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'xtcp_treatment.v1'), + createEmptyInstance: TCPSettings.$_createMessage) + ..aOS(1, _omitFieldNames ? '' : 'congestionControl') + ..aOB(2, _omitFieldNames ? '' : 'congestionLock') + ..aI(3, _omitFieldNames ? '' : 'rtoMinMs', fieldType: $pb.PbFieldType.OU3) + ..aI(4, _omitFieldNames ? '' : 'initialCongestionWindow', + fieldType: $pb.PbFieldType.OU3) + ..aI(5, _omitFieldNames ? '' : 'initialReceiveWindow', + fieldType: $pb.PbFieldType.OU3) + ..aOB(6, _omitFieldNames ? '' : 'quickAck') + ..aOB(7, _omitFieldNames ? '' : 'ecn') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + TCPSettings clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + TCPSettings copyWith(void Function(TCPSettings) updates) => + super.copyWith((message) => updates(message as TCPSettings)) + as TCPSettings; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated('Use TCPSettings() / TCPSettings.new instead') + static TCPSettings create() => TCPSettings._(); + static $pb.GeneratedMessage $_createMessage() => TCPSettings._(); + @$core.override + TCPSettings createEmptyInstance() => TCPSettings._(); + @$core.pragma('dart2js:noInline') + static TCPSettings getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor( + TCPSettings.$_createMessage); + static TCPSettings? _defaultInstance; + + /// Kernel algorithm name (TCP_CA_NAME_MAX is 16 including NUL). + @$pb.TagNumber(1) + $core.String get congestionControl => $_getSZ(0); + @$pb.TagNumber(1) + set congestionControl($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasCongestionControl() => $_has(0); + @$pb.TagNumber(1) + void clearCongestionControl() => $_clearField(1); + + @$pb.TagNumber(2) + $core.bool get congestionLock => $_getBF(1); + @$pb.TagNumber(2) + set congestionLock($core.bool value) => $_setBool(1, value); + @$pb.TagNumber(2) + $core.bool hasCongestionLock() => $_has(1); + @$pb.TagNumber(2) + void clearCongestionLock() => $_clearField(2); + + @$pb.TagNumber(3) + $core.int get rtoMinMs => $_getIZ(2); + @$pb.TagNumber(3) + set rtoMinMs($core.int value) => $_setUnsignedInt32(2, value); + @$pb.TagNumber(3) + $core.bool hasRtoMinMs() => $_has(2); + @$pb.TagNumber(3) + void clearRtoMinMs() => $_clearField(3); + + @$pb.TagNumber(4) + $core.int get initialCongestionWindow => $_getIZ(3); + @$pb.TagNumber(4) + set initialCongestionWindow($core.int value) => $_setUnsignedInt32(3, value); + @$pb.TagNumber(4) + $core.bool hasInitialCongestionWindow() => $_has(3); + @$pb.TagNumber(4) + void clearInitialCongestionWindow() => $_clearField(4); + + @$pb.TagNumber(5) + $core.int get initialReceiveWindow => $_getIZ(4); + @$pb.TagNumber(5) + set initialReceiveWindow($core.int value) => $_setUnsignedInt32(4, value); + @$pb.TagNumber(5) + $core.bool hasInitialReceiveWindow() => $_has(4); + @$pb.TagNumber(5) + void clearInitialReceiveWindow() => $_clearField(5); + + @$pb.TagNumber(6) + $core.bool get quickAck => $_getBF(5); + @$pb.TagNumber(6) + set quickAck($core.bool value) => $_setBool(5, value); + @$pb.TagNumber(6) + $core.bool hasQuickAck() => $_has(5); + @$pb.TagNumber(6) + void clearQuickAck() => $_clearField(6); + + @$pb.TagNumber(7) + $core.bool get ecn => $_getBF(6); + @$pb.TagNumber(7) + set ecn($core.bool value) => $_setBool(6, value); + @$pb.TagNumber(7) + $core.bool hasEcn() => $_has(6); + @$pb.TagNumber(7) + void clearEcn() => $_clearField(7); +} + +class Treatment extends $pb.GeneratedMessage { + factory Treatment({ + $core.String? id, + $fixnum.Int64? revision, + $core.String? destination, + TCPSettings? settings, + $core.bool? ecnFabricVerified, + }) { + final result = Treatment._(); + if (id != null) result.id = id; + if (revision != null) result.revision = revision; + if (destination != null) result.destination = destination; + if (settings != null) result.settings = settings; + if (ecnFabricVerified != null) result.ecnFabricVerified = ecnFabricVerified; + return result; + } + + Treatment._(); + + factory Treatment.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + Treatment()..mergeFromBuffer(data, registry); + factory Treatment.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + Treatment()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'Treatment', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'xtcp_treatment.v1'), + createEmptyInstance: Treatment.$_createMessage) + ..aOS(1, _omitFieldNames ? '' : 'id') + ..a<$fixnum.Int64>( + 2, _omitFieldNames ? '' : 'revision', $pb.PbFieldType.OU6, + defaultOrMaker: $fixnum.Int64.ZERO) + ..aOS(3, _omitFieldNames ? '' : 'destination') + ..aOM(4, _omitFieldNames ? '' : 'settings', + subBuilder: TCPSettings.$_createMessage) + ..aOB(5, _omitFieldNames ? '' : 'ecnFabricVerified') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + Treatment clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + Treatment copyWith(void Function(Treatment) updates) => + super.copyWith((message) => updates(message as Treatment)) as Treatment; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated('Use Treatment() / Treatment.new instead') + static Treatment create() => Treatment._(); + static $pb.GeneratedMessage $_createMessage() => Treatment._(); + @$core.override + Treatment createEmptyInstance() => Treatment._(); + @$core.pragma('dart2js:noInline') + static Treatment getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(Treatment.$_createMessage); + static Treatment? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get id => $_getSZ(0); + @$pb.TagNumber(1) + set id($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasId() => $_has(0); + @$pb.TagNumber(1) + void clearId() => $_clearField(1); + + @$pb.TagNumber(2) + $fixnum.Int64 get revision => $_getI64(1); + @$pb.TagNumber(2) + set revision($fixnum.Int64 value) => $_setInt64(1, value); + @$pb.TagNumber(2) + $core.bool hasRevision() => $_has(1); + @$pb.TagNumber(2) + void clearRevision() => $_clearField(2); + + /// Canonical unicast host prefix (/32 or /128). Canonical form, unicast and + /// not-loopback are checked by treatment.ValidateTreatment. + @$pb.TagNumber(3) + $core.String get destination => $_getSZ(2); + @$pb.TagNumber(3) + set destination($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasDestination() => $_has(2); + @$pb.TagNumber(3) + void clearDestination() => $_clearField(3); + + @$pb.TagNumber(4) + TCPSettings get settings => $_getN(3); + @$pb.TagNumber(4) + set settings(TCPSettings value) => $_setField(4, value); + @$pb.TagNumber(4) + $core.bool hasSettings() => $_has(3); + @$pb.TagNumber(4) + void clearSettings() => $_clearField(4); + @$pb.TagNumber(4) + TCPSettings ensureSettings() => $_ensure(3); + + @$pb.TagNumber(5) + $core.bool get ecnFabricVerified => $_getBF(4); + @$pb.TagNumber(5) + set ecnFabricVerified($core.bool value) => $_setBool(4, value); + @$pb.TagNumber(5) + $core.bool hasEcnFabricVerified() => $_has(4); + @$pb.TagNumber(5) + void clearEcnFabricVerified() => $_clearField(5); +} + +class Assignment extends $pb.GeneratedMessage { + factory Assignment({ + $core.String? id, + $core.String? experimentId, + Treatment? treatment, + AssignmentMode? mode, + $0.Timestamp? leaseExpiresAt, + Treatment? predecessor, + $fixnum.Int64? generation, + }) { + final result = Assignment._(); + if (id != null) result.id = id; + if (experimentId != null) result.experimentId = experimentId; + if (treatment != null) result.treatment = treatment; + if (mode != null) result.mode = mode; + if (leaseExpiresAt != null) result.leaseExpiresAt = leaseExpiresAt; + if (predecessor != null) result.predecessor = predecessor; + if (generation != null) result.generation = generation; + return result; + } + + Assignment._(); + + factory Assignment.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + Assignment()..mergeFromBuffer(data, registry); + factory Assignment.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + Assignment()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'Assignment', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'xtcp_treatment.v1'), + createEmptyInstance: Assignment.$_createMessage) + ..aOS(1, _omitFieldNames ? '' : 'id') + ..aOS(2, _omitFieldNames ? '' : 'experimentId') + ..aOM(3, _omitFieldNames ? '' : 'treatment', + subBuilder: Treatment.$_createMessage) + ..aE(4, _omitFieldNames ? '' : 'mode', + enumValues: AssignmentMode.values) + ..aOM<$0.Timestamp>(5, _omitFieldNames ? '' : 'leaseExpiresAt', + subBuilder: $0.Timestamp.$_createMessage) + ..aOM(6, _omitFieldNames ? '' : 'predecessor', + subBuilder: Treatment.$_createMessage) + ..a<$fixnum.Int64>( + 7, _omitFieldNames ? '' : 'generation', $pb.PbFieldType.OU6, + defaultOrMaker: $fixnum.Int64.ZERO) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + Assignment clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + Assignment copyWith(void Function(Assignment) updates) => + super.copyWith((message) => updates(message as Assignment)) as Assignment; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated('Use Assignment() / Assignment.new instead') + static Assignment create() => Assignment._(); + static $pb.GeneratedMessage $_createMessage() => Assignment._(); + @$core.override + Assignment createEmptyInstance() => Assignment._(); + @$core.pragma('dart2js:noInline') + static Assignment getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(Assignment.$_createMessage); + static Assignment? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get id => $_getSZ(0); + @$pb.TagNumber(1) + set id($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasId() => $_has(0); + @$pb.TagNumber(1) + void clearId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get experimentId => $_getSZ(1); + @$pb.TagNumber(2) + set experimentId($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasExperimentId() => $_has(1); + @$pb.TagNumber(2) + void clearExperimentId() => $_clearField(2); + + @$pb.TagNumber(3) + Treatment get treatment => $_getN(2); + @$pb.TagNumber(3) + set treatment(Treatment value) => $_setField(3, value); + @$pb.TagNumber(3) + $core.bool hasTreatment() => $_has(2); + @$pb.TagNumber(3) + void clearTreatment() => $_clearField(3); + @$pb.TagNumber(3) + Treatment ensureTreatment() => $_ensure(2); + + @$pb.TagNumber(4) + AssignmentMode get mode => $_getN(3); + @$pb.TagNumber(4) + set mode(AssignmentMode value) => $_setField(4, value); + @$pb.TagNumber(4) + $core.bool hasMode() => $_has(3); + @$pb.TagNumber(4) + void clearMode() => $_clearField(4); + + @$pb.TagNumber(5) + $0.Timestamp get leaseExpiresAt => $_getN(4); + @$pb.TagNumber(5) + set leaseExpiresAt($0.Timestamp value) => $_setField(5, value); + @$pb.TagNumber(5) + $core.bool hasLeaseExpiresAt() => $_has(4); + @$pb.TagNumber(5) + void clearLeaseExpiresAt() => $_clearField(5); + @$pb.TagNumber(5) + $0.Timestamp ensureLeaseExpiresAt() => $_ensure(4); + + /// Previous approved stable treatment, restored on trial expiry/rollback. + @$pb.TagNumber(6) + Treatment get predecessor => $_getN(5); + @$pb.TagNumber(6) + set predecessor(Treatment value) => $_setField(6, value); + @$pb.TagNumber(6) + $core.bool hasPredecessor() => $_has(5); + @$pb.TagNumber(6) + void clearPredecessor() => $_clearField(6); + @$pb.TagNumber(6) + Treatment ensurePredecessor() => $_ensure(5); + + /// Increment only on an explicitly approved reactivation, not lease renewal. + @$pb.TagNumber(7) + $fixnum.Int64 get generation => $_getI64(6); + @$pb.TagNumber(7) + set generation($fixnum.Int64 value) => $_setInt64(6, value); + @$pb.TagNumber(7) + $core.bool hasGeneration() => $_has(6); + @$pb.TagNumber(7) + void clearGeneration() => $_clearField(7); +} + +/// DesiredState is the complete revisioned snapshot an agent reconciles to. +/// Duplicate assignment IDs and overlapping destinations are rejected by +/// treatment.ValidateDesired. +class DesiredState extends $pb.GeneratedMessage { + factory DesiredState({ + $core.String? agentId, + $fixnum.Int64? revision, + $core.bool? suspended, + $core.Iterable? assignments, + $0.Timestamp? issuedAt, + }) { + final result = DesiredState._(); + if (agentId != null) result.agentId = agentId; + if (revision != null) result.revision = revision; + if (suspended != null) result.suspended = suspended; + if (assignments != null) result.assignments.addAll(assignments); + if (issuedAt != null) result.issuedAt = issuedAt; + return result; + } + + DesiredState._(); + + factory DesiredState.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + DesiredState()..mergeFromBuffer(data, registry); + factory DesiredState.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + DesiredState()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'DesiredState', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'xtcp_treatment.v1'), + createEmptyInstance: DesiredState.$_createMessage) + ..aOS(1, _omitFieldNames ? '' : 'agentId') + ..a<$fixnum.Int64>( + 2, _omitFieldNames ? '' : 'revision', $pb.PbFieldType.OU6, + defaultOrMaker: $fixnum.Int64.ZERO) + ..aOB(3, _omitFieldNames ? '' : 'suspended') + ..pPM(4, _omitFieldNames ? '' : 'assignments', + subBuilder: Assignment.$_createMessage) + ..aOM<$0.Timestamp>(5, _omitFieldNames ? '' : 'issuedAt', + subBuilder: $0.Timestamp.$_createMessage) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + DesiredState clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + DesiredState copyWith(void Function(DesiredState) updates) => + super.copyWith((message) => updates(message as DesiredState)) + as DesiredState; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated('Use DesiredState() / DesiredState.new instead') + static DesiredState create() => DesiredState._(); + static $pb.GeneratedMessage $_createMessage() => DesiredState._(); + @$core.override + DesiredState createEmptyInstance() => DesiredState._(); + @$core.pragma('dart2js:noInline') + static DesiredState getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor( + DesiredState.$_createMessage); + static DesiredState? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get agentId => $_getSZ(0); + @$pb.TagNumber(1) + set agentId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasAgentId() => $_has(0); + @$pb.TagNumber(1) + void clearAgentId() => $_clearField(1); + + @$pb.TagNumber(2) + $fixnum.Int64 get revision => $_getI64(1); + @$pb.TagNumber(2) + set revision($fixnum.Int64 value) => $_setInt64(1, value); + @$pb.TagNumber(2) + $core.bool hasRevision() => $_has(1); + @$pb.TagNumber(2) + void clearRevision() => $_clearField(2); + + @$pb.TagNumber(3) + $core.bool get suspended => $_getBF(2); + @$pb.TagNumber(3) + set suspended($core.bool value) => $_setBool(2, value); + @$pb.TagNumber(3) + $core.bool hasSuspended() => $_has(2); + @$pb.TagNumber(3) + void clearSuspended() => $_clearField(3); + + @$pb.TagNumber(4) + $pb.PbList get assignments => $_getList(3); + + @$pb.TagNumber(5) + $0.Timestamp get issuedAt => $_getN(4); + @$pb.TagNumber(5) + set issuedAt($0.Timestamp value) => $_setField(5, value); + @$pb.TagNumber(5) + $core.bool hasIssuedAt() => $_has(4); + @$pb.TagNumber(5) + void clearIssuedAt() => $_clearField(5); + @$pb.TagNumber(5) + $0.Timestamp ensureIssuedAt() => $_ensure(4); +} + +class Capabilities extends $pb.GeneratedMessage { + factory Capabilities({ + $core.String? bootId, + $core.String? kernelRelease, + $core.Iterable<$core.String>? congestionAlgorithms, + $core.bool? simpleHostRouting, + $core.bool? actuatorEnabled, + }) { + final result = Capabilities._(); + if (bootId != null) result.bootId = bootId; + if (kernelRelease != null) result.kernelRelease = kernelRelease; + if (congestionAlgorithms != null) + result.congestionAlgorithms.addAll(congestionAlgorithms); + if (simpleHostRouting != null) result.simpleHostRouting = simpleHostRouting; + if (actuatorEnabled != null) result.actuatorEnabled = actuatorEnabled; + return result; + } + + Capabilities._(); + + factory Capabilities.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + Capabilities()..mergeFromBuffer(data, registry); + factory Capabilities.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + Capabilities()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'Capabilities', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'xtcp_treatment.v1'), + createEmptyInstance: Capabilities.$_createMessage) + ..aOS(1, _omitFieldNames ? '' : 'bootId') + ..aOS(2, _omitFieldNames ? '' : 'kernelRelease') + ..pPS(3, _omitFieldNames ? '' : 'congestionAlgorithms') + ..aOB(4, _omitFieldNames ? '' : 'simpleHostRouting') + ..aOB(5, _omitFieldNames ? '' : 'actuatorEnabled') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + Capabilities clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + Capabilities copyWith(void Function(Capabilities) updates) => + super.copyWith((message) => updates(message as Capabilities)) + as Capabilities; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated('Use Capabilities() / Capabilities.new instead') + static Capabilities create() => Capabilities._(); + static $pb.GeneratedMessage $_createMessage() => Capabilities._(); + @$core.override + Capabilities createEmptyInstance() => Capabilities._(); + @$core.pragma('dart2js:noInline') + static Capabilities getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor( + Capabilities.$_createMessage); + static Capabilities? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get bootId => $_getSZ(0); + @$pb.TagNumber(1) + set bootId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasBootId() => $_has(0); + @$pb.TagNumber(1) + void clearBootId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get kernelRelease => $_getSZ(1); + @$pb.TagNumber(2) + set kernelRelease($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasKernelRelease() => $_has(1); + @$pb.TagNumber(2) + void clearKernelRelease() => $_clearField(2); + + @$pb.TagNumber(3) + $pb.PbList<$core.String> get congestionAlgorithms => $_getList(2); + + @$pb.TagNumber(4) + $core.bool get simpleHostRouting => $_getBF(3); + @$pb.TagNumber(4) + set simpleHostRouting($core.bool value) => $_setBool(3, value); + @$pb.TagNumber(4) + $core.bool hasSimpleHostRouting() => $_has(3); + @$pb.TagNumber(4) + void clearSimpleHostRouting() => $_clearField(4); + + @$pb.TagNumber(5) + $core.bool get actuatorEnabled => $_getBF(4); + @$pb.TagNumber(5) + set actuatorEnabled($core.bool value) => $_setBool(4, value); + @$pb.TagNumber(5) + $core.bool hasActuatorEnabled() => $_has(4); + @$pb.TagNumber(5) + void clearActuatorEnabled() => $_clearField(5); +} + +/// ApplicationRecord is the agent's account of one assignment. Agents report +/// these on check-in with installed_route and installed_boot_id cleared. +class ApplicationRecord extends $pb.GeneratedMessage { + factory ApplicationRecord({ + Assignment? assignment, + $fixnum.Int64? desiredRevision, + ApplyStatus? status, + $0.Timestamp? receivedAt, + $0.Timestamp? attemptedAt, + $0.Timestamp? appliedAt, + $0.Timestamp? verifiedAt, + $0.Timestamp? removedAt, + $core.String? error, + $core.List<$core.int>? installedRoute, + $core.String? installedBootId, + $core.bool? expiredLatched, + }) { + final result = ApplicationRecord._(); + if (assignment != null) result.assignment = assignment; + if (desiredRevision != null) result.desiredRevision = desiredRevision; + if (status != null) result.status = status; + if (receivedAt != null) result.receivedAt = receivedAt; + if (attemptedAt != null) result.attemptedAt = attemptedAt; + if (appliedAt != null) result.appliedAt = appliedAt; + if (verifiedAt != null) result.verifiedAt = verifiedAt; + if (removedAt != null) result.removedAt = removedAt; + if (error != null) result.error = error; + if (installedRoute != null) result.installedRoute = installedRoute; + if (installedBootId != null) result.installedBootId = installedBootId; + if (expiredLatched != null) result.expiredLatched = expiredLatched; + return result; + } + + ApplicationRecord._(); + + factory ApplicationRecord.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + ApplicationRecord()..mergeFromBuffer(data, registry); + factory ApplicationRecord.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + ApplicationRecord()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'ApplicationRecord', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'xtcp_treatment.v1'), + createEmptyInstance: ApplicationRecord.$_createMessage) + ..aOM(1, _omitFieldNames ? '' : 'assignment', + subBuilder: Assignment.$_createMessage) + ..a<$fixnum.Int64>( + 2, _omitFieldNames ? '' : 'desiredRevision', $pb.PbFieldType.OU6, + defaultOrMaker: $fixnum.Int64.ZERO) + ..aE(3, _omitFieldNames ? '' : 'status', + enumValues: ApplyStatus.values) + ..aOM<$0.Timestamp>(4, _omitFieldNames ? '' : 'receivedAt', + subBuilder: $0.Timestamp.$_createMessage) + ..aOM<$0.Timestamp>(5, _omitFieldNames ? '' : 'attemptedAt', + subBuilder: $0.Timestamp.$_createMessage) + ..aOM<$0.Timestamp>(6, _omitFieldNames ? '' : 'appliedAt', + subBuilder: $0.Timestamp.$_createMessage) + ..aOM<$0.Timestamp>(7, _omitFieldNames ? '' : 'verifiedAt', + subBuilder: $0.Timestamp.$_createMessage) + ..aOM<$0.Timestamp>(8, _omitFieldNames ? '' : 'removedAt', + subBuilder: $0.Timestamp.$_createMessage) + ..aOS(9, _omitFieldNames ? '' : 'error') + ..a<$core.List<$core.int>>( + 10, _omitFieldNames ? '' : 'installedRoute', $pb.PbFieldType.OY) + ..aOS(11, _omitFieldNames ? '' : 'installedBootId') + ..aOB(12, _omitFieldNames ? '' : 'expiredLatched') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ApplicationRecord clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ApplicationRecord copyWith(void Function(ApplicationRecord) updates) => + super.copyWith((message) => updates(message as ApplicationRecord)) + as ApplicationRecord; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated('Use ApplicationRecord() / ApplicationRecord.new instead') + static ApplicationRecord create() => ApplicationRecord._(); + static $pb.GeneratedMessage $_createMessage() => ApplicationRecord._(); + @$core.override + ApplicationRecord createEmptyInstance() => ApplicationRecord._(); + @$core.pragma('dart2js:noInline') + static ApplicationRecord getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor( + ApplicationRecord.$_createMessage); + static ApplicationRecord? _defaultInstance; + + @$pb.TagNumber(1) + Assignment get assignment => $_getN(0); + @$pb.TagNumber(1) + set assignment(Assignment value) => $_setField(1, value); + @$pb.TagNumber(1) + $core.bool hasAssignment() => $_has(0); + @$pb.TagNumber(1) + void clearAssignment() => $_clearField(1); + @$pb.TagNumber(1) + Assignment ensureAssignment() => $_ensure(0); + + @$pb.TagNumber(2) + $fixnum.Int64 get desiredRevision => $_getI64(1); + @$pb.TagNumber(2) + set desiredRevision($fixnum.Int64 value) => $_setInt64(1, value); + @$pb.TagNumber(2) + $core.bool hasDesiredRevision() => $_has(1); + @$pb.TagNumber(2) + void clearDesiredRevision() => $_clearField(2); + + @$pb.TagNumber(3) + ApplyStatus get status => $_getN(2); + @$pb.TagNumber(3) + set status(ApplyStatus value) => $_setField(3, value); + @$pb.TagNumber(3) + $core.bool hasStatus() => $_has(2); + @$pb.TagNumber(3) + void clearStatus() => $_clearField(3); + + @$pb.TagNumber(4) + $0.Timestamp get receivedAt => $_getN(3); + @$pb.TagNumber(4) + set receivedAt($0.Timestamp value) => $_setField(4, value); + @$pb.TagNumber(4) + $core.bool hasReceivedAt() => $_has(3); + @$pb.TagNumber(4) + void clearReceivedAt() => $_clearField(4); + @$pb.TagNumber(4) + $0.Timestamp ensureReceivedAt() => $_ensure(3); + + @$pb.TagNumber(5) + $0.Timestamp get attemptedAt => $_getN(4); + @$pb.TagNumber(5) + set attemptedAt($0.Timestamp value) => $_setField(5, value); + @$pb.TagNumber(5) + $core.bool hasAttemptedAt() => $_has(4); + @$pb.TagNumber(5) + void clearAttemptedAt() => $_clearField(5); + @$pb.TagNumber(5) + $0.Timestamp ensureAttemptedAt() => $_ensure(4); + + @$pb.TagNumber(6) + $0.Timestamp get appliedAt => $_getN(5); + @$pb.TagNumber(6) + set appliedAt($0.Timestamp value) => $_setField(6, value); + @$pb.TagNumber(6) + $core.bool hasAppliedAt() => $_has(5); + @$pb.TagNumber(6) + void clearAppliedAt() => $_clearField(6); + @$pb.TagNumber(6) + $0.Timestamp ensureAppliedAt() => $_ensure(5); + + @$pb.TagNumber(7) + $0.Timestamp get verifiedAt => $_getN(6); + @$pb.TagNumber(7) + set verifiedAt($0.Timestamp value) => $_setField(7, value); + @$pb.TagNumber(7) + $core.bool hasVerifiedAt() => $_has(6); + @$pb.TagNumber(7) + void clearVerifiedAt() => $_clearField(7); + @$pb.TagNumber(7) + $0.Timestamp ensureVerifiedAt() => $_ensure(6); + + @$pb.TagNumber(8) + $0.Timestamp get removedAt => $_getN(7); + @$pb.TagNumber(8) + set removedAt($0.Timestamp value) => $_setField(8, value); + @$pb.TagNumber(8) + $core.bool hasRemovedAt() => $_has(7); + @$pb.TagNumber(8) + void clearRemovedAt() => $_clearField(8); + @$pb.TagNumber(8) + $0.Timestamp ensureRemovedAt() => $_ensure(7); + + @$pb.TagNumber(9) + $core.String get error => $_getSZ(8); + @$pb.TagNumber(9) + set error($core.String value) => $_setString(8, value); + @$pb.TagNumber(9) + $core.bool hasError() => $_has(8); + @$pb.TagNumber(9) + void clearError() => $_clearField(9); + + /// Locally generated canonical netlink route body, never accepted from RPC. + @$pb.TagNumber(10) + $core.List<$core.int> get installedRoute => $_getN(9); + @$pb.TagNumber(10) + set installedRoute($core.List<$core.int> value) => $_setBytes(9, value); + @$pb.TagNumber(10) + $core.bool hasInstalledRoute() => $_has(9); + @$pb.TagNumber(10) + void clearInstalledRoute() => $_clearField(10); + + @$pb.TagNumber(11) + $core.String get installedBootId => $_getSZ(10); + @$pb.TagNumber(11) + set installedBootId($core.String value) => $_setString(10, value); + @$pb.TagNumber(11) + $core.bool hasInstalledBootId() => $_has(10); + @$pb.TagNumber(11) + void clearInstalledBootId() => $_clearField(11); + + @$pb.TagNumber(12) + $core.bool get expiredLatched => $_getBF(11); + @$pb.TagNumber(12) + set expiredLatched($core.bool value) => $_setBool(11, value); + @$pb.TagNumber(12) + $core.bool hasExpiredLatched() => $_has(11); + @$pb.TagNumber(12) + void clearExpiredLatched() => $_clearField(12); +} + +/// LocalState is the agent's journal snapshot. It is a file format for the +/// host-side journal and the local tooling, not an RPC payload. +class LocalState extends $pb.GeneratedMessage { + factory LocalState({ + DesiredState? desired, + $core.bool? locallySuspended, + $core.String? suspensionReason, + $core.Iterable? records, + $0.Timestamp? updatedAt, + }) { + final result = LocalState._(); + if (desired != null) result.desired = desired; + if (locallySuspended != null) result.locallySuspended = locallySuspended; + if (suspensionReason != null) result.suspensionReason = suspensionReason; + if (records != null) result.records.addAll(records); + if (updatedAt != null) result.updatedAt = updatedAt; + return result; + } + + LocalState._(); + + factory LocalState.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + LocalState()..mergeFromBuffer(data, registry); + factory LocalState.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + LocalState()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'LocalState', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'xtcp_treatment.v1'), + createEmptyInstance: LocalState.$_createMessage) + ..aOM(1, _omitFieldNames ? '' : 'desired', + subBuilder: DesiredState.$_createMessage) + ..aOB(2, _omitFieldNames ? '' : 'locallySuspended') + ..aOS(3, _omitFieldNames ? '' : 'suspensionReason') + ..pPM(4, _omitFieldNames ? '' : 'records', + subBuilder: ApplicationRecord.$_createMessage) + ..aOM<$0.Timestamp>(5, _omitFieldNames ? '' : 'updatedAt', + subBuilder: $0.Timestamp.$_createMessage) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + LocalState clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + LocalState copyWith(void Function(LocalState) updates) => + super.copyWith((message) => updates(message as LocalState)) as LocalState; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated('Use LocalState() / LocalState.new instead') + static LocalState create() => LocalState._(); + static $pb.GeneratedMessage $_createMessage() => LocalState._(); + @$core.override + LocalState createEmptyInstance() => LocalState._(); + @$core.pragma('dart2js:noInline') + static LocalState getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(LocalState.$_createMessage); + static LocalState? _defaultInstance; + + @$pb.TagNumber(1) + DesiredState get desired => $_getN(0); + @$pb.TagNumber(1) + set desired(DesiredState value) => $_setField(1, value); + @$pb.TagNumber(1) + $core.bool hasDesired() => $_has(0); + @$pb.TagNumber(1) + void clearDesired() => $_clearField(1); + @$pb.TagNumber(1) + DesiredState ensureDesired() => $_ensure(0); + + @$pb.TagNumber(2) + $core.bool get locallySuspended => $_getBF(1); + @$pb.TagNumber(2) + set locallySuspended($core.bool value) => $_setBool(1, value); + @$pb.TagNumber(2) + $core.bool hasLocallySuspended() => $_has(1); + @$pb.TagNumber(2) + void clearLocallySuspended() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get suspensionReason => $_getSZ(2); + @$pb.TagNumber(3) + set suspensionReason($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasSuspensionReason() => $_has(2); + @$pb.TagNumber(3) + void clearSuspensionReason() => $_clearField(3); + + @$pb.TagNumber(4) + $pb.PbList get records => $_getList(3); + + @$pb.TagNumber(5) + $0.Timestamp get updatedAt => $_getN(4); + @$pb.TagNumber(5) + set updatedAt($0.Timestamp value) => $_setField(5, value); + @$pb.TagNumber(5) + $core.bool hasUpdatedAt() => $_has(4); + @$pb.TagNumber(5) + void clearUpdatedAt() => $_clearField(5); + @$pb.TagNumber(5) + $0.Timestamp ensureUpdatedAt() => $_ensure(4); +} + +class HostEvidence extends $pb.GeneratedMessage { + factory HostEvidence({ + $core.String? agentId, + $fixnum.Int64? sockets, + $fixnum.Int64? bytesAcked, + $core.double? goodputBps, + $core.double? retransmissionRatio, + $core.double? rttP95Ms, + $fixnum.Int64? exposedSockets, + $core.bool? collectionHealthy, + }) { + final result = HostEvidence._(); + if (agentId != null) result.agentId = agentId; + if (sockets != null) result.sockets = sockets; + if (bytesAcked != null) result.bytesAcked = bytesAcked; + if (goodputBps != null) result.goodputBps = goodputBps; + if (retransmissionRatio != null) + result.retransmissionRatio = retransmissionRatio; + if (rttP95Ms != null) result.rttP95Ms = rttP95Ms; + if (exposedSockets != null) result.exposedSockets = exposedSockets; + if (collectionHealthy != null) result.collectionHealthy = collectionHealthy; + return result; + } + + HostEvidence._(); + + factory HostEvidence.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + HostEvidence()..mergeFromBuffer(data, registry); + factory HostEvidence.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + HostEvidence()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'HostEvidence', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'xtcp_treatment.v1'), + createEmptyInstance: HostEvidence.$_createMessage) + ..aOS(1, _omitFieldNames ? '' : 'agentId') + ..a<$fixnum.Int64>(2, _omitFieldNames ? '' : 'sockets', $pb.PbFieldType.OU6, + defaultOrMaker: $fixnum.Int64.ZERO) + ..a<$fixnum.Int64>( + 3, _omitFieldNames ? '' : 'bytesAcked', $pb.PbFieldType.OU6, + defaultOrMaker: $fixnum.Int64.ZERO) + ..aD(4, _omitFieldNames ? '' : 'goodputBps') + ..aD(5, _omitFieldNames ? '' : 'retransmissionRatio') + ..aD(6, _omitFieldNames ? '' : 'rttP95Ms') + ..a<$fixnum.Int64>( + 7, _omitFieldNames ? '' : 'exposedSockets', $pb.PbFieldType.OU6, + defaultOrMaker: $fixnum.Int64.ZERO) + ..aOB(8, _omitFieldNames ? '' : 'collectionHealthy') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + HostEvidence clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + HostEvidence copyWith(void Function(HostEvidence) updates) => + super.copyWith((message) => updates(message as HostEvidence)) + as HostEvidence; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated('Use HostEvidence() / HostEvidence.new instead') + static HostEvidence create() => HostEvidence._(); + static $pb.GeneratedMessage $_createMessage() => HostEvidence._(); + @$core.override + HostEvidence createEmptyInstance() => HostEvidence._(); + @$core.pragma('dart2js:noInline') + static HostEvidence getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor( + HostEvidence.$_createMessage); + static HostEvidence? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get agentId => $_getSZ(0); + @$pb.TagNumber(1) + set agentId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasAgentId() => $_has(0); + @$pb.TagNumber(1) + void clearAgentId() => $_clearField(1); + + @$pb.TagNumber(2) + $fixnum.Int64 get sockets => $_getI64(1); + @$pb.TagNumber(2) + set sockets($fixnum.Int64 value) => $_setInt64(1, value); + @$pb.TagNumber(2) + $core.bool hasSockets() => $_has(1); + @$pb.TagNumber(2) + void clearSockets() => $_clearField(2); + + @$pb.TagNumber(3) + $fixnum.Int64 get bytesAcked => $_getI64(2); + @$pb.TagNumber(3) + set bytesAcked($fixnum.Int64 value) => $_setInt64(2, value); + @$pb.TagNumber(3) + $core.bool hasBytesAcked() => $_has(2); + @$pb.TagNumber(3) + void clearBytesAcked() => $_clearField(3); + + @$pb.TagNumber(4) + $core.double get goodputBps => $_getN(3); + @$pb.TagNumber(4) + set goodputBps($core.double value) => $_setDouble(3, value); + @$pb.TagNumber(4) + $core.bool hasGoodputBps() => $_has(3); + @$pb.TagNumber(4) + void clearGoodputBps() => $_clearField(4); + + @$pb.TagNumber(5) + $core.double get retransmissionRatio => $_getN(4); + @$pb.TagNumber(5) + set retransmissionRatio($core.double value) => $_setDouble(4, value); + @$pb.TagNumber(5) + $core.bool hasRetransmissionRatio() => $_has(4); + @$pb.TagNumber(5) + void clearRetransmissionRatio() => $_clearField(5); + + @$pb.TagNumber(6) + $core.double get rttP95Ms => $_getN(5); + @$pb.TagNumber(6) + set rttP95Ms($core.double value) => $_setDouble(5, value); + @$pb.TagNumber(6) + $core.bool hasRttP95Ms() => $_has(5); + @$pb.TagNumber(6) + void clearRttP95Ms() => $_clearField(6); + + @$pb.TagNumber(7) + $fixnum.Int64 get exposedSockets => $_getI64(6); + @$pb.TagNumber(7) + set exposedSockets($fixnum.Int64 value) => $_setInt64(6, value); + @$pb.TagNumber(7) + $core.bool hasExposedSockets() => $_has(6); + @$pb.TagNumber(7) + void clearExposedSockets() => $_clearField(7); + + @$pb.TagNumber(8) + $core.bool get collectionHealthy => $_getBF(7); + @$pb.TagNumber(8) + set collectionHealthy($core.bool value) => $_setBool(7, value); + @$pb.TagNumber(8) + $core.bool hasCollectionHealthy() => $_has(7); + @$pb.TagNumber(8) + void clearCollectionHealthy() => $_clearField(8); +} + +class Evidence extends $pb.GeneratedMessage { + factory Evidence({ + $core.String? id, + $0.Timestamp? analyzedAt, + $0.Timestamp? windowStart, + $0.Timestamp? windowEnd, + $core.String? queryVersion, + $core.String? enrichmentVersion, + $core.Iterable? hosts, + $core.String? artifactSha256, + }) { + final result = Evidence._(); + if (id != null) result.id = id; + if (analyzedAt != null) result.analyzedAt = analyzedAt; + if (windowStart != null) result.windowStart = windowStart; + if (windowEnd != null) result.windowEnd = windowEnd; + if (queryVersion != null) result.queryVersion = queryVersion; + if (enrichmentVersion != null) result.enrichmentVersion = enrichmentVersion; + if (hosts != null) result.hosts.addAll(hosts); + if (artifactSha256 != null) result.artifactSha256 = artifactSha256; + return result; + } + + Evidence._(); + + factory Evidence.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + Evidence()..mergeFromBuffer(data, registry); + factory Evidence.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + Evidence()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'Evidence', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'xtcp_treatment.v1'), + createEmptyInstance: Evidence.$_createMessage) + ..aOS(1, _omitFieldNames ? '' : 'id') + ..aOM<$0.Timestamp>(2, _omitFieldNames ? '' : 'analyzedAt', + subBuilder: $0.Timestamp.$_createMessage) + ..aOM<$0.Timestamp>(3, _omitFieldNames ? '' : 'windowStart', + subBuilder: $0.Timestamp.$_createMessage) + ..aOM<$0.Timestamp>(4, _omitFieldNames ? '' : 'windowEnd', + subBuilder: $0.Timestamp.$_createMessage) + ..aOS(5, _omitFieldNames ? '' : 'queryVersion') + ..aOS(6, _omitFieldNames ? '' : 'enrichmentVersion') + ..pPM(7, _omitFieldNames ? '' : 'hosts', + subBuilder: HostEvidence.$_createMessage) + ..aOS(8, _omitFieldNames ? '' : 'artifactSha256') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + Evidence clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + Evidence copyWith(void Function(Evidence) updates) => + super.copyWith((message) => updates(message as Evidence)) as Evidence; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated('Use Evidence() / Evidence.new instead') + static Evidence create() => Evidence._(); + static $pb.GeneratedMessage $_createMessage() => Evidence._(); + @$core.override + Evidence createEmptyInstance() => Evidence._(); + @$core.pragma('dart2js:noInline') + static Evidence getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(Evidence.$_createMessage); + static Evidence? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get id => $_getSZ(0); + @$pb.TagNumber(1) + set id($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasId() => $_has(0); + @$pb.TagNumber(1) + void clearId() => $_clearField(1); + + @$pb.TagNumber(2) + $0.Timestamp get analyzedAt => $_getN(1); + @$pb.TagNumber(2) + set analyzedAt($0.Timestamp value) => $_setField(2, value); + @$pb.TagNumber(2) + $core.bool hasAnalyzedAt() => $_has(1); + @$pb.TagNumber(2) + void clearAnalyzedAt() => $_clearField(2); + @$pb.TagNumber(2) + $0.Timestamp ensureAnalyzedAt() => $_ensure(1); + + @$pb.TagNumber(3) + $0.Timestamp get windowStart => $_getN(2); + @$pb.TagNumber(3) + set windowStart($0.Timestamp value) => $_setField(3, value); + @$pb.TagNumber(3) + $core.bool hasWindowStart() => $_has(2); + @$pb.TagNumber(3) + void clearWindowStart() => $_clearField(3); + @$pb.TagNumber(3) + $0.Timestamp ensureWindowStart() => $_ensure(2); + + @$pb.TagNumber(4) + $0.Timestamp get windowEnd => $_getN(3); + @$pb.TagNumber(4) + set windowEnd($0.Timestamp value) => $_setField(4, value); + @$pb.TagNumber(4) + $core.bool hasWindowEnd() => $_has(3); + @$pb.TagNumber(4) + void clearWindowEnd() => $_clearField(4); + @$pb.TagNumber(4) + $0.Timestamp ensureWindowEnd() => $_ensure(3); + + @$pb.TagNumber(5) + $core.String get queryVersion => $_getSZ(4); + @$pb.TagNumber(5) + set queryVersion($core.String value) => $_setString(4, value); + @$pb.TagNumber(5) + $core.bool hasQueryVersion() => $_has(4); + @$pb.TagNumber(5) + void clearQueryVersion() => $_clearField(5); + + @$pb.TagNumber(6) + $core.String get enrichmentVersion => $_getSZ(5); + @$pb.TagNumber(6) + set enrichmentVersion($core.String value) => $_setString(5, value); + @$pb.TagNumber(6) + $core.bool hasEnrichmentVersion() => $_has(5); + @$pb.TagNumber(6) + void clearEnrichmentVersion() => $_clearField(6); + + @$pb.TagNumber(7) + $pb.PbList get hosts => $_getList(6); + + /// SHA-256 of the complete analysis response this summary was derived from. + @$pb.TagNumber(8) + $core.String get artifactSha256 => $_getSZ(7); + @$pb.TagNumber(8) + set artifactSha256($core.String value) => $_setString(7, value); + @$pb.TagNumber(8) + $core.bool hasArtifactSha256() => $_has(7); + @$pb.TagNumber(8) + void clearArtifactSha256() => $_clearField(8); +} + +class ExperimentPolicy extends $pb.GeneratedMessage { + factory ExperimentPolicy({ + $1.Duration? minimumObservation, + $1.Duration? maximumPhase, + $fixnum.Int64? minimumSocketsPerHost, + $fixnum.Int64? minimumBytesPerHost, + $core.int? minimumTreatedHosts, + $core.double? minimumImprovement, + $core.double? maximumRetransmissionIncrease, + $core.double? maximumRttIncrease, + $1.Duration? maximumEvidenceAge, + }) { + final result = ExperimentPolicy._(); + if (minimumObservation != null) + result.minimumObservation = minimumObservation; + if (maximumPhase != null) result.maximumPhase = maximumPhase; + if (minimumSocketsPerHost != null) + result.minimumSocketsPerHost = minimumSocketsPerHost; + if (minimumBytesPerHost != null) + result.minimumBytesPerHost = minimumBytesPerHost; + if (minimumTreatedHosts != null) + result.minimumTreatedHosts = minimumTreatedHosts; + if (minimumImprovement != null) + result.minimumImprovement = minimumImprovement; + if (maximumRetransmissionIncrease != null) + result.maximumRetransmissionIncrease = maximumRetransmissionIncrease; + if (maximumRttIncrease != null) + result.maximumRttIncrease = maximumRttIncrease; + if (maximumEvidenceAge != null) + result.maximumEvidenceAge = maximumEvidenceAge; + return result; + } + + ExperimentPolicy._(); + + factory ExperimentPolicy.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + ExperimentPolicy()..mergeFromBuffer(data, registry); + factory ExperimentPolicy.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + ExperimentPolicy()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'ExperimentPolicy', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'xtcp_treatment.v1'), + createEmptyInstance: ExperimentPolicy.$_createMessage) + ..aOM<$1.Duration>(1, _omitFieldNames ? '' : 'minimumObservation', + subBuilder: $1.Duration.$_createMessage) + ..aOM<$1.Duration>(2, _omitFieldNames ? '' : 'maximumPhase', + subBuilder: $1.Duration.$_createMessage) + ..a<$fixnum.Int64>( + 3, _omitFieldNames ? '' : 'minimumSocketsPerHost', $pb.PbFieldType.OU6, + defaultOrMaker: $fixnum.Int64.ZERO) + ..a<$fixnum.Int64>( + 4, _omitFieldNames ? '' : 'minimumBytesPerHost', $pb.PbFieldType.OU6, + defaultOrMaker: $fixnum.Int64.ZERO) + ..aI(5, _omitFieldNames ? '' : 'minimumTreatedHosts', + fieldType: $pb.PbFieldType.OU3) + ..aD(6, _omitFieldNames ? '' : 'minimumImprovement') + ..aD(7, _omitFieldNames ? '' : 'maximumRetransmissionIncrease') + ..aD(8, _omitFieldNames ? '' : 'maximumRttIncrease') + ..aOM<$1.Duration>(9, _omitFieldNames ? '' : 'maximumEvidenceAge', + subBuilder: $1.Duration.$_createMessage) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ExperimentPolicy clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ExperimentPolicy copyWith(void Function(ExperimentPolicy) updates) => + super.copyWith((message) => updates(message as ExperimentPolicy)) + as ExperimentPolicy; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated('Use ExperimentPolicy() / ExperimentPolicy.new instead') + static ExperimentPolicy create() => ExperimentPolicy._(); + static $pb.GeneratedMessage $_createMessage() => ExperimentPolicy._(); + @$core.override + ExperimentPolicy createEmptyInstance() => ExperimentPolicy._(); + @$core.pragma('dart2js:noInline') + static ExperimentPolicy getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor( + ExperimentPolicy.$_createMessage); + static ExperimentPolicy? _defaultInstance; + + @$pb.TagNumber(1) + $1.Duration get minimumObservation => $_getN(0); + @$pb.TagNumber(1) + set minimumObservation($1.Duration value) => $_setField(1, value); + @$pb.TagNumber(1) + $core.bool hasMinimumObservation() => $_has(0); + @$pb.TagNumber(1) + void clearMinimumObservation() => $_clearField(1); + @$pb.TagNumber(1) + $1.Duration ensureMinimumObservation() => $_ensure(0); + + @$pb.TagNumber(2) + $1.Duration get maximumPhase => $_getN(1); + @$pb.TagNumber(2) + set maximumPhase($1.Duration value) => $_setField(2, value); + @$pb.TagNumber(2) + $core.bool hasMaximumPhase() => $_has(1); + @$pb.TagNumber(2) + void clearMaximumPhase() => $_clearField(2); + @$pb.TagNumber(2) + $1.Duration ensureMaximumPhase() => $_ensure(1); + + @$pb.TagNumber(3) + $fixnum.Int64 get minimumSocketsPerHost => $_getI64(2); + @$pb.TagNumber(3) + set minimumSocketsPerHost($fixnum.Int64 value) => $_setInt64(2, value); + @$pb.TagNumber(3) + $core.bool hasMinimumSocketsPerHost() => $_has(2); + @$pb.TagNumber(3) + void clearMinimumSocketsPerHost() => $_clearField(3); + + @$pb.TagNumber(4) + $fixnum.Int64 get minimumBytesPerHost => $_getI64(3); + @$pb.TagNumber(4) + set minimumBytesPerHost($fixnum.Int64 value) => $_setInt64(3, value); + @$pb.TagNumber(4) + $core.bool hasMinimumBytesPerHost() => $_has(3); + @$pb.TagNumber(4) + void clearMinimumBytesPerHost() => $_clearField(4); + + @$pb.TagNumber(5) + $core.int get minimumTreatedHosts => $_getIZ(4); + @$pb.TagNumber(5) + set minimumTreatedHosts($core.int value) => $_setUnsignedInt32(4, value); + @$pb.TagNumber(5) + $core.bool hasMinimumTreatedHosts() => $_has(4); + @$pb.TagNumber(5) + void clearMinimumTreatedHosts() => $_clearField(5); + + @$pb.TagNumber(6) + $core.double get minimumImprovement => $_getN(5); + @$pb.TagNumber(6) + set minimumImprovement($core.double value) => $_setDouble(5, value); + @$pb.TagNumber(6) + $core.bool hasMinimumImprovement() => $_has(5); + @$pb.TagNumber(6) + void clearMinimumImprovement() => $_clearField(6); + + @$pb.TagNumber(7) + $core.double get maximumRetransmissionIncrease => $_getN(6); + @$pb.TagNumber(7) + set maximumRetransmissionIncrease($core.double value) => + $_setDouble(6, value); + @$pb.TagNumber(7) + $core.bool hasMaximumRetransmissionIncrease() => $_has(6); + @$pb.TagNumber(7) + void clearMaximumRetransmissionIncrease() => $_clearField(7); + + @$pb.TagNumber(8) + $core.double get maximumRttIncrease => $_getN(7); + @$pb.TagNumber(8) + set maximumRttIncrease($core.double value) => $_setDouble(7, value); + @$pb.TagNumber(8) + $core.bool hasMaximumRttIncrease() => $_has(7); + @$pb.TagNumber(8) + void clearMaximumRttIncrease() => $_clearField(8); + + @$pb.TagNumber(9) + $1.Duration get maximumEvidenceAge => $_getN(8); + @$pb.TagNumber(9) + set maximumEvidenceAge($1.Duration value) => $_setField(9, value); + @$pb.TagNumber(9) + $core.bool hasMaximumEvidenceAge() => $_has(8); + @$pb.TagNumber(9) + void clearMaximumEvidenceAge() => $_clearField(9); + @$pb.TagNumber(9) + $1.Duration ensureMaximumEvidenceAge() => $_ensure(8); +} + +class Phase extends $pb.GeneratedMessage { + factory Phase({ + $core.int? targetHosts, + $0.Timestamp? issuedAt, + $0.Timestamp? observationStartedAt, + $0.Timestamp? evaluatedAt, + Evidence? evidence, + $core.String? outcome, + $core.String? reviewer, + $core.String? reason, + }) { + final result = Phase._(); + if (targetHosts != null) result.targetHosts = targetHosts; + if (issuedAt != null) result.issuedAt = issuedAt; + if (observationStartedAt != null) + result.observationStartedAt = observationStartedAt; + if (evaluatedAt != null) result.evaluatedAt = evaluatedAt; + if (evidence != null) result.evidence = evidence; + if (outcome != null) result.outcome = outcome; + if (reviewer != null) result.reviewer = reviewer; + if (reason != null) result.reason = reason; + return result; + } + + Phase._(); + + factory Phase.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + Phase()..mergeFromBuffer(data, registry); + factory Phase.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + Phase()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'Phase', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'xtcp_treatment.v1'), + createEmptyInstance: Phase.$_createMessage) + ..aI(1, _omitFieldNames ? '' : 'targetHosts', + fieldType: $pb.PbFieldType.OU3) + ..aOM<$0.Timestamp>(2, _omitFieldNames ? '' : 'issuedAt', + subBuilder: $0.Timestamp.$_createMessage) + ..aOM<$0.Timestamp>(3, _omitFieldNames ? '' : 'observationStartedAt', + subBuilder: $0.Timestamp.$_createMessage) + ..aOM<$0.Timestamp>(4, _omitFieldNames ? '' : 'evaluatedAt', + subBuilder: $0.Timestamp.$_createMessage) + ..aOM(5, _omitFieldNames ? '' : 'evidence', + subBuilder: Evidence.$_createMessage) + ..aOS(6, _omitFieldNames ? '' : 'outcome') + ..aOS(7, _omitFieldNames ? '' : 'reviewer') + ..aOS(8, _omitFieldNames ? '' : 'reason') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + Phase clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + Phase copyWith(void Function(Phase) updates) => + super.copyWith((message) => updates(message as Phase)) as Phase; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated('Use Phase() / Phase.new instead') + static Phase create() => Phase._(); + static $pb.GeneratedMessage $_createMessage() => Phase._(); + @$core.override + Phase createEmptyInstance() => Phase._(); + @$core.pragma('dart2js:noInline') + static Phase getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(Phase.$_createMessage); + static Phase? _defaultInstance; + + @$pb.TagNumber(1) + $core.int get targetHosts => $_getIZ(0); + @$pb.TagNumber(1) + set targetHosts($core.int value) => $_setUnsignedInt32(0, value); + @$pb.TagNumber(1) + $core.bool hasTargetHosts() => $_has(0); + @$pb.TagNumber(1) + void clearTargetHosts() => $_clearField(1); + + @$pb.TagNumber(2) + $0.Timestamp get issuedAt => $_getN(1); + @$pb.TagNumber(2) + set issuedAt($0.Timestamp value) => $_setField(2, value); + @$pb.TagNumber(2) + $core.bool hasIssuedAt() => $_has(1); + @$pb.TagNumber(2) + void clearIssuedAt() => $_clearField(2); + @$pb.TagNumber(2) + $0.Timestamp ensureIssuedAt() => $_ensure(1); + + @$pb.TagNumber(3) + $0.Timestamp get observationStartedAt => $_getN(2); + @$pb.TagNumber(3) + set observationStartedAt($0.Timestamp value) => $_setField(3, value); + @$pb.TagNumber(3) + $core.bool hasObservationStartedAt() => $_has(2); + @$pb.TagNumber(3) + void clearObservationStartedAt() => $_clearField(3); + @$pb.TagNumber(3) + $0.Timestamp ensureObservationStartedAt() => $_ensure(2); + + @$pb.TagNumber(4) + $0.Timestamp get evaluatedAt => $_getN(3); + @$pb.TagNumber(4) + set evaluatedAt($0.Timestamp value) => $_setField(4, value); + @$pb.TagNumber(4) + $core.bool hasEvaluatedAt() => $_has(3); + @$pb.TagNumber(4) + void clearEvaluatedAt() => $_clearField(4); + @$pb.TagNumber(4) + $0.Timestamp ensureEvaluatedAt() => $_ensure(3); + + @$pb.TagNumber(5) + Evidence get evidence => $_getN(4); + @$pb.TagNumber(5) + set evidence(Evidence value) => $_setField(5, value); + @$pb.TagNumber(5) + $core.bool hasEvidence() => $_has(4); + @$pb.TagNumber(5) + void clearEvidence() => $_clearField(5); + @$pb.TagNumber(5) + Evidence ensureEvidence() => $_ensure(4); + + @$pb.TagNumber(6) + $core.String get outcome => $_getSZ(5); + @$pb.TagNumber(6) + set outcome($core.String value) => $_setString(5, value); + @$pb.TagNumber(6) + $core.bool hasOutcome() => $_has(5); + @$pb.TagNumber(6) + void clearOutcome() => $_clearField(6); + + @$pb.TagNumber(7) + $core.String get reviewer => $_getSZ(6); + @$pb.TagNumber(7) + set reviewer($core.String value) => $_setString(6, value); + @$pb.TagNumber(7) + $core.bool hasReviewer() => $_has(6); + @$pb.TagNumber(7) + void clearReviewer() => $_clearField(7); + + @$pb.TagNumber(8) + $core.String get reason => $_getSZ(7); + @$pb.TagNumber(8) + set reason($core.String value) => $_setString(7, value); + @$pb.TagNumber(8) + $core.bool hasReason() => $_has(7); + @$pb.TagNumber(8) + void clearReason() => $_clearField(8); +} + +class Experiment extends $pb.GeneratedMessage { + factory Experiment({ + $core.String? id, + $core.String? location, + $core.String? hypothesis, + Treatment? treatment, + ExperimentPolicy? policy, + Evidence? baseline, + $core.Iterable<$core.String>? cohortOrder, + $core.Iterable? phases, + ExperimentStatus? status, + $fixnum.Int64? revision, + $0.Timestamp? proposedAt, + Evidence? latestEvidence, + $core.String? latestOutcome, + }) { + final result = Experiment._(); + if (id != null) result.id = id; + if (location != null) result.location = location; + if (hypothesis != null) result.hypothesis = hypothesis; + if (treatment != null) result.treatment = treatment; + if (policy != null) result.policy = policy; + if (baseline != null) result.baseline = baseline; + if (cohortOrder != null) result.cohortOrder.addAll(cohortOrder); + if (phases != null) result.phases.addAll(phases); + if (status != null) result.status = status; + if (revision != null) result.revision = revision; + if (proposedAt != null) result.proposedAt = proposedAt; + if (latestEvidence != null) result.latestEvidence = latestEvidence; + if (latestOutcome != null) result.latestOutcome = latestOutcome; + return result; + } + + Experiment._(); + + factory Experiment.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + Experiment()..mergeFromBuffer(data, registry); + factory Experiment.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + Experiment()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'Experiment', + package: + const $pb.PackageName(_omitMessageNames ? '' : 'xtcp_treatment.v1'), + createEmptyInstance: Experiment.$_createMessage) + ..aOS(1, _omitFieldNames ? '' : 'id') + ..aOS(2, _omitFieldNames ? '' : 'location') + ..aOS(3, _omitFieldNames ? '' : 'hypothesis') + ..aOM(4, _omitFieldNames ? '' : 'treatment', + subBuilder: Treatment.$_createMessage) + ..aOM(5, _omitFieldNames ? '' : 'policy', + subBuilder: ExperimentPolicy.$_createMessage) + ..aOM(6, _omitFieldNames ? '' : 'baseline', + subBuilder: Evidence.$_createMessage) + ..pPS(7, _omitFieldNames ? '' : 'cohortOrder') + ..pPM(8, _omitFieldNames ? '' : 'phases', + subBuilder: Phase.$_createMessage) + ..aE(9, _omitFieldNames ? '' : 'status', + enumValues: ExperimentStatus.values) + ..a<$fixnum.Int64>( + 10, _omitFieldNames ? '' : 'revision', $pb.PbFieldType.OU6, + defaultOrMaker: $fixnum.Int64.ZERO) + ..aOM<$0.Timestamp>(11, _omitFieldNames ? '' : 'proposedAt', + subBuilder: $0.Timestamp.$_createMessage) + ..aOM(12, _omitFieldNames ? '' : 'latestEvidence', + subBuilder: Evidence.$_createMessage) + ..aOS(13, _omitFieldNames ? '' : 'latestOutcome') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + Experiment clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + Experiment copyWith(void Function(Experiment) updates) => + super.copyWith((message) => updates(message as Experiment)) as Experiment; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated('Use Experiment() / Experiment.new instead') + static Experiment create() => Experiment._(); + static $pb.GeneratedMessage $_createMessage() => Experiment._(); + @$core.override + Experiment createEmptyInstance() => Experiment._(); + @$core.pragma('dart2js:noInline') + static Experiment getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(Experiment.$_createMessage); + static Experiment? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get id => $_getSZ(0); + @$pb.TagNumber(1) + set id($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasId() => $_has(0); + @$pb.TagNumber(1) + void clearId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get location => $_getSZ(1); + @$pb.TagNumber(2) + set location($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasLocation() => $_has(1); + @$pb.TagNumber(2) + void clearLocation() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get hypothesis => $_getSZ(2); + @$pb.TagNumber(3) + set hypothesis($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasHypothesis() => $_has(2); + @$pb.TagNumber(3) + void clearHypothesis() => $_clearField(3); + + @$pb.TagNumber(4) + Treatment get treatment => $_getN(3); + @$pb.TagNumber(4) + set treatment(Treatment value) => $_setField(4, value); + @$pb.TagNumber(4) + $core.bool hasTreatment() => $_has(3); + @$pb.TagNumber(4) + void clearTreatment() => $_clearField(4); + @$pb.TagNumber(4) + Treatment ensureTreatment() => $_ensure(3); + + @$pb.TagNumber(5) + ExperimentPolicy get policy => $_getN(4); + @$pb.TagNumber(5) + set policy(ExperimentPolicy value) => $_setField(5, value); + @$pb.TagNumber(5) + $core.bool hasPolicy() => $_has(4); + @$pb.TagNumber(5) + void clearPolicy() => $_clearField(5); + @$pb.TagNumber(5) + ExperimentPolicy ensurePolicy() => $_ensure(4); + + @$pb.TagNumber(6) + Evidence get baseline => $_getN(5); + @$pb.TagNumber(6) + set baseline(Evidence value) => $_setField(6, value); + @$pb.TagNumber(6) + $core.bool hasBaseline() => $_has(5); + @$pb.TagNumber(6) + void clearBaseline() => $_clearField(6); + @$pb.TagNumber(6) + Evidence ensureBaseline() => $_ensure(5); + + /// Randomised host order; phases treat a growing prefix of it. + @$pb.TagNumber(7) + $pb.PbList<$core.String> get cohortOrder => $_getList(6); + + @$pb.TagNumber(8) + $pb.PbList get phases => $_getList(7); + + @$pb.TagNumber(9) + ExperimentStatus get status => $_getN(8); + @$pb.TagNumber(9) + set status(ExperimentStatus value) => $_setField(9, value); + @$pb.TagNumber(9) + $core.bool hasStatus() => $_has(8); + @$pb.TagNumber(9) + void clearStatus() => $_clearField(9); + + @$pb.TagNumber(10) + $fixnum.Int64 get revision => $_getI64(9); + @$pb.TagNumber(10) + set revision($fixnum.Int64 value) => $_setInt64(9, value); + @$pb.TagNumber(10) + $core.bool hasRevision() => $_has(9); + @$pb.TagNumber(10) + void clearRevision() => $_clearField(10); + + @$pb.TagNumber(11) + $0.Timestamp get proposedAt => $_getN(10); + @$pb.TagNumber(11) + set proposedAt($0.Timestamp value) => $_setField(11, value); + @$pb.TagNumber(11) + $core.bool hasProposedAt() => $_has(10); + @$pb.TagNumber(11) + void clearProposedAt() => $_clearField(11); + @$pb.TagNumber(11) + $0.Timestamp ensureProposedAt() => $_ensure(10); + + @$pb.TagNumber(12) + Evidence get latestEvidence => $_getN(11); + @$pb.TagNumber(12) + set latestEvidence(Evidence value) => $_setField(12, value); + @$pb.TagNumber(12) + $core.bool hasLatestEvidence() => $_has(11); + @$pb.TagNumber(12) + void clearLatestEvidence() => $_clearField(12); + @$pb.TagNumber(12) + Evidence ensureLatestEvidence() => $_ensure(11); + + @$pb.TagNumber(13) + $core.String get latestOutcome => $_getSZ(12); + @$pb.TagNumber(13) + set latestOutcome($core.String value) => $_setString(12, value); + @$pb.TagNumber(13) + $core.bool hasLatestOutcome() => $_has(12); + @$pb.TagNumber(13) + void clearLatestOutcome() => $_clearField(13); +} + +class TreatmentExt { + static final role = $pb.Extension( + _omitMessageNames ? '' : 'google.protobuf.MethodOptions', + _omitFieldNames ? '' : 'role', + 51002, + $pb.PbFieldType.OE, + defaultOrMaker: Role.ROLE_UNSPECIFIED, + valueOf: Role.valueOf, + enumValues: Role.values); + static void registerAllExtensions($pb.ExtensionRegistry registry) { + registry.add(role); + } +} + +const $core.bool _omitFieldNames = + $core.bool.fromEnvironment('protobuf.omit_field_names'); +const $core.bool _omitMessageNames = + $core.bool.fromEnvironment('protobuf.omit_message_names'); diff --git a/gen/dart/xtcp_treatment/v1/treatment.pbenum.dart b/gen/dart/xtcp_treatment/v1/treatment.pbenum.dart new file mode 100644 index 000000000..6ce55507b --- /dev/null +++ b/gen/dart/xtcp_treatment/v1/treatment.pbenum.dart @@ -0,0 +1,145 @@ +// This is a generated file - do not edit. +// +// Generated from xtcp_treatment/v1/treatment.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports + +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +/// Role is the certificate class an RPC requires. The server resolves the +/// caller's SPIFFE identity once per call and compares it with this option; +/// a method without a declaration is refused for everyone (fail closed). +class Role extends $pb.ProtobufEnum { + static const Role ROLE_UNSPECIFIED = + Role._(0, _omitEnumNames ? '' : 'ROLE_UNSPECIFIED'); + + /// spiffe://xtcp/agent//; may only speak for its own agent_id. + static const Role ROLE_AGENT = Role._(1, _omitEnumNames ? '' : 'ROLE_AGENT'); + + /// spiffe://xtcp/operator/; recorded as the audit actor. + static const Role ROLE_OPERATOR = + Role._(2, _omitEnumNames ? '' : 'ROLE_OPERATOR'); + + static const $core.List values = [ + ROLE_UNSPECIFIED, + ROLE_AGENT, + ROLE_OPERATOR, + ]; + + static final $core.List _byValue = + $pb.ProtobufEnum.$_initByValueList(values, 2); + static Role? valueOf($core.int value) => + value < 0 || value >= _byValue.length ? null : _byValue[value]; + + const Role._(super.value, super.name); +} + +class AssignmentMode extends $pb.ProtobufEnum { + static const AssignmentMode ASSIGNMENT_MODE_UNSPECIFIED = + AssignmentMode._(0, _omitEnumNames ? '' : 'ASSIGNMENT_MODE_UNSPECIFIED'); + static const AssignmentMode ASSIGNMENT_MODE_TRIAL = + AssignmentMode._(1, _omitEnumNames ? '' : 'ASSIGNMENT_MODE_TRIAL'); + static const AssignmentMode ASSIGNMENT_MODE_STABLE = + AssignmentMode._(2, _omitEnumNames ? '' : 'ASSIGNMENT_MODE_STABLE'); + static const AssignmentMode ASSIGNMENT_MODE_SUSPENDED = + AssignmentMode._(3, _omitEnumNames ? '' : 'ASSIGNMENT_MODE_SUSPENDED'); + static const AssignmentMode ASSIGNMENT_MODE_RETIRED = + AssignmentMode._(4, _omitEnumNames ? '' : 'ASSIGNMENT_MODE_RETIRED'); + + static const $core.List values = [ + ASSIGNMENT_MODE_UNSPECIFIED, + ASSIGNMENT_MODE_TRIAL, + ASSIGNMENT_MODE_STABLE, + ASSIGNMENT_MODE_SUSPENDED, + ASSIGNMENT_MODE_RETIRED, + ]; + + static final $core.List _byValue = + $pb.ProtobufEnum.$_initByValueList(values, 4); + static AssignmentMode? valueOf($core.int value) => + value < 0 || value >= _byValue.length ? null : _byValue[value]; + + const AssignmentMode._(super.value, super.name); +} + +class ApplyStatus extends $pb.ProtobufEnum { + static const ApplyStatus APPLY_STATUS_UNSPECIFIED = + ApplyStatus._(0, _omitEnumNames ? '' : 'APPLY_STATUS_UNSPECIFIED'); + static const ApplyStatus APPLY_STATUS_PENDING = + ApplyStatus._(1, _omitEnumNames ? '' : 'APPLY_STATUS_PENDING'); + static const ApplyStatus APPLY_STATUS_VERIFIED = + ApplyStatus._(2, _omitEnumNames ? '' : 'APPLY_STATUS_VERIFIED'); + static const ApplyStatus APPLY_STATUS_REMOVED = + ApplyStatus._(3, _omitEnumNames ? '' : 'APPLY_STATUS_REMOVED'); + static const ApplyStatus APPLY_STATUS_EXPIRED = + ApplyStatus._(4, _omitEnumNames ? '' : 'APPLY_STATUS_EXPIRED'); + static const ApplyStatus APPLY_STATUS_FAILED = + ApplyStatus._(5, _omitEnumNames ? '' : 'APPLY_STATUS_FAILED'); + + static const $core.List values = [ + APPLY_STATUS_UNSPECIFIED, + APPLY_STATUS_PENDING, + APPLY_STATUS_VERIFIED, + APPLY_STATUS_REMOVED, + APPLY_STATUS_EXPIRED, + APPLY_STATUS_FAILED, + ]; + + static final $core.List _byValue = + $pb.ProtobufEnum.$_initByValueList(values, 5); + static ApplyStatus? valueOf($core.int value) => + value < 0 || value >= _byValue.length ? null : _byValue[value]; + + const ApplyStatus._(super.value, super.name); +} + +class ExperimentStatus extends $pb.ProtobufEnum { + static const ExperimentStatus EXPERIMENT_STATUS_UNSPECIFIED = + ExperimentStatus._( + 0, _omitEnumNames ? '' : 'EXPERIMENT_STATUS_UNSPECIFIED'); + static const ExperimentStatus EXPERIMENT_STATUS_PROPOSED = + ExperimentStatus._(1, _omitEnumNames ? '' : 'EXPERIMENT_STATUS_PROPOSED'); + static const ExperimentStatus EXPERIMENT_STATUS_RUNNING = + ExperimentStatus._(2, _omitEnumNames ? '' : 'EXPERIMENT_STATUS_RUNNING'); + static const ExperimentStatus EXPERIMENT_STATUS_PAUSED = + ExperimentStatus._(3, _omitEnumNames ? '' : 'EXPERIMENT_STATUS_PAUSED'); + static const ExperimentStatus EXPERIMENT_STATUS_SUSPENDED = + ExperimentStatus._( + 4, _omitEnumNames ? '' : 'EXPERIMENT_STATUS_SUSPENDED'); + static const ExperimentStatus EXPERIMENT_STATUS_COMPLETE = + ExperimentStatus._(5, _omitEnumNames ? '' : 'EXPERIMENT_STATUS_COMPLETE'); + static const ExperimentStatus EXPERIMENT_STATUS_RETIRED = + ExperimentStatus._(6, _omitEnumNames ? '' : 'EXPERIMENT_STATUS_RETIRED'); + static const ExperimentStatus EXPERIMENT_STATUS_ROLLED_BACK = + ExperimentStatus._( + 7, _omitEnumNames ? '' : 'EXPERIMENT_STATUS_ROLLED_BACK'); + + static const $core.List values = [ + EXPERIMENT_STATUS_UNSPECIFIED, + EXPERIMENT_STATUS_PROPOSED, + EXPERIMENT_STATUS_RUNNING, + EXPERIMENT_STATUS_PAUSED, + EXPERIMENT_STATUS_SUSPENDED, + EXPERIMENT_STATUS_COMPLETE, + EXPERIMENT_STATUS_RETIRED, + EXPERIMENT_STATUS_ROLLED_BACK, + ]; + + static final $core.List _byValue = + $pb.ProtobufEnum.$_initByValueList(values, 7); + static ExperimentStatus? valueOf($core.int value) => + value < 0 || value >= _byValue.length ? null : _byValue[value]; + + const ExperimentStatus._(super.value, super.name); +} + +const $core.bool _omitEnumNames = + $core.bool.fromEnvironment('protobuf.omit_enum_names'); diff --git a/gen/dart/xtcp_treatment/v1/treatment.pbjson.dart b/gen/dart/xtcp_treatment/v1/treatment.pbjson.dart new file mode 100644 index 000000000..488fc1d5c --- /dev/null +++ b/gen/dart/xtcp_treatment/v1/treatment.pbjson.dart @@ -0,0 +1,911 @@ +// This is a generated file - do not edit. +// +// Generated from xtcp_treatment/v1/treatment.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports +// ignore_for_file: unused_import + +import 'dart:convert' as $convert; +import 'dart:core' as $core; +import 'dart:typed_data' as $typed_data; + +@$core.Deprecated('Use roleDescriptor instead') +const Role$json = { + '1': 'Role', + '2': [ + {'1': 'ROLE_UNSPECIFIED', '2': 0}, + {'1': 'ROLE_AGENT', '2': 1}, + {'1': 'ROLE_OPERATOR', '2': 2}, + ], +}; + +/// Descriptor for `Role`. Decode as a `google.protobuf.EnumDescriptorProto`. +final $typed_data.Uint8List roleDescriptor = $convert.base64Decode( + 'CgRSb2xlEhQKEFJPTEVfVU5TUEVDSUZJRUQQABIOCgpST0xFX0FHRU5UEAESEQoNUk9MRV9PUE' + 'VSQVRPUhAC'); + +@$core.Deprecated('Use assignmentModeDescriptor instead') +const AssignmentMode$json = { + '1': 'AssignmentMode', + '2': [ + {'1': 'ASSIGNMENT_MODE_UNSPECIFIED', '2': 0}, + {'1': 'ASSIGNMENT_MODE_TRIAL', '2': 1}, + {'1': 'ASSIGNMENT_MODE_STABLE', '2': 2}, + {'1': 'ASSIGNMENT_MODE_SUSPENDED', '2': 3}, + {'1': 'ASSIGNMENT_MODE_RETIRED', '2': 4}, + ], +}; + +/// Descriptor for `AssignmentMode`. Decode as a `google.protobuf.EnumDescriptorProto`. +final $typed_data.Uint8List assignmentModeDescriptor = $convert.base64Decode( + 'Cg5Bc3NpZ25tZW50TW9kZRIfChtBU1NJR05NRU5UX01PREVfVU5TUEVDSUZJRUQQABIZChVBU1' + 'NJR05NRU5UX01PREVfVFJJQUwQARIaChZBU1NJR05NRU5UX01PREVfU1RBQkxFEAISHQoZQVNT' + 'SUdOTUVOVF9NT0RFX1NVU1BFTkRFRBADEhsKF0FTU0lHTk1FTlRfTU9ERV9SRVRJUkVEEAQ='); + +@$core.Deprecated('Use applyStatusDescriptor instead') +const ApplyStatus$json = { + '1': 'ApplyStatus', + '2': [ + {'1': 'APPLY_STATUS_UNSPECIFIED', '2': 0}, + {'1': 'APPLY_STATUS_PENDING', '2': 1}, + {'1': 'APPLY_STATUS_VERIFIED', '2': 2}, + {'1': 'APPLY_STATUS_REMOVED', '2': 3}, + {'1': 'APPLY_STATUS_EXPIRED', '2': 4}, + {'1': 'APPLY_STATUS_FAILED', '2': 5}, + ], +}; + +/// Descriptor for `ApplyStatus`. Decode as a `google.protobuf.EnumDescriptorProto`. +final $typed_data.Uint8List applyStatusDescriptor = $convert.base64Decode( + 'CgtBcHBseVN0YXR1cxIcChhBUFBMWV9TVEFUVVNfVU5TUEVDSUZJRUQQABIYChRBUFBMWV9TVE' + 'FUVVNfUEVORElORxABEhkKFUFQUExZX1NUQVRVU19WRVJJRklFRBACEhgKFEFQUExZX1NUQVRV' + 'U19SRU1PVkVEEAMSGAoUQVBQTFlfU1RBVFVTX0VYUElSRUQQBBIXChNBUFBMWV9TVEFUVVNfRk' + 'FJTEVEEAU='); + +@$core.Deprecated('Use experimentStatusDescriptor instead') +const ExperimentStatus$json = { + '1': 'ExperimentStatus', + '2': [ + {'1': 'EXPERIMENT_STATUS_UNSPECIFIED', '2': 0}, + {'1': 'EXPERIMENT_STATUS_PROPOSED', '2': 1}, + {'1': 'EXPERIMENT_STATUS_RUNNING', '2': 2}, + {'1': 'EXPERIMENT_STATUS_PAUSED', '2': 3}, + {'1': 'EXPERIMENT_STATUS_SUSPENDED', '2': 4}, + {'1': 'EXPERIMENT_STATUS_COMPLETE', '2': 5}, + {'1': 'EXPERIMENT_STATUS_RETIRED', '2': 6}, + {'1': 'EXPERIMENT_STATUS_ROLLED_BACK', '2': 7}, + ], +}; + +/// Descriptor for `ExperimentStatus`. Decode as a `google.protobuf.EnumDescriptorProto`. +final $typed_data.Uint8List experimentStatusDescriptor = $convert.base64Decode( + 'ChBFeHBlcmltZW50U3RhdHVzEiEKHUVYUEVSSU1FTlRfU1RBVFVTX1VOU1BFQ0lGSUVEEAASHg' + 'oaRVhQRVJJTUVOVF9TVEFUVVNfUFJPUE9TRUQQARIdChlFWFBFUklNRU5UX1NUQVRVU19SVU5O' + 'SU5HEAISHAoYRVhQRVJJTUVOVF9TVEFUVVNfUEFVU0VEEAMSHwobRVhQRVJJTUVOVF9TVEFUVV' + 'NfU1VTUEVOREVEEAQSHgoaRVhQRVJJTUVOVF9TVEFUVVNfQ09NUExFVEUQBRIdChlFWFBFUklN' + 'RU5UX1NUQVRVU19SRVRJUkVEEAYSIQodRVhQRVJJTUVOVF9TVEFUVVNfUk9MTEVEX0JBQ0sQBw' + '=='); + +@$core.Deprecated('Use tCPSettingsDescriptor instead') +const TCPSettings$json = { + '1': 'TCPSettings', + '2': [ + { + '1': 'congestion_control', + '3': 1, + '4': 1, + '5': 9, + '8': {}, + '9': 0, + '10': 'congestionControl', + '17': true + }, + {'1': 'congestion_lock', '3': 2, '4': 1, '5': 8, '10': 'congestionLock'}, + { + '1': 'rto_min_ms', + '3': 3, + '4': 1, + '5': 13, + '8': {}, + '9': 1, + '10': 'rtoMinMs', + '17': true + }, + { + '1': 'initial_congestion_window', + '3': 4, + '4': 1, + '5': 13, + '8': {}, + '9': 2, + '10': 'initialCongestionWindow', + '17': true + }, + { + '1': 'initial_receive_window', + '3': 5, + '4': 1, + '5': 13, + '8': {}, + '9': 3, + '10': 'initialReceiveWindow', + '17': true + }, + { + '1': 'quick_ack', + '3': 6, + '4': 1, + '5': 8, + '9': 4, + '10': 'quickAck', + '17': true + }, + {'1': 'ecn', '3': 7, '4': 1, '5': 8, '9': 5, '10': 'ecn', '17': true}, + ], + '7': {}, + '8': [ + {'1': '_congestion_control'}, + {'1': '_rto_min_ms'}, + {'1': '_initial_congestion_window'}, + {'1': '_initial_receive_window'}, + {'1': '_quick_ack'}, + {'1': '_ecn'}, + ], +}; + +/// Descriptor for `TCPSettings`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List tCPSettingsDescriptor = $convert.base64Decode( + 'CgtUQ1BTZXR0aW5ncxJZChJjb25nZXN0aW9uX2NvbnRyb2wYASABKAlCJbpIInIgGA8yHF5bYS' + '16QS1aMC05XVthLXpBLVowLTlfLi1dKiRIAFIRY29uZ2VzdGlvbkNvbnRyb2yIAQESJwoPY29u' + 'Z2VzdGlvbl9sb2NrGAIgASgIUg5jb25nZXN0aW9uTG9jaxIuCgpydG9fbWluX21zGAMgASgNQg' + 'u6SAgqBhjAqQcoAUgBUghydG9NaW5Nc4gBARJLChlpbml0aWFsX2Nvbmdlc3Rpb25fd2luZG93' + 'GAQgASgNQgq6SAcqBRiACCgBSAJSF2luaXRpYWxDb25nZXN0aW9uV2luZG93iAEBEkUKFmluaX' + 'RpYWxfcmVjZWl2ZV93aW5kb3cYBSABKA1CCrpIByoFGIAIKAFIA1IUaW5pdGlhbFJlY2VpdmVX' + 'aW5kb3eIAQESIAoJcXVpY2tfYWNrGAYgASgISARSCHF1aWNrQWNriAEBEhUKA2VjbhgHIAEoCE' + 'gFUgNlY26IAQE6hwO6SIMDGvMBChZ0Y3Bfc2V0dGluZ3Mubm9uX2VtcHR5Ei9hIHRyZWF0bWVu' + 'dCBtdXN0IHNldCBhdCBsZWFzdCBvbmUgVENQIHBhcmFtZXRlchqnAWhhcyh0aGlzLmNvbmdlc3' + 'Rpb25fY29udHJvbCkgfHwgaGFzKHRoaXMucnRvX21pbl9tcykgfHwgaGFzKHRoaXMuaW5pdGlh' + 'bF9jb25nZXN0aW9uX3dpbmRvdykgfHwgaGFzKHRoaXMuaW5pdGlhbF9yZWNlaXZlX3dpbmRvdy' + 'kgfHwgaGFzKHRoaXMucXVpY2tfYWNrKSB8fCBoYXModGhpcy5lY24pGooBCiR0Y3Bfc2V0dGlu' + 'Z3MubG9ja19yZXF1aXJlc19hbGdvcml0aG0SK2Nvbmdlc3Rpb25fbG9jayByZXF1aXJlcyBjb2' + '5nZXN0aW9uX2NvbnRyb2waNSF0aGlzLmNvbmdlc3Rpb25fbG9jayB8fCBoYXModGhpcy5jb25n' + 'ZXN0aW9uX2NvbnRyb2wpQhUKE19jb25nZXN0aW9uX2NvbnRyb2xCDQoLX3J0b19taW5fbXNCHA' + 'oaX2luaXRpYWxfY29uZ2VzdGlvbl93aW5kb3dCGQoXX2luaXRpYWxfcmVjZWl2ZV93aW5kb3dC' + 'DAoKX3F1aWNrX2Fja0IGCgRfZWNu'); + +@$core.Deprecated('Use treatmentDescriptor instead') +const Treatment$json = { + '1': 'Treatment', + '2': [ + {'1': 'id', '3': 1, '4': 1, '5': 9, '8': {}, '10': 'id'}, + {'1': 'revision', '3': 2, '4': 1, '5': 4, '8': {}, '10': 'revision'}, + {'1': 'destination', '3': 3, '4': 1, '5': 9, '8': {}, '10': 'destination'}, + { + '1': 'settings', + '3': 4, + '4': 1, + '5': 11, + '6': '.xtcp_treatment.v1.TCPSettings', + '8': {}, + '10': 'settings' + }, + { + '1': 'ecn_fabric_verified', + '3': 5, + '4': 1, + '5': 8, + '10': 'ecnFabricVerified' + }, + ], + '7': {}, +}; + +/// Descriptor for `Treatment`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List treatmentDescriptor = $convert.base64Decode( + 'CglUcmVhdG1lbnQSOQoCaWQYASABKAlCKbpIJnIkMiJeW2EtekEtWjAtOV1bYS16QS1aMC05Xy' + '4tXXswLDEyN30kUgJpZBIjCghyZXZpc2lvbhgCIAEoBEIHukgEMgIgAFIIcmV2aXNpb24SLAoL' + 'ZGVzdGluYXRpb24YAyABKAlCCrpIB3IFGDHoAQFSC2Rlc3RpbmF0aW9uEkIKCHNldHRpbmdzGA' + 'QgASgLMh4ueHRjcF90cmVhdG1lbnQudjEuVENQU2V0dGluZ3NCBrpIA8gBAVIIc2V0dGluZ3MS' + 'LgoTZWNuX2ZhYnJpY192ZXJpZmllZBgFIAEoCFIRZWNuRmFicmljVmVyaWZpZWQ6yAG6SMQBGs' + 'EBChx0cmVhdG1lbnQuZGN0Y3BfcmVxdWlyZXNfZWNuEkFEQ1RDUCByZXF1aXJlcyBpbmRlcGVu' + 'ZGVudGx5IHZlcmlmaWVkIEVDTiBmYWJyaWMgYW5kIGV4cGxpY2l0IEVDThpedGhpcy5zZXR0aW' + '5ncy5jb25nZXN0aW9uX2NvbnRyb2wgIT0gJ2RjdGNwJyB8fCAodGhpcy5lY25fZmFicmljX3Zl' + 'cmlmaWVkICYmIHRoaXMuc2V0dGluZ3MuZWNuKQ=='); + +@$core.Deprecated('Use assignmentDescriptor instead') +const Assignment$json = { + '1': 'Assignment', + '2': [ + {'1': 'id', '3': 1, '4': 1, '5': 9, '8': {}, '10': 'id'}, + { + '1': 'experiment_id', + '3': 2, + '4': 1, + '5': 9, + '8': {}, + '10': 'experimentId' + }, + { + '1': 'treatment', + '3': 3, + '4': 1, + '5': 11, + '6': '.xtcp_treatment.v1.Treatment', + '8': {}, + '10': 'treatment' + }, + { + '1': 'mode', + '3': 4, + '4': 1, + '5': 14, + '6': '.xtcp_treatment.v1.AssignmentMode', + '8': {}, + '10': 'mode' + }, + { + '1': 'lease_expires_at', + '3': 5, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '10': 'leaseExpiresAt' + }, + { + '1': 'predecessor', + '3': 6, + '4': 1, + '5': 11, + '6': '.xtcp_treatment.v1.Treatment', + '10': 'predecessor' + }, + {'1': 'generation', '3': 7, '4': 1, '5': 4, '8': {}, '10': 'generation'}, + ], + '7': {}, +}; + +/// Descriptor for `Assignment`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List assignmentDescriptor = $convert.base64Decode( + 'CgpBc3NpZ25tZW50EjkKAmlkGAEgASgJQim6SCZyJDIiXlthLXpBLVowLTldW2EtekEtWjAtOV' + '8uLV17MCwxMjd9JFICaWQSTgoNZXhwZXJpbWVudF9pZBgCIAEoCUIpukgmciQyIl5bYS16QS1a' + 'MC05XVthLXpBLVowLTlfLi1dezAsMTI3fSRSDGV4cGVyaW1lbnRJZBJCCgl0cmVhdG1lbnQYAy' + 'ABKAsyHC54dGNwX3RyZWF0bWVudC52MS5UcmVhdG1lbnRCBrpIA8gBAVIJdHJlYXRtZW50EkEK' + 'BG1vZGUYBCABKA4yIS54dGNwX3RyZWF0bWVudC52MS5Bc3NpZ25tZW50TW9kZUIKukgHggEEEA' + 'EgAFIEbW9kZRJEChBsZWFzZV9leHBpcmVzX2F0GAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRp' + 'bWVzdGFtcFIObGVhc2VFeHBpcmVzQXQSPgoLcHJlZGVjZXNzb3IYBiABKAsyHC54dGNwX3RyZW' + 'F0bWVudC52MS5UcmVhdG1lbnRSC3ByZWRlY2Vzc29yEicKCmdlbmVyYXRpb24YByABKARCB7pI' + 'BDICIABSCmdlbmVyYXRpb246rQK6SKkCGn0KH2Fzc2lnbm1lbnQudHJpYWxfcmVxdWlyZXNfbG' + 'Vhc2USLGEgdHJpYWwgYXNzaWdubWVudCByZXF1aXJlcyBsZWFzZV9leHBpcmVzX2F0Gix0aGlz' + 'Lm1vZGUgIT0gMSB8fCBoYXModGhpcy5sZWFzZV9leHBpcmVzX2F0KRqnAQoiYXNzaWdubWVudC' + '5wcmVkZWNlc3Nvcl9kZXN0aW5hdGlvbhIrcHJlZGVjZXNzb3IgbXVzdCBjb3ZlciB0aGUgc2Ft' + 'ZSBkZXN0aW5hdGlvbhpUIWhhcyh0aGlzLnByZWRlY2Vzc29yKSB8fCB0aGlzLnByZWRlY2Vzc2' + '9yLmRlc3RpbmF0aW9uID09IHRoaXMudHJlYXRtZW50LmRlc3RpbmF0aW9u'); + +@$core.Deprecated('Use desiredStateDescriptor instead') +const DesiredState$json = { + '1': 'DesiredState', + '2': [ + {'1': 'agent_id', '3': 1, '4': 1, '5': 9, '8': {}, '10': 'agentId'}, + {'1': 'revision', '3': 2, '4': 1, '5': 4, '8': {}, '10': 'revision'}, + {'1': 'suspended', '3': 3, '4': 1, '5': 8, '10': 'suspended'}, + { + '1': 'assignments', + '3': 4, + '4': 3, + '5': 11, + '6': '.xtcp_treatment.v1.Assignment', + '8': {}, + '10': 'assignments' + }, + { + '1': 'issued_at', + '3': 5, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '8': {}, + '10': 'issuedAt' + }, + ], +}; + +/// Descriptor for `DesiredState`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List desiredStateDescriptor = $convert.base64Decode( + 'CgxEZXNpcmVkU3RhdGUSRAoIYWdlbnRfaWQYASABKAlCKbpIJnIkMiJeW2EtekEtWjAtOV1bYS' + '16QS1aMC05Xy4tXXswLDEyN30kUgdhZ2VudElkEiMKCHJldmlzaW9uGAIgASgEQge6SAQyAiAA' + 'UghyZXZpc2lvbhIcCglzdXNwZW5kZWQYAyABKAhSCXN1c3BlbmRlZBJKCgthc3NpZ25tZW50cx' + 'gEIAMoCzIdLnh0Y3BfdHJlYXRtZW50LnYxLkFzc2lnbm1lbnRCCbpIBpIBAxCAIFILYXNzaWdu' + 'bWVudHMSPwoJaXNzdWVkX2F0GAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEIGuk' + 'gDyAEBUghpc3N1ZWRBdA=='); + +@$core.Deprecated('Use capabilitiesDescriptor instead') +const Capabilities$json = { + '1': 'Capabilities', + '2': [ + {'1': 'boot_id', '3': 1, '4': 1, '5': 9, '8': {}, '10': 'bootId'}, + { + '1': 'kernel_release', + '3': 2, + '4': 1, + '5': 9, + '8': {}, + '10': 'kernelRelease' + }, + { + '1': 'congestion_algorithms', + '3': 3, + '4': 3, + '5': 9, + '8': {}, + '10': 'congestionAlgorithms' + }, + { + '1': 'simple_host_routing', + '3': 4, + '4': 1, + '5': 8, + '10': 'simpleHostRouting' + }, + {'1': 'actuator_enabled', '3': 5, '4': 1, '5': 8, '10': 'actuatorEnabled'}, + ], +}; + +/// Descriptor for `Capabilities`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List capabilitiesDescriptor = $convert.base64Decode( + 'CgxDYXBhYmlsaXRpZXMSQgoHYm9vdF9pZBgBIAEoCUIpukgmciQyIl5bYS16QS1aMC05XVthLX' + 'pBLVowLTlfLi1dezAsMTI3fSRSBmJvb3RJZBIvCg5rZXJuZWxfcmVsZWFzZRgCIAEoCUIIukgF' + 'cgMYgAJSDWtlcm5lbFJlbGVhc2USQwoVY29uZ2VzdGlvbl9hbGdvcml0aG1zGAMgAygJQg66SA' + 'uSAQgQQCIEcgIYD1IUY29uZ2VzdGlvbkFsZ29yaXRobXMSLgoTc2ltcGxlX2hvc3Rfcm91dGlu' + 'ZxgEIAEoCFIRc2ltcGxlSG9zdFJvdXRpbmcSKQoQYWN0dWF0b3JfZW5hYmxlZBgFIAEoCFIPYW' + 'N0dWF0b3JFbmFibGVk'); + +@$core.Deprecated('Use applicationRecordDescriptor instead') +const ApplicationRecord$json = { + '1': 'ApplicationRecord', + '2': [ + { + '1': 'assignment', + '3': 1, + '4': 1, + '5': 11, + '6': '.xtcp_treatment.v1.Assignment', + '8': {}, + '10': 'assignment' + }, + {'1': 'desired_revision', '3': 2, '4': 1, '5': 4, '10': 'desiredRevision'}, + { + '1': 'status', + '3': 3, + '4': 1, + '5': 14, + '6': '.xtcp_treatment.v1.ApplyStatus', + '8': {}, + '10': 'status' + }, + { + '1': 'received_at', + '3': 4, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '10': 'receivedAt' + }, + { + '1': 'attempted_at', + '3': 5, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '10': 'attemptedAt' + }, + { + '1': 'applied_at', + '3': 6, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '10': 'appliedAt' + }, + { + '1': 'verified_at', + '3': 7, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '10': 'verifiedAt' + }, + { + '1': 'removed_at', + '3': 8, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '10': 'removedAt' + }, + {'1': 'error', '3': 9, '4': 1, '5': 9, '8': {}, '10': 'error'}, + { + '1': 'installed_route', + '3': 10, + '4': 1, + '5': 12, + '8': {}, + '10': 'installedRoute' + }, + { + '1': 'installed_boot_id', + '3': 11, + '4': 1, + '5': 9, + '8': {}, + '10': 'installedBootId' + }, + {'1': 'expired_latched', '3': 12, '4': 1, '5': 8, '10': 'expiredLatched'}, + ], +}; + +/// Descriptor for `ApplicationRecord`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List applicationRecordDescriptor = $convert.base64Decode( + 'ChFBcHBsaWNhdGlvblJlY29yZBJFCgphc3NpZ25tZW50GAEgASgLMh0ueHRjcF90cmVhdG1lbn' + 'QudjEuQXNzaWdubWVudEIGukgDyAEBUgphc3NpZ25tZW50EikKEGRlc2lyZWRfcmV2aXNpb24Y' + 'AiABKARSD2Rlc2lyZWRSZXZpc2lvbhJACgZzdGF0dXMYAyABKA4yHi54dGNwX3RyZWF0bWVudC' + '52MS5BcHBseVN0YXR1c0IIukgFggECEAFSBnN0YXR1cxI7CgtyZWNlaXZlZF9hdBgEIAEoCzIa' + 'Lmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBSCnJlY2VpdmVkQXQSPQoMYXR0ZW1wdGVkX2F0GA' + 'UgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcFILYXR0ZW1wdGVkQXQSOQoKYXBwbGll' + 'ZF9hdBgGIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBSCWFwcGxpZWRBdBI7Cgt2ZX' + 'JpZmllZF9hdBgHIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBSCnZlcmlmaWVkQXQS' + 'OQoKcmVtb3ZlZF9hdBgIIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBSCXJlbW92ZW' + 'RBdBIeCgVlcnJvchgJIAEoCUIIukgFcgMYgCBSBWVycm9yEjEKD2luc3RhbGxlZF9yb3V0ZRgK' + 'IAEoDEIIukgFegMYgCBSDmluc3RhbGxlZFJvdXRlEjQKEWluc3RhbGxlZF9ib290X2lkGAsgAS' + 'gJQgi6SAVyAxiAAVIPaW5zdGFsbGVkQm9vdElkEicKD2V4cGlyZWRfbGF0Y2hlZBgMIAEoCFIO' + 'ZXhwaXJlZExhdGNoZWQ='); + +@$core.Deprecated('Use localStateDescriptor instead') +const LocalState$json = { + '1': 'LocalState', + '2': [ + { + '1': 'desired', + '3': 1, + '4': 1, + '5': 11, + '6': '.xtcp_treatment.v1.DesiredState', + '10': 'desired' + }, + { + '1': 'locally_suspended', + '3': 2, + '4': 1, + '5': 8, + '10': 'locallySuspended' + }, + { + '1': 'suspension_reason', + '3': 3, + '4': 1, + '5': 9, + '8': {}, + '10': 'suspensionReason' + }, + { + '1': 'records', + '3': 4, + '4': 3, + '5': 11, + '6': '.xtcp_treatment.v1.ApplicationRecord', + '8': {}, + '10': 'records' + }, + { + '1': 'updated_at', + '3': 5, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '10': 'updatedAt' + }, + ], +}; + +/// Descriptor for `LocalState`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List localStateDescriptor = $convert.base64Decode( + 'CgpMb2NhbFN0YXRlEjkKB2Rlc2lyZWQYASABKAsyHy54dGNwX3RyZWF0bWVudC52MS5EZXNpcm' + 'VkU3RhdGVSB2Rlc2lyZWQSKwoRbG9jYWxseV9zdXNwZW5kZWQYAiABKAhSEGxvY2FsbHlTdXNw' + 'ZW5kZWQSNQoRc3VzcGVuc2lvbl9yZWFzb24YAyABKAlCCLpIBXIDGIAIUhBzdXNwZW5zaW9uUm' + 'Vhc29uEkkKB3JlY29yZHMYBCADKAsyJC54dGNwX3RyZWF0bWVudC52MS5BcHBsaWNhdGlvblJl' + 'Y29yZEIJukgGkgEDEIBAUgdyZWNvcmRzEjkKCnVwZGF0ZWRfYXQYBSABKAsyGi5nb29nbGUucH' + 'JvdG9idWYuVGltZXN0YW1wUgl1cGRhdGVkQXQ='); + +@$core.Deprecated('Use hostEvidenceDescriptor instead') +const HostEvidence$json = { + '1': 'HostEvidence', + '2': [ + {'1': 'agent_id', '3': 1, '4': 1, '5': 9, '8': {}, '10': 'agentId'}, + {'1': 'sockets', '3': 2, '4': 1, '5': 4, '10': 'sockets'}, + {'1': 'bytes_acked', '3': 3, '4': 1, '5': 4, '10': 'bytesAcked'}, + {'1': 'goodput_bps', '3': 4, '4': 1, '5': 1, '8': {}, '10': 'goodputBps'}, + { + '1': 'retransmission_ratio', + '3': 5, + '4': 1, + '5': 1, + '8': {}, + '10': 'retransmissionRatio' + }, + {'1': 'rtt_p95_ms', '3': 6, '4': 1, '5': 1, '8': {}, '10': 'rttP95Ms'}, + {'1': 'exposed_sockets', '3': 7, '4': 1, '5': 4, '10': 'exposedSockets'}, + { + '1': 'collection_healthy', + '3': 8, + '4': 1, + '5': 8, + '10': 'collectionHealthy' + }, + ], + '7': {}, +}; + +/// Descriptor for `HostEvidence`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List hostEvidenceDescriptor = $convert.base64Decode( + 'CgxIb3N0RXZpZGVuY2USRAoIYWdlbnRfaWQYASABKAlCKbpIJnIkMiJeW2EtekEtWjAtOV1bYS' + '16QS1aMC05Xy4tXXswLDEyN30kUgdhZ2VudElkEhgKB3NvY2tldHMYAiABKARSB3NvY2tldHMS' + 'HwoLYnl0ZXNfYWNrZWQYAyABKARSCmJ5dGVzQWNrZWQSMQoLZ29vZHB1dF9icHMYBCABKAFCEL' + 'pIDRILQAEpAAAAAAAAAABSCmdvb2RwdXRCcHMSQwoUcmV0cmFuc21pc3Npb25fcmF0aW8YBSAB' + 'KAFCELpIDRILQAEpAAAAAAAAAABSE3JldHJhbnNtaXNzaW9uUmF0aW8SLgoKcnR0X3A5NV9tcx' + 'gGIAEoAUIQukgNEgtAASkAAAAAAAAAAFIIcnR0UDk1TXMSJwoPZXhwb3NlZF9zb2NrZXRzGAcg' + 'ASgEUg5leHBvc2VkU29ja2V0cxItChJjb2xsZWN0aW9uX2hlYWx0aHkYCCABKAhSEWNvbGxlY3' + 'Rpb25IZWFsdGh5Oni6SHUacwokaG9zdF9ldmlkZW5jZS5leHBvc2VkX3dpdGhpbl9zb2NrZXRz' + 'EiVleHBvc2VkX3NvY2tldHMgY2Fubm90IGV4Y2VlZCBzb2NrZXRzGiR0aGlzLmV4cG9zZWRfc2' + '9ja2V0cyA8PSB0aGlzLnNvY2tldHM='); + +@$core.Deprecated('Use evidenceDescriptor instead') +const Evidence$json = { + '1': 'Evidence', + '2': [ + {'1': 'id', '3': 1, '4': 1, '5': 9, '8': {}, '10': 'id'}, + { + '1': 'analyzed_at', + '3': 2, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '8': {}, + '10': 'analyzedAt' + }, + { + '1': 'window_start', + '3': 3, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '8': {}, + '10': 'windowStart' + }, + { + '1': 'window_end', + '3': 4, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '8': {}, + '10': 'windowEnd' + }, + { + '1': 'query_version', + '3': 5, + '4': 1, + '5': 9, + '8': {}, + '10': 'queryVersion' + }, + { + '1': 'enrichment_version', + '3': 6, + '4': 1, + '5': 9, + '8': {}, + '10': 'enrichmentVersion' + }, + { + '1': 'hosts', + '3': 7, + '4': 3, + '5': 11, + '6': '.xtcp_treatment.v1.HostEvidence', + '8': {}, + '10': 'hosts' + }, + { + '1': 'artifact_sha256', + '3': 8, + '4': 1, + '5': 9, + '8': {}, + '10': 'artifactSha256' + }, + ], + '7': {}, +}; + +/// Descriptor for `Evidence`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List evidenceDescriptor = $convert.base64Decode( + 'CghFdmlkZW5jZRI5CgJpZBgBIAEoCUIpukgmciQyIl5bYS16QS1aMC05XVthLXpBLVowLTlfLi' + '1dezAsMTI3fSRSAmlkEkMKC2FuYWx5emVkX2F0GAIgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRp' + 'bWVzdGFtcEIGukgDyAEBUgphbmFseXplZEF0EkUKDHdpbmRvd19zdGFydBgDIAEoCzIaLmdvb2' + 'dsZS5wcm90b2J1Zi5UaW1lc3RhbXBCBrpIA8gBAVILd2luZG93U3RhcnQSQQoKd2luZG93X2Vu' + 'ZBgEIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBCBrpIA8gBAVIJd2luZG93RW5kEi' + '8KDXF1ZXJ5X3ZlcnNpb24YBSABKAlCCrpIB3IFEAEYgAJSDHF1ZXJ5VmVyc2lvbhI3ChJlbnJp' + 'Y2htZW50X3ZlcnNpb24YBiABKAlCCLpIBXIDGIACUhFlbnJpY2htZW50VmVyc2lvbhJCCgVob3' + 'N0cxgHIAMoCzIfLnh0Y3BfdHJlYXRtZW50LnYxLkhvc3RFdmlkZW5jZUILukgIkgEFCAEQkE5S' + 'BWhvc3RzEj4KD2FydGlmYWN0X3NoYTI1NhgIIAEoCUIVukgSchAyDl5bMC05YS1mXXs2NH0kUg' + '5hcnRpZmFjdFNoYTI1Njq3AbpIswEasAEKEWV2aWRlbmNlLmludGVydmFsEk93aW5kb3dfZW5k' + 'IG11c3QgZm9sbG93IHdpbmRvd19zdGFydCBhbmQgYW5hbHl6ZWRfYXQgbXVzdCBub3QgcHJlY2' + 'VkZSB3aW5kb3dfZW5kGkp0aGlzLndpbmRvd19lbmQgPiB0aGlzLndpbmRvd19zdGFydCAmJiB0' + 'aGlzLmFuYWx5emVkX2F0ID49IHRoaXMud2luZG93X2VuZA=='); + +@$core.Deprecated('Use experimentPolicyDescriptor instead') +const ExperimentPolicy$json = { + '1': 'ExperimentPolicy', + '2': [ + { + '1': 'minimum_observation', + '3': 1, + '4': 1, + '5': 11, + '6': '.google.protobuf.Duration', + '8': {}, + '10': 'minimumObservation' + }, + { + '1': 'maximum_phase', + '3': 2, + '4': 1, + '5': 11, + '6': '.google.protobuf.Duration', + '8': {}, + '10': 'maximumPhase' + }, + { + '1': 'minimum_sockets_per_host', + '3': 3, + '4': 1, + '5': 4, + '8': {}, + '10': 'minimumSocketsPerHost' + }, + { + '1': 'minimum_bytes_per_host', + '3': 4, + '4': 1, + '5': 4, + '8': {}, + '10': 'minimumBytesPerHost' + }, + { + '1': 'minimum_treated_hosts', + '3': 5, + '4': 1, + '5': 13, + '8': {}, + '10': 'minimumTreatedHosts' + }, + { + '1': 'minimum_improvement', + '3': 6, + '4': 1, + '5': 1, + '8': {}, + '10': 'minimumImprovement' + }, + { + '1': 'maximum_retransmission_increase', + '3': 7, + '4': 1, + '5': 1, + '8': {}, + '10': 'maximumRetransmissionIncrease' + }, + { + '1': 'maximum_rtt_increase', + '3': 8, + '4': 1, + '5': 1, + '8': {}, + '10': 'maximumRttIncrease' + }, + { + '1': 'maximum_evidence_age', + '3': 9, + '4': 1, + '5': 11, + '6': '.google.protobuf.Duration', + '8': {}, + '10': 'maximumEvidenceAge' + }, + ], + '7': {}, +}; + +/// Descriptor for `ExperimentPolicy`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List experimentPolicyDescriptor = $convert.base64Decode( + 'ChBFeHBlcmltZW50UG9saWN5El0KE21pbmltdW1fb2JzZXJ2YXRpb24YASABKAsyGS5nb29nbG' + 'UucHJvdG9idWYuRHVyYXRpb25CEbpIDsgBAaoBCCIECID1JCoAUhJtaW5pbXVtT2JzZXJ2YXRp' + 'b24SUQoNbWF4aW11bV9waGFzZRgCIAEoCzIZLmdvb2dsZS5wcm90b2J1Zi5EdXJhdGlvbkIRuk' + 'gOyAEBqgEIIgQIgPUkKgBSDG1heGltdW1QaGFzZRJAChhtaW5pbXVtX3NvY2tldHNfcGVyX2hv' + 'c3QYAyABKARCB7pIBDICIABSFW1pbmltdW1Tb2NrZXRzUGVySG9zdBI8ChZtaW5pbXVtX2J5dG' + 'VzX3Blcl9ob3N0GAQgASgEQge6SAQyAiAAUhNtaW5pbXVtQnl0ZXNQZXJIb3N0Ej4KFW1pbmlt' + 'dW1fdHJlYXRlZF9ob3N0cxgFIAEoDUIKukgHKgUYkE4oAlITbWluaW11bVRyZWF0ZWRIb3N0cx' + 'JKChNtaW5pbXVtX2ltcHJvdmVtZW50GAYgASgBQhm6SBYSFEABGQAAAAAAAPA/KQAAAAAAAAAA' + 'UhJtaW5pbXVtSW1wcm92ZW1lbnQSYQofbWF4aW11bV9yZXRyYW5zbWlzc2lvbl9pbmNyZWFzZR' + 'gHIAEoAUIZukgWEhRAARkAAAAAAADwPykAAAAAAAAAAFIdbWF4aW11bVJldHJhbnNtaXNzaW9u' + 'SW5jcmVhc2USSwoUbWF4aW11bV9ydHRfaW5jcmVhc2UYCCABKAFCGbpIFhIUQAEZAAAAAAAA8D' + '8pAAAAAAAAAABSEm1heGltdW1SdHRJbmNyZWFzZRJeChRtYXhpbXVtX2V2aWRlbmNlX2FnZRgJ' + 'IAEoCzIZLmdvb2dsZS5wcm90b2J1Zi5EdXJhdGlvbkIRukgOyAEBqgEIIgQIgPUkKgBSEm1heG' + 'ltdW1FdmlkZW5jZUFnZTqJAbpIhQEaggEKH3BvbGljeS5vYnNlcnZhdGlvbl93aXRoaW5fcGhh' + 'c2USL21pbmltdW1fb2JzZXJ2YXRpb24gY2Fubm90IGV4Y2VlZCBtYXhpbXVtX3BoYXNlGi50aG' + 'lzLm1pbmltdW1fb2JzZXJ2YXRpb24gPD0gdGhpcy5tYXhpbXVtX3BoYXNl'); + +@$core.Deprecated('Use phaseDescriptor instead') +const Phase$json = { + '1': 'Phase', + '2': [ + { + '1': 'target_hosts', + '3': 1, + '4': 1, + '5': 13, + '8': {}, + '10': 'targetHosts' + }, + { + '1': 'issued_at', + '3': 2, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '10': 'issuedAt' + }, + { + '1': 'observation_started_at', + '3': 3, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '10': 'observationStartedAt' + }, + { + '1': 'evaluated_at', + '3': 4, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '10': 'evaluatedAt' + }, + { + '1': 'evidence', + '3': 5, + '4': 1, + '5': 11, + '6': '.xtcp_treatment.v1.Evidence', + '10': 'evidence' + }, + {'1': 'outcome', '3': 6, '4': 1, '5': 9, '8': {}, '10': 'outcome'}, + {'1': 'reviewer', '3': 7, '4': 1, '5': 9, '8': {}, '10': 'reviewer'}, + {'1': 'reason', '3': 8, '4': 1, '5': 9, '8': {}, '10': 'reason'}, + ], +}; + +/// Descriptor for `Phase`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List phaseDescriptor = $convert.base64Decode( + 'CgVQaGFzZRItCgx0YXJnZXRfaG9zdHMYASABKA1CCrpIByoFGJBOKAFSC3RhcmdldEhvc3RzEj' + 'cKCWlzc3VlZF9hdBgCIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBSCGlzc3VlZEF0' + 'ElAKFm9ic2VydmF0aW9uX3N0YXJ0ZWRfYXQYAyABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZX' + 'N0YW1wUhRvYnNlcnZhdGlvblN0YXJ0ZWRBdBI9CgxldmFsdWF0ZWRfYXQYBCABKAsyGi5nb29n' + 'bGUucHJvdG9idWYuVGltZXN0YW1wUgtldmFsdWF0ZWRBdBI3CghldmlkZW5jZRgFIAEoCzIbLn' + 'h0Y3BfdHJlYXRtZW50LnYxLkV2aWRlbmNlUghldmlkZW5jZRIhCgdvdXRjb21lGAYgASgJQge6' + 'SARyAhhAUgdvdXRjb21lEiQKCHJldmlld2VyGAcgASgJQgi6SAVyAxiAAVIIcmV2aWV3ZXISIA' + 'oGcmVhc29uGAggASgJQgi6SAVyAxiAIFIGcmVhc29u'); + +@$core.Deprecated('Use experimentDescriptor instead') +const Experiment$json = { + '1': 'Experiment', + '2': [ + {'1': 'id', '3': 1, '4': 1, '5': 9, '8': {}, '10': 'id'}, + {'1': 'location', '3': 2, '4': 1, '5': 9, '8': {}, '10': 'location'}, + {'1': 'hypothesis', '3': 3, '4': 1, '5': 9, '8': {}, '10': 'hypothesis'}, + { + '1': 'treatment', + '3': 4, + '4': 1, + '5': 11, + '6': '.xtcp_treatment.v1.Treatment', + '8': {}, + '10': 'treatment' + }, + { + '1': 'policy', + '3': 5, + '4': 1, + '5': 11, + '6': '.xtcp_treatment.v1.ExperimentPolicy', + '8': {}, + '10': 'policy' + }, + { + '1': 'baseline', + '3': 6, + '4': 1, + '5': 11, + '6': '.xtcp_treatment.v1.Evidence', + '8': {}, + '10': 'baseline' + }, + {'1': 'cohort_order', '3': 7, '4': 3, '5': 9, '8': {}, '10': 'cohortOrder'}, + { + '1': 'phases', + '3': 8, + '4': 3, + '5': 11, + '6': '.xtcp_treatment.v1.Phase', + '8': {}, + '10': 'phases' + }, + { + '1': 'status', + '3': 9, + '4': 1, + '5': 14, + '6': '.xtcp_treatment.v1.ExperimentStatus', + '8': {}, + '10': 'status' + }, + {'1': 'revision', '3': 10, '4': 1, '5': 4, '10': 'revision'}, + { + '1': 'proposed_at', + '3': 11, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '10': 'proposedAt' + }, + { + '1': 'latest_evidence', + '3': 12, + '4': 1, + '5': 11, + '6': '.xtcp_treatment.v1.Evidence', + '10': 'latestEvidence' + }, + { + '1': 'latest_outcome', + '3': 13, + '4': 1, + '5': 9, + '8': {}, + '10': 'latestOutcome' + }, + ], +}; + +/// Descriptor for `Experiment`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List experimentDescriptor = $convert.base64Decode( + 'CgpFeHBlcmltZW50EjkKAmlkGAEgASgJQim6SCZyJDIiXlthLXpBLVowLTldW2EtekEtWjAtOV' + '8uLV17MCwxMjd9JFICaWQSRQoIbG9jYXRpb24YAiABKAlCKbpIJnIkMiJeW2EtekEtWjAtOV1b' + 'YS16QS1aMC05Xy4tXXswLDEyN30kUghsb2NhdGlvbhIqCgpoeXBvdGhlc2lzGAMgASgJQgq6SA' + 'dyBRABGIBAUgpoeXBvdGhlc2lzEkIKCXRyZWF0bWVudBgEIAEoCzIcLnh0Y3BfdHJlYXRtZW50' + 'LnYxLlRyZWF0bWVudEIGukgDyAEBUgl0cmVhdG1lbnQSQwoGcG9saWN5GAUgASgLMiMueHRjcF' + '90cmVhdG1lbnQudjEuRXhwZXJpbWVudFBvbGljeUIGukgDyAEBUgZwb2xpY3kSPwoIYmFzZWxp' + 'bmUYBiABKAsyGy54dGNwX3RyZWF0bWVudC52MS5FdmlkZW5jZUIGukgDyAEBUghiYXNlbGluZR' + 'JYCgxjb2hvcnRfb3JkZXIYByADKAlCNbpIMpIBLwgDEJBOGAEiJnIkMiJeW2EtekEtWjAtOV1b' + 'YS16QS1aMC05Xy4tXXswLDEyN30kUgtjb2hvcnRPcmRlchI6CgZwaGFzZXMYCCADKAsyGC54dG' + 'NwX3RyZWF0bWVudC52MS5QaGFzZUIIukgFkgECEEBSBnBoYXNlcxJFCgZzdGF0dXMYCSABKA4y' + 'Iy54dGNwX3RyZWF0bWVudC52MS5FeHBlcmltZW50U3RhdHVzQgi6SAWCAQIQAVIGc3RhdHVzEh' + 'oKCHJldmlzaW9uGAogASgEUghyZXZpc2lvbhI7Cgtwcm9wb3NlZF9hdBgLIAEoCzIaLmdvb2ds' + 'ZS5wcm90b2J1Zi5UaW1lc3RhbXBSCnByb3Bvc2VkQXQSRAoPbGF0ZXN0X2V2aWRlbmNlGAwgAS' + 'gLMhsueHRjcF90cmVhdG1lbnQudjEuRXZpZGVuY2VSDmxhdGVzdEV2aWRlbmNlEi4KDmxhdGVz' + 'dF9vdXRjb21lGA0gASgJQge6SARyAhhAUg1sYXRlc3RPdXRjb21l'); diff --git a/gen/go/xtcp_controller/controller.pb.go b/gen/go/xtcp_controller/controller.pb.go deleted file mode 100644 index 1d0bc74bb..000000000 --- a/gen/go/xtcp_controller/controller.pb.go +++ /dev/null @@ -1,2712 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.36.11 -// protoc v7.36.2 -// source: xtcp/controller/v1/controller.proto - -package xtcp_controller - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - durationpb "google.golang.org/protobuf/types/known/durationpb" - timestamppb "google.golang.org/protobuf/types/known/timestamppb" - reflect "reflect" - sync "sync" - unsafe "unsafe" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type AssignmentMode int32 - -const ( - AssignmentMode_ASSIGNMENT_MODE_UNSPECIFIED AssignmentMode = 0 - AssignmentMode_ASSIGNMENT_MODE_TRIAL AssignmentMode = 1 - AssignmentMode_ASSIGNMENT_MODE_STABLE AssignmentMode = 2 - AssignmentMode_ASSIGNMENT_MODE_SUSPENDED AssignmentMode = 3 - AssignmentMode_ASSIGNMENT_MODE_RETIRED AssignmentMode = 4 -) - -// Enum value maps for AssignmentMode. -var ( - AssignmentMode_name = map[int32]string{ - 0: "ASSIGNMENT_MODE_UNSPECIFIED", - 1: "ASSIGNMENT_MODE_TRIAL", - 2: "ASSIGNMENT_MODE_STABLE", - 3: "ASSIGNMENT_MODE_SUSPENDED", - 4: "ASSIGNMENT_MODE_RETIRED", - } - AssignmentMode_value = map[string]int32{ - "ASSIGNMENT_MODE_UNSPECIFIED": 0, - "ASSIGNMENT_MODE_TRIAL": 1, - "ASSIGNMENT_MODE_STABLE": 2, - "ASSIGNMENT_MODE_SUSPENDED": 3, - "ASSIGNMENT_MODE_RETIRED": 4, - } -) - -func (x AssignmentMode) Enum() *AssignmentMode { - p := new(AssignmentMode) - *p = x - return p -} - -func (x AssignmentMode) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (AssignmentMode) Descriptor() protoreflect.EnumDescriptor { - return file_xtcp_controller_v1_controller_proto_enumTypes[0].Descriptor() -} - -func (AssignmentMode) Type() protoreflect.EnumType { - return &file_xtcp_controller_v1_controller_proto_enumTypes[0] -} - -func (x AssignmentMode) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use AssignmentMode.Descriptor instead. -func (AssignmentMode) EnumDescriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{0} -} - -type ApplyStatus int32 - -const ( - ApplyStatus_APPLY_STATUS_UNSPECIFIED ApplyStatus = 0 - ApplyStatus_APPLY_STATUS_PENDING ApplyStatus = 1 - ApplyStatus_APPLY_STATUS_VERIFIED ApplyStatus = 2 - ApplyStatus_APPLY_STATUS_REMOVED ApplyStatus = 3 - ApplyStatus_APPLY_STATUS_EXPIRED ApplyStatus = 4 - ApplyStatus_APPLY_STATUS_FAILED ApplyStatus = 5 -) - -// Enum value maps for ApplyStatus. -var ( - ApplyStatus_name = map[int32]string{ - 0: "APPLY_STATUS_UNSPECIFIED", - 1: "APPLY_STATUS_PENDING", - 2: "APPLY_STATUS_VERIFIED", - 3: "APPLY_STATUS_REMOVED", - 4: "APPLY_STATUS_EXPIRED", - 5: "APPLY_STATUS_FAILED", - } - ApplyStatus_value = map[string]int32{ - "APPLY_STATUS_UNSPECIFIED": 0, - "APPLY_STATUS_PENDING": 1, - "APPLY_STATUS_VERIFIED": 2, - "APPLY_STATUS_REMOVED": 3, - "APPLY_STATUS_EXPIRED": 4, - "APPLY_STATUS_FAILED": 5, - } -) - -func (x ApplyStatus) Enum() *ApplyStatus { - p := new(ApplyStatus) - *p = x - return p -} - -func (x ApplyStatus) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (ApplyStatus) Descriptor() protoreflect.EnumDescriptor { - return file_xtcp_controller_v1_controller_proto_enumTypes[1].Descriptor() -} - -func (ApplyStatus) Type() protoreflect.EnumType { - return &file_xtcp_controller_v1_controller_proto_enumTypes[1] -} - -func (x ApplyStatus) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use ApplyStatus.Descriptor instead. -func (ApplyStatus) EnumDescriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{1} -} - -type ExperimentStatus int32 - -const ( - ExperimentStatus_EXPERIMENT_STATUS_UNSPECIFIED ExperimentStatus = 0 - ExperimentStatus_EXPERIMENT_STATUS_PROPOSED ExperimentStatus = 1 - ExperimentStatus_EXPERIMENT_STATUS_RUNNING ExperimentStatus = 2 - ExperimentStatus_EXPERIMENT_STATUS_PAUSED ExperimentStatus = 3 - ExperimentStatus_EXPERIMENT_STATUS_SUSPENDED ExperimentStatus = 4 - ExperimentStatus_EXPERIMENT_STATUS_COMPLETE ExperimentStatus = 5 - ExperimentStatus_EXPERIMENT_STATUS_RETIRED ExperimentStatus = 6 - ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK ExperimentStatus = 7 -) - -// Enum value maps for ExperimentStatus. -var ( - ExperimentStatus_name = map[int32]string{ - 0: "EXPERIMENT_STATUS_UNSPECIFIED", - 1: "EXPERIMENT_STATUS_PROPOSED", - 2: "EXPERIMENT_STATUS_RUNNING", - 3: "EXPERIMENT_STATUS_PAUSED", - 4: "EXPERIMENT_STATUS_SUSPENDED", - 5: "EXPERIMENT_STATUS_COMPLETE", - 6: "EXPERIMENT_STATUS_RETIRED", - 7: "EXPERIMENT_STATUS_ROLLED_BACK", - } - ExperimentStatus_value = map[string]int32{ - "EXPERIMENT_STATUS_UNSPECIFIED": 0, - "EXPERIMENT_STATUS_PROPOSED": 1, - "EXPERIMENT_STATUS_RUNNING": 2, - "EXPERIMENT_STATUS_PAUSED": 3, - "EXPERIMENT_STATUS_SUSPENDED": 4, - "EXPERIMENT_STATUS_COMPLETE": 5, - "EXPERIMENT_STATUS_RETIRED": 6, - "EXPERIMENT_STATUS_ROLLED_BACK": 7, - } -) - -func (x ExperimentStatus) Enum() *ExperimentStatus { - p := new(ExperimentStatus) - *p = x - return p -} - -func (x ExperimentStatus) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (ExperimentStatus) Descriptor() protoreflect.EnumDescriptor { - return file_xtcp_controller_v1_controller_proto_enumTypes[2].Descriptor() -} - -func (ExperimentStatus) Type() protoreflect.EnumType { - return &file_xtcp_controller_v1_controller_proto_enumTypes[2] -} - -func (x ExperimentStatus) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use ExperimentStatus.Descriptor instead. -func (ExperimentStatus) EnumDescriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{2} -} - -type ControlAction int32 - -const ( - ControlAction_CONTROL_ACTION_UNSPECIFIED ControlAction = 0 - ControlAction_CONTROL_ACTION_PAUSE ControlAction = 1 - ControlAction_CONTROL_ACTION_SUSPEND ControlAction = 2 - ControlAction_CONTROL_ACTION_RESUME ControlAction = 3 - ControlAction_CONTROL_ACTION_ROLLBACK ControlAction = 4 - ControlAction_CONTROL_ACTION_RETIRE ControlAction = 5 -) - -// Enum value maps for ControlAction. -var ( - ControlAction_name = map[int32]string{ - 0: "CONTROL_ACTION_UNSPECIFIED", - 1: "CONTROL_ACTION_PAUSE", - 2: "CONTROL_ACTION_SUSPEND", - 3: "CONTROL_ACTION_RESUME", - 4: "CONTROL_ACTION_ROLLBACK", - 5: "CONTROL_ACTION_RETIRE", - } - ControlAction_value = map[string]int32{ - "CONTROL_ACTION_UNSPECIFIED": 0, - "CONTROL_ACTION_PAUSE": 1, - "CONTROL_ACTION_SUSPEND": 2, - "CONTROL_ACTION_RESUME": 3, - "CONTROL_ACTION_ROLLBACK": 4, - "CONTROL_ACTION_RETIRE": 5, - } -) - -func (x ControlAction) Enum() *ControlAction { - p := new(ControlAction) - *p = x - return p -} - -func (x ControlAction) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (ControlAction) Descriptor() protoreflect.EnumDescriptor { - return file_xtcp_controller_v1_controller_proto_enumTypes[3].Descriptor() -} - -func (ControlAction) Type() protoreflect.EnumType { - return &file_xtcp_controller_v1_controller_proto_enumTypes[3] -} - -func (x ControlAction) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use ControlAction.Descriptor instead. -func (ControlAction) EnumDescriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{3} -} - -type TCPSettings struct { - state protoimpl.MessageState `protogen:"open.v1"` - CongestionControl *string `protobuf:"bytes,1,opt,name=congestion_control,json=congestionControl,proto3,oneof" json:"congestion_control,omitempty"` - CongestionLock bool `protobuf:"varint,2,opt,name=congestion_lock,json=congestionLock,proto3" json:"congestion_lock,omitempty"` - RtoMinMs *uint32 `protobuf:"varint,3,opt,name=rto_min_ms,json=rtoMinMs,proto3,oneof" json:"rto_min_ms,omitempty"` - InitialCongestionWindow *uint32 `protobuf:"varint,4,opt,name=initial_congestion_window,json=initialCongestionWindow,proto3,oneof" json:"initial_congestion_window,omitempty"` - InitialReceiveWindow *uint32 `protobuf:"varint,5,opt,name=initial_receive_window,json=initialReceiveWindow,proto3,oneof" json:"initial_receive_window,omitempty"` - QuickAck *bool `protobuf:"varint,6,opt,name=quick_ack,json=quickAck,proto3,oneof" json:"quick_ack,omitempty"` - Ecn *bool `protobuf:"varint,7,opt,name=ecn,proto3,oneof" json:"ecn,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *TCPSettings) Reset() { - *x = TCPSettings{} - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *TCPSettings) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*TCPSettings) ProtoMessage() {} - -func (x *TCPSettings) ProtoReflect() protoreflect.Message { - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[0] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use TCPSettings.ProtoReflect.Descriptor instead. -func (*TCPSettings) Descriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{0} -} - -func (x *TCPSettings) GetCongestionControl() string { - if x != nil && x.CongestionControl != nil { - return *x.CongestionControl - } - return "" -} - -func (x *TCPSettings) GetCongestionLock() bool { - if x != nil { - return x.CongestionLock - } - return false -} - -func (x *TCPSettings) GetRtoMinMs() uint32 { - if x != nil && x.RtoMinMs != nil { - return *x.RtoMinMs - } - return 0 -} - -func (x *TCPSettings) GetInitialCongestionWindow() uint32 { - if x != nil && x.InitialCongestionWindow != nil { - return *x.InitialCongestionWindow - } - return 0 -} - -func (x *TCPSettings) GetInitialReceiveWindow() uint32 { - if x != nil && x.InitialReceiveWindow != nil { - return *x.InitialReceiveWindow - } - return 0 -} - -func (x *TCPSettings) GetQuickAck() bool { - if x != nil && x.QuickAck != nil { - return *x.QuickAck - } - return false -} - -func (x *TCPSettings) GetEcn() bool { - if x != nil && x.Ecn != nil { - return *x.Ecn - } - return false -} - -type Treatment struct { - state protoimpl.MessageState `protogen:"open.v1"` - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - Revision uint64 `protobuf:"varint,2,opt,name=revision,proto3" json:"revision,omitempty"` - Destination string `protobuf:"bytes,3,opt,name=destination,proto3" json:"destination,omitempty"` - Settings *TCPSettings `protobuf:"bytes,4,opt,name=settings,proto3" json:"settings,omitempty"` - EcnFabricVerified bool `protobuf:"varint,5,opt,name=ecn_fabric_verified,json=ecnFabricVerified,proto3" json:"ecn_fabric_verified,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *Treatment) Reset() { - *x = Treatment{} - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *Treatment) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Treatment) ProtoMessage() {} - -func (x *Treatment) ProtoReflect() protoreflect.Message { - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[1] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Treatment.ProtoReflect.Descriptor instead. -func (*Treatment) Descriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{1} -} - -func (x *Treatment) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *Treatment) GetRevision() uint64 { - if x != nil { - return x.Revision - } - return 0 -} - -func (x *Treatment) GetDestination() string { - if x != nil { - return x.Destination - } - return "" -} - -func (x *Treatment) GetSettings() *TCPSettings { - if x != nil { - return x.Settings - } - return nil -} - -func (x *Treatment) GetEcnFabricVerified() bool { - if x != nil { - return x.EcnFabricVerified - } - return false -} - -type Assignment struct { - state protoimpl.MessageState `protogen:"open.v1"` - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - ExperimentId string `protobuf:"bytes,2,opt,name=experiment_id,json=experimentId,proto3" json:"experiment_id,omitempty"` - Treatment *Treatment `protobuf:"bytes,3,opt,name=treatment,proto3" json:"treatment,omitempty"` - Mode AssignmentMode `protobuf:"varint,4,opt,name=mode,proto3,enum=xtcp.controller.v1.AssignmentMode" json:"mode,omitempty"` - LeaseExpiresAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=lease_expires_at,json=leaseExpiresAt,proto3" json:"lease_expires_at,omitempty"` - // Previous approved stable treatment, restored on trial expiry/rollback. - Predecessor *Treatment `protobuf:"bytes,6,opt,name=predecessor,proto3" json:"predecessor,omitempty"` - // Increment only on an explicitly approved reactivation, not lease renewal. - Generation uint64 `protobuf:"varint,7,opt,name=generation,proto3" json:"generation,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *Assignment) Reset() { - *x = Assignment{} - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *Assignment) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Assignment) ProtoMessage() {} - -func (x *Assignment) ProtoReflect() protoreflect.Message { - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[2] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Assignment.ProtoReflect.Descriptor instead. -func (*Assignment) Descriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{2} -} - -func (x *Assignment) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *Assignment) GetExperimentId() string { - if x != nil { - return x.ExperimentId - } - return "" -} - -func (x *Assignment) GetTreatment() *Treatment { - if x != nil { - return x.Treatment - } - return nil -} - -func (x *Assignment) GetMode() AssignmentMode { - if x != nil { - return x.Mode - } - return AssignmentMode_ASSIGNMENT_MODE_UNSPECIFIED -} - -func (x *Assignment) GetLeaseExpiresAt() *timestamppb.Timestamp { - if x != nil { - return x.LeaseExpiresAt - } - return nil -} - -func (x *Assignment) GetPredecessor() *Treatment { - if x != nil { - return x.Predecessor - } - return nil -} - -func (x *Assignment) GetGeneration() uint64 { - if x != nil { - return x.Generation - } - return 0 -} - -type DesiredState struct { - state protoimpl.MessageState `protogen:"open.v1"` - AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` - Revision uint64 `protobuf:"varint,2,opt,name=revision,proto3" json:"revision,omitempty"` - Suspended bool `protobuf:"varint,3,opt,name=suspended,proto3" json:"suspended,omitempty"` - Assignments []*Assignment `protobuf:"bytes,4,rep,name=assignments,proto3" json:"assignments,omitempty"` - IssuedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=issued_at,json=issuedAt,proto3" json:"issued_at,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *DesiredState) Reset() { - *x = DesiredState{} - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *DesiredState) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*DesiredState) ProtoMessage() {} - -func (x *DesiredState) ProtoReflect() protoreflect.Message { - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[3] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use DesiredState.ProtoReflect.Descriptor instead. -func (*DesiredState) Descriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{3} -} - -func (x *DesiredState) GetAgentId() string { - if x != nil { - return x.AgentId - } - return "" -} - -func (x *DesiredState) GetRevision() uint64 { - if x != nil { - return x.Revision - } - return 0 -} - -func (x *DesiredState) GetSuspended() bool { - if x != nil { - return x.Suspended - } - return false -} - -func (x *DesiredState) GetAssignments() []*Assignment { - if x != nil { - return x.Assignments - } - return nil -} - -func (x *DesiredState) GetIssuedAt() *timestamppb.Timestamp { - if x != nil { - return x.IssuedAt - } - return nil -} - -type Capabilities struct { - state protoimpl.MessageState `protogen:"open.v1"` - BootId string `protobuf:"bytes,1,opt,name=boot_id,json=bootId,proto3" json:"boot_id,omitempty"` - KernelRelease string `protobuf:"bytes,2,opt,name=kernel_release,json=kernelRelease,proto3" json:"kernel_release,omitempty"` - CongestionAlgorithms []string `protobuf:"bytes,3,rep,name=congestion_algorithms,json=congestionAlgorithms,proto3" json:"congestion_algorithms,omitempty"` - SimpleHostRouting bool `protobuf:"varint,4,opt,name=simple_host_routing,json=simpleHostRouting,proto3" json:"simple_host_routing,omitempty"` - ActuatorEnabled bool `protobuf:"varint,5,opt,name=actuator_enabled,json=actuatorEnabled,proto3" json:"actuator_enabled,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *Capabilities) Reset() { - *x = Capabilities{} - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *Capabilities) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Capabilities) ProtoMessage() {} - -func (x *Capabilities) ProtoReflect() protoreflect.Message { - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[4] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Capabilities.ProtoReflect.Descriptor instead. -func (*Capabilities) Descriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{4} -} - -func (x *Capabilities) GetBootId() string { - if x != nil { - return x.BootId - } - return "" -} - -func (x *Capabilities) GetKernelRelease() string { - if x != nil { - return x.KernelRelease - } - return "" -} - -func (x *Capabilities) GetCongestionAlgorithms() []string { - if x != nil { - return x.CongestionAlgorithms - } - return nil -} - -func (x *Capabilities) GetSimpleHostRouting() bool { - if x != nil { - return x.SimpleHostRouting - } - return false -} - -func (x *Capabilities) GetActuatorEnabled() bool { - if x != nil { - return x.ActuatorEnabled - } - return false -} - -type ApplicationRecord struct { - state protoimpl.MessageState `protogen:"open.v1"` - Assignment *Assignment `protobuf:"bytes,1,opt,name=assignment,proto3" json:"assignment,omitempty"` - DesiredRevision uint64 `protobuf:"varint,2,opt,name=desired_revision,json=desiredRevision,proto3" json:"desired_revision,omitempty"` - Status ApplyStatus `protobuf:"varint,3,opt,name=status,proto3,enum=xtcp.controller.v1.ApplyStatus" json:"status,omitempty"` - ReceivedAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=received_at,json=receivedAt,proto3" json:"received_at,omitempty"` - AttemptedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=attempted_at,json=attemptedAt,proto3" json:"attempted_at,omitempty"` - AppliedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=applied_at,json=appliedAt,proto3" json:"applied_at,omitempty"` - VerifiedAt *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=verified_at,json=verifiedAt,proto3" json:"verified_at,omitempty"` - RemovedAt *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=removed_at,json=removedAt,proto3" json:"removed_at,omitempty"` - Error string `protobuf:"bytes,9,opt,name=error,proto3" json:"error,omitempty"` - // Locally generated canonical netlink route body, never accepted from RPC. - InstalledRoute []byte `protobuf:"bytes,10,opt,name=installed_route,json=installedRoute,proto3" json:"installed_route,omitempty"` - InstalledBootId string `protobuf:"bytes,11,opt,name=installed_boot_id,json=installedBootId,proto3" json:"installed_boot_id,omitempty"` - ExpiredLatched bool `protobuf:"varint,12,opt,name=expired_latched,json=expiredLatched,proto3" json:"expired_latched,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ApplicationRecord) Reset() { - *x = ApplicationRecord{} - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ApplicationRecord) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ApplicationRecord) ProtoMessage() {} - -func (x *ApplicationRecord) ProtoReflect() protoreflect.Message { - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[5] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ApplicationRecord.ProtoReflect.Descriptor instead. -func (*ApplicationRecord) Descriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{5} -} - -func (x *ApplicationRecord) GetAssignment() *Assignment { - if x != nil { - return x.Assignment - } - return nil -} - -func (x *ApplicationRecord) GetDesiredRevision() uint64 { - if x != nil { - return x.DesiredRevision - } - return 0 -} - -func (x *ApplicationRecord) GetStatus() ApplyStatus { - if x != nil { - return x.Status - } - return ApplyStatus_APPLY_STATUS_UNSPECIFIED -} - -func (x *ApplicationRecord) GetReceivedAt() *timestamppb.Timestamp { - if x != nil { - return x.ReceivedAt - } - return nil -} - -func (x *ApplicationRecord) GetAttemptedAt() *timestamppb.Timestamp { - if x != nil { - return x.AttemptedAt - } - return nil -} - -func (x *ApplicationRecord) GetAppliedAt() *timestamppb.Timestamp { - if x != nil { - return x.AppliedAt - } - return nil -} - -func (x *ApplicationRecord) GetVerifiedAt() *timestamppb.Timestamp { - if x != nil { - return x.VerifiedAt - } - return nil -} - -func (x *ApplicationRecord) GetRemovedAt() *timestamppb.Timestamp { - if x != nil { - return x.RemovedAt - } - return nil -} - -func (x *ApplicationRecord) GetError() string { - if x != nil { - return x.Error - } - return "" -} - -func (x *ApplicationRecord) GetInstalledRoute() []byte { - if x != nil { - return x.InstalledRoute - } - return nil -} - -func (x *ApplicationRecord) GetInstalledBootId() string { - if x != nil { - return x.InstalledBootId - } - return "" -} - -func (x *ApplicationRecord) GetExpiredLatched() bool { - if x != nil { - return x.ExpiredLatched - } - return false -} - -type LocalState struct { - state protoimpl.MessageState `protogen:"open.v1"` - Desired *DesiredState `protobuf:"bytes,1,opt,name=desired,proto3" json:"desired,omitempty"` - LocallySuspended bool `protobuf:"varint,2,opt,name=locally_suspended,json=locallySuspended,proto3" json:"locally_suspended,omitempty"` - SuspensionReason string `protobuf:"bytes,3,opt,name=suspension_reason,json=suspensionReason,proto3" json:"suspension_reason,omitempty"` - Records []*ApplicationRecord `protobuf:"bytes,4,rep,name=records,proto3" json:"records,omitempty"` - UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *LocalState) Reset() { - *x = LocalState{} - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *LocalState) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*LocalState) ProtoMessage() {} - -func (x *LocalState) ProtoReflect() protoreflect.Message { - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[6] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use LocalState.ProtoReflect.Descriptor instead. -func (*LocalState) Descriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{6} -} - -func (x *LocalState) GetDesired() *DesiredState { - if x != nil { - return x.Desired - } - return nil -} - -func (x *LocalState) GetLocallySuspended() bool { - if x != nil { - return x.LocallySuspended - } - return false -} - -func (x *LocalState) GetSuspensionReason() string { - if x != nil { - return x.SuspensionReason - } - return "" -} - -func (x *LocalState) GetRecords() []*ApplicationRecord { - if x != nil { - return x.Records - } - return nil -} - -func (x *LocalState) GetUpdatedAt() *timestamppb.Timestamp { - if x != nil { - return x.UpdatedAt - } - return nil -} - -type CheckInRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` - Location string `protobuf:"bytes,2,opt,name=location,proto3" json:"location,omitempty"` - Capabilities *Capabilities `protobuf:"bytes,3,opt,name=capabilities,proto3" json:"capabilities,omitempty"` - DesiredRevision uint64 `protobuf:"varint,4,opt,name=desired_revision,json=desiredRevision,proto3" json:"desired_revision,omitempty"` - Records []*ApplicationRecord `protobuf:"bytes,5,rep,name=records,proto3" json:"records,omitempty"` - LocallySuspended bool `protobuf:"varint,6,opt,name=locally_suspended,json=locallySuspended,proto3" json:"locally_suspended,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *CheckInRequest) Reset() { - *x = CheckInRequest{} - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *CheckInRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CheckInRequest) ProtoMessage() {} - -func (x *CheckInRequest) ProtoReflect() protoreflect.Message { - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[7] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CheckInRequest.ProtoReflect.Descriptor instead. -func (*CheckInRequest) Descriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{7} -} - -func (x *CheckInRequest) GetAgentId() string { - if x != nil { - return x.AgentId - } - return "" -} - -func (x *CheckInRequest) GetLocation() string { - if x != nil { - return x.Location - } - return "" -} - -func (x *CheckInRequest) GetCapabilities() *Capabilities { - if x != nil { - return x.Capabilities - } - return nil -} - -func (x *CheckInRequest) GetDesiredRevision() uint64 { - if x != nil { - return x.DesiredRevision - } - return 0 -} - -func (x *CheckInRequest) GetRecords() []*ApplicationRecord { - if x != nil { - return x.Records - } - return nil -} - -func (x *CheckInRequest) GetLocallySuspended() bool { - if x != nil { - return x.LocallySuspended - } - return false -} - -type CheckInResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Desired *DesiredState `protobuf:"bytes,1,opt,name=desired,proto3" json:"desired,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *CheckInResponse) Reset() { - *x = CheckInResponse{} - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *CheckInResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CheckInResponse) ProtoMessage() {} - -func (x *CheckInResponse) ProtoReflect() protoreflect.Message { - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[8] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CheckInResponse.ProtoReflect.Descriptor instead. -func (*CheckInResponse) Descriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{8} -} - -func (x *CheckInResponse) GetDesired() *DesiredState { - if x != nil { - return x.Desired - } - return nil -} - -type GetDesiredStateRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *GetDesiredStateRequest) Reset() { - *x = GetDesiredStateRequest{} - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *GetDesiredStateRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetDesiredStateRequest) ProtoMessage() {} - -func (x *GetDesiredStateRequest) ProtoReflect() protoreflect.Message { - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[9] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetDesiredStateRequest.ProtoReflect.Descriptor instead. -func (*GetDesiredStateRequest) Descriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{9} -} - -func (x *GetDesiredStateRequest) GetAgentId() string { - if x != nil { - return x.AgentId - } - return "" -} - -type GetDesiredStateResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Desired *DesiredState `protobuf:"bytes,1,opt,name=desired,proto3" json:"desired,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *GetDesiredStateResponse) Reset() { - *x = GetDesiredStateResponse{} - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *GetDesiredStateResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetDesiredStateResponse) ProtoMessage() {} - -func (x *GetDesiredStateResponse) ProtoReflect() protoreflect.Message { - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[10] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetDesiredStateResponse.ProtoReflect.Descriptor instead. -func (*GetDesiredStateResponse) Descriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{10} -} - -func (x *GetDesiredStateResponse) GetDesired() *DesiredState { - if x != nil { - return x.Desired - } - return nil -} - -type WatchDesiredStateRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` - AfterRevision uint64 `protobuf:"varint,2,opt,name=after_revision,json=afterRevision,proto3" json:"after_revision,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *WatchDesiredStateRequest) Reset() { - *x = WatchDesiredStateRequest{} - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *WatchDesiredStateRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*WatchDesiredStateRequest) ProtoMessage() {} - -func (x *WatchDesiredStateRequest) ProtoReflect() protoreflect.Message { - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[11] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use WatchDesiredStateRequest.ProtoReflect.Descriptor instead. -func (*WatchDesiredStateRequest) Descriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{11} -} - -func (x *WatchDesiredStateRequest) GetAgentId() string { - if x != nil { - return x.AgentId - } - return "" -} - -func (x *WatchDesiredStateRequest) GetAfterRevision() uint64 { - if x != nil { - return x.AfterRevision - } - return 0 -} - -type WatchDesiredStateResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Revision uint64 `protobuf:"varint,1,opt,name=revision,proto3" json:"revision,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *WatchDesiredStateResponse) Reset() { - *x = WatchDesiredStateResponse{} - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *WatchDesiredStateResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*WatchDesiredStateResponse) ProtoMessage() {} - -func (x *WatchDesiredStateResponse) ProtoReflect() protoreflect.Message { - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[12] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use WatchDesiredStateResponse.ProtoReflect.Descriptor instead. -func (*WatchDesiredStateResponse) Descriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{12} -} - -func (x *WatchDesiredStateResponse) GetRevision() uint64 { - if x != nil { - return x.Revision - } - return 0 -} - -type HostEvidence struct { - state protoimpl.MessageState `protogen:"open.v1"` - AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` - Sockets uint64 `protobuf:"varint,2,opt,name=sockets,proto3" json:"sockets,omitempty"` - BytesAcked uint64 `protobuf:"varint,3,opt,name=bytes_acked,json=bytesAcked,proto3" json:"bytes_acked,omitempty"` - GoodputBps float64 `protobuf:"fixed64,4,opt,name=goodput_bps,json=goodputBps,proto3" json:"goodput_bps,omitempty"` - RetransmissionRatio float64 `protobuf:"fixed64,5,opt,name=retransmission_ratio,json=retransmissionRatio,proto3" json:"retransmission_ratio,omitempty"` - RttP95Ms float64 `protobuf:"fixed64,6,opt,name=rtt_p95_ms,json=rttP95Ms,proto3" json:"rtt_p95_ms,omitempty"` - ExposedSockets uint64 `protobuf:"varint,7,opt,name=exposed_sockets,json=exposedSockets,proto3" json:"exposed_sockets,omitempty"` - CollectionHealthy bool `protobuf:"varint,8,opt,name=collection_healthy,json=collectionHealthy,proto3" json:"collection_healthy,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *HostEvidence) Reset() { - *x = HostEvidence{} - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[13] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *HostEvidence) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*HostEvidence) ProtoMessage() {} - -func (x *HostEvidence) ProtoReflect() protoreflect.Message { - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[13] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use HostEvidence.ProtoReflect.Descriptor instead. -func (*HostEvidence) Descriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{13} -} - -func (x *HostEvidence) GetAgentId() string { - if x != nil { - return x.AgentId - } - return "" -} - -func (x *HostEvidence) GetSockets() uint64 { - if x != nil { - return x.Sockets - } - return 0 -} - -func (x *HostEvidence) GetBytesAcked() uint64 { - if x != nil { - return x.BytesAcked - } - return 0 -} - -func (x *HostEvidence) GetGoodputBps() float64 { - if x != nil { - return x.GoodputBps - } - return 0 -} - -func (x *HostEvidence) GetRetransmissionRatio() float64 { - if x != nil { - return x.RetransmissionRatio - } - return 0 -} - -func (x *HostEvidence) GetRttP95Ms() float64 { - if x != nil { - return x.RttP95Ms - } - return 0 -} - -func (x *HostEvidence) GetExposedSockets() uint64 { - if x != nil { - return x.ExposedSockets - } - return 0 -} - -func (x *HostEvidence) GetCollectionHealthy() bool { - if x != nil { - return x.CollectionHealthy - } - return false -} - -type Evidence struct { - state protoimpl.MessageState `protogen:"open.v1"` - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - AnalyzedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=analyzed_at,json=analyzedAt,proto3" json:"analyzed_at,omitempty"` - WindowStart *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=window_start,json=windowStart,proto3" json:"window_start,omitempty"` - WindowEnd *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=window_end,json=windowEnd,proto3" json:"window_end,omitempty"` - QueryVersion string `protobuf:"bytes,5,opt,name=query_version,json=queryVersion,proto3" json:"query_version,omitempty"` - EnrichmentVersion string `protobuf:"bytes,6,opt,name=enrichment_version,json=enrichmentVersion,proto3" json:"enrichment_version,omitempty"` - Hosts []*HostEvidence `protobuf:"bytes,7,rep,name=hosts,proto3" json:"hosts,omitempty"` - // Content-addressed reference to the complete immutable analysis artifact. - ArtifactSha256 string `protobuf:"bytes,8,opt,name=artifact_sha256,json=artifactSha256,proto3" json:"artifact_sha256,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *Evidence) Reset() { - *x = Evidence{} - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[14] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *Evidence) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Evidence) ProtoMessage() {} - -func (x *Evidence) ProtoReflect() protoreflect.Message { - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[14] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Evidence.ProtoReflect.Descriptor instead. -func (*Evidence) Descriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{14} -} - -func (x *Evidence) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *Evidence) GetAnalyzedAt() *timestamppb.Timestamp { - if x != nil { - return x.AnalyzedAt - } - return nil -} - -func (x *Evidence) GetWindowStart() *timestamppb.Timestamp { - if x != nil { - return x.WindowStart - } - return nil -} - -func (x *Evidence) GetWindowEnd() *timestamppb.Timestamp { - if x != nil { - return x.WindowEnd - } - return nil -} - -func (x *Evidence) GetQueryVersion() string { - if x != nil { - return x.QueryVersion - } - return "" -} - -func (x *Evidence) GetEnrichmentVersion() string { - if x != nil { - return x.EnrichmentVersion - } - return "" -} - -func (x *Evidence) GetHosts() []*HostEvidence { - if x != nil { - return x.Hosts - } - return nil -} - -func (x *Evidence) GetArtifactSha256() string { - if x != nil { - return x.ArtifactSha256 - } - return "" -} - -type ExperimentPolicy struct { - state protoimpl.MessageState `protogen:"open.v1"` - MinimumObservation *durationpb.Duration `protobuf:"bytes,1,opt,name=minimum_observation,json=minimumObservation,proto3" json:"minimum_observation,omitempty"` - MaximumPhase *durationpb.Duration `protobuf:"bytes,2,opt,name=maximum_phase,json=maximumPhase,proto3" json:"maximum_phase,omitempty"` - MinimumSocketsPerHost uint64 `protobuf:"varint,3,opt,name=minimum_sockets_per_host,json=minimumSocketsPerHost,proto3" json:"minimum_sockets_per_host,omitempty"` - MinimumBytesPerHost uint64 `protobuf:"varint,4,opt,name=minimum_bytes_per_host,json=minimumBytesPerHost,proto3" json:"minimum_bytes_per_host,omitempty"` - MinimumTreatedHosts uint32 `protobuf:"varint,5,opt,name=minimum_treated_hosts,json=minimumTreatedHosts,proto3" json:"minimum_treated_hosts,omitempty"` - MinimumImprovement float64 `protobuf:"fixed64,6,opt,name=minimum_improvement,json=minimumImprovement,proto3" json:"minimum_improvement,omitempty"` - MaximumRetransmissionIncrease float64 `protobuf:"fixed64,7,opt,name=maximum_retransmission_increase,json=maximumRetransmissionIncrease,proto3" json:"maximum_retransmission_increase,omitempty"` - MaximumRttIncrease float64 `protobuf:"fixed64,8,opt,name=maximum_rtt_increase,json=maximumRttIncrease,proto3" json:"maximum_rtt_increase,omitempty"` - MaximumEvidenceAge *durationpb.Duration `protobuf:"bytes,9,opt,name=maximum_evidence_age,json=maximumEvidenceAge,proto3" json:"maximum_evidence_age,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ExperimentPolicy) Reset() { - *x = ExperimentPolicy{} - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[15] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ExperimentPolicy) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ExperimentPolicy) ProtoMessage() {} - -func (x *ExperimentPolicy) ProtoReflect() protoreflect.Message { - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[15] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ExperimentPolicy.ProtoReflect.Descriptor instead. -func (*ExperimentPolicy) Descriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{15} -} - -func (x *ExperimentPolicy) GetMinimumObservation() *durationpb.Duration { - if x != nil { - return x.MinimumObservation - } - return nil -} - -func (x *ExperimentPolicy) GetMaximumPhase() *durationpb.Duration { - if x != nil { - return x.MaximumPhase - } - return nil -} - -func (x *ExperimentPolicy) GetMinimumSocketsPerHost() uint64 { - if x != nil { - return x.MinimumSocketsPerHost - } - return 0 -} - -func (x *ExperimentPolicy) GetMinimumBytesPerHost() uint64 { - if x != nil { - return x.MinimumBytesPerHost - } - return 0 -} - -func (x *ExperimentPolicy) GetMinimumTreatedHosts() uint32 { - if x != nil { - return x.MinimumTreatedHosts - } - return 0 -} - -func (x *ExperimentPolicy) GetMinimumImprovement() float64 { - if x != nil { - return x.MinimumImprovement - } - return 0 -} - -func (x *ExperimentPolicy) GetMaximumRetransmissionIncrease() float64 { - if x != nil { - return x.MaximumRetransmissionIncrease - } - return 0 -} - -func (x *ExperimentPolicy) GetMaximumRttIncrease() float64 { - if x != nil { - return x.MaximumRttIncrease - } - return 0 -} - -func (x *ExperimentPolicy) GetMaximumEvidenceAge() *durationpb.Duration { - if x != nil { - return x.MaximumEvidenceAge - } - return nil -} - -type Phase struct { - state protoimpl.MessageState `protogen:"open.v1"` - TargetHosts uint32 `protobuf:"varint,1,opt,name=target_hosts,json=targetHosts,proto3" json:"target_hosts,omitempty"` - IssuedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=issued_at,json=issuedAt,proto3" json:"issued_at,omitempty"` - ObservationStartedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=observation_started_at,json=observationStartedAt,proto3" json:"observation_started_at,omitempty"` - EvaluatedAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=evaluated_at,json=evaluatedAt,proto3" json:"evaluated_at,omitempty"` - Evidence *Evidence `protobuf:"bytes,5,opt,name=evidence,proto3" json:"evidence,omitempty"` - Outcome string `protobuf:"bytes,6,opt,name=outcome,proto3" json:"outcome,omitempty"` - Reviewer string `protobuf:"bytes,7,opt,name=reviewer,proto3" json:"reviewer,omitempty"` - Reason string `protobuf:"bytes,8,opt,name=reason,proto3" json:"reason,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *Phase) Reset() { - *x = Phase{} - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[16] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *Phase) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Phase) ProtoMessage() {} - -func (x *Phase) ProtoReflect() protoreflect.Message { - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[16] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Phase.ProtoReflect.Descriptor instead. -func (*Phase) Descriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{16} -} - -func (x *Phase) GetTargetHosts() uint32 { - if x != nil { - return x.TargetHosts - } - return 0 -} - -func (x *Phase) GetIssuedAt() *timestamppb.Timestamp { - if x != nil { - return x.IssuedAt - } - return nil -} - -func (x *Phase) GetObservationStartedAt() *timestamppb.Timestamp { - if x != nil { - return x.ObservationStartedAt - } - return nil -} - -func (x *Phase) GetEvaluatedAt() *timestamppb.Timestamp { - if x != nil { - return x.EvaluatedAt - } - return nil -} - -func (x *Phase) GetEvidence() *Evidence { - if x != nil { - return x.Evidence - } - return nil -} - -func (x *Phase) GetOutcome() string { - if x != nil { - return x.Outcome - } - return "" -} - -func (x *Phase) GetReviewer() string { - if x != nil { - return x.Reviewer - } - return "" -} - -func (x *Phase) GetReason() string { - if x != nil { - return x.Reason - } - return "" -} - -type Experiment struct { - state protoimpl.MessageState `protogen:"open.v1"` - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - Location string `protobuf:"bytes,2,opt,name=location,proto3" json:"location,omitempty"` - Hypothesis string `protobuf:"bytes,3,opt,name=hypothesis,proto3" json:"hypothesis,omitempty"` - Treatment *Treatment `protobuf:"bytes,4,opt,name=treatment,proto3" json:"treatment,omitempty"` - Policy *ExperimentPolicy `protobuf:"bytes,5,opt,name=policy,proto3" json:"policy,omitempty"` - Baseline *Evidence `protobuf:"bytes,6,opt,name=baseline,proto3" json:"baseline,omitempty"` - CohortOrder []string `protobuf:"bytes,7,rep,name=cohort_order,json=cohortOrder,proto3" json:"cohort_order,omitempty"` - Phases []*Phase `protobuf:"bytes,8,rep,name=phases,proto3" json:"phases,omitempty"` - Status ExperimentStatus `protobuf:"varint,9,opt,name=status,proto3,enum=xtcp.controller.v1.ExperimentStatus" json:"status,omitempty"` - Revision uint64 `protobuf:"varint,10,opt,name=revision,proto3" json:"revision,omitempty"` - ProposedAt *timestamppb.Timestamp `protobuf:"bytes,11,opt,name=proposed_at,json=proposedAt,proto3" json:"proposed_at,omitempty"` - LatestEvidence *Evidence `protobuf:"bytes,12,opt,name=latest_evidence,json=latestEvidence,proto3" json:"latest_evidence,omitempty"` - LatestOutcome string `protobuf:"bytes,13,opt,name=latest_outcome,json=latestOutcome,proto3" json:"latest_outcome,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *Experiment) Reset() { - *x = Experiment{} - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[17] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *Experiment) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Experiment) ProtoMessage() {} - -func (x *Experiment) ProtoReflect() protoreflect.Message { - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[17] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Experiment.ProtoReflect.Descriptor instead. -func (*Experiment) Descriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{17} -} - -func (x *Experiment) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *Experiment) GetLocation() string { - if x != nil { - return x.Location - } - return "" -} - -func (x *Experiment) GetHypothesis() string { - if x != nil { - return x.Hypothesis - } - return "" -} - -func (x *Experiment) GetTreatment() *Treatment { - if x != nil { - return x.Treatment - } - return nil -} - -func (x *Experiment) GetPolicy() *ExperimentPolicy { - if x != nil { - return x.Policy - } - return nil -} - -func (x *Experiment) GetBaseline() *Evidence { - if x != nil { - return x.Baseline - } - return nil -} - -func (x *Experiment) GetCohortOrder() []string { - if x != nil { - return x.CohortOrder - } - return nil -} - -func (x *Experiment) GetPhases() []*Phase { - if x != nil { - return x.Phases - } - return nil -} - -func (x *Experiment) GetStatus() ExperimentStatus { - if x != nil { - return x.Status - } - return ExperimentStatus_EXPERIMENT_STATUS_UNSPECIFIED -} - -func (x *Experiment) GetRevision() uint64 { - if x != nil { - return x.Revision - } - return 0 -} - -func (x *Experiment) GetProposedAt() *timestamppb.Timestamp { - if x != nil { - return x.ProposedAt - } - return nil -} - -func (x *Experiment) GetLatestEvidence() *Evidence { - if x != nil { - return x.LatestEvidence - } - return nil -} - -func (x *Experiment) GetLatestOutcome() string { - if x != nil { - return x.LatestOutcome - } - return "" -} - -type ProposeRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Experiment *Experiment `protobuf:"bytes,1,opt,name=experiment,proto3" json:"experiment,omitempty"` - RequestId string `protobuf:"bytes,2,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ProposeRequest) Reset() { - *x = ProposeRequest{} - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[18] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ProposeRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ProposeRequest) ProtoMessage() {} - -func (x *ProposeRequest) ProtoReflect() protoreflect.Message { - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[18] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ProposeRequest.ProtoReflect.Descriptor instead. -func (*ProposeRequest) Descriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{18} -} - -func (x *ProposeRequest) GetExperiment() *Experiment { - if x != nil { - return x.Experiment - } - return nil -} - -func (x *ProposeRequest) GetRequestId() string { - if x != nil { - return x.RequestId - } - return "" -} - -type ProposeResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Experiment *Experiment `protobuf:"bytes,1,opt,name=experiment,proto3" json:"experiment,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ProposeResponse) Reset() { - *x = ProposeResponse{} - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[19] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ProposeResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ProposeResponse) ProtoMessage() {} - -func (x *ProposeResponse) ProtoReflect() protoreflect.Message { - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[19] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ProposeResponse.ProtoReflect.Descriptor instead. -func (*ProposeResponse) Descriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{19} -} - -func (x *ProposeResponse) GetExperiment() *Experiment { - if x != nil { - return x.Experiment - } - return nil -} - -type AnalyzeRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - ExperimentId string `protobuf:"bytes,1,opt,name=experiment_id,json=experimentId,proto3" json:"experiment_id,omitempty"` - ExpectedRevision uint64 `protobuf:"varint,2,opt,name=expected_revision,json=expectedRevision,proto3" json:"expected_revision,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *AnalyzeRequest) Reset() { - *x = AnalyzeRequest{} - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[20] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *AnalyzeRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AnalyzeRequest) ProtoMessage() {} - -func (x *AnalyzeRequest) ProtoReflect() protoreflect.Message { - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[20] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AnalyzeRequest.ProtoReflect.Descriptor instead. -func (*AnalyzeRequest) Descriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{20} -} - -func (x *AnalyzeRequest) GetExperimentId() string { - if x != nil { - return x.ExperimentId - } - return "" -} - -func (x *AnalyzeRequest) GetExpectedRevision() uint64 { - if x != nil { - return x.ExpectedRevision - } - return 0 -} - -type AnalyzeResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Evidence *Evidence `protobuf:"bytes,1,opt,name=evidence,proto3" json:"evidence,omitempty"` - Outcome string `protobuf:"bytes,2,opt,name=outcome,proto3" json:"outcome,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *AnalyzeResponse) Reset() { - *x = AnalyzeResponse{} - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[21] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *AnalyzeResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AnalyzeResponse) ProtoMessage() {} - -func (x *AnalyzeResponse) ProtoReflect() protoreflect.Message { - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[21] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AnalyzeResponse.ProtoReflect.Descriptor instead. -func (*AnalyzeResponse) Descriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{21} -} - -func (x *AnalyzeResponse) GetEvidence() *Evidence { - if x != nil { - return x.Evidence - } - return nil -} - -func (x *AnalyzeResponse) GetOutcome() string { - if x != nil { - return x.Outcome - } - return "" -} - -type ReviewRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - ExperimentId string `protobuf:"bytes,1,opt,name=experiment_id,json=experimentId,proto3" json:"experiment_id,omitempty"` - ExpectedRevision uint64 `protobuf:"varint,2,opt,name=expected_revision,json=expectedRevision,proto3" json:"expected_revision,omitempty"` - Approve bool `protobuf:"varint,3,opt,name=approve,proto3" json:"approve,omitempty"` - Reason string `protobuf:"bytes,4,opt,name=reason,proto3" json:"reason,omitempty"` - RequestId string `protobuf:"bytes,5,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` - // Initial review uses baseline; subsequent reviews require fresh evidence. - Evidence *Evidence `protobuf:"bytes,6,opt,name=evidence,proto3" json:"evidence,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ReviewRequest) Reset() { - *x = ReviewRequest{} - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[22] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ReviewRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ReviewRequest) ProtoMessage() {} - -func (x *ReviewRequest) ProtoReflect() protoreflect.Message { - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[22] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ReviewRequest.ProtoReflect.Descriptor instead. -func (*ReviewRequest) Descriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{22} -} - -func (x *ReviewRequest) GetExperimentId() string { - if x != nil { - return x.ExperimentId - } - return "" -} - -func (x *ReviewRequest) GetExpectedRevision() uint64 { - if x != nil { - return x.ExpectedRevision - } - return 0 -} - -func (x *ReviewRequest) GetApprove() bool { - if x != nil { - return x.Approve - } - return false -} - -func (x *ReviewRequest) GetReason() string { - if x != nil { - return x.Reason - } - return "" -} - -func (x *ReviewRequest) GetRequestId() string { - if x != nil { - return x.RequestId - } - return "" -} - -func (x *ReviewRequest) GetEvidence() *Evidence { - if x != nil { - return x.Evidence - } - return nil -} - -type ReviewResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Experiment *Experiment `protobuf:"bytes,1,opt,name=experiment,proto3" json:"experiment,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ReviewResponse) Reset() { - *x = ReviewResponse{} - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[23] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ReviewResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ReviewResponse) ProtoMessage() {} - -func (x *ReviewResponse) ProtoReflect() protoreflect.Message { - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[23] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ReviewResponse.ProtoReflect.Descriptor instead. -func (*ReviewResponse) Descriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{23} -} - -func (x *ReviewResponse) GetExperiment() *Experiment { - if x != nil { - return x.Experiment - } - return nil -} - -type ControlRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Exactly one selector, or fleet=true, is required. - ExperimentId string `protobuf:"bytes,1,opt,name=experiment_id,json=experimentId,proto3" json:"experiment_id,omitempty"` - AgentId string `protobuf:"bytes,2,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` - Location string `protobuf:"bytes,3,opt,name=location,proto3" json:"location,omitempty"` - Fleet bool `protobuf:"varint,4,opt,name=fleet,proto3" json:"fleet,omitempty"` - Action ControlAction `protobuf:"varint,5,opt,name=action,proto3,enum=xtcp.controller.v1.ControlAction" json:"action,omitempty"` - Reason string `protobuf:"bytes,6,opt,name=reason,proto3" json:"reason,omitempty"` - RequestId string `protobuf:"bytes,7,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ControlRequest) Reset() { - *x = ControlRequest{} - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[24] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ControlRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ControlRequest) ProtoMessage() {} - -func (x *ControlRequest) ProtoReflect() protoreflect.Message { - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[24] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ControlRequest.ProtoReflect.Descriptor instead. -func (*ControlRequest) Descriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{24} -} - -func (x *ControlRequest) GetExperimentId() string { - if x != nil { - return x.ExperimentId - } - return "" -} - -func (x *ControlRequest) GetAgentId() string { - if x != nil { - return x.AgentId - } - return "" -} - -func (x *ControlRequest) GetLocation() string { - if x != nil { - return x.Location - } - return "" -} - -func (x *ControlRequest) GetFleet() bool { - if x != nil { - return x.Fleet - } - return false -} - -func (x *ControlRequest) GetAction() ControlAction { - if x != nil { - return x.Action - } - return ControlAction_CONTROL_ACTION_UNSPECIFIED -} - -func (x *ControlRequest) GetReason() string { - if x != nil { - return x.Reason - } - return "" -} - -func (x *ControlRequest) GetRequestId() string { - if x != nil { - return x.RequestId - } - return "" -} - -type ControlResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - AffectedAgents uint32 `protobuf:"varint,1,opt,name=affected_agents,json=affectedAgents,proto3" json:"affected_agents,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ControlResponse) Reset() { - *x = ControlResponse{} - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[25] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ControlResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ControlResponse) ProtoMessage() {} - -func (x *ControlResponse) ProtoReflect() protoreflect.Message { - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[25] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ControlResponse.ProtoReflect.Descriptor instead. -func (*ControlResponse) Descriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{25} -} - -func (x *ControlResponse) GetAffectedAgents() uint32 { - if x != nil { - return x.AffectedAgents - } - return 0 -} - -type InspectRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - ExperimentId string `protobuf:"bytes,1,opt,name=experiment_id,json=experimentId,proto3" json:"experiment_id,omitempty"` - AgentId string `protobuf:"bytes,2,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *InspectRequest) Reset() { - *x = InspectRequest{} - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[26] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *InspectRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*InspectRequest) ProtoMessage() {} - -func (x *InspectRequest) ProtoReflect() protoreflect.Message { - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[26] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use InspectRequest.ProtoReflect.Descriptor instead. -func (*InspectRequest) Descriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{26} -} - -func (x *InspectRequest) GetExperimentId() string { - if x != nil { - return x.ExperimentId - } - return "" -} - -func (x *InspectRequest) GetAgentId() string { - if x != nil { - return x.AgentId - } - return "" -} - -type InspectResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - Experiments []*Experiment `protobuf:"bytes,1,rep,name=experiments,proto3" json:"experiments,omitempty"` - Desired []*DesiredState `protobuf:"bytes,2,rep,name=desired,proto3" json:"desired,omitempty"` - Observed []*CheckInRequest `protobuf:"bytes,3,rep,name=observed,proto3" json:"observed,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *InspectResponse) Reset() { - *x = InspectResponse{} - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[27] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *InspectResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*InspectResponse) ProtoMessage() {} - -func (x *InspectResponse) ProtoReflect() protoreflect.Message { - mi := &file_xtcp_controller_v1_controller_proto_msgTypes[27] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use InspectResponse.ProtoReflect.Descriptor instead. -func (*InspectResponse) Descriptor() ([]byte, []int) { - return file_xtcp_controller_v1_controller_proto_rawDescGZIP(), []int{27} -} - -func (x *InspectResponse) GetExperiments() []*Experiment { - if x != nil { - return x.Experiments - } - return nil -} - -func (x *InspectResponse) GetDesired() []*DesiredState { - if x != nil { - return x.Desired - } - return nil -} - -func (x *InspectResponse) GetObserved() []*CheckInRequest { - if x != nil { - return x.Observed - } - return nil -} - -var File_xtcp_controller_v1_controller_proto protoreflect.FileDescriptor - -const file_xtcp_controller_v1_controller_proto_rawDesc = "" + - "\n" + - "#xtcp/controller/v1/controller.proto\x12\x12xtcp.controller.v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1egoogle/protobuf/duration.proto\"\xb7\x03\n" + - "\vTCPSettings\x122\n" + - "\x12congestion_control\x18\x01 \x01(\tH\x00R\x11congestionControl\x88\x01\x01\x12'\n" + - "\x0fcongestion_lock\x18\x02 \x01(\bR\x0econgestionLock\x12!\n" + - "\n" + - "rto_min_ms\x18\x03 \x01(\rH\x01R\brtoMinMs\x88\x01\x01\x12?\n" + - "\x19initial_congestion_window\x18\x04 \x01(\rH\x02R\x17initialCongestionWindow\x88\x01\x01\x129\n" + - "\x16initial_receive_window\x18\x05 \x01(\rH\x03R\x14initialReceiveWindow\x88\x01\x01\x12 \n" + - "\tquick_ack\x18\x06 \x01(\bH\x04R\bquickAck\x88\x01\x01\x12\x15\n" + - "\x03ecn\x18\a \x01(\bH\x05R\x03ecn\x88\x01\x01B\x15\n" + - "\x13_congestion_controlB\r\n" + - "\v_rto_min_msB\x1c\n" + - "\x1a_initial_congestion_windowB\x19\n" + - "\x17_initial_receive_windowB\f\n" + - "\n" + - "_quick_ackB\x06\n" + - "\x04_ecn\"\xc6\x01\n" + - "\tTreatment\x12\x0e\n" + - "\x02id\x18\x01 \x01(\tR\x02id\x12\x1a\n" + - "\brevision\x18\x02 \x01(\x04R\brevision\x12 \n" + - "\vdestination\x18\x03 \x01(\tR\vdestination\x12;\n" + - "\bsettings\x18\x04 \x01(\v2\x1f.xtcp.controller.v1.TCPSettingsR\bsettings\x12.\n" + - "\x13ecn_fabric_verified\x18\x05 \x01(\bR\x11ecnFabricVerified\"\xdd\x02\n" + - "\n" + - "Assignment\x12\x0e\n" + - "\x02id\x18\x01 \x01(\tR\x02id\x12#\n" + - "\rexperiment_id\x18\x02 \x01(\tR\fexperimentId\x12;\n" + - "\ttreatment\x18\x03 \x01(\v2\x1d.xtcp.controller.v1.TreatmentR\ttreatment\x126\n" + - "\x04mode\x18\x04 \x01(\x0e2\".xtcp.controller.v1.AssignmentModeR\x04mode\x12D\n" + - "\x10lease_expires_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\x0eleaseExpiresAt\x12?\n" + - "\vpredecessor\x18\x06 \x01(\v2\x1d.xtcp.controller.v1.TreatmentR\vpredecessor\x12\x1e\n" + - "\n" + - "generation\x18\a \x01(\x04R\n" + - "generation\"\xde\x01\n" + - "\fDesiredState\x12\x19\n" + - "\bagent_id\x18\x01 \x01(\tR\aagentId\x12\x1a\n" + - "\brevision\x18\x02 \x01(\x04R\brevision\x12\x1c\n" + - "\tsuspended\x18\x03 \x01(\bR\tsuspended\x12@\n" + - "\vassignments\x18\x04 \x03(\v2\x1e.xtcp.controller.v1.AssignmentR\vassignments\x127\n" + - "\tissued_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\bissuedAt\"\xde\x01\n" + - "\fCapabilities\x12\x17\n" + - "\aboot_id\x18\x01 \x01(\tR\x06bootId\x12%\n" + - "\x0ekernel_release\x18\x02 \x01(\tR\rkernelRelease\x123\n" + - "\x15congestion_algorithms\x18\x03 \x03(\tR\x14congestionAlgorithms\x12.\n" + - "\x13simple_host_routing\x18\x04 \x01(\bR\x11simpleHostRouting\x12)\n" + - "\x10actuator_enabled\x18\x05 \x01(\bR\x0factuatorEnabled\"\xfa\x04\n" + - "\x11ApplicationRecord\x12>\n" + - "\n" + - "assignment\x18\x01 \x01(\v2\x1e.xtcp.controller.v1.AssignmentR\n" + - "assignment\x12)\n" + - "\x10desired_revision\x18\x02 \x01(\x04R\x0fdesiredRevision\x127\n" + - "\x06status\x18\x03 \x01(\x0e2\x1f.xtcp.controller.v1.ApplyStatusR\x06status\x12;\n" + - "\vreceived_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\n" + - "receivedAt\x12=\n" + - "\fattempted_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\vattemptedAt\x129\n" + - "\n" + - "applied_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\tappliedAt\x12;\n" + - "\vverified_at\x18\a \x01(\v2\x1a.google.protobuf.TimestampR\n" + - "verifiedAt\x129\n" + - "\n" + - "removed_at\x18\b \x01(\v2\x1a.google.protobuf.TimestampR\tremovedAt\x12\x14\n" + - "\x05error\x18\t \x01(\tR\x05error\x12'\n" + - "\x0finstalled_route\x18\n" + - " \x01(\fR\x0einstalledRoute\x12*\n" + - "\x11installed_boot_id\x18\v \x01(\tR\x0finstalledBootId\x12'\n" + - "\x0fexpired_latched\x18\f \x01(\bR\x0eexpiredLatched\"\x9e\x02\n" + - "\n" + - "LocalState\x12:\n" + - "\adesired\x18\x01 \x01(\v2 .xtcp.controller.v1.DesiredStateR\adesired\x12+\n" + - "\x11locally_suspended\x18\x02 \x01(\bR\x10locallySuspended\x12+\n" + - "\x11suspension_reason\x18\x03 \x01(\tR\x10suspensionReason\x12?\n" + - "\arecords\x18\x04 \x03(\v2%.xtcp.controller.v1.ApplicationRecordR\arecords\x129\n" + - "\n" + - "updated_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\"\xa6\x02\n" + - "\x0eCheckInRequest\x12\x19\n" + - "\bagent_id\x18\x01 \x01(\tR\aagentId\x12\x1a\n" + - "\blocation\x18\x02 \x01(\tR\blocation\x12D\n" + - "\fcapabilities\x18\x03 \x01(\v2 .xtcp.controller.v1.CapabilitiesR\fcapabilities\x12)\n" + - "\x10desired_revision\x18\x04 \x01(\x04R\x0fdesiredRevision\x12?\n" + - "\arecords\x18\x05 \x03(\v2%.xtcp.controller.v1.ApplicationRecordR\arecords\x12+\n" + - "\x11locally_suspended\x18\x06 \x01(\bR\x10locallySuspended\"M\n" + - "\x0fCheckInResponse\x12:\n" + - "\adesired\x18\x01 \x01(\v2 .xtcp.controller.v1.DesiredStateR\adesired\"3\n" + - "\x16GetDesiredStateRequest\x12\x19\n" + - "\bagent_id\x18\x01 \x01(\tR\aagentId\"U\n" + - "\x17GetDesiredStateResponse\x12:\n" + - "\adesired\x18\x01 \x01(\v2 .xtcp.controller.v1.DesiredStateR\adesired\"\\\n" + - "\x18WatchDesiredStateRequest\x12\x19\n" + - "\bagent_id\x18\x01 \x01(\tR\aagentId\x12%\n" + - "\x0eafter_revision\x18\x02 \x01(\x04R\rafterRevision\"7\n" + - "\x19WatchDesiredStateResponse\x12\x1a\n" + - "\brevision\x18\x01 \x01(\x04R\brevision\"\xae\x02\n" + - "\fHostEvidence\x12\x19\n" + - "\bagent_id\x18\x01 \x01(\tR\aagentId\x12\x18\n" + - "\asockets\x18\x02 \x01(\x04R\asockets\x12\x1f\n" + - "\vbytes_acked\x18\x03 \x01(\x04R\n" + - "bytesAcked\x12\x1f\n" + - "\vgoodput_bps\x18\x04 \x01(\x01R\n" + - "goodputBps\x121\n" + - "\x14retransmission_ratio\x18\x05 \x01(\x01R\x13retransmissionRatio\x12\x1c\n" + - "\n" + - "rtt_p95_ms\x18\x06 \x01(\x01R\brttP95Ms\x12'\n" + - "\x0fexposed_sockets\x18\a \x01(\x04R\x0eexposedSockets\x12-\n" + - "\x12collection_healthy\x18\b \x01(\bR\x11collectionHealthy\"\x86\x03\n" + - "\bEvidence\x12\x0e\n" + - "\x02id\x18\x01 \x01(\tR\x02id\x12;\n" + - "\vanalyzed_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\n" + - "analyzedAt\x12=\n" + - "\fwindow_start\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\vwindowStart\x129\n" + - "\n" + - "window_end\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\twindowEnd\x12#\n" + - "\rquery_version\x18\x05 \x01(\tR\fqueryVersion\x12-\n" + - "\x12enrichment_version\x18\x06 \x01(\tR\x11enrichmentVersion\x126\n" + - "\x05hosts\x18\a \x03(\v2 .xtcp.controller.v1.HostEvidenceR\x05hosts\x12'\n" + - "\x0fartifact_sha256\x18\b \x01(\tR\x0eartifactSha256\"\xb8\x04\n" + - "\x10ExperimentPolicy\x12J\n" + - "\x13minimum_observation\x18\x01 \x01(\v2\x19.google.protobuf.DurationR\x12minimumObservation\x12>\n" + - "\rmaximum_phase\x18\x02 \x01(\v2\x19.google.protobuf.DurationR\fmaximumPhase\x127\n" + - "\x18minimum_sockets_per_host\x18\x03 \x01(\x04R\x15minimumSocketsPerHost\x123\n" + - "\x16minimum_bytes_per_host\x18\x04 \x01(\x04R\x13minimumBytesPerHost\x122\n" + - "\x15minimum_treated_hosts\x18\x05 \x01(\rR\x13minimumTreatedHosts\x12/\n" + - "\x13minimum_improvement\x18\x06 \x01(\x01R\x12minimumImprovement\x12F\n" + - "\x1fmaximum_retransmission_increase\x18\a \x01(\x01R\x1dmaximumRetransmissionIncrease\x120\n" + - "\x14maximum_rtt_increase\x18\b \x01(\x01R\x12maximumRttIncrease\x12K\n" + - "\x14maximum_evidence_age\x18\t \x01(\v2\x19.google.protobuf.DurationR\x12maximumEvidenceAge\"\xfc\x02\n" + - "\x05Phase\x12!\n" + - "\ftarget_hosts\x18\x01 \x01(\rR\vtargetHosts\x127\n" + - "\tissued_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\bissuedAt\x12P\n" + - "\x16observation_started_at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\x14observationStartedAt\x12=\n" + - "\fevaluated_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\vevaluatedAt\x128\n" + - "\bevidence\x18\x05 \x01(\v2\x1c.xtcp.controller.v1.EvidenceR\bevidence\x12\x18\n" + - "\aoutcome\x18\x06 \x01(\tR\aoutcome\x12\x1a\n" + - "\breviewer\x18\a \x01(\tR\breviewer\x12\x16\n" + - "\x06reason\x18\b \x01(\tR\x06reason\"\xe8\x04\n" + - "\n" + - "Experiment\x12\x0e\n" + - "\x02id\x18\x01 \x01(\tR\x02id\x12\x1a\n" + - "\blocation\x18\x02 \x01(\tR\blocation\x12\x1e\n" + - "\n" + - "hypothesis\x18\x03 \x01(\tR\n" + - "hypothesis\x12;\n" + - "\ttreatment\x18\x04 \x01(\v2\x1d.xtcp.controller.v1.TreatmentR\ttreatment\x12<\n" + - "\x06policy\x18\x05 \x01(\v2$.xtcp.controller.v1.ExperimentPolicyR\x06policy\x128\n" + - "\bbaseline\x18\x06 \x01(\v2\x1c.xtcp.controller.v1.EvidenceR\bbaseline\x12!\n" + - "\fcohort_order\x18\a \x03(\tR\vcohortOrder\x121\n" + - "\x06phases\x18\b \x03(\v2\x19.xtcp.controller.v1.PhaseR\x06phases\x12<\n" + - "\x06status\x18\t \x01(\x0e2$.xtcp.controller.v1.ExperimentStatusR\x06status\x12\x1a\n" + - "\brevision\x18\n" + - " \x01(\x04R\brevision\x12;\n" + - "\vproposed_at\x18\v \x01(\v2\x1a.google.protobuf.TimestampR\n" + - "proposedAt\x12E\n" + - "\x0flatest_evidence\x18\f \x01(\v2\x1c.xtcp.controller.v1.EvidenceR\x0elatestEvidence\x12%\n" + - "\x0elatest_outcome\x18\r \x01(\tR\rlatestOutcome\"o\n" + - "\x0eProposeRequest\x12>\n" + - "\n" + - "experiment\x18\x01 \x01(\v2\x1e.xtcp.controller.v1.ExperimentR\n" + - "experiment\x12\x1d\n" + - "\n" + - "request_id\x18\x02 \x01(\tR\trequestId\"Q\n" + - "\x0fProposeResponse\x12>\n" + - "\n" + - "experiment\x18\x01 \x01(\v2\x1e.xtcp.controller.v1.ExperimentR\n" + - "experiment\"b\n" + - "\x0eAnalyzeRequest\x12#\n" + - "\rexperiment_id\x18\x01 \x01(\tR\fexperimentId\x12+\n" + - "\x11expected_revision\x18\x02 \x01(\x04R\x10expectedRevision\"e\n" + - "\x0fAnalyzeResponse\x128\n" + - "\bevidence\x18\x01 \x01(\v2\x1c.xtcp.controller.v1.EvidenceR\bevidence\x12\x18\n" + - "\aoutcome\x18\x02 \x01(\tR\aoutcome\"\xec\x01\n" + - "\rReviewRequest\x12#\n" + - "\rexperiment_id\x18\x01 \x01(\tR\fexperimentId\x12+\n" + - "\x11expected_revision\x18\x02 \x01(\x04R\x10expectedRevision\x12\x18\n" + - "\aapprove\x18\x03 \x01(\bR\aapprove\x12\x16\n" + - "\x06reason\x18\x04 \x01(\tR\x06reason\x12\x1d\n" + - "\n" + - "request_id\x18\x05 \x01(\tR\trequestId\x128\n" + - "\bevidence\x18\x06 \x01(\v2\x1c.xtcp.controller.v1.EvidenceR\bevidence\"P\n" + - "\x0eReviewResponse\x12>\n" + - "\n" + - "experiment\x18\x01 \x01(\v2\x1e.xtcp.controller.v1.ExperimentR\n" + - "experiment\"\xf4\x01\n" + - "\x0eControlRequest\x12#\n" + - "\rexperiment_id\x18\x01 \x01(\tR\fexperimentId\x12\x19\n" + - "\bagent_id\x18\x02 \x01(\tR\aagentId\x12\x1a\n" + - "\blocation\x18\x03 \x01(\tR\blocation\x12\x14\n" + - "\x05fleet\x18\x04 \x01(\bR\x05fleet\x129\n" + - "\x06action\x18\x05 \x01(\x0e2!.xtcp.controller.v1.ControlActionR\x06action\x12\x16\n" + - "\x06reason\x18\x06 \x01(\tR\x06reason\x12\x1d\n" + - "\n" + - "request_id\x18\a \x01(\tR\trequestId\":\n" + - "\x0fControlResponse\x12'\n" + - "\x0faffected_agents\x18\x01 \x01(\rR\x0eaffectedAgents\"P\n" + - "\x0eInspectRequest\x12#\n" + - "\rexperiment_id\x18\x01 \x01(\tR\fexperimentId\x12\x19\n" + - "\bagent_id\x18\x02 \x01(\tR\aagentId\"\xcf\x01\n" + - "\x0fInspectResponse\x12@\n" + - "\vexperiments\x18\x01 \x03(\v2\x1e.xtcp.controller.v1.ExperimentR\vexperiments\x12:\n" + - "\adesired\x18\x02 \x03(\v2 .xtcp.controller.v1.DesiredStateR\adesired\x12>\n" + - "\bobserved\x18\x03 \x03(\v2\".xtcp.controller.v1.CheckInRequestR\bobserved*\xa4\x01\n" + - "\x0eAssignmentMode\x12\x1f\n" + - "\x1bASSIGNMENT_MODE_UNSPECIFIED\x10\x00\x12\x19\n" + - "\x15ASSIGNMENT_MODE_TRIAL\x10\x01\x12\x1a\n" + - "\x16ASSIGNMENT_MODE_STABLE\x10\x02\x12\x1d\n" + - "\x19ASSIGNMENT_MODE_SUSPENDED\x10\x03\x12\x1b\n" + - "\x17ASSIGNMENT_MODE_RETIRED\x10\x04*\xad\x01\n" + - "\vApplyStatus\x12\x1c\n" + - "\x18APPLY_STATUS_UNSPECIFIED\x10\x00\x12\x18\n" + - "\x14APPLY_STATUS_PENDING\x10\x01\x12\x19\n" + - "\x15APPLY_STATUS_VERIFIED\x10\x02\x12\x18\n" + - "\x14APPLY_STATUS_REMOVED\x10\x03\x12\x18\n" + - "\x14APPLY_STATUS_EXPIRED\x10\x04\x12\x17\n" + - "\x13APPLY_STATUS_FAILED\x10\x05*\x95\x02\n" + - "\x10ExperimentStatus\x12!\n" + - "\x1dEXPERIMENT_STATUS_UNSPECIFIED\x10\x00\x12\x1e\n" + - "\x1aEXPERIMENT_STATUS_PROPOSED\x10\x01\x12\x1d\n" + - "\x19EXPERIMENT_STATUS_RUNNING\x10\x02\x12\x1c\n" + - "\x18EXPERIMENT_STATUS_PAUSED\x10\x03\x12\x1f\n" + - "\x1bEXPERIMENT_STATUS_SUSPENDED\x10\x04\x12\x1e\n" + - "\x1aEXPERIMENT_STATUS_COMPLETE\x10\x05\x12\x1d\n" + - "\x19EXPERIMENT_STATUS_RETIRED\x10\x06\x12!\n" + - "\x1dEXPERIMENT_STATUS_ROLLED_BACK\x10\a*\xb8\x01\n" + - "\rControlAction\x12\x1e\n" + - "\x1aCONTROL_ACTION_UNSPECIFIED\x10\x00\x12\x18\n" + - "\x14CONTROL_ACTION_PAUSE\x10\x01\x12\x1a\n" + - "\x16CONTROL_ACTION_SUSPEND\x10\x02\x12\x19\n" + - "\x15CONTROL_ACTION_RESUME\x10\x03\x12\x1b\n" + - "\x17CONTROL_ACTION_ROLLBACK\x10\x04\x12\x19\n" + - "\x15CONTROL_ACTION_RETIRE\x10\x052\xc9\x02\n" + - "\x13AgentControlService\x12R\n" + - "\aCheckIn\x12\".xtcp.controller.v1.CheckInRequest\x1a#.xtcp.controller.v1.CheckInResponse\x12j\n" + - "\x0fGetDesiredState\x12*.xtcp.controller.v1.GetDesiredStateRequest\x1a+.xtcp.controller.v1.GetDesiredStateResponse\x12r\n" + - "\x11WatchDesiredState\x12,.xtcp.controller.v1.WatchDesiredStateRequest\x1a-.xtcp.controller.v1.WatchDesiredStateResponse0\x012\xb2\x03\n" + - "\x0fOperatorService\x12R\n" + - "\aPropose\x12\".xtcp.controller.v1.ProposeRequest\x1a#.xtcp.controller.v1.ProposeResponse\x12R\n" + - "\aAnalyze\x12\".xtcp.controller.v1.AnalyzeRequest\x1a#.xtcp.controller.v1.AnalyzeResponse\x12O\n" + - "\x06Review\x12!.xtcp.controller.v1.ReviewRequest\x1a\".xtcp.controller.v1.ReviewResponse\x12R\n" + - "\aControl\x12\".xtcp.controller.v1.ControlRequest\x1a#.xtcp.controller.v1.ControlResponse\x12R\n" + - "\aInspect\x12\".xtcp.controller.v1.InspectRequest\x1a#.xtcp.controller.v1.InspectResponseB9Z7github.com/randomizedcoder/xtcp2/gen/go/xtcp_controllerb\x06proto3" - -var ( - file_xtcp_controller_v1_controller_proto_rawDescOnce sync.Once - file_xtcp_controller_v1_controller_proto_rawDescData []byte -) - -func file_xtcp_controller_v1_controller_proto_rawDescGZIP() []byte { - file_xtcp_controller_v1_controller_proto_rawDescOnce.Do(func() { - file_xtcp_controller_v1_controller_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_xtcp_controller_v1_controller_proto_rawDesc), len(file_xtcp_controller_v1_controller_proto_rawDesc))) - }) - return file_xtcp_controller_v1_controller_proto_rawDescData -} - -var file_xtcp_controller_v1_controller_proto_enumTypes = make([]protoimpl.EnumInfo, 4) -var file_xtcp_controller_v1_controller_proto_msgTypes = make([]protoimpl.MessageInfo, 28) -var file_xtcp_controller_v1_controller_proto_goTypes = []any{ - (AssignmentMode)(0), // 0: xtcp.controller.v1.AssignmentMode - (ApplyStatus)(0), // 1: xtcp.controller.v1.ApplyStatus - (ExperimentStatus)(0), // 2: xtcp.controller.v1.ExperimentStatus - (ControlAction)(0), // 3: xtcp.controller.v1.ControlAction - (*TCPSettings)(nil), // 4: xtcp.controller.v1.TCPSettings - (*Treatment)(nil), // 5: xtcp.controller.v1.Treatment - (*Assignment)(nil), // 6: xtcp.controller.v1.Assignment - (*DesiredState)(nil), // 7: xtcp.controller.v1.DesiredState - (*Capabilities)(nil), // 8: xtcp.controller.v1.Capabilities - (*ApplicationRecord)(nil), // 9: xtcp.controller.v1.ApplicationRecord - (*LocalState)(nil), // 10: xtcp.controller.v1.LocalState - (*CheckInRequest)(nil), // 11: xtcp.controller.v1.CheckInRequest - (*CheckInResponse)(nil), // 12: xtcp.controller.v1.CheckInResponse - (*GetDesiredStateRequest)(nil), // 13: xtcp.controller.v1.GetDesiredStateRequest - (*GetDesiredStateResponse)(nil), // 14: xtcp.controller.v1.GetDesiredStateResponse - (*WatchDesiredStateRequest)(nil), // 15: xtcp.controller.v1.WatchDesiredStateRequest - (*WatchDesiredStateResponse)(nil), // 16: xtcp.controller.v1.WatchDesiredStateResponse - (*HostEvidence)(nil), // 17: xtcp.controller.v1.HostEvidence - (*Evidence)(nil), // 18: xtcp.controller.v1.Evidence - (*ExperimentPolicy)(nil), // 19: xtcp.controller.v1.ExperimentPolicy - (*Phase)(nil), // 20: xtcp.controller.v1.Phase - (*Experiment)(nil), // 21: xtcp.controller.v1.Experiment - (*ProposeRequest)(nil), // 22: xtcp.controller.v1.ProposeRequest - (*ProposeResponse)(nil), // 23: xtcp.controller.v1.ProposeResponse - (*AnalyzeRequest)(nil), // 24: xtcp.controller.v1.AnalyzeRequest - (*AnalyzeResponse)(nil), // 25: xtcp.controller.v1.AnalyzeResponse - (*ReviewRequest)(nil), // 26: xtcp.controller.v1.ReviewRequest - (*ReviewResponse)(nil), // 27: xtcp.controller.v1.ReviewResponse - (*ControlRequest)(nil), // 28: xtcp.controller.v1.ControlRequest - (*ControlResponse)(nil), // 29: xtcp.controller.v1.ControlResponse - (*InspectRequest)(nil), // 30: xtcp.controller.v1.InspectRequest - (*InspectResponse)(nil), // 31: xtcp.controller.v1.InspectResponse - (*timestamppb.Timestamp)(nil), // 32: google.protobuf.Timestamp - (*durationpb.Duration)(nil), // 33: google.protobuf.Duration -} -var file_xtcp_controller_v1_controller_proto_depIdxs = []int32{ - 4, // 0: xtcp.controller.v1.Treatment.settings:type_name -> xtcp.controller.v1.TCPSettings - 5, // 1: xtcp.controller.v1.Assignment.treatment:type_name -> xtcp.controller.v1.Treatment - 0, // 2: xtcp.controller.v1.Assignment.mode:type_name -> xtcp.controller.v1.AssignmentMode - 32, // 3: xtcp.controller.v1.Assignment.lease_expires_at:type_name -> google.protobuf.Timestamp - 5, // 4: xtcp.controller.v1.Assignment.predecessor:type_name -> xtcp.controller.v1.Treatment - 6, // 5: xtcp.controller.v1.DesiredState.assignments:type_name -> xtcp.controller.v1.Assignment - 32, // 6: xtcp.controller.v1.DesiredState.issued_at:type_name -> google.protobuf.Timestamp - 6, // 7: xtcp.controller.v1.ApplicationRecord.assignment:type_name -> xtcp.controller.v1.Assignment - 1, // 8: xtcp.controller.v1.ApplicationRecord.status:type_name -> xtcp.controller.v1.ApplyStatus - 32, // 9: xtcp.controller.v1.ApplicationRecord.received_at:type_name -> google.protobuf.Timestamp - 32, // 10: xtcp.controller.v1.ApplicationRecord.attempted_at:type_name -> google.protobuf.Timestamp - 32, // 11: xtcp.controller.v1.ApplicationRecord.applied_at:type_name -> google.protobuf.Timestamp - 32, // 12: xtcp.controller.v1.ApplicationRecord.verified_at:type_name -> google.protobuf.Timestamp - 32, // 13: xtcp.controller.v1.ApplicationRecord.removed_at:type_name -> google.protobuf.Timestamp - 7, // 14: xtcp.controller.v1.LocalState.desired:type_name -> xtcp.controller.v1.DesiredState - 9, // 15: xtcp.controller.v1.LocalState.records:type_name -> xtcp.controller.v1.ApplicationRecord - 32, // 16: xtcp.controller.v1.LocalState.updated_at:type_name -> google.protobuf.Timestamp - 8, // 17: xtcp.controller.v1.CheckInRequest.capabilities:type_name -> xtcp.controller.v1.Capabilities - 9, // 18: xtcp.controller.v1.CheckInRequest.records:type_name -> xtcp.controller.v1.ApplicationRecord - 7, // 19: xtcp.controller.v1.CheckInResponse.desired:type_name -> xtcp.controller.v1.DesiredState - 7, // 20: xtcp.controller.v1.GetDesiredStateResponse.desired:type_name -> xtcp.controller.v1.DesiredState - 32, // 21: xtcp.controller.v1.Evidence.analyzed_at:type_name -> google.protobuf.Timestamp - 32, // 22: xtcp.controller.v1.Evidence.window_start:type_name -> google.protobuf.Timestamp - 32, // 23: xtcp.controller.v1.Evidence.window_end:type_name -> google.protobuf.Timestamp - 17, // 24: xtcp.controller.v1.Evidence.hosts:type_name -> xtcp.controller.v1.HostEvidence - 33, // 25: xtcp.controller.v1.ExperimentPolicy.minimum_observation:type_name -> google.protobuf.Duration - 33, // 26: xtcp.controller.v1.ExperimentPolicy.maximum_phase:type_name -> google.protobuf.Duration - 33, // 27: xtcp.controller.v1.ExperimentPolicy.maximum_evidence_age:type_name -> google.protobuf.Duration - 32, // 28: xtcp.controller.v1.Phase.issued_at:type_name -> google.protobuf.Timestamp - 32, // 29: xtcp.controller.v1.Phase.observation_started_at:type_name -> google.protobuf.Timestamp - 32, // 30: xtcp.controller.v1.Phase.evaluated_at:type_name -> google.protobuf.Timestamp - 18, // 31: xtcp.controller.v1.Phase.evidence:type_name -> xtcp.controller.v1.Evidence - 5, // 32: xtcp.controller.v1.Experiment.treatment:type_name -> xtcp.controller.v1.Treatment - 19, // 33: xtcp.controller.v1.Experiment.policy:type_name -> xtcp.controller.v1.ExperimentPolicy - 18, // 34: xtcp.controller.v1.Experiment.baseline:type_name -> xtcp.controller.v1.Evidence - 20, // 35: xtcp.controller.v1.Experiment.phases:type_name -> xtcp.controller.v1.Phase - 2, // 36: xtcp.controller.v1.Experiment.status:type_name -> xtcp.controller.v1.ExperimentStatus - 32, // 37: xtcp.controller.v1.Experiment.proposed_at:type_name -> google.protobuf.Timestamp - 18, // 38: xtcp.controller.v1.Experiment.latest_evidence:type_name -> xtcp.controller.v1.Evidence - 21, // 39: xtcp.controller.v1.ProposeRequest.experiment:type_name -> xtcp.controller.v1.Experiment - 21, // 40: xtcp.controller.v1.ProposeResponse.experiment:type_name -> xtcp.controller.v1.Experiment - 18, // 41: xtcp.controller.v1.AnalyzeResponse.evidence:type_name -> xtcp.controller.v1.Evidence - 18, // 42: xtcp.controller.v1.ReviewRequest.evidence:type_name -> xtcp.controller.v1.Evidence - 21, // 43: xtcp.controller.v1.ReviewResponse.experiment:type_name -> xtcp.controller.v1.Experiment - 3, // 44: xtcp.controller.v1.ControlRequest.action:type_name -> xtcp.controller.v1.ControlAction - 21, // 45: xtcp.controller.v1.InspectResponse.experiments:type_name -> xtcp.controller.v1.Experiment - 7, // 46: xtcp.controller.v1.InspectResponse.desired:type_name -> xtcp.controller.v1.DesiredState - 11, // 47: xtcp.controller.v1.InspectResponse.observed:type_name -> xtcp.controller.v1.CheckInRequest - 11, // 48: xtcp.controller.v1.AgentControlService.CheckIn:input_type -> xtcp.controller.v1.CheckInRequest - 13, // 49: xtcp.controller.v1.AgentControlService.GetDesiredState:input_type -> xtcp.controller.v1.GetDesiredStateRequest - 15, // 50: xtcp.controller.v1.AgentControlService.WatchDesiredState:input_type -> xtcp.controller.v1.WatchDesiredStateRequest - 22, // 51: xtcp.controller.v1.OperatorService.Propose:input_type -> xtcp.controller.v1.ProposeRequest - 24, // 52: xtcp.controller.v1.OperatorService.Analyze:input_type -> xtcp.controller.v1.AnalyzeRequest - 26, // 53: xtcp.controller.v1.OperatorService.Review:input_type -> xtcp.controller.v1.ReviewRequest - 28, // 54: xtcp.controller.v1.OperatorService.Control:input_type -> xtcp.controller.v1.ControlRequest - 30, // 55: xtcp.controller.v1.OperatorService.Inspect:input_type -> xtcp.controller.v1.InspectRequest - 12, // 56: xtcp.controller.v1.AgentControlService.CheckIn:output_type -> xtcp.controller.v1.CheckInResponse - 14, // 57: xtcp.controller.v1.AgentControlService.GetDesiredState:output_type -> xtcp.controller.v1.GetDesiredStateResponse - 16, // 58: xtcp.controller.v1.AgentControlService.WatchDesiredState:output_type -> xtcp.controller.v1.WatchDesiredStateResponse - 23, // 59: xtcp.controller.v1.OperatorService.Propose:output_type -> xtcp.controller.v1.ProposeResponse - 25, // 60: xtcp.controller.v1.OperatorService.Analyze:output_type -> xtcp.controller.v1.AnalyzeResponse - 27, // 61: xtcp.controller.v1.OperatorService.Review:output_type -> xtcp.controller.v1.ReviewResponse - 29, // 62: xtcp.controller.v1.OperatorService.Control:output_type -> xtcp.controller.v1.ControlResponse - 31, // 63: xtcp.controller.v1.OperatorService.Inspect:output_type -> xtcp.controller.v1.InspectResponse - 56, // [56:64] is the sub-list for method output_type - 48, // [48:56] is the sub-list for method input_type - 48, // [48:48] is the sub-list for extension type_name - 48, // [48:48] is the sub-list for extension extendee - 0, // [0:48] is the sub-list for field type_name -} - -func init() { file_xtcp_controller_v1_controller_proto_init() } -func file_xtcp_controller_v1_controller_proto_init() { - if File_xtcp_controller_v1_controller_proto != nil { - return - } - file_xtcp_controller_v1_controller_proto_msgTypes[0].OneofWrappers = []any{} - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_xtcp_controller_v1_controller_proto_rawDesc), len(file_xtcp_controller_v1_controller_proto_rawDesc)), - NumEnums: 4, - NumMessages: 28, - NumExtensions: 0, - NumServices: 2, - }, - GoTypes: file_xtcp_controller_v1_controller_proto_goTypes, - DependencyIndexes: file_xtcp_controller_v1_controller_proto_depIdxs, - EnumInfos: file_xtcp_controller_v1_controller_proto_enumTypes, - MessageInfos: file_xtcp_controller_v1_controller_proto_msgTypes, - }.Build() - File_xtcp_controller_v1_controller_proto = out.File - file_xtcp_controller_v1_controller_proto_goTypes = nil - file_xtcp_controller_v1_controller_proto_depIdxs = nil -} diff --git a/gen/go/xtcp_treatment/agent.pb.go b/gen/go/xtcp_treatment/agent.pb.go new file mode 100644 index 000000000..8e4a544dc --- /dev/null +++ b/gen/go/xtcp_treatment/agent.pb.go @@ -0,0 +1,440 @@ +// +// xTCP - treatment agent service +// +// The outbound-only RPC surface an agent uses: check in with observed state, +// fetch the authoritative desired state, and watch for revision hints. Every +// method requires an agent certificate whose SPIFFE id matches agent_id and +// whose location matches the request; see pkg/treatment/identity. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.12 +// protoc (unknown) +// source: xtcp_treatment/v1/agent.proto + +package xtcp_treatment + +import ( + _ "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type CheckInRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` + Location string `protobuf:"bytes,2,opt,name=location,proto3" json:"location,omitempty"` + Capabilities *Capabilities `protobuf:"bytes,3,opt,name=capabilities,proto3" json:"capabilities,omitempty"` + DesiredRevision uint64 `protobuf:"varint,4,opt,name=desired_revision,json=desiredRevision,proto3" json:"desired_revision,omitempty"` + Records []*ApplicationRecord `protobuf:"bytes,5,rep,name=records,proto3" json:"records,omitempty"` + LocallySuspended bool `protobuf:"varint,6,opt,name=locally_suspended,json=locallySuspended,proto3" json:"locally_suspended,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CheckInRequest) Reset() { + *x = CheckInRequest{} + mi := &file_xtcp_treatment_v1_agent_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CheckInRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CheckInRequest) ProtoMessage() {} + +func (x *CheckInRequest) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_v1_agent_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CheckInRequest.ProtoReflect.Descriptor instead. +func (*CheckInRequest) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_agent_proto_rawDescGZIP(), []int{0} +} + +func (x *CheckInRequest) GetAgentId() string { + if x != nil { + return x.AgentId + } + return "" +} + +func (x *CheckInRequest) GetLocation() string { + if x != nil { + return x.Location + } + return "" +} + +func (x *CheckInRequest) GetCapabilities() *Capabilities { + if x != nil { + return x.Capabilities + } + return nil +} + +func (x *CheckInRequest) GetDesiredRevision() uint64 { + if x != nil { + return x.DesiredRevision + } + return 0 +} + +func (x *CheckInRequest) GetRecords() []*ApplicationRecord { + if x != nil { + return x.Records + } + return nil +} + +func (x *CheckInRequest) GetLocallySuspended() bool { + if x != nil { + return x.LocallySuspended + } + return false +} + +type CheckInResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Desired *DesiredState `protobuf:"bytes,1,opt,name=desired,proto3" json:"desired,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CheckInResponse) Reset() { + *x = CheckInResponse{} + mi := &file_xtcp_treatment_v1_agent_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CheckInResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CheckInResponse) ProtoMessage() {} + +func (x *CheckInResponse) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_v1_agent_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CheckInResponse.ProtoReflect.Descriptor instead. +func (*CheckInResponse) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_agent_proto_rawDescGZIP(), []int{1} +} + +func (x *CheckInResponse) GetDesired() *DesiredState { + if x != nil { + return x.Desired + } + return nil +} + +type GetDesiredStateRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetDesiredStateRequest) Reset() { + *x = GetDesiredStateRequest{} + mi := &file_xtcp_treatment_v1_agent_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetDesiredStateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetDesiredStateRequest) ProtoMessage() {} + +func (x *GetDesiredStateRequest) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_v1_agent_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetDesiredStateRequest.ProtoReflect.Descriptor instead. +func (*GetDesiredStateRequest) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_agent_proto_rawDescGZIP(), []int{2} +} + +func (x *GetDesiredStateRequest) GetAgentId() string { + if x != nil { + return x.AgentId + } + return "" +} + +type GetDesiredStateResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Desired *DesiredState `protobuf:"bytes,1,opt,name=desired,proto3" json:"desired,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetDesiredStateResponse) Reset() { + *x = GetDesiredStateResponse{} + mi := &file_xtcp_treatment_v1_agent_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetDesiredStateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetDesiredStateResponse) ProtoMessage() {} + +func (x *GetDesiredStateResponse) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_v1_agent_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetDesiredStateResponse.ProtoReflect.Descriptor instead. +func (*GetDesiredStateResponse) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_agent_proto_rawDescGZIP(), []int{3} +} + +func (x *GetDesiredStateResponse) GetDesired() *DesiredState { + if x != nil { + return x.Desired + } + return nil +} + +type WatchDesiredStateRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` + AfterRevision uint64 `protobuf:"varint,2,opt,name=after_revision,json=afterRevision,proto3" json:"after_revision,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WatchDesiredStateRequest) Reset() { + *x = WatchDesiredStateRequest{} + mi := &file_xtcp_treatment_v1_agent_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WatchDesiredStateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WatchDesiredStateRequest) ProtoMessage() {} + +func (x *WatchDesiredStateRequest) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_v1_agent_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WatchDesiredStateRequest.ProtoReflect.Descriptor instead. +func (*WatchDesiredStateRequest) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_agent_proto_rawDescGZIP(), []int{4} +} + +func (x *WatchDesiredStateRequest) GetAgentId() string { + if x != nil { + return x.AgentId + } + return "" +} + +func (x *WatchDesiredStateRequest) GetAfterRevision() uint64 { + if x != nil { + return x.AfterRevision + } + return 0 +} + +type WatchDesiredStateResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Revision uint64 `protobuf:"varint,1,opt,name=revision,proto3" json:"revision,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WatchDesiredStateResponse) Reset() { + *x = WatchDesiredStateResponse{} + mi := &file_xtcp_treatment_v1_agent_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WatchDesiredStateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WatchDesiredStateResponse) ProtoMessage() {} + +func (x *WatchDesiredStateResponse) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_v1_agent_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WatchDesiredStateResponse.ProtoReflect.Descriptor instead. +func (*WatchDesiredStateResponse) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_agent_proto_rawDescGZIP(), []int{5} +} + +func (x *WatchDesiredStateResponse) GetRevision() uint64 { + if x != nil { + return x.Revision + } + return 0 +} + +var File_xtcp_treatment_v1_agent_proto protoreflect.FileDescriptor + +const file_xtcp_treatment_v1_agent_proto_rawDesc = "" + + "\n" + + "\x1dxtcp_treatment/v1/agent.proto\x12\x11xtcp_treatment.v1\x1a\x1bbuf/validate/validate.proto\x1a!xtcp_treatment/v1/treatment.proto\"\x8d\x03\n" + + "\x0eCheckInRequest\x12D\n" + + "\bagent_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\aagentId\x12E\n" + + "\blocation\x18\x02 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\blocation\x12K\n" + + "\fcapabilities\x18\x03 \x01(\v2\x1f.xtcp_treatment.v1.CapabilitiesB\x06\xbaH\x03\xc8\x01\x01R\fcapabilities\x12)\n" + + "\x10desired_revision\x18\x04 \x01(\x04R\x0fdesiredRevision\x12I\n" + + "\arecords\x18\x05 \x03(\v2$.xtcp_treatment.v1.ApplicationRecordB\t\xbaH\x06\x92\x01\x03\x10\x80@R\arecords\x12+\n" + + "\x11locally_suspended\x18\x06 \x01(\bR\x10locallySuspended\"L\n" + + "\x0fCheckInResponse\x129\n" + + "\adesired\x18\x01 \x01(\v2\x1f.xtcp_treatment.v1.DesiredStateR\adesired\"^\n" + + "\x16GetDesiredStateRequest\x12D\n" + + "\bagent_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\aagentId\"T\n" + + "\x17GetDesiredStateResponse\x129\n" + + "\adesired\x18\x01 \x01(\v2\x1f.xtcp_treatment.v1.DesiredStateR\adesired\"\x87\x01\n" + + "\x18WatchDesiredStateRequest\x12D\n" + + "\bagent_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\aagentId\x12%\n" + + "\x0eafter_revision\x18\x02 \x01(\x04R\rafterRevision\"7\n" + + "\x19WatchDesiredStateResponse\x12\x1a\n" + + "\brevision\x18\x01 \x01(\x04R\brevision2\xd5\x02\n" + + "\x13AgentControlService\x12V\n" + + "\aCheckIn\x12!.xtcp_treatment.v1.CheckInRequest\x1a\".xtcp_treatment.v1.CheckInResponse\"\x04\xd0\xf3\x18\x01\x12n\n" + + "\x0fGetDesiredState\x12).xtcp_treatment.v1.GetDesiredStateRequest\x1a*.xtcp_treatment.v1.GetDesiredStateResponse\"\x04\xd0\xf3\x18\x01\x12v\n" + + "\x11WatchDesiredState\x12+.xtcp_treatment.v1.WatchDesiredStateRequest\x1a,.xtcp_treatment.v1.WatchDesiredStateResponse\"\x04\xd0\xf3\x18\x010\x01B\xbc\x01\n" + + "\x15com.xtcp_treatment.v1B\n" + + "AgentProtoP\x01Z6github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment\xa2\x02\x03XXX\xaa\x02\x10XtcpTreatment.V1\xca\x02\x10XtcpTreatment\\V1\xe2\x02\x1cXtcpTreatment\\V1\\GPBMetadata\xea\x02\x11XtcpTreatment::V1b\x06proto3" + +var ( + file_xtcp_treatment_v1_agent_proto_rawDescOnce sync.Once + file_xtcp_treatment_v1_agent_proto_rawDescData []byte +) + +func file_xtcp_treatment_v1_agent_proto_rawDescGZIP() []byte { + file_xtcp_treatment_v1_agent_proto_rawDescOnce.Do(func() { + file_xtcp_treatment_v1_agent_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_xtcp_treatment_v1_agent_proto_rawDesc), len(file_xtcp_treatment_v1_agent_proto_rawDesc))) + }) + return file_xtcp_treatment_v1_agent_proto_rawDescData +} + +var file_xtcp_treatment_v1_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_xtcp_treatment_v1_agent_proto_goTypes = []any{ + (*CheckInRequest)(nil), // 0: xtcp_treatment.v1.CheckInRequest + (*CheckInResponse)(nil), // 1: xtcp_treatment.v1.CheckInResponse + (*GetDesiredStateRequest)(nil), // 2: xtcp_treatment.v1.GetDesiredStateRequest + (*GetDesiredStateResponse)(nil), // 3: xtcp_treatment.v1.GetDesiredStateResponse + (*WatchDesiredStateRequest)(nil), // 4: xtcp_treatment.v1.WatchDesiredStateRequest + (*WatchDesiredStateResponse)(nil), // 5: xtcp_treatment.v1.WatchDesiredStateResponse + (*Capabilities)(nil), // 6: xtcp_treatment.v1.Capabilities + (*ApplicationRecord)(nil), // 7: xtcp_treatment.v1.ApplicationRecord + (*DesiredState)(nil), // 8: xtcp_treatment.v1.DesiredState +} +var file_xtcp_treatment_v1_agent_proto_depIdxs = []int32{ + 6, // 0: xtcp_treatment.v1.CheckInRequest.capabilities:type_name -> xtcp_treatment.v1.Capabilities + 7, // 1: xtcp_treatment.v1.CheckInRequest.records:type_name -> xtcp_treatment.v1.ApplicationRecord + 8, // 2: xtcp_treatment.v1.CheckInResponse.desired:type_name -> xtcp_treatment.v1.DesiredState + 8, // 3: xtcp_treatment.v1.GetDesiredStateResponse.desired:type_name -> xtcp_treatment.v1.DesiredState + 0, // 4: xtcp_treatment.v1.AgentControlService.CheckIn:input_type -> xtcp_treatment.v1.CheckInRequest + 2, // 5: xtcp_treatment.v1.AgentControlService.GetDesiredState:input_type -> xtcp_treatment.v1.GetDesiredStateRequest + 4, // 6: xtcp_treatment.v1.AgentControlService.WatchDesiredState:input_type -> xtcp_treatment.v1.WatchDesiredStateRequest + 1, // 7: xtcp_treatment.v1.AgentControlService.CheckIn:output_type -> xtcp_treatment.v1.CheckInResponse + 3, // 8: xtcp_treatment.v1.AgentControlService.GetDesiredState:output_type -> xtcp_treatment.v1.GetDesiredStateResponse + 5, // 9: xtcp_treatment.v1.AgentControlService.WatchDesiredState:output_type -> xtcp_treatment.v1.WatchDesiredStateResponse + 7, // [7:10] is the sub-list for method output_type + 4, // [4:7] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_xtcp_treatment_v1_agent_proto_init() } +func file_xtcp_treatment_v1_agent_proto_init() { + if File_xtcp_treatment_v1_agent_proto != nil { + return + } + file_xtcp_treatment_v1_treatment_proto_init() + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_xtcp_treatment_v1_agent_proto_rawDesc), len(file_xtcp_treatment_v1_agent_proto_rawDesc)), + NumEnums: 0, + NumMessages: 6, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_xtcp_treatment_v1_agent_proto_goTypes, + DependencyIndexes: file_xtcp_treatment_v1_agent_proto_depIdxs, + MessageInfos: file_xtcp_treatment_v1_agent_proto_msgTypes, + }.Build() + File_xtcp_treatment_v1_agent_proto = out.File + file_xtcp_treatment_v1_agent_proto_goTypes = nil + file_xtcp_treatment_v1_agent_proto_depIdxs = nil +} diff --git a/gen/go/xtcp_treatment/agent.pb.gw.go b/gen/go/xtcp_treatment/agent.pb.gw.go new file mode 100644 index 000000000..8686a944a --- /dev/null +++ b/gen/go/xtcp_treatment/agent.pb.gw.go @@ -0,0 +1,272 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: xtcp_treatment/v1/agent.proto + +/* +Package xtcp_treatment is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package xtcp_treatment + +import ( + "context" + "errors" + "io" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/grpc-ecosystem/grpc-gateway/v2/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +// Suppress "imported and not used" errors +var ( + _ codes.Code + _ io.Reader + _ status.Status + _ = errors.New + _ = runtime.String + _ = utilities.NewDoubleArray + _ = metadata.Join +) + +func request_AgentControlService_CheckIn_0(ctx context.Context, marshaler runtime.Marshaler, client AgentControlServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq CheckInRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.CheckIn(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_AgentControlService_CheckIn_0(ctx context.Context, marshaler runtime.Marshaler, server AgentControlServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq CheckInRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.CheckIn(ctx, &protoReq) + return msg, metadata, err +} + +func request_AgentControlService_GetDesiredState_0(ctx context.Context, marshaler runtime.Marshaler, client AgentControlServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq GetDesiredStateRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.GetDesiredState(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_AgentControlService_GetDesiredState_0(ctx context.Context, marshaler runtime.Marshaler, server AgentControlServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq GetDesiredStateRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.GetDesiredState(ctx, &protoReq) + return msg, metadata, err +} + +func request_AgentControlService_WatchDesiredState_0(ctx context.Context, marshaler runtime.Marshaler, client AgentControlServiceClient, req *http.Request, pathParams map[string]string) (AgentControlService_WatchDesiredStateClient, runtime.ServerMetadata, error) { + var ( + protoReq WatchDesiredStateRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + stream, err := client.WatchDesiredState(ctx, &protoReq) + if err != nil { + return nil, metadata, err + } + header, err := stream.Header() + if err != nil { + return nil, metadata, err + } + metadata.HeaderMD = header + return stream, metadata, nil +} + +// RegisterAgentControlServiceHandlerServer registers the http handlers for service AgentControlService to "mux". +// UnaryRPC :call AgentControlServiceServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterAgentControlServiceHandlerFromEndpoint instead. +// GRPC interceptors will not work for this type of registration. To use interceptors, you must use the "runtime.WithMiddlewares" option in the "runtime.NewServeMux" call. +func RegisterAgentControlServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server AgentControlServiceServer) error { + mux.Handle(http.MethodPost, pattern_AgentControlService_CheckIn_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/xtcp_treatment.v1.AgentControlService/CheckIn", runtime.WithHTTPPathPattern("/xtcp_treatment.v1.AgentControlService/CheckIn")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AgentControlService_CheckIn_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_AgentControlService_CheckIn_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_AgentControlService_GetDesiredState_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/xtcp_treatment.v1.AgentControlService/GetDesiredState", runtime.WithHTTPPathPattern("/xtcp_treatment.v1.AgentControlService/GetDesiredState")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AgentControlService_GetDesiredState_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_AgentControlService_GetDesiredState_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + + mux.Handle(http.MethodPost, pattern_AgentControlService_WatchDesiredState_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport") + _, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + }) + + return nil +} + +// RegisterAgentControlServiceHandlerFromEndpoint is same as RegisterAgentControlServiceHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterAgentControlServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.NewClient(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Errorf("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Errorf("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + return RegisterAgentControlServiceHandler(ctx, mux, conn) +} + +// RegisterAgentControlServiceHandler registers the http handlers for service AgentControlService to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterAgentControlServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterAgentControlServiceHandlerClient(ctx, mux, NewAgentControlServiceClient(conn)) +} + +// RegisterAgentControlServiceHandlerClient registers the http handlers for service AgentControlService +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "AgentControlServiceClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "AgentControlServiceClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "AgentControlServiceClient" to call the correct interceptors. This client ignores the HTTP middlewares. +func RegisterAgentControlServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client AgentControlServiceClient) error { + mux.Handle(http.MethodPost, pattern_AgentControlService_CheckIn_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/xtcp_treatment.v1.AgentControlService/CheckIn", runtime.WithHTTPPathPattern("/xtcp_treatment.v1.AgentControlService/CheckIn")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AgentControlService_CheckIn_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_AgentControlService_CheckIn_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_AgentControlService_GetDesiredState_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/xtcp_treatment.v1.AgentControlService/GetDesiredState", runtime.WithHTTPPathPattern("/xtcp_treatment.v1.AgentControlService/GetDesiredState")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AgentControlService_GetDesiredState_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_AgentControlService_GetDesiredState_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_AgentControlService_WatchDesiredState_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/xtcp_treatment.v1.AgentControlService/WatchDesiredState", runtime.WithHTTPPathPattern("/xtcp_treatment.v1.AgentControlService/WatchDesiredState")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AgentControlService_WatchDesiredState_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_AgentControlService_WatchDesiredState_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) + }) + return nil +} + +var ( + pattern_AgentControlService_CheckIn_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"xtcp_treatment.v1.AgentControlService", "CheckIn"}, "")) + pattern_AgentControlService_GetDesiredState_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"xtcp_treatment.v1.AgentControlService", "GetDesiredState"}, "")) + pattern_AgentControlService_WatchDesiredState_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"xtcp_treatment.v1.AgentControlService", "WatchDesiredState"}, "")) +) + +var ( + forward_AgentControlService_CheckIn_0 = runtime.ForwardResponseMessage + forward_AgentControlService_GetDesiredState_0 = runtime.ForwardResponseMessage + forward_AgentControlService_WatchDesiredState_0 = runtime.ForwardResponseStream +) diff --git a/gen/go/xtcp_treatment/agent_grpc.pb.go b/gen/go/xtcp_treatment/agent_grpc.pb.go new file mode 100644 index 000000000..29cf28db9 --- /dev/null +++ b/gen/go/xtcp_treatment/agent_grpc.pb.go @@ -0,0 +1,209 @@ +// +// xTCP - treatment agent service +// +// The outbound-only RPC surface an agent uses: check in with observed state, +// fetch the authoritative desired state, and watch for revision hints. Every +// method requires an agent certificate whose SPIFFE id matches agent_id and +// whose location matches the request; see pkg/treatment/identity. + +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.2 +// - protoc (unknown) +// source: xtcp_treatment/v1/agent.proto + +package xtcp_treatment + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + AgentControlService_CheckIn_FullMethodName = "/xtcp_treatment.v1.AgentControlService/CheckIn" + AgentControlService_GetDesiredState_FullMethodName = "/xtcp_treatment.v1.AgentControlService/GetDesiredState" + AgentControlService_WatchDesiredState_FullMethodName = "/xtcp_treatment.v1.AgentControlService/WatchDesiredState" +) + +// AgentControlServiceClient is the client API for AgentControlService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type AgentControlServiceClient interface { + CheckIn(ctx context.Context, in *CheckInRequest, opts ...grpc.CallOption) (*CheckInResponse, error) + GetDesiredState(ctx context.Context, in *GetDesiredStateRequest, opts ...grpc.CallOption) (*GetDesiredStateResponse, error) + WatchDesiredState(ctx context.Context, in *WatchDesiredStateRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[WatchDesiredStateResponse], error) +} + +type agentControlServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewAgentControlServiceClient(cc grpc.ClientConnInterface) AgentControlServiceClient { + return &agentControlServiceClient{cc} +} + +func (c *agentControlServiceClient) CheckIn(ctx context.Context, in *CheckInRequest, opts ...grpc.CallOption) (*CheckInResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CheckInResponse) + err := c.cc.Invoke(ctx, AgentControlService_CheckIn_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *agentControlServiceClient) GetDesiredState(ctx context.Context, in *GetDesiredStateRequest, opts ...grpc.CallOption) (*GetDesiredStateResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetDesiredStateResponse) + err := c.cc.Invoke(ctx, AgentControlService_GetDesiredState_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *agentControlServiceClient) WatchDesiredState(ctx context.Context, in *WatchDesiredStateRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[WatchDesiredStateResponse], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &AgentControlService_ServiceDesc.Streams[0], AgentControlService_WatchDesiredState_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[WatchDesiredStateRequest, WatchDesiredStateResponse]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type AgentControlService_WatchDesiredStateClient = grpc.ServerStreamingClient[WatchDesiredStateResponse] + +// AgentControlServiceServer is the server API for AgentControlService service. +// All implementations must embed UnimplementedAgentControlServiceServer +// for forward compatibility. +type AgentControlServiceServer interface { + CheckIn(context.Context, *CheckInRequest) (*CheckInResponse, error) + GetDesiredState(context.Context, *GetDesiredStateRequest) (*GetDesiredStateResponse, error) + WatchDesiredState(*WatchDesiredStateRequest, grpc.ServerStreamingServer[WatchDesiredStateResponse]) error + mustEmbedUnimplementedAgentControlServiceServer() +} + +// UnimplementedAgentControlServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedAgentControlServiceServer struct{} + +func (UnimplementedAgentControlServiceServer) CheckIn(context.Context, *CheckInRequest) (*CheckInResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CheckIn not implemented") +} +func (UnimplementedAgentControlServiceServer) GetDesiredState(context.Context, *GetDesiredStateRequest) (*GetDesiredStateResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetDesiredState not implemented") +} +func (UnimplementedAgentControlServiceServer) WatchDesiredState(*WatchDesiredStateRequest, grpc.ServerStreamingServer[WatchDesiredStateResponse]) error { + return status.Error(codes.Unimplemented, "method WatchDesiredState not implemented") +} +func (UnimplementedAgentControlServiceServer) mustEmbedUnimplementedAgentControlServiceServer() {} +func (UnimplementedAgentControlServiceServer) testEmbeddedByValue() {} + +// UnsafeAgentControlServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to AgentControlServiceServer will +// result in compilation errors. +type UnsafeAgentControlServiceServer interface { + mustEmbedUnimplementedAgentControlServiceServer() +} + +func RegisterAgentControlServiceServer(s grpc.ServiceRegistrar, srv AgentControlServiceServer) { + // If the following call panics, it indicates UnimplementedAgentControlServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&AgentControlService_ServiceDesc, srv) +} + +func _AgentControlService_CheckIn_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CheckInRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AgentControlServiceServer).CheckIn(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AgentControlService_CheckIn_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AgentControlServiceServer).CheckIn(ctx, req.(*CheckInRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AgentControlService_GetDesiredState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetDesiredStateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AgentControlServiceServer).GetDesiredState(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AgentControlService_GetDesiredState_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AgentControlServiceServer).GetDesiredState(ctx, req.(*GetDesiredStateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AgentControlService_WatchDesiredState_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(WatchDesiredStateRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(AgentControlServiceServer).WatchDesiredState(m, &grpc.GenericServerStream[WatchDesiredStateRequest, WatchDesiredStateResponse]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type AgentControlService_WatchDesiredStateServer = grpc.ServerStreamingServer[WatchDesiredStateResponse] + +// AgentControlService_ServiceDesc is the grpc.ServiceDesc for AgentControlService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var AgentControlService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "xtcp_treatment.v1.AgentControlService", + HandlerType: (*AgentControlServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CheckIn", + Handler: _AgentControlService_CheckIn_Handler, + }, + { + MethodName: "GetDesiredState", + Handler: _AgentControlService_GetDesiredState_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "WatchDesiredState", + Handler: _AgentControlService_WatchDesiredState_Handler, + ServerStreams: true, + }, + }, + Metadata: "xtcp_treatment/v1/agent.proto", +} diff --git a/gen/go/xtcp_treatment/agent_vtproto.pb.go b/gen/go/xtcp_treatment/agent_vtproto.pb.go new file mode 100644 index 000000000..8045d7b0e --- /dev/null +++ b/gen/go/xtcp_treatment/agent_vtproto.pb.go @@ -0,0 +1,1072 @@ +// Code generated by protoc-gen-go-vtproto. DO NOT EDIT. +// protoc-gen-go-vtproto version: (devel) +// source: xtcp_treatment/v1/agent.proto + +package xtcp_treatment + +import ( + fmt "fmt" + protohelpers "github.com/planetscale/vtprotobuf/protohelpers" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + io "io" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +func (m *CheckInRequest) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *CheckInRequest) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *CheckInRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.LocallySuspended { + i-- + if m.LocallySuspended { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x30 + } + if len(m.Records) > 0 { + for iNdEx := len(m.Records) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.Records[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x2a + } + } + if m.DesiredRevision != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.DesiredRevision)) + i-- + dAtA[i] = 0x20 + } + if m.Capabilities != nil { + size, err := m.Capabilities.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x1a + } + if len(m.Location) > 0 { + i -= len(m.Location) + copy(dAtA[i:], m.Location) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Location))) + i-- + dAtA[i] = 0x12 + } + if len(m.AgentId) > 0 { + i -= len(m.AgentId) + copy(dAtA[i:], m.AgentId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.AgentId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *CheckInResponse) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *CheckInResponse) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *CheckInResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.Desired != nil { + size, err := m.Desired.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *GetDesiredStateRequest) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GetDesiredStateRequest) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *GetDesiredStateRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.AgentId) > 0 { + i -= len(m.AgentId) + copy(dAtA[i:], m.AgentId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.AgentId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *GetDesiredStateResponse) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GetDesiredStateResponse) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *GetDesiredStateResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.Desired != nil { + size, err := m.Desired.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *WatchDesiredStateRequest) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *WatchDesiredStateRequest) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *WatchDesiredStateRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.AfterRevision != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.AfterRevision)) + i-- + dAtA[i] = 0x10 + } + if len(m.AgentId) > 0 { + i -= len(m.AgentId) + copy(dAtA[i:], m.AgentId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.AgentId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *WatchDesiredStateResponse) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *WatchDesiredStateResponse) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *WatchDesiredStateResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.Revision != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Revision)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *CheckInRequest) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.AgentId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.Location) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Capabilities != nil { + l = m.Capabilities.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.DesiredRevision != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.DesiredRevision)) + } + if len(m.Records) > 0 { + for _, e := range m.Records { + l = e.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + } + if m.LocallySuspended { + n += 2 + } + n += len(m.unknownFields) + return n +} + +func (m *CheckInResponse) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Desired != nil { + l = m.Desired.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *GetDesiredStateRequest) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.AgentId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *GetDesiredStateResponse) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Desired != nil { + l = m.Desired.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *WatchDesiredStateRequest) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.AgentId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.AfterRevision != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.AfterRevision)) + } + n += len(m.unknownFields) + return n +} + +func (m *WatchDesiredStateResponse) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Revision != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Revision)) + } + n += len(m.unknownFields) + return n +} + +func (m *CheckInRequest) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CheckInRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CheckInRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AgentId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AgentId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Location", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Location = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Capabilities", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Capabilities == nil { + m.Capabilities = &Capabilities{} + } + if err := m.Capabilities.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DesiredRevision", wireType) + } + m.DesiredRevision = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.DesiredRevision |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Records", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Records = append(m.Records, &ApplicationRecord{}) + if err := m.Records[len(m.Records)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field LocallySuspended", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.LocallySuspended = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *CheckInResponse) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CheckInResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CheckInResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Desired", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Desired == nil { + m.Desired = &DesiredState{} + } + if err := m.Desired.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GetDesiredStateRequest) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GetDesiredStateRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GetDesiredStateRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AgentId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AgentId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GetDesiredStateResponse) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GetDesiredStateResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GetDesiredStateResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Desired", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Desired == nil { + m.Desired = &DesiredState{} + } + if err := m.Desired.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *WatchDesiredStateRequest) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: WatchDesiredStateRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: WatchDesiredStateRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AgentId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AgentId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AfterRevision", wireType) + } + m.AfterRevision = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.AfterRevision |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *WatchDesiredStateResponse) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: WatchDesiredStateResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: WatchDesiredStateResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Revision", wireType) + } + m.Revision = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Revision |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} diff --git a/gen/go/xtcp_treatment/operator.pb.go b/gen/go/xtcp_treatment/operator.pb.go new file mode 100644 index 000000000..ff9f7de58 --- /dev/null +++ b/gen/go/xtcp_treatment/operator.pb.go @@ -0,0 +1,824 @@ +// +// xTCP - treatment operator service +// +// The authenticated review surface. Every mutation carries a request_id; +// repeating one with the same body replays the original response, repeating +// it with a different body is refused. Reviews also fence on the experiment +// revision the operator last saw. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.12 +// protoc (unknown) +// source: xtcp_treatment/v1/operator.proto + +package xtcp_treatment + +import ( + _ "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ControlAction int32 + +const ( + ControlAction_CONTROL_ACTION_UNSPECIFIED ControlAction = 0 + ControlAction_CONTROL_ACTION_PAUSE ControlAction = 1 + ControlAction_CONTROL_ACTION_SUSPEND ControlAction = 2 + ControlAction_CONTROL_ACTION_RESUME ControlAction = 3 + ControlAction_CONTROL_ACTION_ROLLBACK ControlAction = 4 + ControlAction_CONTROL_ACTION_RETIRE ControlAction = 5 +) + +// Enum value maps for ControlAction. +var ( + ControlAction_name = map[int32]string{ + 0: "CONTROL_ACTION_UNSPECIFIED", + 1: "CONTROL_ACTION_PAUSE", + 2: "CONTROL_ACTION_SUSPEND", + 3: "CONTROL_ACTION_RESUME", + 4: "CONTROL_ACTION_ROLLBACK", + 5: "CONTROL_ACTION_RETIRE", + } + ControlAction_value = map[string]int32{ + "CONTROL_ACTION_UNSPECIFIED": 0, + "CONTROL_ACTION_PAUSE": 1, + "CONTROL_ACTION_SUSPEND": 2, + "CONTROL_ACTION_RESUME": 3, + "CONTROL_ACTION_ROLLBACK": 4, + "CONTROL_ACTION_RETIRE": 5, + } +) + +func (x ControlAction) Enum() *ControlAction { + p := new(ControlAction) + *p = x + return p +} + +func (x ControlAction) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ControlAction) Descriptor() protoreflect.EnumDescriptor { + return file_xtcp_treatment_v1_operator_proto_enumTypes[0].Descriptor() +} + +func (ControlAction) Type() protoreflect.EnumType { + return &file_xtcp_treatment_v1_operator_proto_enumTypes[0] +} + +func (x ControlAction) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ControlAction.Descriptor instead. +func (ControlAction) EnumDescriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_operator_proto_rawDescGZIP(), []int{0} +} + +type ProposeRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Experiment *Experiment `protobuf:"bytes,1,opt,name=experiment,proto3" json:"experiment,omitempty"` + RequestId string `protobuf:"bytes,2,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProposeRequest) Reset() { + *x = ProposeRequest{} + mi := &file_xtcp_treatment_v1_operator_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProposeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProposeRequest) ProtoMessage() {} + +func (x *ProposeRequest) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_v1_operator_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProposeRequest.ProtoReflect.Descriptor instead. +func (*ProposeRequest) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_operator_proto_rawDescGZIP(), []int{0} +} + +func (x *ProposeRequest) GetExperiment() *Experiment { + if x != nil { + return x.Experiment + } + return nil +} + +func (x *ProposeRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +type ProposeResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Experiment *Experiment `protobuf:"bytes,1,opt,name=experiment,proto3" json:"experiment,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProposeResponse) Reset() { + *x = ProposeResponse{} + mi := &file_xtcp_treatment_v1_operator_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProposeResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProposeResponse) ProtoMessage() {} + +func (x *ProposeResponse) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_v1_operator_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProposeResponse.ProtoReflect.Descriptor instead. +func (*ProposeResponse) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_operator_proto_rawDescGZIP(), []int{1} +} + +func (x *ProposeResponse) GetExperiment() *Experiment { + if x != nil { + return x.Experiment + } + return nil +} + +type AnalyzeRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ExperimentId string `protobuf:"bytes,1,opt,name=experiment_id,json=experimentId,proto3" json:"experiment_id,omitempty"` + ExpectedRevision uint64 `protobuf:"varint,2,opt,name=expected_revision,json=expectedRevision,proto3" json:"expected_revision,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AnalyzeRequest) Reset() { + *x = AnalyzeRequest{} + mi := &file_xtcp_treatment_v1_operator_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AnalyzeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AnalyzeRequest) ProtoMessage() {} + +func (x *AnalyzeRequest) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_v1_operator_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AnalyzeRequest.ProtoReflect.Descriptor instead. +func (*AnalyzeRequest) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_operator_proto_rawDescGZIP(), []int{2} +} + +func (x *AnalyzeRequest) GetExperimentId() string { + if x != nil { + return x.ExperimentId + } + return "" +} + +func (x *AnalyzeRequest) GetExpectedRevision() uint64 { + if x != nil { + return x.ExpectedRevision + } + return 0 +} + +type AnalyzeResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Evidence *Evidence `protobuf:"bytes,1,opt,name=evidence,proto3" json:"evidence,omitempty"` + Outcome string `protobuf:"bytes,2,opt,name=outcome,proto3" json:"outcome,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AnalyzeResponse) Reset() { + *x = AnalyzeResponse{} + mi := &file_xtcp_treatment_v1_operator_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AnalyzeResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AnalyzeResponse) ProtoMessage() {} + +func (x *AnalyzeResponse) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_v1_operator_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AnalyzeResponse.ProtoReflect.Descriptor instead. +func (*AnalyzeResponse) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_operator_proto_rawDescGZIP(), []int{3} +} + +func (x *AnalyzeResponse) GetEvidence() *Evidence { + if x != nil { + return x.Evidence + } + return nil +} + +func (x *AnalyzeResponse) GetOutcome() string { + if x != nil { + return x.Outcome + } + return "" +} + +type ReviewRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ExperimentId string `protobuf:"bytes,1,opt,name=experiment_id,json=experimentId,proto3" json:"experiment_id,omitempty"` + ExpectedRevision uint64 `protobuf:"varint,2,opt,name=expected_revision,json=expectedRevision,proto3" json:"expected_revision,omitempty"` + Approve bool `protobuf:"varint,3,opt,name=approve,proto3" json:"approve,omitempty"` + Reason string `protobuf:"bytes,4,opt,name=reason,proto3" json:"reason,omitempty"` + RequestId string `protobuf:"bytes,5,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + // Initial review uses baseline; subsequent reviews require fresh evidence. + Evidence *Evidence `protobuf:"bytes,6,opt,name=evidence,proto3" json:"evidence,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ReviewRequest) Reset() { + *x = ReviewRequest{} + mi := &file_xtcp_treatment_v1_operator_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ReviewRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReviewRequest) ProtoMessage() {} + +func (x *ReviewRequest) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_v1_operator_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReviewRequest.ProtoReflect.Descriptor instead. +func (*ReviewRequest) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_operator_proto_rawDescGZIP(), []int{4} +} + +func (x *ReviewRequest) GetExperimentId() string { + if x != nil { + return x.ExperimentId + } + return "" +} + +func (x *ReviewRequest) GetExpectedRevision() uint64 { + if x != nil { + return x.ExpectedRevision + } + return 0 +} + +func (x *ReviewRequest) GetApprove() bool { + if x != nil { + return x.Approve + } + return false +} + +func (x *ReviewRequest) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *ReviewRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *ReviewRequest) GetEvidence() *Evidence { + if x != nil { + return x.Evidence + } + return nil +} + +type ReviewResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Experiment *Experiment `protobuf:"bytes,1,opt,name=experiment,proto3" json:"experiment,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ReviewResponse) Reset() { + *x = ReviewResponse{} + mi := &file_xtcp_treatment_v1_operator_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ReviewResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReviewResponse) ProtoMessage() {} + +func (x *ReviewResponse) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_v1_operator_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReviewResponse.ProtoReflect.Descriptor instead. +func (*ReviewResponse) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_operator_proto_rawDescGZIP(), []int{5} +} + +func (x *ReviewResponse) GetExperiment() *Experiment { + if x != nil { + return x.Experiment + } + return nil +} + +type ControlRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ExperimentId string `protobuf:"bytes,1,opt,name=experiment_id,json=experimentId,proto3" json:"experiment_id,omitempty"` + AgentId string `protobuf:"bytes,2,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` + Location string `protobuf:"bytes,3,opt,name=location,proto3" json:"location,omitempty"` + Fleet bool `protobuf:"varint,4,opt,name=fleet,proto3" json:"fleet,omitempty"` + Action ControlAction `protobuf:"varint,5,opt,name=action,proto3,enum=xtcp_treatment.v1.ControlAction" json:"action,omitempty"` + Reason string `protobuf:"bytes,6,opt,name=reason,proto3" json:"reason,omitempty"` + RequestId string `protobuf:"bytes,7,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ControlRequest) Reset() { + *x = ControlRequest{} + mi := &file_xtcp_treatment_v1_operator_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ControlRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ControlRequest) ProtoMessage() {} + +func (x *ControlRequest) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_v1_operator_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ControlRequest.ProtoReflect.Descriptor instead. +func (*ControlRequest) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_operator_proto_rawDescGZIP(), []int{6} +} + +func (x *ControlRequest) GetExperimentId() string { + if x != nil { + return x.ExperimentId + } + return "" +} + +func (x *ControlRequest) GetAgentId() string { + if x != nil { + return x.AgentId + } + return "" +} + +func (x *ControlRequest) GetLocation() string { + if x != nil { + return x.Location + } + return "" +} + +func (x *ControlRequest) GetFleet() bool { + if x != nil { + return x.Fleet + } + return false +} + +func (x *ControlRequest) GetAction() ControlAction { + if x != nil { + return x.Action + } + return ControlAction_CONTROL_ACTION_UNSPECIFIED +} + +func (x *ControlRequest) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *ControlRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +type ControlResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + AffectedAgents uint32 `protobuf:"varint,1,opt,name=affected_agents,json=affectedAgents,proto3" json:"affected_agents,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ControlResponse) Reset() { + *x = ControlResponse{} + mi := &file_xtcp_treatment_v1_operator_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ControlResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ControlResponse) ProtoMessage() {} + +func (x *ControlResponse) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_v1_operator_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ControlResponse.ProtoReflect.Descriptor instead. +func (*ControlResponse) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_operator_proto_rawDescGZIP(), []int{7} +} + +func (x *ControlResponse) GetAffectedAgents() uint32 { + if x != nil { + return x.AffectedAgents + } + return 0 +} + +type InspectRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ExperimentId string `protobuf:"bytes,1,opt,name=experiment_id,json=experimentId,proto3" json:"experiment_id,omitempty"` + AgentId string `protobuf:"bytes,2,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InspectRequest) Reset() { + *x = InspectRequest{} + mi := &file_xtcp_treatment_v1_operator_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InspectRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InspectRequest) ProtoMessage() {} + +func (x *InspectRequest) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_v1_operator_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InspectRequest.ProtoReflect.Descriptor instead. +func (*InspectRequest) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_operator_proto_rawDescGZIP(), []int{8} +} + +func (x *InspectRequest) GetExperimentId() string { + if x != nil { + return x.ExperimentId + } + return "" +} + +func (x *InspectRequest) GetAgentId() string { + if x != nil { + return x.AgentId + } + return "" +} + +type InspectResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Experiments []*Experiment `protobuf:"bytes,1,rep,name=experiments,proto3" json:"experiments,omitempty"` + Desired []*DesiredState `protobuf:"bytes,2,rep,name=desired,proto3" json:"desired,omitempty"` + Observed []*CheckInRequest `protobuf:"bytes,3,rep,name=observed,proto3" json:"observed,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InspectResponse) Reset() { + *x = InspectResponse{} + mi := &file_xtcp_treatment_v1_operator_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InspectResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InspectResponse) ProtoMessage() {} + +func (x *InspectResponse) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_v1_operator_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InspectResponse.ProtoReflect.Descriptor instead. +func (*InspectResponse) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_operator_proto_rawDescGZIP(), []int{9} +} + +func (x *InspectResponse) GetExperiments() []*Experiment { + if x != nil { + return x.Experiments + } + return nil +} + +func (x *InspectResponse) GetDesired() []*DesiredState { + if x != nil { + return x.Desired + } + return nil +} + +func (x *InspectResponse) GetObserved() []*CheckInRequest { + if x != nil { + return x.Observed + } + return nil +} + +var File_xtcp_treatment_v1_operator_proto protoreflect.FileDescriptor + +const file_xtcp_treatment_v1_operator_proto_rawDesc = "" + + "\n" + + " xtcp_treatment/v1/operator.proto\x12\x11xtcp_treatment.v1\x1a\x1bbuf/validate/validate.proto\x1a\x1dxtcp_treatment/v1/agent.proto\x1a!xtcp_treatment/v1/treatment.proto\"\xbb\x02\n" + + "\x0eProposeRequest\x12E\n" + + "\n" + + "experiment\x18\x01 \x01(\v2\x1d.xtcp_treatment.v1.ExperimentB\x06\xbaH\x03\xc8\x01\x01R\n" + + "experiment\x12H\n" + + "\n" + + "request_id\x18\x02 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\trequestId:\x97\x01\xbaH\x93\x01\x1a\x90\x01\n" + + "\x19propose.request_id_suffix\x12Mrequest_id must not end with .result (reserved for the paired response event)\x1a$!this.request_id.endsWith('.result')\"P\n" + + "\x0fProposeResponse\x12=\n" + + "\n" + + "experiment\x18\x01 \x01(\v2\x1d.xtcp_treatment.v1.ExperimentR\n" + + "experiment\"\x8d\x01\n" + + "\x0eAnalyzeRequest\x12N\n" + + "\rexperiment_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\fexperimentId\x12+\n" + + "\x11expected_revision\x18\x02 \x01(\x04R\x10expectedRevision\"d\n" + + "\x0fAnalyzeResponse\x127\n" + + "\bevidence\x18\x01 \x01(\v2\x1b.xtcp_treatment.v1.EvidenceR\bevidence\x12\x18\n" + + "\aoutcome\x18\x02 \x01(\tR\aoutcome\"\xe6\x03\n" + + "\rReviewRequest\x12N\n" + + "\rexperiment_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\fexperimentId\x12+\n" + + "\x11expected_revision\x18\x02 \x01(\x04R\x10expectedRevision\x12\x18\n" + + "\aapprove\x18\x03 \x01(\bR\aapprove\x12\"\n" + + "\x06reason\x18\x04 \x01(\tB\n" + + "\xbaH\ar\x05\x10\x01\x18\x80 R\x06reason\x12H\n" + + "\n" + + "request_id\x18\x05 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\trequestId\x127\n" + + "\bevidence\x18\x06 \x01(\v2\x1b.xtcp_treatment.v1.EvidenceR\bevidence:\x96\x01\xbaH\x92\x01\x1a\x8f\x01\n" + + "\x18review.request_id_suffix\x12Mrequest_id must not end with .result (reserved for the paired response event)\x1a$!this.request_id.endsWith('.result')\"O\n" + + "\x0eReviewResponse\x12=\n" + + "\n" + + "experiment\x18\x01 \x01(\v2\x1d.xtcp_treatment.v1.ExperimentR\n" + + "experiment\"\xba\x06\n" + + "\x0eControlRequest\x12Q\n" + + "\rexperiment_id\x18\x01 \x01(\tB,\xbaH)\xd8\x01\x01r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\fexperimentId\x12G\n" + + "\bagent_id\x18\x02 \x01(\tB,\xbaH)\xd8\x01\x01r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\aagentId\x12H\n" + + "\blocation\x18\x03 \x01(\tB,\xbaH)\xd8\x01\x01r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\blocation\x12\x14\n" + + "\x05fleet\x18\x04 \x01(\bR\x05fleet\x12D\n" + + "\x06action\x18\x05 \x01(\x0e2 .xtcp_treatment.v1.ControlActionB\n" + + "\xbaH\a\x82\x01\x04\x10\x01 \x00R\x06action\x12\"\n" + + "\x06reason\x18\x06 \x01(\tB\n" + + "\xbaH\ar\x05\x10\x01\x18\x80 R\x06reason\x12H\n" + + "\n" + + "request_id\x18\a \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\trequestId:\xf7\x02\xbaH\xf3\x02\x1a\x90\x01\n" + + "\x19control.request_id_suffix\x12Mrequest_id must not end with .result (reserved for the paired response event)\x1a$!this.request_id.endsWith('.result')\x1a\xdd\x01\n" + + "\x14control.one_selector\x12Eexactly one of experiment_id, agent_id, location or fleet is required\x1a~(this.fleet ? 1 : 0) + (this.experiment_id != '' ? 1 : 0) + (this.agent_id != '' ? 1 : 0) + (this.location != '' ? 1 : 0) == 1\":\n" + + "\x0fControlResponse\x12'\n" + + "\x0faffected_agents\x18\x01 \x01(\rR\x0eaffectedAgents\"\xac\x01\n" + + "\x0eInspectRequest\x12Q\n" + + "\rexperiment_id\x18\x01 \x01(\tB,\xbaH)\xd8\x01\x01r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\fexperimentId\x12G\n" + + "\bagent_id\x18\x02 \x01(\tB,\xbaH)\xd8\x01\x01r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\aagentId\"\xcc\x01\n" + + "\x0fInspectResponse\x12?\n" + + "\vexperiments\x18\x01 \x03(\v2\x1d.xtcp_treatment.v1.ExperimentR\vexperiments\x129\n" + + "\adesired\x18\x02 \x03(\v2\x1f.xtcp_treatment.v1.DesiredStateR\adesired\x12=\n" + + "\bobserved\x18\x03 \x03(\v2!.xtcp_treatment.v1.CheckInRequestR\bobserved*\xb8\x01\n" + + "\rControlAction\x12\x1e\n" + + "\x1aCONTROL_ACTION_UNSPECIFIED\x10\x00\x12\x18\n" + + "\x14CONTROL_ACTION_PAUSE\x10\x01\x12\x1a\n" + + "\x16CONTROL_ACTION_SUSPEND\x10\x02\x12\x19\n" + + "\x15CONTROL_ACTION_RESUME\x10\x03\x12\x1b\n" + + "\x17CONTROL_ACTION_ROLLBACK\x10\x04\x12\x19\n" + + "\x15CONTROL_ACTION_RETIRE\x10\x052\xc6\x03\n" + + "\x0fOperatorService\x12V\n" + + "\aPropose\x12!.xtcp_treatment.v1.ProposeRequest\x1a\".xtcp_treatment.v1.ProposeResponse\"\x04\xd0\xf3\x18\x02\x12V\n" + + "\aAnalyze\x12!.xtcp_treatment.v1.AnalyzeRequest\x1a\".xtcp_treatment.v1.AnalyzeResponse\"\x04\xd0\xf3\x18\x02\x12S\n" + + "\x06Review\x12 .xtcp_treatment.v1.ReviewRequest\x1a!.xtcp_treatment.v1.ReviewResponse\"\x04\xd0\xf3\x18\x02\x12V\n" + + "\aControl\x12!.xtcp_treatment.v1.ControlRequest\x1a\".xtcp_treatment.v1.ControlResponse\"\x04\xd0\xf3\x18\x02\x12V\n" + + "\aInspect\x12!.xtcp_treatment.v1.InspectRequest\x1a\".xtcp_treatment.v1.InspectResponse\"\x04\xd0\xf3\x18\x02B\xbf\x01\n" + + "\x15com.xtcp_treatment.v1B\rOperatorProtoP\x01Z6github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment\xa2\x02\x03XXX\xaa\x02\x10XtcpTreatment.V1\xca\x02\x10XtcpTreatment\\V1\xe2\x02\x1cXtcpTreatment\\V1\\GPBMetadata\xea\x02\x11XtcpTreatment::V1b\x06proto3" + +var ( + file_xtcp_treatment_v1_operator_proto_rawDescOnce sync.Once + file_xtcp_treatment_v1_operator_proto_rawDescData []byte +) + +func file_xtcp_treatment_v1_operator_proto_rawDescGZIP() []byte { + file_xtcp_treatment_v1_operator_proto_rawDescOnce.Do(func() { + file_xtcp_treatment_v1_operator_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_xtcp_treatment_v1_operator_proto_rawDesc), len(file_xtcp_treatment_v1_operator_proto_rawDesc))) + }) + return file_xtcp_treatment_v1_operator_proto_rawDescData +} + +var file_xtcp_treatment_v1_operator_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_xtcp_treatment_v1_operator_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_xtcp_treatment_v1_operator_proto_goTypes = []any{ + (ControlAction)(0), // 0: xtcp_treatment.v1.ControlAction + (*ProposeRequest)(nil), // 1: xtcp_treatment.v1.ProposeRequest + (*ProposeResponse)(nil), // 2: xtcp_treatment.v1.ProposeResponse + (*AnalyzeRequest)(nil), // 3: xtcp_treatment.v1.AnalyzeRequest + (*AnalyzeResponse)(nil), // 4: xtcp_treatment.v1.AnalyzeResponse + (*ReviewRequest)(nil), // 5: xtcp_treatment.v1.ReviewRequest + (*ReviewResponse)(nil), // 6: xtcp_treatment.v1.ReviewResponse + (*ControlRequest)(nil), // 7: xtcp_treatment.v1.ControlRequest + (*ControlResponse)(nil), // 8: xtcp_treatment.v1.ControlResponse + (*InspectRequest)(nil), // 9: xtcp_treatment.v1.InspectRequest + (*InspectResponse)(nil), // 10: xtcp_treatment.v1.InspectResponse + (*Experiment)(nil), // 11: xtcp_treatment.v1.Experiment + (*Evidence)(nil), // 12: xtcp_treatment.v1.Evidence + (*DesiredState)(nil), // 13: xtcp_treatment.v1.DesiredState + (*CheckInRequest)(nil), // 14: xtcp_treatment.v1.CheckInRequest +} +var file_xtcp_treatment_v1_operator_proto_depIdxs = []int32{ + 11, // 0: xtcp_treatment.v1.ProposeRequest.experiment:type_name -> xtcp_treatment.v1.Experiment + 11, // 1: xtcp_treatment.v1.ProposeResponse.experiment:type_name -> xtcp_treatment.v1.Experiment + 12, // 2: xtcp_treatment.v1.AnalyzeResponse.evidence:type_name -> xtcp_treatment.v1.Evidence + 12, // 3: xtcp_treatment.v1.ReviewRequest.evidence:type_name -> xtcp_treatment.v1.Evidence + 11, // 4: xtcp_treatment.v1.ReviewResponse.experiment:type_name -> xtcp_treatment.v1.Experiment + 0, // 5: xtcp_treatment.v1.ControlRequest.action:type_name -> xtcp_treatment.v1.ControlAction + 11, // 6: xtcp_treatment.v1.InspectResponse.experiments:type_name -> xtcp_treatment.v1.Experiment + 13, // 7: xtcp_treatment.v1.InspectResponse.desired:type_name -> xtcp_treatment.v1.DesiredState + 14, // 8: xtcp_treatment.v1.InspectResponse.observed:type_name -> xtcp_treatment.v1.CheckInRequest + 1, // 9: xtcp_treatment.v1.OperatorService.Propose:input_type -> xtcp_treatment.v1.ProposeRequest + 3, // 10: xtcp_treatment.v1.OperatorService.Analyze:input_type -> xtcp_treatment.v1.AnalyzeRequest + 5, // 11: xtcp_treatment.v1.OperatorService.Review:input_type -> xtcp_treatment.v1.ReviewRequest + 7, // 12: xtcp_treatment.v1.OperatorService.Control:input_type -> xtcp_treatment.v1.ControlRequest + 9, // 13: xtcp_treatment.v1.OperatorService.Inspect:input_type -> xtcp_treatment.v1.InspectRequest + 2, // 14: xtcp_treatment.v1.OperatorService.Propose:output_type -> xtcp_treatment.v1.ProposeResponse + 4, // 15: xtcp_treatment.v1.OperatorService.Analyze:output_type -> xtcp_treatment.v1.AnalyzeResponse + 6, // 16: xtcp_treatment.v1.OperatorService.Review:output_type -> xtcp_treatment.v1.ReviewResponse + 8, // 17: xtcp_treatment.v1.OperatorService.Control:output_type -> xtcp_treatment.v1.ControlResponse + 10, // 18: xtcp_treatment.v1.OperatorService.Inspect:output_type -> xtcp_treatment.v1.InspectResponse + 14, // [14:19] is the sub-list for method output_type + 9, // [9:14] is the sub-list for method input_type + 9, // [9:9] is the sub-list for extension type_name + 9, // [9:9] is the sub-list for extension extendee + 0, // [0:9] is the sub-list for field type_name +} + +func init() { file_xtcp_treatment_v1_operator_proto_init() } +func file_xtcp_treatment_v1_operator_proto_init() { + if File_xtcp_treatment_v1_operator_proto != nil { + return + } + file_xtcp_treatment_v1_agent_proto_init() + file_xtcp_treatment_v1_treatment_proto_init() + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_xtcp_treatment_v1_operator_proto_rawDesc), len(file_xtcp_treatment_v1_operator_proto_rawDesc)), + NumEnums: 1, + NumMessages: 10, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_xtcp_treatment_v1_operator_proto_goTypes, + DependencyIndexes: file_xtcp_treatment_v1_operator_proto_depIdxs, + EnumInfos: file_xtcp_treatment_v1_operator_proto_enumTypes, + MessageInfos: file_xtcp_treatment_v1_operator_proto_msgTypes, + }.Build() + File_xtcp_treatment_v1_operator_proto = out.File + file_xtcp_treatment_v1_operator_proto_goTypes = nil + file_xtcp_treatment_v1_operator_proto_depIdxs = nil +} diff --git a/gen/go/xtcp_treatment/operator.pb.gw.go b/gen/go/xtcp_treatment/operator.pb.gw.go new file mode 100644 index 000000000..7da6d7302 --- /dev/null +++ b/gen/go/xtcp_treatment/operator.pb.gw.go @@ -0,0 +1,421 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: xtcp_treatment/v1/operator.proto + +/* +Package xtcp_treatment is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package xtcp_treatment + +import ( + "context" + "errors" + "io" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/grpc-ecosystem/grpc-gateway/v2/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +// Suppress "imported and not used" errors +var ( + _ codes.Code + _ io.Reader + _ status.Status + _ = errors.New + _ = runtime.String + _ = utilities.NewDoubleArray + _ = metadata.Join +) + +func request_OperatorService_Propose_0(ctx context.Context, marshaler runtime.Marshaler, client OperatorServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ProposeRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.Propose(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_OperatorService_Propose_0(ctx context.Context, marshaler runtime.Marshaler, server OperatorServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ProposeRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.Propose(ctx, &protoReq) + return msg, metadata, err +} + +func request_OperatorService_Analyze_0(ctx context.Context, marshaler runtime.Marshaler, client OperatorServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq AnalyzeRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.Analyze(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_OperatorService_Analyze_0(ctx context.Context, marshaler runtime.Marshaler, server OperatorServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq AnalyzeRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.Analyze(ctx, &protoReq) + return msg, metadata, err +} + +func request_OperatorService_Review_0(ctx context.Context, marshaler runtime.Marshaler, client OperatorServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ReviewRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.Review(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_OperatorService_Review_0(ctx context.Context, marshaler runtime.Marshaler, server OperatorServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ReviewRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.Review(ctx, &protoReq) + return msg, metadata, err +} + +func request_OperatorService_Control_0(ctx context.Context, marshaler runtime.Marshaler, client OperatorServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ControlRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.Control(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_OperatorService_Control_0(ctx context.Context, marshaler runtime.Marshaler, server OperatorServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ControlRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.Control(ctx, &protoReq) + return msg, metadata, err +} + +func request_OperatorService_Inspect_0(ctx context.Context, marshaler runtime.Marshaler, client OperatorServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq InspectRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.Inspect(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_OperatorService_Inspect_0(ctx context.Context, marshaler runtime.Marshaler, server OperatorServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq InspectRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.Inspect(ctx, &protoReq) + return msg, metadata, err +} + +// RegisterOperatorServiceHandlerServer registers the http handlers for service OperatorService to "mux". +// UnaryRPC :call OperatorServiceServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterOperatorServiceHandlerFromEndpoint instead. +// GRPC interceptors will not work for this type of registration. To use interceptors, you must use the "runtime.WithMiddlewares" option in the "runtime.NewServeMux" call. +func RegisterOperatorServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server OperatorServiceServer) error { + mux.Handle(http.MethodPost, pattern_OperatorService_Propose_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/xtcp_treatment.v1.OperatorService/Propose", runtime.WithHTTPPathPattern("/xtcp_treatment.v1.OperatorService/Propose")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_OperatorService_Propose_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_OperatorService_Propose_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_OperatorService_Analyze_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/xtcp_treatment.v1.OperatorService/Analyze", runtime.WithHTTPPathPattern("/xtcp_treatment.v1.OperatorService/Analyze")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_OperatorService_Analyze_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_OperatorService_Analyze_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_OperatorService_Review_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/xtcp_treatment.v1.OperatorService/Review", runtime.WithHTTPPathPattern("/xtcp_treatment.v1.OperatorService/Review")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_OperatorService_Review_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_OperatorService_Review_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_OperatorService_Control_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/xtcp_treatment.v1.OperatorService/Control", runtime.WithHTTPPathPattern("/xtcp_treatment.v1.OperatorService/Control")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_OperatorService_Control_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_OperatorService_Control_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_OperatorService_Inspect_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/xtcp_treatment.v1.OperatorService/Inspect", runtime.WithHTTPPathPattern("/xtcp_treatment.v1.OperatorService/Inspect")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_OperatorService_Inspect_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_OperatorService_Inspect_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + + return nil +} + +// RegisterOperatorServiceHandlerFromEndpoint is same as RegisterOperatorServiceHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterOperatorServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.NewClient(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Errorf("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Errorf("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + return RegisterOperatorServiceHandler(ctx, mux, conn) +} + +// RegisterOperatorServiceHandler registers the http handlers for service OperatorService to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterOperatorServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterOperatorServiceHandlerClient(ctx, mux, NewOperatorServiceClient(conn)) +} + +// RegisterOperatorServiceHandlerClient registers the http handlers for service OperatorService +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "OperatorServiceClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "OperatorServiceClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "OperatorServiceClient" to call the correct interceptors. This client ignores the HTTP middlewares. +func RegisterOperatorServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client OperatorServiceClient) error { + mux.Handle(http.MethodPost, pattern_OperatorService_Propose_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/xtcp_treatment.v1.OperatorService/Propose", runtime.WithHTTPPathPattern("/xtcp_treatment.v1.OperatorService/Propose")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_OperatorService_Propose_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_OperatorService_Propose_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_OperatorService_Analyze_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/xtcp_treatment.v1.OperatorService/Analyze", runtime.WithHTTPPathPattern("/xtcp_treatment.v1.OperatorService/Analyze")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_OperatorService_Analyze_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_OperatorService_Analyze_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_OperatorService_Review_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/xtcp_treatment.v1.OperatorService/Review", runtime.WithHTTPPathPattern("/xtcp_treatment.v1.OperatorService/Review")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_OperatorService_Review_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_OperatorService_Review_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_OperatorService_Control_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/xtcp_treatment.v1.OperatorService/Control", runtime.WithHTTPPathPattern("/xtcp_treatment.v1.OperatorService/Control")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_OperatorService_Control_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_OperatorService_Control_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_OperatorService_Inspect_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/xtcp_treatment.v1.OperatorService/Inspect", runtime.WithHTTPPathPattern("/xtcp_treatment.v1.OperatorService/Inspect")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_OperatorService_Inspect_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_OperatorService_Inspect_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + return nil +} + +var ( + pattern_OperatorService_Propose_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"xtcp_treatment.v1.OperatorService", "Propose"}, "")) + pattern_OperatorService_Analyze_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"xtcp_treatment.v1.OperatorService", "Analyze"}, "")) + pattern_OperatorService_Review_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"xtcp_treatment.v1.OperatorService", "Review"}, "")) + pattern_OperatorService_Control_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"xtcp_treatment.v1.OperatorService", "Control"}, "")) + pattern_OperatorService_Inspect_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"xtcp_treatment.v1.OperatorService", "Inspect"}, "")) +) + +var ( + forward_OperatorService_Propose_0 = runtime.ForwardResponseMessage + forward_OperatorService_Analyze_0 = runtime.ForwardResponseMessage + forward_OperatorService_Review_0 = runtime.ForwardResponseMessage + forward_OperatorService_Control_0 = runtime.ForwardResponseMessage + forward_OperatorService_Inspect_0 = runtime.ForwardResponseMessage +) diff --git a/gen/go/xtcp_controller/controller_grpc.pb.go b/gen/go/xtcp_treatment/operator_grpc.pb.go similarity index 51% rename from gen/go/xtcp_controller/controller_grpc.pb.go rename to gen/go/xtcp_treatment/operator_grpc.pb.go index a222a138c..ef9606a04 100644 --- a/gen/go/xtcp_controller/controller_grpc.pb.go +++ b/gen/go/xtcp_treatment/operator_grpc.pb.go @@ -1,10 +1,18 @@ +// +// xTCP - treatment operator service +// +// The authenticated review surface. Every mutation carries a request_id; +// repeating one with the same body replays the original response, repeating +// it with a different body is refused. Reviews also fence on the experiment +// revision the operator last saw. + // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.6.2 -// - protoc v7.36.2 -// source: xtcp/controller/v1/controller.proto +// - protoc (unknown) +// source: xtcp_treatment/v1/operator.proto -package xtcp_controller +package xtcp_treatment import ( context "context" @@ -19,201 +27,11 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - AgentControlService_CheckIn_FullMethodName = "/xtcp.controller.v1.AgentControlService/CheckIn" - AgentControlService_GetDesiredState_FullMethodName = "/xtcp.controller.v1.AgentControlService/GetDesiredState" - AgentControlService_WatchDesiredState_FullMethodName = "/xtcp.controller.v1.AgentControlService/WatchDesiredState" -) - -// AgentControlServiceClient is the client API for AgentControlService service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -// -// All inbound messages are validated by the controller application layer. -// IDs are bounded ASCII identifiers; address selectors are canonical host CIDRs. -// Limits, required fields and contextual constraints are in pkg/treatment/validate.go. -type AgentControlServiceClient interface { - CheckIn(ctx context.Context, in *CheckInRequest, opts ...grpc.CallOption) (*CheckInResponse, error) - GetDesiredState(ctx context.Context, in *GetDesiredStateRequest, opts ...grpc.CallOption) (*GetDesiredStateResponse, error) - WatchDesiredState(ctx context.Context, in *WatchDesiredStateRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[WatchDesiredStateResponse], error) -} - -type agentControlServiceClient struct { - cc grpc.ClientConnInterface -} - -func NewAgentControlServiceClient(cc grpc.ClientConnInterface) AgentControlServiceClient { - return &agentControlServiceClient{cc} -} - -func (c *agentControlServiceClient) CheckIn(ctx context.Context, in *CheckInRequest, opts ...grpc.CallOption) (*CheckInResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(CheckInResponse) - err := c.cc.Invoke(ctx, AgentControlService_CheckIn_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *agentControlServiceClient) GetDesiredState(ctx context.Context, in *GetDesiredStateRequest, opts ...grpc.CallOption) (*GetDesiredStateResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(GetDesiredStateResponse) - err := c.cc.Invoke(ctx, AgentControlService_GetDesiredState_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *agentControlServiceClient) WatchDesiredState(ctx context.Context, in *WatchDesiredStateRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[WatchDesiredStateResponse], error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - stream, err := c.cc.NewStream(ctx, &AgentControlService_ServiceDesc.Streams[0], AgentControlService_WatchDesiredState_FullMethodName, cOpts...) - if err != nil { - return nil, err - } - x := &grpc.GenericClientStream[WatchDesiredStateRequest, WatchDesiredStateResponse]{ClientStream: stream} - if err := x.ClientStream.SendMsg(in); err != nil { - return nil, err - } - if err := x.ClientStream.CloseSend(); err != nil { - return nil, err - } - return x, nil -} - -// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. -type AgentControlService_WatchDesiredStateClient = grpc.ServerStreamingClient[WatchDesiredStateResponse] - -// AgentControlServiceServer is the server API for AgentControlService service. -// All implementations must embed UnimplementedAgentControlServiceServer -// for forward compatibility. -// -// All inbound messages are validated by the controller application layer. -// IDs are bounded ASCII identifiers; address selectors are canonical host CIDRs. -// Limits, required fields and contextual constraints are in pkg/treatment/validate.go. -type AgentControlServiceServer interface { - CheckIn(context.Context, *CheckInRequest) (*CheckInResponse, error) - GetDesiredState(context.Context, *GetDesiredStateRequest) (*GetDesiredStateResponse, error) - WatchDesiredState(*WatchDesiredStateRequest, grpc.ServerStreamingServer[WatchDesiredStateResponse]) error - mustEmbedUnimplementedAgentControlServiceServer() -} - -// UnimplementedAgentControlServiceServer must be embedded to have -// forward compatible implementations. -// -// NOTE: this should be embedded by value instead of pointer to avoid a nil -// pointer dereference when methods are called. -type UnimplementedAgentControlServiceServer struct{} - -func (UnimplementedAgentControlServiceServer) CheckIn(context.Context, *CheckInRequest) (*CheckInResponse, error) { - return nil, status.Error(codes.Unimplemented, "method CheckIn not implemented") -} -func (UnimplementedAgentControlServiceServer) GetDesiredState(context.Context, *GetDesiredStateRequest) (*GetDesiredStateResponse, error) { - return nil, status.Error(codes.Unimplemented, "method GetDesiredState not implemented") -} -func (UnimplementedAgentControlServiceServer) WatchDesiredState(*WatchDesiredStateRequest, grpc.ServerStreamingServer[WatchDesiredStateResponse]) error { - return status.Error(codes.Unimplemented, "method WatchDesiredState not implemented") -} -func (UnimplementedAgentControlServiceServer) mustEmbedUnimplementedAgentControlServiceServer() {} -func (UnimplementedAgentControlServiceServer) testEmbeddedByValue() {} - -// UnsafeAgentControlServiceServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to AgentControlServiceServer will -// result in compilation errors. -type UnsafeAgentControlServiceServer interface { - mustEmbedUnimplementedAgentControlServiceServer() -} - -func RegisterAgentControlServiceServer(s grpc.ServiceRegistrar, srv AgentControlServiceServer) { - // If the following call panics, it indicates UnimplementedAgentControlServiceServer was - // embedded by pointer and is nil. This will cause panics if an - // unimplemented method is ever invoked, so we test this at initialization - // time to prevent it from happening at runtime later due to I/O. - if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { - t.testEmbeddedByValue() - } - s.RegisterService(&AgentControlService_ServiceDesc, srv) -} - -func _AgentControlService_CheckIn_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(CheckInRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(AgentControlServiceServer).CheckIn(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: AgentControlService_CheckIn_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(AgentControlServiceServer).CheckIn(ctx, req.(*CheckInRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _AgentControlService_GetDesiredState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetDesiredStateRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(AgentControlServiceServer).GetDesiredState(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: AgentControlService_GetDesiredState_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(AgentControlServiceServer).GetDesiredState(ctx, req.(*GetDesiredStateRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _AgentControlService_WatchDesiredState_Handler(srv interface{}, stream grpc.ServerStream) error { - m := new(WatchDesiredStateRequest) - if err := stream.RecvMsg(m); err != nil { - return err - } - return srv.(AgentControlServiceServer).WatchDesiredState(m, &grpc.GenericServerStream[WatchDesiredStateRequest, WatchDesiredStateResponse]{ServerStream: stream}) -} - -// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. -type AgentControlService_WatchDesiredStateServer = grpc.ServerStreamingServer[WatchDesiredStateResponse] - -// AgentControlService_ServiceDesc is the grpc.ServiceDesc for AgentControlService service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var AgentControlService_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "xtcp.controller.v1.AgentControlService", - HandlerType: (*AgentControlServiceServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "CheckIn", - Handler: _AgentControlService_CheckIn_Handler, - }, - { - MethodName: "GetDesiredState", - Handler: _AgentControlService_GetDesiredState_Handler, - }, - }, - Streams: []grpc.StreamDesc{ - { - StreamName: "WatchDesiredState", - Handler: _AgentControlService_WatchDesiredState_Handler, - ServerStreams: true, - }, - }, - Metadata: "xtcp/controller/v1/controller.proto", -} - -const ( - OperatorService_Propose_FullMethodName = "/xtcp.controller.v1.OperatorService/Propose" - OperatorService_Analyze_FullMethodName = "/xtcp.controller.v1.OperatorService/Analyze" - OperatorService_Review_FullMethodName = "/xtcp.controller.v1.OperatorService/Review" - OperatorService_Control_FullMethodName = "/xtcp.controller.v1.OperatorService/Control" - OperatorService_Inspect_FullMethodName = "/xtcp.controller.v1.OperatorService/Inspect" + OperatorService_Propose_FullMethodName = "/xtcp_treatment.v1.OperatorService/Propose" + OperatorService_Analyze_FullMethodName = "/xtcp_treatment.v1.OperatorService/Analyze" + OperatorService_Review_FullMethodName = "/xtcp_treatment.v1.OperatorService/Review" + OperatorService_Control_FullMethodName = "/xtcp_treatment.v1.OperatorService/Control" + OperatorService_Inspect_FullMethodName = "/xtcp_treatment.v1.OperatorService/Inspect" ) // OperatorServiceClient is the client API for OperatorService service. @@ -434,7 +252,7 @@ func _OperatorService_Inspect_Handler(srv interface{}, ctx context.Context, dec // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var OperatorService_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "xtcp.controller.v1.OperatorService", + ServiceName: "xtcp_treatment.v1.OperatorService", HandlerType: (*OperatorServiceServer)(nil), Methods: []grpc.MethodDesc{ { @@ -459,5 +277,5 @@ var OperatorService_ServiceDesc = grpc.ServiceDesc{ }, }, Streams: []grpc.StreamDesc{}, - Metadata: "xtcp/controller/v1/controller.proto", + Metadata: "xtcp_treatment/v1/operator.proto", } diff --git a/gen/go/xtcp_treatment/operator_vtproto.pb.go b/gen/go/xtcp_treatment/operator_vtproto.pb.go new file mode 100644 index 000000000..c88aa9ec3 --- /dev/null +++ b/gen/go/xtcp_treatment/operator_vtproto.pb.go @@ -0,0 +1,2099 @@ +// Code generated by protoc-gen-go-vtproto. DO NOT EDIT. +// protoc-gen-go-vtproto version: (devel) +// source: xtcp_treatment/v1/operator.proto + +package xtcp_treatment + +import ( + fmt "fmt" + protohelpers "github.com/planetscale/vtprotobuf/protohelpers" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + io "io" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +func (m *ProposeRequest) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ProposeRequest) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *ProposeRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.RequestId) > 0 { + i -= len(m.RequestId) + copy(dAtA[i:], m.RequestId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.RequestId))) + i-- + dAtA[i] = 0x12 + } + if m.Experiment != nil { + size, err := m.Experiment.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *ProposeResponse) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ProposeResponse) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *ProposeResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.Experiment != nil { + size, err := m.Experiment.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *AnalyzeRequest) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AnalyzeRequest) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *AnalyzeRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.ExpectedRevision != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.ExpectedRevision)) + i-- + dAtA[i] = 0x10 + } + if len(m.ExperimentId) > 0 { + i -= len(m.ExperimentId) + copy(dAtA[i:], m.ExperimentId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.ExperimentId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *AnalyzeResponse) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AnalyzeResponse) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *AnalyzeResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.Outcome) > 0 { + i -= len(m.Outcome) + copy(dAtA[i:], m.Outcome) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Outcome))) + i-- + dAtA[i] = 0x12 + } + if m.Evidence != nil { + size, err := m.Evidence.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *ReviewRequest) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ReviewRequest) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *ReviewRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.Evidence != nil { + size, err := m.Evidence.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x32 + } + if len(m.RequestId) > 0 { + i -= len(m.RequestId) + copy(dAtA[i:], m.RequestId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.RequestId))) + i-- + dAtA[i] = 0x2a + } + if len(m.Reason) > 0 { + i -= len(m.Reason) + copy(dAtA[i:], m.Reason) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Reason))) + i-- + dAtA[i] = 0x22 + } + if m.Approve { + i-- + if m.Approve { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x18 + } + if m.ExpectedRevision != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.ExpectedRevision)) + i-- + dAtA[i] = 0x10 + } + if len(m.ExperimentId) > 0 { + i -= len(m.ExperimentId) + copy(dAtA[i:], m.ExperimentId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.ExperimentId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *ReviewResponse) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ReviewResponse) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *ReviewResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.Experiment != nil { + size, err := m.Experiment.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *ControlRequest) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ControlRequest) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *ControlRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.RequestId) > 0 { + i -= len(m.RequestId) + copy(dAtA[i:], m.RequestId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.RequestId))) + i-- + dAtA[i] = 0x3a + } + if len(m.Reason) > 0 { + i -= len(m.Reason) + copy(dAtA[i:], m.Reason) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Reason))) + i-- + dAtA[i] = 0x32 + } + if m.Action != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Action)) + i-- + dAtA[i] = 0x28 + } + if m.Fleet { + i-- + if m.Fleet { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x20 + } + if len(m.Location) > 0 { + i -= len(m.Location) + copy(dAtA[i:], m.Location) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Location))) + i-- + dAtA[i] = 0x1a + } + if len(m.AgentId) > 0 { + i -= len(m.AgentId) + copy(dAtA[i:], m.AgentId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.AgentId))) + i-- + dAtA[i] = 0x12 + } + if len(m.ExperimentId) > 0 { + i -= len(m.ExperimentId) + copy(dAtA[i:], m.ExperimentId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.ExperimentId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *ControlResponse) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ControlResponse) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *ControlResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.AffectedAgents != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.AffectedAgents)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *InspectRequest) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *InspectRequest) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *InspectRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.AgentId) > 0 { + i -= len(m.AgentId) + copy(dAtA[i:], m.AgentId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.AgentId))) + i-- + dAtA[i] = 0x12 + } + if len(m.ExperimentId) > 0 { + i -= len(m.ExperimentId) + copy(dAtA[i:], m.ExperimentId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.ExperimentId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *InspectResponse) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *InspectResponse) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *InspectResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.Observed) > 0 { + for iNdEx := len(m.Observed) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.Observed[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x1a + } + } + if len(m.Desired) > 0 { + for iNdEx := len(m.Desired) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.Desired[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x12 + } + } + if len(m.Experiments) > 0 { + for iNdEx := len(m.Experiments) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.Experiments[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *ProposeRequest) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Experiment != nil { + l = m.Experiment.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.RequestId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *ProposeResponse) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Experiment != nil { + l = m.Experiment.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *AnalyzeRequest) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ExperimentId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.ExpectedRevision != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.ExpectedRevision)) + } + n += len(m.unknownFields) + return n +} + +func (m *AnalyzeResponse) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Evidence != nil { + l = m.Evidence.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.Outcome) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *ReviewRequest) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ExperimentId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.ExpectedRevision != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.ExpectedRevision)) + } + if m.Approve { + n += 2 + } + l = len(m.Reason) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.RequestId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Evidence != nil { + l = m.Evidence.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *ReviewResponse) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Experiment != nil { + l = m.Experiment.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *ControlRequest) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ExperimentId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.AgentId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.Location) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Fleet { + n += 2 + } + if m.Action != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Action)) + } + l = len(m.Reason) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.RequestId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *ControlResponse) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.AffectedAgents != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.AffectedAgents)) + } + n += len(m.unknownFields) + return n +} + +func (m *InspectRequest) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ExperimentId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.AgentId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *InspectResponse) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Experiments) > 0 { + for _, e := range m.Experiments { + l = e.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + } + if len(m.Desired) > 0 { + for _, e := range m.Desired { + l = e.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + } + if len(m.Observed) > 0 { + for _, e := range m.Observed { + l = e.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + } + n += len(m.unknownFields) + return n +} + +func (m *ProposeRequest) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ProposeRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ProposeRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Experiment", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Experiment == nil { + m.Experiment = &Experiment{} + } + if err := m.Experiment.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RequestId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RequestId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ProposeResponse) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ProposeResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ProposeResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Experiment", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Experiment == nil { + m.Experiment = &Experiment{} + } + if err := m.Experiment.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *AnalyzeRequest) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AnalyzeRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AnalyzeRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExperimentId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ExperimentId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ExpectedRevision", wireType) + } + m.ExpectedRevision = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ExpectedRevision |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *AnalyzeResponse) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AnalyzeResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AnalyzeResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Evidence", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Evidence == nil { + m.Evidence = &Evidence{} + } + if err := m.Evidence.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Outcome", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Outcome = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ReviewRequest) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ReviewRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ReviewRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExperimentId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ExperimentId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ExpectedRevision", wireType) + } + m.ExpectedRevision = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ExpectedRevision |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Approve", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Approve = bool(v != 0) + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Reason", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Reason = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RequestId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RequestId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Evidence", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Evidence == nil { + m.Evidence = &Evidence{} + } + if err := m.Evidence.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ReviewResponse) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ReviewResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ReviewResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Experiment", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Experiment == nil { + m.Experiment = &Experiment{} + } + if err := m.Experiment.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ControlRequest) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ControlRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ControlRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExperimentId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ExperimentId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AgentId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AgentId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Location", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Location = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Fleet", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Fleet = bool(v != 0) + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Action", wireType) + } + m.Action = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Action |= ControlAction(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Reason", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Reason = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RequestId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RequestId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ControlResponse) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ControlResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ControlResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AffectedAgents", wireType) + } + m.AffectedAgents = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.AffectedAgents |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *InspectRequest) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: InspectRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: InspectRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExperimentId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ExperimentId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AgentId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AgentId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *InspectResponse) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: InspectResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: InspectResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Experiments", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Experiments = append(m.Experiments, &Experiment{}) + if err := m.Experiments[len(m.Experiments)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Desired", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Desired = append(m.Desired, &DesiredState{}) + if err := m.Desired[len(m.Desired)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Observed", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Observed = append(m.Observed, &CheckInRequest{}) + if err := m.Observed[len(m.Observed)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} diff --git a/gen/go/xtcp_treatment/treatment.pb.go b/gen/go/xtcp_treatment/treatment.pb.go new file mode 100644 index 000000000..119041b0e --- /dev/null +++ b/gen/go/xtcp_treatment/treatment.pb.go @@ -0,0 +1,1772 @@ +// +// xTCP - treatment +// +// Shared message types for the TCP route treatment control plane: what a +// treatment is, how it is assigned to an agent, what an agent reports back, +// and how an experiment over a cohort of agents is described and evidenced. +// +// Wire contract only. The controller and the agent work on Go domain types +// and convert at the gRPC boundary; nothing here is a storage schema. +// +// Validation: every client-controlled field carries a buf.validate rule that +// mirrors the bound in pkg/treatment/validate.go, so a malformed request is +// rejected before any handler runs. The Go validators remain authoritative +// for contextual rules that need more than one message (duplicate assignment +// IDs, overlapping destinations, cohort/baseline agreement). +// +// See docs/tcp-controller.md. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.12 +// protoc (unknown) +// source: xtcp_treatment/v1/treatment.proto + +package xtcp_treatment + +import ( + _ "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + descriptorpb "google.golang.org/protobuf/types/descriptorpb" + durationpb "google.golang.org/protobuf/types/known/durationpb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Role is the certificate class an RPC requires. The server resolves the +// caller's SPIFFE identity once per call and compares it with this option; +// a method without a declaration is refused for everyone (fail closed). +type Role int32 + +const ( + Role_ROLE_UNSPECIFIED Role = 0 + // spiffe://xtcp/agent//; may only speak for its own agent_id. + Role_ROLE_AGENT Role = 1 + // spiffe://xtcp/operator/; recorded as the audit actor. + Role_ROLE_OPERATOR Role = 2 +) + +// Enum value maps for Role. +var ( + Role_name = map[int32]string{ + 0: "ROLE_UNSPECIFIED", + 1: "ROLE_AGENT", + 2: "ROLE_OPERATOR", + } + Role_value = map[string]int32{ + "ROLE_UNSPECIFIED": 0, + "ROLE_AGENT": 1, + "ROLE_OPERATOR": 2, + } +) + +func (x Role) Enum() *Role { + p := new(Role) + *p = x + return p +} + +func (x Role) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Role) Descriptor() protoreflect.EnumDescriptor { + return file_xtcp_treatment_v1_treatment_proto_enumTypes[0].Descriptor() +} + +func (Role) Type() protoreflect.EnumType { + return &file_xtcp_treatment_v1_treatment_proto_enumTypes[0] +} + +func (x Role) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Role.Descriptor instead. +func (Role) EnumDescriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_treatment_proto_rawDescGZIP(), []int{0} +} + +type AssignmentMode int32 + +const ( + AssignmentMode_ASSIGNMENT_MODE_UNSPECIFIED AssignmentMode = 0 + AssignmentMode_ASSIGNMENT_MODE_TRIAL AssignmentMode = 1 + AssignmentMode_ASSIGNMENT_MODE_STABLE AssignmentMode = 2 + AssignmentMode_ASSIGNMENT_MODE_SUSPENDED AssignmentMode = 3 + AssignmentMode_ASSIGNMENT_MODE_RETIRED AssignmentMode = 4 +) + +// Enum value maps for AssignmentMode. +var ( + AssignmentMode_name = map[int32]string{ + 0: "ASSIGNMENT_MODE_UNSPECIFIED", + 1: "ASSIGNMENT_MODE_TRIAL", + 2: "ASSIGNMENT_MODE_STABLE", + 3: "ASSIGNMENT_MODE_SUSPENDED", + 4: "ASSIGNMENT_MODE_RETIRED", + } + AssignmentMode_value = map[string]int32{ + "ASSIGNMENT_MODE_UNSPECIFIED": 0, + "ASSIGNMENT_MODE_TRIAL": 1, + "ASSIGNMENT_MODE_STABLE": 2, + "ASSIGNMENT_MODE_SUSPENDED": 3, + "ASSIGNMENT_MODE_RETIRED": 4, + } +) + +func (x AssignmentMode) Enum() *AssignmentMode { + p := new(AssignmentMode) + *p = x + return p +} + +func (x AssignmentMode) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (AssignmentMode) Descriptor() protoreflect.EnumDescriptor { + return file_xtcp_treatment_v1_treatment_proto_enumTypes[1].Descriptor() +} + +func (AssignmentMode) Type() protoreflect.EnumType { + return &file_xtcp_treatment_v1_treatment_proto_enumTypes[1] +} + +func (x AssignmentMode) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use AssignmentMode.Descriptor instead. +func (AssignmentMode) EnumDescriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_treatment_proto_rawDescGZIP(), []int{1} +} + +type ApplyStatus int32 + +const ( + ApplyStatus_APPLY_STATUS_UNSPECIFIED ApplyStatus = 0 + ApplyStatus_APPLY_STATUS_PENDING ApplyStatus = 1 + ApplyStatus_APPLY_STATUS_VERIFIED ApplyStatus = 2 + ApplyStatus_APPLY_STATUS_REMOVED ApplyStatus = 3 + ApplyStatus_APPLY_STATUS_EXPIRED ApplyStatus = 4 + ApplyStatus_APPLY_STATUS_FAILED ApplyStatus = 5 +) + +// Enum value maps for ApplyStatus. +var ( + ApplyStatus_name = map[int32]string{ + 0: "APPLY_STATUS_UNSPECIFIED", + 1: "APPLY_STATUS_PENDING", + 2: "APPLY_STATUS_VERIFIED", + 3: "APPLY_STATUS_REMOVED", + 4: "APPLY_STATUS_EXPIRED", + 5: "APPLY_STATUS_FAILED", + } + ApplyStatus_value = map[string]int32{ + "APPLY_STATUS_UNSPECIFIED": 0, + "APPLY_STATUS_PENDING": 1, + "APPLY_STATUS_VERIFIED": 2, + "APPLY_STATUS_REMOVED": 3, + "APPLY_STATUS_EXPIRED": 4, + "APPLY_STATUS_FAILED": 5, + } +) + +func (x ApplyStatus) Enum() *ApplyStatus { + p := new(ApplyStatus) + *p = x + return p +} + +func (x ApplyStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ApplyStatus) Descriptor() protoreflect.EnumDescriptor { + return file_xtcp_treatment_v1_treatment_proto_enumTypes[2].Descriptor() +} + +func (ApplyStatus) Type() protoreflect.EnumType { + return &file_xtcp_treatment_v1_treatment_proto_enumTypes[2] +} + +func (x ApplyStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ApplyStatus.Descriptor instead. +func (ApplyStatus) EnumDescriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_treatment_proto_rawDescGZIP(), []int{2} +} + +type ExperimentStatus int32 + +const ( + ExperimentStatus_EXPERIMENT_STATUS_UNSPECIFIED ExperimentStatus = 0 + ExperimentStatus_EXPERIMENT_STATUS_PROPOSED ExperimentStatus = 1 + ExperimentStatus_EXPERIMENT_STATUS_RUNNING ExperimentStatus = 2 + ExperimentStatus_EXPERIMENT_STATUS_PAUSED ExperimentStatus = 3 + ExperimentStatus_EXPERIMENT_STATUS_SUSPENDED ExperimentStatus = 4 + ExperimentStatus_EXPERIMENT_STATUS_COMPLETE ExperimentStatus = 5 + ExperimentStatus_EXPERIMENT_STATUS_RETIRED ExperimentStatus = 6 + ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK ExperimentStatus = 7 +) + +// Enum value maps for ExperimentStatus. +var ( + ExperimentStatus_name = map[int32]string{ + 0: "EXPERIMENT_STATUS_UNSPECIFIED", + 1: "EXPERIMENT_STATUS_PROPOSED", + 2: "EXPERIMENT_STATUS_RUNNING", + 3: "EXPERIMENT_STATUS_PAUSED", + 4: "EXPERIMENT_STATUS_SUSPENDED", + 5: "EXPERIMENT_STATUS_COMPLETE", + 6: "EXPERIMENT_STATUS_RETIRED", + 7: "EXPERIMENT_STATUS_ROLLED_BACK", + } + ExperimentStatus_value = map[string]int32{ + "EXPERIMENT_STATUS_UNSPECIFIED": 0, + "EXPERIMENT_STATUS_PROPOSED": 1, + "EXPERIMENT_STATUS_RUNNING": 2, + "EXPERIMENT_STATUS_PAUSED": 3, + "EXPERIMENT_STATUS_SUSPENDED": 4, + "EXPERIMENT_STATUS_COMPLETE": 5, + "EXPERIMENT_STATUS_RETIRED": 6, + "EXPERIMENT_STATUS_ROLLED_BACK": 7, + } +) + +func (x ExperimentStatus) Enum() *ExperimentStatus { + p := new(ExperimentStatus) + *p = x + return p +} + +func (x ExperimentStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ExperimentStatus) Descriptor() protoreflect.EnumDescriptor { + return file_xtcp_treatment_v1_treatment_proto_enumTypes[3].Descriptor() +} + +func (ExperimentStatus) Type() protoreflect.EnumType { + return &file_xtcp_treatment_v1_treatment_proto_enumTypes[3] +} + +func (x ExperimentStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ExperimentStatus.Descriptor instead. +func (ExperimentStatus) EnumDescriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_treatment_proto_rawDescGZIP(), []int{3} +} + +// TCPSettings are the per-route TCP parameters a treatment installs. Every +// field is optional; at least one must be present. +type TCPSettings struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Kernel algorithm name (TCP_CA_NAME_MAX is 16 including NUL). + CongestionControl *string `protobuf:"bytes,1,opt,name=congestion_control,json=congestionControl,proto3,oneof" json:"congestion_control,omitempty"` + CongestionLock bool `protobuf:"varint,2,opt,name=congestion_lock,json=congestionLock,proto3" json:"congestion_lock,omitempty"` + RtoMinMs *uint32 `protobuf:"varint,3,opt,name=rto_min_ms,json=rtoMinMs,proto3,oneof" json:"rto_min_ms,omitempty"` + InitialCongestionWindow *uint32 `protobuf:"varint,4,opt,name=initial_congestion_window,json=initialCongestionWindow,proto3,oneof" json:"initial_congestion_window,omitempty"` + InitialReceiveWindow *uint32 `protobuf:"varint,5,opt,name=initial_receive_window,json=initialReceiveWindow,proto3,oneof" json:"initial_receive_window,omitempty"` + QuickAck *bool `protobuf:"varint,6,opt,name=quick_ack,json=quickAck,proto3,oneof" json:"quick_ack,omitempty"` + Ecn *bool `protobuf:"varint,7,opt,name=ecn,proto3,oneof" json:"ecn,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TCPSettings) Reset() { + *x = TCPSettings{} + mi := &file_xtcp_treatment_v1_treatment_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TCPSettings) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TCPSettings) ProtoMessage() {} + +func (x *TCPSettings) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_v1_treatment_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TCPSettings.ProtoReflect.Descriptor instead. +func (*TCPSettings) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_treatment_proto_rawDescGZIP(), []int{0} +} + +func (x *TCPSettings) GetCongestionControl() string { + if x != nil && x.CongestionControl != nil { + return *x.CongestionControl + } + return "" +} + +func (x *TCPSettings) GetCongestionLock() bool { + if x != nil { + return x.CongestionLock + } + return false +} + +func (x *TCPSettings) GetRtoMinMs() uint32 { + if x != nil && x.RtoMinMs != nil { + return *x.RtoMinMs + } + return 0 +} + +func (x *TCPSettings) GetInitialCongestionWindow() uint32 { + if x != nil && x.InitialCongestionWindow != nil { + return *x.InitialCongestionWindow + } + return 0 +} + +func (x *TCPSettings) GetInitialReceiveWindow() uint32 { + if x != nil && x.InitialReceiveWindow != nil { + return *x.InitialReceiveWindow + } + return 0 +} + +func (x *TCPSettings) GetQuickAck() bool { + if x != nil && x.QuickAck != nil { + return *x.QuickAck + } + return false +} + +func (x *TCPSettings) GetEcn() bool { + if x != nil && x.Ecn != nil { + return *x.Ecn + } + return false +} + +type Treatment struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Revision uint64 `protobuf:"varint,2,opt,name=revision,proto3" json:"revision,omitempty"` + // Canonical unicast host prefix (/32 or /128). Canonical form, unicast and + // not-loopback are checked by treatment.ValidateTreatment. + Destination string `protobuf:"bytes,3,opt,name=destination,proto3" json:"destination,omitempty"` + Settings *TCPSettings `protobuf:"bytes,4,opt,name=settings,proto3" json:"settings,omitempty"` + EcnFabricVerified bool `protobuf:"varint,5,opt,name=ecn_fabric_verified,json=ecnFabricVerified,proto3" json:"ecn_fabric_verified,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Treatment) Reset() { + *x = Treatment{} + mi := &file_xtcp_treatment_v1_treatment_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Treatment) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Treatment) ProtoMessage() {} + +func (x *Treatment) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_v1_treatment_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Treatment.ProtoReflect.Descriptor instead. +func (*Treatment) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_treatment_proto_rawDescGZIP(), []int{1} +} + +func (x *Treatment) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Treatment) GetRevision() uint64 { + if x != nil { + return x.Revision + } + return 0 +} + +func (x *Treatment) GetDestination() string { + if x != nil { + return x.Destination + } + return "" +} + +func (x *Treatment) GetSettings() *TCPSettings { + if x != nil { + return x.Settings + } + return nil +} + +func (x *Treatment) GetEcnFabricVerified() bool { + if x != nil { + return x.EcnFabricVerified + } + return false +} + +type Assignment struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ExperimentId string `protobuf:"bytes,2,opt,name=experiment_id,json=experimentId,proto3" json:"experiment_id,omitempty"` + Treatment *Treatment `protobuf:"bytes,3,opt,name=treatment,proto3" json:"treatment,omitempty"` + Mode AssignmentMode `protobuf:"varint,4,opt,name=mode,proto3,enum=xtcp_treatment.v1.AssignmentMode" json:"mode,omitempty"` + LeaseExpiresAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=lease_expires_at,json=leaseExpiresAt,proto3" json:"lease_expires_at,omitempty"` + // Previous approved stable treatment, restored on trial expiry/rollback. + Predecessor *Treatment `protobuf:"bytes,6,opt,name=predecessor,proto3" json:"predecessor,omitempty"` + // Increment only on an explicitly approved reactivation, not lease renewal. + Generation uint64 `protobuf:"varint,7,opt,name=generation,proto3" json:"generation,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Assignment) Reset() { + *x = Assignment{} + mi := &file_xtcp_treatment_v1_treatment_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Assignment) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Assignment) ProtoMessage() {} + +func (x *Assignment) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_v1_treatment_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Assignment.ProtoReflect.Descriptor instead. +func (*Assignment) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_treatment_proto_rawDescGZIP(), []int{2} +} + +func (x *Assignment) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Assignment) GetExperimentId() string { + if x != nil { + return x.ExperimentId + } + return "" +} + +func (x *Assignment) GetTreatment() *Treatment { + if x != nil { + return x.Treatment + } + return nil +} + +func (x *Assignment) GetMode() AssignmentMode { + if x != nil { + return x.Mode + } + return AssignmentMode_ASSIGNMENT_MODE_UNSPECIFIED +} + +func (x *Assignment) GetLeaseExpiresAt() *timestamppb.Timestamp { + if x != nil { + return x.LeaseExpiresAt + } + return nil +} + +func (x *Assignment) GetPredecessor() *Treatment { + if x != nil { + return x.Predecessor + } + return nil +} + +func (x *Assignment) GetGeneration() uint64 { + if x != nil { + return x.Generation + } + return 0 +} + +// DesiredState is the complete revisioned snapshot an agent reconciles to. +// Duplicate assignment IDs and overlapping destinations are rejected by +// treatment.ValidateDesired. +type DesiredState struct { + state protoimpl.MessageState `protogen:"open.v1"` + AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` + Revision uint64 `protobuf:"varint,2,opt,name=revision,proto3" json:"revision,omitempty"` + Suspended bool `protobuf:"varint,3,opt,name=suspended,proto3" json:"suspended,omitempty"` + Assignments []*Assignment `protobuf:"bytes,4,rep,name=assignments,proto3" json:"assignments,omitempty"` + IssuedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=issued_at,json=issuedAt,proto3" json:"issued_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DesiredState) Reset() { + *x = DesiredState{} + mi := &file_xtcp_treatment_v1_treatment_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DesiredState) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DesiredState) ProtoMessage() {} + +func (x *DesiredState) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_v1_treatment_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DesiredState.ProtoReflect.Descriptor instead. +func (*DesiredState) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_treatment_proto_rawDescGZIP(), []int{3} +} + +func (x *DesiredState) GetAgentId() string { + if x != nil { + return x.AgentId + } + return "" +} + +func (x *DesiredState) GetRevision() uint64 { + if x != nil { + return x.Revision + } + return 0 +} + +func (x *DesiredState) GetSuspended() bool { + if x != nil { + return x.Suspended + } + return false +} + +func (x *DesiredState) GetAssignments() []*Assignment { + if x != nil { + return x.Assignments + } + return nil +} + +func (x *DesiredState) GetIssuedAt() *timestamppb.Timestamp { + if x != nil { + return x.IssuedAt + } + return nil +} + +type Capabilities struct { + state protoimpl.MessageState `protogen:"open.v1"` + BootId string `protobuf:"bytes,1,opt,name=boot_id,json=bootId,proto3" json:"boot_id,omitempty"` + KernelRelease string `protobuf:"bytes,2,opt,name=kernel_release,json=kernelRelease,proto3" json:"kernel_release,omitempty"` + CongestionAlgorithms []string `protobuf:"bytes,3,rep,name=congestion_algorithms,json=congestionAlgorithms,proto3" json:"congestion_algorithms,omitempty"` + SimpleHostRouting bool `protobuf:"varint,4,opt,name=simple_host_routing,json=simpleHostRouting,proto3" json:"simple_host_routing,omitempty"` + ActuatorEnabled bool `protobuf:"varint,5,opt,name=actuator_enabled,json=actuatorEnabled,proto3" json:"actuator_enabled,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Capabilities) Reset() { + *x = Capabilities{} + mi := &file_xtcp_treatment_v1_treatment_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Capabilities) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Capabilities) ProtoMessage() {} + +func (x *Capabilities) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_v1_treatment_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Capabilities.ProtoReflect.Descriptor instead. +func (*Capabilities) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_treatment_proto_rawDescGZIP(), []int{4} +} + +func (x *Capabilities) GetBootId() string { + if x != nil { + return x.BootId + } + return "" +} + +func (x *Capabilities) GetKernelRelease() string { + if x != nil { + return x.KernelRelease + } + return "" +} + +func (x *Capabilities) GetCongestionAlgorithms() []string { + if x != nil { + return x.CongestionAlgorithms + } + return nil +} + +func (x *Capabilities) GetSimpleHostRouting() bool { + if x != nil { + return x.SimpleHostRouting + } + return false +} + +func (x *Capabilities) GetActuatorEnabled() bool { + if x != nil { + return x.ActuatorEnabled + } + return false +} + +// ApplicationRecord is the agent's account of one assignment. Agents report +// these on check-in with installed_route and installed_boot_id cleared. +type ApplicationRecord struct { + state protoimpl.MessageState `protogen:"open.v1"` + Assignment *Assignment `protobuf:"bytes,1,opt,name=assignment,proto3" json:"assignment,omitempty"` + DesiredRevision uint64 `protobuf:"varint,2,opt,name=desired_revision,json=desiredRevision,proto3" json:"desired_revision,omitempty"` + Status ApplyStatus `protobuf:"varint,3,opt,name=status,proto3,enum=xtcp_treatment.v1.ApplyStatus" json:"status,omitempty"` + ReceivedAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=received_at,json=receivedAt,proto3" json:"received_at,omitempty"` + AttemptedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=attempted_at,json=attemptedAt,proto3" json:"attempted_at,omitempty"` + AppliedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=applied_at,json=appliedAt,proto3" json:"applied_at,omitempty"` + VerifiedAt *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=verified_at,json=verifiedAt,proto3" json:"verified_at,omitempty"` + RemovedAt *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=removed_at,json=removedAt,proto3" json:"removed_at,omitempty"` + Error string `protobuf:"bytes,9,opt,name=error,proto3" json:"error,omitempty"` + // Locally generated canonical netlink route body, never accepted from RPC. + InstalledRoute []byte `protobuf:"bytes,10,opt,name=installed_route,json=installedRoute,proto3" json:"installed_route,omitempty"` + InstalledBootId string `protobuf:"bytes,11,opt,name=installed_boot_id,json=installedBootId,proto3" json:"installed_boot_id,omitempty"` + ExpiredLatched bool `protobuf:"varint,12,opt,name=expired_latched,json=expiredLatched,proto3" json:"expired_latched,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ApplicationRecord) Reset() { + *x = ApplicationRecord{} + mi := &file_xtcp_treatment_v1_treatment_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ApplicationRecord) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ApplicationRecord) ProtoMessage() {} + +func (x *ApplicationRecord) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_v1_treatment_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ApplicationRecord.ProtoReflect.Descriptor instead. +func (*ApplicationRecord) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_treatment_proto_rawDescGZIP(), []int{5} +} + +func (x *ApplicationRecord) GetAssignment() *Assignment { + if x != nil { + return x.Assignment + } + return nil +} + +func (x *ApplicationRecord) GetDesiredRevision() uint64 { + if x != nil { + return x.DesiredRevision + } + return 0 +} + +func (x *ApplicationRecord) GetStatus() ApplyStatus { + if x != nil { + return x.Status + } + return ApplyStatus_APPLY_STATUS_UNSPECIFIED +} + +func (x *ApplicationRecord) GetReceivedAt() *timestamppb.Timestamp { + if x != nil { + return x.ReceivedAt + } + return nil +} + +func (x *ApplicationRecord) GetAttemptedAt() *timestamppb.Timestamp { + if x != nil { + return x.AttemptedAt + } + return nil +} + +func (x *ApplicationRecord) GetAppliedAt() *timestamppb.Timestamp { + if x != nil { + return x.AppliedAt + } + return nil +} + +func (x *ApplicationRecord) GetVerifiedAt() *timestamppb.Timestamp { + if x != nil { + return x.VerifiedAt + } + return nil +} + +func (x *ApplicationRecord) GetRemovedAt() *timestamppb.Timestamp { + if x != nil { + return x.RemovedAt + } + return nil +} + +func (x *ApplicationRecord) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +func (x *ApplicationRecord) GetInstalledRoute() []byte { + if x != nil { + return x.InstalledRoute + } + return nil +} + +func (x *ApplicationRecord) GetInstalledBootId() string { + if x != nil { + return x.InstalledBootId + } + return "" +} + +func (x *ApplicationRecord) GetExpiredLatched() bool { + if x != nil { + return x.ExpiredLatched + } + return false +} + +// LocalState is the agent's journal snapshot. It is a file format for the +// host-side journal and the local tooling, not an RPC payload. +type LocalState struct { + state protoimpl.MessageState `protogen:"open.v1"` + Desired *DesiredState `protobuf:"bytes,1,opt,name=desired,proto3" json:"desired,omitempty"` + LocallySuspended bool `protobuf:"varint,2,opt,name=locally_suspended,json=locallySuspended,proto3" json:"locally_suspended,omitempty"` + SuspensionReason string `protobuf:"bytes,3,opt,name=suspension_reason,json=suspensionReason,proto3" json:"suspension_reason,omitempty"` + Records []*ApplicationRecord `protobuf:"bytes,4,rep,name=records,proto3" json:"records,omitempty"` + UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LocalState) Reset() { + *x = LocalState{} + mi := &file_xtcp_treatment_v1_treatment_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LocalState) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LocalState) ProtoMessage() {} + +func (x *LocalState) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_v1_treatment_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LocalState.ProtoReflect.Descriptor instead. +func (*LocalState) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_treatment_proto_rawDescGZIP(), []int{6} +} + +func (x *LocalState) GetDesired() *DesiredState { + if x != nil { + return x.Desired + } + return nil +} + +func (x *LocalState) GetLocallySuspended() bool { + if x != nil { + return x.LocallySuspended + } + return false +} + +func (x *LocalState) GetSuspensionReason() string { + if x != nil { + return x.SuspensionReason + } + return "" +} + +func (x *LocalState) GetRecords() []*ApplicationRecord { + if x != nil { + return x.Records + } + return nil +} + +func (x *LocalState) GetUpdatedAt() *timestamppb.Timestamp { + if x != nil { + return x.UpdatedAt + } + return nil +} + +type HostEvidence struct { + state protoimpl.MessageState `protogen:"open.v1"` + AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` + Sockets uint64 `protobuf:"varint,2,opt,name=sockets,proto3" json:"sockets,omitempty"` + BytesAcked uint64 `protobuf:"varint,3,opt,name=bytes_acked,json=bytesAcked,proto3" json:"bytes_acked,omitempty"` + GoodputBps float64 `protobuf:"fixed64,4,opt,name=goodput_bps,json=goodputBps,proto3" json:"goodput_bps,omitempty"` + RetransmissionRatio float64 `protobuf:"fixed64,5,opt,name=retransmission_ratio,json=retransmissionRatio,proto3" json:"retransmission_ratio,omitempty"` + RttP95Ms float64 `protobuf:"fixed64,6,opt,name=rtt_p95_ms,json=rttP95Ms,proto3" json:"rtt_p95_ms,omitempty"` + ExposedSockets uint64 `protobuf:"varint,7,opt,name=exposed_sockets,json=exposedSockets,proto3" json:"exposed_sockets,omitempty"` + CollectionHealthy bool `protobuf:"varint,8,opt,name=collection_healthy,json=collectionHealthy,proto3" json:"collection_healthy,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HostEvidence) Reset() { + *x = HostEvidence{} + mi := &file_xtcp_treatment_v1_treatment_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HostEvidence) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HostEvidence) ProtoMessage() {} + +func (x *HostEvidence) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_v1_treatment_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HostEvidence.ProtoReflect.Descriptor instead. +func (*HostEvidence) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_treatment_proto_rawDescGZIP(), []int{7} +} + +func (x *HostEvidence) GetAgentId() string { + if x != nil { + return x.AgentId + } + return "" +} + +func (x *HostEvidence) GetSockets() uint64 { + if x != nil { + return x.Sockets + } + return 0 +} + +func (x *HostEvidence) GetBytesAcked() uint64 { + if x != nil { + return x.BytesAcked + } + return 0 +} + +func (x *HostEvidence) GetGoodputBps() float64 { + if x != nil { + return x.GoodputBps + } + return 0 +} + +func (x *HostEvidence) GetRetransmissionRatio() float64 { + if x != nil { + return x.RetransmissionRatio + } + return 0 +} + +func (x *HostEvidence) GetRttP95Ms() float64 { + if x != nil { + return x.RttP95Ms + } + return 0 +} + +func (x *HostEvidence) GetExposedSockets() uint64 { + if x != nil { + return x.ExposedSockets + } + return 0 +} + +func (x *HostEvidence) GetCollectionHealthy() bool { + if x != nil { + return x.CollectionHealthy + } + return false +} + +type Evidence struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + AnalyzedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=analyzed_at,json=analyzedAt,proto3" json:"analyzed_at,omitempty"` + WindowStart *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=window_start,json=windowStart,proto3" json:"window_start,omitempty"` + WindowEnd *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=window_end,json=windowEnd,proto3" json:"window_end,omitempty"` + QueryVersion string `protobuf:"bytes,5,opt,name=query_version,json=queryVersion,proto3" json:"query_version,omitempty"` + EnrichmentVersion string `protobuf:"bytes,6,opt,name=enrichment_version,json=enrichmentVersion,proto3" json:"enrichment_version,omitempty"` + Hosts []*HostEvidence `protobuf:"bytes,7,rep,name=hosts,proto3" json:"hosts,omitempty"` + // SHA-256 of the complete analysis response this summary was derived from. + ArtifactSha256 string `protobuf:"bytes,8,opt,name=artifact_sha256,json=artifactSha256,proto3" json:"artifact_sha256,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Evidence) Reset() { + *x = Evidence{} + mi := &file_xtcp_treatment_v1_treatment_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Evidence) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Evidence) ProtoMessage() {} + +func (x *Evidence) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_v1_treatment_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Evidence.ProtoReflect.Descriptor instead. +func (*Evidence) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_treatment_proto_rawDescGZIP(), []int{8} +} + +func (x *Evidence) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Evidence) GetAnalyzedAt() *timestamppb.Timestamp { + if x != nil { + return x.AnalyzedAt + } + return nil +} + +func (x *Evidence) GetWindowStart() *timestamppb.Timestamp { + if x != nil { + return x.WindowStart + } + return nil +} + +func (x *Evidence) GetWindowEnd() *timestamppb.Timestamp { + if x != nil { + return x.WindowEnd + } + return nil +} + +func (x *Evidence) GetQueryVersion() string { + if x != nil { + return x.QueryVersion + } + return "" +} + +func (x *Evidence) GetEnrichmentVersion() string { + if x != nil { + return x.EnrichmentVersion + } + return "" +} + +func (x *Evidence) GetHosts() []*HostEvidence { + if x != nil { + return x.Hosts + } + return nil +} + +func (x *Evidence) GetArtifactSha256() string { + if x != nil { + return x.ArtifactSha256 + } + return "" +} + +type ExperimentPolicy struct { + state protoimpl.MessageState `protogen:"open.v1"` + MinimumObservation *durationpb.Duration `protobuf:"bytes,1,opt,name=minimum_observation,json=minimumObservation,proto3" json:"minimum_observation,omitempty"` + MaximumPhase *durationpb.Duration `protobuf:"bytes,2,opt,name=maximum_phase,json=maximumPhase,proto3" json:"maximum_phase,omitempty"` + MinimumSocketsPerHost uint64 `protobuf:"varint,3,opt,name=minimum_sockets_per_host,json=minimumSocketsPerHost,proto3" json:"minimum_sockets_per_host,omitempty"` + MinimumBytesPerHost uint64 `protobuf:"varint,4,opt,name=minimum_bytes_per_host,json=minimumBytesPerHost,proto3" json:"minimum_bytes_per_host,omitempty"` + MinimumTreatedHosts uint32 `protobuf:"varint,5,opt,name=minimum_treated_hosts,json=minimumTreatedHosts,proto3" json:"minimum_treated_hosts,omitempty"` + MinimumImprovement float64 `protobuf:"fixed64,6,opt,name=minimum_improvement,json=minimumImprovement,proto3" json:"minimum_improvement,omitempty"` + MaximumRetransmissionIncrease float64 `protobuf:"fixed64,7,opt,name=maximum_retransmission_increase,json=maximumRetransmissionIncrease,proto3" json:"maximum_retransmission_increase,omitempty"` + MaximumRttIncrease float64 `protobuf:"fixed64,8,opt,name=maximum_rtt_increase,json=maximumRttIncrease,proto3" json:"maximum_rtt_increase,omitempty"` + MaximumEvidenceAge *durationpb.Duration `protobuf:"bytes,9,opt,name=maximum_evidence_age,json=maximumEvidenceAge,proto3" json:"maximum_evidence_age,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExperimentPolicy) Reset() { + *x = ExperimentPolicy{} + mi := &file_xtcp_treatment_v1_treatment_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExperimentPolicy) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExperimentPolicy) ProtoMessage() {} + +func (x *ExperimentPolicy) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_v1_treatment_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExperimentPolicy.ProtoReflect.Descriptor instead. +func (*ExperimentPolicy) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_treatment_proto_rawDescGZIP(), []int{9} +} + +func (x *ExperimentPolicy) GetMinimumObservation() *durationpb.Duration { + if x != nil { + return x.MinimumObservation + } + return nil +} + +func (x *ExperimentPolicy) GetMaximumPhase() *durationpb.Duration { + if x != nil { + return x.MaximumPhase + } + return nil +} + +func (x *ExperimentPolicy) GetMinimumSocketsPerHost() uint64 { + if x != nil { + return x.MinimumSocketsPerHost + } + return 0 +} + +func (x *ExperimentPolicy) GetMinimumBytesPerHost() uint64 { + if x != nil { + return x.MinimumBytesPerHost + } + return 0 +} + +func (x *ExperimentPolicy) GetMinimumTreatedHosts() uint32 { + if x != nil { + return x.MinimumTreatedHosts + } + return 0 +} + +func (x *ExperimentPolicy) GetMinimumImprovement() float64 { + if x != nil { + return x.MinimumImprovement + } + return 0 +} + +func (x *ExperimentPolicy) GetMaximumRetransmissionIncrease() float64 { + if x != nil { + return x.MaximumRetransmissionIncrease + } + return 0 +} + +func (x *ExperimentPolicy) GetMaximumRttIncrease() float64 { + if x != nil { + return x.MaximumRttIncrease + } + return 0 +} + +func (x *ExperimentPolicy) GetMaximumEvidenceAge() *durationpb.Duration { + if x != nil { + return x.MaximumEvidenceAge + } + return nil +} + +type Phase struct { + state protoimpl.MessageState `protogen:"open.v1"` + TargetHosts uint32 `protobuf:"varint,1,opt,name=target_hosts,json=targetHosts,proto3" json:"target_hosts,omitempty"` + IssuedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=issued_at,json=issuedAt,proto3" json:"issued_at,omitempty"` + ObservationStartedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=observation_started_at,json=observationStartedAt,proto3" json:"observation_started_at,omitempty"` + EvaluatedAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=evaluated_at,json=evaluatedAt,proto3" json:"evaluated_at,omitempty"` + Evidence *Evidence `protobuf:"bytes,5,opt,name=evidence,proto3" json:"evidence,omitempty"` + Outcome string `protobuf:"bytes,6,opt,name=outcome,proto3" json:"outcome,omitempty"` + Reviewer string `protobuf:"bytes,7,opt,name=reviewer,proto3" json:"reviewer,omitempty"` + Reason string `protobuf:"bytes,8,opt,name=reason,proto3" json:"reason,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Phase) Reset() { + *x = Phase{} + mi := &file_xtcp_treatment_v1_treatment_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Phase) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Phase) ProtoMessage() {} + +func (x *Phase) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_v1_treatment_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Phase.ProtoReflect.Descriptor instead. +func (*Phase) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_treatment_proto_rawDescGZIP(), []int{10} +} + +func (x *Phase) GetTargetHosts() uint32 { + if x != nil { + return x.TargetHosts + } + return 0 +} + +func (x *Phase) GetIssuedAt() *timestamppb.Timestamp { + if x != nil { + return x.IssuedAt + } + return nil +} + +func (x *Phase) GetObservationStartedAt() *timestamppb.Timestamp { + if x != nil { + return x.ObservationStartedAt + } + return nil +} + +func (x *Phase) GetEvaluatedAt() *timestamppb.Timestamp { + if x != nil { + return x.EvaluatedAt + } + return nil +} + +func (x *Phase) GetEvidence() *Evidence { + if x != nil { + return x.Evidence + } + return nil +} + +func (x *Phase) GetOutcome() string { + if x != nil { + return x.Outcome + } + return "" +} + +func (x *Phase) GetReviewer() string { + if x != nil { + return x.Reviewer + } + return "" +} + +func (x *Phase) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +type Experiment struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Location string `protobuf:"bytes,2,opt,name=location,proto3" json:"location,omitempty"` + Hypothesis string `protobuf:"bytes,3,opt,name=hypothesis,proto3" json:"hypothesis,omitempty"` + Treatment *Treatment `protobuf:"bytes,4,opt,name=treatment,proto3" json:"treatment,omitempty"` + Policy *ExperimentPolicy `protobuf:"bytes,5,opt,name=policy,proto3" json:"policy,omitempty"` + Baseline *Evidence `protobuf:"bytes,6,opt,name=baseline,proto3" json:"baseline,omitempty"` + // Randomised host order; phases treat a growing prefix of it. + CohortOrder []string `protobuf:"bytes,7,rep,name=cohort_order,json=cohortOrder,proto3" json:"cohort_order,omitempty"` + Phases []*Phase `protobuf:"bytes,8,rep,name=phases,proto3" json:"phases,omitempty"` + Status ExperimentStatus `protobuf:"varint,9,opt,name=status,proto3,enum=xtcp_treatment.v1.ExperimentStatus" json:"status,omitempty"` + Revision uint64 `protobuf:"varint,10,opt,name=revision,proto3" json:"revision,omitempty"` + ProposedAt *timestamppb.Timestamp `protobuf:"bytes,11,opt,name=proposed_at,json=proposedAt,proto3" json:"proposed_at,omitempty"` + LatestEvidence *Evidence `protobuf:"bytes,12,opt,name=latest_evidence,json=latestEvidence,proto3" json:"latest_evidence,omitempty"` + LatestOutcome string `protobuf:"bytes,13,opt,name=latest_outcome,json=latestOutcome,proto3" json:"latest_outcome,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Experiment) Reset() { + *x = Experiment{} + mi := &file_xtcp_treatment_v1_treatment_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Experiment) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Experiment) ProtoMessage() {} + +func (x *Experiment) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_v1_treatment_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Experiment.ProtoReflect.Descriptor instead. +func (*Experiment) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_treatment_proto_rawDescGZIP(), []int{11} +} + +func (x *Experiment) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Experiment) GetLocation() string { + if x != nil { + return x.Location + } + return "" +} + +func (x *Experiment) GetHypothesis() string { + if x != nil { + return x.Hypothesis + } + return "" +} + +func (x *Experiment) GetTreatment() *Treatment { + if x != nil { + return x.Treatment + } + return nil +} + +func (x *Experiment) GetPolicy() *ExperimentPolicy { + if x != nil { + return x.Policy + } + return nil +} + +func (x *Experiment) GetBaseline() *Evidence { + if x != nil { + return x.Baseline + } + return nil +} + +func (x *Experiment) GetCohortOrder() []string { + if x != nil { + return x.CohortOrder + } + return nil +} + +func (x *Experiment) GetPhases() []*Phase { + if x != nil { + return x.Phases + } + return nil +} + +func (x *Experiment) GetStatus() ExperimentStatus { + if x != nil { + return x.Status + } + return ExperimentStatus_EXPERIMENT_STATUS_UNSPECIFIED +} + +func (x *Experiment) GetRevision() uint64 { + if x != nil { + return x.Revision + } + return 0 +} + +func (x *Experiment) GetProposedAt() *timestamppb.Timestamp { + if x != nil { + return x.ProposedAt + } + return nil +} + +func (x *Experiment) GetLatestEvidence() *Evidence { + if x != nil { + return x.LatestEvidence + } + return nil +} + +func (x *Experiment) GetLatestOutcome() string { + if x != nil { + return x.LatestOutcome + } + return "" +} + +var file_xtcp_treatment_v1_treatment_proto_extTypes = []protoimpl.ExtensionInfo{ + { + ExtendedType: (*descriptorpb.MethodOptions)(nil), + ExtensionType: (*Role)(nil), + Field: 51002, + Name: "xtcp_treatment.v1.role", + Tag: "varint,51002,opt,name=role,enum=xtcp_treatment.v1.Role", + Filename: "xtcp_treatment/v1/treatment.proto", + }, +} + +// Extension fields to descriptorpb.MethodOptions. +var ( + // Field number from the 50000-99999 range reserved for internal use; + // 51001 is xtcp_auth.v1.access. + // + // optional xtcp_treatment.v1.Role role = 51002; + E_Role = &file_xtcp_treatment_v1_treatment_proto_extTypes[0] +) + +var File_xtcp_treatment_v1_treatment_proto protoreflect.FileDescriptor + +const file_xtcp_treatment_v1_treatment_proto_rawDesc = "" + + "\n" + + "!xtcp_treatment/v1/treatment.proto\x12\x11xtcp_treatment.v1\x1a\x1bbuf/validate/validate.proto\x1a google/protobuf/descriptor.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\x8d\a\n" + + "\vTCPSettings\x12Y\n" + + "\x12congestion_control\x18\x01 \x01(\tB%\xbaH\"r \x18\x0f2\x1c^[a-zA-Z0-9][a-zA-Z0-9_.-]*$H\x00R\x11congestionControl\x88\x01\x01\x12'\n" + + "\x0fcongestion_lock\x18\x02 \x01(\bR\x0econgestionLock\x12.\n" + + "\n" + + "rto_min_ms\x18\x03 \x01(\rB\v\xbaH\b*\x06\x18\xc0\xa9\a(\x01H\x01R\brtoMinMs\x88\x01\x01\x12K\n" + + "\x19initial_congestion_window\x18\x04 \x01(\rB\n" + + "\xbaH\a*\x05\x18\x80\b(\x01H\x02R\x17initialCongestionWindow\x88\x01\x01\x12E\n" + + "\x16initial_receive_window\x18\x05 \x01(\rB\n" + + "\xbaH\a*\x05\x18\x80\b(\x01H\x03R\x14initialReceiveWindow\x88\x01\x01\x12 \n" + + "\tquick_ack\x18\x06 \x01(\bH\x04R\bquickAck\x88\x01\x01\x12\x15\n" + + "\x03ecn\x18\a \x01(\bH\x05R\x03ecn\x88\x01\x01:\x87\x03\xbaH\x83\x03\x1a\xf3\x01\n" + + "\x16tcp_settings.non_empty\x12/a treatment must set at least one TCP parameter\x1a\xa7\x01has(this.congestion_control) || has(this.rto_min_ms) || has(this.initial_congestion_window) || has(this.initial_receive_window) || has(this.quick_ack) || has(this.ecn)\x1a\x8a\x01\n" + + "$tcp_settings.lock_requires_algorithm\x12+congestion_lock requires congestion_control\x1a5!this.congestion_lock || has(this.congestion_control)B\x15\n" + + "\x13_congestion_controlB\r\n" + + "\v_rto_min_msB\x1c\n" + + "\x1a_initial_congestion_windowB\x19\n" + + "\x17_initial_receive_windowB\f\n" + + "\n" + + "_quick_ackB\x06\n" + + "\x04_ecn\"\xd8\x03\n" + + "\tTreatment\x129\n" + + "\x02id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x02id\x12#\n" + + "\brevision\x18\x02 \x01(\x04B\a\xbaH\x042\x02 \x00R\brevision\x12,\n" + + "\vdestination\x18\x03 \x01(\tB\n" + + "\xbaH\ar\x05\x181\xe8\x01\x01R\vdestination\x12B\n" + + "\bsettings\x18\x04 \x01(\v2\x1e.xtcp_treatment.v1.TCPSettingsB\x06\xbaH\x03\xc8\x01\x01R\bsettings\x12.\n" + + "\x13ecn_fabric_verified\x18\x05 \x01(\bR\x11ecnFabricVerified:\xc8\x01\xbaH\xc4\x01\x1a\xc1\x01\n" + + "\x1ctreatment.dctcp_requires_ecn\x12ADCTCP requires independently verified ECN fabric and explicit ECN\x1a^this.settings.congestion_control != 'dctcp' || (this.ecn_fabric_verified && this.settings.ecn)\"\xfd\x05\n" + + "\n" + + "Assignment\x129\n" + + "\x02id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x02id\x12N\n" + + "\rexperiment_id\x18\x02 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\fexperimentId\x12B\n" + + "\ttreatment\x18\x03 \x01(\v2\x1c.xtcp_treatment.v1.TreatmentB\x06\xbaH\x03\xc8\x01\x01R\ttreatment\x12A\n" + + "\x04mode\x18\x04 \x01(\x0e2!.xtcp_treatment.v1.AssignmentModeB\n" + + "\xbaH\a\x82\x01\x04\x10\x01 \x00R\x04mode\x12D\n" + + "\x10lease_expires_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\x0eleaseExpiresAt\x12>\n" + + "\vpredecessor\x18\x06 \x01(\v2\x1c.xtcp_treatment.v1.TreatmentR\vpredecessor\x12'\n" + + "\n" + + "generation\x18\a \x01(\x04B\a\xbaH\x042\x02 \x00R\n" + + "generation:\xad\x02\xbaH\xa9\x02\x1a}\n" + + "\x1fassignment.trial_requires_lease\x12,a trial assignment requires lease_expires_at\x1a,this.mode != 1 || has(this.lease_expires_at)\x1a\xa7\x01\n" + + "\"assignment.predecessor_destination\x12+predecessor must cover the same destination\x1aT!has(this.predecessor) || this.predecessor.destination == this.treatment.destination\"\xa4\x02\n" + + "\fDesiredState\x12D\n" + + "\bagent_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\aagentId\x12#\n" + + "\brevision\x18\x02 \x01(\x04B\a\xbaH\x042\x02 \x00R\brevision\x12\x1c\n" + + "\tsuspended\x18\x03 \x01(\bR\tsuspended\x12J\n" + + "\vassignments\x18\x04 \x03(\v2\x1d.xtcp_treatment.v1.AssignmentB\t\xbaH\x06\x92\x01\x03\x10\x80 R\vassignments\x12?\n" + + "\tissued_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\bissuedAt\"\xa3\x02\n" + + "\fCapabilities\x12B\n" + + "\aboot_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x06bootId\x12/\n" + + "\x0ekernel_release\x18\x02 \x01(\tB\b\xbaH\x05r\x03\x18\x80\x02R\rkernelRelease\x12C\n" + + "\x15congestion_algorithms\x18\x03 \x03(\tB\x0e\xbaH\v\x92\x01\b\x10@\"\x04r\x02\x18\x0fR\x14congestionAlgorithms\x12.\n" + + "\x13simple_host_routing\x18\x04 \x01(\bR\x11simpleHostRouting\x12)\n" + + "\x10actuator_enabled\x18\x05 \x01(\bR\x0factuatorEnabled\"\xa8\x05\n" + + "\x11ApplicationRecord\x12E\n" + + "\n" + + "assignment\x18\x01 \x01(\v2\x1d.xtcp_treatment.v1.AssignmentB\x06\xbaH\x03\xc8\x01\x01R\n" + + "assignment\x12)\n" + + "\x10desired_revision\x18\x02 \x01(\x04R\x0fdesiredRevision\x12@\n" + + "\x06status\x18\x03 \x01(\x0e2\x1e.xtcp_treatment.v1.ApplyStatusB\b\xbaH\x05\x82\x01\x02\x10\x01R\x06status\x12;\n" + + "\vreceived_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\n" + + "receivedAt\x12=\n" + + "\fattempted_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\vattemptedAt\x129\n" + + "\n" + + "applied_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\tappliedAt\x12;\n" + + "\vverified_at\x18\a \x01(\v2\x1a.google.protobuf.TimestampR\n" + + "verifiedAt\x129\n" + + "\n" + + "removed_at\x18\b \x01(\v2\x1a.google.protobuf.TimestampR\tremovedAt\x12\x1e\n" + + "\x05error\x18\t \x01(\tB\b\xbaH\x05r\x03\x18\x80 R\x05error\x121\n" + + "\x0finstalled_route\x18\n" + + " \x01(\fB\b\xbaH\x05z\x03\x18\x80 R\x0einstalledRoute\x124\n" + + "\x11installed_boot_id\x18\v \x01(\tB\b\xbaH\x05r\x03\x18\x80\x01R\x0finstalledBootId\x12'\n" + + "\x0fexpired_latched\x18\f \x01(\bR\x0eexpiredLatched\"\xb1\x02\n" + + "\n" + + "LocalState\x129\n" + + "\adesired\x18\x01 \x01(\v2\x1f.xtcp_treatment.v1.DesiredStateR\adesired\x12+\n" + + "\x11locally_suspended\x18\x02 \x01(\bR\x10locallySuspended\x125\n" + + "\x11suspension_reason\x18\x03 \x01(\tB\b\xbaH\x05r\x03\x18\x80\bR\x10suspensionReason\x12I\n" + + "\arecords\x18\x04 \x03(\v2$.xtcp_treatment.v1.ApplicationRecordB\t\xbaH\x06\x92\x01\x03\x10\x80@R\arecords\x129\n" + + "\n" + + "updated_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\"\x89\x04\n" + + "\fHostEvidence\x12D\n" + + "\bagent_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\aagentId\x12\x18\n" + + "\asockets\x18\x02 \x01(\x04R\asockets\x12\x1f\n" + + "\vbytes_acked\x18\x03 \x01(\x04R\n" + + "bytesAcked\x121\n" + + "\vgoodput_bps\x18\x04 \x01(\x01B\x10\xbaH\r\x12\v@\x01)\x00\x00\x00\x00\x00\x00\x00\x00R\n" + + "goodputBps\x12C\n" + + "\x14retransmission_ratio\x18\x05 \x01(\x01B\x10\xbaH\r\x12\v@\x01)\x00\x00\x00\x00\x00\x00\x00\x00R\x13retransmissionRatio\x12.\n" + + "\n" + + "rtt_p95_ms\x18\x06 \x01(\x01B\x10\xbaH\r\x12\v@\x01)\x00\x00\x00\x00\x00\x00\x00\x00R\brttP95Ms\x12'\n" + + "\x0fexposed_sockets\x18\a \x01(\x04R\x0eexposedSockets\x12-\n" + + "\x12collection_healthy\x18\b \x01(\bR\x11collectionHealthy:x\xbaHu\x1as\n" + + "$host_evidence.exposed_within_sockets\x12%exposed_sockets cannot exceed sockets\x1a$this.exposed_sockets <= this.sockets\"\xbc\x05\n" + + "\bEvidence\x129\n" + + "\x02id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x02id\x12C\n" + + "\vanalyzed_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\n" + + "analyzedAt\x12E\n" + + "\fwindow_start\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\vwindowStart\x12A\n" + + "\n" + + "window_end\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\twindowEnd\x12/\n" + + "\rquery_version\x18\x05 \x01(\tB\n" + + "\xbaH\ar\x05\x10\x01\x18\x80\x02R\fqueryVersion\x127\n" + + "\x12enrichment_version\x18\x06 \x01(\tB\b\xbaH\x05r\x03\x18\x80\x02R\x11enrichmentVersion\x12B\n" + + "\x05hosts\x18\a \x03(\v2\x1f.xtcp_treatment.v1.HostEvidenceB\v\xbaH\b\x92\x01\x05\b\x01\x10\x90NR\x05hosts\x12>\n" + + "\x0fartifact_sha256\x18\b \x01(\tB\x15\xbaH\x12r\x102\x0e^[0-9a-f]{64}$R\x0eartifactSha256:\xb7\x01\xbaH\xb3\x01\x1a\xb0\x01\n" + + "\x11evidence.interval\x12Owindow_end must follow window_start and analyzed_at must not precede window_end\x1aJthis.window_end > this.window_start && this.analyzed_at >= this.window_end\"\xec\x06\n" + + "\x10ExperimentPolicy\x12]\n" + + "\x13minimum_observation\x18\x01 \x01(\v2\x19.google.protobuf.DurationB\x11\xbaH\x0e\xc8\x01\x01\xaa\x01\b\"\x04\b\x80\xf5$*\x00R\x12minimumObservation\x12Q\n" + + "\rmaximum_phase\x18\x02 \x01(\v2\x19.google.protobuf.DurationB\x11\xbaH\x0e\xc8\x01\x01\xaa\x01\b\"\x04\b\x80\xf5$*\x00R\fmaximumPhase\x12@\n" + + "\x18minimum_sockets_per_host\x18\x03 \x01(\x04B\a\xbaH\x042\x02 \x00R\x15minimumSocketsPerHost\x12<\n" + + "\x16minimum_bytes_per_host\x18\x04 \x01(\x04B\a\xbaH\x042\x02 \x00R\x13minimumBytesPerHost\x12>\n" + + "\x15minimum_treated_hosts\x18\x05 \x01(\rB\n" + + "\xbaH\a*\x05\x18\x90N(\x02R\x13minimumTreatedHosts\x12J\n" + + "\x13minimum_improvement\x18\x06 \x01(\x01B\x19\xbaH\x16\x12\x14@\x01\x19\x00\x00\x00\x00\x00\x00\xf0?)\x00\x00\x00\x00\x00\x00\x00\x00R\x12minimumImprovement\x12a\n" + + "\x1fmaximum_retransmission_increase\x18\a \x01(\x01B\x19\xbaH\x16\x12\x14@\x01\x19\x00\x00\x00\x00\x00\x00\xf0?)\x00\x00\x00\x00\x00\x00\x00\x00R\x1dmaximumRetransmissionIncrease\x12K\n" + + "\x14maximum_rtt_increase\x18\b \x01(\x01B\x19\xbaH\x16\x12\x14@\x01\x19\x00\x00\x00\x00\x00\x00\xf0?)\x00\x00\x00\x00\x00\x00\x00\x00R\x12maximumRttIncrease\x12^\n" + + "\x14maximum_evidence_age\x18\t \x01(\v2\x19.google.protobuf.DurationB\x11\xbaH\x0e\xc8\x01\x01\xaa\x01\b\"\x04\b\x80\xf5$*\x00R\x12maximumEvidenceAge:\x89\x01\xbaH\x85\x01\x1a\x82\x01\n" + + "\x1fpolicy.observation_within_phase\x12/minimum_observation cannot exceed maximum_phase\x1a.this.minimum_observation <= this.maximum_phase\"\xa4\x03\n" + + "\x05Phase\x12-\n" + + "\ftarget_hosts\x18\x01 \x01(\rB\n" + + "\xbaH\a*\x05\x18\x90N(\x01R\vtargetHosts\x127\n" + + "\tissued_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\bissuedAt\x12P\n" + + "\x16observation_started_at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\x14observationStartedAt\x12=\n" + + "\fevaluated_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\vevaluatedAt\x127\n" + + "\bevidence\x18\x05 \x01(\v2\x1b.xtcp_treatment.v1.EvidenceR\bevidence\x12!\n" + + "\aoutcome\x18\x06 \x01(\tB\a\xbaH\x04r\x02\x18@R\aoutcome\x12$\n" + + "\breviewer\x18\a \x01(\tB\b\xbaH\x05r\x03\x18\x80\x01R\breviewer\x12 \n" + + "\x06reason\x18\b \x01(\tB\b\xbaH\x05r\x03\x18\x80 R\x06reason\"\xb0\x06\n" + + "\n" + + "Experiment\x129\n" + + "\x02id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x02id\x12E\n" + + "\blocation\x18\x02 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\blocation\x12*\n" + + "\n" + + "hypothesis\x18\x03 \x01(\tB\n" + + "\xbaH\ar\x05\x10\x01\x18\x80@R\n" + + "hypothesis\x12B\n" + + "\ttreatment\x18\x04 \x01(\v2\x1c.xtcp_treatment.v1.TreatmentB\x06\xbaH\x03\xc8\x01\x01R\ttreatment\x12C\n" + + "\x06policy\x18\x05 \x01(\v2#.xtcp_treatment.v1.ExperimentPolicyB\x06\xbaH\x03\xc8\x01\x01R\x06policy\x12?\n" + + "\bbaseline\x18\x06 \x01(\v2\x1b.xtcp_treatment.v1.EvidenceB\x06\xbaH\x03\xc8\x01\x01R\bbaseline\x12X\n" + + "\fcohort_order\x18\a \x03(\tB5\xbaH2\x92\x01/\b\x03\x10\x90N\x18\x01\"&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\vcohortOrder\x12:\n" + + "\x06phases\x18\b \x03(\v2\x18.xtcp_treatment.v1.PhaseB\b\xbaH\x05\x92\x01\x02\x10@R\x06phases\x12E\n" + + "\x06status\x18\t \x01(\x0e2#.xtcp_treatment.v1.ExperimentStatusB\b\xbaH\x05\x82\x01\x02\x10\x01R\x06status\x12\x1a\n" + + "\brevision\x18\n" + + " \x01(\x04R\brevision\x12;\n" + + "\vproposed_at\x18\v \x01(\v2\x1a.google.protobuf.TimestampR\n" + + "proposedAt\x12D\n" + + "\x0flatest_evidence\x18\f \x01(\v2\x1b.xtcp_treatment.v1.EvidenceR\x0elatestEvidence\x12.\n" + + "\x0elatest_outcome\x18\r \x01(\tB\a\xbaH\x04r\x02\x18@R\rlatestOutcome*?\n" + + "\x04Role\x12\x14\n" + + "\x10ROLE_UNSPECIFIED\x10\x00\x12\x0e\n" + + "\n" + + "ROLE_AGENT\x10\x01\x12\x11\n" + + "\rROLE_OPERATOR\x10\x02*\xa4\x01\n" + + "\x0eAssignmentMode\x12\x1f\n" + + "\x1bASSIGNMENT_MODE_UNSPECIFIED\x10\x00\x12\x19\n" + + "\x15ASSIGNMENT_MODE_TRIAL\x10\x01\x12\x1a\n" + + "\x16ASSIGNMENT_MODE_STABLE\x10\x02\x12\x1d\n" + + "\x19ASSIGNMENT_MODE_SUSPENDED\x10\x03\x12\x1b\n" + + "\x17ASSIGNMENT_MODE_RETIRED\x10\x04*\xad\x01\n" + + "\vApplyStatus\x12\x1c\n" + + "\x18APPLY_STATUS_UNSPECIFIED\x10\x00\x12\x18\n" + + "\x14APPLY_STATUS_PENDING\x10\x01\x12\x19\n" + + "\x15APPLY_STATUS_VERIFIED\x10\x02\x12\x18\n" + + "\x14APPLY_STATUS_REMOVED\x10\x03\x12\x18\n" + + "\x14APPLY_STATUS_EXPIRED\x10\x04\x12\x17\n" + + "\x13APPLY_STATUS_FAILED\x10\x05*\x95\x02\n" + + "\x10ExperimentStatus\x12!\n" + + "\x1dEXPERIMENT_STATUS_UNSPECIFIED\x10\x00\x12\x1e\n" + + "\x1aEXPERIMENT_STATUS_PROPOSED\x10\x01\x12\x1d\n" + + "\x19EXPERIMENT_STATUS_RUNNING\x10\x02\x12\x1c\n" + + "\x18EXPERIMENT_STATUS_PAUSED\x10\x03\x12\x1f\n" + + "\x1bEXPERIMENT_STATUS_SUSPENDED\x10\x04\x12\x1e\n" + + "\x1aEXPERIMENT_STATUS_COMPLETE\x10\x05\x12\x1d\n" + + "\x19EXPERIMENT_STATUS_RETIRED\x10\x06\x12!\n" + + "\x1dEXPERIMENT_STATUS_ROLLED_BACK\x10\a:M\n" + + "\x04role\x12\x1e.google.protobuf.MethodOptions\x18\xba\x8e\x03 \x01(\x0e2\x17.xtcp_treatment.v1.RoleR\x04roleB\xc0\x01\n" + + "\x15com.xtcp_treatment.v1B\x0eTreatmentProtoP\x01Z6github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment\xa2\x02\x03XXX\xaa\x02\x10XtcpTreatment.V1\xca\x02\x10XtcpTreatment\\V1\xe2\x02\x1cXtcpTreatment\\V1\\GPBMetadata\xea\x02\x11XtcpTreatment::V1b\x06proto3" + +var ( + file_xtcp_treatment_v1_treatment_proto_rawDescOnce sync.Once + file_xtcp_treatment_v1_treatment_proto_rawDescData []byte +) + +func file_xtcp_treatment_v1_treatment_proto_rawDescGZIP() []byte { + file_xtcp_treatment_v1_treatment_proto_rawDescOnce.Do(func() { + file_xtcp_treatment_v1_treatment_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_xtcp_treatment_v1_treatment_proto_rawDesc), len(file_xtcp_treatment_v1_treatment_proto_rawDesc))) + }) + return file_xtcp_treatment_v1_treatment_proto_rawDescData +} + +var file_xtcp_treatment_v1_treatment_proto_enumTypes = make([]protoimpl.EnumInfo, 4) +var file_xtcp_treatment_v1_treatment_proto_msgTypes = make([]protoimpl.MessageInfo, 12) +var file_xtcp_treatment_v1_treatment_proto_goTypes = []any{ + (Role)(0), // 0: xtcp_treatment.v1.Role + (AssignmentMode)(0), // 1: xtcp_treatment.v1.AssignmentMode + (ApplyStatus)(0), // 2: xtcp_treatment.v1.ApplyStatus + (ExperimentStatus)(0), // 3: xtcp_treatment.v1.ExperimentStatus + (*TCPSettings)(nil), // 4: xtcp_treatment.v1.TCPSettings + (*Treatment)(nil), // 5: xtcp_treatment.v1.Treatment + (*Assignment)(nil), // 6: xtcp_treatment.v1.Assignment + (*DesiredState)(nil), // 7: xtcp_treatment.v1.DesiredState + (*Capabilities)(nil), // 8: xtcp_treatment.v1.Capabilities + (*ApplicationRecord)(nil), // 9: xtcp_treatment.v1.ApplicationRecord + (*LocalState)(nil), // 10: xtcp_treatment.v1.LocalState + (*HostEvidence)(nil), // 11: xtcp_treatment.v1.HostEvidence + (*Evidence)(nil), // 12: xtcp_treatment.v1.Evidence + (*ExperimentPolicy)(nil), // 13: xtcp_treatment.v1.ExperimentPolicy + (*Phase)(nil), // 14: xtcp_treatment.v1.Phase + (*Experiment)(nil), // 15: xtcp_treatment.v1.Experiment + (*timestamppb.Timestamp)(nil), // 16: google.protobuf.Timestamp + (*durationpb.Duration)(nil), // 17: google.protobuf.Duration + (*descriptorpb.MethodOptions)(nil), // 18: google.protobuf.MethodOptions +} +var file_xtcp_treatment_v1_treatment_proto_depIdxs = []int32{ + 4, // 0: xtcp_treatment.v1.Treatment.settings:type_name -> xtcp_treatment.v1.TCPSettings + 5, // 1: xtcp_treatment.v1.Assignment.treatment:type_name -> xtcp_treatment.v1.Treatment + 1, // 2: xtcp_treatment.v1.Assignment.mode:type_name -> xtcp_treatment.v1.AssignmentMode + 16, // 3: xtcp_treatment.v1.Assignment.lease_expires_at:type_name -> google.protobuf.Timestamp + 5, // 4: xtcp_treatment.v1.Assignment.predecessor:type_name -> xtcp_treatment.v1.Treatment + 6, // 5: xtcp_treatment.v1.DesiredState.assignments:type_name -> xtcp_treatment.v1.Assignment + 16, // 6: xtcp_treatment.v1.DesiredState.issued_at:type_name -> google.protobuf.Timestamp + 6, // 7: xtcp_treatment.v1.ApplicationRecord.assignment:type_name -> xtcp_treatment.v1.Assignment + 2, // 8: xtcp_treatment.v1.ApplicationRecord.status:type_name -> xtcp_treatment.v1.ApplyStatus + 16, // 9: xtcp_treatment.v1.ApplicationRecord.received_at:type_name -> google.protobuf.Timestamp + 16, // 10: xtcp_treatment.v1.ApplicationRecord.attempted_at:type_name -> google.protobuf.Timestamp + 16, // 11: xtcp_treatment.v1.ApplicationRecord.applied_at:type_name -> google.protobuf.Timestamp + 16, // 12: xtcp_treatment.v1.ApplicationRecord.verified_at:type_name -> google.protobuf.Timestamp + 16, // 13: xtcp_treatment.v1.ApplicationRecord.removed_at:type_name -> google.protobuf.Timestamp + 7, // 14: xtcp_treatment.v1.LocalState.desired:type_name -> xtcp_treatment.v1.DesiredState + 9, // 15: xtcp_treatment.v1.LocalState.records:type_name -> xtcp_treatment.v1.ApplicationRecord + 16, // 16: xtcp_treatment.v1.LocalState.updated_at:type_name -> google.protobuf.Timestamp + 16, // 17: xtcp_treatment.v1.Evidence.analyzed_at:type_name -> google.protobuf.Timestamp + 16, // 18: xtcp_treatment.v1.Evidence.window_start:type_name -> google.protobuf.Timestamp + 16, // 19: xtcp_treatment.v1.Evidence.window_end:type_name -> google.protobuf.Timestamp + 11, // 20: xtcp_treatment.v1.Evidence.hosts:type_name -> xtcp_treatment.v1.HostEvidence + 17, // 21: xtcp_treatment.v1.ExperimentPolicy.minimum_observation:type_name -> google.protobuf.Duration + 17, // 22: xtcp_treatment.v1.ExperimentPolicy.maximum_phase:type_name -> google.protobuf.Duration + 17, // 23: xtcp_treatment.v1.ExperimentPolicy.maximum_evidence_age:type_name -> google.protobuf.Duration + 16, // 24: xtcp_treatment.v1.Phase.issued_at:type_name -> google.protobuf.Timestamp + 16, // 25: xtcp_treatment.v1.Phase.observation_started_at:type_name -> google.protobuf.Timestamp + 16, // 26: xtcp_treatment.v1.Phase.evaluated_at:type_name -> google.protobuf.Timestamp + 12, // 27: xtcp_treatment.v1.Phase.evidence:type_name -> xtcp_treatment.v1.Evidence + 5, // 28: xtcp_treatment.v1.Experiment.treatment:type_name -> xtcp_treatment.v1.Treatment + 13, // 29: xtcp_treatment.v1.Experiment.policy:type_name -> xtcp_treatment.v1.ExperimentPolicy + 12, // 30: xtcp_treatment.v1.Experiment.baseline:type_name -> xtcp_treatment.v1.Evidence + 14, // 31: xtcp_treatment.v1.Experiment.phases:type_name -> xtcp_treatment.v1.Phase + 3, // 32: xtcp_treatment.v1.Experiment.status:type_name -> xtcp_treatment.v1.ExperimentStatus + 16, // 33: xtcp_treatment.v1.Experiment.proposed_at:type_name -> google.protobuf.Timestamp + 12, // 34: xtcp_treatment.v1.Experiment.latest_evidence:type_name -> xtcp_treatment.v1.Evidence + 18, // 35: xtcp_treatment.v1.role:extendee -> google.protobuf.MethodOptions + 0, // 36: xtcp_treatment.v1.role:type_name -> xtcp_treatment.v1.Role + 37, // [37:37] is the sub-list for method output_type + 37, // [37:37] is the sub-list for method input_type + 36, // [36:37] is the sub-list for extension type_name + 35, // [35:36] is the sub-list for extension extendee + 0, // [0:35] is the sub-list for field type_name +} + +func init() { file_xtcp_treatment_v1_treatment_proto_init() } +func file_xtcp_treatment_v1_treatment_proto_init() { + if File_xtcp_treatment_v1_treatment_proto != nil { + return + } + file_xtcp_treatment_v1_treatment_proto_msgTypes[0].OneofWrappers = []any{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_xtcp_treatment_v1_treatment_proto_rawDesc), len(file_xtcp_treatment_v1_treatment_proto_rawDesc)), + NumEnums: 4, + NumMessages: 12, + NumExtensions: 1, + NumServices: 0, + }, + GoTypes: file_xtcp_treatment_v1_treatment_proto_goTypes, + DependencyIndexes: file_xtcp_treatment_v1_treatment_proto_depIdxs, + EnumInfos: file_xtcp_treatment_v1_treatment_proto_enumTypes, + MessageInfos: file_xtcp_treatment_v1_treatment_proto_msgTypes, + ExtensionInfos: file_xtcp_treatment_v1_treatment_proto_extTypes, + }.Build() + File_xtcp_treatment_v1_treatment_proto = out.File + file_xtcp_treatment_v1_treatment_proto_goTypes = nil + file_xtcp_treatment_v1_treatment_proto_depIdxs = nil +} diff --git a/gen/go/xtcp_controller/controller_vtproto.pb.go b/gen/go/xtcp_treatment/treatment_vtproto.pb.go similarity index 61% rename from gen/go/xtcp_controller/controller_vtproto.pb.go rename to gen/go/xtcp_treatment/treatment_vtproto.pb.go index 5bae1914f..72165a706 100644 --- a/gen/go/xtcp_controller/controller_vtproto.pb.go +++ b/gen/go/xtcp_treatment/treatment_vtproto.pb.go @@ -1,8 +1,8 @@ // Code generated by protoc-gen-go-vtproto. DO NOT EDIT. -// protoc-gen-go-vtproto version: v0.6.1-0.20240319094008-0393e58bdf10 -// source: xtcp/controller/v1/controller.proto +// protoc-gen-go-vtproto version: (devel) +// source: xtcp_treatment/v1/treatment.proto -package xtcp_controller +package xtcp_treatment import ( binary "encoding/binary" @@ -637,7 +637,7 @@ func (m *LocalState) MarshalToSizedBufferVT(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *CheckInRequest) MarshalVT() (dAtA []byte, err error) { +func (m *HostEvidence) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil } @@ -650,12 +650,12 @@ func (m *CheckInRequest) MarshalVT() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *CheckInRequest) MarshalToVT(dAtA []byte) (int, error) { +func (m *HostEvidence) MarshalToVT(dAtA []byte) (int, error) { size := m.SizeVT() return m.MarshalToSizedBufferVT(dAtA[:size]) } -func (m *CheckInRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { +func (m *HostEvidence) MarshalToSizedBufferVT(dAtA []byte) (int, error) { if m == nil { return 0, nil } @@ -667,49 +667,48 @@ func (m *CheckInRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } - if m.LocallySuspended { + if m.CollectionHealthy { i-- - if m.LocallySuspended { + if m.CollectionHealthy { dAtA[i] = 1 } else { dAtA[i] = 0 } i-- - dAtA[i] = 0x30 + dAtA[i] = 0x40 } - if len(m.Records) > 0 { - for iNdEx := len(m.Records) - 1; iNdEx >= 0; iNdEx-- { - size, err := m.Records[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0x2a - } + if m.ExposedSockets != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.ExposedSockets)) + i-- + dAtA[i] = 0x38 } - if m.DesiredRevision != 0 { - i = protohelpers.EncodeVarint(dAtA, i, uint64(m.DesiredRevision)) + if m.RttP95Ms != 0 { + i -= 8 + binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.RttP95Ms)))) i-- - dAtA[i] = 0x20 + dAtA[i] = 0x31 } - if m.Capabilities != nil { - size, err := m.Capabilities.MarshalToSizedBufferVT(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + if m.RetransmissionRatio != 0 { + i -= 8 + binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.RetransmissionRatio)))) i-- - dAtA[i] = 0x1a + dAtA[i] = 0x29 } - if len(m.Location) > 0 { - i -= len(m.Location) - copy(dAtA[i:], m.Location) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Location))) + if m.GoodputBps != 0 { + i -= 8 + binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.GoodputBps)))) i-- - dAtA[i] = 0x12 + dAtA[i] = 0x21 + } + if m.BytesAcked != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.BytesAcked)) + i-- + dAtA[i] = 0x18 + } + if m.Sockets != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Sockets)) + i-- + dAtA[i] = 0x10 } if len(m.AgentId) > 0 { i -= len(m.AgentId) @@ -721,7 +720,7 @@ func (m *CheckInRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *CheckInResponse) MarshalVT() (dAtA []byte, err error) { +func (m *Evidence) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil } @@ -734,12 +733,12 @@ func (m *CheckInResponse) MarshalVT() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *CheckInResponse) MarshalToVT(dAtA []byte) (int, error) { +func (m *Evidence) MarshalToVT(dAtA []byte) (int, error) { size := m.SizeVT() return m.MarshalToSizedBufferVT(dAtA[:size]) } -func (m *CheckInResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { +func (m *Evidence) MarshalToSizedBufferVT(dAtA []byte) (int, error) { if m == nil { return 0, nil } @@ -751,60 +750,80 @@ func (m *CheckInResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } - if m.Desired != nil { - size, err := m.Desired.MarshalToSizedBufferVT(dAtA[:i]) + if len(m.ArtifactSha256) > 0 { + i -= len(m.ArtifactSha256) + copy(dAtA[i:], m.ArtifactSha256) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.ArtifactSha256))) + i-- + dAtA[i] = 0x42 + } + if len(m.Hosts) > 0 { + for iNdEx := len(m.Hosts) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.Hosts[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x3a + } + } + if len(m.EnrichmentVersion) > 0 { + i -= len(m.EnrichmentVersion) + copy(dAtA[i:], m.EnrichmentVersion) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.EnrichmentVersion))) + i-- + dAtA[i] = 0x32 + } + if len(m.QueryVersion) > 0 { + i -= len(m.QueryVersion) + copy(dAtA[i:], m.QueryVersion) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.QueryVersion))) + i-- + dAtA[i] = 0x2a + } + if m.WindowEnd != nil { + size, err := (*timestamppb.Timestamp)(m.WindowEnd).MarshalToSizedBufferVT(dAtA[:i]) if err != nil { return 0, err } i -= size i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *GetDesiredStateRequest) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err + dAtA[i] = 0x22 } - return dAtA[:n], nil -} - -func (m *GetDesiredStateRequest) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) -} - -func (m *GetDesiredStateRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { - if m == nil { - return 0, nil + if m.WindowStart != nil { + size, err := (*timestamppb.Timestamp)(m.WindowStart).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x1a } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) + if m.AnalyzedAt != nil { + size, err := (*timestamppb.Timestamp)(m.AnalyzedAt).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x12 } - if len(m.AgentId) > 0 { - i -= len(m.AgentId) - copy(dAtA[i:], m.AgentId) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.AgentId))) + if len(m.Id) > 0 { + i -= len(m.Id) + copy(dAtA[i:], m.Id) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Id))) i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } -func (m *GetDesiredStateResponse) MarshalVT() (dAtA []byte, err error) { +func (m *ExperimentPolicy) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil } @@ -817,12 +836,12 @@ func (m *GetDesiredStateResponse) MarshalVT() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *GetDesiredStateResponse) MarshalToVT(dAtA []byte) (int, error) { +func (m *ExperimentPolicy) MarshalToVT(dAtA []byte) (int, error) { size := m.SizeVT() return m.MarshalToSizedBufferVT(dAtA[:size]) } -func (m *GetDesiredStateResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { +func (m *ExperimentPolicy) MarshalToSizedBufferVT(dAtA []byte) (int, error) { if m == nil { return 0, nil } @@ -834,65 +853,73 @@ func (m *GetDesiredStateResponse) MarshalToSizedBufferVT(dAtA []byte) (int, erro i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } - if m.Desired != nil { - size, err := m.Desired.MarshalToSizedBufferVT(dAtA[:i]) + if m.MaximumEvidenceAge != nil { + size, err := (*durationpb.Duration)(m.MaximumEvidenceAge).MarshalToSizedBufferVT(dAtA[:i]) if err != nil { return 0, err } i -= size i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) i-- - dAtA[i] = 0xa + dAtA[i] = 0x4a } - return len(dAtA) - i, nil -} - -func (m *WatchDesiredStateRequest) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil + if m.MaximumRttIncrease != 0 { + i -= 8 + binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.MaximumRttIncrease)))) + i-- + dAtA[i] = 0x41 } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err + if m.MaximumRetransmissionIncrease != 0 { + i -= 8 + binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.MaximumRetransmissionIncrease)))) + i-- + dAtA[i] = 0x39 } - return dAtA[:n], nil -} - -func (m *WatchDesiredStateRequest) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) -} - -func (m *WatchDesiredStateRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { - if m == nil { - return 0, nil + if m.MinimumImprovement != 0 { + i -= 8 + binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.MinimumImprovement)))) + i-- + dAtA[i] = 0x31 } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) + if m.MinimumTreatedHosts != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.MinimumTreatedHosts)) + i-- + dAtA[i] = 0x28 } - if m.AfterRevision != 0 { - i = protohelpers.EncodeVarint(dAtA, i, uint64(m.AfterRevision)) + if m.MinimumBytesPerHost != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.MinimumBytesPerHost)) i-- - dAtA[i] = 0x10 + dAtA[i] = 0x20 } - if len(m.AgentId) > 0 { - i -= len(m.AgentId) - copy(dAtA[i:], m.AgentId) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.AgentId))) + if m.MinimumSocketsPerHost != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.MinimumSocketsPerHost)) + i-- + dAtA[i] = 0x18 + } + if m.MaximumPhase != nil { + size, err := (*durationpb.Duration)(m.MaximumPhase).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x12 + } + if m.MinimumObservation != nil { + size, err := (*durationpb.Duration)(m.MinimumObservation).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } -func (m *WatchDesiredStateResponse) MarshalVT() (dAtA []byte, err error) { +func (m *Phase) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil } @@ -905,12 +932,12 @@ func (m *WatchDesiredStateResponse) MarshalVT() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *WatchDesiredStateResponse) MarshalToVT(dAtA []byte) (int, error) { +func (m *Phase) MarshalToVT(dAtA []byte) (int, error) { size := m.SizeVT() return m.MarshalToSizedBufferVT(dAtA[:size]) } -func (m *WatchDesiredStateResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { +func (m *Phase) MarshalToSizedBufferVT(dAtA []byte) (int, error) { if m == nil { return 0, nil } @@ -922,98 +949,76 @@ func (m *WatchDesiredStateResponse) MarshalToSizedBufferVT(dAtA []byte) (int, er i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } - if m.Revision != 0 { - i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Revision)) + if len(m.Reason) > 0 { + i -= len(m.Reason) + copy(dAtA[i:], m.Reason) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Reason))) i-- - dAtA[i] = 0x8 + dAtA[i] = 0x42 } - return len(dAtA) - i, nil -} - -func (m *HostEvidence) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *HostEvidence) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) -} - -func (m *HostEvidence) MarshalToSizedBufferVT(dAtA []byte) (int, error) { - if m == nil { - return 0, nil - } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if m.CollectionHealthy { - i-- - if m.CollectionHealthy { - dAtA[i] = 1 - } else { - dAtA[i] = 0 - } - i-- - dAtA[i] = 0x40 - } - if m.ExposedSockets != 0 { - i = protohelpers.EncodeVarint(dAtA, i, uint64(m.ExposedSockets)) + if len(m.Reviewer) > 0 { + i -= len(m.Reviewer) + copy(dAtA[i:], m.Reviewer) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Reviewer))) i-- - dAtA[i] = 0x38 + dAtA[i] = 0x3a } - if m.RttP95Ms != 0 { - i -= 8 - binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.RttP95Ms)))) + if len(m.Outcome) > 0 { + i -= len(m.Outcome) + copy(dAtA[i:], m.Outcome) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Outcome))) i-- - dAtA[i] = 0x31 + dAtA[i] = 0x32 } - if m.RetransmissionRatio != 0 { - i -= 8 - binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.RetransmissionRatio)))) + if m.Evidence != nil { + size, err := m.Evidence.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) i-- - dAtA[i] = 0x29 + dAtA[i] = 0x2a } - if m.GoodputBps != 0 { - i -= 8 - binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.GoodputBps)))) + if m.EvaluatedAt != nil { + size, err := (*timestamppb.Timestamp)(m.EvaluatedAt).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) i-- - dAtA[i] = 0x21 + dAtA[i] = 0x22 } - if m.BytesAcked != 0 { - i = protohelpers.EncodeVarint(dAtA, i, uint64(m.BytesAcked)) + if m.ObservationStartedAt != nil { + size, err := (*timestamppb.Timestamp)(m.ObservationStartedAt).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) i-- - dAtA[i] = 0x18 + dAtA[i] = 0x1a } - if m.Sockets != 0 { - i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Sockets)) + if m.IssuedAt != nil { + size, err := (*timestamppb.Timestamp)(m.IssuedAt).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) i-- - dAtA[i] = 0x10 + dAtA[i] = 0x12 } - if len(m.AgentId) > 0 { - i -= len(m.AgentId) - copy(dAtA[i:], m.AgentId) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.AgentId))) + if m.TargetHosts != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.TargetHosts)) i-- - dAtA[i] = 0xa + dAtA[i] = 0x8 } return len(dAtA) - i, nil } -func (m *Evidence) MarshalVT() (dAtA []byte, err error) { +func (m *Experiment) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil } @@ -1026,12 +1031,12 @@ func (m *Evidence) MarshalVT() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *Evidence) MarshalToVT(dAtA []byte) (int, error) { +func (m *Experiment) MarshalToVT(dAtA []byte) (int, error) { size := m.SizeVT() return m.MarshalToSizedBufferVT(dAtA[:size]) } -func (m *Evidence) MarshalToSizedBufferVT(dAtA []byte) (int, error) { +func (m *Experiment) MarshalToSizedBufferVT(dAtA []byte) (int, error) { if m == nil { return 0, nil } @@ -1043,67 +1048,106 @@ func (m *Evidence) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } - if len(m.ArtifactSha256) > 0 { - i -= len(m.ArtifactSha256) - copy(dAtA[i:], m.ArtifactSha256) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.ArtifactSha256))) + if len(m.LatestOutcome) > 0 { + i -= len(m.LatestOutcome) + copy(dAtA[i:], m.LatestOutcome) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.LatestOutcome))) i-- - dAtA[i] = 0x42 + dAtA[i] = 0x6a } - if len(m.Hosts) > 0 { - for iNdEx := len(m.Hosts) - 1; iNdEx >= 0; iNdEx-- { - size, err := m.Hosts[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) + if m.LatestEvidence != nil { + size, err := m.LatestEvidence.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x62 + } + if m.ProposedAt != nil { + size, err := (*timestamppb.Timestamp)(m.ProposedAt).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x5a + } + if m.Revision != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Revision)) + i-- + dAtA[i] = 0x50 + } + if m.Status != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Status)) + i-- + dAtA[i] = 0x48 + } + if len(m.Phases) > 0 { + for iNdEx := len(m.Phases) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.Phases[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) if err != nil { return 0, err } i -= size i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) i-- - dAtA[i] = 0x3a + dAtA[i] = 0x42 } } - if len(m.EnrichmentVersion) > 0 { - i -= len(m.EnrichmentVersion) - copy(dAtA[i:], m.EnrichmentVersion) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.EnrichmentVersion))) - i-- - dAtA[i] = 0x32 - } - if len(m.QueryVersion) > 0 { - i -= len(m.QueryVersion) - copy(dAtA[i:], m.QueryVersion) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.QueryVersion))) - i-- - dAtA[i] = 0x2a + if len(m.CohortOrder) > 0 { + for iNdEx := len(m.CohortOrder) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.CohortOrder[iNdEx]) + copy(dAtA[i:], m.CohortOrder[iNdEx]) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.CohortOrder[iNdEx]))) + i-- + dAtA[i] = 0x3a + } } - if m.WindowEnd != nil { - size, err := (*timestamppb.Timestamp)(m.WindowEnd).MarshalToSizedBufferVT(dAtA[:i]) + if m.Baseline != nil { + size, err := m.Baseline.MarshalToSizedBufferVT(dAtA[:i]) if err != nil { return 0, err } i -= size i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) i-- - dAtA[i] = 0x22 + dAtA[i] = 0x32 } - if m.WindowStart != nil { - size, err := (*timestamppb.Timestamp)(m.WindowStart).MarshalToSizedBufferVT(dAtA[:i]) + if m.Policy != nil { + size, err := m.Policy.MarshalToSizedBufferVT(dAtA[:i]) if err != nil { return 0, err } i -= size i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) i-- - dAtA[i] = 0x1a + dAtA[i] = 0x2a } - if m.AnalyzedAt != nil { - size, err := (*timestamppb.Timestamp)(m.AnalyzedAt).MarshalToSizedBufferVT(dAtA[:i]) + if m.Treatment != nil { + size, err := m.Treatment.MarshalToSizedBufferVT(dAtA[:i]) if err != nil { return 0, err } i -= size i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) i-- + dAtA[i] = 0x22 + } + if len(m.Hypothesis) > 0 { + i -= len(m.Hypothesis) + copy(dAtA[i:], m.Hypothesis) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Hypothesis))) + i-- + dAtA[i] = 0x1a + } + if len(m.Location) > 0 { + i -= len(m.Location) + copy(dAtA[i:], m.Location) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Location))) + i-- dAtA[i] = 0x12 } if len(m.Id) > 0 { @@ -1116,3339 +1160,473 @@ func (m *Evidence) MarshalToSizedBufferVT(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *ExperimentPolicy) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ExperimentPolicy) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) -} - -func (m *ExperimentPolicy) MarshalToSizedBufferVT(dAtA []byte) (int, error) { +func (m *TCPSettings) SizeVT() (n int) { if m == nil { - return 0, nil + return 0 } - i := len(dAtA) - _ = i var l int _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) + if m.CongestionControl != nil { + l = len(*m.CongestionControl) + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - if m.MaximumEvidenceAge != nil { - size, err := (*durationpb.Duration)(m.MaximumEvidenceAge).MarshalToSizedBufferVT(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0x4a + if m.CongestionLock { + n += 2 } - if m.MaximumRttIncrease != 0 { - i -= 8 - binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.MaximumRttIncrease)))) - i-- - dAtA[i] = 0x41 - } - if m.MaximumRetransmissionIncrease != 0 { - i -= 8 - binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.MaximumRetransmissionIncrease)))) - i-- - dAtA[i] = 0x39 - } - if m.MinimumImprovement != 0 { - i -= 8 - binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.MinimumImprovement)))) - i-- - dAtA[i] = 0x31 - } - if m.MinimumTreatedHosts != 0 { - i = protohelpers.EncodeVarint(dAtA, i, uint64(m.MinimumTreatedHosts)) - i-- - dAtA[i] = 0x28 + if m.RtoMinMs != nil { + n += 1 + protohelpers.SizeOfVarint(uint64(*m.RtoMinMs)) } - if m.MinimumBytesPerHost != 0 { - i = protohelpers.EncodeVarint(dAtA, i, uint64(m.MinimumBytesPerHost)) - i-- - dAtA[i] = 0x20 + if m.InitialCongestionWindow != nil { + n += 1 + protohelpers.SizeOfVarint(uint64(*m.InitialCongestionWindow)) } - if m.MinimumSocketsPerHost != 0 { - i = protohelpers.EncodeVarint(dAtA, i, uint64(m.MinimumSocketsPerHost)) - i-- - dAtA[i] = 0x18 + if m.InitialReceiveWindow != nil { + n += 1 + protohelpers.SizeOfVarint(uint64(*m.InitialReceiveWindow)) } - if m.MaximumPhase != nil { - size, err := (*durationpb.Duration)(m.MaximumPhase).MarshalToSizedBufferVT(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0x12 + if m.QuickAck != nil { + n += 2 } - if m.MinimumObservation != nil { - size, err := (*durationpb.Duration)(m.MinimumObservation).MarshalToSizedBufferVT(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0xa + if m.Ecn != nil { + n += 2 } - return len(dAtA) - i, nil + n += len(m.unknownFields) + return n } -func (m *Phase) MarshalVT() (dAtA []byte, err error) { +func (m *Treatment) SizeVT() (n int) { if m == nil { - return nil, nil + return 0 } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err + var l int + _ = l + l = len(m.Id) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - return dAtA[:n], nil -} - -func (m *Phase) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) + if m.Revision != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Revision)) + } + l = len(m.Destination) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Settings != nil { + l = m.Settings.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.EcnFabricVerified { + n += 2 + } + n += len(m.unknownFields) + return n } -func (m *Phase) MarshalToSizedBufferVT(dAtA []byte) (int, error) { +func (m *Assignment) SizeVT() (n int) { if m == nil { - return 0, nil + return 0 } - i := len(dAtA) - _ = i var l int _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if len(m.Reason) > 0 { - i -= len(m.Reason) - copy(dAtA[i:], m.Reason) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Reason))) - i-- - dAtA[i] = 0x42 - } - if len(m.Reviewer) > 0 { - i -= len(m.Reviewer) - copy(dAtA[i:], m.Reviewer) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Reviewer))) - i-- - dAtA[i] = 0x3a + l = len(m.Id) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - if len(m.Outcome) > 0 { - i -= len(m.Outcome) - copy(dAtA[i:], m.Outcome) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Outcome))) - i-- - dAtA[i] = 0x32 + l = len(m.ExperimentId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - if m.Evidence != nil { - size, err := m.Evidence.MarshalToSizedBufferVT(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0x2a + if m.Treatment != nil { + l = m.Treatment.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - if m.EvaluatedAt != nil { - size, err := (*timestamppb.Timestamp)(m.EvaluatedAt).MarshalToSizedBufferVT(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0x22 + if m.Mode != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Mode)) } - if m.ObservationStartedAt != nil { - size, err := (*timestamppb.Timestamp)(m.ObservationStartedAt).MarshalToSizedBufferVT(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0x1a + if m.LeaseExpiresAt != nil { + l = (*timestamppb.Timestamp)(m.LeaseExpiresAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - if m.IssuedAt != nil { - size, err := (*timestamppb.Timestamp)(m.IssuedAt).MarshalToSizedBufferVT(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0x12 + if m.Predecessor != nil { + l = m.Predecessor.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - if m.TargetHosts != 0 { - i = protohelpers.EncodeVarint(dAtA, i, uint64(m.TargetHosts)) - i-- - dAtA[i] = 0x8 + if m.Generation != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Generation)) } - return len(dAtA) - i, nil + n += len(m.unknownFields) + return n } -func (m *Experiment) MarshalVT() (dAtA []byte, err error) { +func (m *DesiredState) SizeVT() (n int) { if m == nil { - return nil, nil + return 0 } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err + var l int + _ = l + l = len(m.AgentId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - return dAtA[:n], nil -} - -func (m *Experiment) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) + if m.Revision != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Revision)) + } + if m.Suspended { + n += 2 + } + if len(m.Assignments) > 0 { + for _, e := range m.Assignments { + l = e.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + } + if m.IssuedAt != nil { + l = (*timestamppb.Timestamp)(m.IssuedAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n } -func (m *Experiment) MarshalToSizedBufferVT(dAtA []byte) (int, error) { +func (m *Capabilities) SizeVT() (n int) { if m == nil { - return 0, nil + return 0 } - i := len(dAtA) - _ = i var l int _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) + l = len(m.BootId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - if len(m.LatestOutcome) > 0 { - i -= len(m.LatestOutcome) - copy(dAtA[i:], m.LatestOutcome) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.LatestOutcome))) - i-- - dAtA[i] = 0x6a + l = len(m.KernelRelease) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - if m.LatestEvidence != nil { - size, err := m.LatestEvidence.MarshalToSizedBufferVT(dAtA[:i]) - if err != nil { - return 0, err + if len(m.CongestionAlgorithms) > 0 { + for _, s := range m.CongestionAlgorithms { + l = len(s) + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0x62 } - if m.ProposedAt != nil { - size, err := (*timestamppb.Timestamp)(m.ProposedAt).MarshalToSizedBufferVT(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0x5a + if m.SimpleHostRouting { + n += 2 } - if m.Revision != 0 { - i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Revision)) - i-- - dAtA[i] = 0x50 + if m.ActuatorEnabled { + n += 2 } - if m.Status != 0 { - i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Status)) - i-- - dAtA[i] = 0x48 + n += len(m.unknownFields) + return n +} + +func (m *ApplicationRecord) SizeVT() (n int) { + if m == nil { + return 0 } - if len(m.Phases) > 0 { - for iNdEx := len(m.Phases) - 1; iNdEx >= 0; iNdEx-- { - size, err := m.Phases[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0x42 - } + var l int + _ = l + if m.Assignment != nil { + l = m.Assignment.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - if len(m.CohortOrder) > 0 { - for iNdEx := len(m.CohortOrder) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.CohortOrder[iNdEx]) - copy(dAtA[i:], m.CohortOrder[iNdEx]) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.CohortOrder[iNdEx]))) - i-- - dAtA[i] = 0x3a - } + if m.DesiredRevision != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.DesiredRevision)) } - if m.Baseline != nil { - size, err := m.Baseline.MarshalToSizedBufferVT(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0x32 + if m.Status != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Status)) } - if m.Policy != nil { - size, err := m.Policy.MarshalToSizedBufferVT(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0x2a + if m.ReceivedAt != nil { + l = (*timestamppb.Timestamp)(m.ReceivedAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - if m.Treatment != nil { - size, err := m.Treatment.MarshalToSizedBufferVT(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0x22 + if m.AttemptedAt != nil { + l = (*timestamppb.Timestamp)(m.AttemptedAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - if len(m.Hypothesis) > 0 { - i -= len(m.Hypothesis) - copy(dAtA[i:], m.Hypothesis) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Hypothesis))) - i-- - dAtA[i] = 0x1a + if m.AppliedAt != nil { + l = (*timestamppb.Timestamp)(m.AppliedAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - if len(m.Location) > 0 { - i -= len(m.Location) - copy(dAtA[i:], m.Location) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Location))) - i-- - dAtA[i] = 0x12 + if m.VerifiedAt != nil { + l = (*timestamppb.Timestamp)(m.VerifiedAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - if len(m.Id) > 0 { - i -= len(m.Id) - copy(dAtA[i:], m.Id) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Id))) - i-- - dAtA[i] = 0xa + if m.RemovedAt != nil { + l = (*timestamppb.Timestamp)(m.RemovedAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - return len(dAtA) - i, nil -} - -func (m *ProposeRequest) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil + l = len(m.Error) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err + l = len(m.InstalledRoute) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - return dAtA[:n], nil -} - -func (m *ProposeRequest) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) + l = len(m.InstalledBootId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.ExpiredLatched { + n += 2 + } + n += len(m.unknownFields) + return n } -func (m *ProposeRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { +func (m *LocalState) SizeVT() (n int) { if m == nil { - return 0, nil + return 0 } - i := len(dAtA) - _ = i var l int _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) + if m.Desired != nil { + l = m.Desired.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - if len(m.RequestId) > 0 { - i -= len(m.RequestId) - copy(dAtA[i:], m.RequestId) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.RequestId))) - i-- - dAtA[i] = 0x12 + if m.LocallySuspended { + n += 2 } - if m.Experiment != nil { - size, err := m.Experiment.MarshalToSizedBufferVT(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0xa + l = len(m.SuspensionReason) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - return len(dAtA) - i, nil -} - -func (m *ProposeResponse) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil + if len(m.Records) > 0 { + for _, e := range m.Records { + l = e.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err + if m.UpdatedAt != nil { + l = (*timestamppb.Timestamp)(m.UpdatedAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - return dAtA[:n], nil -} - -func (m *ProposeResponse) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) + n += len(m.unknownFields) + return n } -func (m *ProposeResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { +func (m *HostEvidence) SizeVT() (n int) { if m == nil { - return 0, nil + return 0 } - i := len(dAtA) - _ = i var l int _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) + l = len(m.AgentId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - if m.Experiment != nil { - size, err := m.Experiment.MarshalToSizedBufferVT(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0xa + if m.Sockets != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Sockets)) } - return len(dAtA) - i, nil -} - -func (m *AnalyzeRequest) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil + if m.BytesAcked != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.BytesAcked)) } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err + if m.GoodputBps != 0 { + n += 9 } - return dAtA[:n], nil -} - -func (m *AnalyzeRequest) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) + if m.RetransmissionRatio != 0 { + n += 9 + } + if m.RttP95Ms != 0 { + n += 9 + } + if m.ExposedSockets != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.ExposedSockets)) + } + if m.CollectionHealthy { + n += 2 + } + n += len(m.unknownFields) + return n } -func (m *AnalyzeRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { +func (m *Evidence) SizeVT() (n int) { if m == nil { - return 0, nil + return 0 } - i := len(dAtA) - _ = i var l int _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if m.ExpectedRevision != 0 { - i = protohelpers.EncodeVarint(dAtA, i, uint64(m.ExpectedRevision)) - i-- - dAtA[i] = 0x10 - } - if len(m.ExperimentId) > 0 { - i -= len(m.ExperimentId) - copy(dAtA[i:], m.ExperimentId) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.ExperimentId))) - i-- - dAtA[i] = 0xa + l = len(m.Id) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - return len(dAtA) - i, nil -} - -func (m *AnalyzeResponse) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil + if m.AnalyzedAt != nil { + l = (*timestamppb.Timestamp)(m.AnalyzedAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err + if m.WindowStart != nil { + l = (*timestamppb.Timestamp)(m.WindowStart).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - return dAtA[:n], nil -} - -func (m *AnalyzeResponse) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) -} - -func (m *AnalyzeResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { - if m == nil { - return 0, nil + if m.WindowEnd != nil { + l = (*timestamppb.Timestamp)(m.WindowEnd).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) + l = len(m.QueryVersion) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - if len(m.Outcome) > 0 { - i -= len(m.Outcome) - copy(dAtA[i:], m.Outcome) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Outcome))) - i-- - dAtA[i] = 0x12 + l = len(m.EnrichmentVersion) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - if m.Evidence != nil { - size, err := m.Evidence.MarshalToSizedBufferVT(dAtA[:i]) - if err != nil { - return 0, err + if len(m.Hosts) > 0 { + for _, e := range m.Hosts { + l = e.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *ReviewRequest) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err + l = len(m.ArtifactSha256) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - return dAtA[:n], nil -} - -func (m *ReviewRequest) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) + n += len(m.unknownFields) + return n } -func (m *ReviewRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { +func (m *ExperimentPolicy) SizeVT() (n int) { if m == nil { - return 0, nil + return 0 } - i := len(dAtA) - _ = i var l int _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) + if m.MinimumObservation != nil { + l = (*durationpb.Duration)(m.MinimumObservation).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - if m.Evidence != nil { - size, err := m.Evidence.MarshalToSizedBufferVT(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0x32 + if m.MaximumPhase != nil { + l = (*durationpb.Duration)(m.MaximumPhase).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - if len(m.RequestId) > 0 { - i -= len(m.RequestId) - copy(dAtA[i:], m.RequestId) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.RequestId))) - i-- - dAtA[i] = 0x2a + if m.MinimumSocketsPerHost != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.MinimumSocketsPerHost)) } - if len(m.Reason) > 0 { - i -= len(m.Reason) - copy(dAtA[i:], m.Reason) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Reason))) - i-- - dAtA[i] = 0x22 + if m.MinimumBytesPerHost != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.MinimumBytesPerHost)) } - if m.Approve { - i-- - if m.Approve { - dAtA[i] = 1 - } else { - dAtA[i] = 0 - } - i-- - dAtA[i] = 0x18 + if m.MinimumTreatedHosts != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.MinimumTreatedHosts)) } - if m.ExpectedRevision != 0 { - i = protohelpers.EncodeVarint(dAtA, i, uint64(m.ExpectedRevision)) - i-- - dAtA[i] = 0x10 + if m.MinimumImprovement != 0 { + n += 9 } - if len(m.ExperimentId) > 0 { - i -= len(m.ExperimentId) - copy(dAtA[i:], m.ExperimentId) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.ExperimentId))) - i-- - dAtA[i] = 0xa + if m.MaximumRetransmissionIncrease != 0 { + n += 9 } - return len(dAtA) - i, nil -} - -func (m *ReviewResponse) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil + if m.MaximumRttIncrease != 0 { + n += 9 } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err + if m.MaximumEvidenceAge != nil { + l = (*durationpb.Duration)(m.MaximumEvidenceAge).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - return dAtA[:n], nil -} - -func (m *ReviewResponse) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) + n += len(m.unknownFields) + return n } -func (m *ReviewResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { +func (m *Phase) SizeVT() (n int) { if m == nil { - return 0, nil + return 0 } - i := len(dAtA) - _ = i var l int _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) + if m.TargetHosts != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.TargetHosts)) } - if m.Experiment != nil { - size, err := m.Experiment.MarshalToSizedBufferVT(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0xa + if m.IssuedAt != nil { + l = (*timestamppb.Timestamp)(m.IssuedAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - return len(dAtA) - i, nil -} - -func (m *ControlRequest) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil + if m.ObservationStartedAt != nil { + l = (*timestamppb.Timestamp)(m.ObservationStartedAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err + if m.EvaluatedAt != nil { + l = (*timestamppb.Timestamp)(m.EvaluatedAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - return dAtA[:n], nil -} - -func (m *ControlRequest) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) + if m.Evidence != nil { + l = m.Evidence.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.Outcome) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.Reviewer) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.Reason) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n } -func (m *ControlRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { +func (m *Experiment) SizeVT() (n int) { if m == nil { - return 0, nil + return 0 } - i := len(dAtA) - _ = i var l int _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if len(m.RequestId) > 0 { - i -= len(m.RequestId) - copy(dAtA[i:], m.RequestId) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.RequestId))) - i-- - dAtA[i] = 0x3a + l = len(m.Id) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - if len(m.Reason) > 0 { - i -= len(m.Reason) - copy(dAtA[i:], m.Reason) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Reason))) - i-- - dAtA[i] = 0x32 + l = len(m.Location) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - if m.Action != 0 { - i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Action)) - i-- - dAtA[i] = 0x28 - } - if m.Fleet { - i-- - if m.Fleet { - dAtA[i] = 1 - } else { - dAtA[i] = 0 - } - i-- - dAtA[i] = 0x20 - } - if len(m.Location) > 0 { - i -= len(m.Location) - copy(dAtA[i:], m.Location) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Location))) - i-- - dAtA[i] = 0x1a - } - if len(m.AgentId) > 0 { - i -= len(m.AgentId) - copy(dAtA[i:], m.AgentId) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.AgentId))) - i-- - dAtA[i] = 0x12 - } - if len(m.ExperimentId) > 0 { - i -= len(m.ExperimentId) - copy(dAtA[i:], m.ExperimentId) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.ExperimentId))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *ControlResponse) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ControlResponse) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) -} - -func (m *ControlResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { - if m == nil { - return 0, nil - } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if m.AffectedAgents != 0 { - i = protohelpers.EncodeVarint(dAtA, i, uint64(m.AffectedAgents)) - i-- - dAtA[i] = 0x8 - } - return len(dAtA) - i, nil -} - -func (m *InspectRequest) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *InspectRequest) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) -} - -func (m *InspectRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { - if m == nil { - return 0, nil - } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if len(m.AgentId) > 0 { - i -= len(m.AgentId) - copy(dAtA[i:], m.AgentId) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.AgentId))) - i-- - dAtA[i] = 0x12 - } - if len(m.ExperimentId) > 0 { - i -= len(m.ExperimentId) - copy(dAtA[i:], m.ExperimentId) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.ExperimentId))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *InspectResponse) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *InspectResponse) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) -} - -func (m *InspectResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { - if m == nil { - return 0, nil - } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if len(m.Observed) > 0 { - for iNdEx := len(m.Observed) - 1; iNdEx >= 0; iNdEx-- { - size, err := m.Observed[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0x1a - } - } - if len(m.Desired) > 0 { - for iNdEx := len(m.Desired) - 1; iNdEx >= 0; iNdEx-- { - size, err := m.Desired[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0x12 - } - } - if len(m.Experiments) > 0 { - for iNdEx := len(m.Experiments) - 1; iNdEx >= 0; iNdEx-- { - size, err := m.Experiments[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0xa - } - } - return len(dAtA) - i, nil -} - -func (m *TCPSettings) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.CongestionControl != nil { - l = len(*m.CongestionControl) - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.CongestionLock { - n += 2 - } - if m.RtoMinMs != nil { - n += 1 + protohelpers.SizeOfVarint(uint64(*m.RtoMinMs)) - } - if m.InitialCongestionWindow != nil { - n += 1 + protohelpers.SizeOfVarint(uint64(*m.InitialCongestionWindow)) - } - if m.InitialReceiveWindow != nil { - n += 1 + protohelpers.SizeOfVarint(uint64(*m.InitialReceiveWindow)) - } - if m.QuickAck != nil { - n += 2 - } - if m.Ecn != nil { - n += 2 - } - n += len(m.unknownFields) - return n -} - -func (m *Treatment) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Id) + l = len(m.Hypothesis) if l > 0 { n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - if m.Revision != 0 { - n += 1 + protohelpers.SizeOfVarint(uint64(m.Revision)) + if m.Treatment != nil { + l = m.Treatment.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - l = len(m.Destination) - if l > 0 { + if m.Policy != nil { + l = m.Policy.SizeVT() n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - if m.Settings != nil { - l = m.Settings.SizeVT() + if m.Baseline != nil { + l = m.Baseline.SizeVT() n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - if m.EcnFabricVerified { - n += 2 + if len(m.CohortOrder) > 0 { + for _, s := range m.CohortOrder { + l = len(s) + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } } - n += len(m.unknownFields) - return n -} - -func (m *Assignment) SizeVT() (n int) { - if m == nil { - return 0 + if len(m.Phases) > 0 { + for _, e := range m.Phases { + l = e.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } } - var l int - _ = l - l = len(m.Id) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + if m.Status != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Status)) } - l = len(m.ExperimentId) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + if m.Revision != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Revision)) } - if m.Treatment != nil { - l = m.Treatment.SizeVT() + if m.ProposedAt != nil { + l = (*timestamppb.Timestamp)(m.ProposedAt).SizeVT() n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - if m.Mode != 0 { - n += 1 + protohelpers.SizeOfVarint(uint64(m.Mode)) - } - if m.LeaseExpiresAt != nil { - l = (*timestamppb.Timestamp)(m.LeaseExpiresAt).SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.Predecessor != nil { - l = m.Predecessor.SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.Generation != 0 { - n += 1 + protohelpers.SizeOfVarint(uint64(m.Generation)) - } - n += len(m.unknownFields) - return n -} - -func (m *DesiredState) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.AgentId) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.Revision != 0 { - n += 1 + protohelpers.SizeOfVarint(uint64(m.Revision)) - } - if m.Suspended { - n += 2 - } - if len(m.Assignments) > 0 { - for _, e := range m.Assignments { - l = e.SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - } - if m.IssuedAt != nil { - l = (*timestamppb.Timestamp)(m.IssuedAt).SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - n += len(m.unknownFields) - return n -} - -func (m *Capabilities) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.BootId) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - l = len(m.KernelRelease) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if len(m.CongestionAlgorithms) > 0 { - for _, s := range m.CongestionAlgorithms { - l = len(s) - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - } - if m.SimpleHostRouting { - n += 2 - } - if m.ActuatorEnabled { - n += 2 - } - n += len(m.unknownFields) - return n -} - -func (m *ApplicationRecord) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.Assignment != nil { - l = m.Assignment.SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.DesiredRevision != 0 { - n += 1 + protohelpers.SizeOfVarint(uint64(m.DesiredRevision)) - } - if m.Status != 0 { - n += 1 + protohelpers.SizeOfVarint(uint64(m.Status)) - } - if m.ReceivedAt != nil { - l = (*timestamppb.Timestamp)(m.ReceivedAt).SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.AttemptedAt != nil { - l = (*timestamppb.Timestamp)(m.AttemptedAt).SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.AppliedAt != nil { - l = (*timestamppb.Timestamp)(m.AppliedAt).SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.VerifiedAt != nil { - l = (*timestamppb.Timestamp)(m.VerifiedAt).SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.RemovedAt != nil { - l = (*timestamppb.Timestamp)(m.RemovedAt).SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - l = len(m.Error) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - l = len(m.InstalledRoute) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - l = len(m.InstalledBootId) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.ExpiredLatched { - n += 2 - } - n += len(m.unknownFields) - return n -} - -func (m *LocalState) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.Desired != nil { - l = m.Desired.SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.LocallySuspended { - n += 2 - } - l = len(m.SuspensionReason) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if len(m.Records) > 0 { - for _, e := range m.Records { - l = e.SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - } - if m.UpdatedAt != nil { - l = (*timestamppb.Timestamp)(m.UpdatedAt).SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - n += len(m.unknownFields) - return n -} - -func (m *CheckInRequest) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.AgentId) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - l = len(m.Location) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.Capabilities != nil { - l = m.Capabilities.SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.DesiredRevision != 0 { - n += 1 + protohelpers.SizeOfVarint(uint64(m.DesiredRevision)) - } - if len(m.Records) > 0 { - for _, e := range m.Records { - l = e.SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - } - if m.LocallySuspended { - n += 2 - } - n += len(m.unknownFields) - return n -} - -func (m *CheckInResponse) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.Desired != nil { - l = m.Desired.SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - n += len(m.unknownFields) - return n -} - -func (m *GetDesiredStateRequest) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.AgentId) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - n += len(m.unknownFields) - return n -} - -func (m *GetDesiredStateResponse) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.Desired != nil { - l = m.Desired.SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - n += len(m.unknownFields) - return n -} - -func (m *WatchDesiredStateRequest) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.AgentId) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.AfterRevision != 0 { - n += 1 + protohelpers.SizeOfVarint(uint64(m.AfterRevision)) - } - n += len(m.unknownFields) - return n -} - -func (m *WatchDesiredStateResponse) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.Revision != 0 { - n += 1 + protohelpers.SizeOfVarint(uint64(m.Revision)) - } - n += len(m.unknownFields) - return n -} - -func (m *HostEvidence) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.AgentId) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.Sockets != 0 { - n += 1 + protohelpers.SizeOfVarint(uint64(m.Sockets)) - } - if m.BytesAcked != 0 { - n += 1 + protohelpers.SizeOfVarint(uint64(m.BytesAcked)) - } - if m.GoodputBps != 0 { - n += 9 - } - if m.RetransmissionRatio != 0 { - n += 9 - } - if m.RttP95Ms != 0 { - n += 9 - } - if m.ExposedSockets != 0 { - n += 1 + protohelpers.SizeOfVarint(uint64(m.ExposedSockets)) - } - if m.CollectionHealthy { - n += 2 - } - n += len(m.unknownFields) - return n -} - -func (m *Evidence) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Id) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.AnalyzedAt != nil { - l = (*timestamppb.Timestamp)(m.AnalyzedAt).SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.WindowStart != nil { - l = (*timestamppb.Timestamp)(m.WindowStart).SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.WindowEnd != nil { - l = (*timestamppb.Timestamp)(m.WindowEnd).SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - l = len(m.QueryVersion) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - l = len(m.EnrichmentVersion) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if len(m.Hosts) > 0 { - for _, e := range m.Hosts { - l = e.SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - } - l = len(m.ArtifactSha256) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - n += len(m.unknownFields) - return n -} - -func (m *ExperimentPolicy) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.MinimumObservation != nil { - l = (*durationpb.Duration)(m.MinimumObservation).SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.MaximumPhase != nil { - l = (*durationpb.Duration)(m.MaximumPhase).SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.MinimumSocketsPerHost != 0 { - n += 1 + protohelpers.SizeOfVarint(uint64(m.MinimumSocketsPerHost)) - } - if m.MinimumBytesPerHost != 0 { - n += 1 + protohelpers.SizeOfVarint(uint64(m.MinimumBytesPerHost)) - } - if m.MinimumTreatedHosts != 0 { - n += 1 + protohelpers.SizeOfVarint(uint64(m.MinimumTreatedHosts)) - } - if m.MinimumImprovement != 0 { - n += 9 - } - if m.MaximumRetransmissionIncrease != 0 { - n += 9 - } - if m.MaximumRttIncrease != 0 { - n += 9 - } - if m.MaximumEvidenceAge != nil { - l = (*durationpb.Duration)(m.MaximumEvidenceAge).SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - n += len(m.unknownFields) - return n -} - -func (m *Phase) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.TargetHosts != 0 { - n += 1 + protohelpers.SizeOfVarint(uint64(m.TargetHosts)) - } - if m.IssuedAt != nil { - l = (*timestamppb.Timestamp)(m.IssuedAt).SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.ObservationStartedAt != nil { - l = (*timestamppb.Timestamp)(m.ObservationStartedAt).SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.EvaluatedAt != nil { - l = (*timestamppb.Timestamp)(m.EvaluatedAt).SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.Evidence != nil { - l = m.Evidence.SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - l = len(m.Outcome) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - l = len(m.Reviewer) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - l = len(m.Reason) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - n += len(m.unknownFields) - return n -} - -func (m *Experiment) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Id) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - l = len(m.Location) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - l = len(m.Hypothesis) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.Treatment != nil { - l = m.Treatment.SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.Policy != nil { - l = m.Policy.SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.Baseline != nil { - l = m.Baseline.SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if len(m.CohortOrder) > 0 { - for _, s := range m.CohortOrder { - l = len(s) - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - } - if len(m.Phases) > 0 { - for _, e := range m.Phases { - l = e.SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - } - if m.Status != 0 { - n += 1 + protohelpers.SizeOfVarint(uint64(m.Status)) - } - if m.Revision != 0 { - n += 1 + protohelpers.SizeOfVarint(uint64(m.Revision)) - } - if m.ProposedAt != nil { - l = (*timestamppb.Timestamp)(m.ProposedAt).SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.LatestEvidence != nil { - l = m.LatestEvidence.SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - l = len(m.LatestOutcome) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - n += len(m.unknownFields) - return n -} - -func (m *ProposeRequest) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.Experiment != nil { - l = m.Experiment.SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - l = len(m.RequestId) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - n += len(m.unknownFields) - return n -} - -func (m *ProposeResponse) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.Experiment != nil { - l = m.Experiment.SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - n += len(m.unknownFields) - return n -} - -func (m *AnalyzeRequest) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.ExperimentId) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.ExpectedRevision != 0 { - n += 1 + protohelpers.SizeOfVarint(uint64(m.ExpectedRevision)) - } - n += len(m.unknownFields) - return n -} - -func (m *AnalyzeResponse) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.Evidence != nil { - l = m.Evidence.SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - l = len(m.Outcome) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - n += len(m.unknownFields) - return n -} - -func (m *ReviewRequest) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.ExperimentId) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.ExpectedRevision != 0 { - n += 1 + protohelpers.SizeOfVarint(uint64(m.ExpectedRevision)) - } - if m.Approve { - n += 2 - } - l = len(m.Reason) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - l = len(m.RequestId) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.Evidence != nil { - l = m.Evidence.SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - n += len(m.unknownFields) - return n -} - -func (m *ReviewResponse) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.Experiment != nil { - l = m.Experiment.SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - n += len(m.unknownFields) - return n -} - -func (m *ControlRequest) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.ExperimentId) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - l = len(m.AgentId) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - l = len(m.Location) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.Fleet { - n += 2 - } - if m.Action != 0 { - n += 1 + protohelpers.SizeOfVarint(uint64(m.Action)) - } - l = len(m.Reason) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - l = len(m.RequestId) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - n += len(m.unknownFields) - return n -} - -func (m *ControlResponse) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.AffectedAgents != 0 { - n += 1 + protohelpers.SizeOfVarint(uint64(m.AffectedAgents)) - } - n += len(m.unknownFields) - return n -} - -func (m *InspectRequest) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.ExperimentId) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - l = len(m.AgentId) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - n += len(m.unknownFields) - return n -} - -func (m *InspectResponse) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if len(m.Experiments) > 0 { - for _, e := range m.Experiments { - l = e.SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - } - if len(m.Desired) > 0 { - for _, e := range m.Desired { - l = e.SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - } - if len(m.Observed) > 0 { - for _, e := range m.Observed { - l = e.SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - } - n += len(m.unknownFields) - return n -} - -func (m *TCPSettings) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: TCPSettings: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: TCPSettings: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CongestionControl", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - s := string(dAtA[iNdEx:postIndex]) - m.CongestionControl = &s - iNdEx = postIndex - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field CongestionLock", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - m.CongestionLock = bool(v != 0) - case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field RtoMinMs", wireType) - } - var v uint32 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= uint32(b&0x7F) << shift - if b < 0x80 { - break - } - } - m.RtoMinMs = &v - case 4: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field InitialCongestionWindow", wireType) - } - var v uint32 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= uint32(b&0x7F) << shift - if b < 0x80 { - break - } - } - m.InitialCongestionWindow = &v - case 5: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field InitialReceiveWindow", wireType) - } - var v uint32 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= uint32(b&0x7F) << shift - if b < 0x80 { - break - } - } - m.InitialReceiveWindow = &v - case 6: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field QuickAck", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - b := bool(v != 0) - m.QuickAck = &b - case 7: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Ecn", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - b := bool(v != 0) - m.Ecn = &b - default: - iNdEx = preIndex - skippy, err := protohelpers.Skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return protohelpers.ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *Treatment) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: Treatment: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: Treatment: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Id = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Revision", wireType) - } - m.Revision = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Revision |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Destination", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Destination = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Settings", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Settings == nil { - m.Settings = &TCPSettings{} - } - if err := m.Settings.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 5: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field EcnFabricVerified", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - m.EcnFabricVerified = bool(v != 0) - default: - iNdEx = preIndex - skippy, err := protohelpers.Skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return protohelpers.ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *Assignment) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: Assignment: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: Assignment: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Id = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ExperimentId", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.ExperimentId = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Treatment", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Treatment == nil { - m.Treatment = &Treatment{} - } - if err := m.Treatment.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 4: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Mode", wireType) - } - m.Mode = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Mode |= AssignmentMode(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field LeaseExpiresAt", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.LeaseExpiresAt == nil { - m.LeaseExpiresAt = ×tamppb1.Timestamp{} - } - if err := (*timestamppb.Timestamp)(m.LeaseExpiresAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 6: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Predecessor", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Predecessor == nil { - m.Predecessor = &Treatment{} - } - if err := m.Predecessor.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 7: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Generation", wireType) - } - m.Generation = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Generation |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - default: - iNdEx = preIndex - skippy, err := protohelpers.Skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return protohelpers.ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *DesiredState) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: DesiredState: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: DesiredState: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AgentId", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AgentId = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Revision", wireType) - } - m.Revision = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Revision |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Suspended", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - m.Suspended = bool(v != 0) - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Assignments", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Assignments = append(m.Assignments, &Assignment{}) - if err := m.Assignments[len(m.Assignments)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field IssuedAt", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.IssuedAt == nil { - m.IssuedAt = ×tamppb1.Timestamp{} - } - if err := (*timestamppb.Timestamp)(m.IssuedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := protohelpers.Skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return protohelpers.ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *Capabilities) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: Capabilities: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: Capabilities: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field BootId", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.BootId = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field KernelRelease", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.KernelRelease = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CongestionAlgorithms", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.CongestionAlgorithms = append(m.CongestionAlgorithms, string(dAtA[iNdEx:postIndex])) - iNdEx = postIndex - case 4: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field SimpleHostRouting", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - m.SimpleHostRouting = bool(v != 0) - case 5: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field ActuatorEnabled", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - m.ActuatorEnabled = bool(v != 0) - default: - iNdEx = preIndex - skippy, err := protohelpers.Skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return protohelpers.ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ApplicationRecord) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ApplicationRecord: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ApplicationRecord: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Assignment", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Assignment == nil { - m.Assignment = &Assignment{} - } - if err := m.Assignment.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field DesiredRevision", wireType) - } - m.DesiredRevision = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.DesiredRevision |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) - } - m.Status = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Status |= ApplyStatus(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ReceivedAt", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.ReceivedAt == nil { - m.ReceivedAt = ×tamppb1.Timestamp{} - } - if err := (*timestamppb.Timestamp)(m.ReceivedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AttemptedAt", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.AttemptedAt == nil { - m.AttemptedAt = ×tamppb1.Timestamp{} - } - if err := (*timestamppb.Timestamp)(m.AttemptedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 6: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppliedAt", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.AppliedAt == nil { - m.AppliedAt = ×tamppb1.Timestamp{} - } - if err := (*timestamppb.Timestamp)(m.AppliedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 7: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field VerifiedAt", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.VerifiedAt == nil { - m.VerifiedAt = ×tamppb1.Timestamp{} - } - if err := (*timestamppb.Timestamp)(m.VerifiedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 8: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field RemovedAt", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.RemovedAt == nil { - m.RemovedAt = ×tamppb1.Timestamp{} - } - if err := (*timestamppb.Timestamp)(m.RemovedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 9: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Error", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Error = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 10: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field InstalledRoute", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.InstalledRoute = append(m.InstalledRoute[:0], dAtA[iNdEx:postIndex]...) - if m.InstalledRoute == nil { - m.InstalledRoute = []byte{} - } - iNdEx = postIndex - case 11: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field InstalledBootId", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.InstalledBootId = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 12: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field ExpiredLatched", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - m.ExpiredLatched = bool(v != 0) - default: - iNdEx = preIndex - skippy, err := protohelpers.Skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return protohelpers.ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *LocalState) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: LocalState: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: LocalState: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Desired", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Desired == nil { - m.Desired = &DesiredState{} - } - if err := m.Desired.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field LocallySuspended", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - m.LocallySuspended = bool(v != 0) - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SuspensionReason", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.SuspensionReason = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Records", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Records = append(m.Records, &ApplicationRecord{}) - if err := m.Records[len(m.Records)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field UpdatedAt", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.UpdatedAt == nil { - m.UpdatedAt = ×tamppb1.Timestamp{} - } - if err := (*timestamppb.Timestamp)(m.UpdatedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := protohelpers.Skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return protohelpers.ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } + if m.LatestEvidence != nil { + l = m.LatestEvidence.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - - if iNdEx > l { - return io.ErrUnexpectedEOF + l = len(m.LatestOutcome) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - return nil + n += len(m.unknownFields) + return n } -func (m *CheckInRequest) UnmarshalVT(dAtA []byte) error { + +func (m *TCPSettings) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -4471,15 +1649,15 @@ func (m *CheckInRequest) UnmarshalVT(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: CheckInRequest: wiretype end group for non-group") + return fmt.Errorf("proto: TCPSettings: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: CheckInRequest: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: TCPSettings: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AgentId", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field CongestionControl", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -4507,13 +1685,14 @@ func (m *CheckInRequest) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AgentId = string(dAtA[iNdEx:postIndex]) + s := string(dAtA[iNdEx:postIndex]) + m.CongestionControl = &s iNdEx = postIndex case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Location", wireType) + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field CongestionLock", wireType) } - var stringLen uint64 + var v int for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -4523,29 +1702,17 @@ func (m *CheckInRequest) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + v |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Location = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex + m.CongestionLock = bool(v != 0) case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Capabilities", wireType) + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RtoMinMs", wireType) } - var msglen int + var v uint32 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -4555,33 +1722,17 @@ func (m *CheckInRequest) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + v |= uint32(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Capabilities == nil { - m.Capabilities = &Capabilities{} - } - if err := m.Capabilities.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex + m.RtoMinMs = &v case 4: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field DesiredRevision", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field InitialCongestionWindow", wireType) } - m.DesiredRevision = 0 + var v uint32 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -4591,16 +1742,17 @@ func (m *CheckInRequest) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.DesiredRevision |= uint64(b&0x7F) << shift + v |= uint32(b&0x7F) << shift if b < 0x80 { break } } + m.InitialCongestionWindow = &v case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Records", wireType) + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field InitialReceiveWindow", wireType) } - var msglen int + var v uint32 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -4610,29 +1762,15 @@ func (m *CheckInRequest) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + v |= uint32(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Records = append(m.Records, &ApplicationRecord{}) - if err := m.Records[len(m.Records)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex + m.InitialReceiveWindow = &v case 6: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field LocallySuspended", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field QuickAck", wireType) } var v int for shift := uint(0); ; shift += 7 { @@ -4649,150 +1787,13 @@ func (m *CheckInRequest) UnmarshalVT(dAtA []byte) error { break } } - m.LocallySuspended = bool(v != 0) - default: - iNdEx = preIndex - skippy, err := protohelpers.Skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return protohelpers.ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *CheckInResponse) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: CheckInResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: CheckInResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Desired", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Desired == nil { - m.Desired = &DesiredState{} - } - if err := m.Desired.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := protohelpers.Skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return protohelpers.ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *GetDesiredStateRequest) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: GetDesiredStateRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: GetDesiredStateRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AgentId", wireType) + b := bool(v != 0) + m.QuickAck = &b + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Ecn", wireType) } - var stringLen uint64 + var v int for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -4802,24 +1803,13 @@ func (m *GetDesiredStateRequest) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } } - m.AgentId = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex + b := bool(v != 0) + m.Ecn = &b default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -4842,7 +1832,7 @@ func (m *GetDesiredStateRequest) UnmarshalVT(dAtA []byte) error { } return nil } -func (m *GetDesiredStateResponse) UnmarshalVT(dAtA []byte) error { +func (m *Treatment) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -4865,17 +1855,17 @@ func (m *GetDesiredStateResponse) UnmarshalVT(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: GetDesiredStateResponse: wiretype end group for non-group") + return fmt.Errorf("proto: Treatment: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: GetDesiredStateResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: Treatment: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Desired", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -4885,82 +1875,46 @@ func (m *GetDesiredStateResponse) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return protohelpers.ErrInvalidLength } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return protohelpers.ErrInvalidLength } if postIndex > l { return io.ErrUnexpectedEOF } - if m.Desired == nil { - m.Desired = &DesiredState{} - } - if err := m.Desired.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.Id = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := protohelpers.Skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return protohelpers.ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *WatchDesiredStateRequest) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Revision", wireType) } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break + m.Revision = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Revision |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: WatchDesiredStateRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: WatchDesiredStateRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: + case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AgentId", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Destination", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -4988,13 +1942,13 @@ func (m *WatchDesiredStateRequest) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AgentId = string(dAtA[iNdEx:postIndex]) + m.Destination = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field AfterRevision", wireType) + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Settings", wireType) } - m.AfterRevision = 0 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -5004,67 +1958,33 @@ func (m *WatchDesiredStateRequest) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.AfterRevision |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - default: - iNdEx = preIndex - skippy, err := protohelpers.Skip(dAtA[iNdEx:]) - if err != nil { - return err + if msglen < 0 { + return protohelpers.ErrInvalidLength } - if (skippy < 0) || (iNdEx+skippy) < 0 { + postIndex := iNdEx + msglen + if postIndex < 0 { return protohelpers.ErrInvalidLength } - if (iNdEx + skippy) > l { + if postIndex > l { return io.ErrUnexpectedEOF } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *WatchDesiredStateResponse) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF + if m.Settings == nil { + m.Settings = &TCPSettings{} } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break + if err := m.Settings.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: WatchDesiredStateResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: WatchDesiredStateResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: + iNdEx = postIndex + case 5: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Revision", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field EcnFabricVerified", wireType) } - m.Revision = 0 + var v int for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -5074,11 +1994,12 @@ func (m *WatchDesiredStateResponse) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.Revision |= uint64(b&0x7F) << shift + v |= int(b&0x7F) << shift if b < 0x80 { break } } + m.EcnFabricVerified = bool(v != 0) default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -5101,7 +2022,7 @@ func (m *WatchDesiredStateResponse) UnmarshalVT(dAtA []byte) error { } return nil } -func (m *HostEvidence) UnmarshalVT(dAtA []byte) error { +func (m *Assignment) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -5124,15 +2045,15 @@ func (m *HostEvidence) UnmarshalVT(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: HostEvidence: wiretype end group for non-group") + return fmt.Errorf("proto: Assignment: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: HostEvidence: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: Assignment: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AgentId", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -5160,13 +2081,13 @@ func (m *HostEvidence) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AgentId = string(dAtA[iNdEx:postIndex]) + m.Id = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Sockets", wireType) + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExperimentId", wireType) } - m.Sockets = 0 + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -5176,16 +2097,29 @@ func (m *HostEvidence) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.Sockets |= uint64(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ExperimentId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field BytesAcked", wireType) + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Treatment", wireType) } - m.BytesAcked = 0 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -5195,49 +2129,88 @@ func (m *HostEvidence) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.BytesAcked |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Treatment == nil { + m.Treatment = &Treatment{} + } + if err := m.Treatment.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex case 4: - if wireType != 1 { - return fmt.Errorf("proto: wrong wireType = %d for field GoodputBps", wireType) + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Mode", wireType) + } + m.Mode = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Mode |= AssignmentMode(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LeaseExpiresAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } } - var v uint64 - if (iNdEx + 8) > l { - return io.ErrUnexpectedEOF + if msglen < 0 { + return protohelpers.ErrInvalidLength } - v = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) - iNdEx += 8 - m.GoodputBps = float64(math.Float64frombits(v)) - case 5: - if wireType != 1 { - return fmt.Errorf("proto: wrong wireType = %d for field RetransmissionRatio", wireType) + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength } - var v uint64 - if (iNdEx + 8) > l { + if postIndex > l { return io.ErrUnexpectedEOF } - v = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) - iNdEx += 8 - m.RetransmissionRatio = float64(math.Float64frombits(v)) - case 6: - if wireType != 1 { - return fmt.Errorf("proto: wrong wireType = %d for field RttP95Ms", wireType) + if m.LeaseExpiresAt == nil { + m.LeaseExpiresAt = ×tamppb1.Timestamp{} } - var v uint64 - if (iNdEx + 8) > l { - return io.ErrUnexpectedEOF + if err := (*timestamppb.Timestamp)(m.LeaseExpiresAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err } - v = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) - iNdEx += 8 - m.RttP95Ms = float64(math.Float64frombits(v)) - case 7: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field ExposedSockets", wireType) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Predecessor", wireType) } - m.ExposedSockets = 0 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -5247,16 +2220,33 @@ func (m *HostEvidence) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.ExposedSockets |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - case 8: + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Predecessor == nil { + m.Predecessor = &Treatment{} + } + if err := m.Predecessor.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field CollectionHealthy", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Generation", wireType) } - var v int + m.Generation = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -5266,12 +2256,11 @@ func (m *HostEvidence) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - v |= int(b&0x7F) << shift + m.Generation |= uint64(b&0x7F) << shift if b < 0x80 { break } } - m.CollectionHealthy = bool(v != 0) default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -5294,7 +2283,7 @@ func (m *HostEvidence) UnmarshalVT(dAtA []byte) error { } return nil } -func (m *Evidence) UnmarshalVT(dAtA []byte) error { +func (m *DesiredState) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -5317,15 +2306,15 @@ func (m *Evidence) UnmarshalVT(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: Evidence: wiretype end group for non-group") + return fmt.Errorf("proto: DesiredState: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: Evidence: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: DesiredState: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field AgentId", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -5353,13 +2342,13 @@ func (m *Evidence) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Id = string(dAtA[iNdEx:postIndex]) + m.AgentId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AnalyzedAt", wireType) + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Revision", wireType) } - var msglen int + m.Revision = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -5369,31 +2358,34 @@ func (m *Evidence) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + m.Revision |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.AnalyzedAt == nil { - m.AnalyzedAt = ×tamppb1.Timestamp{} + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Suspended", wireType) } - if err := (*timestamppb.Timestamp)(m.AnalyzedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } } - iNdEx = postIndex - case 3: + m.Suspended = bool(v != 0) + case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field WindowStart", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Assignments", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -5420,16 +2412,14 @@ func (m *Evidence) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.WindowStart == nil { - m.WindowStart = ×tamppb1.Timestamp{} - } - if err := (*timestamppb.Timestamp)(m.WindowStart).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + m.Assignments = append(m.Assignments, &Assignment{}) + if err := m.Assignments[len(m.Assignments)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 4: + case 5: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field WindowEnd", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field IssuedAt", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -5456,16 +2446,67 @@ func (m *Evidence) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.WindowEnd == nil { - m.WindowEnd = ×tamppb1.Timestamp{} + if m.IssuedAt == nil { + m.IssuedAt = ×tamppb1.Timestamp{} } - if err := (*timestamppb.Timestamp)(m.WindowEnd).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + if err := (*timestamppb.Timestamp)(m.IssuedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 5: + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Capabilities) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Capabilities: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Capabilities: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field QueryVersion", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field BootId", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -5493,11 +2534,11 @@ func (m *Evidence) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.QueryVersion = string(dAtA[iNdEx:postIndex]) + m.BootId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 6: + case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field EnrichmentVersion", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field KernelRelease", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -5525,13 +2566,13 @@ func (m *Evidence) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.EnrichmentVersion = string(dAtA[iNdEx:postIndex]) + m.KernelRelease = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 7: + case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Hosts", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field CongestionAlgorithms", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -5541,31 +2582,29 @@ func (m *Evidence) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return protohelpers.ErrInvalidLength } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return protohelpers.ErrInvalidLength } if postIndex > l { return io.ErrUnexpectedEOF } - m.Hosts = append(m.Hosts, &HostEvidence{}) - if err := m.Hosts[len(m.Hosts)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.CongestionAlgorithms = append(m.CongestionAlgorithms, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex - case 8: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ArtifactSha256", wireType) + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SimpleHostRouting", wireType) } - var stringLen uint64 + var v int for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -5575,24 +2614,32 @@ func (m *Evidence) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + v |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return protohelpers.ErrInvalidLength + m.SimpleHostRouting = bool(v != 0) + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ActuatorEnabled", wireType) } - if postIndex > l { - return io.ErrUnexpectedEOF + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } } - m.ArtifactSha256 = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex + m.ActuatorEnabled = bool(v != 0) default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -5615,7 +2662,7 @@ func (m *Evidence) UnmarshalVT(dAtA []byte) error { } return nil } -func (m *ExperimentPolicy) UnmarshalVT(dAtA []byte) error { +func (m *ApplicationRecord) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -5638,15 +2685,15 @@ func (m *ExperimentPolicy) UnmarshalVT(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: ExperimentPolicy: wiretype end group for non-group") + return fmt.Errorf("proto: ApplicationRecord: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: ExperimentPolicy: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: ApplicationRecord: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field MinimumObservation", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Assignment", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -5673,16 +2720,54 @@ func (m *ExperimentPolicy) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.MinimumObservation == nil { - m.MinimumObservation = &durationpb1.Duration{} + if m.Assignment == nil { + m.Assignment = &Assignment{} } - if err := (*durationpb.Duration)(m.MinimumObservation).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + if err := m.Assignment.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DesiredRevision", wireType) + } + m.DesiredRevision = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.DesiredRevision |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + m.Status = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Status |= ApplyStatus(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field MaximumPhase", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ReceivedAt", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -5709,18 +2794,18 @@ func (m *ExperimentPolicy) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.MaximumPhase == nil { - m.MaximumPhase = &durationpb1.Duration{} + if m.ReceivedAt == nil { + m.ReceivedAt = ×tamppb1.Timestamp{} } - if err := (*durationpb.Duration)(m.MaximumPhase).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + if err := (*timestamppb.Timestamp)(m.ReceivedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field MinimumSocketsPerHost", wireType) + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AttemptedAt", wireType) } - m.MinimumSocketsPerHost = 0 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -5730,16 +2815,33 @@ func (m *ExperimentPolicy) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.MinimumSocketsPerHost |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - case 4: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field MinimumBytesPerHost", wireType) + if msglen < 0 { + return protohelpers.ErrInvalidLength } - m.MinimumBytesPerHost = 0 + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.AttemptedAt == nil { + m.AttemptedAt = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.AttemptedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppliedAt", wireType) + } + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -5749,16 +2851,33 @@ func (m *ExperimentPolicy) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.MinimumBytesPerHost |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - case 5: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field MinimumTreatedHosts", wireType) + if msglen < 0 { + return protohelpers.ErrInvalidLength } - m.MinimumTreatedHosts = 0 + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.AppliedAt == nil { + m.AppliedAt = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.AppliedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field VerifiedAt", wireType) + } + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -5768,47 +2887,31 @@ func (m *ExperimentPolicy) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.MinimumTreatedHosts |= uint32(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - case 6: - if wireType != 1 { - return fmt.Errorf("proto: wrong wireType = %d for field MinimumImprovement", wireType) - } - var v uint64 - if (iNdEx + 8) > l { - return io.ErrUnexpectedEOF + if msglen < 0 { + return protohelpers.ErrInvalidLength } - v = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) - iNdEx += 8 - m.MinimumImprovement = float64(math.Float64frombits(v)) - case 7: - if wireType != 1 { - return fmt.Errorf("proto: wrong wireType = %d for field MaximumRetransmissionIncrease", wireType) + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength } - var v uint64 - if (iNdEx + 8) > l { + if postIndex > l { return io.ErrUnexpectedEOF } - v = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) - iNdEx += 8 - m.MaximumRetransmissionIncrease = float64(math.Float64frombits(v)) - case 8: - if wireType != 1 { - return fmt.Errorf("proto: wrong wireType = %d for field MaximumRttIncrease", wireType) + if m.VerifiedAt == nil { + m.VerifiedAt = ×tamppb1.Timestamp{} } - var v uint64 - if (iNdEx + 8) > l { - return io.ErrUnexpectedEOF + if err := (*timestamppb.Timestamp)(m.VerifiedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err } - v = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) - iNdEx += 8 - m.MaximumRttIncrease = float64(math.Float64frombits(v)) - case 9: + iNdEx = postIndex + case 8: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field MaximumEvidenceAge", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field RemovedAt", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -5835,69 +2938,18 @@ func (m *ExperimentPolicy) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.MaximumEvidenceAge == nil { - m.MaximumEvidenceAge = &durationpb1.Duration{} + if m.RemovedAt == nil { + m.RemovedAt = ×tamppb1.Timestamp{} } - if err := (*durationpb.Duration)(m.MaximumEvidenceAge).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + if err := (*timestamppb.Timestamp)(m.RemovedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := protohelpers.Skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return protohelpers.ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *Phase) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: Phase: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: Phase: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field TargetHosts", wireType) + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Error", wireType) } - m.TargetHosts = 0 + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -5907,16 +2959,29 @@ func (m *Phase) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.TargetHosts |= uint32(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - case 2: + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Error = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 10: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field IssuedAt", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field InstalledRoute", wireType) } - var msglen int + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -5926,33 +2991,31 @@ func (m *Phase) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + if byteLen < 0 { return protohelpers.ErrInvalidLength } - postIndex := iNdEx + msglen + postIndex := iNdEx + byteLen if postIndex < 0 { return protohelpers.ErrInvalidLength } if postIndex > l { return io.ErrUnexpectedEOF } - if m.IssuedAt == nil { - m.IssuedAt = ×tamppb1.Timestamp{} - } - if err := (*timestamppb.Timestamp)(m.IssuedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err + m.InstalledRoute = append(m.InstalledRoute[:0], dAtA[iNdEx:postIndex]...) + if m.InstalledRoute == nil { + m.InstalledRoute = []byte{} } iNdEx = postIndex - case 3: + case 11: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ObservationStartedAt", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field InstalledBootId", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -5962,33 +3025,29 @@ func (m *Phase) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return protohelpers.ErrInvalidLength } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return protohelpers.ErrInvalidLength } if postIndex > l { return io.ErrUnexpectedEOF } - if m.ObservationStartedAt == nil { - m.ObservationStartedAt = ×tamppb1.Timestamp{} - } - if err := (*timestamppb.Timestamp)(m.ObservationStartedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.InstalledBootId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field EvaluatedAt", wireType) + case 12: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ExpiredLatched", wireType) } - var msglen int + var v int for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -5998,31 +3057,66 @@ func (m *Phase) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + v |= int(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { - return protohelpers.ErrInvalidLength + m.ExpiredLatched = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err } - postIndex := iNdEx + msglen - if postIndex < 0 { + if (skippy < 0) || (iNdEx+skippy) < 0 { return protohelpers.ErrInvalidLength } - if postIndex > l { + if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } - if m.EvaluatedAt == nil { - m.EvaluatedAt = ×tamppb1.Timestamp{} + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *LocalState) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow } - if err := (*timestamppb.Timestamp)(m.EvaluatedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err + if iNdEx >= l { + return io.ErrUnexpectedEOF } - iNdEx = postIndex - case 5: + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: LocalState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: LocalState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Evidence", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Desired", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -6049,16 +3143,36 @@ func (m *Phase) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.Evidence == nil { - m.Evidence = &Evidence{} + if m.Desired == nil { + m.Desired = &DesiredState{} } - if err := m.Evidence.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + if err := m.Desired.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 6: + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field LocallySuspended", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.LocallySuspended = bool(v != 0) + case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Outcome", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field SuspensionReason", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -6086,13 +3200,13 @@ func (m *Phase) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Outcome = string(dAtA[iNdEx:postIndex]) + m.SuspensionReason = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 7: + case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Reviewer", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Records", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -6102,29 +3216,31 @@ func (m *Phase) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return protohelpers.ErrInvalidLength } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return protohelpers.ErrInvalidLength } if postIndex > l { return io.ErrUnexpectedEOF } - m.Reviewer = string(dAtA[iNdEx:postIndex]) + m.Records = append(m.Records, &ApplicationRecord{}) + if err := m.Records[len(m.Records)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex - case 8: + case 5: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Reason", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field UpdatedAt", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -6134,23 +3250,27 @@ func (m *Phase) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return protohelpers.ErrInvalidLength } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return protohelpers.ErrInvalidLength } if postIndex > l { return io.ErrUnexpectedEOF } - m.Reason = string(dAtA[iNdEx:postIndex]) + if m.UpdatedAt == nil { + m.UpdatedAt = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.UpdatedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex default: iNdEx = preIndex @@ -6174,7 +3294,7 @@ func (m *Phase) UnmarshalVT(dAtA []byte) error { } return nil } -func (m *Experiment) UnmarshalVT(dAtA []byte) error { +func (m *HostEvidence) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -6197,15 +3317,15 @@ func (m *Experiment) UnmarshalVT(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: Experiment: wiretype end group for non-group") + return fmt.Errorf("proto: HostEvidence: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: Experiment: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: HostEvidence: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field AgentId", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -6233,13 +3353,13 @@ func (m *Experiment) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Id = string(dAtA[iNdEx:postIndex]) + m.AgentId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Location", wireType) + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sockets", wireType) } - var stringLen uint64 + m.Sockets = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -6249,29 +3369,16 @@ func (m *Experiment) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + m.Sockets |= uint64(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Location = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Hypothesis", wireType) + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field BytesAcked", wireType) } - var stringLen uint64 + m.BytesAcked = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -6281,65 +3388,49 @@ func (m *Experiment) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + m.BytesAcked |= uint64(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Hypothesis = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Treatment", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return protohelpers.ErrInvalidLength + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field GoodputBps", wireType) } - postIndex := iNdEx + msglen - if postIndex < 0 { - return protohelpers.ErrInvalidLength + var v uint64 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF } - if postIndex > l { + v = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + m.GoodputBps = float64(math.Float64frombits(v)) + case 5: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field RetransmissionRatio", wireType) + } + var v uint64 + if (iNdEx + 8) > l { return io.ErrUnexpectedEOF } - if m.Treatment == nil { - m.Treatment = &Treatment{} + v = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + m.RetransmissionRatio = float64(math.Float64frombits(v)) + case 6: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field RttP95Ms", wireType) } - if err := m.Treatment.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err + var v uint64 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF } - iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Policy", wireType) + v = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + m.RttP95Ms = float64(math.Float64frombits(v)) + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ExposedSockets", wireType) } - var msglen int + m.ExposedSockets = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -6349,33 +3440,16 @@ func (m *Experiment) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + m.ExposedSockets |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Policy == nil { - m.Policy = &ExperimentPolicy{} - } - if err := m.Policy.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 6: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Baseline", wireType) + case 8: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field CollectionHealthy", wireType) } - var msglen int + var v int for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -6385,31 +3459,66 @@ func (m *Experiment) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + v |= int(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { - return protohelpers.ErrInvalidLength + m.CollectionHealthy = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err } - postIndex := iNdEx + msglen - if postIndex < 0 { + if (skippy < 0) || (iNdEx+skippy) < 0 { return protohelpers.ErrInvalidLength } - if postIndex > l { + if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } - if m.Baseline == nil { - m.Baseline = &Evidence{} + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Evidence) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow } - if err := m.Baseline.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err + if iNdEx >= l { + return io.ErrUnexpectedEOF } - iNdEx = postIndex - case 7: + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Evidence: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Evidence: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CohortOrder", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -6437,11 +3546,11 @@ func (m *Experiment) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.CohortOrder = append(m.CohortOrder, string(dAtA[iNdEx:postIndex])) + m.Id = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 8: + case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Phases", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field AnalyzedAt", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -6468,16 +3577,18 @@ func (m *Experiment) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Phases = append(m.Phases, &Phase{}) - if err := m.Phases[len(m.Phases)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + if m.AnalyzedAt == nil { + m.AnalyzedAt = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.AnalyzedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 9: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field WindowStart", wireType) } - m.Status = 0 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -6487,33 +3598,31 @@ func (m *Experiment) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.Status |= ExperimentStatus(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - case 10: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Revision", wireType) + if msglen < 0 { + return protohelpers.ErrInvalidLength } - m.Revision = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Revision |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength } - case 11: + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.WindowStart == nil { + m.WindowStart = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.WindowStart).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ProposedAt", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field WindowEnd", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -6540,18 +3649,18 @@ func (m *Experiment) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.ProposedAt == nil { - m.ProposedAt = ×tamppb1.Timestamp{} + if m.WindowEnd == nil { + m.WindowEnd = ×tamppb1.Timestamp{} } - if err := (*timestamppb.Timestamp)(m.ProposedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + if err := (*timestamppb.Timestamp)(m.WindowEnd).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 12: + case 5: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field LatestEvidence", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field QueryVersion", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -6561,31 +3670,27 @@ func (m *Experiment) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return protohelpers.ErrInvalidLength } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return protohelpers.ErrInvalidLength } if postIndex > l { return io.ErrUnexpectedEOF } - if m.LatestEvidence == nil { - m.LatestEvidence = &Evidence{} - } - if err := m.LatestEvidence.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.QueryVersion = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 13: + case 6: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field LatestOutcome", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field EnrichmentVersion", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -6610,65 +3715,14 @@ func (m *Experiment) UnmarshalVT(dAtA []byte) error { if postIndex < 0 { return protohelpers.ErrInvalidLength } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.LatestOutcome = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := protohelpers.Skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return protohelpers.ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ProposeRequest) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break + if postIndex > l { + return io.ErrUnexpectedEOF } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ProposeRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ProposeRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: + m.EnrichmentVersion = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 7: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Experiment", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Hosts", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -6695,16 +3749,14 @@ func (m *ProposeRequest) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.Experiment == nil { - m.Experiment = &Experiment{} - } - if err := m.Experiment.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + m.Hosts = append(m.Hosts, &HostEvidence{}) + if err := m.Hosts[len(m.Hosts)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 2: + case 8: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field RequestId", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ArtifactSha256", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -6732,7 +3784,7 @@ func (m *ProposeRequest) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.RequestId = string(dAtA[iNdEx:postIndex]) + m.ArtifactSha256 = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex @@ -6756,7 +3808,7 @@ func (m *ProposeRequest) UnmarshalVT(dAtA []byte) error { } return nil } -func (m *ProposeResponse) UnmarshalVT(dAtA []byte) error { +func (m *ExperimentPolicy) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -6779,15 +3831,15 @@ func (m *ProposeResponse) UnmarshalVT(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: ProposeResponse: wiretype end group for non-group") + return fmt.Errorf("proto: ExperimentPolicy: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: ProposeResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: ExperimentPolicy: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Experiment", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field MinimumObservation", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -6814,69 +3866,18 @@ func (m *ProposeResponse) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.Experiment == nil { - m.Experiment = &Experiment{} + if m.MinimumObservation == nil { + m.MinimumObservation = &durationpb1.Duration{} } - if err := m.Experiment.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + if err := (*durationpb.Duration)(m.MinimumObservation).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := protohelpers.Skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return protohelpers.ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *AnalyzeRequest) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: AnalyzeRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: AnalyzeRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: + case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ExperimentId", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field MaximumPhase", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -6886,29 +3887,33 @@ func (m *AnalyzeRequest) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return protohelpers.ErrInvalidLength } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return protohelpers.ErrInvalidLength } if postIndex > l { return io.ErrUnexpectedEOF } - m.ExperimentId = string(dAtA[iNdEx:postIndex]) + if m.MaximumPhase == nil { + m.MaximumPhase = &durationpb1.Duration{} + } + if err := (*durationpb.Duration)(m.MaximumPhase).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex - case 2: + case 3: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field ExpectedRevision", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field MinimumSocketsPerHost", wireType) } - m.ExpectedRevision = 0 + m.MinimumSocketsPerHost = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -6918,67 +3923,35 @@ func (m *AnalyzeRequest) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.ExpectedRevision |= uint64(b&0x7F) << shift + m.MinimumSocketsPerHost |= uint64(b&0x7F) << shift if b < 0x80 { break } } - default: - iNdEx = preIndex - skippy, err := protohelpers.Skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return protohelpers.ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *AnalyzeResponse) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MinimumBytesPerHost", wireType) } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break + m.MinimumBytesPerHost = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MinimumBytesPerHost |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: AnalyzeResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: AnalyzeResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Evidence", wireType) + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MinimumTreatedHosts", wireType) } - var msglen int + m.MinimumTreatedHosts = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -6988,33 +3961,49 @@ func (m *AnalyzeResponse) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + m.MinimumTreatedHosts |= uint32(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { - return protohelpers.ErrInvalidLength + case 6: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field MinimumImprovement", wireType) } - postIndex := iNdEx + msglen - if postIndex < 0 { - return protohelpers.ErrInvalidLength + var v uint64 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF } - if postIndex > l { + v = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + m.MinimumImprovement = float64(math.Float64frombits(v)) + case 7: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field MaximumRetransmissionIncrease", wireType) + } + var v uint64 + if (iNdEx + 8) > l { return io.ErrUnexpectedEOF } - if m.Evidence == nil { - m.Evidence = &Evidence{} + v = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + m.MaximumRetransmissionIncrease = float64(math.Float64frombits(v)) + case 8: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field MaximumRttIncrease", wireType) } - if err := m.Evidence.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err + var v uint64 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF } - iNdEx = postIndex - case 2: + v = uint64(binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + m.MaximumRttIncrease = float64(math.Float64frombits(v)) + case 9: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Outcome", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field MaximumEvidenceAge", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -7024,23 +4013,27 @@ func (m *AnalyzeResponse) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return protohelpers.ErrInvalidLength } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return protohelpers.ErrInvalidLength } if postIndex > l { return io.ErrUnexpectedEOF } - m.Outcome = string(dAtA[iNdEx:postIndex]) + if m.MaximumEvidenceAge == nil { + m.MaximumEvidenceAge = &durationpb1.Duration{} + } + if err := (*durationpb.Duration)(m.MaximumEvidenceAge).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex default: iNdEx = preIndex @@ -7064,7 +4057,7 @@ func (m *AnalyzeResponse) UnmarshalVT(dAtA []byte) error { } return nil } -func (m *ReviewRequest) UnmarshalVT(dAtA []byte) error { +func (m *Phase) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -7083,21 +4076,40 @@ func (m *ReviewRequest) UnmarshalVT(dAtA []byte) error { if b < 0x80 { break } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ReviewRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ReviewRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Phase: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Phase: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TargetHosts", wireType) + } + m.TargetHosts = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.TargetHosts |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ExperimentId", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field IssuedAt", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -7107,29 +4119,33 @@ func (m *ReviewRequest) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return protohelpers.ErrInvalidLength } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return protohelpers.ErrInvalidLength } if postIndex > l { return io.ErrUnexpectedEOF } - m.ExperimentId = string(dAtA[iNdEx:postIndex]) + if m.IssuedAt == nil { + m.IssuedAt = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.IssuedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field ExpectedRevision", wireType) + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ObservationStartedAt", wireType) } - m.ExpectedRevision = 0 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -7139,16 +4155,33 @@ func (m *ReviewRequest) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.ExpectedRevision |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Approve", wireType) + if msglen < 0 { + return protohelpers.ErrInvalidLength } - var v int + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ObservationStartedAt == nil { + m.ObservationStartedAt = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.ObservationStartedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field EvaluatedAt", wireType) + } + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -7158,17 +4191,33 @@ func (m *ReviewRequest) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - v |= int(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - m.Approve = bool(v != 0) - case 4: + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.EvaluatedAt == nil { + m.EvaluatedAt = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.EvaluatedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Reason", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Evidence", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -7178,27 +4227,31 @@ func (m *ReviewRequest) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return protohelpers.ErrInvalidLength } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return protohelpers.ErrInvalidLength } if postIndex > l { return io.ErrUnexpectedEOF } - m.Reason = string(dAtA[iNdEx:postIndex]) + if m.Evidence == nil { + m.Evidence = &Evidence{} + } + if err := m.Evidence.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex - case 5: + case 6: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field RequestId", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Outcome", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -7226,13 +4279,13 @@ func (m *ReviewRequest) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.RequestId = string(dAtA[iNdEx:postIndex]) + m.Outcome = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 6: + case 7: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Evidence", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Reviewer", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -7242,84 +4295,29 @@ func (m *ReviewRequest) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return protohelpers.ErrInvalidLength } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return protohelpers.ErrInvalidLength } if postIndex > l { return io.ErrUnexpectedEOF } - if m.Evidence == nil { - m.Evidence = &Evidence{} - } - if err := m.Evidence.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.Reviewer = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := protohelpers.Skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return protohelpers.ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ReviewResponse) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ReviewResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ReviewResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: + case 8: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Experiment", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Reason", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -7329,27 +4327,23 @@ func (m *ReviewResponse) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return protohelpers.ErrInvalidLength } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return protohelpers.ErrInvalidLength } if postIndex > l { return io.ErrUnexpectedEOF } - if m.Experiment == nil { - m.Experiment = &Experiment{} - } - if err := m.Experiment.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.Reason = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex @@ -7373,7 +4367,7 @@ func (m *ReviewResponse) UnmarshalVT(dAtA []byte) error { } return nil } -func (m *ControlRequest) UnmarshalVT(dAtA []byte) error { +func (m *Experiment) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -7396,15 +4390,15 @@ func (m *ControlRequest) UnmarshalVT(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: ControlRequest: wiretype end group for non-group") + return fmt.Errorf("proto: Experiment: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: ControlRequest: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: Experiment: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ExperimentId", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -7432,11 +4426,11 @@ func (m *ControlRequest) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.ExperimentId = string(dAtA[iNdEx:postIndex]) + m.Id = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AgentId", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Location", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -7464,11 +4458,11 @@ func (m *ControlRequest) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AgentId = string(dAtA[iNdEx:postIndex]) + m.Location = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Location", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Hypothesis", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -7496,52 +4490,13 @@ func (m *ControlRequest) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Location = string(dAtA[iNdEx:postIndex]) + m.Hypothesis = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 4: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Fleet", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - m.Fleet = bool(v != 0) - case 5: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Action", wireType) - } - m.Action = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Action |= ControlAction(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 6: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Reason", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Treatment", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -7551,29 +4506,33 @@ func (m *ControlRequest) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return protohelpers.ErrInvalidLength } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return protohelpers.ErrInvalidLength } if postIndex > l { return io.ErrUnexpectedEOF } - m.Reason = string(dAtA[iNdEx:postIndex]) + if m.Treatment == nil { + m.Treatment = &Treatment{} + } + if err := m.Treatment.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex - case 7: + case 5: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field RequestId", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Policy", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -7583,80 +4542,33 @@ func (m *ControlRequest) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return protohelpers.ErrInvalidLength } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return protohelpers.ErrInvalidLength } if postIndex > l { return io.ErrUnexpectedEOF } - m.RequestId = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := protohelpers.Skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return protohelpers.ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ControlResponse) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF + if m.Policy == nil { + m.Policy = &ExperimentPolicy{} } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break + if err := m.Policy.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ControlResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ControlResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field AffectedAgents", wireType) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Baseline", wireType) } - m.AffectedAgents = 0 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -7666,65 +4578,31 @@ func (m *ControlResponse) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.AffectedAgents |= uint32(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - default: - iNdEx = preIndex - skippy, err := protohelpers.Skip(dAtA[iNdEx:]) - if err != nil { - return err + if msglen < 0 { + return protohelpers.ErrInvalidLength } - if (skippy < 0) || (iNdEx+skippy) < 0 { + postIndex := iNdEx + msglen + if postIndex < 0 { return protohelpers.ErrInvalidLength } - if (iNdEx + skippy) > l { + if postIndex > l { return io.ErrUnexpectedEOF } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *InspectRequest) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF + if m.Baseline == nil { + m.Baseline = &Evidence{} } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break + if err := m.Baseline.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: InspectRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: InspectRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: + iNdEx = postIndex + case 7: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ExperimentId", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field CohortOrder", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -7752,13 +4630,13 @@ func (m *InspectRequest) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.ExperimentId = string(dAtA[iNdEx:postIndex]) + m.CohortOrder = append(m.CohortOrder, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex - case 2: + case 8: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AgentId", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Phases", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -7768,78 +4646,67 @@ func (m *InspectRequest) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return protohelpers.ErrInvalidLength } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return protohelpers.ErrInvalidLength } if postIndex > l { return io.ErrUnexpectedEOF } - m.AgentId = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := protohelpers.Skip(dAtA[iNdEx:]) - if err != nil { + m.Phases = append(m.Phases, &Phase{}) + if err := m.Phases[len(m.Phases)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { return err } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return protohelpers.ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF + iNdEx = postIndex + case 9: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *InspectResponse) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow + m.Status = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Status |= ExperimentStatus(b&0x7F) << shift + if b < 0x80 { + break + } } - if iNdEx >= l { - return io.ErrUnexpectedEOF + case 10: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Revision", wireType) } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break + m.Revision = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Revision |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: InspectResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: InspectResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: + case 11: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Experiments", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ProposedAt", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -7866,14 +4733,16 @@ func (m *InspectResponse) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Experiments = append(m.Experiments, &Experiment{}) - if err := m.Experiments[len(m.Experiments)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + if m.ProposedAt == nil { + m.ProposedAt = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.ProposedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 2: + case 12: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Desired", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field LatestEvidence", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -7900,16 +4769,18 @@ func (m *InspectResponse) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Desired = append(m.Desired, &DesiredState{}) - if err := m.Desired[len(m.Desired)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + if m.LatestEvidence == nil { + m.LatestEvidence = &Evidence{} + } + if err := m.LatestEvidence.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 3: + case 13: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Observed", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field LatestOutcome", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -7919,25 +4790,23 @@ func (m *InspectResponse) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return protohelpers.ErrInvalidLength } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return protohelpers.ErrInvalidLength } if postIndex > l { return io.ErrUnexpectedEOF } - m.Observed = append(m.Observed, &CheckInRequest{}) - if err := m.Observed[len(m.Observed)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.LatestOutcome = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex diff --git a/gen/openapi/xtcp_treatment/v1/agent.swagger.json b/gen/openapi/xtcp_treatment/v1/agent.swagger.json new file mode 100644 index 000000000..3cfab79d6 --- /dev/null +++ b/gen/openapi/xtcp_treatment/v1/agent.swagger.json @@ -0,0 +1,49 @@ +{ + "swagger": "2.0", + "info": { + "title": "xtcp_treatment/v1/agent.proto", + "version": "version not set" + }, + "tags": [ + { + "name": "AgentControlService" + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": {}, + "definitions": { + "protobufAny": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } + }, + "additionalProperties": true + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/protobufAny" + } + } + } + } + } +} diff --git a/gen/openapi/xtcp_treatment/v1/operator.swagger.json b/gen/openapi/xtcp_treatment/v1/operator.swagger.json new file mode 100644 index 000000000..eada7abea --- /dev/null +++ b/gen/openapi/xtcp_treatment/v1/operator.swagger.json @@ -0,0 +1,49 @@ +{ + "swagger": "2.0", + "info": { + "title": "xtcp_treatment/v1/operator.proto", + "version": "version not set" + }, + "tags": [ + { + "name": "OperatorService" + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": {}, + "definitions": { + "protobufAny": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } + }, + "additionalProperties": true + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/protobufAny" + } + } + } + } + } +} diff --git a/gen/openapi/xtcp_treatment/v1/treatment.swagger.json b/gen/openapi/xtcp_treatment/v1/treatment.swagger.json new file mode 100644 index 000000000..f50b3cdd6 --- /dev/null +++ b/gen/openapi/xtcp_treatment/v1/treatment.swagger.json @@ -0,0 +1,44 @@ +{ + "swagger": "2.0", + "info": { + "title": "xtcp_treatment/v1/treatment.proto", + "version": "version not set" + }, + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": {}, + "definitions": { + "protobufAny": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } + }, + "additionalProperties": true + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/protobufAny" + } + } + } + } + } +} diff --git a/gen/python/xtcp_treatment/v1/agent_pb2.py b/gen/python/xtcp_treatment/v1/agent_pb2.py new file mode 100644 index 000000000..b9f238e2c --- /dev/null +++ b/gen/python/xtcp_treatment/v1/agent_pb2.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: xtcp_treatment/v1/agent.proto +# Protobuf Python Version: 7.36.2 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 7, + 36, + 2, + '', + 'xtcp_treatment/v1/agent.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from buf.validate import validate_pb2 as buf_dot_validate_dot_validate__pb2 +from xtcp_treatment.v1 import treatment_pb2 as xtcp__treatment_dot_v1_dot_treatment__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1dxtcp_treatment/v1/agent.proto\x12\x11xtcp_treatment.v1\x1a\x1b\x62uf/validate/validate.proto\x1a!xtcp_treatment/v1/treatment.proto\"\x8d\x03\n\x0e\x43heckInRequest\x12\x44\n\x08\x61gent_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x07\x61gentId\x12\x45\n\x08location\x18\x02 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x08location\x12K\n\x0c\x63\x61pabilities\x18\x03 \x01(\x0b\x32\x1f.xtcp_treatment.v1.CapabilitiesB\x06\xbaH\x03\xc8\x01\x01R\x0c\x63\x61pabilities\x12)\n\x10\x64\x65sired_revision\x18\x04 \x01(\x04R\x0f\x64\x65siredRevision\x12I\n\x07records\x18\x05 \x03(\x0b\x32$.xtcp_treatment.v1.ApplicationRecordB\t\xbaH\x06\x92\x01\x03\x10\x80@R\x07records\x12+\n\x11locally_suspended\x18\x06 \x01(\x08R\x10locallySuspended\"L\n\x0f\x43heckInResponse\x12\x39\n\x07\x64\x65sired\x18\x01 \x01(\x0b\x32\x1f.xtcp_treatment.v1.DesiredStateR\x07\x64\x65sired\"^\n\x16GetDesiredStateRequest\x12\x44\n\x08\x61gent_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x07\x61gentId\"T\n\x17GetDesiredStateResponse\x12\x39\n\x07\x64\x65sired\x18\x01 \x01(\x0b\x32\x1f.xtcp_treatment.v1.DesiredStateR\x07\x64\x65sired\"\x87\x01\n\x18WatchDesiredStateRequest\x12\x44\n\x08\x61gent_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x07\x61gentId\x12%\n\x0e\x61\x66ter_revision\x18\x02 \x01(\x04R\rafterRevision\"7\n\x19WatchDesiredStateResponse\x12\x1a\n\x08revision\x18\x01 \x01(\x04R\x08revision2\xd5\x02\n\x13\x41gentControlService\x12V\n\x07\x43heckIn\x12!.xtcp_treatment.v1.CheckInRequest\x1a\".xtcp_treatment.v1.CheckInResponse\"\x04\xd0\xf3\x18\x01\x12n\n\x0fGetDesiredState\x12).xtcp_treatment.v1.GetDesiredStateRequest\x1a*.xtcp_treatment.v1.GetDesiredStateResponse\"\x04\xd0\xf3\x18\x01\x12v\n\x11WatchDesiredState\x12+.xtcp_treatment.v1.WatchDesiredStateRequest\x1a,.xtcp_treatment.v1.WatchDesiredStateResponse\"\x04\xd0\xf3\x18\x01\x30\x01\x42\xbc\x01\n\x15\x63om.xtcp_treatment.v1B\nAgentProtoP\x01Z6github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment\xa2\x02\x03XXX\xaa\x02\x10XtcpTreatment.V1\xca\x02\x10XtcpTreatment\\V1\xe2\x02\x1cXtcpTreatment\\V1\\GPBMetadata\xea\x02\x11XtcpTreatment::V1b\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'xtcp_treatment.v1.agent_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\025com.xtcp_treatment.v1B\nAgentProtoP\001Z6github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment\242\002\003XXX\252\002\020XtcpTreatment.V1\312\002\020XtcpTreatment\\V1\342\002\034XtcpTreatment\\V1\\GPBMetadata\352\002\021XtcpTreatment::V1' + _globals['_CHECKINREQUEST'].fields_by_name['agent_id']._loaded_options = None + _globals['_CHECKINREQUEST'].fields_by_name['agent_id']._serialized_options = b'\272H&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$' + _globals['_CHECKINREQUEST'].fields_by_name['location']._loaded_options = None + _globals['_CHECKINREQUEST'].fields_by_name['location']._serialized_options = b'\272H&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$' + _globals['_CHECKINREQUEST'].fields_by_name['capabilities']._loaded_options = None + _globals['_CHECKINREQUEST'].fields_by_name['capabilities']._serialized_options = b'\272H\003\310\001\001' + _globals['_CHECKINREQUEST'].fields_by_name['records']._loaded_options = None + _globals['_CHECKINREQUEST'].fields_by_name['records']._serialized_options = b'\272H\006\222\001\003\020\200@' + _globals['_GETDESIREDSTATEREQUEST'].fields_by_name['agent_id']._loaded_options = None + _globals['_GETDESIREDSTATEREQUEST'].fields_by_name['agent_id']._serialized_options = b'\272H&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$' + _globals['_WATCHDESIREDSTATEREQUEST'].fields_by_name['agent_id']._loaded_options = None + _globals['_WATCHDESIREDSTATEREQUEST'].fields_by_name['agent_id']._serialized_options = b'\272H&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$' + _globals['_AGENTCONTROLSERVICE'].methods_by_name['CheckIn']._loaded_options = None + _globals['_AGENTCONTROLSERVICE'].methods_by_name['CheckIn']._serialized_options = b'\320\363\030\001' + _globals['_AGENTCONTROLSERVICE'].methods_by_name['GetDesiredState']._loaded_options = None + _globals['_AGENTCONTROLSERVICE'].methods_by_name['GetDesiredState']._serialized_options = b'\320\363\030\001' + _globals['_AGENTCONTROLSERVICE'].methods_by_name['WatchDesiredState']._loaded_options = None + _globals['_AGENTCONTROLSERVICE'].methods_by_name['WatchDesiredState']._serialized_options = b'\320\363\030\001' + _globals['_CHECKINREQUEST']._serialized_start=117 + _globals['_CHECKINREQUEST']._serialized_end=514 + _globals['_CHECKINRESPONSE']._serialized_start=516 + _globals['_CHECKINRESPONSE']._serialized_end=592 + _globals['_GETDESIREDSTATEREQUEST']._serialized_start=594 + _globals['_GETDESIREDSTATEREQUEST']._serialized_end=688 + _globals['_GETDESIREDSTATERESPONSE']._serialized_start=690 + _globals['_GETDESIREDSTATERESPONSE']._serialized_end=774 + _globals['_WATCHDESIREDSTATEREQUEST']._serialized_start=777 + _globals['_WATCHDESIREDSTATEREQUEST']._serialized_end=912 + _globals['_WATCHDESIREDSTATERESPONSE']._serialized_start=914 + _globals['_WATCHDESIREDSTATERESPONSE']._serialized_end=969 + _globals['_AGENTCONTROLSERVICE']._serialized_start=972 + _globals['_AGENTCONTROLSERVICE']._serialized_end=1313 +# @@protoc_insertion_point(module_scope) diff --git a/gen/python/xtcp_treatment/v1/agent_pb2.pyi b/gen/python/xtcp_treatment/v1/agent_pb2.pyi new file mode 100644 index 000000000..1d1a91240 --- /dev/null +++ b/gen/python/xtcp_treatment/v1/agent_pb2.pyi @@ -0,0 +1,57 @@ +from buf.validate import validate_pb2 as _validate_pb2 +from xtcp_treatment.v1 import treatment_pb2 as _treatment_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class CheckInRequest(_message.Message): + __slots__ = ("agent_id", "location", "capabilities", "desired_revision", "records", "locally_suspended") + AGENT_ID_FIELD_NUMBER: _ClassVar[int] + LOCATION_FIELD_NUMBER: _ClassVar[int] + CAPABILITIES_FIELD_NUMBER: _ClassVar[int] + DESIRED_REVISION_FIELD_NUMBER: _ClassVar[int] + RECORDS_FIELD_NUMBER: _ClassVar[int] + LOCALLY_SUSPENDED_FIELD_NUMBER: _ClassVar[int] + agent_id: str + location: str + capabilities: _treatment_pb2.Capabilities + desired_revision: int + records: _containers.RepeatedCompositeFieldContainer[_treatment_pb2.ApplicationRecord] + locally_suspended: bool + def __init__(self, agent_id: _Optional[str] = ..., location: _Optional[str] = ..., capabilities: _Optional[_Union[_treatment_pb2.Capabilities, _Mapping]] = ..., desired_revision: _Optional[int] = ..., records: _Optional[_Iterable[_Union[_treatment_pb2.ApplicationRecord, _Mapping]]] = ..., locally_suspended: _Optional[bool] = ...) -> None: ... + +class CheckInResponse(_message.Message): + __slots__ = ("desired",) + DESIRED_FIELD_NUMBER: _ClassVar[int] + desired: _treatment_pb2.DesiredState + def __init__(self, desired: _Optional[_Union[_treatment_pb2.DesiredState, _Mapping]] = ...) -> None: ... + +class GetDesiredStateRequest(_message.Message): + __slots__ = ("agent_id",) + AGENT_ID_FIELD_NUMBER: _ClassVar[int] + agent_id: str + def __init__(self, agent_id: _Optional[str] = ...) -> None: ... + +class GetDesiredStateResponse(_message.Message): + __slots__ = ("desired",) + DESIRED_FIELD_NUMBER: _ClassVar[int] + desired: _treatment_pb2.DesiredState + def __init__(self, desired: _Optional[_Union[_treatment_pb2.DesiredState, _Mapping]] = ...) -> None: ... + +class WatchDesiredStateRequest(_message.Message): + __slots__ = ("agent_id", "after_revision") + AGENT_ID_FIELD_NUMBER: _ClassVar[int] + AFTER_REVISION_FIELD_NUMBER: _ClassVar[int] + agent_id: str + after_revision: int + def __init__(self, agent_id: _Optional[str] = ..., after_revision: _Optional[int] = ...) -> None: ... + +class WatchDesiredStateResponse(_message.Message): + __slots__ = ("revision",) + REVISION_FIELD_NUMBER: _ClassVar[int] + revision: int + def __init__(self, revision: _Optional[int] = ...) -> None: ... diff --git a/gen/python/xtcp_treatment/v1/agent_pb2_grpc.py b/gen/python/xtcp_treatment/v1/agent_pb2_grpc.py new file mode 100644 index 000000000..31fd206cb --- /dev/null +++ b/gen/python/xtcp_treatment/v1/agent_pb2_grpc.py @@ -0,0 +1,163 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +from xtcp_treatment.v1 import agent_pb2 as xtcp__treatment_dot_v1_dot_agent__pb2 + + +class AgentControlServiceStub: + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.CheckIn = channel.unary_unary( + '/xtcp_treatment.v1.AgentControlService/CheckIn', + request_serializer=xtcp__treatment_dot_v1_dot_agent__pb2.CheckInRequest.SerializeToString, + response_deserializer=xtcp__treatment_dot_v1_dot_agent__pb2.CheckInResponse.FromString, + _registered_method=True) + self.GetDesiredState = channel.unary_unary( + '/xtcp_treatment.v1.AgentControlService/GetDesiredState', + request_serializer=xtcp__treatment_dot_v1_dot_agent__pb2.GetDesiredStateRequest.SerializeToString, + response_deserializer=xtcp__treatment_dot_v1_dot_agent__pb2.GetDesiredStateResponse.FromString, + _registered_method=True) + self.WatchDesiredState = channel.unary_stream( + '/xtcp_treatment.v1.AgentControlService/WatchDesiredState', + request_serializer=xtcp__treatment_dot_v1_dot_agent__pb2.WatchDesiredStateRequest.SerializeToString, + response_deserializer=xtcp__treatment_dot_v1_dot_agent__pb2.WatchDesiredStateResponse.FromString, + _registered_method=True) + + +class AgentControlServiceServicer: + """Missing associated documentation comment in .proto file.""" + + def CheckIn(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetDesiredState(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def WatchDesiredState(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_AgentControlServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'CheckIn': grpc.unary_unary_rpc_method_handler( + servicer.CheckIn, + request_deserializer=xtcp__treatment_dot_v1_dot_agent__pb2.CheckInRequest.FromString, + response_serializer=xtcp__treatment_dot_v1_dot_agent__pb2.CheckInResponse.SerializeToString, + ), + 'GetDesiredState': grpc.unary_unary_rpc_method_handler( + servicer.GetDesiredState, + request_deserializer=xtcp__treatment_dot_v1_dot_agent__pb2.GetDesiredStateRequest.FromString, + response_serializer=xtcp__treatment_dot_v1_dot_agent__pb2.GetDesiredStateResponse.SerializeToString, + ), + 'WatchDesiredState': grpc.unary_stream_rpc_method_handler( + servicer.WatchDesiredState, + request_deserializer=xtcp__treatment_dot_v1_dot_agent__pb2.WatchDesiredStateRequest.FromString, + response_serializer=xtcp__treatment_dot_v1_dot_agent__pb2.WatchDesiredStateResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'xtcp_treatment.v1.AgentControlService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('xtcp_treatment.v1.AgentControlService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class AgentControlService: + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def CheckIn(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/xtcp_treatment.v1.AgentControlService/CheckIn', + xtcp__treatment_dot_v1_dot_agent__pb2.CheckInRequest.SerializeToString, + xtcp__treatment_dot_v1_dot_agent__pb2.CheckInResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def GetDesiredState(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/xtcp_treatment.v1.AgentControlService/GetDesiredState', + xtcp__treatment_dot_v1_dot_agent__pb2.GetDesiredStateRequest.SerializeToString, + xtcp__treatment_dot_v1_dot_agent__pb2.GetDesiredStateResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def WatchDesiredState(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_stream( + request, + target, + '/xtcp_treatment.v1.AgentControlService/WatchDesiredState', + xtcp__treatment_dot_v1_dot_agent__pb2.WatchDesiredStateRequest.SerializeToString, + xtcp__treatment_dot_v1_dot_agent__pb2.WatchDesiredStateResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/gen/python/xtcp_treatment/v1/operator_pb2.py b/gen/python/xtcp_treatment/v1/operator_pb2.py new file mode 100644 index 000000000..8c145d282 --- /dev/null +++ b/gen/python/xtcp_treatment/v1/operator_pb2.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: xtcp_treatment/v1/operator.proto +# Protobuf Python Version: 7.36.2 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 7, + 36, + 2, + '', + 'xtcp_treatment/v1/operator.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from buf.validate import validate_pb2 as buf_dot_validate_dot_validate__pb2 +from xtcp_treatment.v1 import agent_pb2 as xtcp__treatment_dot_v1_dot_agent__pb2 +from xtcp_treatment.v1 import treatment_pb2 as xtcp__treatment_dot_v1_dot_treatment__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n xtcp_treatment/v1/operator.proto\x12\x11xtcp_treatment.v1\x1a\x1b\x62uf/validate/validate.proto\x1a\x1dxtcp_treatment/v1/agent.proto\x1a!xtcp_treatment/v1/treatment.proto\"\xbb\x02\n\x0eProposeRequest\x12\x45\n\nexperiment\x18\x01 \x01(\x0b\x32\x1d.xtcp_treatment.v1.ExperimentB\x06\xbaH\x03\xc8\x01\x01R\nexperiment\x12H\n\nrequest_id\x18\x02 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\trequestId:\x97\x01\xbaH\x93\x01\x1a\x90\x01\n\x19propose.request_id_suffix\x12Mrequest_id must not end with .result (reserved for the paired response event)\x1a$!this.request_id.endsWith(\'.result\')\"P\n\x0fProposeResponse\x12=\n\nexperiment\x18\x01 \x01(\x0b\x32\x1d.xtcp_treatment.v1.ExperimentR\nexperiment\"\x8d\x01\n\x0e\x41nalyzeRequest\x12N\n\rexperiment_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x0c\x65xperimentId\x12+\n\x11\x65xpected_revision\x18\x02 \x01(\x04R\x10\x65xpectedRevision\"d\n\x0f\x41nalyzeResponse\x12\x37\n\x08\x65vidence\x18\x01 \x01(\x0b\x32\x1b.xtcp_treatment.v1.EvidenceR\x08\x65vidence\x12\x18\n\x07outcome\x18\x02 \x01(\tR\x07outcome\"\xe6\x03\n\rReviewRequest\x12N\n\rexperiment_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x0c\x65xperimentId\x12+\n\x11\x65xpected_revision\x18\x02 \x01(\x04R\x10\x65xpectedRevision\x12\x18\n\x07\x61pprove\x18\x03 \x01(\x08R\x07\x61pprove\x12\"\n\x06reason\x18\x04 \x01(\tB\n\xbaH\x07r\x05\x10\x01\x18\x80 R\x06reason\x12H\n\nrequest_id\x18\x05 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\trequestId\x12\x37\n\x08\x65vidence\x18\x06 \x01(\x0b\x32\x1b.xtcp_treatment.v1.EvidenceR\x08\x65vidence:\x96\x01\xbaH\x92\x01\x1a\x8f\x01\n\x18review.request_id_suffix\x12Mrequest_id must not end with .result (reserved for the paired response event)\x1a$!this.request_id.endsWith(\'.result\')\"O\n\x0eReviewResponse\x12=\n\nexperiment\x18\x01 \x01(\x0b\x32\x1d.xtcp_treatment.v1.ExperimentR\nexperiment\"\xba\x06\n\x0e\x43ontrolRequest\x12Q\n\rexperiment_id\x18\x01 \x01(\tB,\xbaH)r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$\xd8\x01\x01R\x0c\x65xperimentId\x12G\n\x08\x61gent_id\x18\x02 \x01(\tB,\xbaH)r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$\xd8\x01\x01R\x07\x61gentId\x12H\n\x08location\x18\x03 \x01(\tB,\xbaH)r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$\xd8\x01\x01R\x08location\x12\x14\n\x05\x66leet\x18\x04 \x01(\x08R\x05\x66leet\x12\x44\n\x06\x61\x63tion\x18\x05 \x01(\x0e\x32 .xtcp_treatment.v1.ControlActionB\n\xbaH\x07\x82\x01\x04\x10\x01 \x00R\x06\x61\x63tion\x12\"\n\x06reason\x18\x06 \x01(\tB\n\xbaH\x07r\x05\x10\x01\x18\x80 R\x06reason\x12H\n\nrequest_id\x18\x07 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\trequestId:\xf7\x02\xbaH\xf3\x02\x1a\x90\x01\n\x19\x63ontrol.request_id_suffix\x12Mrequest_id must not end with .result (reserved for the paired response event)\x1a$!this.request_id.endsWith(\'.result\')\x1a\xdd\x01\n\x14\x63ontrol.one_selector\x12\x45\x65xactly one of experiment_id, agent_id, location or fleet is required\x1a~(this.fleet ? 1 : 0) + (this.experiment_id != \'\' ? 1 : 0) + (this.agent_id != \'\' ? 1 : 0) + (this.location != \'\' ? 1 : 0) == 1\":\n\x0f\x43ontrolResponse\x12\'\n\x0f\x61\x66\x66\x65\x63ted_agents\x18\x01 \x01(\rR\x0e\x61\x66\x66\x65\x63tedAgents\"\xac\x01\n\x0eInspectRequest\x12Q\n\rexperiment_id\x18\x01 \x01(\tB,\xbaH)r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$\xd8\x01\x01R\x0c\x65xperimentId\x12G\n\x08\x61gent_id\x18\x02 \x01(\tB,\xbaH)r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$\xd8\x01\x01R\x07\x61gentId\"\xcc\x01\n\x0fInspectResponse\x12?\n\x0b\x65xperiments\x18\x01 \x03(\x0b\x32\x1d.xtcp_treatment.v1.ExperimentR\x0b\x65xperiments\x12\x39\n\x07\x64\x65sired\x18\x02 \x03(\x0b\x32\x1f.xtcp_treatment.v1.DesiredStateR\x07\x64\x65sired\x12=\n\x08observed\x18\x03 \x03(\x0b\x32!.xtcp_treatment.v1.CheckInRequestR\x08observed*\xb8\x01\n\rControlAction\x12\x1e\n\x1a\x43ONTROL_ACTION_UNSPECIFIED\x10\x00\x12\x18\n\x14\x43ONTROL_ACTION_PAUSE\x10\x01\x12\x1a\n\x16\x43ONTROL_ACTION_SUSPEND\x10\x02\x12\x19\n\x15\x43ONTROL_ACTION_RESUME\x10\x03\x12\x1b\n\x17\x43ONTROL_ACTION_ROLLBACK\x10\x04\x12\x19\n\x15\x43ONTROL_ACTION_RETIRE\x10\x05\x32\xc6\x03\n\x0fOperatorService\x12V\n\x07Propose\x12!.xtcp_treatment.v1.ProposeRequest\x1a\".xtcp_treatment.v1.ProposeResponse\"\x04\xd0\xf3\x18\x02\x12V\n\x07\x41nalyze\x12!.xtcp_treatment.v1.AnalyzeRequest\x1a\".xtcp_treatment.v1.AnalyzeResponse\"\x04\xd0\xf3\x18\x02\x12S\n\x06Review\x12 .xtcp_treatment.v1.ReviewRequest\x1a!.xtcp_treatment.v1.ReviewResponse\"\x04\xd0\xf3\x18\x02\x12V\n\x07\x43ontrol\x12!.xtcp_treatment.v1.ControlRequest\x1a\".xtcp_treatment.v1.ControlResponse\"\x04\xd0\xf3\x18\x02\x12V\n\x07Inspect\x12!.xtcp_treatment.v1.InspectRequest\x1a\".xtcp_treatment.v1.InspectResponse\"\x04\xd0\xf3\x18\x02\x42\xbf\x01\n\x15\x63om.xtcp_treatment.v1B\rOperatorProtoP\x01Z6github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment\xa2\x02\x03XXX\xaa\x02\x10XtcpTreatment.V1\xca\x02\x10XtcpTreatment\\V1\xe2\x02\x1cXtcpTreatment\\V1\\GPBMetadata\xea\x02\x11XtcpTreatment::V1b\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'xtcp_treatment.v1.operator_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\025com.xtcp_treatment.v1B\rOperatorProtoP\001Z6github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment\242\002\003XXX\252\002\020XtcpTreatment.V1\312\002\020XtcpTreatment\\V1\342\002\034XtcpTreatment\\V1\\GPBMetadata\352\002\021XtcpTreatment::V1' + _globals['_PROPOSEREQUEST'].fields_by_name['experiment']._loaded_options = None + _globals['_PROPOSEREQUEST'].fields_by_name['experiment']._serialized_options = b'\272H\003\310\001\001' + _globals['_PROPOSEREQUEST'].fields_by_name['request_id']._loaded_options = None + _globals['_PROPOSEREQUEST'].fields_by_name['request_id']._serialized_options = b'\272H&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$' + _globals['_PROPOSEREQUEST']._loaded_options = None + _globals['_PROPOSEREQUEST']._serialized_options = b'\272H\223\001\032\220\001\n\031propose.request_id_suffix\022Mrequest_id must not end with .result (reserved for the paired response event)\032$!this.request_id.endsWith(\'.result\')' + _globals['_ANALYZEREQUEST'].fields_by_name['experiment_id']._loaded_options = None + _globals['_ANALYZEREQUEST'].fields_by_name['experiment_id']._serialized_options = b'\272H&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$' + _globals['_REVIEWREQUEST'].fields_by_name['experiment_id']._loaded_options = None + _globals['_REVIEWREQUEST'].fields_by_name['experiment_id']._serialized_options = b'\272H&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$' + _globals['_REVIEWREQUEST'].fields_by_name['reason']._loaded_options = None + _globals['_REVIEWREQUEST'].fields_by_name['reason']._serialized_options = b'\272H\007r\005\020\001\030\200 ' + _globals['_REVIEWREQUEST'].fields_by_name['request_id']._loaded_options = None + _globals['_REVIEWREQUEST'].fields_by_name['request_id']._serialized_options = b'\272H&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$' + _globals['_REVIEWREQUEST']._loaded_options = None + _globals['_REVIEWREQUEST']._serialized_options = b'\272H\222\001\032\217\001\n\030review.request_id_suffix\022Mrequest_id must not end with .result (reserved for the paired response event)\032$!this.request_id.endsWith(\'.result\')' + _globals['_CONTROLREQUEST'].fields_by_name['experiment_id']._loaded_options = None + _globals['_CONTROLREQUEST'].fields_by_name['experiment_id']._serialized_options = b'\272H)r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$\330\001\001' + _globals['_CONTROLREQUEST'].fields_by_name['agent_id']._loaded_options = None + _globals['_CONTROLREQUEST'].fields_by_name['agent_id']._serialized_options = b'\272H)r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$\330\001\001' + _globals['_CONTROLREQUEST'].fields_by_name['location']._loaded_options = None + _globals['_CONTROLREQUEST'].fields_by_name['location']._serialized_options = b'\272H)r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$\330\001\001' + _globals['_CONTROLREQUEST'].fields_by_name['action']._loaded_options = None + _globals['_CONTROLREQUEST'].fields_by_name['action']._serialized_options = b'\272H\007\202\001\004\020\001 \000' + _globals['_CONTROLREQUEST'].fields_by_name['reason']._loaded_options = None + _globals['_CONTROLREQUEST'].fields_by_name['reason']._serialized_options = b'\272H\007r\005\020\001\030\200 ' + _globals['_CONTROLREQUEST'].fields_by_name['request_id']._loaded_options = None + _globals['_CONTROLREQUEST'].fields_by_name['request_id']._serialized_options = b'\272H&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$' + _globals['_CONTROLREQUEST']._loaded_options = None + _globals['_CONTROLREQUEST']._serialized_options = b'\272H\363\002\032\220\001\n\031control.request_id_suffix\022Mrequest_id must not end with .result (reserved for the paired response event)\032$!this.request_id.endsWith(\'.result\')\032\335\001\n\024control.one_selector\022Eexactly one of experiment_id, agent_id, location or fleet is required\032~(this.fleet ? 1 : 0) + (this.experiment_id != \'\' ? 1 : 0) + (this.agent_id != \'\' ? 1 : 0) + (this.location != \'\' ? 1 : 0) == 1' + _globals['_INSPECTREQUEST'].fields_by_name['experiment_id']._loaded_options = None + _globals['_INSPECTREQUEST'].fields_by_name['experiment_id']._serialized_options = b'\272H)r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$\330\001\001' + _globals['_INSPECTREQUEST'].fields_by_name['agent_id']._loaded_options = None + _globals['_INSPECTREQUEST'].fields_by_name['agent_id']._serialized_options = b'\272H)r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$\330\001\001' + _globals['_OPERATORSERVICE'].methods_by_name['Propose']._loaded_options = None + _globals['_OPERATORSERVICE'].methods_by_name['Propose']._serialized_options = b'\320\363\030\002' + _globals['_OPERATORSERVICE'].methods_by_name['Analyze']._loaded_options = None + _globals['_OPERATORSERVICE'].methods_by_name['Analyze']._serialized_options = b'\320\363\030\002' + _globals['_OPERATORSERVICE'].methods_by_name['Review']._loaded_options = None + _globals['_OPERATORSERVICE'].methods_by_name['Review']._serialized_options = b'\320\363\030\002' + _globals['_OPERATORSERVICE'].methods_by_name['Control']._loaded_options = None + _globals['_OPERATORSERVICE'].methods_by_name['Control']._serialized_options = b'\320\363\030\002' + _globals['_OPERATORSERVICE'].methods_by_name['Inspect']._loaded_options = None + _globals['_OPERATORSERVICE'].methods_by_name['Inspect']._serialized_options = b'\320\363\030\002' + _globals['_CONTROLACTION']._serialized_start=2638 + _globals['_CONTROLACTION']._serialized_end=2822 + _globals['_PROPOSEREQUEST']._serialized_start=151 + _globals['_PROPOSEREQUEST']._serialized_end=466 + _globals['_PROPOSERESPONSE']._serialized_start=468 + _globals['_PROPOSERESPONSE']._serialized_end=548 + _globals['_ANALYZEREQUEST']._serialized_start=551 + _globals['_ANALYZEREQUEST']._serialized_end=692 + _globals['_ANALYZERESPONSE']._serialized_start=694 + _globals['_ANALYZERESPONSE']._serialized_end=794 + _globals['_REVIEWREQUEST']._serialized_start=797 + _globals['_REVIEWREQUEST']._serialized_end=1283 + _globals['_REVIEWRESPONSE']._serialized_start=1285 + _globals['_REVIEWRESPONSE']._serialized_end=1364 + _globals['_CONTROLREQUEST']._serialized_start=1367 + _globals['_CONTROLREQUEST']._serialized_end=2193 + _globals['_CONTROLRESPONSE']._serialized_start=2195 + _globals['_CONTROLRESPONSE']._serialized_end=2253 + _globals['_INSPECTREQUEST']._serialized_start=2256 + _globals['_INSPECTREQUEST']._serialized_end=2428 + _globals['_INSPECTRESPONSE']._serialized_start=2431 + _globals['_INSPECTRESPONSE']._serialized_end=2635 + _globals['_OPERATORSERVICE']._serialized_start=2825 + _globals['_OPERATORSERVICE']._serialized_end=3279 +# @@protoc_insertion_point(module_scope) diff --git a/gen/python/xtcp_treatment/v1/operator_pb2.pyi b/gen/python/xtcp_treatment/v1/operator_pb2.pyi new file mode 100644 index 000000000..ba43e23e3 --- /dev/null +++ b/gen/python/xtcp_treatment/v1/operator_pb2.pyi @@ -0,0 +1,120 @@ +from buf.validate import validate_pb2 as _validate_pb2 +from xtcp_treatment.v1 import agent_pb2 as _agent_pb2 +from xtcp_treatment.v1 import treatment_pb2 as _treatment_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class ControlAction(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + CONTROL_ACTION_UNSPECIFIED: _ClassVar[ControlAction] + CONTROL_ACTION_PAUSE: _ClassVar[ControlAction] + CONTROL_ACTION_SUSPEND: _ClassVar[ControlAction] + CONTROL_ACTION_RESUME: _ClassVar[ControlAction] + CONTROL_ACTION_ROLLBACK: _ClassVar[ControlAction] + CONTROL_ACTION_RETIRE: _ClassVar[ControlAction] +CONTROL_ACTION_UNSPECIFIED: ControlAction +CONTROL_ACTION_PAUSE: ControlAction +CONTROL_ACTION_SUSPEND: ControlAction +CONTROL_ACTION_RESUME: ControlAction +CONTROL_ACTION_ROLLBACK: ControlAction +CONTROL_ACTION_RETIRE: ControlAction + +class ProposeRequest(_message.Message): + __slots__ = ("experiment", "request_id") + EXPERIMENT_FIELD_NUMBER: _ClassVar[int] + REQUEST_ID_FIELD_NUMBER: _ClassVar[int] + experiment: _treatment_pb2.Experiment + request_id: str + def __init__(self, experiment: _Optional[_Union[_treatment_pb2.Experiment, _Mapping]] = ..., request_id: _Optional[str] = ...) -> None: ... + +class ProposeResponse(_message.Message): + __slots__ = ("experiment",) + EXPERIMENT_FIELD_NUMBER: _ClassVar[int] + experiment: _treatment_pb2.Experiment + def __init__(self, experiment: _Optional[_Union[_treatment_pb2.Experiment, _Mapping]] = ...) -> None: ... + +class AnalyzeRequest(_message.Message): + __slots__ = ("experiment_id", "expected_revision") + EXPERIMENT_ID_FIELD_NUMBER: _ClassVar[int] + EXPECTED_REVISION_FIELD_NUMBER: _ClassVar[int] + experiment_id: str + expected_revision: int + def __init__(self, experiment_id: _Optional[str] = ..., expected_revision: _Optional[int] = ...) -> None: ... + +class AnalyzeResponse(_message.Message): + __slots__ = ("evidence", "outcome") + EVIDENCE_FIELD_NUMBER: _ClassVar[int] + OUTCOME_FIELD_NUMBER: _ClassVar[int] + evidence: _treatment_pb2.Evidence + outcome: str + def __init__(self, evidence: _Optional[_Union[_treatment_pb2.Evidence, _Mapping]] = ..., outcome: _Optional[str] = ...) -> None: ... + +class ReviewRequest(_message.Message): + __slots__ = ("experiment_id", "expected_revision", "approve", "reason", "request_id", "evidence") + EXPERIMENT_ID_FIELD_NUMBER: _ClassVar[int] + EXPECTED_REVISION_FIELD_NUMBER: _ClassVar[int] + APPROVE_FIELD_NUMBER: _ClassVar[int] + REASON_FIELD_NUMBER: _ClassVar[int] + REQUEST_ID_FIELD_NUMBER: _ClassVar[int] + EVIDENCE_FIELD_NUMBER: _ClassVar[int] + experiment_id: str + expected_revision: int + approve: bool + reason: str + request_id: str + evidence: _treatment_pb2.Evidence + def __init__(self, experiment_id: _Optional[str] = ..., expected_revision: _Optional[int] = ..., approve: _Optional[bool] = ..., reason: _Optional[str] = ..., request_id: _Optional[str] = ..., evidence: _Optional[_Union[_treatment_pb2.Evidence, _Mapping]] = ...) -> None: ... + +class ReviewResponse(_message.Message): + __slots__ = ("experiment",) + EXPERIMENT_FIELD_NUMBER: _ClassVar[int] + experiment: _treatment_pb2.Experiment + def __init__(self, experiment: _Optional[_Union[_treatment_pb2.Experiment, _Mapping]] = ...) -> None: ... + +class ControlRequest(_message.Message): + __slots__ = ("experiment_id", "agent_id", "location", "fleet", "action", "reason", "request_id") + EXPERIMENT_ID_FIELD_NUMBER: _ClassVar[int] + AGENT_ID_FIELD_NUMBER: _ClassVar[int] + LOCATION_FIELD_NUMBER: _ClassVar[int] + FLEET_FIELD_NUMBER: _ClassVar[int] + ACTION_FIELD_NUMBER: _ClassVar[int] + REASON_FIELD_NUMBER: _ClassVar[int] + REQUEST_ID_FIELD_NUMBER: _ClassVar[int] + experiment_id: str + agent_id: str + location: str + fleet: bool + action: ControlAction + reason: str + request_id: str + def __init__(self, experiment_id: _Optional[str] = ..., agent_id: _Optional[str] = ..., location: _Optional[str] = ..., fleet: _Optional[bool] = ..., action: _Optional[_Union[ControlAction, str]] = ..., reason: _Optional[str] = ..., request_id: _Optional[str] = ...) -> None: ... + +class ControlResponse(_message.Message): + __slots__ = ("affected_agents",) + AFFECTED_AGENTS_FIELD_NUMBER: _ClassVar[int] + affected_agents: int + def __init__(self, affected_agents: _Optional[int] = ...) -> None: ... + +class InspectRequest(_message.Message): + __slots__ = ("experiment_id", "agent_id") + EXPERIMENT_ID_FIELD_NUMBER: _ClassVar[int] + AGENT_ID_FIELD_NUMBER: _ClassVar[int] + experiment_id: str + agent_id: str + def __init__(self, experiment_id: _Optional[str] = ..., agent_id: _Optional[str] = ...) -> None: ... + +class InspectResponse(_message.Message): + __slots__ = ("experiments", "desired", "observed") + EXPERIMENTS_FIELD_NUMBER: _ClassVar[int] + DESIRED_FIELD_NUMBER: _ClassVar[int] + OBSERVED_FIELD_NUMBER: _ClassVar[int] + experiments: _containers.RepeatedCompositeFieldContainer[_treatment_pb2.Experiment] + desired: _containers.RepeatedCompositeFieldContainer[_treatment_pb2.DesiredState] + observed: _containers.RepeatedCompositeFieldContainer[_agent_pb2.CheckInRequest] + def __init__(self, experiments: _Optional[_Iterable[_Union[_treatment_pb2.Experiment, _Mapping]]] = ..., desired: _Optional[_Iterable[_Union[_treatment_pb2.DesiredState, _Mapping]]] = ..., observed: _Optional[_Iterable[_Union[_agent_pb2.CheckInRequest, _Mapping]]] = ...) -> None: ... diff --git a/gen/python/xtcp_treatment/v1/operator_pb2_grpc.py b/gen/python/xtcp_treatment/v1/operator_pb2_grpc.py new file mode 100644 index 000000000..4a8259dc3 --- /dev/null +++ b/gen/python/xtcp_treatment/v1/operator_pb2_grpc.py @@ -0,0 +1,249 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +from xtcp_treatment.v1 import operator_pb2 as xtcp__treatment_dot_v1_dot_operator__pb2 + + +class OperatorServiceStub: + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.Propose = channel.unary_unary( + '/xtcp_treatment.v1.OperatorService/Propose', + request_serializer=xtcp__treatment_dot_v1_dot_operator__pb2.ProposeRequest.SerializeToString, + response_deserializer=xtcp__treatment_dot_v1_dot_operator__pb2.ProposeResponse.FromString, + _registered_method=True) + self.Analyze = channel.unary_unary( + '/xtcp_treatment.v1.OperatorService/Analyze', + request_serializer=xtcp__treatment_dot_v1_dot_operator__pb2.AnalyzeRequest.SerializeToString, + response_deserializer=xtcp__treatment_dot_v1_dot_operator__pb2.AnalyzeResponse.FromString, + _registered_method=True) + self.Review = channel.unary_unary( + '/xtcp_treatment.v1.OperatorService/Review', + request_serializer=xtcp__treatment_dot_v1_dot_operator__pb2.ReviewRequest.SerializeToString, + response_deserializer=xtcp__treatment_dot_v1_dot_operator__pb2.ReviewResponse.FromString, + _registered_method=True) + self.Control = channel.unary_unary( + '/xtcp_treatment.v1.OperatorService/Control', + request_serializer=xtcp__treatment_dot_v1_dot_operator__pb2.ControlRequest.SerializeToString, + response_deserializer=xtcp__treatment_dot_v1_dot_operator__pb2.ControlResponse.FromString, + _registered_method=True) + self.Inspect = channel.unary_unary( + '/xtcp_treatment.v1.OperatorService/Inspect', + request_serializer=xtcp__treatment_dot_v1_dot_operator__pb2.InspectRequest.SerializeToString, + response_deserializer=xtcp__treatment_dot_v1_dot_operator__pb2.InspectResponse.FromString, + _registered_method=True) + + +class OperatorServiceServicer: + """Missing associated documentation comment in .proto file.""" + + def Propose(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Analyze(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Review(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Control(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Inspect(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_OperatorServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'Propose': grpc.unary_unary_rpc_method_handler( + servicer.Propose, + request_deserializer=xtcp__treatment_dot_v1_dot_operator__pb2.ProposeRequest.FromString, + response_serializer=xtcp__treatment_dot_v1_dot_operator__pb2.ProposeResponse.SerializeToString, + ), + 'Analyze': grpc.unary_unary_rpc_method_handler( + servicer.Analyze, + request_deserializer=xtcp__treatment_dot_v1_dot_operator__pb2.AnalyzeRequest.FromString, + response_serializer=xtcp__treatment_dot_v1_dot_operator__pb2.AnalyzeResponse.SerializeToString, + ), + 'Review': grpc.unary_unary_rpc_method_handler( + servicer.Review, + request_deserializer=xtcp__treatment_dot_v1_dot_operator__pb2.ReviewRequest.FromString, + response_serializer=xtcp__treatment_dot_v1_dot_operator__pb2.ReviewResponse.SerializeToString, + ), + 'Control': grpc.unary_unary_rpc_method_handler( + servicer.Control, + request_deserializer=xtcp__treatment_dot_v1_dot_operator__pb2.ControlRequest.FromString, + response_serializer=xtcp__treatment_dot_v1_dot_operator__pb2.ControlResponse.SerializeToString, + ), + 'Inspect': grpc.unary_unary_rpc_method_handler( + servicer.Inspect, + request_deserializer=xtcp__treatment_dot_v1_dot_operator__pb2.InspectRequest.FromString, + response_serializer=xtcp__treatment_dot_v1_dot_operator__pb2.InspectResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'xtcp_treatment.v1.OperatorService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('xtcp_treatment.v1.OperatorService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class OperatorService: + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def Propose(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/xtcp_treatment.v1.OperatorService/Propose', + xtcp__treatment_dot_v1_dot_operator__pb2.ProposeRequest.SerializeToString, + xtcp__treatment_dot_v1_dot_operator__pb2.ProposeResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Analyze(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/xtcp_treatment.v1.OperatorService/Analyze', + xtcp__treatment_dot_v1_dot_operator__pb2.AnalyzeRequest.SerializeToString, + xtcp__treatment_dot_v1_dot_operator__pb2.AnalyzeResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Review(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/xtcp_treatment.v1.OperatorService/Review', + xtcp__treatment_dot_v1_dot_operator__pb2.ReviewRequest.SerializeToString, + xtcp__treatment_dot_v1_dot_operator__pb2.ReviewResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Control(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/xtcp_treatment.v1.OperatorService/Control', + xtcp__treatment_dot_v1_dot_operator__pb2.ControlRequest.SerializeToString, + xtcp__treatment_dot_v1_dot_operator__pb2.ControlResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Inspect(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/xtcp_treatment.v1.OperatorService/Inspect', + xtcp__treatment_dot_v1_dot_operator__pb2.InspectRequest.SerializeToString, + xtcp__treatment_dot_v1_dot_operator__pb2.InspectResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/gen/python/xtcp_treatment/v1/treatment_pb2.py b/gen/python/xtcp_treatment/v1/treatment_pb2.py new file mode 100644 index 000000000..0abee4601 --- /dev/null +++ b/gen/python/xtcp_treatment/v1/treatment_pb2.py @@ -0,0 +1,207 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: xtcp_treatment/v1/treatment.proto +# Protobuf Python Version: 7.36.2 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 7, + 36, + 2, + '', + 'xtcp_treatment/v1/treatment.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from buf.validate import validate_pb2 as buf_dot_validate_dot_validate__pb2 +from google.protobuf import descriptor_pb2 as google_dot_protobuf_dot_descriptor__pb2 +from google.protobuf import duration_pb2 as google_dot_protobuf_dot_duration__pb2 +from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!xtcp_treatment/v1/treatment.proto\x12\x11xtcp_treatment.v1\x1a\x1b\x62uf/validate/validate.proto\x1a google/protobuf/descriptor.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\x8d\x07\n\x0bTCPSettings\x12Y\n\x12\x63ongestion_control\x18\x01 \x01(\tB%\xbaH\"r \x18\x0f\x32\x1c^[a-zA-Z0-9][a-zA-Z0-9_.-]*$H\x00R\x11\x63ongestionControl\x88\x01\x01\x12\'\n\x0f\x63ongestion_lock\x18\x02 \x01(\x08R\x0e\x63ongestionLock\x12.\n\nrto_min_ms\x18\x03 \x01(\rB\x0b\xbaH\x08*\x06\x18\xc0\xa9\x07(\x01H\x01R\x08rtoMinMs\x88\x01\x01\x12K\n\x19initial_congestion_window\x18\x04 \x01(\rB\n\xbaH\x07*\x05\x18\x80\x08(\x01H\x02R\x17initialCongestionWindow\x88\x01\x01\x12\x45\n\x16initial_receive_window\x18\x05 \x01(\rB\n\xbaH\x07*\x05\x18\x80\x08(\x01H\x03R\x14initialReceiveWindow\x88\x01\x01\x12 \n\tquick_ack\x18\x06 \x01(\x08H\x04R\x08quickAck\x88\x01\x01\x12\x15\n\x03\x65\x63n\x18\x07 \x01(\x08H\x05R\x03\x65\x63n\x88\x01\x01:\x87\x03\xbaH\x83\x03\x1a\xf3\x01\n\x16tcp_settings.non_empty\x12/a treatment must set at least one TCP parameter\x1a\xa7\x01has(this.congestion_control) || has(this.rto_min_ms) || has(this.initial_congestion_window) || has(this.initial_receive_window) || has(this.quick_ack) || has(this.ecn)\x1a\x8a\x01\n$tcp_settings.lock_requires_algorithm\x12+congestion_lock requires congestion_control\x1a\x35!this.congestion_lock || has(this.congestion_control)B\x15\n\x13_congestion_controlB\r\n\x0b_rto_min_msB\x1c\n\x1a_initial_congestion_windowB\x19\n\x17_initial_receive_windowB\x0c\n\n_quick_ackB\x06\n\x04_ecn\"\xd8\x03\n\tTreatment\x12\x39\n\x02id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x02id\x12#\n\x08revision\x18\x02 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\x08revision\x12,\n\x0b\x64\x65stination\x18\x03 \x01(\tB\n\xbaH\x07r\x05\x18\x31\xe8\x01\x01R\x0b\x64\x65stination\x12\x42\n\x08settings\x18\x04 \x01(\x0b\x32\x1e.xtcp_treatment.v1.TCPSettingsB\x06\xbaH\x03\xc8\x01\x01R\x08settings\x12.\n\x13\x65\x63n_fabric_verified\x18\x05 \x01(\x08R\x11\x65\x63nFabricVerified:\xc8\x01\xbaH\xc4\x01\x1a\xc1\x01\n\x1ctreatment.dctcp_requires_ecn\x12\x41\x44\x43TCP requires independently verified ECN fabric and explicit ECN\x1a^this.settings.congestion_control != \'dctcp\' || (this.ecn_fabric_verified && this.settings.ecn)\"\xfd\x05\n\nAssignment\x12\x39\n\x02id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x02id\x12N\n\rexperiment_id\x18\x02 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x0c\x65xperimentId\x12\x42\n\ttreatment\x18\x03 \x01(\x0b\x32\x1c.xtcp_treatment.v1.TreatmentB\x06\xbaH\x03\xc8\x01\x01R\ttreatment\x12\x41\n\x04mode\x18\x04 \x01(\x0e\x32!.xtcp_treatment.v1.AssignmentModeB\n\xbaH\x07\x82\x01\x04\x10\x01 \x00R\x04mode\x12\x44\n\x10lease_expires_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0eleaseExpiresAt\x12>\n\x0bpredecessor\x18\x06 \x01(\x0b\x32\x1c.xtcp_treatment.v1.TreatmentR\x0bpredecessor\x12\'\n\ngeneration\x18\x07 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\ngeneration:\xad\x02\xbaH\xa9\x02\x1a}\n\x1f\x61ssignment.trial_requires_lease\x12,a trial assignment requires lease_expires_at\x1a,this.mode != 1 || has(this.lease_expires_at)\x1a\xa7\x01\n\"assignment.predecessor_destination\x12+predecessor must cover the same destination\x1aT!has(this.predecessor) || this.predecessor.destination == this.treatment.destination\"\xa4\x02\n\x0c\x44\x65siredState\x12\x44\n\x08\x61gent_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x07\x61gentId\x12#\n\x08revision\x18\x02 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\x08revision\x12\x1c\n\tsuspended\x18\x03 \x01(\x08R\tsuspended\x12J\n\x0b\x61ssignments\x18\x04 \x03(\x0b\x32\x1d.xtcp_treatment.v1.AssignmentB\t\xbaH\x06\x92\x01\x03\x10\x80 R\x0b\x61ssignments\x12?\n\tissued_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\x08issuedAt\"\xa3\x02\n\x0c\x43\x61pabilities\x12\x42\n\x07\x62oot_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x06\x62ootId\x12/\n\x0ekernel_release\x18\x02 \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x02R\rkernelRelease\x12\x43\n\x15\x63ongestion_algorithms\x18\x03 \x03(\tB\x0e\xbaH\x0b\x92\x01\x08\x10@\"\x04r\x02\x18\x0fR\x14\x63ongestionAlgorithms\x12.\n\x13simple_host_routing\x18\x04 \x01(\x08R\x11simpleHostRouting\x12)\n\x10\x61\x63tuator_enabled\x18\x05 \x01(\x08R\x0f\x61\x63tuatorEnabled\"\xa8\x05\n\x11\x41pplicationRecord\x12\x45\n\nassignment\x18\x01 \x01(\x0b\x32\x1d.xtcp_treatment.v1.AssignmentB\x06\xbaH\x03\xc8\x01\x01R\nassignment\x12)\n\x10\x64\x65sired_revision\x18\x02 \x01(\x04R\x0f\x64\x65siredRevision\x12@\n\x06status\x18\x03 \x01(\x0e\x32\x1e.xtcp_treatment.v1.ApplyStatusB\x08\xbaH\x05\x82\x01\x02\x10\x01R\x06status\x12;\n\x0breceived_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\nreceivedAt\x12=\n\x0c\x61ttempted_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0b\x61ttemptedAt\x12\x39\n\napplied_at\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tappliedAt\x12;\n\x0bverified_at\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\nverifiedAt\x12\x39\n\nremoved_at\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tremovedAt\x12\x1e\n\x05\x65rror\x18\t \x01(\tB\x08\xbaH\x05r\x03\x18\x80 R\x05\x65rror\x12\x31\n\x0finstalled_route\x18\n \x01(\x0c\x42\x08\xbaH\x05z\x03\x18\x80 R\x0einstalledRoute\x12\x34\n\x11installed_boot_id\x18\x0b \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x01R\x0finstalledBootId\x12\'\n\x0f\x65xpired_latched\x18\x0c \x01(\x08R\x0e\x65xpiredLatched\"\xb1\x02\n\nLocalState\x12\x39\n\x07\x64\x65sired\x18\x01 \x01(\x0b\x32\x1f.xtcp_treatment.v1.DesiredStateR\x07\x64\x65sired\x12+\n\x11locally_suspended\x18\x02 \x01(\x08R\x10locallySuspended\x12\x35\n\x11suspension_reason\x18\x03 \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x08R\x10suspensionReason\x12I\n\x07records\x18\x04 \x03(\x0b\x32$.xtcp_treatment.v1.ApplicationRecordB\t\xbaH\x06\x92\x01\x03\x10\x80@R\x07records\x12\x39\n\nupdated_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tupdatedAt\"\x89\x04\n\x0cHostEvidence\x12\x44\n\x08\x61gent_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x07\x61gentId\x12\x18\n\x07sockets\x18\x02 \x01(\x04R\x07sockets\x12\x1f\n\x0b\x62ytes_acked\x18\x03 \x01(\x04R\nbytesAcked\x12\x31\n\x0bgoodput_bps\x18\x04 \x01(\x01\x42\x10\xbaH\r\x12\x0b)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\ngoodputBps\x12\x43\n\x14retransmission_ratio\x18\x05 \x01(\x01\x42\x10\xbaH\r\x12\x0b)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x13retransmissionRatio\x12.\n\nrtt_p95_ms\x18\x06 \x01(\x01\x42\x10\xbaH\r\x12\x0b)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x08rttP95Ms\x12\'\n\x0f\x65xposed_sockets\x18\x07 \x01(\x04R\x0e\x65xposedSockets\x12-\n\x12\x63ollection_healthy\x18\x08 \x01(\x08R\x11\x63ollectionHealthy:x\xbaHu\x1as\n$host_evidence.exposed_within_sockets\x12%exposed_sockets cannot exceed sockets\x1a$this.exposed_sockets <= this.sockets\"\xbc\x05\n\x08\x45vidence\x12\x39\n\x02id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x02id\x12\x43\n\x0b\x61nalyzed_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\nanalyzedAt\x12\x45\n\x0cwindow_start\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\x0bwindowStart\x12\x41\n\nwindow_end\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\twindowEnd\x12/\n\rquery_version\x18\x05 \x01(\tB\n\xbaH\x07r\x05\x10\x01\x18\x80\x02R\x0cqueryVersion\x12\x37\n\x12\x65nrichment_version\x18\x06 \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x02R\x11\x65nrichmentVersion\x12\x42\n\x05hosts\x18\x07 \x03(\x0b\x32\x1f.xtcp_treatment.v1.HostEvidenceB\x0b\xbaH\x08\x92\x01\x05\x08\x01\x10\x90NR\x05hosts\x12>\n\x0f\x61rtifact_sha256\x18\x08 \x01(\tB\x15\xbaH\x12r\x10\x32\x0e^[0-9a-f]{64}$R\x0e\x61rtifactSha256:\xb7\x01\xbaH\xb3\x01\x1a\xb0\x01\n\x11\x65vidence.interval\x12Owindow_end must follow window_start and analyzed_at must not precede window_end\x1aJthis.window_end > this.window_start && this.analyzed_at >= this.window_end\"\xec\x06\n\x10\x45xperimentPolicy\x12]\n\x13minimum_observation\x18\x01 \x01(\x0b\x32\x19.google.protobuf.DurationB\x11\xbaH\x0e\xaa\x01\x08\"\x04\x08\x80\xf5$*\x00\xc8\x01\x01R\x12minimumObservation\x12Q\n\rmaximum_phase\x18\x02 \x01(\x0b\x32\x19.google.protobuf.DurationB\x11\xbaH\x0e\xaa\x01\x08\"\x04\x08\x80\xf5$*\x00\xc8\x01\x01R\x0cmaximumPhase\x12@\n\x18minimum_sockets_per_host\x18\x03 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\x15minimumSocketsPerHost\x12<\n\x16minimum_bytes_per_host\x18\x04 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\x13minimumBytesPerHost\x12>\n\x15minimum_treated_hosts\x18\x05 \x01(\rB\n\xbaH\x07*\x05\x18\x90N(\x02R\x13minimumTreatedHosts\x12J\n\x13minimum_improvement\x18\x06 \x01(\x01\x42\x19\xbaH\x16\x12\x14\x19\x00\x00\x00\x00\x00\x00\xf0?)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x12minimumImprovement\x12\x61\n\x1fmaximum_retransmission_increase\x18\x07 \x01(\x01\x42\x19\xbaH\x16\x12\x14\x19\x00\x00\x00\x00\x00\x00\xf0?)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x1dmaximumRetransmissionIncrease\x12K\n\x14maximum_rtt_increase\x18\x08 \x01(\x01\x42\x19\xbaH\x16\x12\x14\x19\x00\x00\x00\x00\x00\x00\xf0?)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x12maximumRttIncrease\x12^\n\x14maximum_evidence_age\x18\t \x01(\x0b\x32\x19.google.protobuf.DurationB\x11\xbaH\x0e\xaa\x01\x08\"\x04\x08\x80\xf5$*\x00\xc8\x01\x01R\x12maximumEvidenceAge:\x89\x01\xbaH\x85\x01\x1a\x82\x01\n\x1fpolicy.observation_within_phase\x12/minimum_observation cannot exceed maximum_phase\x1a.this.minimum_observation <= this.maximum_phase\"\xa4\x03\n\x05Phase\x12-\n\x0ctarget_hosts\x18\x01 \x01(\rB\n\xbaH\x07*\x05\x18\x90N(\x01R\x0btargetHosts\x12\x37\n\tissued_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x08issuedAt\x12P\n\x16observation_started_at\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x14observationStartedAt\x12=\n\x0c\x65valuated_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0b\x65valuatedAt\x12\x37\n\x08\x65vidence\x18\x05 \x01(\x0b\x32\x1b.xtcp_treatment.v1.EvidenceR\x08\x65vidence\x12!\n\x07outcome\x18\x06 \x01(\tB\x07\xbaH\x04r\x02\x18@R\x07outcome\x12$\n\x08reviewer\x18\x07 \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x01R\x08reviewer\x12 \n\x06reason\x18\x08 \x01(\tB\x08\xbaH\x05r\x03\x18\x80 R\x06reason\"\xb0\x06\n\nExperiment\x12\x39\n\x02id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x02id\x12\x45\n\x08location\x18\x02 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x08location\x12*\n\nhypothesis\x18\x03 \x01(\tB\n\xbaH\x07r\x05\x10\x01\x18\x80@R\nhypothesis\x12\x42\n\ttreatment\x18\x04 \x01(\x0b\x32\x1c.xtcp_treatment.v1.TreatmentB\x06\xbaH\x03\xc8\x01\x01R\ttreatment\x12\x43\n\x06policy\x18\x05 \x01(\x0b\x32#.xtcp_treatment.v1.ExperimentPolicyB\x06\xbaH\x03\xc8\x01\x01R\x06policy\x12?\n\x08\x62\x61seline\x18\x06 \x01(\x0b\x32\x1b.xtcp_treatment.v1.EvidenceB\x06\xbaH\x03\xc8\x01\x01R\x08\x62\x61seline\x12X\n\x0c\x63ohort_order\x18\x07 \x03(\tB5\xbaH2\x92\x01/\x08\x03\x10\x90N\x18\x01\"&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x0b\x63ohortOrder\x12:\n\x06phases\x18\x08 \x03(\x0b\x32\x18.xtcp_treatment.v1.PhaseB\x08\xbaH\x05\x92\x01\x02\x10@R\x06phases\x12\x45\n\x06status\x18\t \x01(\x0e\x32#.xtcp_treatment.v1.ExperimentStatusB\x08\xbaH\x05\x82\x01\x02\x10\x01R\x06status\x12\x1a\n\x08revision\x18\n \x01(\x04R\x08revision\x12;\n\x0bproposed_at\x18\x0b \x01(\x0b\x32\x1a.google.protobuf.TimestampR\nproposedAt\x12\x44\n\x0flatest_evidence\x18\x0c \x01(\x0b\x32\x1b.xtcp_treatment.v1.EvidenceR\x0elatestEvidence\x12.\n\x0elatest_outcome\x18\r \x01(\tB\x07\xbaH\x04r\x02\x18@R\rlatestOutcome*?\n\x04Role\x12\x14\n\x10ROLE_UNSPECIFIED\x10\x00\x12\x0e\n\nROLE_AGENT\x10\x01\x12\x11\n\rROLE_OPERATOR\x10\x02*\xa4\x01\n\x0e\x41ssignmentMode\x12\x1f\n\x1b\x41SSIGNMENT_MODE_UNSPECIFIED\x10\x00\x12\x19\n\x15\x41SSIGNMENT_MODE_TRIAL\x10\x01\x12\x1a\n\x16\x41SSIGNMENT_MODE_STABLE\x10\x02\x12\x1d\n\x19\x41SSIGNMENT_MODE_SUSPENDED\x10\x03\x12\x1b\n\x17\x41SSIGNMENT_MODE_RETIRED\x10\x04*\xad\x01\n\x0b\x41pplyStatus\x12\x1c\n\x18\x41PPLY_STATUS_UNSPECIFIED\x10\x00\x12\x18\n\x14\x41PPLY_STATUS_PENDING\x10\x01\x12\x19\n\x15\x41PPLY_STATUS_VERIFIED\x10\x02\x12\x18\n\x14\x41PPLY_STATUS_REMOVED\x10\x03\x12\x18\n\x14\x41PPLY_STATUS_EXPIRED\x10\x04\x12\x17\n\x13\x41PPLY_STATUS_FAILED\x10\x05*\x95\x02\n\x10\x45xperimentStatus\x12!\n\x1d\x45XPERIMENT_STATUS_UNSPECIFIED\x10\x00\x12\x1e\n\x1a\x45XPERIMENT_STATUS_PROPOSED\x10\x01\x12\x1d\n\x19\x45XPERIMENT_STATUS_RUNNING\x10\x02\x12\x1c\n\x18\x45XPERIMENT_STATUS_PAUSED\x10\x03\x12\x1f\n\x1b\x45XPERIMENT_STATUS_SUSPENDED\x10\x04\x12\x1e\n\x1a\x45XPERIMENT_STATUS_COMPLETE\x10\x05\x12\x1d\n\x19\x45XPERIMENT_STATUS_RETIRED\x10\x06\x12!\n\x1d\x45XPERIMENT_STATUS_ROLLED_BACK\x10\x07:M\n\x04role\x12\x1e.google.protobuf.MethodOptions\x18\xba\x8e\x03 \x01(\x0e\x32\x17.xtcp_treatment.v1.RoleR\x04roleB\xc0\x01\n\x15\x63om.xtcp_treatment.v1B\x0eTreatmentProtoP\x01Z6github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment\xa2\x02\x03XXX\xaa\x02\x10XtcpTreatment.V1\xca\x02\x10XtcpTreatment\\V1\xe2\x02\x1cXtcpTreatment\\V1\\GPBMetadata\xea\x02\x11XtcpTreatment::V1b\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'xtcp_treatment.v1.treatment_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\025com.xtcp_treatment.v1B\016TreatmentProtoP\001Z6github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment\242\002\003XXX\252\002\020XtcpTreatment.V1\312\002\020XtcpTreatment\\V1\342\002\034XtcpTreatment\\V1\\GPBMetadata\352\002\021XtcpTreatment::V1' + _globals['_TCPSETTINGS'].fields_by_name['congestion_control']._loaded_options = None + _globals['_TCPSETTINGS'].fields_by_name['congestion_control']._serialized_options = b'\272H\"r \030\0172\034^[a-zA-Z0-9][a-zA-Z0-9_.-]*$' + _globals['_TCPSETTINGS'].fields_by_name['rto_min_ms']._loaded_options = None + _globals['_TCPSETTINGS'].fields_by_name['rto_min_ms']._serialized_options = b'\272H\010*\006\030\300\251\007(\001' + _globals['_TCPSETTINGS'].fields_by_name['initial_congestion_window']._loaded_options = None + _globals['_TCPSETTINGS'].fields_by_name['initial_congestion_window']._serialized_options = b'\272H\007*\005\030\200\010(\001' + _globals['_TCPSETTINGS'].fields_by_name['initial_receive_window']._loaded_options = None + _globals['_TCPSETTINGS'].fields_by_name['initial_receive_window']._serialized_options = b'\272H\007*\005\030\200\010(\001' + _globals['_TCPSETTINGS']._loaded_options = None + _globals['_TCPSETTINGS']._serialized_options = b'\272H\203\003\032\363\001\n\026tcp_settings.non_empty\022/a treatment must set at least one TCP parameter\032\247\001has(this.congestion_control) || has(this.rto_min_ms) || has(this.initial_congestion_window) || has(this.initial_receive_window) || has(this.quick_ack) || has(this.ecn)\032\212\001\n$tcp_settings.lock_requires_algorithm\022+congestion_lock requires congestion_control\0325!this.congestion_lock || has(this.congestion_control)' + _globals['_TREATMENT'].fields_by_name['id']._loaded_options = None + _globals['_TREATMENT'].fields_by_name['id']._serialized_options = b'\272H&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$' + _globals['_TREATMENT'].fields_by_name['revision']._loaded_options = None + _globals['_TREATMENT'].fields_by_name['revision']._serialized_options = b'\272H\0042\002 \000' + _globals['_TREATMENT'].fields_by_name['destination']._loaded_options = None + _globals['_TREATMENT'].fields_by_name['destination']._serialized_options = b'\272H\007r\005\0301\350\001\001' + _globals['_TREATMENT'].fields_by_name['settings']._loaded_options = None + _globals['_TREATMENT'].fields_by_name['settings']._serialized_options = b'\272H\003\310\001\001' + _globals['_TREATMENT']._loaded_options = None + _globals['_TREATMENT']._serialized_options = b'\272H\304\001\032\301\001\n\034treatment.dctcp_requires_ecn\022ADCTCP requires independently verified ECN fabric and explicit ECN\032^this.settings.congestion_control != \'dctcp\' || (this.ecn_fabric_verified && this.settings.ecn)' + _globals['_ASSIGNMENT'].fields_by_name['id']._loaded_options = None + _globals['_ASSIGNMENT'].fields_by_name['id']._serialized_options = b'\272H&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$' + _globals['_ASSIGNMENT'].fields_by_name['experiment_id']._loaded_options = None + _globals['_ASSIGNMENT'].fields_by_name['experiment_id']._serialized_options = b'\272H&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$' + _globals['_ASSIGNMENT'].fields_by_name['treatment']._loaded_options = None + _globals['_ASSIGNMENT'].fields_by_name['treatment']._serialized_options = b'\272H\003\310\001\001' + _globals['_ASSIGNMENT'].fields_by_name['mode']._loaded_options = None + _globals['_ASSIGNMENT'].fields_by_name['mode']._serialized_options = b'\272H\007\202\001\004\020\001 \000' + _globals['_ASSIGNMENT'].fields_by_name['generation']._loaded_options = None + _globals['_ASSIGNMENT'].fields_by_name['generation']._serialized_options = b'\272H\0042\002 \000' + _globals['_ASSIGNMENT']._loaded_options = None + _globals['_ASSIGNMENT']._serialized_options = b'\272H\251\002\032}\n\037assignment.trial_requires_lease\022,a trial assignment requires lease_expires_at\032,this.mode != 1 || has(this.lease_expires_at)\032\247\001\n\"assignment.predecessor_destination\022+predecessor must cover the same destination\032T!has(this.predecessor) || this.predecessor.destination == this.treatment.destination' + _globals['_DESIREDSTATE'].fields_by_name['agent_id']._loaded_options = None + _globals['_DESIREDSTATE'].fields_by_name['agent_id']._serialized_options = b'\272H&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$' + _globals['_DESIREDSTATE'].fields_by_name['revision']._loaded_options = None + _globals['_DESIREDSTATE'].fields_by_name['revision']._serialized_options = b'\272H\0042\002 \000' + _globals['_DESIREDSTATE'].fields_by_name['assignments']._loaded_options = None + _globals['_DESIREDSTATE'].fields_by_name['assignments']._serialized_options = b'\272H\006\222\001\003\020\200 ' + _globals['_DESIREDSTATE'].fields_by_name['issued_at']._loaded_options = None + _globals['_DESIREDSTATE'].fields_by_name['issued_at']._serialized_options = b'\272H\003\310\001\001' + _globals['_CAPABILITIES'].fields_by_name['boot_id']._loaded_options = None + _globals['_CAPABILITIES'].fields_by_name['boot_id']._serialized_options = b'\272H&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$' + _globals['_CAPABILITIES'].fields_by_name['kernel_release']._loaded_options = None + _globals['_CAPABILITIES'].fields_by_name['kernel_release']._serialized_options = b'\272H\005r\003\030\200\002' + _globals['_CAPABILITIES'].fields_by_name['congestion_algorithms']._loaded_options = None + _globals['_CAPABILITIES'].fields_by_name['congestion_algorithms']._serialized_options = b'\272H\013\222\001\010\020@\"\004r\002\030\017' + _globals['_APPLICATIONRECORD'].fields_by_name['assignment']._loaded_options = None + _globals['_APPLICATIONRECORD'].fields_by_name['assignment']._serialized_options = b'\272H\003\310\001\001' + _globals['_APPLICATIONRECORD'].fields_by_name['status']._loaded_options = None + _globals['_APPLICATIONRECORD'].fields_by_name['status']._serialized_options = b'\272H\005\202\001\002\020\001' + _globals['_APPLICATIONRECORD'].fields_by_name['error']._loaded_options = None + _globals['_APPLICATIONRECORD'].fields_by_name['error']._serialized_options = b'\272H\005r\003\030\200 ' + _globals['_APPLICATIONRECORD'].fields_by_name['installed_route']._loaded_options = None + _globals['_APPLICATIONRECORD'].fields_by_name['installed_route']._serialized_options = b'\272H\005z\003\030\200 ' + _globals['_APPLICATIONRECORD'].fields_by_name['installed_boot_id']._loaded_options = None + _globals['_APPLICATIONRECORD'].fields_by_name['installed_boot_id']._serialized_options = b'\272H\005r\003\030\200\001' + _globals['_LOCALSTATE'].fields_by_name['suspension_reason']._loaded_options = None + _globals['_LOCALSTATE'].fields_by_name['suspension_reason']._serialized_options = b'\272H\005r\003\030\200\010' + _globals['_LOCALSTATE'].fields_by_name['records']._loaded_options = None + _globals['_LOCALSTATE'].fields_by_name['records']._serialized_options = b'\272H\006\222\001\003\020\200@' + _globals['_HOSTEVIDENCE'].fields_by_name['agent_id']._loaded_options = None + _globals['_HOSTEVIDENCE'].fields_by_name['agent_id']._serialized_options = b'\272H&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$' + _globals['_HOSTEVIDENCE'].fields_by_name['goodput_bps']._loaded_options = None + _globals['_HOSTEVIDENCE'].fields_by_name['goodput_bps']._serialized_options = b'\272H\r\022\013)\000\000\000\000\000\000\000\000@\001' + _globals['_HOSTEVIDENCE'].fields_by_name['retransmission_ratio']._loaded_options = None + _globals['_HOSTEVIDENCE'].fields_by_name['retransmission_ratio']._serialized_options = b'\272H\r\022\013)\000\000\000\000\000\000\000\000@\001' + _globals['_HOSTEVIDENCE'].fields_by_name['rtt_p95_ms']._loaded_options = None + _globals['_HOSTEVIDENCE'].fields_by_name['rtt_p95_ms']._serialized_options = b'\272H\r\022\013)\000\000\000\000\000\000\000\000@\001' + _globals['_HOSTEVIDENCE']._loaded_options = None + _globals['_HOSTEVIDENCE']._serialized_options = b'\272Hu\032s\n$host_evidence.exposed_within_sockets\022%exposed_sockets cannot exceed sockets\032$this.exposed_sockets <= this.sockets' + _globals['_EVIDENCE'].fields_by_name['id']._loaded_options = None + _globals['_EVIDENCE'].fields_by_name['id']._serialized_options = b'\272H&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$' + _globals['_EVIDENCE'].fields_by_name['analyzed_at']._loaded_options = None + _globals['_EVIDENCE'].fields_by_name['analyzed_at']._serialized_options = b'\272H\003\310\001\001' + _globals['_EVIDENCE'].fields_by_name['window_start']._loaded_options = None + _globals['_EVIDENCE'].fields_by_name['window_start']._serialized_options = b'\272H\003\310\001\001' + _globals['_EVIDENCE'].fields_by_name['window_end']._loaded_options = None + _globals['_EVIDENCE'].fields_by_name['window_end']._serialized_options = b'\272H\003\310\001\001' + _globals['_EVIDENCE'].fields_by_name['query_version']._loaded_options = None + _globals['_EVIDENCE'].fields_by_name['query_version']._serialized_options = b'\272H\007r\005\020\001\030\200\002' + _globals['_EVIDENCE'].fields_by_name['enrichment_version']._loaded_options = None + _globals['_EVIDENCE'].fields_by_name['enrichment_version']._serialized_options = b'\272H\005r\003\030\200\002' + _globals['_EVIDENCE'].fields_by_name['hosts']._loaded_options = None + _globals['_EVIDENCE'].fields_by_name['hosts']._serialized_options = b'\272H\010\222\001\005\010\001\020\220N' + _globals['_EVIDENCE'].fields_by_name['artifact_sha256']._loaded_options = None + _globals['_EVIDENCE'].fields_by_name['artifact_sha256']._serialized_options = b'\272H\022r\0202\016^[0-9a-f]{64}$' + _globals['_EVIDENCE']._loaded_options = None + _globals['_EVIDENCE']._serialized_options = b'\272H\263\001\032\260\001\n\021evidence.interval\022Owindow_end must follow window_start and analyzed_at must not precede window_end\032Jthis.window_end > this.window_start && this.analyzed_at >= this.window_end' + _globals['_EXPERIMENTPOLICY'].fields_by_name['minimum_observation']._loaded_options = None + _globals['_EXPERIMENTPOLICY'].fields_by_name['minimum_observation']._serialized_options = b'\272H\016\252\001\010\"\004\010\200\365$*\000\310\001\001' + _globals['_EXPERIMENTPOLICY'].fields_by_name['maximum_phase']._loaded_options = None + _globals['_EXPERIMENTPOLICY'].fields_by_name['maximum_phase']._serialized_options = b'\272H\016\252\001\010\"\004\010\200\365$*\000\310\001\001' + _globals['_EXPERIMENTPOLICY'].fields_by_name['minimum_sockets_per_host']._loaded_options = None + _globals['_EXPERIMENTPOLICY'].fields_by_name['minimum_sockets_per_host']._serialized_options = b'\272H\0042\002 \000' + _globals['_EXPERIMENTPOLICY'].fields_by_name['minimum_bytes_per_host']._loaded_options = None + _globals['_EXPERIMENTPOLICY'].fields_by_name['minimum_bytes_per_host']._serialized_options = b'\272H\0042\002 \000' + _globals['_EXPERIMENTPOLICY'].fields_by_name['minimum_treated_hosts']._loaded_options = None + _globals['_EXPERIMENTPOLICY'].fields_by_name['minimum_treated_hosts']._serialized_options = b'\272H\007*\005\030\220N(\002' + _globals['_EXPERIMENTPOLICY'].fields_by_name['minimum_improvement']._loaded_options = None + _globals['_EXPERIMENTPOLICY'].fields_by_name['minimum_improvement']._serialized_options = b'\272H\026\022\024\031\000\000\000\000\000\000\360?)\000\000\000\000\000\000\000\000@\001' + _globals['_EXPERIMENTPOLICY'].fields_by_name['maximum_retransmission_increase']._loaded_options = None + _globals['_EXPERIMENTPOLICY'].fields_by_name['maximum_retransmission_increase']._serialized_options = b'\272H\026\022\024\031\000\000\000\000\000\000\360?)\000\000\000\000\000\000\000\000@\001' + _globals['_EXPERIMENTPOLICY'].fields_by_name['maximum_rtt_increase']._loaded_options = None + _globals['_EXPERIMENTPOLICY'].fields_by_name['maximum_rtt_increase']._serialized_options = b'\272H\026\022\024\031\000\000\000\000\000\000\360?)\000\000\000\000\000\000\000\000@\001' + _globals['_EXPERIMENTPOLICY'].fields_by_name['maximum_evidence_age']._loaded_options = None + _globals['_EXPERIMENTPOLICY'].fields_by_name['maximum_evidence_age']._serialized_options = b'\272H\016\252\001\010\"\004\010\200\365$*\000\310\001\001' + _globals['_EXPERIMENTPOLICY']._loaded_options = None + _globals['_EXPERIMENTPOLICY']._serialized_options = b'\272H\205\001\032\202\001\n\037policy.observation_within_phase\022/minimum_observation cannot exceed maximum_phase\032.this.minimum_observation <= this.maximum_phase' + _globals['_PHASE'].fields_by_name['target_hosts']._loaded_options = None + _globals['_PHASE'].fields_by_name['target_hosts']._serialized_options = b'\272H\007*\005\030\220N(\001' + _globals['_PHASE'].fields_by_name['outcome']._loaded_options = None + _globals['_PHASE'].fields_by_name['outcome']._serialized_options = b'\272H\004r\002\030@' + _globals['_PHASE'].fields_by_name['reviewer']._loaded_options = None + _globals['_PHASE'].fields_by_name['reviewer']._serialized_options = b'\272H\005r\003\030\200\001' + _globals['_PHASE'].fields_by_name['reason']._loaded_options = None + _globals['_PHASE'].fields_by_name['reason']._serialized_options = b'\272H\005r\003\030\200 ' + _globals['_EXPERIMENT'].fields_by_name['id']._loaded_options = None + _globals['_EXPERIMENT'].fields_by_name['id']._serialized_options = b'\272H&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$' + _globals['_EXPERIMENT'].fields_by_name['location']._loaded_options = None + _globals['_EXPERIMENT'].fields_by_name['location']._serialized_options = b'\272H&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$' + _globals['_EXPERIMENT'].fields_by_name['hypothesis']._loaded_options = None + _globals['_EXPERIMENT'].fields_by_name['hypothesis']._serialized_options = b'\272H\007r\005\020\001\030\200@' + _globals['_EXPERIMENT'].fields_by_name['treatment']._loaded_options = None + _globals['_EXPERIMENT'].fields_by_name['treatment']._serialized_options = b'\272H\003\310\001\001' + _globals['_EXPERIMENT'].fields_by_name['policy']._loaded_options = None + _globals['_EXPERIMENT'].fields_by_name['policy']._serialized_options = b'\272H\003\310\001\001' + _globals['_EXPERIMENT'].fields_by_name['baseline']._loaded_options = None + _globals['_EXPERIMENT'].fields_by_name['baseline']._serialized_options = b'\272H\003\310\001\001' + _globals['_EXPERIMENT'].fields_by_name['cohort_order']._loaded_options = None + _globals['_EXPERIMENT'].fields_by_name['cohort_order']._serialized_options = b'\272H2\222\001/\010\003\020\220N\030\001\"&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$' + _globals['_EXPERIMENT'].fields_by_name['phases']._loaded_options = None + _globals['_EXPERIMENT'].fields_by_name['phases']._serialized_options = b'\272H\005\222\001\002\020@' + _globals['_EXPERIMENT'].fields_by_name['status']._loaded_options = None + _globals['_EXPERIMENT'].fields_by_name['status']._serialized_options = b'\272H\005\202\001\002\020\001' + _globals['_EXPERIMENT'].fields_by_name['latest_outcome']._loaded_options = None + _globals['_EXPERIMENT'].fields_by_name['latest_outcome']._serialized_options = b'\272H\004r\002\030@' + _globals['_ROLE']._serialized_start=7267 + _globals['_ROLE']._serialized_end=7330 + _globals['_ASSIGNMENTMODE']._serialized_start=7333 + _globals['_ASSIGNMENTMODE']._serialized_end=7497 + _globals['_APPLYSTATUS']._serialized_start=7500 + _globals['_APPLYSTATUS']._serialized_end=7673 + _globals['_EXPERIMENTSTATUS']._serialized_start=7676 + _globals['_EXPERIMENTSTATUS']._serialized_end=7953 + _globals['_TCPSETTINGS']._serialized_start=185 + _globals['_TCPSETTINGS']._serialized_end=1094 + _globals['_TREATMENT']._serialized_start=1097 + _globals['_TREATMENT']._serialized_end=1569 + _globals['_ASSIGNMENT']._serialized_start=1572 + _globals['_ASSIGNMENT']._serialized_end=2337 + _globals['_DESIREDSTATE']._serialized_start=2340 + _globals['_DESIREDSTATE']._serialized_end=2632 + _globals['_CAPABILITIES']._serialized_start=2635 + _globals['_CAPABILITIES']._serialized_end=2926 + _globals['_APPLICATIONRECORD']._serialized_start=2929 + _globals['_APPLICATIONRECORD']._serialized_end=3609 + _globals['_LOCALSTATE']._serialized_start=3612 + _globals['_LOCALSTATE']._serialized_end=3917 + _globals['_HOSTEVIDENCE']._serialized_start=3920 + _globals['_HOSTEVIDENCE']._serialized_end=4441 + _globals['_EVIDENCE']._serialized_start=4444 + _globals['_EVIDENCE']._serialized_end=5144 + _globals['_EXPERIMENTPOLICY']._serialized_start=5147 + _globals['_EXPERIMENTPOLICY']._serialized_end=6023 + _globals['_PHASE']._serialized_start=6026 + _globals['_PHASE']._serialized_end=6446 + _globals['_EXPERIMENT']._serialized_start=6449 + _globals['_EXPERIMENT']._serialized_end=7265 +# @@protoc_insertion_point(module_scope) diff --git a/gen/python/xtcp_treatment/v1/treatment_pb2.pyi b/gen/python/xtcp_treatment/v1/treatment_pb2.pyi new file mode 100644 index 000000000..326e1eff1 --- /dev/null +++ b/gen/python/xtcp_treatment/v1/treatment_pb2.pyi @@ -0,0 +1,304 @@ +import datetime + +from buf.validate import validate_pb2 as _validate_pb2 +from google.protobuf import descriptor_pb2 as _descriptor_pb2 +from google.protobuf import duration_pb2 as _duration_pb2 +from google.protobuf import timestamp_pb2 as _timestamp_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class Role(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + ROLE_UNSPECIFIED: _ClassVar[Role] + ROLE_AGENT: _ClassVar[Role] + ROLE_OPERATOR: _ClassVar[Role] + +class AssignmentMode(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + ASSIGNMENT_MODE_UNSPECIFIED: _ClassVar[AssignmentMode] + ASSIGNMENT_MODE_TRIAL: _ClassVar[AssignmentMode] + ASSIGNMENT_MODE_STABLE: _ClassVar[AssignmentMode] + ASSIGNMENT_MODE_SUSPENDED: _ClassVar[AssignmentMode] + ASSIGNMENT_MODE_RETIRED: _ClassVar[AssignmentMode] + +class ApplyStatus(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + APPLY_STATUS_UNSPECIFIED: _ClassVar[ApplyStatus] + APPLY_STATUS_PENDING: _ClassVar[ApplyStatus] + APPLY_STATUS_VERIFIED: _ClassVar[ApplyStatus] + APPLY_STATUS_REMOVED: _ClassVar[ApplyStatus] + APPLY_STATUS_EXPIRED: _ClassVar[ApplyStatus] + APPLY_STATUS_FAILED: _ClassVar[ApplyStatus] + +class ExperimentStatus(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + EXPERIMENT_STATUS_UNSPECIFIED: _ClassVar[ExperimentStatus] + EXPERIMENT_STATUS_PROPOSED: _ClassVar[ExperimentStatus] + EXPERIMENT_STATUS_RUNNING: _ClassVar[ExperimentStatus] + EXPERIMENT_STATUS_PAUSED: _ClassVar[ExperimentStatus] + EXPERIMENT_STATUS_SUSPENDED: _ClassVar[ExperimentStatus] + EXPERIMENT_STATUS_COMPLETE: _ClassVar[ExperimentStatus] + EXPERIMENT_STATUS_RETIRED: _ClassVar[ExperimentStatus] + EXPERIMENT_STATUS_ROLLED_BACK: _ClassVar[ExperimentStatus] +ROLE_UNSPECIFIED: Role +ROLE_AGENT: Role +ROLE_OPERATOR: Role +ASSIGNMENT_MODE_UNSPECIFIED: AssignmentMode +ASSIGNMENT_MODE_TRIAL: AssignmentMode +ASSIGNMENT_MODE_STABLE: AssignmentMode +ASSIGNMENT_MODE_SUSPENDED: AssignmentMode +ASSIGNMENT_MODE_RETIRED: AssignmentMode +APPLY_STATUS_UNSPECIFIED: ApplyStatus +APPLY_STATUS_PENDING: ApplyStatus +APPLY_STATUS_VERIFIED: ApplyStatus +APPLY_STATUS_REMOVED: ApplyStatus +APPLY_STATUS_EXPIRED: ApplyStatus +APPLY_STATUS_FAILED: ApplyStatus +EXPERIMENT_STATUS_UNSPECIFIED: ExperimentStatus +EXPERIMENT_STATUS_PROPOSED: ExperimentStatus +EXPERIMENT_STATUS_RUNNING: ExperimentStatus +EXPERIMENT_STATUS_PAUSED: ExperimentStatus +EXPERIMENT_STATUS_SUSPENDED: ExperimentStatus +EXPERIMENT_STATUS_COMPLETE: ExperimentStatus +EXPERIMENT_STATUS_RETIRED: ExperimentStatus +EXPERIMENT_STATUS_ROLLED_BACK: ExperimentStatus +ROLE_FIELD_NUMBER: _ClassVar[int] +role: _descriptor.FieldDescriptor + +class TCPSettings(_message.Message): + __slots__ = ("congestion_control", "congestion_lock", "rto_min_ms", "initial_congestion_window", "initial_receive_window", "quick_ack", "ecn") + CONGESTION_CONTROL_FIELD_NUMBER: _ClassVar[int] + CONGESTION_LOCK_FIELD_NUMBER: _ClassVar[int] + RTO_MIN_MS_FIELD_NUMBER: _ClassVar[int] + INITIAL_CONGESTION_WINDOW_FIELD_NUMBER: _ClassVar[int] + INITIAL_RECEIVE_WINDOW_FIELD_NUMBER: _ClassVar[int] + QUICK_ACK_FIELD_NUMBER: _ClassVar[int] + ECN_FIELD_NUMBER: _ClassVar[int] + congestion_control: str + congestion_lock: bool + rto_min_ms: int + initial_congestion_window: int + initial_receive_window: int + quick_ack: bool + ecn: bool + def __init__(self, congestion_control: _Optional[str] = ..., congestion_lock: _Optional[bool] = ..., rto_min_ms: _Optional[int] = ..., initial_congestion_window: _Optional[int] = ..., initial_receive_window: _Optional[int] = ..., quick_ack: _Optional[bool] = ..., ecn: _Optional[bool] = ...) -> None: ... + +class Treatment(_message.Message): + __slots__ = ("id", "revision", "destination", "settings", "ecn_fabric_verified") + ID_FIELD_NUMBER: _ClassVar[int] + REVISION_FIELD_NUMBER: _ClassVar[int] + DESTINATION_FIELD_NUMBER: _ClassVar[int] + SETTINGS_FIELD_NUMBER: _ClassVar[int] + ECN_FABRIC_VERIFIED_FIELD_NUMBER: _ClassVar[int] + id: str + revision: int + destination: str + settings: TCPSettings + ecn_fabric_verified: bool + def __init__(self, id: _Optional[str] = ..., revision: _Optional[int] = ..., destination: _Optional[str] = ..., settings: _Optional[_Union[TCPSettings, _Mapping]] = ..., ecn_fabric_verified: _Optional[bool] = ...) -> None: ... + +class Assignment(_message.Message): + __slots__ = ("id", "experiment_id", "treatment", "mode", "lease_expires_at", "predecessor", "generation") + ID_FIELD_NUMBER: _ClassVar[int] + EXPERIMENT_ID_FIELD_NUMBER: _ClassVar[int] + TREATMENT_FIELD_NUMBER: _ClassVar[int] + MODE_FIELD_NUMBER: _ClassVar[int] + LEASE_EXPIRES_AT_FIELD_NUMBER: _ClassVar[int] + PREDECESSOR_FIELD_NUMBER: _ClassVar[int] + GENERATION_FIELD_NUMBER: _ClassVar[int] + id: str + experiment_id: str + treatment: Treatment + mode: AssignmentMode + lease_expires_at: _timestamp_pb2.Timestamp + predecessor: Treatment + generation: int + def __init__(self, id: _Optional[str] = ..., experiment_id: _Optional[str] = ..., treatment: _Optional[_Union[Treatment, _Mapping]] = ..., mode: _Optional[_Union[AssignmentMode, str]] = ..., lease_expires_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., predecessor: _Optional[_Union[Treatment, _Mapping]] = ..., generation: _Optional[int] = ...) -> None: ... + +class DesiredState(_message.Message): + __slots__ = ("agent_id", "revision", "suspended", "assignments", "issued_at") + AGENT_ID_FIELD_NUMBER: _ClassVar[int] + REVISION_FIELD_NUMBER: _ClassVar[int] + SUSPENDED_FIELD_NUMBER: _ClassVar[int] + ASSIGNMENTS_FIELD_NUMBER: _ClassVar[int] + ISSUED_AT_FIELD_NUMBER: _ClassVar[int] + agent_id: str + revision: int + suspended: bool + assignments: _containers.RepeatedCompositeFieldContainer[Assignment] + issued_at: _timestamp_pb2.Timestamp + def __init__(self, agent_id: _Optional[str] = ..., revision: _Optional[int] = ..., suspended: _Optional[bool] = ..., assignments: _Optional[_Iterable[_Union[Assignment, _Mapping]]] = ..., issued_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ... + +class Capabilities(_message.Message): + __slots__ = ("boot_id", "kernel_release", "congestion_algorithms", "simple_host_routing", "actuator_enabled") + BOOT_ID_FIELD_NUMBER: _ClassVar[int] + KERNEL_RELEASE_FIELD_NUMBER: _ClassVar[int] + CONGESTION_ALGORITHMS_FIELD_NUMBER: _ClassVar[int] + SIMPLE_HOST_ROUTING_FIELD_NUMBER: _ClassVar[int] + ACTUATOR_ENABLED_FIELD_NUMBER: _ClassVar[int] + boot_id: str + kernel_release: str + congestion_algorithms: _containers.RepeatedScalarFieldContainer[str] + simple_host_routing: bool + actuator_enabled: bool + def __init__(self, boot_id: _Optional[str] = ..., kernel_release: _Optional[str] = ..., congestion_algorithms: _Optional[_Iterable[str]] = ..., simple_host_routing: _Optional[bool] = ..., actuator_enabled: _Optional[bool] = ...) -> None: ... + +class ApplicationRecord(_message.Message): + __slots__ = ("assignment", "desired_revision", "status", "received_at", "attempted_at", "applied_at", "verified_at", "removed_at", "error", "installed_route", "installed_boot_id", "expired_latched") + ASSIGNMENT_FIELD_NUMBER: _ClassVar[int] + DESIRED_REVISION_FIELD_NUMBER: _ClassVar[int] + STATUS_FIELD_NUMBER: _ClassVar[int] + RECEIVED_AT_FIELD_NUMBER: _ClassVar[int] + ATTEMPTED_AT_FIELD_NUMBER: _ClassVar[int] + APPLIED_AT_FIELD_NUMBER: _ClassVar[int] + VERIFIED_AT_FIELD_NUMBER: _ClassVar[int] + REMOVED_AT_FIELD_NUMBER: _ClassVar[int] + ERROR_FIELD_NUMBER: _ClassVar[int] + INSTALLED_ROUTE_FIELD_NUMBER: _ClassVar[int] + INSTALLED_BOOT_ID_FIELD_NUMBER: _ClassVar[int] + EXPIRED_LATCHED_FIELD_NUMBER: _ClassVar[int] + assignment: Assignment + desired_revision: int + status: ApplyStatus + received_at: _timestamp_pb2.Timestamp + attempted_at: _timestamp_pb2.Timestamp + applied_at: _timestamp_pb2.Timestamp + verified_at: _timestamp_pb2.Timestamp + removed_at: _timestamp_pb2.Timestamp + error: str + installed_route: bytes + installed_boot_id: str + expired_latched: bool + def __init__(self, assignment: _Optional[_Union[Assignment, _Mapping]] = ..., desired_revision: _Optional[int] = ..., status: _Optional[_Union[ApplyStatus, str]] = ..., received_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., attempted_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., applied_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., verified_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., removed_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., error: _Optional[str] = ..., installed_route: _Optional[bytes] = ..., installed_boot_id: _Optional[str] = ..., expired_latched: _Optional[bool] = ...) -> None: ... + +class LocalState(_message.Message): + __slots__ = ("desired", "locally_suspended", "suspension_reason", "records", "updated_at") + DESIRED_FIELD_NUMBER: _ClassVar[int] + LOCALLY_SUSPENDED_FIELD_NUMBER: _ClassVar[int] + SUSPENSION_REASON_FIELD_NUMBER: _ClassVar[int] + RECORDS_FIELD_NUMBER: _ClassVar[int] + UPDATED_AT_FIELD_NUMBER: _ClassVar[int] + desired: DesiredState + locally_suspended: bool + suspension_reason: str + records: _containers.RepeatedCompositeFieldContainer[ApplicationRecord] + updated_at: _timestamp_pb2.Timestamp + def __init__(self, desired: _Optional[_Union[DesiredState, _Mapping]] = ..., locally_suspended: _Optional[bool] = ..., suspension_reason: _Optional[str] = ..., records: _Optional[_Iterable[_Union[ApplicationRecord, _Mapping]]] = ..., updated_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ... + +class HostEvidence(_message.Message): + __slots__ = ("agent_id", "sockets", "bytes_acked", "goodput_bps", "retransmission_ratio", "rtt_p95_ms", "exposed_sockets", "collection_healthy") + AGENT_ID_FIELD_NUMBER: _ClassVar[int] + SOCKETS_FIELD_NUMBER: _ClassVar[int] + BYTES_ACKED_FIELD_NUMBER: _ClassVar[int] + GOODPUT_BPS_FIELD_NUMBER: _ClassVar[int] + RETRANSMISSION_RATIO_FIELD_NUMBER: _ClassVar[int] + RTT_P95_MS_FIELD_NUMBER: _ClassVar[int] + EXPOSED_SOCKETS_FIELD_NUMBER: _ClassVar[int] + COLLECTION_HEALTHY_FIELD_NUMBER: _ClassVar[int] + agent_id: str + sockets: int + bytes_acked: int + goodput_bps: float + retransmission_ratio: float + rtt_p95_ms: float + exposed_sockets: int + collection_healthy: bool + def __init__(self, agent_id: _Optional[str] = ..., sockets: _Optional[int] = ..., bytes_acked: _Optional[int] = ..., goodput_bps: _Optional[float] = ..., retransmission_ratio: _Optional[float] = ..., rtt_p95_ms: _Optional[float] = ..., exposed_sockets: _Optional[int] = ..., collection_healthy: _Optional[bool] = ...) -> None: ... + +class Evidence(_message.Message): + __slots__ = ("id", "analyzed_at", "window_start", "window_end", "query_version", "enrichment_version", "hosts", "artifact_sha256") + ID_FIELD_NUMBER: _ClassVar[int] + ANALYZED_AT_FIELD_NUMBER: _ClassVar[int] + WINDOW_START_FIELD_NUMBER: _ClassVar[int] + WINDOW_END_FIELD_NUMBER: _ClassVar[int] + QUERY_VERSION_FIELD_NUMBER: _ClassVar[int] + ENRICHMENT_VERSION_FIELD_NUMBER: _ClassVar[int] + HOSTS_FIELD_NUMBER: _ClassVar[int] + ARTIFACT_SHA256_FIELD_NUMBER: _ClassVar[int] + id: str + analyzed_at: _timestamp_pb2.Timestamp + window_start: _timestamp_pb2.Timestamp + window_end: _timestamp_pb2.Timestamp + query_version: str + enrichment_version: str + hosts: _containers.RepeatedCompositeFieldContainer[HostEvidence] + artifact_sha256: str + def __init__(self, id: _Optional[str] = ..., analyzed_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., window_start: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., window_end: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., query_version: _Optional[str] = ..., enrichment_version: _Optional[str] = ..., hosts: _Optional[_Iterable[_Union[HostEvidence, _Mapping]]] = ..., artifact_sha256: _Optional[str] = ...) -> None: ... + +class ExperimentPolicy(_message.Message): + __slots__ = ("minimum_observation", "maximum_phase", "minimum_sockets_per_host", "minimum_bytes_per_host", "minimum_treated_hosts", "minimum_improvement", "maximum_retransmission_increase", "maximum_rtt_increase", "maximum_evidence_age") + MINIMUM_OBSERVATION_FIELD_NUMBER: _ClassVar[int] + MAXIMUM_PHASE_FIELD_NUMBER: _ClassVar[int] + MINIMUM_SOCKETS_PER_HOST_FIELD_NUMBER: _ClassVar[int] + MINIMUM_BYTES_PER_HOST_FIELD_NUMBER: _ClassVar[int] + MINIMUM_TREATED_HOSTS_FIELD_NUMBER: _ClassVar[int] + MINIMUM_IMPROVEMENT_FIELD_NUMBER: _ClassVar[int] + MAXIMUM_RETRANSMISSION_INCREASE_FIELD_NUMBER: _ClassVar[int] + MAXIMUM_RTT_INCREASE_FIELD_NUMBER: _ClassVar[int] + MAXIMUM_EVIDENCE_AGE_FIELD_NUMBER: _ClassVar[int] + minimum_observation: _duration_pb2.Duration + maximum_phase: _duration_pb2.Duration + minimum_sockets_per_host: int + minimum_bytes_per_host: int + minimum_treated_hosts: int + minimum_improvement: float + maximum_retransmission_increase: float + maximum_rtt_increase: float + maximum_evidence_age: _duration_pb2.Duration + def __init__(self, minimum_observation: _Optional[_Union[datetime.timedelta, _duration_pb2.Duration, _Mapping]] = ..., maximum_phase: _Optional[_Union[datetime.timedelta, _duration_pb2.Duration, _Mapping]] = ..., minimum_sockets_per_host: _Optional[int] = ..., minimum_bytes_per_host: _Optional[int] = ..., minimum_treated_hosts: _Optional[int] = ..., minimum_improvement: _Optional[float] = ..., maximum_retransmission_increase: _Optional[float] = ..., maximum_rtt_increase: _Optional[float] = ..., maximum_evidence_age: _Optional[_Union[datetime.timedelta, _duration_pb2.Duration, _Mapping]] = ...) -> None: ... + +class Phase(_message.Message): + __slots__ = ("target_hosts", "issued_at", "observation_started_at", "evaluated_at", "evidence", "outcome", "reviewer", "reason") + TARGET_HOSTS_FIELD_NUMBER: _ClassVar[int] + ISSUED_AT_FIELD_NUMBER: _ClassVar[int] + OBSERVATION_STARTED_AT_FIELD_NUMBER: _ClassVar[int] + EVALUATED_AT_FIELD_NUMBER: _ClassVar[int] + EVIDENCE_FIELD_NUMBER: _ClassVar[int] + OUTCOME_FIELD_NUMBER: _ClassVar[int] + REVIEWER_FIELD_NUMBER: _ClassVar[int] + REASON_FIELD_NUMBER: _ClassVar[int] + target_hosts: int + issued_at: _timestamp_pb2.Timestamp + observation_started_at: _timestamp_pb2.Timestamp + evaluated_at: _timestamp_pb2.Timestamp + evidence: Evidence + outcome: str + reviewer: str + reason: str + def __init__(self, target_hosts: _Optional[int] = ..., issued_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., observation_started_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., evaluated_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., evidence: _Optional[_Union[Evidence, _Mapping]] = ..., outcome: _Optional[str] = ..., reviewer: _Optional[str] = ..., reason: _Optional[str] = ...) -> None: ... + +class Experiment(_message.Message): + __slots__ = ("id", "location", "hypothesis", "treatment", "policy", "baseline", "cohort_order", "phases", "status", "revision", "proposed_at", "latest_evidence", "latest_outcome") + ID_FIELD_NUMBER: _ClassVar[int] + LOCATION_FIELD_NUMBER: _ClassVar[int] + HYPOTHESIS_FIELD_NUMBER: _ClassVar[int] + TREATMENT_FIELD_NUMBER: _ClassVar[int] + POLICY_FIELD_NUMBER: _ClassVar[int] + BASELINE_FIELD_NUMBER: _ClassVar[int] + COHORT_ORDER_FIELD_NUMBER: _ClassVar[int] + PHASES_FIELD_NUMBER: _ClassVar[int] + STATUS_FIELD_NUMBER: _ClassVar[int] + REVISION_FIELD_NUMBER: _ClassVar[int] + PROPOSED_AT_FIELD_NUMBER: _ClassVar[int] + LATEST_EVIDENCE_FIELD_NUMBER: _ClassVar[int] + LATEST_OUTCOME_FIELD_NUMBER: _ClassVar[int] + id: str + location: str + hypothesis: str + treatment: Treatment + policy: ExperimentPolicy + baseline: Evidence + cohort_order: _containers.RepeatedScalarFieldContainer[str] + phases: _containers.RepeatedCompositeFieldContainer[Phase] + status: ExperimentStatus + revision: int + proposed_at: _timestamp_pb2.Timestamp + latest_evidence: Evidence + latest_outcome: str + def __init__(self, id: _Optional[str] = ..., location: _Optional[str] = ..., hypothesis: _Optional[str] = ..., treatment: _Optional[_Union[Treatment, _Mapping]] = ..., policy: _Optional[_Union[ExperimentPolicy, _Mapping]] = ..., baseline: _Optional[_Union[Evidence, _Mapping]] = ..., cohort_order: _Optional[_Iterable[str]] = ..., phases: _Optional[_Iterable[_Union[Phase, _Mapping]]] = ..., status: _Optional[_Union[ExperimentStatus, str]] = ..., revision: _Optional[int] = ..., proposed_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., latest_evidence: _Optional[_Union[Evidence, _Mapping]] = ..., latest_outcome: _Optional[str] = ...) -> None: ... diff --git a/gen/python/xtcp_treatment/v1/treatment_pb2_grpc.py b/gen/python/xtcp_treatment/v1/treatment_pb2_grpc.py new file mode 100644 index 000000000..2daafffeb --- /dev/null +++ b/gen/python/xtcp_treatment/v1/treatment_pb2_grpc.py @@ -0,0 +1,4 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + diff --git a/pkg/treatment/agent/agent.go b/pkg/treatment/agent/agent.go index 97574f199..8c2c29db5 100644 --- a/pkg/treatment/agent/agent.go +++ b/pkg/treatment/agent/agent.go @@ -19,8 +19,8 @@ import ( "sync/atomic" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" flat "github.com/randomizedcoder/xtcp2/gen/go/xtcp_flat_record" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/internal/goip" "github.com/randomizedcoder/xtcp2/pkg/treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" diff --git a/pkg/treatment/agent/agent_test.go b/pkg/treatment/agent/agent_test.go index d0916994e..f7949498f 100644 --- a/pkg/treatment/agent/agent_test.go +++ b/pkg/treatment/agent/agent_test.go @@ -4,8 +4,8 @@ import ( "testing" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" flat "github.com/randomizedcoder/xtcp2/gen/go/xtcp_flat_record" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/reconcile" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" diff --git a/pkg/treatment/agent/backend_linux.go b/pkg/treatment/agent/backend_linux.go index 01c1054ed..9b4fa704e 100644 --- a/pkg/treatment/agent/backend_linux.go +++ b/pkg/treatment/agent/backend_linux.go @@ -2,7 +2,7 @@ package agent import ( "context" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/internal/goip" "github.com/randomizedcoder/xtcp2/pkg/treatment" ) diff --git a/pkg/treatment/agent/benchmark_test.go b/pkg/treatment/agent/benchmark_test.go index b0bb3ac43..1297c1d57 100644 --- a/pkg/treatment/agent/benchmark_test.go +++ b/pkg/treatment/agent/benchmark_test.go @@ -6,8 +6,8 @@ import ( "testing" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" flat "github.com/randomizedcoder/xtcp2/gen/go/xtcp_flat_record" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/reconcile" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" diff --git a/pkg/treatment/agent/contracts_test.go b/pkg/treatment/agent/contracts_test.go index ff93723e4..8f4e831be 100644 --- a/pkg/treatment/agent/contracts_test.go +++ b/pkg/treatment/agent/contracts_test.go @@ -9,8 +9,8 @@ import ( "testing" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" flat "github.com/randomizedcoder/xtcp2/gen/go/xtcp_flat_record" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" ) diff --git a/pkg/treatment/agent/lifecycle_test.go b/pkg/treatment/agent/lifecycle_test.go index b7a6bf695..94e3f6bd6 100644 --- a/pkg/treatment/agent/lifecycle_test.go +++ b/pkg/treatment/agent/lifecycle_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "google.golang.org/grpc" "google.golang.org/protobuf/types/known/timestamppb" ) diff --git a/pkg/treatment/boundaries_test.go b/pkg/treatment/boundaries_test.go index ee4425b7c..6275ca316 100644 --- a/pkg/treatment/boundaries_test.go +++ b/pkg/treatment/boundaries_test.go @@ -59,7 +59,7 @@ func treatmentBoundaryRules() []boundaryRule { expectAny: []string{tp("pkg/treatment/identity"), tp("pkg/treatment")}, }, { - // TODO(item 2): add gen/go/xtcp_controller to forbidden once the + // TODO(item 2): add gen/go/xtcp_treatment to forbidden once the // contract package stops importing the proto (domain types land). description: "negative: the identity package is a leaf below the contract and gRPC credentials", root: tp("pkg/treatment/identity"), diff --git a/pkg/treatment/controller/analyze.go b/pkg/treatment/controller/analyze.go index a199e7244..df12c915a 100644 --- a/pkg/treatment/controller/analyze.go +++ b/pkg/treatment/controller/analyze.go @@ -6,7 +6,7 @@ import ( "errors" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/experiment" "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" diff --git a/pkg/treatment/controller/clickhouse.go b/pkg/treatment/controller/clickhouse.go index eb372604c..157064735 100644 --- a/pkg/treatment/controller/clickhouse.go +++ b/pkg/treatment/controller/clickhouse.go @@ -18,7 +18,7 @@ import ( "strings" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" "google.golang.org/protobuf/types/known/timestamppb" ) diff --git a/pkg/treatment/controller/clickhouse_test.go b/pkg/treatment/controller/clickhouse_test.go index 1e958d368..1f20f073d 100644 --- a/pkg/treatment/controller/clickhouse_test.go +++ b/pkg/treatment/controller/clickhouse_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" ) // Execute the production SQL against an isolated ClickHouse local database. diff --git a/pkg/treatment/controller/discovery.go b/pkg/treatment/controller/discovery.go index 0cbb943f8..08ea83ec2 100644 --- a/pkg/treatment/controller/discovery.go +++ b/pkg/treatment/controller/discovery.go @@ -13,7 +13,7 @@ import ( "strconv" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" "google.golang.org/protobuf/proto" ) diff --git a/pkg/treatment/controller/discovery_contracts_test.go b/pkg/treatment/controller/discovery_contracts_test.go index b6fe27108..87e6cc62c 100644 --- a/pkg/treatment/controller/discovery_contracts_test.go +++ b/pkg/treatment/controller/discovery_contracts_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" ) func TestPostgresDiscoveryScopeAndCooldown(t *testing.T) { diff --git a/pkg/treatment/controller/discovery_test.go b/pkg/treatment/controller/discovery_test.go index fc2827dde..6c081fa9e 100644 --- a/pkg/treatment/controller/discovery_test.go +++ b/pkg/treatment/controller/discovery_test.go @@ -11,7 +11,7 @@ import ( "testing" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" "google.golang.org/protobuf/proto" ) diff --git a/pkg/treatment/controller/maintenance.go b/pkg/treatment/controller/maintenance.go index ea87bd046..d8c83aaab 100644 --- a/pkg/treatment/controller/maintenance.go +++ b/pkg/treatment/controller/maintenance.go @@ -7,7 +7,7 @@ import ( "fmt" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/experiment" "google.golang.org/protobuf/encoding/protojson" diff --git a/pkg/treatment/controller/postgres_indexes_test.go b/pkg/treatment/controller/postgres_indexes_test.go index 5b39f8441..41a9f32cf 100644 --- a/pkg/treatment/controller/postgres_indexes_test.go +++ b/pkg/treatment/controller/postgres_indexes_test.go @@ -12,7 +12,7 @@ import ( "testing" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "google.golang.org/protobuf/proto" ) diff --git a/pkg/treatment/controller/rpc_contracts_test.go b/pkg/treatment/controller/rpc_contracts_test.go index 6ace60c9a..8303f6995 100644 --- a/pkg/treatment/controller/rpc_contracts_test.go +++ b/pkg/treatment/controller/rpc_contracts_test.go @@ -12,7 +12,7 @@ import ( "testing" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" diff --git a/pkg/treatment/controller/server.go b/pkg/treatment/controller/server.go index 77d5e1423..a2f980340 100644 --- a/pkg/treatment/controller/server.go +++ b/pkg/treatment/controller/server.go @@ -10,7 +10,7 @@ import ( "strings" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/experiment" "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" diff --git a/pkg/treatment/controller/server_test.go b/pkg/treatment/controller/server_test.go index fa1eca86a..9402369e7 100644 --- a/pkg/treatment/controller/server_test.go +++ b/pkg/treatment/controller/server_test.go @@ -13,7 +13,7 @@ import ( "testing" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" "google.golang.org/grpc/credentials" diff --git a/pkg/treatment/controller/store.go b/pkg/treatment/controller/store.go index 033d16949..f8e56e495 100644 --- a/pkg/treatment/controller/store.go +++ b/pkg/treatment/controller/store.go @@ -9,7 +9,7 @@ import ( "time" _ "github.com/lib/pq" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" ) diff --git a/pkg/treatment/experiment/benchmark_test.go b/pkg/treatment/experiment/benchmark_test.go index d6064cb99..20dfb87ca 100644 --- a/pkg/treatment/experiment/benchmark_test.go +++ b/pkg/treatment/experiment/benchmark_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" "google.golang.org/protobuf/proto" diff --git a/pkg/treatment/experiment/boundaries_test.go b/pkg/treatment/experiment/boundaries_test.go index 6718013ac..f8292cd34 100644 --- a/pkg/treatment/experiment/boundaries_test.go +++ b/pkg/treatment/experiment/boundaries_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" "google.golang.org/protobuf/proto" diff --git a/pkg/treatment/experiment/experiment.go b/pkg/treatment/experiment/experiment.go index 083155756..f4398bfc2 100644 --- a/pkg/treatment/experiment/experiment.go +++ b/pkg/treatment/experiment/experiment.go @@ -6,7 +6,7 @@ import ( "math" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" diff --git a/pkg/treatment/experiment/experiment_test.go b/pkg/treatment/experiment/experiment_test.go index ba9567044..4aaaf21df 100644 --- a/pkg/treatment/experiment/experiment_test.go +++ b/pkg/treatment/experiment/experiment_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" "google.golang.org/protobuf/proto" diff --git a/pkg/treatment/identity/mtls_test.go b/pkg/treatment/identity/mtls_test.go index 63c57a8e1..e4c8c5688 100644 --- a/pkg/treatment/identity/mtls_test.go +++ b/pkg/treatment/identity/mtls_test.go @@ -17,7 +17,7 @@ import ( "testing" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" diff --git a/pkg/treatment/internal/fixtures/fixtures.go b/pkg/treatment/internal/fixtures/fixtures.go index 39e95a25c..af7edd2aa 100644 --- a/pkg/treatment/internal/fixtures/fixtures.go +++ b/pkg/treatment/internal/fixtures/fixtures.go @@ -8,7 +8,7 @@ import ( "strings" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" ) diff --git a/pkg/treatment/proto_rules_test.go b/pkg/treatment/proto_rules_test.go new file mode 100644 index 000000000..208e2f41c --- /dev/null +++ b/pkg/treatment/proto_rules_test.go @@ -0,0 +1,188 @@ +package treatment + +import ( + "errors" + "slices" + "strings" + "testing" + "time" + + "buf.build/go/protovalidate" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/timestamppb" +) + +// The buf.validate rules on proto/xtcp_treatment/v1 mirror the bounds in +// validate.go. These rows pin that mirror: a bound changed in one place and +// not the other fails here. Contextual checks (duplicate IDs, overlapping +// destinations, cohort/baseline agreement) stay Go-only and are covered by +// validate_test.go. +// +// go test ./pkg/treatment -run TestProtoRulesMirrorValidators +func TestProtoRulesMirrorValidators(t *testing.T) { + now := time.Unix(1700000000, 0) + id128 := strings.Repeat("a", 128) + experiment := func(mut func(*pb.Experiment)) proto.Message { + e, _ := fixtures.Experiment(now, DefaultPolicy()) + mut(e) + return e + } + desired := func(mut func(*pb.DesiredState)) proto.Message { + d := fixtures.Desired(now) + mut(d) + return d + } + control := func(mut func(*pb.ControlRequest)) proto.Message { + c := &pb.ControlRequest{ExperimentId: "exp", Action: pb.ControlAction_CONTROL_ACTION_PAUSE, Reason: "why", RequestId: "req-1"} + mut(c) + return c + } + tests := []struct { + description string + msg proto.Message + expected string // substring of the violation rule id, or "" for valid + }{ + {"positive: the desired-state fixture is valid", desired(func(*pb.DesiredState) {}), ""}, + {"positive: the experiment fixture is valid", experiment(func(*pb.Experiment) {}), ""}, + {"positive: a propose request wrapping the fixture is valid", &pb.ProposeRequest{Experiment: experiment(func(*pb.Experiment) {}).(*pb.Experiment), RequestId: "req-1"}, ""}, + {"positive: a minimal check-in is valid", &pb.CheckInRequest{AgentId: "host-1", Location: "dc1", Capabilities: &pb.Capabilities{BootId: "boot"}}, ""}, + {"positive: a control request with exactly one selector is valid", control(func(*pb.ControlRequest) {}), ""}, + {"positive: fleet control with no other selector is valid", control(func(c *pb.ControlRequest) { c.ExperimentId = ""; c.Fleet = true }), ""}, + + {"negative: identifier with a leading dash", desired(func(d *pb.DesiredState) { d.AgentId = "-host" }), "string.pattern"}, + {"negative: revision zero", desired(func(d *pb.DesiredState) { d.Revision = 0 }), "uint64.gt"}, + {"negative: missing issued_at", desired(func(d *pb.DesiredState) { d.IssuedAt = nil }), "required"}, + {"negative: empty TCP settings", desired(func(d *pb.DesiredState) { d.Assignments[0].Treatment.Settings = &pb.TCPSettings{} }), "tcp_settings.non_empty"}, + {"negative: congestion_lock without an algorithm", desired(func(d *pb.DesiredState) { + d.Assignments[0].Treatment.Settings = &pb.TCPSettings{CongestionLock: true, QuickAck: proto.Bool(true)} + }), "tcp_settings.lock_requires_algorithm"}, + {"negative: DCTCP without verified ECN fabric", desired(func(d *pb.DesiredState) { + d.Assignments[0].Treatment.Settings.CongestionControl = proto.String("dctcp") + }), "treatment.dctcp_requires_ecn"}, + {"negative: destination that is not a prefix", desired(func(d *pb.DesiredState) { d.Assignments[0].Treatment.Destination = "192.0.2.1" }), "string.ip_prefix"}, + {"negative: trial without a lease", desired(func(d *pb.DesiredState) { d.Assignments[0].LeaseExpiresAt = nil }), "assignment.trial_requires_lease"}, + {"negative: unspecified assignment mode", desired(func(d *pb.DesiredState) { d.Assignments[0].Mode = pb.AssignmentMode_ASSIGNMENT_MODE_UNSPECIFIED }), "enum"}, + {"negative: predecessor on another destination", desired(func(d *pb.DesiredState) { + p := proto.Clone(d.Assignments[0].Treatment).(*pb.Treatment) + p.Destination = "192.0.2.2/32" + d.Assignments[0].Predecessor = p + }), "assignment.predecessor_destination"}, + {"negative: evidence window inverted", experiment(func(e *pb.Experiment) { + e.Baseline.WindowStart, e.Baseline.WindowEnd = e.Baseline.WindowEnd, e.Baseline.WindowStart + }), "evidence.interval"}, + {"negative: evidence analysed before its window closed", experiment(func(e *pb.Experiment) { + e.Baseline.AnalyzedAt = timestamppb.New(e.Baseline.WindowEnd.AsTime().Add(-time.Second)) + }), "evidence.interval"}, + {"negative: artifact digest of the wrong length", experiment(func(e *pb.Experiment) { e.Baseline.ArtifactSha256 = "abc" }), "string.pattern"}, + {"negative: exposed sockets exceed sockets", experiment(func(e *pb.Experiment) { e.Baseline.Hosts[0].ExposedSockets = e.Baseline.Hosts[0].Sockets + 1 }), "host_evidence.exposed_within_sockets"}, + {"negative: observation longer than the phase", experiment(func(e *pb.Experiment) { + e.Policy.MinimumObservation = durationpb.New(25 * time.Hour) + }), "policy.observation_within_phase"}, + {"negative: too few cohort hosts", experiment(func(e *pb.Experiment) { e.CohortOrder = e.CohortOrder[:2] }), "repeated.min_items"}, + {"negative: duplicate cohort hosts", experiment(func(e *pb.Experiment) { e.CohortOrder[1] = e.CohortOrder[0] }), "repeated.unique"}, + {"negative: empty hypothesis", experiment(func(e *pb.Experiment) { e.Hypothesis = "" }), "string.min_len"}, + {"negative: two control selectors", control(func(c *pb.ControlRequest) { c.AgentId = "host-1" }), "control.one_selector"}, + {"negative: no control selector", control(func(c *pb.ControlRequest) { c.ExperimentId = "" }), "control.one_selector"}, + {"negative: unspecified control action", control(func(c *pb.ControlRequest) { c.Action = pb.ControlAction_CONTROL_ACTION_UNSPECIFIED }), "enum"}, + {"negative: empty control reason", control(func(c *pb.ControlRequest) { c.Reason = "" }), "string.min_len"}, + {"negative: request_id reserved suffix", control(func(c *pb.ControlRequest) { c.RequestId = "req.result" }), "control.request_id_suffix"}, + + {"boundary: identifier of exactly 128 bytes", desired(func(d *pb.DesiredState) { d.AgentId = id128 }), ""}, + {"boundary: identifier of 129 bytes", desired(func(d *pb.DesiredState) { d.AgentId = id128 + "a" }), "string.pattern"}, + {"boundary: rto_min_ms at the upper bound", desired(func(d *pb.DesiredState) { d.Assignments[0].Treatment.Settings.RtoMinMs = proto.Uint32(120000) }), ""}, + {"boundary: rto_min_ms one over the upper bound", desired(func(d *pb.DesiredState) { d.Assignments[0].Treatment.Settings.RtoMinMs = proto.Uint32(120001) }), "uint32.gte_lte"}, + {"boundary: initial window at the upper bound", desired(func(d *pb.DesiredState) { + d.Assignments[0].Treatment.Settings.InitialCongestionWindow = proto.Uint32(1024) + }), ""}, + {"boundary: initial window of zero", desired(func(d *pb.DesiredState) { + d.Assignments[0].Treatment.Settings.InitialCongestionWindow = proto.Uint32(0) + }), "uint32.gte"}, + {"boundary: reason of exactly 4096 bytes", control(func(c *pb.ControlRequest) { c.Reason = strings.Repeat("r", 4096) }), ""}, + {"boundary: reason of 4097 bytes", control(func(c *pb.ControlRequest) { c.Reason = strings.Repeat("r", 4097) }), "string.max_len"}, + {"boundary: policy duration at seven days", experiment(func(e *pb.Experiment) { e.Policy.MaximumPhase = durationpb.New(7 * 24 * time.Hour) }), ""}, + {"boundary: policy duration over seven days", experiment(func(e *pb.Experiment) { e.Policy.MaximumPhase = durationpb.New(7*24*time.Hour + time.Second) }), "duration.gt_lte"}, + {"boundary: minimum_treated_hosts of one", experiment(func(e *pb.Experiment) { e.Policy.MinimumTreatedHosts = 1 }), "uint32.gte"}, + + {"corner: inspect with no selectors is valid (ignore on zero value)", &pb.InspectRequest{}, ""}, + {"corner: inspect with a malformed selector is rejected", &pb.InspectRequest{AgentId: "bad id"}, "string.pattern"}, + {"corner: upper-case hex digest is rejected", experiment(func(e *pb.Experiment) { e.Baseline.ArtifactSha256 = strings.ToUpper(e.Baseline.ArtifactSha256) }), "string.pattern"}, + {"corner: a 15-byte congestion algorithm name is accepted, 16 is not", desired(func(d *pb.DesiredState) { + d.Assignments[0].Treatment.Settings.CongestionControl = proto.String(strings.Repeat("c", 16)) + }), "string.max_len"}, + {"corner: an IPv6 host prefix is accepted", desired(func(d *pb.DesiredState) { d.Assignments[0].Treatment.Destination = "2001:db8::1/128" }), ""}, + {"corner: NaN guardrail is rejected", experiment(func(e *pb.Experiment) { e.Policy.MaximumRttIncrease = nan() }), "double.finite"}, + } + for _, tc := range tests { + t.Run(tc.description, func(t *testing.T) { + err := protovalidate.Validate(tc.msg) + if tc.expected == "" { + if err != nil { + t.Fatalf("expected valid, got %v", err) + } + return + } + if err == nil { + t.Fatalf("expected a violation matching %q", tc.expected) + } + var ve *protovalidate.ValidationError + if !errors.As(err, &ve) { + t.Fatalf("expected a ValidationError, got %T: %v", err, err) + } + var ids []string + for _, v := range ve.Violations { + ids = append(ids, v.Proto.GetRuleId()) + } + if !slices.ContainsFunc(ids, func(id string) bool { return strings.Contains(id, tc.expected) }) { + t.Fatalf("expected a violation of %q, got %v (%v)", tc.expected, ids, err) + } + }) + } +} + +func nan() float64 { + zero := 0.0 + return zero / zero +} + +// Every RPC must declare a role; a forgotten annotation is refused for all +// callers, so this test turns a silent outage into a build-time failure. +// +// go test ./pkg/treatment -run TestEveryRPCDeclaresARole +func TestEveryRPCDeclaresARole(t *testing.T) { + files := []protoreflect.FileDescriptor{pb.File_xtcp_treatment_v1_agent_proto, pb.File_xtcp_treatment_v1_operator_proto} + want := map[protoreflect.FullName]pb.Role{ + "xtcp_treatment.v1.AgentControlService": pb.Role_ROLE_AGENT, + "xtcp_treatment.v1.OperatorService": pb.Role_ROLE_OPERATOR, + } + seen := 0 + for _, f := range files { + services := f.Services() + for i := 0; i < services.Len(); i++ { + svc := services.Get(i) + expected, ok := want[svc.FullName()] + if !ok { + t.Errorf("service %s has no expected role in this test; add it", svc.FullName()) + continue + } + methods := svc.Methods() + for j := 0; j < methods.Len(); j++ { + m := methods.Get(j) + seen++ + role := proto.GetExtension(m.Options(), pb.E_Role).(pb.Role) + if role == pb.Role_ROLE_UNSPECIFIED { + t.Errorf("%s.%s declares no (xtcp_treatment.v1.role)", svc.FullName(), m.Name()) + } + if role != expected { + t.Errorf("%s.%s declares %s, expected %s", svc.FullName(), m.Name(), role, expected) + } + } + } + } + if seen != 8 { + t.Errorf("walked %d methods, expected 8; a service was added or removed", seen) + } +} diff --git a/pkg/treatment/reconcile/contracts_test.go b/pkg/treatment/reconcile/contracts_test.go index 44a858c6a..395f15bed 100644 --- a/pkg/treatment/reconcile/contracts_test.go +++ b/pkg/treatment/reconcile/contracts_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" diff --git a/pkg/treatment/reconcile/journal.go b/pkg/treatment/reconcile/journal.go index a3a5c3574..c5e3cf3b0 100644 --- a/pkg/treatment/reconcile/journal.go +++ b/pkg/treatment/reconcile/journal.go @@ -8,7 +8,7 @@ import ( "os" "path/filepath" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" "golang.org/x/sys/unix" "google.golang.org/protobuf/encoding/prototext" diff --git a/pkg/treatment/reconcile/journal_test.go b/pkg/treatment/reconcile/journal_test.go index b29ecde02..103bdac29 100644 --- a/pkg/treatment/reconcile/journal_test.go +++ b/pkg/treatment/reconcile/journal_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" "golang.org/x/sys/unix" diff --git a/pkg/treatment/reconcile/reconcile.go b/pkg/treatment/reconcile/reconcile.go index 68a7c0b82..eb96b7d88 100644 --- a/pkg/treatment/reconcile/reconcile.go +++ b/pkg/treatment/reconcile/reconcile.go @@ -7,7 +7,7 @@ import ( "fmt" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" diff --git a/pkg/treatment/reconcile/reconcile_test.go b/pkg/treatment/reconcile/reconcile_test.go index cfe4b5e6c..9b69a6ffa 100644 --- a/pkg/treatment/reconcile/reconcile_test.go +++ b/pkg/treatment/reconcile/reconcile_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" diff --git a/pkg/treatment/validate.go b/pkg/treatment/validate.go index 25d28d54b..018381a03 100644 --- a/pkg/treatment/validate.go +++ b/pkg/treatment/validate.go @@ -10,7 +10,7 @@ import ( "regexp" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "google.golang.org/protobuf/types/known/durationpb" ) diff --git a/pkg/treatment/validate_test.go b/pkg/treatment/validate_test.go index d9498b16e..633085c75 100644 --- a/pkg/treatment/validate_test.go +++ b/pkg/treatment/validate_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/durationpb" diff --git a/proto/xtcp/controller/v1/controller.proto b/proto/xtcp/controller/v1/controller.proto deleted file mode 100644 index 0ea33c324..000000000 --- a/proto/xtcp/controller/v1/controller.proto +++ /dev/null @@ -1,240 +0,0 @@ -syntax = "proto3"; - -package xtcp.controller.v1; - -option go_package = "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller"; - -import "google/protobuf/timestamp.proto"; -import "google/protobuf/duration.proto"; - -// All inbound messages are validated by the controller application layer. -// IDs are bounded ASCII identifiers; address selectors are canonical host CIDRs. -// Limits, required fields and contextual constraints are in pkg/treatment/validate.go. -service AgentControlService { - rpc CheckIn(CheckInRequest) returns (CheckInResponse); - rpc GetDesiredState(GetDesiredStateRequest) returns (GetDesiredStateResponse); - rpc WatchDesiredState(WatchDesiredStateRequest) returns (stream WatchDesiredStateResponse); -} - -service OperatorService { - rpc Propose(ProposeRequest) returns (ProposeResponse); - rpc Analyze(AnalyzeRequest) returns (AnalyzeResponse); - rpc Review(ReviewRequest) returns (ReviewResponse); - rpc Control(ControlRequest) returns (ControlResponse); - rpc Inspect(InspectRequest) returns (InspectResponse); -} - -enum AssignmentMode { - ASSIGNMENT_MODE_UNSPECIFIED = 0; - ASSIGNMENT_MODE_TRIAL = 1; - ASSIGNMENT_MODE_STABLE = 2; - ASSIGNMENT_MODE_SUSPENDED = 3; - ASSIGNMENT_MODE_RETIRED = 4; -} - -enum ApplyStatus { - APPLY_STATUS_UNSPECIFIED = 0; - APPLY_STATUS_PENDING = 1; - APPLY_STATUS_VERIFIED = 2; - APPLY_STATUS_REMOVED = 3; - APPLY_STATUS_EXPIRED = 4; - APPLY_STATUS_FAILED = 5; -} - -message TCPSettings { - optional string congestion_control = 1; - bool congestion_lock = 2; - optional uint32 rto_min_ms = 3; - optional uint32 initial_congestion_window = 4; - optional uint32 initial_receive_window = 5; - optional bool quick_ack = 6; - optional bool ecn = 7; -} - -message Treatment { - string id = 1; - uint64 revision = 2; - string destination = 3; - TCPSettings settings = 4; - bool ecn_fabric_verified = 5; -} - -message Assignment { - string id = 1; - string experiment_id = 2; - Treatment treatment = 3; - AssignmentMode mode = 4; - google.protobuf.Timestamp lease_expires_at = 5; - // Previous approved stable treatment, restored on trial expiry/rollback. - Treatment predecessor = 6; - // Increment only on an explicitly approved reactivation, not lease renewal. - uint64 generation = 7; -} - -message DesiredState { - string agent_id = 1; - uint64 revision = 2; - bool suspended = 3; - repeated Assignment assignments = 4; - google.protobuf.Timestamp issued_at = 5; -} - -message Capabilities { - string boot_id = 1; - string kernel_release = 2; - repeated string congestion_algorithms = 3; - bool simple_host_routing = 4; - bool actuator_enabled = 5; -} - -message ApplicationRecord { - Assignment assignment = 1; - uint64 desired_revision = 2; - ApplyStatus status = 3; - google.protobuf.Timestamp received_at = 4; - google.protobuf.Timestamp attempted_at = 5; - google.protobuf.Timestamp applied_at = 6; - google.protobuf.Timestamp verified_at = 7; - google.protobuf.Timestamp removed_at = 8; - string error = 9; - // Locally generated canonical netlink route body, never accepted from RPC. - bytes installed_route = 10; - string installed_boot_id = 11; - bool expired_latched = 12; -} - -message LocalState { - DesiredState desired = 1; - bool locally_suspended = 2; - string suspension_reason = 3; - repeated ApplicationRecord records = 4; - google.protobuf.Timestamp updated_at = 5; -} - -message CheckInRequest { - string agent_id = 1; - string location = 2; - Capabilities capabilities = 3; - uint64 desired_revision = 4; - repeated ApplicationRecord records = 5; - bool locally_suspended = 6; -} -message CheckInResponse { DesiredState desired = 1; } -message GetDesiredStateRequest { string agent_id = 1; } -message GetDesiredStateResponse { DesiredState desired = 1; } -message WatchDesiredStateRequest { string agent_id = 1; uint64 after_revision = 2; } -message WatchDesiredStateResponse { uint64 revision = 1; } - -message HostEvidence { - string agent_id = 1; - uint64 sockets = 2; - uint64 bytes_acked = 3; - double goodput_bps = 4; - double retransmission_ratio = 5; - double rtt_p95_ms = 6; - uint64 exposed_sockets = 7; - bool collection_healthy = 8; -} - -message Evidence { - string id = 1; - google.protobuf.Timestamp analyzed_at = 2; - google.protobuf.Timestamp window_start = 3; - google.protobuf.Timestamp window_end = 4; - string query_version = 5; - string enrichment_version = 6; - repeated HostEvidence hosts = 7; - // Content-addressed reference to the complete immutable analysis artifact. - string artifact_sha256 = 8; -} - -message ExperimentPolicy { - google.protobuf.Duration minimum_observation = 1; - google.protobuf.Duration maximum_phase = 2; - uint64 minimum_sockets_per_host = 3; - uint64 minimum_bytes_per_host = 4; - uint32 minimum_treated_hosts = 5; - double minimum_improvement = 6; - double maximum_retransmission_increase = 7; - double maximum_rtt_increase = 8; - google.protobuf.Duration maximum_evidence_age = 9; -} - -enum ExperimentStatus { - EXPERIMENT_STATUS_UNSPECIFIED = 0; - EXPERIMENT_STATUS_PROPOSED = 1; - EXPERIMENT_STATUS_RUNNING = 2; - EXPERIMENT_STATUS_PAUSED = 3; - EXPERIMENT_STATUS_SUSPENDED = 4; - EXPERIMENT_STATUS_COMPLETE = 5; - EXPERIMENT_STATUS_RETIRED = 6; - EXPERIMENT_STATUS_ROLLED_BACK = 7; -} - -message Phase { - uint32 target_hosts = 1; - google.protobuf.Timestamp issued_at = 2; - google.protobuf.Timestamp observation_started_at = 3; - google.protobuf.Timestamp evaluated_at = 4; - Evidence evidence = 5; - string outcome = 6; - string reviewer = 7; - string reason = 8; -} - -message Experiment { - string id = 1; - string location = 2; - string hypothesis = 3; - Treatment treatment = 4; - ExperimentPolicy policy = 5; - Evidence baseline = 6; - repeated string cohort_order = 7; - repeated Phase phases = 8; - ExperimentStatus status = 9; - uint64 revision = 10; - google.protobuf.Timestamp proposed_at = 11; - Evidence latest_evidence = 12; - string latest_outcome = 13; -} - -message ProposeRequest { Experiment experiment = 1; string request_id = 2; } -message ProposeResponse { Experiment experiment = 1; } -message AnalyzeRequest { string experiment_id = 1; uint64 expected_revision = 2; } -message AnalyzeResponse { Evidence evidence = 1; string outcome = 2; } -message ReviewRequest { - string experiment_id = 1; - uint64 expected_revision = 2; - bool approve = 3; - string reason = 4; - string request_id = 5; - // Initial review uses baseline; subsequent reviews require fresh evidence. - Evidence evidence = 6; -} -message ReviewResponse { Experiment experiment = 1; } - -enum ControlAction { - CONTROL_ACTION_UNSPECIFIED = 0; - CONTROL_ACTION_PAUSE = 1; - CONTROL_ACTION_SUSPEND = 2; - CONTROL_ACTION_RESUME = 3; - CONTROL_ACTION_ROLLBACK = 4; - CONTROL_ACTION_RETIRE = 5; -} -message ControlRequest { - // Exactly one selector, or fleet=true, is required. - string experiment_id = 1; - string agent_id = 2; - string location = 3; - bool fleet = 4; - ControlAction action = 5; - string reason = 6; - string request_id = 7; -} -message ControlResponse { uint32 affected_agents = 1; } -message InspectRequest { string experiment_id = 1; string agent_id = 2; } -message InspectResponse { - repeated Experiment experiments = 1; - repeated DesiredState desired = 2; - repeated CheckInRequest observed = 3; -} diff --git a/proto/xtcp_treatment/v1/agent.proto b/proto/xtcp_treatment/v1/agent.proto new file mode 100644 index 000000000..8d920a9c2 --- /dev/null +++ b/proto/xtcp_treatment/v1/agent.proto @@ -0,0 +1,58 @@ +// +// xTCP - treatment agent service +// +// The outbound-only RPC surface an agent uses: check in with observed state, +// fetch the authoritative desired state, and watch for revision hints. Every +// method requires an agent certificate whose SPIFFE id matches agent_id and +// whose location matches the request; see pkg/treatment/identity. + +syntax = "proto3"; + +package xtcp_treatment.v1; + +option go_package = "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment"; + +import "buf/validate/validate.proto"; +import "xtcp_treatment/v1/treatment.proto"; + +service AgentControlService { + rpc CheckIn(CheckInRequest) returns (CheckInResponse) { + option (xtcp_treatment.v1.role) = ROLE_AGENT; + } + rpc GetDesiredState(GetDesiredStateRequest) returns (GetDesiredStateResponse) { + option (xtcp_treatment.v1.role) = ROLE_AGENT; + } + rpc WatchDesiredState(WatchDesiredStateRequest) returns (stream WatchDesiredStateResponse) { + option (xtcp_treatment.v1.role) = ROLE_AGENT; + } +} + +message CheckInRequest { + string agent_id = 1 [(buf.validate.field).string.pattern = "^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$"]; + string location = 2 [(buf.validate.field).string.pattern = "^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$"]; + Capabilities capabilities = 3 [(buf.validate.field).required = true]; + uint64 desired_revision = 4; + repeated ApplicationRecord records = 5 [(buf.validate.field).repeated.max_items = 8192]; + bool locally_suspended = 6; +} + +message CheckInResponse { + DesiredState desired = 1; +} + +message GetDesiredStateRequest { + string agent_id = 1 [(buf.validate.field).string.pattern = "^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$"]; +} + +message GetDesiredStateResponse { + DesiredState desired = 1; +} + +message WatchDesiredStateRequest { + string agent_id = 1 [(buf.validate.field).string.pattern = "^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$"]; + uint64 after_revision = 2; +} + +message WatchDesiredStateResponse { + uint64 revision = 1; +} diff --git a/proto/xtcp_treatment/v1/operator.proto b/proto/xtcp_treatment/v1/operator.proto new file mode 100644 index 000000000..8eb1c777c --- /dev/null +++ b/proto/xtcp_treatment/v1/operator.proto @@ -0,0 +1,122 @@ +// +// xTCP - treatment operator service +// +// The authenticated review surface. Every mutation carries a request_id; +// repeating one with the same body replays the original response, repeating +// it with a different body is refused. Reviews also fence on the experiment +// revision the operator last saw. + +syntax = "proto3"; + +package xtcp_treatment.v1; + +option go_package = "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment"; + +import "buf/validate/validate.proto"; +import "xtcp_treatment/v1/agent.proto"; +import "xtcp_treatment/v1/treatment.proto"; + +service OperatorService { + rpc Propose(ProposeRequest) returns (ProposeResponse) { + option (xtcp_treatment.v1.role) = ROLE_OPERATOR; + } + rpc Analyze(AnalyzeRequest) returns (AnalyzeResponse) { + option (xtcp_treatment.v1.role) = ROLE_OPERATOR; + } + rpc Review(ReviewRequest) returns (ReviewResponse) { + option (xtcp_treatment.v1.role) = ROLE_OPERATOR; + } + rpc Control(ControlRequest) returns (ControlResponse) { + option (xtcp_treatment.v1.role) = ROLE_OPERATOR; + } + rpc Inspect(InspectRequest) returns (InspectResponse) { + option (xtcp_treatment.v1.role) = ROLE_OPERATOR; + } +} + +message ProposeRequest { + option (buf.validate.message).cel = { + id: "propose.request_id_suffix" + message: "request_id must not end with .result (reserved for the paired response event)" + expression: "!this.request_id.endsWith('.result')" + }; + Experiment experiment = 1 [(buf.validate.field).required = true]; + string request_id = 2 [(buf.validate.field).string.pattern = "^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$"]; +} + +message ProposeResponse { + Experiment experiment = 1; +} + +message AnalyzeRequest { + string experiment_id = 1 [(buf.validate.field).string.pattern = "^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$"]; + uint64 expected_revision = 2; +} + +message AnalyzeResponse { + Evidence evidence = 1; + string outcome = 2; +} + +message ReviewRequest { + option (buf.validate.message).cel = { + id: "review.request_id_suffix" + message: "request_id must not end with .result (reserved for the paired response event)" + expression: "!this.request_id.endsWith('.result')" + }; + string experiment_id = 1 [(buf.validate.field).string.pattern = "^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$"]; + uint64 expected_revision = 2; + bool approve = 3; + string reason = 4 [(buf.validate.field).string = {min_len: 1, max_len: 4096}]; + string request_id = 5 [(buf.validate.field).string.pattern = "^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$"]; + // Initial review uses baseline; subsequent reviews require fresh evidence. + Evidence evidence = 6; +} + +message ReviewResponse { + Experiment experiment = 1; +} + +enum ControlAction { + CONTROL_ACTION_UNSPECIFIED = 0; + CONTROL_ACTION_PAUSE = 1; + CONTROL_ACTION_SUSPEND = 2; + CONTROL_ACTION_RESUME = 3; + CONTROL_ACTION_ROLLBACK = 4; + CONTROL_ACTION_RETIRE = 5; +} + +message ControlRequest { + option (buf.validate.message).cel = { + id: "control.request_id_suffix" + message: "request_id must not end with .result (reserved for the paired response event)" + expression: "!this.request_id.endsWith('.result')" + }; + option (buf.validate.message).cel = { + id: "control.one_selector" + message: "exactly one of experiment_id, agent_id, location or fleet is required" + expression: "(this.fleet ? 1 : 0) + (this.experiment_id != '' ? 1 : 0) + (this.agent_id != '' ? 1 : 0) + (this.location != '' ? 1 : 0) == 1" + }; + string experiment_id = 1 [(buf.validate.field).string.pattern = "^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$", (buf.validate.field).ignore = IGNORE_IF_ZERO_VALUE]; + string agent_id = 2 [(buf.validate.field).string.pattern = "^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$", (buf.validate.field).ignore = IGNORE_IF_ZERO_VALUE]; + string location = 3 [(buf.validate.field).string.pattern = "^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$", (buf.validate.field).ignore = IGNORE_IF_ZERO_VALUE]; + bool fleet = 4; + ControlAction action = 5 [(buf.validate.field).enum = {defined_only: true, not_in: [0]}]; + string reason = 6 [(buf.validate.field).string = {min_len: 1, max_len: 4096}]; + string request_id = 7 [(buf.validate.field).string.pattern = "^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$"]; +} + +message ControlResponse { + uint32 affected_agents = 1; +} + +message InspectRequest { + string experiment_id = 1 [(buf.validate.field).string.pattern = "^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$", (buf.validate.field).ignore = IGNORE_IF_ZERO_VALUE]; + string agent_id = 2 [(buf.validate.field).string.pattern = "^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$", (buf.validate.field).ignore = IGNORE_IF_ZERO_VALUE]; +} + +message InspectResponse { + repeated Experiment experiments = 1; + repeated DesiredState desired = 2; + repeated CheckInRequest observed = 3; +} diff --git a/proto/xtcp_treatment/v1/treatment.proto b/proto/xtcp_treatment/v1/treatment.proto new file mode 100644 index 000000000..347998737 --- /dev/null +++ b/proto/xtcp_treatment/v1/treatment.proto @@ -0,0 +1,281 @@ +// +// xTCP - treatment +// +// Shared message types for the TCP route treatment control plane: what a +// treatment is, how it is assigned to an agent, what an agent reports back, +// and how an experiment over a cohort of agents is described and evidenced. +// +// Wire contract only. The controller and the agent work on Go domain types +// and convert at the gRPC boundary; nothing here is a storage schema. +// +// Validation: every client-controlled field carries a buf.validate rule that +// mirrors the bound in pkg/treatment/validate.go, so a malformed request is +// rejected before any handler runs. The Go validators remain authoritative +// for contextual rules that need more than one message (duplicate assignment +// IDs, overlapping destinations, cohort/baseline agreement). +// +// See docs/tcp-controller.md. + +syntax = "proto3"; + +package xtcp_treatment.v1; + +option go_package = "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment"; + +import "buf/validate/validate.proto"; +import "google/protobuf/descriptor.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; + +// Role is the certificate class an RPC requires. The server resolves the +// caller's SPIFFE identity once per call and compares it with this option; +// a method without a declaration is refused for everyone (fail closed). +enum Role { + ROLE_UNSPECIFIED = 0; + // spiffe://xtcp/agent//; may only speak for its own agent_id. + ROLE_AGENT = 1; + // spiffe://xtcp/operator/; recorded as the audit actor. + ROLE_OPERATOR = 2; +} + +extend google.protobuf.MethodOptions { + // Field number from the 50000-99999 range reserved for internal use; + // 51001 is xtcp_auth.v1.access. + Role role = 51002; +} + +// Identifiers are bounded ASCII: a leading alphanumeric, then up to 127 of +// [A-Za-z0-9_.-]. This is the same regexp as treatment.ValidID. + +enum AssignmentMode { + ASSIGNMENT_MODE_UNSPECIFIED = 0; + ASSIGNMENT_MODE_TRIAL = 1; + ASSIGNMENT_MODE_STABLE = 2; + ASSIGNMENT_MODE_SUSPENDED = 3; + ASSIGNMENT_MODE_RETIRED = 4; +} + +enum ApplyStatus { + APPLY_STATUS_UNSPECIFIED = 0; + APPLY_STATUS_PENDING = 1; + APPLY_STATUS_VERIFIED = 2; + APPLY_STATUS_REMOVED = 3; + APPLY_STATUS_EXPIRED = 4; + APPLY_STATUS_FAILED = 5; +} + +enum ExperimentStatus { + EXPERIMENT_STATUS_UNSPECIFIED = 0; + EXPERIMENT_STATUS_PROPOSED = 1; + EXPERIMENT_STATUS_RUNNING = 2; + EXPERIMENT_STATUS_PAUSED = 3; + EXPERIMENT_STATUS_SUSPENDED = 4; + EXPERIMENT_STATUS_COMPLETE = 5; + EXPERIMENT_STATUS_RETIRED = 6; + EXPERIMENT_STATUS_ROLLED_BACK = 7; +} + +// TCPSettings are the per-route TCP parameters a treatment installs. Every +// field is optional; at least one must be present. +message TCPSettings { + option (buf.validate.message).cel = { + id: "tcp_settings.non_empty" + message: "a treatment must set at least one TCP parameter" + expression: "has(this.congestion_control) || has(this.rto_min_ms) || has(this.initial_congestion_window) || has(this.initial_receive_window) || has(this.quick_ack) || has(this.ecn)" + }; + option (buf.validate.message).cel = { + id: "tcp_settings.lock_requires_algorithm" + message: "congestion_lock requires congestion_control" + expression: "!this.congestion_lock || has(this.congestion_control)" + }; + // Kernel algorithm name (TCP_CA_NAME_MAX is 16 including NUL). + optional string congestion_control = 1 [(buf.validate.field).string = { + max_len: 15 + pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$" + }]; + bool congestion_lock = 2; + optional uint32 rto_min_ms = 3 [(buf.validate.field).uint32 = {gte: 1, lte: 120000}]; + optional uint32 initial_congestion_window = 4 [(buf.validate.field).uint32 = {gte: 1, lte: 1024}]; + optional uint32 initial_receive_window = 5 [(buf.validate.field).uint32 = {gte: 1, lte: 1024}]; + optional bool quick_ack = 6; + optional bool ecn = 7; +} + +message Treatment { + option (buf.validate.message).cel = { + id: "treatment.dctcp_requires_ecn" + message: "DCTCP requires independently verified ECN fabric and explicit ECN" + expression: "this.settings.congestion_control != 'dctcp' || (this.ecn_fabric_verified && this.settings.ecn)" + }; + string id = 1 [(buf.validate.field).string.pattern = "^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$"]; + uint64 revision = 2 [(buf.validate.field).uint64.gt = 0]; + // Canonical unicast host prefix (/32 or /128). Canonical form, unicast and + // not-loopback are checked by treatment.ValidateTreatment. + string destination = 3 [(buf.validate.field).string = {ip_prefix: true, max_len: 49}]; + TCPSettings settings = 4 [(buf.validate.field).required = true]; + bool ecn_fabric_verified = 5; +} + +message Assignment { + option (buf.validate.message).cel = { + id: "assignment.trial_requires_lease" + message: "a trial assignment requires lease_expires_at" + expression: "this.mode != 1 || has(this.lease_expires_at)" + }; + option (buf.validate.message).cel = { + id: "assignment.predecessor_destination" + message: "predecessor must cover the same destination" + expression: "!has(this.predecessor) || this.predecessor.destination == this.treatment.destination" + }; + string id = 1 [(buf.validate.field).string.pattern = "^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$"]; + string experiment_id = 2 [(buf.validate.field).string.pattern = "^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$"]; + Treatment treatment = 3 [(buf.validate.field).required = true]; + AssignmentMode mode = 4 [(buf.validate.field).enum = {defined_only: true, not_in: [0]}]; + google.protobuf.Timestamp lease_expires_at = 5; + // Previous approved stable treatment, restored on trial expiry/rollback. + Treatment predecessor = 6; + // Increment only on an explicitly approved reactivation, not lease renewal. + uint64 generation = 7 [(buf.validate.field).uint64.gt = 0]; +} + +// DesiredState is the complete revisioned snapshot an agent reconciles to. +// Duplicate assignment IDs and overlapping destinations are rejected by +// treatment.ValidateDesired. +message DesiredState { + string agent_id = 1 [(buf.validate.field).string.pattern = "^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$"]; + uint64 revision = 2 [(buf.validate.field).uint64.gt = 0]; + bool suspended = 3; + repeated Assignment assignments = 4 [(buf.validate.field).repeated.max_items = 4096]; + google.protobuf.Timestamp issued_at = 5 [(buf.validate.field).required = true]; +} + +message Capabilities { + string boot_id = 1 [(buf.validate.field).string.pattern = "^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$"]; + string kernel_release = 2 [(buf.validate.field).string.max_len = 256]; + repeated string congestion_algorithms = 3 [(buf.validate.field).repeated = { + max_items: 64 + items: {string: {max_len: 15}} + }]; + bool simple_host_routing = 4; + bool actuator_enabled = 5; +} + +// ApplicationRecord is the agent's account of one assignment. Agents report +// these on check-in with installed_route and installed_boot_id cleared. +message ApplicationRecord { + Assignment assignment = 1 [(buf.validate.field).required = true]; + uint64 desired_revision = 2; + ApplyStatus status = 3 [(buf.validate.field).enum.defined_only = true]; + google.protobuf.Timestamp received_at = 4; + google.protobuf.Timestamp attempted_at = 5; + google.protobuf.Timestamp applied_at = 6; + google.protobuf.Timestamp verified_at = 7; + google.protobuf.Timestamp removed_at = 8; + string error = 9 [(buf.validate.field).string.max_len = 4096]; + // Locally generated canonical netlink route body, never accepted from RPC. + bytes installed_route = 10 [(buf.validate.field).bytes.max_len = 4096]; + string installed_boot_id = 11 [(buf.validate.field).string.max_len = 128]; + bool expired_latched = 12; +} + +// LocalState is the agent's journal snapshot. It is a file format for the +// host-side journal and the local tooling, not an RPC payload. +message LocalState { + DesiredState desired = 1; + bool locally_suspended = 2; + string suspension_reason = 3 [(buf.validate.field).string.max_len = 1024]; + repeated ApplicationRecord records = 4 [(buf.validate.field).repeated.max_items = 8192]; + google.protobuf.Timestamp updated_at = 5; +} + +message HostEvidence { + option (buf.validate.message).cel = { + id: "host_evidence.exposed_within_sockets" + message: "exposed_sockets cannot exceed sockets" + expression: "this.exposed_sockets <= this.sockets" + }; + string agent_id = 1 [(buf.validate.field).string.pattern = "^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$"]; + uint64 sockets = 2; + uint64 bytes_acked = 3; + double goodput_bps = 4 [(buf.validate.field).double = {gte: 0, finite: true}]; + double retransmission_ratio = 5 [(buf.validate.field).double = {gte: 0, finite: true}]; + double rtt_p95_ms = 6 [(buf.validate.field).double = {gte: 0, finite: true}]; + uint64 exposed_sockets = 7; + bool collection_healthy = 8; +} + +message Evidence { + option (buf.validate.message).cel = { + id: "evidence.interval" + message: "window_end must follow window_start and analyzed_at must not precede window_end" + expression: "this.window_end > this.window_start && this.analyzed_at >= this.window_end" + }; + string id = 1 [(buf.validate.field).string.pattern = "^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$"]; + google.protobuf.Timestamp analyzed_at = 2 [(buf.validate.field).required = true]; + google.protobuf.Timestamp window_start = 3 [(buf.validate.field).required = true]; + google.protobuf.Timestamp window_end = 4 [(buf.validate.field).required = true]; + string query_version = 5 [(buf.validate.field).string = {min_len: 1, max_len: 256}]; + string enrichment_version = 6 [(buf.validate.field).string.max_len = 256]; + repeated HostEvidence hosts = 7 [(buf.validate.field).repeated = {min_items: 1, max_items: 10000}]; + // SHA-256 of the complete analysis response this summary was derived from. + string artifact_sha256 = 8 [(buf.validate.field).string.pattern = "^[0-9a-f]{64}$"]; +} + +message ExperimentPolicy { + option (buf.validate.message).cel = { + id: "policy.observation_within_phase" + message: "minimum_observation cannot exceed maximum_phase" + expression: "this.minimum_observation <= this.maximum_phase" + }; + google.protobuf.Duration minimum_observation = 1 [ + (buf.validate.field).required = true, + (buf.validate.field).duration = {gt: {seconds: 0}, lte: {seconds: 604800}} + ]; + google.protobuf.Duration maximum_phase = 2 [ + (buf.validate.field).required = true, + (buf.validate.field).duration = {gt: {seconds: 0}, lte: {seconds: 604800}} + ]; + uint64 minimum_sockets_per_host = 3 [(buf.validate.field).uint64.gt = 0]; + uint64 minimum_bytes_per_host = 4 [(buf.validate.field).uint64.gt = 0]; + uint32 minimum_treated_hosts = 5 [(buf.validate.field).uint32 = {gte: 2, lte: 10000}]; + double minimum_improvement = 6 [(buf.validate.field).double = {gte: 0, lte: 1, finite: true}]; + double maximum_retransmission_increase = 7 [(buf.validate.field).double = {gte: 0, lte: 1, finite: true}]; + double maximum_rtt_increase = 8 [(buf.validate.field).double = {gte: 0, lte: 1, finite: true}]; + google.protobuf.Duration maximum_evidence_age = 9 [ + (buf.validate.field).required = true, + (buf.validate.field).duration = {gt: {seconds: 0}, lte: {seconds: 604800}} + ]; +} + +message Phase { + uint32 target_hosts = 1 [(buf.validate.field).uint32 = {gte: 1, lte: 10000}]; + google.protobuf.Timestamp issued_at = 2; + google.protobuf.Timestamp observation_started_at = 3; + google.protobuf.Timestamp evaluated_at = 4; + Evidence evidence = 5; + string outcome = 6 [(buf.validate.field).string.max_len = 64]; + string reviewer = 7 [(buf.validate.field).string.max_len = 128]; + string reason = 8 [(buf.validate.field).string.max_len = 4096]; +} + +message Experiment { + string id = 1 [(buf.validate.field).string.pattern = "^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$"]; + string location = 2 [(buf.validate.field).string.pattern = "^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$"]; + string hypothesis = 3 [(buf.validate.field).string = {min_len: 1, max_len: 8192}]; + Treatment treatment = 4 [(buf.validate.field).required = true]; + ExperimentPolicy policy = 5 [(buf.validate.field).required = true]; + Evidence baseline = 6 [(buf.validate.field).required = true]; + // Randomised host order; phases treat a growing prefix of it. + repeated string cohort_order = 7 [(buf.validate.field).repeated = { + min_items: 3 + max_items: 10000 + unique: true + items: {string: {pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$"}} + }]; + repeated Phase phases = 8 [(buf.validate.field).repeated.max_items = 64]; + ExperimentStatus status = 9 [(buf.validate.field).enum.defined_only = true]; + uint64 revision = 10; + google.protobuf.Timestamp proposed_at = 11; + Evidence latest_evidence = 12; + string latest_outcome = 13 [(buf.validate.field).string.max_len = 64]; +} diff --git a/tools/tcp-controller-integration/main.go b/tools/tcp-controller-integration/main.go index eb22d4e29..b5ef7777e 100644 --- a/tools/tcp-controller-integration/main.go +++ b/tools/tcp-controller-integration/main.go @@ -18,7 +18,7 @@ import ( "strings" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/controller" "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" "google.golang.org/grpc" diff --git a/tools/tcp-controller-integration/nearby.go b/tools/tcp-controller-integration/nearby.go index 6e261ce43..7527f7ef6 100644 --- a/tools/tcp-controller-integration/nearby.go +++ b/tools/tcp-controller-integration/nearby.go @@ -3,7 +3,7 @@ package main import ( "encoding/json" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "google.golang.org/protobuf/proto" ) diff --git a/tools/tcp-controller-integration/soak.go b/tools/tcp-controller-integration/soak.go index 2d67f43c3..ba47b8bab 100644 --- a/tools/tcp-controller-integration/soak.go +++ b/tools/tcp-controller-integration/soak.go @@ -9,7 +9,7 @@ import ( "strings" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/experiment" "google.golang.org/protobuf/encoding/protojson" diff --git a/tools/tcp-controller-integration/soak_test.go b/tools/tcp-controller-integration/soak_test.go index 39b2cfa2c..7808af3dd 100644 --- a/tools/tcp-controller-integration/soak_test.go +++ b/tools/tcp-controller-integration/soak_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_controller" + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" From 85b2cf591741bb82d86c7b2ea08300355ec775e9 Mon Sep 17 00:00:00 2001 From: "randomizedcoder dave.seddon.ca@gmail.com" Date: Sun, 25 Oct 2026 17:07:38 -0700 Subject: [PATCH 05/27] refactor(treatment): explicit experiment state machine and Outcome enum Status changes now go through one transition table in pkg/treatment/experiment/statemachine.go (Transition, Event, Terminal). Review, Control and Maintain apply named events instead of assigning statuses inline, and any pair outside the table fails with FailedPrecondition. TestTransitionExhaustive visits every status and event pair against an independently written legal table. Evaluation results move from free strings to the Outcome enum in treatment.proto, mirrored by experiment.Outcome with Promotable, Withdraws, RenewsLease, ParseOutcome, Wire and OutcomeFromWire. The "observing" state the controller set after verified application becomes OUTCOME_OBSERVING. TestOutcomeMirrorsWire pins the Go and proto values to each other; unknown wire values are rejected rather than defaulted. Assignment.id is removed (reserved) in favour of experiment_id, which it always equalled. docs/tcp-controller.md gains a lifecycle section. Co-Authored-By: Claude Fable 5.1 --- docs/tcp-controller.md | 32 + gen/cpp/xtcp_treatment/v1/operator.pb.cc | 254 +++-- gen/cpp/xtcp_treatment/v1/operator.pb.h | 109 +-- gen/cpp/xtcp_treatment/v1/treatment.pb.cc | 891 +++++++++--------- gen/cpp/xtcp_treatment/v1/treatment.pb.h | 607 +++++------- gen/dart/xtcp_treatment/v1/operator.pb.dart | 9 +- .../xtcp_treatment/v1/operator.pbjson.dart | 12 +- gen/dart/xtcp_treatment/v1/treatment.pb.dart | 64 +- .../xtcp_treatment/v1/treatment.pbenum.dart | 91 ++ .../xtcp_treatment/v1/treatment.pbjson.dart | 96 +- gen/go/xtcp_treatment/operator.pb.go | 60 +- gen/go/xtcp_treatment/operator_vtproto.pb.go | 32 +- gen/go/xtcp_treatment/treatment.pb.go | 297 ++++-- gen/go/xtcp_treatment/treatment_vtproto.pb.go | 107 +-- gen/python/xtcp_treatment/v1/operator_pb2.py | 38 +- gen/python/xtcp_treatment/v1/operator_pb2.pyi | 4 +- gen/python/xtcp_treatment/v1/treatment_pb2.py | 64 +- .../xtcp_treatment/v1/treatment_pb2.pyi | 55 +- pkg/treatment/agent/agent.go | 2 +- pkg/treatment/agent/agent_test.go | 2 +- pkg/treatment/agent/benchmark_test.go | 2 +- pkg/treatment/agent/contracts_test.go | 4 +- pkg/treatment/controller/analyze.go | 4 +- pkg/treatment/controller/maintenance.go | 30 +- .../controller/rpc_contracts_test.go | 2 +- pkg/treatment/controller/server.go | 83 +- pkg/treatment/controller/server_test.go | 2 +- pkg/treatment/experiment/benchmark_test.go | 2 +- pkg/treatment/experiment/boundaries_test.go | 63 +- pkg/treatment/experiment/experiment.go | 52 +- pkg/treatment/experiment/experiment_test.go | 21 +- pkg/treatment/experiment/outcome.go | 106 +++ pkg/treatment/experiment/outcome_test.go | 90 ++ pkg/treatment/experiment/statemachine.go | 146 +++ pkg/treatment/experiment/statemachine_test.go | 162 ++++ pkg/treatment/internal/fixtures/fixtures.go | 1 - pkg/treatment/reconcile/journal.go | 8 +- pkg/treatment/reconcile/journal_test.go | 2 +- pkg/treatment/reconcile/reconcile.go | 14 +- pkg/treatment/reconcile/reconcile_test.go | 2 +- pkg/treatment/validate.go | 4 +- pkg/treatment/validate_test.go | 6 +- proto/xtcp_treatment/v1/operator.proto | 2 +- proto/xtcp_treatment/v1/treatment.proto | 45 +- tools/tcp-controller-integration/main.go | 4 +- tools/tcp-controller-integration/soak.go | 2 +- tools/tcp-controller-integration/soak_test.go | 4 +- 47 files changed, 2156 insertions(+), 1533 deletions(-) create mode 100644 pkg/treatment/experiment/outcome.go create mode 100644 pkg/treatment/experiment/outcome_test.go create mode 100644 pkg/treatment/experiment/statemachine.go create mode 100644 pkg/treatment/experiment/statemachine_test.go diff --git a/docs/tcp-controller.md b/docs/tcp-controller.md index b36533f28..514935c4c 100644 --- a/docs/tcp-controller.md +++ b/docs/tcp-controller.md @@ -153,6 +153,38 @@ trial leases eventually expire. Stable assignments have no lease. Completed experiments receive hourly drift analysis; regressions request operator review instead of automatically changing approved stable routes. +### Lifecycle + +An experiment's status only changes through the transition table in +[`experiment.Transition`](../pkg/treatment/experiment/statemachine.go); Review, +Control and maintenance all apply a named event to the current status, and any +pair outside the table fails with `FailedPrecondition`. `TestTransitionExhaustive` +pins every status and event pair. + +| From | Event | To | +| --- | --- | --- | +| PROPOSED | approve | RUNNING | +| RUNNING | approve (cohorts remain) | RUNNING, next phase issued | +| RUNNING | approve_final (100% phase) | COMPLETE | +| RUNNING | pause | PAUSED (routes stay, leases run down) | +| PROPOSED, RUNNING, PAUSED, COMPLETE | suspend | SUSPENDED (routes withdrawn) | +| PAUSED, SUSPENDED | resume | RUNNING with a fresh phase of the same size | +| PAUSED, SUSPENDED | resume_stable (last phase was FINAL_SAFETY) | COMPLETE | +| RUNNING, PAUSED | expire | ROLLED_BACK | +| RUNNING | harm | ROLLED_BACK | +| any non-terminal | reject, retire | RETIRED | +| any non-terminal | rollback | ROLLED_BACK | + +RETIRED and ROLLED_BACK are terminal; propose a new experiment instead. + +Evaluation results are the `Outcome` enum in `treatment.proto`, mirrored by +`experiment.Outcome`. Only `BENEFICIAL`, `SAFETY_ONLY` and `FINAL_SAFETY` can +promote a phase, and only through a review; `HARMFUL` and `EXPIRED` withdraw +automatically; `STALE` and `INSUFFICIENT_DATA` cannot renew a lease. `OBSERVING` +means every treated host has verified the assignment and the window is open. +An assignment is identified by its `experiment_id`; one experiment never issues +two concurrent assignments to the same host. + [analysis.sql](../pkg/treatment/controller/analysis.sql) computes whole-window counter deltas per boot, host, namespace and socket cookie, deduplicates poll timestamps, then aggregates per host. It rejects negative endpoint deltas and diff --git a/gen/cpp/xtcp_treatment/v1/operator.pb.cc b/gen/cpp/xtcp_treatment/v1/operator.pb.cc index 608f20fd8..bdd43e97f 100644 --- a/gen/cpp/xtcp_treatment/v1/operator.pb.cc +++ b/gen/cpp/xtcp_treatment/v1/operator.pb.cc @@ -921,21 +921,21 @@ constexpr AnalyzeResponse::ParseTableT_ AnalyzeResponse::InternalGenerateParseTa ::_pbi::TcParser::GetTable<::xtcp_treatment::v1::AnalyzeResponse>(), // to_prefetch #endif // PROTOBUF_PREFETCH_PARSE_TABLE }, {{ - // string outcome = 2 [json_name = "outcome"]; - {::_pbi::TcParser::FastUS1, - {18, 0, 0, + // .xtcp_treatment.v1.Outcome outcome = 2 [json_name = "outcome"]; + {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(AnalyzeResponse, _impl_.outcome_), 1>(), + {16, 1, 0, PROTOBUF_FIELD_OFFSET(AnalyzeResponse, _impl_.outcome_)}}, // .xtcp_treatment.v1.Evidence evidence = 1 [json_name = "evidence"]; {::_pbi::TcParser::FastMtS1, - {10, 1, 0, + {10, 0, 0, PROTOBUF_FIELD_OFFSET(AnalyzeResponse, _impl_.evidence_)}}, }}, {{ 65535, 65535 }}, {{ // .xtcp_treatment.v1.Evidence evidence = 1 [json_name = "evidence"]; - {PROTOBUF_FIELD_OFFSET(AnalyzeResponse, _impl_.evidence_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, - // string outcome = 2 [json_name = "outcome"]; - {PROTOBUF_FIELD_OFFSET(AnalyzeResponse, _impl_.outcome_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + {PROTOBUF_FIELD_OFFSET(AnalyzeResponse, _impl_.evidence_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // .xtcp_treatment.v1.Outcome outcome = 2 [json_name = "outcome"]; + {PROTOBUF_FIELD_OFFSET(AnalyzeResponse, _impl_.outcome_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kOpenEnum)}, }}, {{ #ifndef PROTOBUF_MESSAGE_GLOBALS @@ -945,9 +945,6 @@ constexpr AnalyzeResponse::ParseTableT_ AnalyzeResponse::InternalGenerateParseTa #endif }}, {{ - "\41\0\7\0\0\0\0\0" - "xtcp_treatment.v1.AnalyzeResponse" - "outcome" }}, }; } @@ -956,10 +953,8 @@ constexpr AnalyzeResponse::ParseTableT_ AnalyzeResponse::InternalGenerateParseTa inline constexpr AnalyzeResponse::Impl_::Impl_( [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, ::_pbi::ConstantInitialized) noexcept - : outcome_( - &::google::protobuf::internal::fixed_address_empty_string, - ::_pbi::ConstantInitialized()), - evidence_{nullptr} {} + : evidence_{nullptr}, + outcome_{static_cast< ::xtcp_treatment::v1::Outcome >(0)} {} template constexpr AnalyzeResponse::AnalyzeResponse(::_pbi::ConstantInitialized, @@ -977,7 +972,7 @@ inline void* PROTOBUF_NONNULL AnalyzeResponse::PlacementNew_( return ::new (mem) AnalyzeResponse(arena); } constexpr auto AnalyzeResponse::InternalNewImpl_() { - return ::google::protobuf::internal::MessageCreator::CopyInit(sizeof(AnalyzeResponse), alignof(AnalyzeResponse)); + return ::google::protobuf::internal::MessageCreator::ZeroInit(sizeof(AnalyzeResponse), alignof(AnalyzeResponse)); } constexpr auto AnalyzeResponse::InternalGenerateClassData_( const MessageLite& prototype, @@ -1719,8 +1714,8 @@ const ::uint32_t 5, // hasbit index offset PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::AnalyzeResponse, _impl_.evidence_), PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::AnalyzeResponse, _impl_.outcome_), - 1, 0, + 1, 0x081, // bitmap PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ReviewRequest, _impl_._has_bits_), 9, // hasbit index offset @@ -1826,77 +1821,77 @@ const char descriptor_table_protodef_xtcp_5ftreatment_2fv1_2foperator_2eproto[] "Request\022N\n\rexperiment_id\030\001 \001(\tB)\272H&r$2\"^" "[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\014exper" "imentId\022+\n\021expected_revision\030\002 \001(\004R\020expe" - "ctedRevision\"d\n\017AnalyzeResponse\0227\n\010evide" - "nce\030\001 \001(\0132\033.xtcp_treatment.v1.EvidenceR\010" - "evidence\022\030\n\007outcome\030\002 \001(\tR\007outcome\"\346\003\n\rR" - "eviewRequest\022N\n\rexperiment_id\030\001 \001(\tB)\272H&" - "r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\014" - "experimentId\022+\n\021expected_revision\030\002 \001(\004R" - "\020expectedRevision\022\030\n\007approve\030\003 \001(\010R\007appr" - "ove\022\"\n\006reason\030\004 \001(\tB\n\272H\007r\005\020\001\030\200 R\006reason\022" - "H\n\nrequest_id\030\005 \001(\tB)\272H&r$2\"^[a-zA-Z0-9]" - "[a-zA-Z0-9_.-]{0,127}$R\trequestId\0227\n\010evi" - "dence\030\006 \001(\0132\033.xtcp_treatment.v1.Evidence" - "R\010evidence:\226\001\272H\222\001\032\217\001\n\030review.request_id_" - "suffix\022Mrequest_id must not end with .re" - "sult (reserved for the paired response e" - "vent)\032$!this.request_id.endsWith(\'.resul" - "t\')\"O\n\016ReviewResponse\022=\n\nexperiment\030\001 \001(" - "\0132\035.xtcp_treatment.v1.ExperimentR\nexperi" - "ment\"\272\006\n\016ControlRequest\022Q\n\rexperiment_id" - "\030\001 \001(\tB,\272H)r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-" - "]{0,127}$\330\001\001R\014experimentId\022G\n\010agent_id\030\002" + "ctedRevision\"\200\001\n\017AnalyzeResponse\0227\n\010evid" + "ence\030\001 \001(\0132\033.xtcp_treatment.v1.EvidenceR" + "\010evidence\0224\n\007outcome\030\002 \001(\0162\032.xtcp_treatm" + "ent.v1.OutcomeR\007outcome\"\346\003\n\rReviewReques" + "t\022N\n\rexperiment_id\030\001 \001(\tB)\272H&r$2\"^[a-zA-" + "Z0-9][a-zA-Z0-9_.-]{0,127}$R\014experimentI" + "d\022+\n\021expected_revision\030\002 \001(\004R\020expectedRe" + "vision\022\030\n\007approve\030\003 \001(\010R\007approve\022\"\n\006reas" + "on\030\004 \001(\tB\n\272H\007r\005\020\001\030\200 R\006reason\022H\n\nrequest_" + "id\030\005 \001(\tB)\272H&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_" + ".-]{0,127}$R\trequestId\0227\n\010evidence\030\006 \001(\013" + "2\033.xtcp_treatment.v1.EvidenceR\010evidence:" + "\226\001\272H\222\001\032\217\001\n\030review.request_id_suffix\022Mreq" + "uest_id must not end with .result (reser" + "ved for the paired response event)\032$!thi" + "s.request_id.endsWith(\'.result\')\"O\n\016Revi" + "ewResponse\022=\n\nexperiment\030\001 \001(\0132\035.xtcp_tr" + "eatment.v1.ExperimentR\nexperiment\"\272\006\n\016Co" + "ntrolRequest\022Q\n\rexperiment_id\030\001 \001(\tB,\272H)" + "r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$\330\001" + "\001R\014experimentId\022G\n\010agent_id\030\002 \001(\tB,\272H)r$" + "2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$\330\001\001R" + "\007agentId\022H\n\010location\030\003 \001(\tB,\272H)r$2\"^[a-z" + "A-Z0-9][a-zA-Z0-9_.-]{0,127}$\330\001\001R\010locati" + "on\022\024\n\005fleet\030\004 \001(\010R\005fleet\022D\n\006action\030\005 \001(\016" + "2 .xtcp_treatment.v1.ControlActionB\n\272H\007\202" + "\001\004\020\001 \000R\006action\022\"\n\006reason\030\006 \001(\tB\n\272H\007r\005\020\001\030" + "\200 R\006reason\022H\n\nrequest_id\030\007 \001(\tB)\272H&r$2\"^" + "[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\treque" + "stId:\367\002\272H\363\002\032\220\001\n\031control.request_id_suffi" + "x\022Mrequest_id must not end with .result " + "(reserved for the paired response event)" + "\032$!this.request_id.endsWith(\'.result\')\032\335" + "\001\n\024control.one_selector\022Eexactly one of " + "experiment_id, agent_id, location or fle" + "et is required\032~(this.fleet \? 1 : 0) + (" + "this.experiment_id != \'\' \? 1 : 0) + (thi" + "s.agent_id != \'\' \? 1 : 0) + (this.locati" + "on != \'\' \? 1 : 0) == 1\":\n\017ControlRespons" + "e\022\'\n\017affected_agents\030\001 \001(\rR\016affectedAgen" + "ts\"\254\001\n\016InspectRequest\022Q\n\rexperiment_id\030\001" " \001(\tB,\272H)r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{" - "0,127}$\330\001\001R\007agentId\022H\n\010location\030\003 \001(\tB,\272" - "H)r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$" - "\330\001\001R\010location\022\024\n\005fleet\030\004 \001(\010R\005fleet\022D\n\006a" - "ction\030\005 \001(\0162 .xtcp_treatment.v1.ControlA" - "ctionB\n\272H\007\202\001\004\020\001 \000R\006action\022\"\n\006reason\030\006 \001(" - "\tB\n\272H\007r\005\020\001\030\200 R\006reason\022H\n\nrequest_id\030\007 \001(" - "\tB)\272H&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,1" - "27}$R\trequestId:\367\002\272H\363\002\032\220\001\n\031control.reque" - "st_id_suffix\022Mrequest_id must not end wi" - "th .result (reserved for the paired resp" - "onse event)\032$!this.request_id.endsWith(\'" - ".result\')\032\335\001\n\024control.one_selector\022Eexac" - "tly one of experiment_id, agent_id, loca" - "tion or fleet is required\032~(this.fleet \?" - " 1 : 0) + (this.experiment_id != \'\' \? 1 " - ": 0) + (this.agent_id != \'\' \? 1 : 0) + (" - "this.location != \'\' \? 1 : 0) == 1\":\n\017Con" - "trolResponse\022\'\n\017affected_agents\030\001 \001(\rR\016a" - "ffectedAgents\"\254\001\n\016InspectRequest\022Q\n\rexpe" - "riment_id\030\001 \001(\tB,\272H)r$2\"^[a-zA-Z0-9][a-z" - "A-Z0-9_.-]{0,127}$\330\001\001R\014experimentId\022G\n\010a" - "gent_id\030\002 \001(\tB,\272H)r$2\"^[a-zA-Z0-9][a-zA-" - "Z0-9_.-]{0,127}$\330\001\001R\007agentId\"\314\001\n\017Inspect" - "Response\022\?\n\013experiments\030\001 \003(\0132\035.xtcp_tre" - "atment.v1.ExperimentR\013experiments\0229\n\007des" - "ired\030\002 \003(\0132\037.xtcp_treatment.v1.DesiredSt" - "ateR\007desired\022=\n\010observed\030\003 \003(\0132!.xtcp_tr" - "eatment.v1.CheckInRequestR\010observed*\270\001\n\r" - "ControlAction\022\036\n\032CONTROL_ACTION_UNSPECIF" - "IED\020\000\022\030\n\024CONTROL_ACTION_PAUSE\020\001\022\032\n\026CONTR" - "OL_ACTION_SUSPEND\020\002\022\031\n\025CONTROL_ACTION_RE" - "SUME\020\003\022\033\n\027CONTROL_ACTION_ROLLBACK\020\004\022\031\n\025C" - "ONTROL_ACTION_RETIRE\020\0052\306\003\n\017OperatorServi" - "ce\022V\n\007Propose\022!.xtcp_treatment.v1.Propos" - "eRequest\032\".xtcp_treatment.v1.ProposeResp" - "onse\"\004\320\363\030\002\022V\n\007Analyze\022!.xtcp_treatment.v" - "1.AnalyzeRequest\032\".xtcp_treatment.v1.Ana" - "lyzeResponse\"\004\320\363\030\002\022S\n\006Review\022 .xtcp_trea" - "tment.v1.ReviewRequest\032!.xtcp_treatment." - "v1.ReviewResponse\"\004\320\363\030\002\022V\n\007Control\022!.xtc" - "p_treatment.v1.ControlRequest\032\".xtcp_tre" - "atment.v1.ControlResponse\"\004\320\363\030\002\022V\n\007Inspe" - "ct\022!.xtcp_treatment.v1.InspectRequest\032\"." - "xtcp_treatment.v1.InspectResponse\"\004\320\363\030\002B" - "\277\001\n\025com.xtcp_treatment.v1B\rOperatorProto" - "P\001Z6github.com/randomizedcoder/xtcp2/gen" - "/go/xtcp_treatment\242\002\003XXX\252\002\020XtcpTreatment" - ".V1\312\002\020XtcpTreatment\\V1\342\002\034XtcpTreatment\\V" - "1\\GPBMetadata\352\002\021XtcpTreatment::V1b\006proto" - "3" + "0,127}$\330\001\001R\014experimentId\022G\n\010agent_id\030\002 \001" + "(\tB,\272H)r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0," + "127}$\330\001\001R\007agentId\"\314\001\n\017InspectResponse\022\?\n" + "\013experiments\030\001 \003(\0132\035.xtcp_treatment.v1.E" + "xperimentR\013experiments\0229\n\007desired\030\002 \003(\0132" + "\037.xtcp_treatment.v1.DesiredStateR\007desire" + "d\022=\n\010observed\030\003 \003(\0132!.xtcp_treatment.v1." + "CheckInRequestR\010observed*\270\001\n\rControlActi" + "on\022\036\n\032CONTROL_ACTION_UNSPECIFIED\020\000\022\030\n\024CO" + "NTROL_ACTION_PAUSE\020\001\022\032\n\026CONTROL_ACTION_S" + "USPEND\020\002\022\031\n\025CONTROL_ACTION_RESUME\020\003\022\033\n\027C" + "ONTROL_ACTION_ROLLBACK\020\004\022\031\n\025CONTROL_ACTI" + "ON_RETIRE\020\0052\306\003\n\017OperatorService\022V\n\007Propo" + "se\022!.xtcp_treatment.v1.ProposeRequest\032\"." + "xtcp_treatment.v1.ProposeResponse\"\004\320\363\030\002\022" + "V\n\007Analyze\022!.xtcp_treatment.v1.AnalyzeRe" + "quest\032\".xtcp_treatment.v1.AnalyzeRespons" + "e\"\004\320\363\030\002\022S\n\006Review\022 .xtcp_treatment.v1.Re" + "viewRequest\032!.xtcp_treatment.v1.ReviewRe" + "sponse\"\004\320\363\030\002\022V\n\007Control\022!.xtcp_treatment" + ".v1.ControlRequest\032\".xtcp_treatment.v1.C" + "ontrolResponse\"\004\320\363\030\002\022V\n\007Inspect\022!.xtcp_t" + "reatment.v1.InspectRequest\032\".xtcp_treatm" + "ent.v1.InspectResponse\"\004\320\363\030\002B\277\001\n\025com.xtc" + "p_treatment.v1B\rOperatorProtoP\001Z6github." + "com/randomizedcoder/xtcp2/gen/go/xtcp_tr" + "eatment\242\002\003XXX\252\002\020XtcpTreatment.V1\312\002\020XtcpT" + "reatment\\V1\342\002\034XtcpTreatment\\V1\\GPBMetada" + "ta\352\002\021XtcpTreatment::V1b\006proto3" }; static const ::_pbi::DescriptorTable* PROTOBUF_NONNULL const descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2eproto_deps[3] = { @@ -1908,7 +1903,7 @@ static ::absl::once_flag descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2epro PROTOBUF_CONSTINIT const ::_pbi::DescriptorTable descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2eproto = { false, false, - 3481, + 3510, descriptor_table_protodef_xtcp_5ftreatment_2fv1_2foperator_2eproto, "xtcp_treatment/v1/operator.proto", &descriptor_table_xtcp_5ftreatment_2fv1_2foperator_2eproto_once, @@ -2635,7 +2630,7 @@ ::google::protobuf::Metadata AnalyzeRequest::GetMetadata() const { void AnalyzeResponse::clear_evidence() { ::google::protobuf::internal::TSanWrite(&_impl_); if (_impl_.evidence_ != nullptr) _impl_.evidence_->Clear(); - ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); } AnalyzeResponse::AnalyzeResponse(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) #if defined(PROTOBUF_CUSTOM_VTABLE) @@ -2650,8 +2645,7 @@ PROTOBUF_NDEBUG_INLINE AnalyzeResponse::Impl_::Impl_( [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, [[maybe_unused]] const ::xtcp_treatment::v1::AnalyzeResponse& from_msg) - : _has_bits_{from._has_bits_}, - outcome_(arena, from.outcome_) {} + : _has_bits_{from._has_bits_} {} AnalyzeResponse::AnalyzeResponse( ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, @@ -2668,20 +2662,26 @@ AnalyzeResponse::AnalyzeResponse( from._internal_metadata_); new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); ::uint32_t cached_has_bits = _impl_._has_bits_[0]; - _impl_.evidence_ = (CheckHasBit(cached_has_bits, 0x00000002U)) + _impl_.evidence_ = (CheckHasBit(cached_has_bits, 0x00000001U)) ? Super_::CopyConstruct(arena, *from._impl_.evidence_) : nullptr; + _impl_.outcome_ = from._impl_.outcome_; // @@protoc_insertion_point(copy_constructor:xtcp_treatment.v1.AnalyzeResponse) } PROTOBUF_NDEBUG_INLINE AnalyzeResponse::Impl_::Impl_( [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) - : outcome_(arena) {} + {} inline void AnalyzeResponse::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { new (&_impl_) Impl_(internal_visibility(), arena); - _impl_.evidence_ = {}; + ::memset(reinterpret_cast(&_impl_) + + offsetof(Impl_, evidence_), + 0, + offsetof(Impl_, outcome_) - + offsetof(Impl_, evidence_) + + sizeof(Impl_::outcome_)); } AnalyzeResponse::~AnalyzeResponse() { // @@protoc_insertion_point(destructor:xtcp_treatment.v1.AnalyzeResponse) @@ -2694,7 +2694,6 @@ inline void AnalyzeResponse::SharedDtor(MessageLite& self) { } this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); ABSL_DCHECK(this_.GetArena() == nullptr); - this_._impl_.outcome_.Destroy(); delete this_._impl_.evidence_; this_._impl_.~Impl_(); } @@ -2732,15 +2731,11 @@ PROTOBUF_NOINLINE void AnalyzeResponse::Clear() { ::uint32_t cached_has_bits [[maybe_unused]] = 0; cached_has_bits = this_._impl_._has_bits_[0]; - if (BatchCheckHasBit(cached_has_bits, 0x00000003U)) { - if (CheckHasBit(cached_has_bits, 0x00000001U)) { - this_._impl_.outcome_.ClearNonDefaultToEmpty(); - } - if (CheckHasBit(cached_has_bits, 0x00000002U)) { - ABSL_DCHECK(this_._impl_.evidence_ != nullptr); - this_._impl_.evidence_->Clear(); - } + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + ABSL_DCHECK(this_._impl_.evidence_ != nullptr); + this_._impl_.evidence_->Clear(); } + this_._impl_.outcome_ = 0; _impl_._has_bits_.Clear(); _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); } @@ -2765,19 +2760,18 @@ ::uint8_t* PROTOBUF_NONNULL AnalyzeResponse::_InternalSerialize( cached_has_bits = this_._impl_._has_bits_[0]; // .xtcp_treatment.v1.Evidence evidence = 1 [json_name = "evidence"]; - if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( 1, *this_._impl_.evidence_, this_._impl_.evidence_->GetCachedSize(), target, stream); } - // string outcome = 2 [json_name = "outcome"]; - if (CheckHasBit(cached_has_bits, 0x00000001U)) { - if (!this_._internal_outcome().empty()) { - const ::std::string& _s = this_._internal_outcome(); - ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( - _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.AnalyzeResponse.outcome"); - target = stream->WriteStringMaybeAliased(2, _s, target); + // .xtcp_treatment.v1.Outcome outcome = 2 [json_name = "outcome"]; + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (this_._internal_outcome() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 2, this_._internal_outcome(), target); } } @@ -2805,18 +2799,18 @@ ::size_t AnalyzeResponse::ByteSizeLong() const { ::_pbi::Prefetch5LinesFrom7Lines(&this_); cached_has_bits = this_._impl_._has_bits_[0]; if (BatchCheckHasBit(cached_has_bits, 0x00000003U)) { - // string outcome = 2 [json_name = "outcome"]; - if (CheckHasBit(cached_has_bits, 0x00000001U)) { - if (!this_._internal_outcome().empty()) { - total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( - this_._internal_outcome()); - } - } // .xtcp_treatment.v1.Evidence evidence = 1 [json_name = "evidence"]; - if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { total_size += 1 + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.evidence_); } + // .xtcp_treatment.v1.Outcome outcome = 2 [json_name = "outcome"]; + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (this_._internal_outcome() != 0) { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this_._internal_outcome()); + } + } } return this_.MaybeComputeUnknownFieldsSize(total_size, &this_._impl_._cached_size_); @@ -2838,15 +2832,6 @@ void AnalyzeResponse::MergeImpl(::google::protobuf::MessageLite& to_msg, cached_has_bits = from._impl_._has_bits_[0]; if (BatchCheckHasBit(cached_has_bits, 0x00000003U)) { if (CheckHasBit(cached_has_bits, 0x00000001U)) { - if (!from._internal_outcome().empty()) { - _this->_internal_set_outcome(from._internal_outcome()); - } else { - if (_this->_impl_.outcome_.IsDefault()) { - _this->_internal_set_outcome(""); - } - } - } - if (CheckHasBit(cached_has_bits, 0x00000002U)) { ABSL_DCHECK(from._impl_.evidence_ != nullptr); if (_this->_impl_.evidence_ == nullptr) { _this->_impl_.evidence_ = Super_::CopyConstruct(arena, *from._impl_.evidence_); @@ -2854,6 +2839,11 @@ void AnalyzeResponse::MergeImpl(::google::protobuf::MessageLite& to_msg, _this->_impl_.evidence_->MergeFrom(*from._impl_.evidence_); } } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (from._internal_outcome() != 0) { + _this->_impl_.outcome_ = from._impl_.outcome_; + } + } } _this->_impl_._has_bits_[0] |= cached_has_bits; _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( @@ -2870,12 +2860,14 @@ void AnalyzeResponse::CopyFrom(const AnalyzeResponse& from) { void AnalyzeResponse::InternalSwap(AnalyzeResponse* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { using ::std::swap; - auto* arena = GetArena(); - ABSL_DCHECK_EQ(arena, other->GetArena()); _internal_metadata_.InternalSwap(&other->_internal_metadata_); swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); - ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.outcome_, &other->_impl_.outcome_, arena); - swap(_impl_.evidence_, other->_impl_.evidence_); + ::google::protobuf::internal::memswap< + PROTOBUF_FIELD_OFFSET(AnalyzeResponse, _impl_.outcome_) + + sizeof(AnalyzeResponse::_impl_.outcome_) + - PROTOBUF_FIELD_OFFSET(AnalyzeResponse, _impl_.evidence_)>( + reinterpret_cast(&_impl_.evidence_), + reinterpret_cast(&other->_impl_.evidence_)); } ::google::protobuf::Metadata AnalyzeResponse::GetMetadata() const { diff --git a/gen/cpp/xtcp_treatment/v1/operator.pb.h b/gen/cpp/xtcp_treatment/v1/operator.pb.h index 645536689..57c204eab 100644 --- a/gen/cpp/xtcp_treatment/v1/operator.pb.h +++ b/gen/cpp/xtcp_treatment/v1/operator.pb.h @@ -1565,24 +1565,9 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED AnalyzeResponse final : public ::go // accessors ------------------------------------------------------- enum : int { - kOutcomeFieldNumber = 2, kEvidenceFieldNumber = 1, + kOutcomeFieldNumber = 2, }; - // string outcome = 2 [json_name = "outcome"]; - void clear_outcome() ; - [[nodiscard]] const ::std::string& outcome() const; - template - void set_outcome(Arg_&& arg, Args_... args); - ::std::string* PROTOBUF_NONNULL mutable_outcome(); - [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_outcome(); - void set_allocated_outcome(::std::string* PROTOBUF_NULLABLE value); - - private: - const ::std::string& _internal_outcome() const; - PROTOBUF_ALWAYS_INLINE void _internal_set_outcome(const ::std::string& value); - ::std::string* PROTOBUF_NONNULL _internal_mutable_outcome(); - - public: // .xtcp_treatment.v1.Evidence evidence = 1 [json_name = "evidence"]; [[nodiscard]] bool has_evidence() const; void clear_evidence() ; @@ -1597,13 +1582,23 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED AnalyzeResponse final : public ::go const ::xtcp_treatment::v1::Evidence& _internal_evidence() const; ::xtcp_treatment::v1::Evidence* PROTOBUF_NONNULL _internal_mutable_evidence(); + public: + // .xtcp_treatment.v1.Outcome outcome = 2 [json_name = "outcome"]; + void clear_outcome() ; + [[nodiscard]] ::xtcp_treatment::v1::Outcome outcome() const; + void set_outcome(::xtcp_treatment::v1::Outcome value); + + private: + ::xtcp_treatment::v1::Outcome _internal_outcome() const; + void _internal_set_outcome(::xtcp_treatment::v1::Outcome value); + public: // @@protoc_insertion_point(class_scope:xtcp_treatment.v1.AnalyzeResponse) private: class _Internal; using ParseTableT_ = ::google::protobuf::internal::TcParseTable<1, 2, - 1, 49, + 1, 0, 2>; static constexpr ParseTableT_ InternalGenerateParseTable_( const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); @@ -1631,8 +1626,8 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED AnalyzeResponse final : public ::go const AnalyzeResponse& from_msg); ::google::protobuf::internal::HasBits<1> _has_bits_; ::google::protobuf::internal::CachedSize _cached_size_; - ::google::protobuf::internal::ArenaStringPtr outcome_; ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE evidence_; + int outcome_; PROTOBUF_TSAN_DECLARE_MEMBER }; union { Impl_ _impl_; }; @@ -2897,7 +2892,7 @@ inline void AnalyzeRequest::_internal_set_expected_revision(::uint64_t value) { // .xtcp_treatment.v1.Evidence evidence = 1 [json_name = "evidence"]; inline bool AnalyzeResponse::has_evidence() const { - bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000002U); + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000001U); PROTOBUF_ASSUME(!value || _impl_.evidence_ != nullptr); return value; } @@ -2918,16 +2913,16 @@ inline void AnalyzeResponse::unsafe_arena_set_allocated_evidence( } _impl_.evidence_ = reinterpret_cast<::xtcp_treatment::v1::Evidence*>(value); if (value != nullptr) { - SetHasBit(_impl_._has_bits_[0], 0x00000002U); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); } // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.AnalyzeResponse.evidence) } inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE AnalyzeResponse::release_evidence() { ::google::protobuf::internal::TSanWrite(&_impl_); - ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); ::xtcp_treatment::v1::Evidence* released = _impl_.evidence_; _impl_.evidence_ = nullptr; if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { @@ -2947,7 +2942,7 @@ inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE AnalyzeResponse::unsafe ::google::protobuf::internal::TSanWrite(&_impl_); // @@protoc_insertion_point(field_release:xtcp_treatment.v1.AnalyzeResponse.evidence) - ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); ::xtcp_treatment::v1::Evidence* temp = _impl_.evidence_; _impl_.evidence_ = nullptr; return temp; @@ -2962,7 +2957,7 @@ inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NONNULL AnalyzeResponse::_intern } inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NONNULL AnalyzeResponse::mutable_evidence() ABSL_ATTRIBUTE_LIFETIME_BOUND { - SetHasBit(_impl_._has_bits_[0], 0x00000002U); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); ::xtcp_treatment::v1::Evidence* _msg = _internal_mutable_evidence(); // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.AnalyzeResponse.evidence) return _msg; @@ -2979,77 +2974,37 @@ inline void AnalyzeResponse::set_allocated_evidence(::xtcp_treatment::v1::Eviden if (message_arena != submessage_arena) { value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); } - SetHasBit(_impl_._has_bits_[0], 0x00000002U); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); } _impl_.evidence_ = reinterpret_cast<::xtcp_treatment::v1::Evidence*>(value); // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.AnalyzeResponse.evidence) } -// string outcome = 2 [json_name = "outcome"]; +// .xtcp_treatment.v1.Outcome outcome = 2 [json_name = "outcome"]; inline void AnalyzeResponse::clear_outcome() { ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.outcome_.ClearToEmpty(); - ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + _impl_.outcome_ = 0; + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); } -inline const ::std::string& AnalyzeResponse::outcome() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { +inline ::xtcp_treatment::v1::Outcome AnalyzeResponse::outcome() const { // @@protoc_insertion_point(field_get:xtcp_treatment.v1.AnalyzeResponse.outcome) return _internal_outcome(); } -template -PROTOBUF_ALWAYS_INLINE void AnalyzeResponse::set_outcome(Arg_&& arg, Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - SetHasBit(_impl_._has_bits_[0], 0x00000001U); - _impl_.outcome_.Set(static_cast(arg), args..., GetArena()); +inline void AnalyzeResponse::set_outcome(::xtcp_treatment::v1::Outcome value) { + _internal_set_outcome(value); + SetHasBit(_impl_._has_bits_[0], 0x00000002U); // @@protoc_insertion_point(field_set:xtcp_treatment.v1.AnalyzeResponse.outcome) } -inline ::std::string* PROTOBUF_NONNULL AnalyzeResponse::mutable_outcome() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - SetHasBit(_impl_._has_bits_[0], 0x00000001U); - ::std::string* _s = _internal_mutable_outcome(); - // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.AnalyzeResponse.outcome) - return _s; -} -inline const ::std::string& AnalyzeResponse::_internal_outcome() const { +inline ::xtcp_treatment::v1::Outcome AnalyzeResponse::_internal_outcome() const { ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.outcome_.Get(); -} -inline void AnalyzeResponse::_internal_set_outcome(const ::std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.outcome_.Set(value, GetArena()); -} -inline ::std::string* PROTOBUF_NONNULL AnalyzeResponse::_internal_mutable_outcome() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.outcome_.Mutable( GetArena()); + return static_cast<::xtcp_treatment::v1::Outcome>(_impl_.outcome_); } -inline ::std::string* PROTOBUF_NULLABLE AnalyzeResponse::release_outcome() { +inline void AnalyzeResponse::_internal_set_outcome(::xtcp_treatment::v1::Outcome value) { ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:xtcp_treatment.v1.AnalyzeResponse.outcome) - if (!CheckHasBit(_impl_._has_bits_[0], 0x00000001U)) { - return nullptr; - } - ClearHasBit(_impl_._has_bits_[0], 0x00000001U); - auto* released = _impl_.outcome_.Release(); - if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { - _impl_.outcome_.Set("", GetArena()); - } - return released; -} -inline void AnalyzeResponse::set_allocated_outcome(::std::string* PROTOBUF_NULLABLE value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - if (value != nullptr) { - SetHasBit(_impl_._has_bits_[0], 0x00000001U); - } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000001U); - } - _impl_.outcome_.SetAllocated(value, GetArena()); - if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.outcome_.IsDefault()) { - _impl_.outcome_.Set("", GetArena()); - } - // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.AnalyzeResponse.outcome) + _impl_.outcome_ = value; } // ------------------------------------------------------------------- diff --git a/gen/cpp/xtcp_treatment/v1/treatment.pb.cc b/gen/cpp/xtcp_treatment/v1/treatment.pb.cc index d9958320c..a0f22ac86 100644 --- a/gen/cpp/xtcp_treatment/v1/treatment.pb.cc +++ b/gen/cpp/xtcp_treatment/v1/treatment.pb.cc @@ -1284,55 +1284,55 @@ constexpr Phase::ParseTableT_ Phase::InternalGenerateParseTable_(const ::_pbi::C }, {{ // string reason = 8 [json_name = "reason", (.buf.validate.field) = { {::_pbi::TcParser::FastUS1, - {66, 2, 0, + {66, 1, 0, PROTOBUF_FIELD_OFFSET(Phase, _impl_.reason_)}}, // uint32 target_hosts = 1 [json_name = "targetHosts", (.buf.validate.field) = { - {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(Phase, _impl_.target_hosts_), 7>(), - {8, 7, 0, + {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(Phase, _impl_.target_hosts_), 6>(), + {8, 6, 0, PROTOBUF_FIELD_OFFSET(Phase, _impl_.target_hosts_)}}, // .google.protobuf.Timestamp issued_at = 2 [json_name = "issuedAt"]; {::_pbi::TcParser::FastMtS1, - {18, 3, 0, + {18, 2, 0, PROTOBUF_FIELD_OFFSET(Phase, _impl_.issued_at_)}}, // .google.protobuf.Timestamp observation_started_at = 3 [json_name = "observationStartedAt"]; {::_pbi::TcParser::FastMtS1, - {26, 4, 1, + {26, 3, 1, PROTOBUF_FIELD_OFFSET(Phase, _impl_.observation_started_at_)}}, // .google.protobuf.Timestamp evaluated_at = 4 [json_name = "evaluatedAt"]; {::_pbi::TcParser::FastMtS1, - {34, 5, 2, + {34, 4, 2, PROTOBUF_FIELD_OFFSET(Phase, _impl_.evaluated_at_)}}, // .xtcp_treatment.v1.Evidence evidence = 5 [json_name = "evidence"]; {::_pbi::TcParser::FastMtS1, - {42, 6, 3, + {42, 5, 3, PROTOBUF_FIELD_OFFSET(Phase, _impl_.evidence_)}}, - // string outcome = 6 [json_name = "outcome", (.buf.validate.field) = { - {::_pbi::TcParser::FastUS1, - {50, 0, 0, + // .xtcp_treatment.v1.Outcome outcome = 6 [json_name = "outcome", (.buf.validate.field) = { + {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(Phase, _impl_.outcome_), 7>(), + {48, 7, 0, PROTOBUF_FIELD_OFFSET(Phase, _impl_.outcome_)}}, // string reviewer = 7 [json_name = "reviewer", (.buf.validate.field) = { {::_pbi::TcParser::FastUS1, - {58, 1, 0, + {58, 0, 0, PROTOBUF_FIELD_OFFSET(Phase, _impl_.reviewer_)}}, }}, {{ 65535, 65535 }}, {{ // uint32 target_hosts = 1 [json_name = "targetHosts", (.buf.validate.field) = { - {PROTOBUF_FIELD_OFFSET(Phase, _impl_.target_hosts_), _Internal::kHasBitsOffset + 7, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt32)}, + {PROTOBUF_FIELD_OFFSET(Phase, _impl_.target_hosts_), _Internal::kHasBitsOffset + 6, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt32)}, // .google.protobuf.Timestamp issued_at = 2 [json_name = "issuedAt"]; - {PROTOBUF_FIELD_OFFSET(Phase, _impl_.issued_at_), _Internal::kHasBitsOffset + 3, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + {PROTOBUF_FIELD_OFFSET(Phase, _impl_.issued_at_), _Internal::kHasBitsOffset + 2, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, // .google.protobuf.Timestamp observation_started_at = 3 [json_name = "observationStartedAt"]; - {PROTOBUF_FIELD_OFFSET(Phase, _impl_.observation_started_at_), _Internal::kHasBitsOffset + 4, 1, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + {PROTOBUF_FIELD_OFFSET(Phase, _impl_.observation_started_at_), _Internal::kHasBitsOffset + 3, 1, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, // .google.protobuf.Timestamp evaluated_at = 4 [json_name = "evaluatedAt"]; - {PROTOBUF_FIELD_OFFSET(Phase, _impl_.evaluated_at_), _Internal::kHasBitsOffset + 5, 2, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + {PROTOBUF_FIELD_OFFSET(Phase, _impl_.evaluated_at_), _Internal::kHasBitsOffset + 4, 2, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, // .xtcp_treatment.v1.Evidence evidence = 5 [json_name = "evidence"]; - {PROTOBUF_FIELD_OFFSET(Phase, _impl_.evidence_), _Internal::kHasBitsOffset + 6, 3, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, - // string outcome = 6 [json_name = "outcome", (.buf.validate.field) = { - {PROTOBUF_FIELD_OFFSET(Phase, _impl_.outcome_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + {PROTOBUF_FIELD_OFFSET(Phase, _impl_.evidence_), _Internal::kHasBitsOffset + 5, 3, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // .xtcp_treatment.v1.Outcome outcome = 6 [json_name = "outcome", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Phase, _impl_.outcome_), _Internal::kHasBitsOffset + 7, 0, (0 | ::_fl::kFcOptional | ::_fl::kOpenEnum)}, // string reviewer = 7 [json_name = "reviewer", (.buf.validate.field) = { - {PROTOBUF_FIELD_OFFSET(Phase, _impl_.reviewer_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + {PROTOBUF_FIELD_OFFSET(Phase, _impl_.reviewer_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, // string reason = 8 [json_name = "reason", (.buf.validate.field) = { - {PROTOBUF_FIELD_OFFSET(Phase, _impl_.reason_), _Internal::kHasBitsOffset + 2, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + {PROTOBUF_FIELD_OFFSET(Phase, _impl_.reason_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, }}, {{ #ifndef PROTOBUF_MESSAGE_GLOBALS @@ -1357,9 +1357,8 @@ constexpr Phase::ParseTableT_ Phase::InternalGenerateParseTable_(const ::_pbi::C #endif }}, {{ - "\27\0\0\0\0\0\7\10\6\0\0\0\0\0\0\0" + "\27\0\0\0\0\0\0\10\6\0\0\0\0\0\0\0" "xtcp_treatment.v1.Phase" - "outcome" "reviewer" "reason" }}, @@ -1370,10 +1369,7 @@ constexpr Phase::ParseTableT_ Phase::InternalGenerateParseTable_(const ::_pbi::C inline constexpr Phase::Impl_::Impl_( [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, ::_pbi::ConstantInitialized) noexcept - : outcome_( - &::google::protobuf::internal::fixed_address_empty_string, - ::_pbi::ConstantInitialized()), - reviewer_( + : reviewer_( &::google::protobuf::internal::fixed_address_empty_string, ::_pbi::ConstantInitialized()), reason_( @@ -1383,7 +1379,8 @@ inline constexpr Phase::Impl_::Impl_( observation_started_at_{nullptr}, evaluated_at_{nullptr}, evidence_{nullptr}, - target_hosts_{0u} {} + target_hosts_{0u}, + outcome_{static_cast< ::xtcp_treatment::v1::Outcome >(0)} {} template constexpr Phase::Phase(::_pbi::ConstantInitialized, @@ -1491,9 +1488,9 @@ constexpr Assignment::ParseTableT_ Assignment::InternalGenerateParseTable_(const 0, // no _extensions_ 7, 56, // max_field_number, fast_idx_mask offsetof(ParseTableT_, field_lookup_table), - 4294967168, // skipmap + 4294967169, // skipmap offsetof(ParseTableT_, field_entries), - 7, // num_field_entries + 6, // num_field_entries 3, // num_aux_entries offsetof(ParseTableT_, aux_entries), class_data, @@ -1504,51 +1501,46 @@ constexpr Assignment::ParseTableT_ Assignment::InternalGenerateParseTable_(const #endif // PROTOBUF_PREFETCH_PARSE_TABLE }, {{ {::_pbi::TcParser::MiniParse, {}}, - // string id = 1 [json_name = "id", (.buf.validate.field) = { - {::_pbi::TcParser::FastUS1, - {10, 0, 0, - PROTOBUF_FIELD_OFFSET(Assignment, _impl_.id_)}}, + {::_pbi::TcParser::MiniParse, {}}, // string experiment_id = 2 [json_name = "experimentId", (.buf.validate.field) = { {::_pbi::TcParser::FastUS1, - {18, 1, 0, + {18, 0, 0, PROTOBUF_FIELD_OFFSET(Assignment, _impl_.experiment_id_)}}, // .xtcp_treatment.v1.Treatment treatment = 3 [json_name = "treatment", (.buf.validate.field) = { {::_pbi::TcParser::FastMtS1, - {26, 2, 0, + {26, 1, 0, PROTOBUF_FIELD_OFFSET(Assignment, _impl_.treatment_)}}, // .xtcp_treatment.v1.AssignmentMode mode = 4 [json_name = "mode", (.buf.validate.field) = { - {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(Assignment, _impl_.mode_), 6>(), - {32, 6, 0, + {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(Assignment, _impl_.mode_), 5>(), + {32, 5, 0, PROTOBUF_FIELD_OFFSET(Assignment, _impl_.mode_)}}, // .google.protobuf.Timestamp lease_expires_at = 5 [json_name = "leaseExpiresAt"]; {::_pbi::TcParser::FastMtS1, - {42, 3, 1, + {42, 2, 1, PROTOBUF_FIELD_OFFSET(Assignment, _impl_.lease_expires_at_)}}, // .xtcp_treatment.v1.Treatment predecessor = 6 [json_name = "predecessor"]; {::_pbi::TcParser::FastMtS1, - {50, 4, 2, + {50, 3, 2, PROTOBUF_FIELD_OFFSET(Assignment, _impl_.predecessor_)}}, // uint64 generation = 7 [json_name = "generation", (.buf.validate.field) = { - {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(Assignment, _impl_.generation_), 5>(), - {56, 5, 0, + {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(Assignment, _impl_.generation_), 4>(), + {56, 4, 0, PROTOBUF_FIELD_OFFSET(Assignment, _impl_.generation_)}}, }}, {{ 65535, 65535 }}, {{ - // string id = 1 [json_name = "id", (.buf.validate.field) = { - {PROTOBUF_FIELD_OFFSET(Assignment, _impl_.id_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, // string experiment_id = 2 [json_name = "experimentId", (.buf.validate.field) = { - {PROTOBUF_FIELD_OFFSET(Assignment, _impl_.experiment_id_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + {PROTOBUF_FIELD_OFFSET(Assignment, _impl_.experiment_id_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, // .xtcp_treatment.v1.Treatment treatment = 3 [json_name = "treatment", (.buf.validate.field) = { - {PROTOBUF_FIELD_OFFSET(Assignment, _impl_.treatment_), _Internal::kHasBitsOffset + 2, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + {PROTOBUF_FIELD_OFFSET(Assignment, _impl_.treatment_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, // .xtcp_treatment.v1.AssignmentMode mode = 4 [json_name = "mode", (.buf.validate.field) = { - {PROTOBUF_FIELD_OFFSET(Assignment, _impl_.mode_), _Internal::kHasBitsOffset + 6, 0, (0 | ::_fl::kFcOptional | ::_fl::kOpenEnum)}, + {PROTOBUF_FIELD_OFFSET(Assignment, _impl_.mode_), _Internal::kHasBitsOffset + 5, 0, (0 | ::_fl::kFcOptional | ::_fl::kOpenEnum)}, // .google.protobuf.Timestamp lease_expires_at = 5 [json_name = "leaseExpiresAt"]; - {PROTOBUF_FIELD_OFFSET(Assignment, _impl_.lease_expires_at_), _Internal::kHasBitsOffset + 3, 1, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + {PROTOBUF_FIELD_OFFSET(Assignment, _impl_.lease_expires_at_), _Internal::kHasBitsOffset + 2, 1, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, // .xtcp_treatment.v1.Treatment predecessor = 6 [json_name = "predecessor"]; - {PROTOBUF_FIELD_OFFSET(Assignment, _impl_.predecessor_), _Internal::kHasBitsOffset + 4, 2, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + {PROTOBUF_FIELD_OFFSET(Assignment, _impl_.predecessor_), _Internal::kHasBitsOffset + 3, 2, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, // uint64 generation = 7 [json_name = "generation", (.buf.validate.field) = { - {PROTOBUF_FIELD_OFFSET(Assignment, _impl_.generation_), _Internal::kHasBitsOffset + 5, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt64)}, + {PROTOBUF_FIELD_OFFSET(Assignment, _impl_.generation_), _Internal::kHasBitsOffset + 4, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt64)}, }}, {{ #ifndef PROTOBUF_MESSAGE_GLOBALS @@ -1568,9 +1560,8 @@ constexpr Assignment::ParseTableT_ Assignment::InternalGenerateParseTable_(const #endif }}, {{ - "\34\2\15\0\0\0\0\0" + "\34\15\0\0\0\0\0\0" "xtcp_treatment.v1.Assignment" - "id" "experiment_id" }}, }; @@ -1580,10 +1571,7 @@ constexpr Assignment::ParseTableT_ Assignment::InternalGenerateParseTable_(const inline constexpr Assignment::Impl_::Impl_( [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, ::_pbi::ConstantInitialized) noexcept - : id_( - &::google::protobuf::internal::fixed_address_empty_string, - ::_pbi::ConstantInitialized()), - experiment_id_( + : experiment_id_( &::google::protobuf::internal::fixed_address_empty_string, ::_pbi::ConstantInitialized()), treatment_{nullptr}, @@ -1725,15 +1713,15 @@ constexpr Experiment::ParseTableT_ Experiment::InternalGenerateParseTable_(const PROTOBUF_FIELD_OFFSET(Experiment, _impl_.hypothesis_)}}, // .xtcp_treatment.v1.Treatment treatment = 4 [json_name = "treatment", (.buf.validate.field) = { {::_pbi::TcParser::FastMtS1, - {34, 6, 0, + {34, 5, 0, PROTOBUF_FIELD_OFFSET(Experiment, _impl_.treatment_)}}, // .xtcp_treatment.v1.ExperimentPolicy policy = 5 [json_name = "policy", (.buf.validate.field) = { {::_pbi::TcParser::FastMtS1, - {42, 7, 1, + {42, 6, 1, PROTOBUF_FIELD_OFFSET(Experiment, _impl_.policy_)}}, // .xtcp_treatment.v1.Evidence baseline = 6 [json_name = "baseline", (.buf.validate.field) = { {::_pbi::TcParser::FastMtS1, - {50, 8, 2, + {50, 7, 2, PROTOBUF_FIELD_OFFSET(Experiment, _impl_.baseline_)}}, // repeated string cohort_order = 7 [json_name = "cohortOrder", (.buf.validate.field) = { {::_pbi::TcParser::FastUR1, @@ -1744,24 +1732,24 @@ constexpr Experiment::ParseTableT_ Experiment::InternalGenerateParseTable_(const {66, 1, 3, PROTOBUF_FIELD_OFFSET(Experiment, _impl_.phases_)}}, // .xtcp_treatment.v1.ExperimentStatus status = 9 [json_name = "status", (.buf.validate.field) = { - {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(Experiment, _impl_.status_), 12>(), - {72, 12, 0, + {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(Experiment, _impl_.status_), 11>(), + {72, 11, 0, PROTOBUF_FIELD_OFFSET(Experiment, _impl_.status_)}}, // uint64 revision = 10 [json_name = "revision"]; - {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(Experiment, _impl_.revision_), 11>(), - {80, 11, 0, + {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(Experiment, _impl_.revision_), 10>(), + {80, 10, 0, PROTOBUF_FIELD_OFFSET(Experiment, _impl_.revision_)}}, // .google.protobuf.Timestamp proposed_at = 11 [json_name = "proposedAt"]; {::_pbi::TcParser::FastMtS1, - {90, 9, 4, + {90, 8, 4, PROTOBUF_FIELD_OFFSET(Experiment, _impl_.proposed_at_)}}, // .xtcp_treatment.v1.Evidence latest_evidence = 12 [json_name = "latestEvidence"]; {::_pbi::TcParser::FastMtS1, - {98, 10, 5, + {98, 9, 5, PROTOBUF_FIELD_OFFSET(Experiment, _impl_.latest_evidence_)}}, - // string latest_outcome = 13 [json_name = "latestOutcome", (.buf.validate.field) = { - {::_pbi::TcParser::FastUS1, - {106, 5, 0, + // .xtcp_treatment.v1.Outcome latest_outcome = 13 [json_name = "latestOutcome", (.buf.validate.field) = { + {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(Experiment, _impl_.latest_outcome_), 12>(), + {104, 12, 0, PROTOBUF_FIELD_OFFSET(Experiment, _impl_.latest_outcome_)}}, {::_pbi::TcParser::MiniParse, {}}, {::_pbi::TcParser::MiniParse, {}}, @@ -1775,25 +1763,25 @@ constexpr Experiment::ParseTableT_ Experiment::InternalGenerateParseTable_(const // string hypothesis = 3 [json_name = "hypothesis", (.buf.validate.field) = { {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.hypothesis_), _Internal::kHasBitsOffset + 4, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, // .xtcp_treatment.v1.Treatment treatment = 4 [json_name = "treatment", (.buf.validate.field) = { - {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.treatment_), _Internal::kHasBitsOffset + 6, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.treatment_), _Internal::kHasBitsOffset + 5, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, // .xtcp_treatment.v1.ExperimentPolicy policy = 5 [json_name = "policy", (.buf.validate.field) = { - {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.policy_), _Internal::kHasBitsOffset + 7, 1, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.policy_), _Internal::kHasBitsOffset + 6, 1, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, // .xtcp_treatment.v1.Evidence baseline = 6 [json_name = "baseline", (.buf.validate.field) = { - {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.baseline_), _Internal::kHasBitsOffset + 8, 2, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.baseline_), _Internal::kHasBitsOffset + 7, 2, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, // repeated string cohort_order = 7 [json_name = "cohortOrder", (.buf.validate.field) = { {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.cohort_order_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcRepeated | ::_fl::kUtf8String | ::_fl::kRepSString)}, // repeated .xtcp_treatment.v1.Phase phases = 8 [json_name = "phases", (.buf.validate.field) = { {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.phases_), _Internal::kHasBitsOffset + 1, 3, (0 | ::_fl::kFcRepeated | ::_fl::kMessage | ::_fl::kTvTable)}, // .xtcp_treatment.v1.ExperimentStatus status = 9 [json_name = "status", (.buf.validate.field) = { - {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.status_), _Internal::kHasBitsOffset + 12, 0, (0 | ::_fl::kFcOptional | ::_fl::kOpenEnum)}, + {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.status_), _Internal::kHasBitsOffset + 11, 0, (0 | ::_fl::kFcOptional | ::_fl::kOpenEnum)}, // uint64 revision = 10 [json_name = "revision"]; - {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.revision_), _Internal::kHasBitsOffset + 11, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt64)}, + {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.revision_), _Internal::kHasBitsOffset + 10, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt64)}, // .google.protobuf.Timestamp proposed_at = 11 [json_name = "proposedAt"]; - {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.proposed_at_), _Internal::kHasBitsOffset + 9, 4, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.proposed_at_), _Internal::kHasBitsOffset + 8, 4, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, // .xtcp_treatment.v1.Evidence latest_evidence = 12 [json_name = "latestEvidence"]; - {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.latest_evidence_), _Internal::kHasBitsOffset + 10, 5, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, - // string latest_outcome = 13 [json_name = "latestOutcome", (.buf.validate.field) = { - {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.latest_outcome_), _Internal::kHasBitsOffset + 5, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.latest_evidence_), _Internal::kHasBitsOffset + 9, 5, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // .xtcp_treatment.v1.Outcome latest_outcome = 13 [json_name = "latestOutcome", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.latest_outcome_), _Internal::kHasBitsOffset + 12, 0, (0 | ::_fl::kFcOptional | ::_fl::kOpenEnum)}, }}, {{ #ifndef PROTOBUF_MESSAGE_GLOBALS @@ -1828,13 +1816,12 @@ constexpr Experiment::ParseTableT_ Experiment::InternalGenerateParseTable_(const #endif }}, {{ - "\34\2\10\12\0\0\0\14\0\0\0\0\0\16\0\0" + "\34\2\10\12\0\0\0\14\0\0\0\0\0\0\0\0" "xtcp_treatment.v1.Experiment" "id" "location" "hypothesis" "cohort_order" - "latest_outcome" }}, }; } @@ -1862,16 +1849,14 @@ inline constexpr Experiment::Impl_::Impl_( hypothesis_( &::google::protobuf::internal::fixed_address_empty_string, ::_pbi::ConstantInitialized()), - latest_outcome_( - &::google::protobuf::internal::fixed_address_empty_string, - ::_pbi::ConstantInitialized()), treatment_{nullptr}, policy_{nullptr}, baseline_{nullptr}, proposed_at_{nullptr}, latest_evidence_{nullptr}, revision_{::uint64_t{0u}}, - status_{static_cast< ::xtcp_treatment::v1::ExperimentStatus >(0)} {} + status_{static_cast< ::xtcp_treatment::v1::ExperimentStatus >(0)}, + latest_outcome_{static_cast< ::xtcp_treatment::v1::Outcome >(0)} {} template constexpr Experiment::Experiment(::_pbi::ConstantInitialized, @@ -2617,7 +2602,7 @@ const ::_pbi::ClassData* LocalState_get_class_data() { } // namespace v1 } // namespace xtcp_treatment static const ::_pb::EnumDescriptor* PROTOBUF_NONNULL - file_level_enum_descriptors_xtcp_5ftreatment_2fv1_2ftreatment_2eproto[4]; + file_level_enum_descriptors_xtcp_5ftreatment_2fv1_2ftreatment_2eproto[5]; static constexpr const ::_pb::ServiceDescriptor* PROTOBUF_NONNULL* PROTOBUF_NULLABLE file_level_service_descriptors_xtcp_5ftreatment_2fv1_2ftreatment_2eproto = nullptr; const ::uint32_t @@ -2655,8 +2640,7 @@ const ::uint32_t 4, 0x081, // bitmap PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Assignment, _impl_._has_bits_), - 10, // hasbit index offset - PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Assignment, _impl_.id_), + 9, // hasbit index offset PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Assignment, _impl_.experiment_id_), PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Assignment, _impl_.treatment_), PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Assignment, _impl_.mode_), @@ -2665,11 +2649,10 @@ const ::uint32_t PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Assignment, _impl_.generation_), 0, 1, + 5, 2, - 6, 3, 4, - 5, 0x081, // bitmap PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::DesiredState, _impl_._has_bits_), 8, // hasbit index offset @@ -2806,14 +2789,14 @@ const ::uint32_t PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Phase, _impl_.outcome_), PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Phase, _impl_.reviewer_), PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Phase, _impl_.reason_), - 7, + 6, + 2, 3, 4, 5, - 6, + 7, 0, 1, - 2, 0x081, // bitmap PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Experiment, _impl_._has_bits_), 16, // hasbit index offset @@ -2833,16 +2816,16 @@ const ::uint32_t 2, 3, 4, + 5, 6, 7, - 8, 0, 1, - 12, 11, - 9, 10, - 5, + 8, + 9, + 12, }; static const ::_pbi::MigrationSchema @@ -2850,15 +2833,15 @@ static const ::_pbi::MigrationSchema {0, sizeof(::xtcp_treatment::v1::TCPSettings)}, {17, sizeof(::xtcp_treatment::v1::Treatment)}, {30, sizeof(::xtcp_treatment::v1::Assignment)}, - {47, sizeof(::xtcp_treatment::v1::DesiredState)}, - {60, sizeof(::xtcp_treatment::v1::Capabilities)}, - {73, sizeof(::xtcp_treatment::v1::ApplicationRecord)}, - {100, sizeof(::xtcp_treatment::v1::LocalState)}, - {113, sizeof(::xtcp_treatment::v1::HostEvidence)}, - {132, sizeof(::xtcp_treatment::v1::Evidence)}, - {151, sizeof(::xtcp_treatment::v1::ExperimentPolicy)}, - {172, sizeof(::xtcp_treatment::v1::Phase)}, - {191, sizeof(::xtcp_treatment::v1::Experiment)}, + {45, sizeof(::xtcp_treatment::v1::DesiredState)}, + {58, sizeof(::xtcp_treatment::v1::Capabilities)}, + {71, sizeof(::xtcp_treatment::v1::ApplicationRecord)}, + {98, sizeof(::xtcp_treatment::v1::LocalState)}, + {111, sizeof(::xtcp_treatment::v1::HostEvidence)}, + {130, sizeof(::xtcp_treatment::v1::Evidence)}, + {149, sizeof(::xtcp_treatment::v1::ExperimentPolicy)}, + {170, sizeof(::xtcp_treatment::v1::Phase)}, + {189, sizeof(::xtcp_treatment::v1::Experiment)}, }; static const ::_pbi::MessageGlobalsBase* PROTOBUF_NONNULL const file_message_globals[] = { @@ -2916,159 +2899,173 @@ const char descriptor_table_protodef_xtcp_5ftreatment_2fv1_2ftreatment_2eproto[] "ified ECN fabric and explicit ECN\032^this." "settings.congestion_control != \'dctcp\' |" "| (this.ecn_fabric_verified && this.sett" - "ings.ecn)\"\375\005\n\nAssignment\0229\n\002id\030\001 \001(\tB)\272H" - "&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R" - "\002id\022N\n\rexperiment_id\030\002 \001(\tB)\272H&r$2\"^[a-z" - "A-Z0-9][a-zA-Z0-9_.-]{0,127}$R\014experimen" - "tId\022B\n\ttreatment\030\003 \001(\0132\034.xtcp_treatment." - "v1.TreatmentB\006\272H\003\310\001\001R\ttreatment\022A\n\004mode\030" - "\004 \001(\0162!.xtcp_treatment.v1.AssignmentMode" - "B\n\272H\007\202\001\004\020\001 \000R\004mode\022D\n\020lease_expires_at\030\005" - " \001(\0132\032.google.protobuf.TimestampR\016leaseE" - "xpiresAt\022>\n\013predecessor\030\006 \001(\0132\034.xtcp_tre" - "atment.v1.TreatmentR\013predecessor\022\'\n\ngene" - "ration\030\007 \001(\004B\007\272H\0042\002 \000R\ngeneration:\255\002\272H\251\002" - "\032}\n\037assignment.trial_requires_lease\022,a t" - "rial assignment requires lease_expires_a" - "t\032,this.mode != 1 || has(this.lease_expi" - "res_at)\032\247\001\n\"assignment.predecessor_desti" - "nation\022+predecessor must cover the same " - "destination\032T!has(this.predecessor) || t" - "his.predecessor.destination == this.trea" - "tment.destination\"\244\002\n\014DesiredState\022D\n\010ag" - "ent_id\030\001 \001(\tB)\272H&r$2\"^[a-zA-Z0-9][a-zA-Z" - "0-9_.-]{0,127}$R\007agentId\022#\n\010revision\030\002 \001" - "(\004B\007\272H\0042\002 \000R\010revision\022\034\n\tsuspended\030\003 \001(\010" - "R\tsuspended\022J\n\013assignments\030\004 \003(\0132\035.xtcp_" - "treatment.v1.AssignmentB\t\272H\006\222\001\003\020\200 R\013assi" - "gnments\022\?\n\tissued_at\030\005 \001(\0132\032.google.prot" - "obuf.TimestampB\006\272H\003\310\001\001R\010issuedAt\"\243\002\n\014Cap" - "abilities\022B\n\007boot_id\030\001 \001(\tB)\272H&r$2\"^[a-z" - "A-Z0-9][a-zA-Z0-9_.-]{0,127}$R\006bootId\022/\n" - "\016kernel_release\030\002 \001(\tB\010\272H\005r\003\030\200\002R\rkernelR" - "elease\022C\n\025congestion_algorithms\030\003 \003(\tB\016\272" - "H\013\222\001\010\020@\"\004r\002\030\017R\024congestionAlgorithms\022.\n\023s" - "imple_host_routing\030\004 \001(\010R\021simpleHostRout" - "ing\022)\n\020actuator_enabled\030\005 \001(\010R\017actuatorE" - "nabled\"\250\005\n\021ApplicationRecord\022E\n\nassignme" - "nt\030\001 \001(\0132\035.xtcp_treatment.v1.AssignmentB" - "\006\272H\003\310\001\001R\nassignment\022)\n\020desired_revision\030" - "\002 \001(\004R\017desiredRevision\022@\n\006status\030\003 \001(\0162\036" - ".xtcp_treatment.v1.ApplyStatusB\010\272H\005\202\001\002\020\001" - "R\006status\022;\n\013received_at\030\004 \001(\0132\032.google.p" - "rotobuf.TimestampR\nreceivedAt\022=\n\014attempt" - "ed_at\030\005 \001(\0132\032.google.protobuf.TimestampR" - "\013attemptedAt\0229\n\napplied_at\030\006 \001(\0132\032.googl" - "e.protobuf.TimestampR\tappliedAt\022;\n\013verif" - "ied_at\030\007 \001(\0132\032.google.protobuf.Timestamp" - "R\nverifiedAt\0229\n\nremoved_at\030\010 \001(\0132\032.googl" - "e.protobuf.TimestampR\tremovedAt\022\036\n\005error" - "\030\t \001(\tB\010\272H\005r\003\030\200 R\005error\0221\n\017installed_rou" - "te\030\n \001(\014B\010\272H\005z\003\030\200 R\016installedRoute\0224\n\021in" - "stalled_boot_id\030\013 \001(\tB\010\272H\005r\003\030\200\001R\017install" - "edBootId\022\'\n\017expired_latched\030\014 \001(\010R\016expir" - "edLatched\"\261\002\n\nLocalState\0229\n\007desired\030\001 \001(" - "\0132\037.xtcp_treatment.v1.DesiredStateR\007desi" - "red\022+\n\021locally_suspended\030\002 \001(\010R\020locallyS" - "uspended\0225\n\021suspension_reason\030\003 \001(\tB\010\272H\005" - "r\003\030\200\010R\020suspensionReason\022I\n\007records\030\004 \003(\013" - "2$.xtcp_treatment.v1.ApplicationRecordB\t" - "\272H\006\222\001\003\020\200@R\007records\0229\n\nupdated_at\030\005 \001(\0132\032" - ".google.protobuf.TimestampR\tupdatedAt\"\211\004" - "\n\014HostEvidence\022D\n\010agent_id\030\001 \001(\tB)\272H&r$2" - "\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\007age" - "ntId\022\030\n\007sockets\030\002 \001(\004R\007sockets\022\037\n\013bytes_" - "acked\030\003 \001(\004R\nbytesAcked\0221\n\013goodput_bps\030\004" - " \001(\001B\020\272H\r\022\013)\000\000\000\000\000\000\000\000@\001R\ngoodputBps\022C\n\024re" - "transmission_ratio\030\005 \001(\001B\020\272H\r\022\013)\000\000\000\000\000\000\000\000" - "@\001R\023retransmissionRatio\022.\n\nrtt_p95_ms\030\006 " - "\001(\001B\020\272H\r\022\013)\000\000\000\000\000\000\000\000@\001R\010rttP95Ms\022\'\n\017expos" - "ed_sockets\030\007 \001(\004R\016exposedSockets\022-\n\022coll" - "ection_healthy\030\010 \001(\010R\021collectionHealthy:" - "x\272Hu\032s\n$host_evidence.exposed_within_soc" - "kets\022%exposed_sockets cannot exceed sock" - "ets\032$this.exposed_sockets <= this.socket" - "s\"\274\005\n\010Evidence\0229\n\002id\030\001 \001(\tB)\272H&r$2\"^[a-z" - "A-Z0-9][a-zA-Z0-9_.-]{0,127}$R\002id\022C\n\013ana" - "lyzed_at\030\002 \001(\0132\032.google.protobuf.Timesta" - "mpB\006\272H\003\310\001\001R\nanalyzedAt\022E\n\014window_start\030\003" - " \001(\0132\032.google.protobuf.TimestampB\006\272H\003\310\001\001" - "R\013windowStart\022A\n\nwindow_end\030\004 \001(\0132\032.goog" - "le.protobuf.TimestampB\006\272H\003\310\001\001R\twindowEnd" - "\022/\n\rquery_version\030\005 \001(\tB\n\272H\007r\005\020\001\030\200\002R\014que" - "ryVersion\0227\n\022enrichment_version\030\006 \001(\tB\010\272" - "H\005r\003\030\200\002R\021enrichmentVersion\022B\n\005hosts\030\007 \003(" - "\0132\037.xtcp_treatment.v1.HostEvidenceB\013\272H\010\222" - "\001\005\010\001\020\220NR\005hosts\022>\n\017artifact_sha256\030\010 \001(\tB" - "\025\272H\022r\0202\016^[0-9a-f]{64}$R\016artifactSha256:\267" - "\001\272H\263\001\032\260\001\n\021evidence.interval\022Owindow_end " - "must follow window_start and analyzed_at" - " must not precede window_end\032Jthis.windo" - "w_end > this.window_start && this.analyz" - "ed_at >= this.window_end\"\354\006\n\020ExperimentP" - "olicy\022]\n\023minimum_observation\030\001 \001(\0132\031.goo" + "ings.ecn)\"\314\005\n\nAssignment\022N\n\rexperiment_i" + "d\030\002 \001(\tB)\272H&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_." + "-]{0,127}$R\014experimentId\022B\n\ttreatment\030\003 " + "\001(\0132\034.xtcp_treatment.v1.TreatmentB\006\272H\003\310\001" + "\001R\ttreatment\022A\n\004mode\030\004 \001(\0162!.xtcp_treatm" + "ent.v1.AssignmentModeB\n\272H\007\202\001\004\020\001 \000R\004mode\022" + "D\n\020lease_expires_at\030\005 \001(\0132\032.google.proto" + "buf.TimestampR\016leaseExpiresAt\022>\n\013predece" + "ssor\030\006 \001(\0132\034.xtcp_treatment.v1.Treatment" + "R\013predecessor\022\'\n\ngeneration\030\007 \001(\004B\007\272H\0042\002" + " \000R\ngeneration:\255\002\272H\251\002\032}\n\037assignment.tria" + "l_requires_lease\022,a trial assignment req" + "uires lease_expires_at\032,this.mode != 1 |" + "| has(this.lease_expires_at)\032\247\001\n\"assignm" + "ent.predecessor_destination\022+predecessor" + " must cover the same destination\032T!has(t" + "his.predecessor) || this.predecessor.des" + "tination == this.treatment.destinationJ\004" + "\010\001\020\002R\002id\"\244\002\n\014DesiredState\022D\n\010agent_id\030\001 " + "\001(\tB)\272H&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0" + ",127}$R\007agentId\022#\n\010revision\030\002 \001(\004B\007\272H\0042\002" + " \000R\010revision\022\034\n\tsuspended\030\003 \001(\010R\tsuspend" + "ed\022J\n\013assignments\030\004 \003(\0132\035.xtcp_treatment" + ".v1.AssignmentB\t\272H\006\222\001\003\020\200 R\013assignments\022\?" + "\n\tissued_at\030\005 \001(\0132\032.google.protobuf.Time" + "stampB\006\272H\003\310\001\001R\010issuedAt\"\243\002\n\014Capabilities" + "\022B\n\007boot_id\030\001 \001(\tB)\272H&r$2\"^[a-zA-Z0-9][a" + "-zA-Z0-9_.-]{0,127}$R\006bootId\022/\n\016kernel_r" + "elease\030\002 \001(\tB\010\272H\005r\003\030\200\002R\rkernelRelease\022C\n" + "\025congestion_algorithms\030\003 \003(\tB\016\272H\013\222\001\010\020@\"\004" + "r\002\030\017R\024congestionAlgorithms\022.\n\023simple_hos" + "t_routing\030\004 \001(\010R\021simpleHostRouting\022)\n\020ac" + "tuator_enabled\030\005 \001(\010R\017actuatorEnabled\"\250\005" + "\n\021ApplicationRecord\022E\n\nassignment\030\001 \001(\0132" + "\035.xtcp_treatment.v1.AssignmentB\006\272H\003\310\001\001R\n" + "assignment\022)\n\020desired_revision\030\002 \001(\004R\017de" + "siredRevision\022@\n\006status\030\003 \001(\0162\036.xtcp_tre" + "atment.v1.ApplyStatusB\010\272H\005\202\001\002\020\001R\006status\022" + ";\n\013received_at\030\004 \001(\0132\032.google.protobuf.T" + "imestampR\nreceivedAt\022=\n\014attempted_at\030\005 \001" + "(\0132\032.google.protobuf.TimestampR\013attempte" + "dAt\0229\n\napplied_at\030\006 \001(\0132\032.google.protobu" + "f.TimestampR\tappliedAt\022;\n\013verified_at\030\007 " + "\001(\0132\032.google.protobuf.TimestampR\nverifie" + "dAt\0229\n\nremoved_at\030\010 \001(\0132\032.google.protobu" + "f.TimestampR\tremovedAt\022\036\n\005error\030\t \001(\tB\010\272" + "H\005r\003\030\200 R\005error\0221\n\017installed_route\030\n \001(\014B" + "\010\272H\005z\003\030\200 R\016installedRoute\0224\n\021installed_b" + "oot_id\030\013 \001(\tB\010\272H\005r\003\030\200\001R\017installedBootId\022" + "\'\n\017expired_latched\030\014 \001(\010R\016expiredLatched" + "\"\261\002\n\nLocalState\0229\n\007desired\030\001 \001(\0132\037.xtcp_" + "treatment.v1.DesiredStateR\007desired\022+\n\021lo" + "cally_suspended\030\002 \001(\010R\020locallySuspended\022" + "5\n\021suspension_reason\030\003 \001(\tB\010\272H\005r\003\030\200\010R\020su" + "spensionReason\022I\n\007records\030\004 \003(\0132$.xtcp_t" + "reatment.v1.ApplicationRecordB\t\272H\006\222\001\003\020\200@" + "R\007records\0229\n\nupdated_at\030\005 \001(\0132\032.google.p" + "rotobuf.TimestampR\tupdatedAt\"\211\004\n\014HostEvi" + "dence\022D\n\010agent_id\030\001 \001(\tB)\272H&r$2\"^[a-zA-Z" + "0-9][a-zA-Z0-9_.-]{0,127}$R\007agentId\022\030\n\007s" + "ockets\030\002 \001(\004R\007sockets\022\037\n\013bytes_acked\030\003 \001" + "(\004R\nbytesAcked\0221\n\013goodput_bps\030\004 \001(\001B\020\272H\r" + "\022\013)\000\000\000\000\000\000\000\000@\001R\ngoodputBps\022C\n\024retransmiss" + "ion_ratio\030\005 \001(\001B\020\272H\r\022\013)\000\000\000\000\000\000\000\000@\001R\023retra" + "nsmissionRatio\022.\n\nrtt_p95_ms\030\006 \001(\001B\020\272H\r\022" + "\013)\000\000\000\000\000\000\000\000@\001R\010rttP95Ms\022\'\n\017exposed_socket" + "s\030\007 \001(\004R\016exposedSockets\022-\n\022collection_he" + "althy\030\010 \001(\010R\021collectionHealthy:x\272Hu\032s\n$h" + "ost_evidence.exposed_within_sockets\022%exp" + "osed_sockets cannot exceed sockets\032$this" + ".exposed_sockets <= this.sockets\"\274\005\n\010Evi" + "dence\0229\n\002id\030\001 \001(\tB)\272H&r$2\"^[a-zA-Z0-9][a" + "-zA-Z0-9_.-]{0,127}$R\002id\022C\n\013analyzed_at\030" + "\002 \001(\0132\032.google.protobuf.TimestampB\006\272H\003\310\001" + "\001R\nanalyzedAt\022E\n\014window_start\030\003 \001(\0132\032.go" + "ogle.protobuf.TimestampB\006\272H\003\310\001\001R\013windowS" + "tart\022A\n\nwindow_end\030\004 \001(\0132\032.google.protob" + "uf.TimestampB\006\272H\003\310\001\001R\twindowEnd\022/\n\rquery" + "_version\030\005 \001(\tB\n\272H\007r\005\020\001\030\200\002R\014queryVersion" + "\0227\n\022enrichment_version\030\006 \001(\tB\010\272H\005r\003\030\200\002R\021" + "enrichmentVersion\022B\n\005hosts\030\007 \003(\0132\037.xtcp_" + "treatment.v1.HostEvidenceB\013\272H\010\222\001\005\010\001\020\220NR\005" + "hosts\022>\n\017artifact_sha256\030\010 \001(\tB\025\272H\022r\0202\016^" + "[0-9a-f]{64}$R\016artifactSha256:\267\001\272H\263\001\032\260\001\n" + "\021evidence.interval\022Owindow_end must foll" + "ow window_start and analyzed_at must not" + " precede window_end\032Jthis.window_end > t" + "his.window_start && this.analyzed_at >= " + "this.window_end\"\354\006\n\020ExperimentPolicy\022]\n\023" + "minimum_observation\030\001 \001(\0132\031.google.proto" + "buf.DurationB\021\272H\016\252\001\010\"\004\010\200\365$*\000\310\001\001R\022minimum" + "Observation\022Q\n\rmaximum_phase\030\002 \001(\0132\031.goo" "gle.protobuf.DurationB\021\272H\016\252\001\010\"\004\010\200\365$*\000\310\001\001" - "R\022minimumObservation\022Q\n\rmaximum_phase\030\002 " - "\001(\0132\031.google.protobuf.DurationB\021\272H\016\252\001\010\"\004" - "\010\200\365$*\000\310\001\001R\014maximumPhase\022@\n\030minimum_socke" - "ts_per_host\030\003 \001(\004B\007\272H\0042\002 \000R\025minimumSocke" - "tsPerHost\022<\n\026minimum_bytes_per_host\030\004 \001(" - "\004B\007\272H\0042\002 \000R\023minimumBytesPerHost\022>\n\025minim" - "um_treated_hosts\030\005 \001(\rB\n\272H\007*\005\030\220N(\002R\023mini" - "mumTreatedHosts\022J\n\023minimum_improvement\030\006" - " \001(\001B\031\272H\026\022\024\031\000\000\000\000\000\000\360\?)\000\000\000\000\000\000\000\000@\001R\022minimum" - "Improvement\022a\n\037maximum_retransmission_in" - "crease\030\007 \001(\001B\031\272H\026\022\024\031\000\000\000\000\000\000\360\?)\000\000\000\000\000\000\000\000@\001R" - "\035maximumRetransmissionIncrease\022K\n\024maximu" - "m_rtt_increase\030\010 \001(\001B\031\272H\026\022\024\031\000\000\000\000\000\000\360\?)\000\000\000" - "\000\000\000\000\000@\001R\022maximumRttIncrease\022^\n\024maximum_e" - "vidence_age\030\t \001(\0132\031.google.protobuf.Dura" - "tionB\021\272H\016\252\001\010\"\004\010\200\365$*\000\310\001\001R\022maximumEvidence" - "Age:\211\001\272H\205\001\032\202\001\n\037policy.observation_within" - "_phase\022/minimum_observation cannot excee" - "d maximum_phase\032.this.minimum_observatio" - "n <= this.maximum_phase\"\244\003\n\005Phase\022-\n\014tar" - "get_hosts\030\001 \001(\rB\n\272H\007*\005\030\220N(\001R\013targetHosts" - "\0227\n\tissued_at\030\002 \001(\0132\032.google.protobuf.Ti" - "mestampR\010issuedAt\022P\n\026observation_started" - "_at\030\003 \001(\0132\032.google.protobuf.TimestampR\024o" - "bservationStartedAt\022=\n\014evaluated_at\030\004 \001(" - "\0132\032.google.protobuf.TimestampR\013evaluated" - "At\0227\n\010evidence\030\005 \001(\0132\033.xtcp_treatment.v1" - ".EvidenceR\010evidence\022!\n\007outcome\030\006 \001(\tB\007\272H" - "\004r\002\030@R\007outcome\022$\n\010reviewer\030\007 \001(\tB\010\272H\005r\003\030" - "\200\001R\010reviewer\022 \n\006reason\030\010 \001(\tB\010\272H\005r\003\030\200 R\006" - "reason\"\260\006\n\nExperiment\0229\n\002id\030\001 \001(\tB)\272H&r$" - "2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\002id" - "\022E\n\010location\030\002 \001(\tB)\272H&r$2\"^[a-zA-Z0-9][" - "a-zA-Z0-9_.-]{0,127}$R\010location\022*\n\nhypot" - "hesis\030\003 \001(\tB\n\272H\007r\005\020\001\030\200@R\nhypothesis\022B\n\tt" - "reatment\030\004 \001(\0132\034.xtcp_treatment.v1.Treat" - "mentB\006\272H\003\310\001\001R\ttreatment\022C\n\006policy\030\005 \001(\0132" - "#.xtcp_treatment.v1.ExperimentPolicyB\006\272H" - "\003\310\001\001R\006policy\022\?\n\010baseline\030\006 \001(\0132\033.xtcp_tr" - "eatment.v1.EvidenceB\006\272H\003\310\001\001R\010baseline\022X\n" - "\014cohort_order\030\007 \003(\tB5\272H2\222\001/\010\003\020\220N\030\001\"&r$2\"" - "^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\013coho" - "rtOrder\022:\n\006phases\030\010 \003(\0132\030.xtcp_treatment" - ".v1.PhaseB\010\272H\005\222\001\002\020@R\006phases\022E\n\006status\030\t " - "\001(\0162#.xtcp_treatment.v1.ExperimentStatus" - "B\010\272H\005\202\001\002\020\001R\006status\022\032\n\010revision\030\n \001(\004R\010re" - "vision\022;\n\013proposed_at\030\013 \001(\0132\032.google.pro" - "tobuf.TimestampR\nproposedAt\022D\n\017latest_ev" - "idence\030\014 \001(\0132\033.xtcp_treatment.v1.Evidenc" - "eR\016latestEvidence\022.\n\016latest_outcome\030\r \001(" - "\tB\007\272H\004r\002\030@R\rlatestOutcome*\?\n\004Role\022\024\n\020ROL" - "E_UNSPECIFIED\020\000\022\016\n\nROLE_AGENT\020\001\022\021\n\rROLE_" - "OPERATOR\020\002*\244\001\n\016AssignmentMode\022\037\n\033ASSIGNM" - "ENT_MODE_UNSPECIFIED\020\000\022\031\n\025ASSIGNMENT_MOD" - "E_TRIAL\020\001\022\032\n\026ASSIGNMENT_MODE_STABLE\020\002\022\035\n" - "\031ASSIGNMENT_MODE_SUSPENDED\020\003\022\033\n\027ASSIGNME" - "NT_MODE_RETIRED\020\004*\255\001\n\013ApplyStatus\022\034\n\030APP" - "LY_STATUS_UNSPECIFIED\020\000\022\030\n\024APPLY_STATUS_" - "PENDING\020\001\022\031\n\025APPLY_STATUS_VERIFIED\020\002\022\030\n\024" - "APPLY_STATUS_REMOVED\020\003\022\030\n\024APPLY_STATUS_E" - "XPIRED\020\004\022\027\n\023APPLY_STATUS_FAILED\020\005*\225\002\n\020Ex" + "R\014maximumPhase\022@\n\030minimum_sockets_per_ho" + "st\030\003 \001(\004B\007\272H\0042\002 \000R\025minimumSocketsPerHost" + "\022<\n\026minimum_bytes_per_host\030\004 \001(\004B\007\272H\0042\002 " + "\000R\023minimumBytesPerHost\022>\n\025minimum_treate" + "d_hosts\030\005 \001(\rB\n\272H\007*\005\030\220N(\002R\023minimumTreate" + "dHosts\022J\n\023minimum_improvement\030\006 \001(\001B\031\272H\026" + "\022\024\031\000\000\000\000\000\000\360\?)\000\000\000\000\000\000\000\000@\001R\022minimumImproveme" + "nt\022a\n\037maximum_retransmission_increase\030\007 " + "\001(\001B\031\272H\026\022\024\031\000\000\000\000\000\000\360\?)\000\000\000\000\000\000\000\000@\001R\035maximumR" + "etransmissionIncrease\022K\n\024maximum_rtt_inc" + "rease\030\010 \001(\001B\031\272H\026\022\024\031\000\000\000\000\000\000\360\?)\000\000\000\000\000\000\000\000@\001R\022" + "maximumRttIncrease\022^\n\024maximum_evidence_a" + "ge\030\t \001(\0132\031.google.protobuf.DurationB\021\272H\016" + "\252\001\010\"\004\010\200\365$*\000\310\001\001R\022maximumEvidenceAge:\211\001\272H\205" + "\001\032\202\001\n\037policy.observation_within_phase\022/m" + "inimum_observation cannot exceed maximum" + "_phase\032.this.minimum_observation <= this" + ".maximum_phase\"\301\003\n\005Phase\022-\n\014target_hosts" + "\030\001 \001(\rB\n\272H\007*\005\030\220N(\001R\013targetHosts\0227\n\tissue" + "d_at\030\002 \001(\0132\032.google.protobuf.TimestampR\010" + "issuedAt\022P\n\026observation_started_at\030\003 \001(\013" + "2\032.google.protobuf.TimestampR\024observatio" + "nStartedAt\022=\n\014evaluated_at\030\004 \001(\0132\032.googl" + "e.protobuf.TimestampR\013evaluatedAt\0227\n\010evi" + "dence\030\005 \001(\0132\033.xtcp_treatment.v1.Evidence" + "R\010evidence\022>\n\007outcome\030\006 \001(\0162\032.xtcp_treat" + "ment.v1.OutcomeB\010\272H\005\202\001\002\020\001R\007outcome\022$\n\010re" + "viewer\030\007 \001(\tB\010\272H\005r\003\030\200\001R\010reviewer\022 \n\006reas" + "on\030\010 \001(\tB\010\272H\005r\003\030\200 R\006reason\"\315\006\n\nExperimen" + "t\0229\n\002id\030\001 \001(\tB)\272H&r$2\"^[a-zA-Z0-9][a-zA-" + "Z0-9_.-]{0,127}$R\002id\022E\n\010location\030\002 \001(\tB)" + "\272H&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}" + "$R\010location\022*\n\nhypothesis\030\003 \001(\tB\n\272H\007r\005\020\001" + "\030\200@R\nhypothesis\022B\n\ttreatment\030\004 \001(\0132\034.xtc" + "p_treatment.v1.TreatmentB\006\272H\003\310\001\001R\ttreatm" + "ent\022C\n\006policy\030\005 \001(\0132#.xtcp_treatment.v1." + "ExperimentPolicyB\006\272H\003\310\001\001R\006policy\022\?\n\010base" + "line\030\006 \001(\0132\033.xtcp_treatment.v1.EvidenceB" + "\006\272H\003\310\001\001R\010baseline\022X\n\014cohort_order\030\007 \003(\tB" + "5\272H2\222\001/\010\003\020\220N\030\001\"&r$2\"^[a-zA-Z0-9][a-zA-Z0" + "-9_.-]{0,127}$R\013cohortOrder\022:\n\006phases\030\010 " + "\003(\0132\030.xtcp_treatment.v1.PhaseB\010\272H\005\222\001\002\020@R" + "\006phases\022E\n\006status\030\t \001(\0162#.xtcp_treatment" + ".v1.ExperimentStatusB\010\272H\005\202\001\002\020\001R\006status\022\032" + "\n\010revision\030\n \001(\004R\010revision\022;\n\013proposed_a" + "t\030\013 \001(\0132\032.google.protobuf.TimestampR\npro" + "posedAt\022D\n\017latest_evidence\030\014 \001(\0132\033.xtcp_" + "treatment.v1.EvidenceR\016latestEvidence\022K\n" + "\016latest_outcome\030\r \001(\0162\032.xtcp_treatment.v" + "1.OutcomeB\010\272H\005\202\001\002\020\001R\rlatestOutcome*\?\n\004Ro" + "le\022\024\n\020ROLE_UNSPECIFIED\020\000\022\016\n\nROLE_AGENT\020\001" + "\022\021\n\rROLE_OPERATOR\020\002*\244\001\n\016AssignmentMode\022\037" + "\n\033ASSIGNMENT_MODE_UNSPECIFIED\020\000\022\031\n\025ASSIG" + "NMENT_MODE_TRIAL\020\001\022\032\n\026ASSIGNMENT_MODE_ST" + "ABLE\020\002\022\035\n\031ASSIGNMENT_MODE_SUSPENDED\020\003\022\033\n" + "\027ASSIGNMENT_MODE_RETIRED\020\004*\255\001\n\013ApplyStat" + "us\022\034\n\030APPLY_STATUS_UNSPECIFIED\020\000\022\030\n\024APPL" + "Y_STATUS_PENDING\020\001\022\031\n\025APPLY_STATUS_VERIF" + "IED\020\002\022\030\n\024APPLY_STATUS_REMOVED\020\003\022\030\n\024APPLY" + "_STATUS_EXPIRED\020\004\022\027\n\023APPLY_STATUS_FAILED" + "\020\005*\244\004\n\007Outcome\022\027\n\023OUTCOME_UNSPECIFIED\020\000\022" + "\024\n\020OUTCOME_BASELINE\020\001\022 \n\034OUTCOME_AWAITIN" + "G_APPLICATION\020\002\022\035\n\031OUTCOME_AWAITING_EXPO" + "SURE\020\003\022!\n\035OUTCOME_INSUFFICIENT_DURATION\020" + "\004\022\035\n\031OUTCOME_INSUFFICIENT_DATA\020\005\022!\n\035OUTC" + "OME_INSUFFICIENT_CONTROLS\020\006\022\021\n\rOUTCOME_S" + "TALE\020\007\022\027\n\023OUTCOME_UNAVAILABLE\020\010\022\030\n\024OUTCO" + "ME_INCONCLUSIVE\020\t\022\027\n\023OUTCOME_SAFETY_ONLY" + "\020\n\022\030\n\024OUTCOME_FINAL_SAFETY\020\013\022\026\n\022OUTCOME_" + "BENEFICIAL\020\014\022\023\n\017OUTCOME_HARMFUL\020\r\022\023\n\017OUT" + "COME_EXPIRED\020\016\022\033\n\027OUTCOME_STABLE_OBSERVE" + "D\020\017\022&\n\"OUTCOME_STABLE_EVIDENCE_INCOMPLET" + "E\020\020\022-\n)OUTCOME_STABLE_REGRESSION_REVIEW_" + "REQUIRED\020\021\022\025\n\021OUTCOME_OBSERVING\020\022*\225\002\n\020Ex" "perimentStatus\022!\n\035EXPERIMENT_STATUS_UNSP" "ECIFIED\020\000\022\036\n\032EXPERIMENT_STATUS_PROPOSED\020" "\001\022\035\n\031EXPERIMENT_STATUS_RUNNING\020\002\022\034\n\030EXPE" @@ -3095,7 +3092,7 @@ static ::absl::once_flag descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2epr PROTOBUF_CONSTINIT const ::_pbi::DescriptorTable descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto = { false, false, - 8235, + 8795, descriptor_table_protodef_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, "xtcp_treatment/v1/treatment.proto", &descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto_once, @@ -3132,10 +3129,17 @@ ApplyStatus_descriptor() { PROTOBUF_CONSTINIT const uint32_t ApplyStatus_internal_data_[] = { 393216u, 0u, }; [[nodiscard]] const ::google::protobuf::EnumDescriptor* PROTOBUF_NONNULL -ExperimentStatus_descriptor() { +Outcome_descriptor() { ::google::protobuf::internal::AssignDescriptors(&descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto); return file_level_enum_descriptors_xtcp_5ftreatment_2fv1_2ftreatment_2eproto[3]; } +PROTOBUF_CONSTINIT const uint32_t Outcome_internal_data_[] = { + 1245184u, 0u, }; +[[nodiscard]] const ::google::protobuf::EnumDescriptor* PROTOBUF_NONNULL +ExperimentStatus_descriptor() { + ::google::protobuf::internal::AssignDescriptors(&descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto); + return file_level_enum_descriptors_xtcp_5ftreatment_2fv1_2ftreatment_2eproto[4]; +} PROTOBUF_CONSTINIT const uint32_t ExperimentStatus_internal_data_[] = { 524288u, 0u, }; // =================================================================== @@ -3801,7 +3805,7 @@ ::google::protobuf::Metadata Treatment::GetMetadata() const { void Assignment::clear_lease_expires_at() { ::google::protobuf::internal::TSanWrite(&_impl_); if (_impl_.lease_expires_at_ != nullptr) _impl_.lease_expires_at_->Clear(); - ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); } Assignment::Assignment(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) #if defined(PROTOBUF_CUSTOM_VTABLE) @@ -3817,7 +3821,6 @@ PROTOBUF_NDEBUG_INLINE Assignment::Impl_::Impl_( [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, [[maybe_unused]] const ::xtcp_treatment::v1::Assignment& from_msg) : _has_bits_{from._has_bits_}, - id_(arena, from.id_), experiment_id_(arena, from.experiment_id_) {} Assignment::Assignment( @@ -3835,13 +3838,13 @@ Assignment::Assignment( from._internal_metadata_); new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); ::uint32_t cached_has_bits = _impl_._has_bits_[0]; - _impl_.treatment_ = (CheckHasBit(cached_has_bits, 0x00000004U)) + _impl_.treatment_ = (CheckHasBit(cached_has_bits, 0x00000002U)) ? Super_::CopyConstruct(arena, *from._impl_.treatment_) : nullptr; - _impl_.lease_expires_at_ = (CheckHasBit(cached_has_bits, 0x00000008U)) + _impl_.lease_expires_at_ = (CheckHasBit(cached_has_bits, 0x00000004U)) ? Super_::CopyConstruct(arena, *from._impl_.lease_expires_at_) : nullptr; - _impl_.predecessor_ = (CheckHasBit(cached_has_bits, 0x00000010U)) + _impl_.predecessor_ = (CheckHasBit(cached_has_bits, 0x00000008U)) ? Super_::CopyConstruct(arena, *from._impl_.predecessor_) : nullptr; ::memcpy(reinterpret_cast(&_impl_) + @@ -3857,8 +3860,7 @@ Assignment::Assignment( PROTOBUF_NDEBUG_INLINE Assignment::Impl_::Impl_( [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) - : id_(arena), - experiment_id_(arena) {} + : experiment_id_(arena) {} inline void Assignment::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { new (&_impl_) Impl_(internal_visibility(), arena); @@ -3880,7 +3882,6 @@ inline void Assignment::SharedDtor(MessageLite& self) { } this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); ABSL_DCHECK(this_.GetArena() == nullptr); - this_._impl_.id_.Destroy(); this_._impl_.experiment_id_.Destroy(); delete this_._impl_.treatment_; delete this_._impl_.lease_expires_at_; @@ -3921,27 +3922,24 @@ PROTOBUF_NOINLINE void Assignment::Clear() { ::uint32_t cached_has_bits [[maybe_unused]] = 0; cached_has_bits = this_._impl_._has_bits_[0]; - if (BatchCheckHasBit(cached_has_bits, 0x0000001fU)) { + if (BatchCheckHasBit(cached_has_bits, 0x0000000fU)) { if (CheckHasBit(cached_has_bits, 0x00000001U)) { - this_._impl_.id_.ClearNonDefaultToEmpty(); - } - if (CheckHasBit(cached_has_bits, 0x00000002U)) { this_._impl_.experiment_id_.ClearNonDefaultToEmpty(); } - if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { ABSL_DCHECK(this_._impl_.treatment_ != nullptr); this_._impl_.treatment_->Clear(); } - if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { ABSL_DCHECK(this_._impl_.lease_expires_at_ != nullptr); this_._impl_.lease_expires_at_->Clear(); } - if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { ABSL_DCHECK(this_._impl_.predecessor_ != nullptr); this_._impl_.predecessor_->Clear(); } } - if (BatchCheckHasBit(cached_has_bits, 0x00000060U)) { + if (BatchCheckHasBit(cached_has_bits, 0x00000030U)) { ::memset(&this_._impl_.generation_, 0, static_cast<::size_t>( reinterpret_cast(&this_._impl_.mode_) - @@ -3971,18 +3969,8 @@ ::uint8_t* PROTOBUF_NONNULL Assignment::_InternalSerialize( (void)cached_has_bits; cached_has_bits = this_._impl_._has_bits_[0]; - // string id = 1 [json_name = "id", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00000001U)) { - if (!this_._internal_id().empty()) { - const ::std::string& _s = this_._internal_id(); - ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( - _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.Assignment.id"); - target = stream->WriteStringMaybeAliased(1, _s, target); - } - } - // string experiment_id = 2 [json_name = "experimentId", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { if (!this_._internal_experiment_id().empty()) { const ::std::string& _s = this_._internal_experiment_id(); ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( @@ -3992,14 +3980,14 @@ ::uint8_t* PROTOBUF_NONNULL Assignment::_InternalSerialize( } // .xtcp_treatment.v1.Treatment treatment = 3 [json_name = "treatment", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( 3, *this_._impl_.treatment_, this_._impl_.treatment_->GetCachedSize(), target, stream); } // .xtcp_treatment.v1.AssignmentMode mode = 4 [json_name = "mode", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00000040U)) { + if (CheckHasBit(cached_has_bits, 0x00000020U)) { if (this_._internal_mode() != 0) { target = stream->EnsureSpace(target); target = ::_pbi::WireFormatLite::WriteEnumToArray( @@ -4008,21 +3996,21 @@ ::uint8_t* PROTOBUF_NONNULL Assignment::_InternalSerialize( } // .google.protobuf.Timestamp lease_expires_at = 5 [json_name = "leaseExpiresAt"]; - if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( 5, *this_._impl_.lease_expires_at_, this_._impl_.lease_expires_at_->GetCachedSize(), target, stream); } // .xtcp_treatment.v1.Treatment predecessor = 6 [json_name = "predecessor"]; - if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( 6, *this_._impl_.predecessor_, this_._impl_.predecessor_->GetCachedSize(), target, stream); } // uint64 generation = 7 [json_name = "generation", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (CheckHasBit(cached_has_bits, 0x00000010U)) { if (this_._internal_generation() != 0) { target = stream->EnsureSpace(target); target = ::_pbi::WireFormatLite::WriteUInt64ToArray( @@ -4053,45 +4041,38 @@ ::size_t Assignment::ByteSizeLong() const { ::_pbi::Prefetch5LinesFrom7Lines(&this_); cached_has_bits = this_._impl_._has_bits_[0]; - if (BatchCheckHasBit(cached_has_bits, 0x0000007fU)) { - // string id = 1 [json_name = "id", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00000001U)) { - if (!this_._internal_id().empty()) { - total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( - this_._internal_id()); - } - } + if (BatchCheckHasBit(cached_has_bits, 0x0000003fU)) { // string experiment_id = 2 [json_name = "experimentId", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { if (!this_._internal_experiment_id().empty()) { total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( this_._internal_experiment_id()); } } // .xtcp_treatment.v1.Treatment treatment = 3 [json_name = "treatment", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { total_size += 1 + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.treatment_); } // .google.protobuf.Timestamp lease_expires_at = 5 [json_name = "leaseExpiresAt"]; - if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { total_size += 1 + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.lease_expires_at_); } // .xtcp_treatment.v1.Treatment predecessor = 6 [json_name = "predecessor"]; - if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { total_size += 1 + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.predecessor_); } // uint64 generation = 7 [json_name = "generation", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (CheckHasBit(cached_has_bits, 0x00000010U)) { if (this_._internal_generation() != 0) { total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne( this_._internal_generation()); } } // .xtcp_treatment.v1.AssignmentMode mode = 4 [json_name = "mode", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00000040U)) { + if (CheckHasBit(cached_has_bits, 0x00000020U)) { if (this_._internal_mode() != 0) { total_size += 1 + ::_pbi::WireFormatLite::EnumSize(this_._internal_mode()); @@ -4116,17 +4097,8 @@ void Assignment::MergeImpl(::google::protobuf::MessageLite& to_msg, (void)cached_has_bits; cached_has_bits = from._impl_._has_bits_[0]; - if (BatchCheckHasBit(cached_has_bits, 0x0000007fU)) { + if (BatchCheckHasBit(cached_has_bits, 0x0000003fU)) { if (CheckHasBit(cached_has_bits, 0x00000001U)) { - if (!from._internal_id().empty()) { - _this->_internal_set_id(from._internal_id()); - } else { - if (_this->_impl_.id_.IsDefault()) { - _this->_internal_set_id(""); - } - } - } - if (CheckHasBit(cached_has_bits, 0x00000002U)) { if (!from._internal_experiment_id().empty()) { _this->_internal_set_experiment_id(from._internal_experiment_id()); } else { @@ -4135,7 +4107,7 @@ void Assignment::MergeImpl(::google::protobuf::MessageLite& to_msg, } } } - if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { ABSL_DCHECK(from._impl_.treatment_ != nullptr); if (_this->_impl_.treatment_ == nullptr) { _this->_impl_.treatment_ = Super_::CopyConstruct(arena, *from._impl_.treatment_); @@ -4143,7 +4115,7 @@ void Assignment::MergeImpl(::google::protobuf::MessageLite& to_msg, _this->_impl_.treatment_->MergeFrom(*from._impl_.treatment_); } } - if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { ABSL_DCHECK(from._impl_.lease_expires_at_ != nullptr); if (_this->_impl_.lease_expires_at_ == nullptr) { _this->_impl_.lease_expires_at_ = Super_::CopyConstruct(arena, *from._impl_.lease_expires_at_); @@ -4151,7 +4123,7 @@ void Assignment::MergeImpl(::google::protobuf::MessageLite& to_msg, _this->_impl_.lease_expires_at_->MergeFrom(*from._impl_.lease_expires_at_); } } - if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { ABSL_DCHECK(from._impl_.predecessor_ != nullptr); if (_this->_impl_.predecessor_ == nullptr) { _this->_impl_.predecessor_ = Super_::CopyConstruct(arena, *from._impl_.predecessor_); @@ -4159,12 +4131,12 @@ void Assignment::MergeImpl(::google::protobuf::MessageLite& to_msg, _this->_impl_.predecessor_->MergeFrom(*from._impl_.predecessor_); } } - if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (CheckHasBit(cached_has_bits, 0x00000010U)) { if (from._internal_generation() != 0) { _this->_impl_.generation_ = from._impl_.generation_; } } - if (CheckHasBit(cached_has_bits, 0x00000040U)) { + if (CheckHasBit(cached_has_bits, 0x00000020U)) { if (from._internal_mode() != 0) { _this->_impl_.mode_ = from._impl_.mode_; } @@ -4189,7 +4161,6 @@ void Assignment::InternalSwap(Assignment* PROTOBUF_RESTRICT PROTOBUF_NONNULL oth ABSL_DCHECK_EQ(arena, other->GetArena()); _internal_metadata_.InternalSwap(&other->_internal_metadata_); swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); - ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.id_, &other->_impl_.id_, arena); ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.experiment_id_, &other->_impl_.experiment_id_, arena); ::google::protobuf::internal::memswap< PROTOBUF_FIELD_OFFSET(Assignment, _impl_.mode_) @@ -7111,17 +7082,17 @@ ::google::protobuf::Metadata ExperimentPolicy::GetMetadata() const { void Phase::clear_issued_at() { ::google::protobuf::internal::TSanWrite(&_impl_); if (_impl_.issued_at_ != nullptr) _impl_.issued_at_->Clear(); - ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); } void Phase::clear_observation_started_at() { ::google::protobuf::internal::TSanWrite(&_impl_); if (_impl_.observation_started_at_ != nullptr) _impl_.observation_started_at_->Clear(); - ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); } void Phase::clear_evaluated_at() { ::google::protobuf::internal::TSanWrite(&_impl_); if (_impl_.evaluated_at_ != nullptr) _impl_.evaluated_at_->Clear(); - ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); } Phase::Phase(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) #if defined(PROTOBUF_CUSTOM_VTABLE) @@ -7137,7 +7108,6 @@ PROTOBUF_NDEBUG_INLINE Phase::Impl_::Impl_( [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, [[maybe_unused]] const ::xtcp_treatment::v1::Phase& from_msg) : _has_bits_{from._has_bits_}, - outcome_(arena, from.outcome_), reviewer_(arena, from.reviewer_), reason_(arena, from.reason_) {} @@ -7156,27 +7126,32 @@ Phase::Phase( from._internal_metadata_); new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); ::uint32_t cached_has_bits = _impl_._has_bits_[0]; - _impl_.issued_at_ = (CheckHasBit(cached_has_bits, 0x00000008U)) + _impl_.issued_at_ = (CheckHasBit(cached_has_bits, 0x00000004U)) ? Super_::CopyConstruct(arena, *from._impl_.issued_at_) : nullptr; - _impl_.observation_started_at_ = (CheckHasBit(cached_has_bits, 0x00000010U)) + _impl_.observation_started_at_ = (CheckHasBit(cached_has_bits, 0x00000008U)) ? Super_::CopyConstruct(arena, *from._impl_.observation_started_at_) : nullptr; - _impl_.evaluated_at_ = (CheckHasBit(cached_has_bits, 0x00000020U)) + _impl_.evaluated_at_ = (CheckHasBit(cached_has_bits, 0x00000010U)) ? Super_::CopyConstruct(arena, *from._impl_.evaluated_at_) : nullptr; - _impl_.evidence_ = (CheckHasBit(cached_has_bits, 0x00000040U)) + _impl_.evidence_ = (CheckHasBit(cached_has_bits, 0x00000020U)) ? Super_::CopyConstruct(arena, *from._impl_.evidence_) : nullptr; - _impl_.target_hosts_ = from._impl_.target_hosts_; + ::memcpy(reinterpret_cast(&_impl_) + + offsetof(Impl_, target_hosts_), + reinterpret_cast(&from._impl_) + + offsetof(Impl_, target_hosts_), + offsetof(Impl_, outcome_) - + offsetof(Impl_, target_hosts_) + + sizeof(Impl_::outcome_)); // @@protoc_insertion_point(copy_constructor:xtcp_treatment.v1.Phase) } PROTOBUF_NDEBUG_INLINE Phase::Impl_::Impl_( [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) - : outcome_(arena), - reviewer_(arena), + : reviewer_(arena), reason_(arena) {} inline void Phase::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { @@ -7184,9 +7159,9 @@ inline void Phase::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { ::memset(reinterpret_cast(&_impl_) + offsetof(Impl_, issued_at_), 0, - offsetof(Impl_, target_hosts_) - + offsetof(Impl_, outcome_) - offsetof(Impl_, issued_at_) + - sizeof(Impl_::target_hosts_)); + sizeof(Impl_::outcome_)); } Phase::~Phase() { // @@protoc_insertion_point(destructor:xtcp_treatment.v1.Phase) @@ -7199,7 +7174,6 @@ inline void Phase::SharedDtor(MessageLite& self) { } this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); ABSL_DCHECK(this_.GetArena() == nullptr); - this_._impl_.outcome_.Destroy(); this_._impl_.reviewer_.Destroy(); this_._impl_.reason_.Destroy(); delete this_._impl_.issued_at_; @@ -7242,34 +7216,37 @@ PROTOBUF_NOINLINE void Phase::Clear() { ::uint32_t cached_has_bits [[maybe_unused]] = 0; cached_has_bits = this_._impl_._has_bits_[0]; - if (BatchCheckHasBit(cached_has_bits, 0x0000007fU)) { + if (BatchCheckHasBit(cached_has_bits, 0x0000003fU)) { if (CheckHasBit(cached_has_bits, 0x00000001U)) { - this_._impl_.outcome_.ClearNonDefaultToEmpty(); - } - if (CheckHasBit(cached_has_bits, 0x00000002U)) { this_._impl_.reviewer_.ClearNonDefaultToEmpty(); } - if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { this_._impl_.reason_.ClearNonDefaultToEmpty(); } - if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { ABSL_DCHECK(this_._impl_.issued_at_ != nullptr); this_._impl_.issued_at_->Clear(); } - if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { ABSL_DCHECK(this_._impl_.observation_started_at_ != nullptr); this_._impl_.observation_started_at_->Clear(); } - if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (CheckHasBit(cached_has_bits, 0x00000010U)) { ABSL_DCHECK(this_._impl_.evaluated_at_ != nullptr); this_._impl_.evaluated_at_->Clear(); } - if (CheckHasBit(cached_has_bits, 0x00000040U)) { + if (CheckHasBit(cached_has_bits, 0x00000020U)) { ABSL_DCHECK(this_._impl_.evidence_ != nullptr); this_._impl_.evidence_->Clear(); } } - this_._impl_.target_hosts_ = 0u; + if (BatchCheckHasBit(cached_has_bits, 0x000000c0U)) { + ::memset(&this_._impl_.target_hosts_, 0, + static_cast<::size_t>( + reinterpret_cast(&this_._impl_.outcome_) - + reinterpret_cast(&this_._impl_.target_hosts_)) + + sizeof(_impl_.outcome_)); + } _impl_._has_bits_.Clear(); _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); } @@ -7294,7 +7271,7 @@ ::uint8_t* PROTOBUF_NONNULL Phase::_InternalSerialize( cached_has_bits = this_._impl_._has_bits_[0]; // uint32 target_hosts = 1 [json_name = "targetHosts", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00000080U)) { + if (CheckHasBit(cached_has_bits, 0x00000040U)) { if (this_._internal_target_hosts() != 0) { target = stream->EnsureSpace(target); target = ::_pbi::WireFormatLite::WriteUInt32ToArray( @@ -7303,45 +7280,44 @@ ::uint8_t* PROTOBUF_NONNULL Phase::_InternalSerialize( } // .google.protobuf.Timestamp issued_at = 2 [json_name = "issuedAt"]; - if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( 2, *this_._impl_.issued_at_, this_._impl_.issued_at_->GetCachedSize(), target, stream); } // .google.protobuf.Timestamp observation_started_at = 3 [json_name = "observationStartedAt"]; - if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( 3, *this_._impl_.observation_started_at_, this_._impl_.observation_started_at_->GetCachedSize(), target, stream); } // .google.protobuf.Timestamp evaluated_at = 4 [json_name = "evaluatedAt"]; - if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (CheckHasBit(cached_has_bits, 0x00000010U)) { target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( 4, *this_._impl_.evaluated_at_, this_._impl_.evaluated_at_->GetCachedSize(), target, stream); } // .xtcp_treatment.v1.Evidence evidence = 5 [json_name = "evidence"]; - if (CheckHasBit(cached_has_bits, 0x00000040U)) { + if (CheckHasBit(cached_has_bits, 0x00000020U)) { target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( 5, *this_._impl_.evidence_, this_._impl_.evidence_->GetCachedSize(), target, stream); } - // string outcome = 6 [json_name = "outcome", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00000001U)) { - if (!this_._internal_outcome().empty()) { - const ::std::string& _s = this_._internal_outcome(); - ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( - _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.Phase.outcome"); - target = stream->WriteStringMaybeAliased(6, _s, target); + // .xtcp_treatment.v1.Outcome outcome = 6 [json_name = "outcome", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + if (this_._internal_outcome() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 6, this_._internal_outcome(), target); } } // string reviewer = 7 [json_name = "reviewer", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { if (!this_._internal_reviewer().empty()) { const ::std::string& _s = this_._internal_reviewer(); ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( @@ -7351,7 +7327,7 @@ ::uint8_t* PROTOBUF_NONNULL Phase::_InternalSerialize( } // string reason = 8 [json_name = "reason", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { if (!this_._internal_reason().empty()) { const ::std::string& _s = this_._internal_reason(); ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( @@ -7384,54 +7360,54 @@ ::size_t Phase::ByteSizeLong() const { ::_pbi::Prefetch5LinesFrom7Lines(&this_); cached_has_bits = this_._impl_._has_bits_[0]; if (BatchCheckHasBit(cached_has_bits, 0x000000ffU)) { - // string outcome = 6 [json_name = "outcome", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00000001U)) { - if (!this_._internal_outcome().empty()) { - total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( - this_._internal_outcome()); - } - } // string reviewer = 7 [json_name = "reviewer", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { if (!this_._internal_reviewer().empty()) { total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( this_._internal_reviewer()); } } // string reason = 8 [json_name = "reason", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { if (!this_._internal_reason().empty()) { total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( this_._internal_reason()); } } // .google.protobuf.Timestamp issued_at = 2 [json_name = "issuedAt"]; - if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { total_size += 1 + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.issued_at_); } // .google.protobuf.Timestamp observation_started_at = 3 [json_name = "observationStartedAt"]; - if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { total_size += 1 + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.observation_started_at_); } // .google.protobuf.Timestamp evaluated_at = 4 [json_name = "evaluatedAt"]; - if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (CheckHasBit(cached_has_bits, 0x00000010U)) { total_size += 1 + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.evaluated_at_); } // .xtcp_treatment.v1.Evidence evidence = 5 [json_name = "evidence"]; - if (CheckHasBit(cached_has_bits, 0x00000040U)) { + if (CheckHasBit(cached_has_bits, 0x00000020U)) { total_size += 1 + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.evidence_); } // uint32 target_hosts = 1 [json_name = "targetHosts", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00000080U)) { + if (CheckHasBit(cached_has_bits, 0x00000040U)) { if (this_._internal_target_hosts() != 0) { total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne( this_._internal_target_hosts()); } } + // .xtcp_treatment.v1.Outcome outcome = 6 [json_name = "outcome", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + if (this_._internal_outcome() != 0) { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this_._internal_outcome()); + } + } } return this_.MaybeComputeUnknownFieldsSize(total_size, &this_._impl_._cached_size_); @@ -7453,15 +7429,6 @@ void Phase::MergeImpl(::google::protobuf::MessageLite& to_msg, cached_has_bits = from._impl_._has_bits_[0]; if (BatchCheckHasBit(cached_has_bits, 0x000000ffU)) { if (CheckHasBit(cached_has_bits, 0x00000001U)) { - if (!from._internal_outcome().empty()) { - _this->_internal_set_outcome(from._internal_outcome()); - } else { - if (_this->_impl_.outcome_.IsDefault()) { - _this->_internal_set_outcome(""); - } - } - } - if (CheckHasBit(cached_has_bits, 0x00000002U)) { if (!from._internal_reviewer().empty()) { _this->_internal_set_reviewer(from._internal_reviewer()); } else { @@ -7470,7 +7437,7 @@ void Phase::MergeImpl(::google::protobuf::MessageLite& to_msg, } } } - if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (CheckHasBit(cached_has_bits, 0x00000002U)) { if (!from._internal_reason().empty()) { _this->_internal_set_reason(from._internal_reason()); } else { @@ -7479,7 +7446,7 @@ void Phase::MergeImpl(::google::protobuf::MessageLite& to_msg, } } } - if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (CheckHasBit(cached_has_bits, 0x00000004U)) { ABSL_DCHECK(from._impl_.issued_at_ != nullptr); if (_this->_impl_.issued_at_ == nullptr) { _this->_impl_.issued_at_ = Super_::CopyConstruct(arena, *from._impl_.issued_at_); @@ -7487,7 +7454,7 @@ void Phase::MergeImpl(::google::protobuf::MessageLite& to_msg, _this->_impl_.issued_at_->MergeFrom(*from._impl_.issued_at_); } } - if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { ABSL_DCHECK(from._impl_.observation_started_at_ != nullptr); if (_this->_impl_.observation_started_at_ == nullptr) { _this->_impl_.observation_started_at_ = Super_::CopyConstruct(arena, *from._impl_.observation_started_at_); @@ -7495,7 +7462,7 @@ void Phase::MergeImpl(::google::protobuf::MessageLite& to_msg, _this->_impl_.observation_started_at_->MergeFrom(*from._impl_.observation_started_at_); } } - if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (CheckHasBit(cached_has_bits, 0x00000010U)) { ABSL_DCHECK(from._impl_.evaluated_at_ != nullptr); if (_this->_impl_.evaluated_at_ == nullptr) { _this->_impl_.evaluated_at_ = Super_::CopyConstruct(arena, *from._impl_.evaluated_at_); @@ -7503,7 +7470,7 @@ void Phase::MergeImpl(::google::protobuf::MessageLite& to_msg, _this->_impl_.evaluated_at_->MergeFrom(*from._impl_.evaluated_at_); } } - if (CheckHasBit(cached_has_bits, 0x00000040U)) { + if (CheckHasBit(cached_has_bits, 0x00000020U)) { ABSL_DCHECK(from._impl_.evidence_ != nullptr); if (_this->_impl_.evidence_ == nullptr) { _this->_impl_.evidence_ = Super_::CopyConstruct(arena, *from._impl_.evidence_); @@ -7511,11 +7478,16 @@ void Phase::MergeImpl(::google::protobuf::MessageLite& to_msg, _this->_impl_.evidence_->MergeFrom(*from._impl_.evidence_); } } - if (CheckHasBit(cached_has_bits, 0x00000080U)) { + if (CheckHasBit(cached_has_bits, 0x00000040U)) { if (from._internal_target_hosts() != 0) { _this->_impl_.target_hosts_ = from._impl_.target_hosts_; } } + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + if (from._internal_outcome() != 0) { + _this->_impl_.outcome_ = from._impl_.outcome_; + } + } } _this->_impl_._has_bits_[0] |= cached_has_bits; _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( @@ -7536,12 +7508,11 @@ void Phase::InternalSwap(Phase* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { ABSL_DCHECK_EQ(arena, other->GetArena()); _internal_metadata_.InternalSwap(&other->_internal_metadata_); swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); - ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.outcome_, &other->_impl_.outcome_, arena); ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.reviewer_, &other->_impl_.reviewer_, arena); ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.reason_, &other->_impl_.reason_, arena); ::google::protobuf::internal::memswap< - PROTOBUF_FIELD_OFFSET(Phase, _impl_.target_hosts_) - + sizeof(Phase::_impl_.target_hosts_) + PROTOBUF_FIELD_OFFSET(Phase, _impl_.outcome_) + + sizeof(Phase::_impl_.outcome_) - PROTOBUF_FIELD_OFFSET(Phase, _impl_.issued_at_)>( reinterpret_cast(&_impl_.issued_at_), reinterpret_cast(&other->_impl_.issued_at_)); @@ -7555,7 +7526,7 @@ ::google::protobuf::Metadata Phase::GetMetadata() const { void Experiment::clear_proposed_at() { ::google::protobuf::internal::TSanWrite(&_impl_); if (_impl_.proposed_at_ != nullptr) _impl_.proposed_at_->Clear(); - ClearHasBit(_impl_._has_bits_[0], 0x00000200U); + ClearHasBit(_impl_._has_bits_[0], 0x00000100U); } Experiment::Experiment(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) #if defined(PROTOBUF_CUSTOM_VTABLE) @@ -7587,8 +7558,7 @@ PROTOBUF_NDEBUG_INLINE Experiment::Impl_::Impl_( , id_(arena, from.id_), location_(arena, from.location_), - hypothesis_(arena, from.hypothesis_), - latest_outcome_(arena, from.latest_outcome_) {} + hypothesis_(arena, from.hypothesis_) {} Experiment::Experiment( ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, @@ -7605,28 +7575,28 @@ Experiment::Experiment( from._internal_metadata_); new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); ::uint32_t cached_has_bits = _impl_._has_bits_[0]; - _impl_.treatment_ = (CheckHasBit(cached_has_bits, 0x00000040U)) + _impl_.treatment_ = (CheckHasBit(cached_has_bits, 0x00000020U)) ? Super_::CopyConstruct(arena, *from._impl_.treatment_) : nullptr; - _impl_.policy_ = (CheckHasBit(cached_has_bits, 0x00000080U)) + _impl_.policy_ = (CheckHasBit(cached_has_bits, 0x00000040U)) ? Super_::CopyConstruct(arena, *from._impl_.policy_) : nullptr; - _impl_.baseline_ = (CheckHasBit(cached_has_bits, 0x00000100U)) + _impl_.baseline_ = (CheckHasBit(cached_has_bits, 0x00000080U)) ? Super_::CopyConstruct(arena, *from._impl_.baseline_) : nullptr; - _impl_.proposed_at_ = (CheckHasBit(cached_has_bits, 0x00000200U)) + _impl_.proposed_at_ = (CheckHasBit(cached_has_bits, 0x00000100U)) ? Super_::CopyConstruct(arena, *from._impl_.proposed_at_) : nullptr; - _impl_.latest_evidence_ = (CheckHasBit(cached_has_bits, 0x00000400U)) + _impl_.latest_evidence_ = (CheckHasBit(cached_has_bits, 0x00000200U)) ? Super_::CopyConstruct(arena, *from._impl_.latest_evidence_) : nullptr; ::memcpy(reinterpret_cast(&_impl_) + offsetof(Impl_, revision_), reinterpret_cast(&from._impl_) + offsetof(Impl_, revision_), - offsetof(Impl_, status_) - + offsetof(Impl_, latest_outcome_) - offsetof(Impl_, revision_) + - sizeof(Impl_::status_)); + sizeof(Impl_::latest_outcome_)); // @@protoc_insertion_point(copy_constructor:xtcp_treatment.v1.Experiment) } @@ -7645,17 +7615,16 @@ PROTOBUF_NDEBUG_INLINE Experiment::Impl_::Impl_( , id_(arena), location_(arena), - hypothesis_(arena), - latest_outcome_(arena) {} + hypothesis_(arena) {} inline void Experiment::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { new (&_impl_) Impl_(internal_visibility(), arena); ::memset(reinterpret_cast(&_impl_) + offsetof(Impl_, treatment_), 0, - offsetof(Impl_, status_) - + offsetof(Impl_, latest_outcome_) - offsetof(Impl_, treatment_) + - sizeof(Impl_::status_)); + sizeof(Impl_::latest_outcome_)); } Experiment::~Experiment() { // @@protoc_insertion_point(destructor:xtcp_treatment.v1.Experiment) @@ -7671,7 +7640,6 @@ inline void Experiment::SharedDtor(MessageLite& self) { this_._impl_.id_.Destroy(); this_._impl_.location_.Destroy(); this_._impl_.hypothesis_.Destroy(); - this_._impl_.latest_outcome_.Destroy(); delete this_._impl_.treatment_; delete this_._impl_.policy_; delete this_._impl_.baseline_; @@ -7730,37 +7698,34 @@ PROTOBUF_NOINLINE void Experiment::Clear() { this_._impl_.hypothesis_.ClearNonDefaultToEmpty(); } if (CheckHasBit(cached_has_bits, 0x00000020U)) { - this_._impl_.latest_outcome_.ClearNonDefaultToEmpty(); - } - if (CheckHasBit(cached_has_bits, 0x00000040U)) { ABSL_DCHECK(this_._impl_.treatment_ != nullptr); this_._impl_.treatment_->Clear(); } - if (CheckHasBit(cached_has_bits, 0x00000080U)) { + if (CheckHasBit(cached_has_bits, 0x00000040U)) { ABSL_DCHECK(this_._impl_.policy_ != nullptr); this_._impl_.policy_->Clear(); } - } - if (BatchCheckHasBit(cached_has_bits, 0x00000700U)) { - if (CheckHasBit(cached_has_bits, 0x00000100U)) { + if (CheckHasBit(cached_has_bits, 0x00000080U)) { ABSL_DCHECK(this_._impl_.baseline_ != nullptr); this_._impl_.baseline_->Clear(); } - if (CheckHasBit(cached_has_bits, 0x00000200U)) { + } + if (BatchCheckHasBit(cached_has_bits, 0x00000300U)) { + if (CheckHasBit(cached_has_bits, 0x00000100U)) { ABSL_DCHECK(this_._impl_.proposed_at_ != nullptr); this_._impl_.proposed_at_->Clear(); } - if (CheckHasBit(cached_has_bits, 0x00000400U)) { + if (CheckHasBit(cached_has_bits, 0x00000200U)) { ABSL_DCHECK(this_._impl_.latest_evidence_ != nullptr); this_._impl_.latest_evidence_->Clear(); } } - if (BatchCheckHasBit(cached_has_bits, 0x00001800U)) { + if (BatchCheckHasBit(cached_has_bits, 0x00001c00U)) { ::memset(&this_._impl_.revision_, 0, static_cast<::size_t>( - reinterpret_cast(&this_._impl_.status_) - + reinterpret_cast(&this_._impl_.latest_outcome_) - reinterpret_cast(&this_._impl_.revision_)) + - sizeof(_impl_.status_)); + sizeof(_impl_.latest_outcome_)); } _impl_._has_bits_.Clear(); _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); @@ -7816,21 +7781,21 @@ ::uint8_t* PROTOBUF_NONNULL Experiment::_InternalSerialize( } // .xtcp_treatment.v1.Treatment treatment = 4 [json_name = "treatment", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00000040U)) { + if (CheckHasBit(cached_has_bits, 0x00000020U)) { target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( 4, *this_._impl_.treatment_, this_._impl_.treatment_->GetCachedSize(), target, stream); } // .xtcp_treatment.v1.ExperimentPolicy policy = 5 [json_name = "policy", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00000080U)) { + if (CheckHasBit(cached_has_bits, 0x00000040U)) { target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( 5, *this_._impl_.policy_, this_._impl_.policy_->GetCachedSize(), target, stream); } // .xtcp_treatment.v1.Evidence baseline = 6 [json_name = "baseline", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00000100U)) { + if (CheckHasBit(cached_has_bits, 0x00000080U)) { target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( 6, *this_._impl_.baseline_, this_._impl_.baseline_->GetCachedSize(), target, stream); @@ -7859,7 +7824,7 @@ ::uint8_t* PROTOBUF_NONNULL Experiment::_InternalSerialize( } // .xtcp_treatment.v1.ExperimentStatus status = 9 [json_name = "status", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00001000U)) { + if (CheckHasBit(cached_has_bits, 0x00000800U)) { if (this_._internal_status() != 0) { target = stream->EnsureSpace(target); target = ::_pbi::WireFormatLite::WriteEnumToArray( @@ -7868,7 +7833,7 @@ ::uint8_t* PROTOBUF_NONNULL Experiment::_InternalSerialize( } // uint64 revision = 10 [json_name = "revision"]; - if (CheckHasBit(cached_has_bits, 0x00000800U)) { + if (CheckHasBit(cached_has_bits, 0x00000400U)) { if (this_._internal_revision() != 0) { target = stream->EnsureSpace(target); target = ::_pbi::WireFormatLite::WriteUInt64ToArray( @@ -7877,26 +7842,25 @@ ::uint8_t* PROTOBUF_NONNULL Experiment::_InternalSerialize( } // .google.protobuf.Timestamp proposed_at = 11 [json_name = "proposedAt"]; - if (CheckHasBit(cached_has_bits, 0x00000200U)) { + if (CheckHasBit(cached_has_bits, 0x00000100U)) { target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( 11, *this_._impl_.proposed_at_, this_._impl_.proposed_at_->GetCachedSize(), target, stream); } // .xtcp_treatment.v1.Evidence latest_evidence = 12 [json_name = "latestEvidence"]; - if (CheckHasBit(cached_has_bits, 0x00000400U)) { + if (CheckHasBit(cached_has_bits, 0x00000200U)) { target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( 12, *this_._impl_.latest_evidence_, this_._impl_.latest_evidence_->GetCachedSize(), target, stream); } - // string latest_outcome = 13 [json_name = "latestOutcome", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00000020U)) { - if (!this_._internal_latest_outcome().empty()) { - const ::std::string& _s = this_._internal_latest_outcome(); - ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( - _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.Experiment.latest_outcome"); - target = stream->WriteStringMaybeAliased(13, _s, target); + // .xtcp_treatment.v1.Outcome latest_outcome = 13 [json_name = "latestOutcome", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00001000U)) { + if (this_._internal_latest_outcome() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 13, this_._internal_latest_outcome(), target); } } @@ -7961,54 +7925,54 @@ ::size_t Experiment::ByteSizeLong() const { this_._internal_hypothesis()); } } - // string latest_outcome = 13 [json_name = "latestOutcome", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00000020U)) { - if (!this_._internal_latest_outcome().empty()) { - total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( - this_._internal_latest_outcome()); - } - } // .xtcp_treatment.v1.Treatment treatment = 4 [json_name = "treatment", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00000040U)) { + if (CheckHasBit(cached_has_bits, 0x00000020U)) { total_size += 1 + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.treatment_); } // .xtcp_treatment.v1.ExperimentPolicy policy = 5 [json_name = "policy", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00000080U)) { + if (CheckHasBit(cached_has_bits, 0x00000040U)) { total_size += 1 + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.policy_); } - } - if (BatchCheckHasBit(cached_has_bits, 0x00001f00U)) { // .xtcp_treatment.v1.Evidence baseline = 6 [json_name = "baseline", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00000100U)) { + if (CheckHasBit(cached_has_bits, 0x00000080U)) { total_size += 1 + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.baseline_); } + } + if (BatchCheckHasBit(cached_has_bits, 0x00001f00U)) { // .google.protobuf.Timestamp proposed_at = 11 [json_name = "proposedAt"]; - if (CheckHasBit(cached_has_bits, 0x00000200U)) { + if (CheckHasBit(cached_has_bits, 0x00000100U)) { total_size += 1 + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.proposed_at_); } // .xtcp_treatment.v1.Evidence latest_evidence = 12 [json_name = "latestEvidence"]; - if (CheckHasBit(cached_has_bits, 0x00000400U)) { + if (CheckHasBit(cached_has_bits, 0x00000200U)) { total_size += 1 + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.latest_evidence_); } // uint64 revision = 10 [json_name = "revision"]; - if (CheckHasBit(cached_has_bits, 0x00000800U)) { + if (CheckHasBit(cached_has_bits, 0x00000400U)) { if (this_._internal_revision() != 0) { total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne( this_._internal_revision()); } } // .xtcp_treatment.v1.ExperimentStatus status = 9 [json_name = "status", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00001000U)) { + if (CheckHasBit(cached_has_bits, 0x00000800U)) { if (this_._internal_status() != 0) { total_size += 1 + ::_pbi::WireFormatLite::EnumSize(this_._internal_status()); } } + // .xtcp_treatment.v1.Outcome latest_outcome = 13 [json_name = "latestOutcome", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00001000U)) { + if (this_._internal_latest_outcome() != 0) { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this_._internal_latest_outcome()); + } + } } return this_.MaybeComputeUnknownFieldsSize(total_size, &this_._impl_._cached_size_); @@ -8067,15 +8031,6 @@ void Experiment::MergeImpl(::google::protobuf::MessageLite& to_msg, } } if (CheckHasBit(cached_has_bits, 0x00000020U)) { - if (!from._internal_latest_outcome().empty()) { - _this->_internal_set_latest_outcome(from._internal_latest_outcome()); - } else { - if (_this->_impl_.latest_outcome_.IsDefault()) { - _this->_internal_set_latest_outcome(""); - } - } - } - if (CheckHasBit(cached_has_bits, 0x00000040U)) { ABSL_DCHECK(from._impl_.treatment_ != nullptr); if (_this->_impl_.treatment_ == nullptr) { _this->_impl_.treatment_ = Super_::CopyConstruct(arena, *from._impl_.treatment_); @@ -8083,7 +8038,7 @@ void Experiment::MergeImpl(::google::protobuf::MessageLite& to_msg, _this->_impl_.treatment_->MergeFrom(*from._impl_.treatment_); } } - if (CheckHasBit(cached_has_bits, 0x00000080U)) { + if (CheckHasBit(cached_has_bits, 0x00000040U)) { ABSL_DCHECK(from._impl_.policy_ != nullptr); if (_this->_impl_.policy_ == nullptr) { _this->_impl_.policy_ = Super_::CopyConstruct(arena, *from._impl_.policy_); @@ -8091,9 +8046,7 @@ void Experiment::MergeImpl(::google::protobuf::MessageLite& to_msg, _this->_impl_.policy_->MergeFrom(*from._impl_.policy_); } } - } - if (BatchCheckHasBit(cached_has_bits, 0x00001f00U)) { - if (CheckHasBit(cached_has_bits, 0x00000100U)) { + if (CheckHasBit(cached_has_bits, 0x00000080U)) { ABSL_DCHECK(from._impl_.baseline_ != nullptr); if (_this->_impl_.baseline_ == nullptr) { _this->_impl_.baseline_ = Super_::CopyConstruct(arena, *from._impl_.baseline_); @@ -8101,7 +8054,9 @@ void Experiment::MergeImpl(::google::protobuf::MessageLite& to_msg, _this->_impl_.baseline_->MergeFrom(*from._impl_.baseline_); } } - if (CheckHasBit(cached_has_bits, 0x00000200U)) { + } + if (BatchCheckHasBit(cached_has_bits, 0x00001f00U)) { + if (CheckHasBit(cached_has_bits, 0x00000100U)) { ABSL_DCHECK(from._impl_.proposed_at_ != nullptr); if (_this->_impl_.proposed_at_ == nullptr) { _this->_impl_.proposed_at_ = Super_::CopyConstruct(arena, *from._impl_.proposed_at_); @@ -8109,7 +8064,7 @@ void Experiment::MergeImpl(::google::protobuf::MessageLite& to_msg, _this->_impl_.proposed_at_->MergeFrom(*from._impl_.proposed_at_); } } - if (CheckHasBit(cached_has_bits, 0x00000400U)) { + if (CheckHasBit(cached_has_bits, 0x00000200U)) { ABSL_DCHECK(from._impl_.latest_evidence_ != nullptr); if (_this->_impl_.latest_evidence_ == nullptr) { _this->_impl_.latest_evidence_ = Super_::CopyConstruct(arena, *from._impl_.latest_evidence_); @@ -8117,16 +8072,21 @@ void Experiment::MergeImpl(::google::protobuf::MessageLite& to_msg, _this->_impl_.latest_evidence_->MergeFrom(*from._impl_.latest_evidence_); } } - if (CheckHasBit(cached_has_bits, 0x00000800U)) { + if (CheckHasBit(cached_has_bits, 0x00000400U)) { if (from._internal_revision() != 0) { _this->_impl_.revision_ = from._impl_.revision_; } } - if (CheckHasBit(cached_has_bits, 0x00001000U)) { + if (CheckHasBit(cached_has_bits, 0x00000800U)) { if (from._internal_status() != 0) { _this->_impl_.status_ = from._impl_.status_; } } + if (CheckHasBit(cached_has_bits, 0x00001000U)) { + if (from._internal_latest_outcome() != 0) { + _this->_impl_.latest_outcome_ = from._impl_.latest_outcome_; + } + } } _this->_impl_._has_bits_[0] |= cached_has_bits; _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( @@ -8152,10 +8112,9 @@ void Experiment::InternalSwap(Experiment* PROTOBUF_RESTRICT PROTOBUF_NONNULL oth ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.id_, &other->_impl_.id_, arena); ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.location_, &other->_impl_.location_, arena); ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.hypothesis_, &other->_impl_.hypothesis_, arena); - ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.latest_outcome_, &other->_impl_.latest_outcome_, arena); ::google::protobuf::internal::memswap< - PROTOBUF_FIELD_OFFSET(Experiment, _impl_.status_) - + sizeof(Experiment::_impl_.status_) + PROTOBUF_FIELD_OFFSET(Experiment, _impl_.latest_outcome_) + + sizeof(Experiment::_impl_.latest_outcome_) - PROTOBUF_FIELD_OFFSET(Experiment, _impl_.treatment_)>( reinterpret_cast(&_impl_.treatment_), reinterpret_cast(&other->_impl_.treatment_)); diff --git a/gen/cpp/xtcp_treatment/v1/treatment.pb.h b/gen/cpp/xtcp_treatment/v1/treatment.pb.h index 2b074341e..0d1e6d4f5 100644 --- a/gen/cpp/xtcp_treatment/v1/treatment.pb.h +++ b/gen/cpp/xtcp_treatment/v1/treatment.pb.h @@ -57,6 +57,8 @@ enum AssignmentMode : int; extern const uint32_t AssignmentMode_internal_data_[]; enum ExperimentStatus : int; extern const uint32_t ExperimentStatus_internal_data_[]; +enum Outcome : int; +extern const uint32_t Outcome_internal_data_[]; enum Role : int; extern const uint32_t Role_internal_data_[]; class ApplicationRecord; @@ -169,6 +171,9 @@ template <> internal::EnumTraitsT<::xtcp_treatment::v1::ExperimentStatus_internal_data_> internal::EnumTraitsImpl::value<::xtcp_treatment::v1::ExperimentStatus>; template <> +internal::EnumTraitsT<::xtcp_treatment::v1::Outcome_internal_data_> + internal::EnumTraitsImpl::value<::xtcp_treatment::v1::Outcome>; +template <> internal::EnumTraitsT<::xtcp_treatment::v1::Role_internal_data_> internal::EnumTraitsImpl::value<::xtcp_treatment::v1::Role>; } // namespace protobuf @@ -304,6 +309,63 @@ template <> return ::google::protobuf::internal::ParseNamedEnum(ApplyStatus_descriptor(), name, value); } +enum Outcome : int { + OUTCOME_UNSPECIFIED = 0, + OUTCOME_BASELINE = 1, + OUTCOME_AWAITING_APPLICATION = 2, + OUTCOME_AWAITING_EXPOSURE = 3, + OUTCOME_INSUFFICIENT_DURATION = 4, + OUTCOME_INSUFFICIENT_DATA = 5, + OUTCOME_INSUFFICIENT_CONTROLS = 6, + OUTCOME_STALE = 7, + OUTCOME_UNAVAILABLE = 8, + OUTCOME_INCONCLUSIVE = 9, + OUTCOME_SAFETY_ONLY = 10, + OUTCOME_FINAL_SAFETY = 11, + OUTCOME_BENEFICIAL = 12, + OUTCOME_HARMFUL = 13, + OUTCOME_EXPIRED = 14, + OUTCOME_STABLE_OBSERVED = 15, + OUTCOME_STABLE_EVIDENCE_INCOMPLETE = 16, + OUTCOME_STABLE_REGRESSION_REVIEW_REQUIRED = 17, + OUTCOME_OBSERVING = 18, + Outcome_INT_MIN_SENTINEL_DO_NOT_USE_ = + ::std::numeric_limits<::int32_t>::min(), + Outcome_INT_MAX_SENTINEL_DO_NOT_USE_ = + ::std::numeric_limits<::int32_t>::max(), +}; + +extern const uint32_t Outcome_internal_data_[]; +inline constexpr Outcome Outcome_MIN = + static_cast(0); +inline constexpr Outcome Outcome_MAX = + static_cast(18); +[[nodiscard]] inline bool Outcome_IsValid(int value) { + return 0 <= value && value <= 18; +} +inline constexpr int Outcome_ARRAYSIZE = 18 + 1; +[[nodiscard]] const ::google::protobuf::EnumDescriptor* PROTOBUF_NONNULL +Outcome_descriptor(); +[[nodiscard]] inline auto ProtobufInternalGetEnumDescriptor(Outcome) { + return Outcome_descriptor(); +} +template +[[nodiscard]] const ::std::string& Outcome_Name(T value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to Outcome_Name()."); + return Outcome_Name(static_cast(value)); +} +template <> +[[nodiscard]] inline const ::std::string& Outcome_Name(Outcome value) { + return ::google::protobuf::internal::NameOfDenseEnum( + static_cast(value)); +} +[[nodiscard]] inline bool Outcome_Parse( + ::absl::string_view name, Outcome* PROTOBUF_NONNULL value) { + return ::google::protobuf::internal::ParseNamedEnum(Outcome_descriptor(), name, + value); +} enum ExperimentStatus : int { EXPERIMENT_STATUS_UNSPECIFIED = 0, EXPERIMENT_STATUS_PROPOSED = 1, @@ -2261,7 +2323,6 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Phase final : public ::google::prot // accessors ------------------------------------------------------- enum : int { - kOutcomeFieldNumber = 6, kReviewerFieldNumber = 7, kReasonFieldNumber = 8, kIssuedAtFieldNumber = 2, @@ -2269,22 +2330,8 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Phase final : public ::google::prot kEvaluatedAtFieldNumber = 4, kEvidenceFieldNumber = 5, kTargetHostsFieldNumber = 1, + kOutcomeFieldNumber = 6, }; - // string outcome = 6 [json_name = "outcome", (.buf.validate.field) = { - void clear_outcome() ; - [[nodiscard]] const ::std::string& outcome() const; - template - void set_outcome(Arg_&& arg, Args_... args); - ::std::string* PROTOBUF_NONNULL mutable_outcome(); - [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_outcome(); - void set_allocated_outcome(::std::string* PROTOBUF_NULLABLE value); - - private: - const ::std::string& _internal_outcome() const; - PROTOBUF_ALWAYS_INLINE void _internal_set_outcome(const ::std::string& value); - ::std::string* PROTOBUF_NONNULL _internal_mutable_outcome(); - - public: // string reviewer = 7 [json_name = "reviewer", (.buf.validate.field) = { void clear_reviewer() ; [[nodiscard]] const ::std::string& reviewer() const; @@ -2384,13 +2431,23 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Phase final : public ::google::prot ::uint32_t _internal_target_hosts() const; void _internal_set_target_hosts(::uint32_t value); + public: + // .xtcp_treatment.v1.Outcome outcome = 6 [json_name = "outcome", (.buf.validate.field) = { + void clear_outcome() ; + [[nodiscard]] ::xtcp_treatment::v1::Outcome outcome() const; + void set_outcome(::xtcp_treatment::v1::Outcome value); + + private: + ::xtcp_treatment::v1::Outcome _internal_outcome() const; + void _internal_set_outcome(::xtcp_treatment::v1::Outcome value); + public: // @@protoc_insertion_point(class_scope:xtcp_treatment.v1.Phase) private: class _Internal; using ParseTableT_ = ::google::protobuf::internal::TcParseTable<3, 8, - 4, 61, + 4, 54, 2>; static constexpr ParseTableT_ InternalGenerateParseTable_( const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); @@ -2418,7 +2475,6 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Phase final : public ::google::prot const Phase& from_msg); ::google::protobuf::internal::HasBits<1> _has_bits_; ::google::protobuf::internal::CachedSize _cached_size_; - ::google::protobuf::internal::ArenaStringPtr outcome_; ::google::protobuf::internal::ArenaStringPtr reviewer_; ::google::protobuf::internal::ArenaStringPtr reason_; ::google::protobuf::Timestamp* PROTOBUF_NULLABLE issued_at_; @@ -2426,6 +2482,7 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Phase final : public ::google::prot ::google::protobuf::Timestamp* PROTOBUF_NULLABLE evaluated_at_; ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE evidence_; ::uint32_t target_hosts_; + int outcome_; PROTOBUF_TSAN_DECLARE_MEMBER }; union { Impl_ _impl_; }; @@ -2581,7 +2638,6 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Assignment final : public ::google: // accessors ------------------------------------------------------- enum : int { - kIdFieldNumber = 1, kExperimentIdFieldNumber = 2, kTreatmentFieldNumber = 3, kLeaseExpiresAtFieldNumber = 5, @@ -2589,21 +2645,6 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Assignment final : public ::google: kGenerationFieldNumber = 7, kModeFieldNumber = 4, }; - // string id = 1 [json_name = "id", (.buf.validate.field) = { - void clear_id() ; - [[nodiscard]] const ::std::string& id() const; - template - void set_id(Arg_&& arg, Args_... args); - ::std::string* PROTOBUF_NONNULL mutable_id(); - [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_id(); - void set_allocated_id(::std::string* PROTOBUF_NULLABLE value); - - private: - const ::std::string& _internal_id() const; - PROTOBUF_ALWAYS_INLINE void _internal_set_id(const ::std::string& value); - ::std::string* PROTOBUF_NONNULL _internal_mutable_id(); - - public: // string experiment_id = 2 [json_name = "experimentId", (.buf.validate.field) = { void clear_experiment_id() ; [[nodiscard]] const ::std::string& experiment_id() const; @@ -2688,8 +2729,8 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Assignment final : public ::google: private: class _Internal; using ParseTableT_ = - ::google::protobuf::internal::TcParseTable<3, 7, - 3, 52, + ::google::protobuf::internal::TcParseTable<3, 6, + 3, 50, 2>; static constexpr ParseTableT_ InternalGenerateParseTable_( const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); @@ -2717,7 +2758,6 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Assignment final : public ::google: const Assignment& from_msg); ::google::protobuf::internal::HasBits<1> _has_bits_; ::google::protobuf::internal::CachedSize _cached_size_; - ::google::protobuf::internal::ArenaStringPtr id_; ::google::protobuf::internal::ArenaStringPtr experiment_id_; ::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE treatment_; ::google::protobuf::Timestamp* PROTOBUF_NULLABLE lease_expires_at_; @@ -2884,7 +2924,6 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Experiment final : public ::google: kIdFieldNumber = 1, kLocationFieldNumber = 2, kHypothesisFieldNumber = 3, - kLatestOutcomeFieldNumber = 13, kTreatmentFieldNumber = 4, kPolicyFieldNumber = 5, kBaselineFieldNumber = 6, @@ -2892,6 +2931,7 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Experiment final : public ::google: kLatestEvidenceFieldNumber = 12, kRevisionFieldNumber = 10, kStatusFieldNumber = 9, + kLatestOutcomeFieldNumber = 13, }; // repeated string cohort_order = 7 [json_name = "cohortOrder", (.buf.validate.field) = { [[nodiscard]] int cohort_order_size() const; @@ -2983,21 +3023,6 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Experiment final : public ::google: PROTOBUF_ALWAYS_INLINE void _internal_set_hypothesis(const ::std::string& value); ::std::string* PROTOBUF_NONNULL _internal_mutable_hypothesis(); - public: - // string latest_outcome = 13 [json_name = "latestOutcome", (.buf.validate.field) = { - void clear_latest_outcome() ; - [[nodiscard]] const ::std::string& latest_outcome() const; - template - void set_latest_outcome(Arg_&& arg, Args_... args); - ::std::string* PROTOBUF_NONNULL mutable_latest_outcome(); - [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_latest_outcome(); - void set_allocated_latest_outcome(::std::string* PROTOBUF_NULLABLE value); - - private: - const ::std::string& _internal_latest_outcome() const; - PROTOBUF_ALWAYS_INLINE void _internal_set_latest_outcome(const ::std::string& value); - ::std::string* PROTOBUF_NONNULL _internal_mutable_latest_outcome(); - public: // .xtcp_treatment.v1.Treatment treatment = 4 [json_name = "treatment", (.buf.validate.field) = { [[nodiscard]] bool has_treatment() const; @@ -3093,13 +3118,23 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Experiment final : public ::google: ::xtcp_treatment::v1::ExperimentStatus _internal_status() const; void _internal_set_status(::xtcp_treatment::v1::ExperimentStatus value); + public: + // .xtcp_treatment.v1.Outcome latest_outcome = 13 [json_name = "latestOutcome", (.buf.validate.field) = { + void clear_latest_outcome() ; + [[nodiscard]] ::xtcp_treatment::v1::Outcome latest_outcome() const; + void set_latest_outcome(::xtcp_treatment::v1::Outcome value); + + private: + ::xtcp_treatment::v1::Outcome _internal_latest_outcome() const; + void _internal_set_latest_outcome(::xtcp_treatment::v1::Outcome value); + public: // @@protoc_insertion_point(class_scope:xtcp_treatment.v1.Experiment) private: class _Internal; using ParseTableT_ = ::google::protobuf::internal::TcParseTable<4, 13, - 6, 91, + 6, 77, 2>; static constexpr ParseTableT_ InternalGenerateParseTable_( const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); @@ -3132,7 +3167,6 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Experiment final : public ::google: ::google::protobuf::internal::ArenaStringPtr id_; ::google::protobuf::internal::ArenaStringPtr location_; ::google::protobuf::internal::ArenaStringPtr hypothesis_; - ::google::protobuf::internal::ArenaStringPtr latest_outcome_; ::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE treatment_; ::xtcp_treatment::v1::ExperimentPolicy* PROTOBUF_NULLABLE policy_; ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE baseline_; @@ -3140,6 +3174,7 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Experiment final : public ::google: ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE latest_evidence_; ::uint64_t revision_; int status_; + int latest_outcome_; PROTOBUF_TSAN_DECLARE_MEMBER }; union { Impl_ _impl_; }; @@ -4600,75 +4635,11 @@ inline void Treatment::_internal_set_ecn_fabric_verified(bool value) { // Assignment -// string id = 1 [json_name = "id", (.buf.validate.field) = { -inline void Assignment::clear_id() { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.id_.ClearToEmpty(); - ClearHasBit(_impl_._has_bits_[0], 0x00000001U); -} -inline const ::std::string& Assignment::id() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { - // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Assignment.id) - return _internal_id(); -} -template -PROTOBUF_ALWAYS_INLINE void Assignment::set_id(Arg_&& arg, Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - SetHasBit(_impl_._has_bits_[0], 0x00000001U); - _impl_.id_.Set(static_cast(arg), args..., GetArena()); - // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Assignment.id) -} -inline ::std::string* PROTOBUF_NONNULL Assignment::mutable_id() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - SetHasBit(_impl_._has_bits_[0], 0x00000001U); - ::std::string* _s = _internal_mutable_id(); - // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Assignment.id) - return _s; -} -inline const ::std::string& Assignment::_internal_id() const { - ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.id_.Get(); -} -inline void Assignment::_internal_set_id(const ::std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.id_.Set(value, GetArena()); -} -inline ::std::string* PROTOBUF_NONNULL Assignment::_internal_mutable_id() { - ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.id_.Mutable( GetArena()); -} -inline ::std::string* PROTOBUF_NULLABLE Assignment::release_id() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Assignment.id) - if (!CheckHasBit(_impl_._has_bits_[0], 0x00000001U)) { - return nullptr; - } - ClearHasBit(_impl_._has_bits_[0], 0x00000001U); - auto* released = _impl_.id_.Release(); - if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { - _impl_.id_.Set("", GetArena()); - } - return released; -} -inline void Assignment::set_allocated_id(::std::string* PROTOBUF_NULLABLE value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - if (value != nullptr) { - SetHasBit(_impl_._has_bits_[0], 0x00000001U); - } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000001U); - } - _impl_.id_.SetAllocated(value, GetArena()); - if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.id_.IsDefault()) { - _impl_.id_.Set("", GetArena()); - } - // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Assignment.id) -} - // string experiment_id = 2 [json_name = "experimentId", (.buf.validate.field) = { inline void Assignment::clear_experiment_id() { ::google::protobuf::internal::TSanWrite(&_impl_); _impl_.experiment_id_.ClearToEmpty(); - ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); } inline const ::std::string& Assignment::experiment_id() const ABSL_ATTRIBUTE_LIFETIME_BOUND { @@ -4678,13 +4649,13 @@ inline const ::std::string& Assignment::experiment_id() const template PROTOBUF_ALWAYS_INLINE void Assignment::set_experiment_id(Arg_&& arg, Args_... args) { ::google::protobuf::internal::TSanWrite(&_impl_); - SetHasBit(_impl_._has_bits_[0], 0x00000002U); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); _impl_.experiment_id_.Set(static_cast(arg), args..., GetArena()); // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Assignment.experiment_id) } inline ::std::string* PROTOBUF_NONNULL Assignment::mutable_experiment_id() ABSL_ATTRIBUTE_LIFETIME_BOUND { - SetHasBit(_impl_._has_bits_[0], 0x00000002U); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); ::std::string* _s = _internal_mutable_experiment_id(); // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Assignment.experiment_id) return _s; @@ -4704,10 +4675,10 @@ inline ::std::string* PROTOBUF_NONNULL Assignment::_internal_mutable_experiment_ inline ::std::string* PROTOBUF_NULLABLE Assignment::release_experiment_id() { ::google::protobuf::internal::TSanWrite(&_impl_); // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Assignment.experiment_id) - if (!CheckHasBit(_impl_._has_bits_[0], 0x00000002U)) { + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000001U)) { return nullptr; } - ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); auto* released = _impl_.experiment_id_.Release(); if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { _impl_.experiment_id_.Set("", GetArena()); @@ -4717,9 +4688,9 @@ inline ::std::string* PROTOBUF_NULLABLE Assignment::release_experiment_id() { inline void Assignment::set_allocated_experiment_id(::std::string* PROTOBUF_NULLABLE value) { ::google::protobuf::internal::TSanWrite(&_impl_); if (value != nullptr) { - SetHasBit(_impl_._has_bits_[0], 0x00000002U); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); } _impl_.experiment_id_.SetAllocated(value, GetArena()); if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.experiment_id_.IsDefault()) { @@ -4730,14 +4701,14 @@ inline void Assignment::set_allocated_experiment_id(::std::string* PROTOBUF_NULL // .xtcp_treatment.v1.Treatment treatment = 3 [json_name = "treatment", (.buf.validate.field) = { inline bool Assignment::has_treatment() const { - bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000004U); + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000002U); PROTOBUF_ASSUME(!value || _impl_.treatment_ != nullptr); return value; } inline void Assignment::clear_treatment() { ::google::protobuf::internal::TSanWrite(&_impl_); if (_impl_.treatment_ != nullptr) _impl_.treatment_->Clear(); - ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); } inline const ::xtcp_treatment::v1::Treatment& Assignment::_internal_treatment() const { ::google::protobuf::internal::TSanRead(&_impl_); @@ -4756,16 +4727,16 @@ inline void Assignment::unsafe_arena_set_allocated_treatment( } _impl_.treatment_ = reinterpret_cast<::xtcp_treatment::v1::Treatment*>(value); if (value != nullptr) { - SetHasBit(_impl_._has_bits_[0], 0x00000004U); + SetHasBit(_impl_._has_bits_[0], 0x00000002U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); } // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.Assignment.treatment) } inline ::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE Assignment::release_treatment() { ::google::protobuf::internal::TSanWrite(&_impl_); - ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); ::xtcp_treatment::v1::Treatment* released = _impl_.treatment_; _impl_.treatment_ = nullptr; if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { @@ -4785,7 +4756,7 @@ inline ::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE Assignment::unsafe_are ::google::protobuf::internal::TSanWrite(&_impl_); // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Assignment.treatment) - ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); ::xtcp_treatment::v1::Treatment* temp = _impl_.treatment_; _impl_.treatment_ = nullptr; return temp; @@ -4800,7 +4771,7 @@ inline ::xtcp_treatment::v1::Treatment* PROTOBUF_NONNULL Assignment::_internal_m } inline ::xtcp_treatment::v1::Treatment* PROTOBUF_NONNULL Assignment::mutable_treatment() ABSL_ATTRIBUTE_LIFETIME_BOUND { - SetHasBit(_impl_._has_bits_[0], 0x00000004U); + SetHasBit(_impl_._has_bits_[0], 0x00000002U); ::xtcp_treatment::v1::Treatment* _msg = _internal_mutable_treatment(); // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Assignment.treatment) return _msg; @@ -4817,9 +4788,9 @@ inline void Assignment::set_allocated_treatment(::xtcp_treatment::v1::Treatment* if (message_arena != submessage_arena) { value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); } - SetHasBit(_impl_._has_bits_[0], 0x00000004U); + SetHasBit(_impl_._has_bits_[0], 0x00000002U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); } _impl_.treatment_ = reinterpret_cast<::xtcp_treatment::v1::Treatment*>(value); @@ -4830,7 +4801,7 @@ inline void Assignment::set_allocated_treatment(::xtcp_treatment::v1::Treatment* inline void Assignment::clear_mode() { ::google::protobuf::internal::TSanWrite(&_impl_); _impl_.mode_ = 0; - ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); } inline ::xtcp_treatment::v1::AssignmentMode Assignment::mode() const { // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Assignment.mode) @@ -4838,7 +4809,7 @@ inline ::xtcp_treatment::v1::AssignmentMode Assignment::mode() const { } inline void Assignment::set_mode(::xtcp_treatment::v1::AssignmentMode value) { _internal_set_mode(value); - SetHasBit(_impl_._has_bits_[0], 0x00000040U); + SetHasBit(_impl_._has_bits_[0], 0x00000020U); // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Assignment.mode) } inline ::xtcp_treatment::v1::AssignmentMode Assignment::_internal_mode() const { @@ -4852,7 +4823,7 @@ inline void Assignment::_internal_set_mode(::xtcp_treatment::v1::AssignmentMode // .google.protobuf.Timestamp lease_expires_at = 5 [json_name = "leaseExpiresAt"]; inline bool Assignment::has_lease_expires_at() const { - bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000008U); + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000004U); PROTOBUF_ASSUME(!value || _impl_.lease_expires_at_ != nullptr); return value; } @@ -4873,16 +4844,16 @@ inline void Assignment::unsafe_arena_set_allocated_lease_expires_at( } _impl_.lease_expires_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); if (value != nullptr) { - SetHasBit(_impl_._has_bits_[0], 0x00000008U); + SetHasBit(_impl_._has_bits_[0], 0x00000004U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); } // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.Assignment.lease_expires_at) } inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Assignment::release_lease_expires_at() { ::google::protobuf::internal::TSanWrite(&_impl_); - ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); ::google::protobuf::Timestamp* released = _impl_.lease_expires_at_; _impl_.lease_expires_at_ = nullptr; if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { @@ -4902,7 +4873,7 @@ inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Assignment::unsafe_arena ::google::protobuf::internal::TSanWrite(&_impl_); // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Assignment.lease_expires_at) - ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); ::google::protobuf::Timestamp* temp = _impl_.lease_expires_at_; _impl_.lease_expires_at_ = nullptr; return temp; @@ -4917,7 +4888,7 @@ inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Assignment::_internal_mut } inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Assignment::mutable_lease_expires_at() ABSL_ATTRIBUTE_LIFETIME_BOUND { - SetHasBit(_impl_._has_bits_[0], 0x00000008U); + SetHasBit(_impl_._has_bits_[0], 0x00000004U); ::google::protobuf::Timestamp* _msg = _internal_mutable_lease_expires_at(); // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Assignment.lease_expires_at) return _msg; @@ -4934,9 +4905,9 @@ inline void Assignment::set_allocated_lease_expires_at(::google::protobuf::Times if (message_arena != submessage_arena) { value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); } - SetHasBit(_impl_._has_bits_[0], 0x00000008U); + SetHasBit(_impl_._has_bits_[0], 0x00000004U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); } _impl_.lease_expires_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); @@ -4945,14 +4916,14 @@ inline void Assignment::set_allocated_lease_expires_at(::google::protobuf::Times // .xtcp_treatment.v1.Treatment predecessor = 6 [json_name = "predecessor"]; inline bool Assignment::has_predecessor() const { - bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000010U); + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000008U); PROTOBUF_ASSUME(!value || _impl_.predecessor_ != nullptr); return value; } inline void Assignment::clear_predecessor() { ::google::protobuf::internal::TSanWrite(&_impl_); if (_impl_.predecessor_ != nullptr) _impl_.predecessor_->Clear(); - ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); } inline const ::xtcp_treatment::v1::Treatment& Assignment::_internal_predecessor() const { ::google::protobuf::internal::TSanRead(&_impl_); @@ -4971,16 +4942,16 @@ inline void Assignment::unsafe_arena_set_allocated_predecessor( } _impl_.predecessor_ = reinterpret_cast<::xtcp_treatment::v1::Treatment*>(value); if (value != nullptr) { - SetHasBit(_impl_._has_bits_[0], 0x00000010U); + SetHasBit(_impl_._has_bits_[0], 0x00000008U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); } // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.Assignment.predecessor) } inline ::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE Assignment::release_predecessor() { ::google::protobuf::internal::TSanWrite(&_impl_); - ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); ::xtcp_treatment::v1::Treatment* released = _impl_.predecessor_; _impl_.predecessor_ = nullptr; if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { @@ -5000,7 +4971,7 @@ inline ::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE Assignment::unsafe_are ::google::protobuf::internal::TSanWrite(&_impl_); // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Assignment.predecessor) - ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); ::xtcp_treatment::v1::Treatment* temp = _impl_.predecessor_; _impl_.predecessor_ = nullptr; return temp; @@ -5015,7 +4986,7 @@ inline ::xtcp_treatment::v1::Treatment* PROTOBUF_NONNULL Assignment::_internal_m } inline ::xtcp_treatment::v1::Treatment* PROTOBUF_NONNULL Assignment::mutable_predecessor() ABSL_ATTRIBUTE_LIFETIME_BOUND { - SetHasBit(_impl_._has_bits_[0], 0x00000010U); + SetHasBit(_impl_._has_bits_[0], 0x00000008U); ::xtcp_treatment::v1::Treatment* _msg = _internal_mutable_predecessor(); // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Assignment.predecessor) return _msg; @@ -5032,9 +5003,9 @@ inline void Assignment::set_allocated_predecessor(::xtcp_treatment::v1::Treatmen if (message_arena != submessage_arena) { value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); } - SetHasBit(_impl_._has_bits_[0], 0x00000010U); + SetHasBit(_impl_._has_bits_[0], 0x00000008U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); } _impl_.predecessor_ = reinterpret_cast<::xtcp_treatment::v1::Treatment*>(value); @@ -5045,7 +5016,7 @@ inline void Assignment::set_allocated_predecessor(::xtcp_treatment::v1::Treatmen inline void Assignment::clear_generation() { ::google::protobuf::internal::TSanWrite(&_impl_); _impl_.generation_ = ::uint64_t{0u}; - ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); } inline ::uint64_t Assignment::generation() const { // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Assignment.generation) @@ -5053,7 +5024,7 @@ inline ::uint64_t Assignment::generation() const { } inline void Assignment::set_generation(::uint64_t value) { _internal_set_generation(value); - SetHasBit(_impl_._has_bits_[0], 0x00000020U); + SetHasBit(_impl_._has_bits_[0], 0x00000010U); // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Assignment.generation) } inline ::uint64_t Assignment::_internal_generation() const { @@ -8015,7 +7986,7 @@ inline void ExperimentPolicy::set_allocated_maximum_evidence_age(::google::proto inline void Phase::clear_target_hosts() { ::google::protobuf::internal::TSanWrite(&_impl_); _impl_.target_hosts_ = 0u; - ClearHasBit(_impl_._has_bits_[0], 0x00000080U); + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); } inline ::uint32_t Phase::target_hosts() const { // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Phase.target_hosts) @@ -8023,7 +7994,7 @@ inline ::uint32_t Phase::target_hosts() const { } inline void Phase::set_target_hosts(::uint32_t value) { _internal_set_target_hosts(value); - SetHasBit(_impl_._has_bits_[0], 0x00000080U); + SetHasBit(_impl_._has_bits_[0], 0x00000040U); // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Phase.target_hosts) } inline ::uint32_t Phase::_internal_target_hosts() const { @@ -8037,7 +8008,7 @@ inline void Phase::_internal_set_target_hosts(::uint32_t value) { // .google.protobuf.Timestamp issued_at = 2 [json_name = "issuedAt"]; inline bool Phase::has_issued_at() const { - bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000008U); + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000004U); PROTOBUF_ASSUME(!value || _impl_.issued_at_ != nullptr); return value; } @@ -8058,16 +8029,16 @@ inline void Phase::unsafe_arena_set_allocated_issued_at( } _impl_.issued_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); if (value != nullptr) { - SetHasBit(_impl_._has_bits_[0], 0x00000008U); + SetHasBit(_impl_._has_bits_[0], 0x00000004U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); } // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.Phase.issued_at) } inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Phase::release_issued_at() { ::google::protobuf::internal::TSanWrite(&_impl_); - ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); ::google::protobuf::Timestamp* released = _impl_.issued_at_; _impl_.issued_at_ = nullptr; if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { @@ -8087,7 +8058,7 @@ inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Phase::unsafe_arena_rele ::google::protobuf::internal::TSanWrite(&_impl_); // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Phase.issued_at) - ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); ::google::protobuf::Timestamp* temp = _impl_.issued_at_; _impl_.issued_at_ = nullptr; return temp; @@ -8102,7 +8073,7 @@ inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Phase::_internal_mutable_ } inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Phase::mutable_issued_at() ABSL_ATTRIBUTE_LIFETIME_BOUND { - SetHasBit(_impl_._has_bits_[0], 0x00000008U); + SetHasBit(_impl_._has_bits_[0], 0x00000004U); ::google::protobuf::Timestamp* _msg = _internal_mutable_issued_at(); // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Phase.issued_at) return _msg; @@ -8119,9 +8090,9 @@ inline void Phase::set_allocated_issued_at(::google::protobuf::Timestamp* PROTOB if (message_arena != submessage_arena) { value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); } - SetHasBit(_impl_._has_bits_[0], 0x00000008U); + SetHasBit(_impl_._has_bits_[0], 0x00000004U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); } _impl_.issued_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); @@ -8130,7 +8101,7 @@ inline void Phase::set_allocated_issued_at(::google::protobuf::Timestamp* PROTOB // .google.protobuf.Timestamp observation_started_at = 3 [json_name = "observationStartedAt"]; inline bool Phase::has_observation_started_at() const { - bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000010U); + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000008U); PROTOBUF_ASSUME(!value || _impl_.observation_started_at_ != nullptr); return value; } @@ -8151,16 +8122,16 @@ inline void Phase::unsafe_arena_set_allocated_observation_started_at( } _impl_.observation_started_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); if (value != nullptr) { - SetHasBit(_impl_._has_bits_[0], 0x00000010U); + SetHasBit(_impl_._has_bits_[0], 0x00000008U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); } // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.Phase.observation_started_at) } inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Phase::release_observation_started_at() { ::google::protobuf::internal::TSanWrite(&_impl_); - ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); ::google::protobuf::Timestamp* released = _impl_.observation_started_at_; _impl_.observation_started_at_ = nullptr; if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { @@ -8180,7 +8151,7 @@ inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Phase::unsafe_arena_rele ::google::protobuf::internal::TSanWrite(&_impl_); // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Phase.observation_started_at) - ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); ::google::protobuf::Timestamp* temp = _impl_.observation_started_at_; _impl_.observation_started_at_ = nullptr; return temp; @@ -8195,7 +8166,7 @@ inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Phase::_internal_mutable_ } inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Phase::mutable_observation_started_at() ABSL_ATTRIBUTE_LIFETIME_BOUND { - SetHasBit(_impl_._has_bits_[0], 0x00000010U); + SetHasBit(_impl_._has_bits_[0], 0x00000008U); ::google::protobuf::Timestamp* _msg = _internal_mutable_observation_started_at(); // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Phase.observation_started_at) return _msg; @@ -8212,9 +8183,9 @@ inline void Phase::set_allocated_observation_started_at(::google::protobuf::Time if (message_arena != submessage_arena) { value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); } - SetHasBit(_impl_._has_bits_[0], 0x00000010U); + SetHasBit(_impl_._has_bits_[0], 0x00000008U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); } _impl_.observation_started_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); @@ -8223,7 +8194,7 @@ inline void Phase::set_allocated_observation_started_at(::google::protobuf::Time // .google.protobuf.Timestamp evaluated_at = 4 [json_name = "evaluatedAt"]; inline bool Phase::has_evaluated_at() const { - bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000020U); + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000010U); PROTOBUF_ASSUME(!value || _impl_.evaluated_at_ != nullptr); return value; } @@ -8244,16 +8215,16 @@ inline void Phase::unsafe_arena_set_allocated_evaluated_at( } _impl_.evaluated_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); if (value != nullptr) { - SetHasBit(_impl_._has_bits_[0], 0x00000020U); + SetHasBit(_impl_._has_bits_[0], 0x00000010U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); } // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.Phase.evaluated_at) } inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Phase::release_evaluated_at() { ::google::protobuf::internal::TSanWrite(&_impl_); - ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); ::google::protobuf::Timestamp* released = _impl_.evaluated_at_; _impl_.evaluated_at_ = nullptr; if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { @@ -8273,7 +8244,7 @@ inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Phase::unsafe_arena_rele ::google::protobuf::internal::TSanWrite(&_impl_); // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Phase.evaluated_at) - ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); ::google::protobuf::Timestamp* temp = _impl_.evaluated_at_; _impl_.evaluated_at_ = nullptr; return temp; @@ -8288,7 +8259,7 @@ inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Phase::_internal_mutable_ } inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Phase::mutable_evaluated_at() ABSL_ATTRIBUTE_LIFETIME_BOUND { - SetHasBit(_impl_._has_bits_[0], 0x00000020U); + SetHasBit(_impl_._has_bits_[0], 0x00000010U); ::google::protobuf::Timestamp* _msg = _internal_mutable_evaluated_at(); // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Phase.evaluated_at) return _msg; @@ -8305,9 +8276,9 @@ inline void Phase::set_allocated_evaluated_at(::google::protobuf::Timestamp* PRO if (message_arena != submessage_arena) { value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); } - SetHasBit(_impl_._has_bits_[0], 0x00000020U); + SetHasBit(_impl_._has_bits_[0], 0x00000010U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); } _impl_.evaluated_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); @@ -8316,14 +8287,14 @@ inline void Phase::set_allocated_evaluated_at(::google::protobuf::Timestamp* PRO // .xtcp_treatment.v1.Evidence evidence = 5 [json_name = "evidence"]; inline bool Phase::has_evidence() const { - bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000040U); + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000020U); PROTOBUF_ASSUME(!value || _impl_.evidence_ != nullptr); return value; } inline void Phase::clear_evidence() { ::google::protobuf::internal::TSanWrite(&_impl_); if (_impl_.evidence_ != nullptr) _impl_.evidence_->Clear(); - ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); } inline const ::xtcp_treatment::v1::Evidence& Phase::_internal_evidence() const { ::google::protobuf::internal::TSanRead(&_impl_); @@ -8342,16 +8313,16 @@ inline void Phase::unsafe_arena_set_allocated_evidence( } _impl_.evidence_ = reinterpret_cast<::xtcp_treatment::v1::Evidence*>(value); if (value != nullptr) { - SetHasBit(_impl_._has_bits_[0], 0x00000040U); + SetHasBit(_impl_._has_bits_[0], 0x00000020U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); } // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.Phase.evidence) } inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE Phase::release_evidence() { ::google::protobuf::internal::TSanWrite(&_impl_); - ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); ::xtcp_treatment::v1::Evidence* released = _impl_.evidence_; _impl_.evidence_ = nullptr; if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { @@ -8371,7 +8342,7 @@ inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE Phase::unsafe_arena_rel ::google::protobuf::internal::TSanWrite(&_impl_); // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Phase.evidence) - ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); ::xtcp_treatment::v1::Evidence* temp = _impl_.evidence_; _impl_.evidence_ = nullptr; return temp; @@ -8386,7 +8357,7 @@ inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NONNULL Phase::_internal_mutable } inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NONNULL Phase::mutable_evidence() ABSL_ATTRIBUTE_LIFETIME_BOUND { - SetHasBit(_impl_._has_bits_[0], 0x00000040U); + SetHasBit(_impl_._has_bits_[0], 0x00000020U); ::xtcp_treatment::v1::Evidence* _msg = _internal_mutable_evidence(); // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Phase.evidence) return _msg; @@ -8403,84 +8374,44 @@ inline void Phase::set_allocated_evidence(::xtcp_treatment::v1::Evidence* PROTOB if (message_arena != submessage_arena) { value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); } - SetHasBit(_impl_._has_bits_[0], 0x00000040U); + SetHasBit(_impl_._has_bits_[0], 0x00000020U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); } _impl_.evidence_ = reinterpret_cast<::xtcp_treatment::v1::Evidence*>(value); // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Phase.evidence) } -// string outcome = 6 [json_name = "outcome", (.buf.validate.field) = { +// .xtcp_treatment.v1.Outcome outcome = 6 [json_name = "outcome", (.buf.validate.field) = { inline void Phase::clear_outcome() { ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.outcome_.ClearToEmpty(); - ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + _impl_.outcome_ = 0; + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); } -inline const ::std::string& Phase::outcome() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { +inline ::xtcp_treatment::v1::Outcome Phase::outcome() const { // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Phase.outcome) return _internal_outcome(); } -template -PROTOBUF_ALWAYS_INLINE void Phase::set_outcome(Arg_&& arg, Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - SetHasBit(_impl_._has_bits_[0], 0x00000001U); - _impl_.outcome_.Set(static_cast(arg), args..., GetArena()); +inline void Phase::set_outcome(::xtcp_treatment::v1::Outcome value) { + _internal_set_outcome(value); + SetHasBit(_impl_._has_bits_[0], 0x00000080U); // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Phase.outcome) } -inline ::std::string* PROTOBUF_NONNULL Phase::mutable_outcome() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - SetHasBit(_impl_._has_bits_[0], 0x00000001U); - ::std::string* _s = _internal_mutable_outcome(); - // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Phase.outcome) - return _s; -} -inline const ::std::string& Phase::_internal_outcome() const { +inline ::xtcp_treatment::v1::Outcome Phase::_internal_outcome() const { ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.outcome_.Get(); -} -inline void Phase::_internal_set_outcome(const ::std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.outcome_.Set(value, GetArena()); + return static_cast<::xtcp_treatment::v1::Outcome>(_impl_.outcome_); } -inline ::std::string* PROTOBUF_NONNULL Phase::_internal_mutable_outcome() { +inline void Phase::_internal_set_outcome(::xtcp_treatment::v1::Outcome value) { ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.outcome_.Mutable( GetArena()); -} -inline ::std::string* PROTOBUF_NULLABLE Phase::release_outcome() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Phase.outcome) - if (!CheckHasBit(_impl_._has_bits_[0], 0x00000001U)) { - return nullptr; - } - ClearHasBit(_impl_._has_bits_[0], 0x00000001U); - auto* released = _impl_.outcome_.Release(); - if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { - _impl_.outcome_.Set("", GetArena()); - } - return released; -} -inline void Phase::set_allocated_outcome(::std::string* PROTOBUF_NULLABLE value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - if (value != nullptr) { - SetHasBit(_impl_._has_bits_[0], 0x00000001U); - } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000001U); - } - _impl_.outcome_.SetAllocated(value, GetArena()); - if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.outcome_.IsDefault()) { - _impl_.outcome_.Set("", GetArena()); - } - // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Phase.outcome) + _impl_.outcome_ = value; } // string reviewer = 7 [json_name = "reviewer", (.buf.validate.field) = { inline void Phase::clear_reviewer() { ::google::protobuf::internal::TSanWrite(&_impl_); _impl_.reviewer_.ClearToEmpty(); - ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); } inline const ::std::string& Phase::reviewer() const ABSL_ATTRIBUTE_LIFETIME_BOUND { @@ -8490,13 +8421,13 @@ inline const ::std::string& Phase::reviewer() const template PROTOBUF_ALWAYS_INLINE void Phase::set_reviewer(Arg_&& arg, Args_... args) { ::google::protobuf::internal::TSanWrite(&_impl_); - SetHasBit(_impl_._has_bits_[0], 0x00000002U); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); _impl_.reviewer_.Set(static_cast(arg), args..., GetArena()); // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Phase.reviewer) } inline ::std::string* PROTOBUF_NONNULL Phase::mutable_reviewer() ABSL_ATTRIBUTE_LIFETIME_BOUND { - SetHasBit(_impl_._has_bits_[0], 0x00000002U); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); ::std::string* _s = _internal_mutable_reviewer(); // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Phase.reviewer) return _s; @@ -8516,10 +8447,10 @@ inline ::std::string* PROTOBUF_NONNULL Phase::_internal_mutable_reviewer() { inline ::std::string* PROTOBUF_NULLABLE Phase::release_reviewer() { ::google::protobuf::internal::TSanWrite(&_impl_); // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Phase.reviewer) - if (!CheckHasBit(_impl_._has_bits_[0], 0x00000002U)) { + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000001U)) { return nullptr; } - ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); auto* released = _impl_.reviewer_.Release(); if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { _impl_.reviewer_.Set("", GetArena()); @@ -8529,9 +8460,9 @@ inline ::std::string* PROTOBUF_NULLABLE Phase::release_reviewer() { inline void Phase::set_allocated_reviewer(::std::string* PROTOBUF_NULLABLE value) { ::google::protobuf::internal::TSanWrite(&_impl_); if (value != nullptr) { - SetHasBit(_impl_._has_bits_[0], 0x00000002U); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); } _impl_.reviewer_.SetAllocated(value, GetArena()); if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.reviewer_.IsDefault()) { @@ -8544,7 +8475,7 @@ inline void Phase::set_allocated_reviewer(::std::string* PROTOBUF_NULLABLE value inline void Phase::clear_reason() { ::google::protobuf::internal::TSanWrite(&_impl_); _impl_.reason_.ClearToEmpty(); - ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); } inline const ::std::string& Phase::reason() const ABSL_ATTRIBUTE_LIFETIME_BOUND { @@ -8554,13 +8485,13 @@ inline const ::std::string& Phase::reason() const template PROTOBUF_ALWAYS_INLINE void Phase::set_reason(Arg_&& arg, Args_... args) { ::google::protobuf::internal::TSanWrite(&_impl_); - SetHasBit(_impl_._has_bits_[0], 0x00000004U); + SetHasBit(_impl_._has_bits_[0], 0x00000002U); _impl_.reason_.Set(static_cast(arg), args..., GetArena()); // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Phase.reason) } inline ::std::string* PROTOBUF_NONNULL Phase::mutable_reason() ABSL_ATTRIBUTE_LIFETIME_BOUND { - SetHasBit(_impl_._has_bits_[0], 0x00000004U); + SetHasBit(_impl_._has_bits_[0], 0x00000002U); ::std::string* _s = _internal_mutable_reason(); // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Phase.reason) return _s; @@ -8580,10 +8511,10 @@ inline ::std::string* PROTOBUF_NONNULL Phase::_internal_mutable_reason() { inline ::std::string* PROTOBUF_NULLABLE Phase::release_reason() { ::google::protobuf::internal::TSanWrite(&_impl_); // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Phase.reason) - if (!CheckHasBit(_impl_._has_bits_[0], 0x00000004U)) { + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000002U)) { return nullptr; } - ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); auto* released = _impl_.reason_.Release(); if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { _impl_.reason_.Set("", GetArena()); @@ -8593,9 +8524,9 @@ inline ::std::string* PROTOBUF_NULLABLE Phase::release_reason() { inline void Phase::set_allocated_reason(::std::string* PROTOBUF_NULLABLE value) { ::google::protobuf::internal::TSanWrite(&_impl_); if (value != nullptr) { - SetHasBit(_impl_._has_bits_[0], 0x00000004U); + SetHasBit(_impl_._has_bits_[0], 0x00000002U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); } _impl_.reason_.SetAllocated(value, GetArena()); if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.reason_.IsDefault()) { @@ -8802,14 +8733,14 @@ inline void Experiment::set_allocated_hypothesis(::std::string* PROTOBUF_NULLABL // .xtcp_treatment.v1.Treatment treatment = 4 [json_name = "treatment", (.buf.validate.field) = { inline bool Experiment::has_treatment() const { - bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000040U); + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000020U); PROTOBUF_ASSUME(!value || _impl_.treatment_ != nullptr); return value; } inline void Experiment::clear_treatment() { ::google::protobuf::internal::TSanWrite(&_impl_); if (_impl_.treatment_ != nullptr) _impl_.treatment_->Clear(); - ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); } inline const ::xtcp_treatment::v1::Treatment& Experiment::_internal_treatment() const { ::google::protobuf::internal::TSanRead(&_impl_); @@ -8828,16 +8759,16 @@ inline void Experiment::unsafe_arena_set_allocated_treatment( } _impl_.treatment_ = reinterpret_cast<::xtcp_treatment::v1::Treatment*>(value); if (value != nullptr) { - SetHasBit(_impl_._has_bits_[0], 0x00000040U); + SetHasBit(_impl_._has_bits_[0], 0x00000020U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); } // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.Experiment.treatment) } inline ::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE Experiment::release_treatment() { ::google::protobuf::internal::TSanWrite(&_impl_); - ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); ::xtcp_treatment::v1::Treatment* released = _impl_.treatment_; _impl_.treatment_ = nullptr; if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { @@ -8857,7 +8788,7 @@ inline ::xtcp_treatment::v1::Treatment* PROTOBUF_NULLABLE Experiment::unsafe_are ::google::protobuf::internal::TSanWrite(&_impl_); // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Experiment.treatment) - ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); ::xtcp_treatment::v1::Treatment* temp = _impl_.treatment_; _impl_.treatment_ = nullptr; return temp; @@ -8872,7 +8803,7 @@ inline ::xtcp_treatment::v1::Treatment* PROTOBUF_NONNULL Experiment::_internal_m } inline ::xtcp_treatment::v1::Treatment* PROTOBUF_NONNULL Experiment::mutable_treatment() ABSL_ATTRIBUTE_LIFETIME_BOUND { - SetHasBit(_impl_._has_bits_[0], 0x00000040U); + SetHasBit(_impl_._has_bits_[0], 0x00000020U); ::xtcp_treatment::v1::Treatment* _msg = _internal_mutable_treatment(); // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Experiment.treatment) return _msg; @@ -8889,9 +8820,9 @@ inline void Experiment::set_allocated_treatment(::xtcp_treatment::v1::Treatment* if (message_arena != submessage_arena) { value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); } - SetHasBit(_impl_._has_bits_[0], 0x00000040U); + SetHasBit(_impl_._has_bits_[0], 0x00000020U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); } _impl_.treatment_ = reinterpret_cast<::xtcp_treatment::v1::Treatment*>(value); @@ -8900,14 +8831,14 @@ inline void Experiment::set_allocated_treatment(::xtcp_treatment::v1::Treatment* // .xtcp_treatment.v1.ExperimentPolicy policy = 5 [json_name = "policy", (.buf.validate.field) = { inline bool Experiment::has_policy() const { - bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000080U); + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000040U); PROTOBUF_ASSUME(!value || _impl_.policy_ != nullptr); return value; } inline void Experiment::clear_policy() { ::google::protobuf::internal::TSanWrite(&_impl_); if (_impl_.policy_ != nullptr) _impl_.policy_->Clear(); - ClearHasBit(_impl_._has_bits_[0], 0x00000080U); + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); } inline const ::xtcp_treatment::v1::ExperimentPolicy& Experiment::_internal_policy() const { ::google::protobuf::internal::TSanRead(&_impl_); @@ -8926,16 +8857,16 @@ inline void Experiment::unsafe_arena_set_allocated_policy( } _impl_.policy_ = reinterpret_cast<::xtcp_treatment::v1::ExperimentPolicy*>(value); if (value != nullptr) { - SetHasBit(_impl_._has_bits_[0], 0x00000080U); + SetHasBit(_impl_._has_bits_[0], 0x00000040U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000080U); + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); } // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.Experiment.policy) } inline ::xtcp_treatment::v1::ExperimentPolicy* PROTOBUF_NULLABLE Experiment::release_policy() { ::google::protobuf::internal::TSanWrite(&_impl_); - ClearHasBit(_impl_._has_bits_[0], 0x00000080U); + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); ::xtcp_treatment::v1::ExperimentPolicy* released = _impl_.policy_; _impl_.policy_ = nullptr; if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { @@ -8955,7 +8886,7 @@ inline ::xtcp_treatment::v1::ExperimentPolicy* PROTOBUF_NULLABLE Experiment::uns ::google::protobuf::internal::TSanWrite(&_impl_); // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Experiment.policy) - ClearHasBit(_impl_._has_bits_[0], 0x00000080U); + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); ::xtcp_treatment::v1::ExperimentPolicy* temp = _impl_.policy_; _impl_.policy_ = nullptr; return temp; @@ -8970,7 +8901,7 @@ inline ::xtcp_treatment::v1::ExperimentPolicy* PROTOBUF_NONNULL Experiment::_int } inline ::xtcp_treatment::v1::ExperimentPolicy* PROTOBUF_NONNULL Experiment::mutable_policy() ABSL_ATTRIBUTE_LIFETIME_BOUND { - SetHasBit(_impl_._has_bits_[0], 0x00000080U); + SetHasBit(_impl_._has_bits_[0], 0x00000040U); ::xtcp_treatment::v1::ExperimentPolicy* _msg = _internal_mutable_policy(); // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Experiment.policy) return _msg; @@ -8987,9 +8918,9 @@ inline void Experiment::set_allocated_policy(::xtcp_treatment::v1::ExperimentPol if (message_arena != submessage_arena) { value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); } - SetHasBit(_impl_._has_bits_[0], 0x00000080U); + SetHasBit(_impl_._has_bits_[0], 0x00000040U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000080U); + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); } _impl_.policy_ = reinterpret_cast<::xtcp_treatment::v1::ExperimentPolicy*>(value); @@ -8998,14 +8929,14 @@ inline void Experiment::set_allocated_policy(::xtcp_treatment::v1::ExperimentPol // .xtcp_treatment.v1.Evidence baseline = 6 [json_name = "baseline", (.buf.validate.field) = { inline bool Experiment::has_baseline() const { - bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000100U); + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000080U); PROTOBUF_ASSUME(!value || _impl_.baseline_ != nullptr); return value; } inline void Experiment::clear_baseline() { ::google::protobuf::internal::TSanWrite(&_impl_); if (_impl_.baseline_ != nullptr) _impl_.baseline_->Clear(); - ClearHasBit(_impl_._has_bits_[0], 0x00000100U); + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); } inline const ::xtcp_treatment::v1::Evidence& Experiment::_internal_baseline() const { ::google::protobuf::internal::TSanRead(&_impl_); @@ -9024,16 +8955,16 @@ inline void Experiment::unsafe_arena_set_allocated_baseline( } _impl_.baseline_ = reinterpret_cast<::xtcp_treatment::v1::Evidence*>(value); if (value != nullptr) { - SetHasBit(_impl_._has_bits_[0], 0x00000100U); + SetHasBit(_impl_._has_bits_[0], 0x00000080U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000100U); + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); } // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.Experiment.baseline) } inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE Experiment::release_baseline() { ::google::protobuf::internal::TSanWrite(&_impl_); - ClearHasBit(_impl_._has_bits_[0], 0x00000100U); + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); ::xtcp_treatment::v1::Evidence* released = _impl_.baseline_; _impl_.baseline_ = nullptr; if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { @@ -9053,7 +8984,7 @@ inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE Experiment::unsafe_aren ::google::protobuf::internal::TSanWrite(&_impl_); // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Experiment.baseline) - ClearHasBit(_impl_._has_bits_[0], 0x00000100U); + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); ::xtcp_treatment::v1::Evidence* temp = _impl_.baseline_; _impl_.baseline_ = nullptr; return temp; @@ -9068,7 +8999,7 @@ inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NONNULL Experiment::_internal_mu } inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NONNULL Experiment::mutable_baseline() ABSL_ATTRIBUTE_LIFETIME_BOUND { - SetHasBit(_impl_._has_bits_[0], 0x00000100U); + SetHasBit(_impl_._has_bits_[0], 0x00000080U); ::xtcp_treatment::v1::Evidence* _msg = _internal_mutable_baseline(); // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Experiment.baseline) return _msg; @@ -9085,9 +9016,9 @@ inline void Experiment::set_allocated_baseline(::xtcp_treatment::v1::Evidence* P if (message_arena != submessage_arena) { value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); } - SetHasBit(_impl_._has_bits_[0], 0x00000100U); + SetHasBit(_impl_._has_bits_[0], 0x00000080U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000100U); + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); } _impl_.baseline_ = reinterpret_cast<::xtcp_treatment::v1::Evidence*>(value); @@ -9225,7 +9156,7 @@ Experiment::_internal_mutable_phases() { inline void Experiment::clear_status() { ::google::protobuf::internal::TSanWrite(&_impl_); _impl_.status_ = 0; - ClearHasBit(_impl_._has_bits_[0], 0x00001000U); + ClearHasBit(_impl_._has_bits_[0], 0x00000800U); } inline ::xtcp_treatment::v1::ExperimentStatus Experiment::status() const { // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Experiment.status) @@ -9233,7 +9164,7 @@ inline ::xtcp_treatment::v1::ExperimentStatus Experiment::status() const { } inline void Experiment::set_status(::xtcp_treatment::v1::ExperimentStatus value) { _internal_set_status(value); - SetHasBit(_impl_._has_bits_[0], 0x00001000U); + SetHasBit(_impl_._has_bits_[0], 0x00000800U); // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Experiment.status) } inline ::xtcp_treatment::v1::ExperimentStatus Experiment::_internal_status() const { @@ -9249,7 +9180,7 @@ inline void Experiment::_internal_set_status(::xtcp_treatment::v1::ExperimentSta inline void Experiment::clear_revision() { ::google::protobuf::internal::TSanWrite(&_impl_); _impl_.revision_ = ::uint64_t{0u}; - ClearHasBit(_impl_._has_bits_[0], 0x00000800U); + ClearHasBit(_impl_._has_bits_[0], 0x00000400U); } inline ::uint64_t Experiment::revision() const { // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Experiment.revision) @@ -9257,7 +9188,7 @@ inline ::uint64_t Experiment::revision() const { } inline void Experiment::set_revision(::uint64_t value) { _internal_set_revision(value); - SetHasBit(_impl_._has_bits_[0], 0x00000800U); + SetHasBit(_impl_._has_bits_[0], 0x00000400U); // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Experiment.revision) } inline ::uint64_t Experiment::_internal_revision() const { @@ -9271,7 +9202,7 @@ inline void Experiment::_internal_set_revision(::uint64_t value) { // .google.protobuf.Timestamp proposed_at = 11 [json_name = "proposedAt"]; inline bool Experiment::has_proposed_at() const { - bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000200U); + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000100U); PROTOBUF_ASSUME(!value || _impl_.proposed_at_ != nullptr); return value; } @@ -9292,16 +9223,16 @@ inline void Experiment::unsafe_arena_set_allocated_proposed_at( } _impl_.proposed_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); if (value != nullptr) { - SetHasBit(_impl_._has_bits_[0], 0x00000200U); + SetHasBit(_impl_._has_bits_[0], 0x00000100U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000200U); + ClearHasBit(_impl_._has_bits_[0], 0x00000100U); } // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.Experiment.proposed_at) } inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Experiment::release_proposed_at() { ::google::protobuf::internal::TSanWrite(&_impl_); - ClearHasBit(_impl_._has_bits_[0], 0x00000200U); + ClearHasBit(_impl_._has_bits_[0], 0x00000100U); ::google::protobuf::Timestamp* released = _impl_.proposed_at_; _impl_.proposed_at_ = nullptr; if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { @@ -9321,7 +9252,7 @@ inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Experiment::unsafe_arena ::google::protobuf::internal::TSanWrite(&_impl_); // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Experiment.proposed_at) - ClearHasBit(_impl_._has_bits_[0], 0x00000200U); + ClearHasBit(_impl_._has_bits_[0], 0x00000100U); ::google::protobuf::Timestamp* temp = _impl_.proposed_at_; _impl_.proposed_at_ = nullptr; return temp; @@ -9336,7 +9267,7 @@ inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Experiment::_internal_mut } inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Experiment::mutable_proposed_at() ABSL_ATTRIBUTE_LIFETIME_BOUND { - SetHasBit(_impl_._has_bits_[0], 0x00000200U); + SetHasBit(_impl_._has_bits_[0], 0x00000100U); ::google::protobuf::Timestamp* _msg = _internal_mutable_proposed_at(); // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Experiment.proposed_at) return _msg; @@ -9353,9 +9284,9 @@ inline void Experiment::set_allocated_proposed_at(::google::protobuf::Timestamp* if (message_arena != submessage_arena) { value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); } - SetHasBit(_impl_._has_bits_[0], 0x00000200U); + SetHasBit(_impl_._has_bits_[0], 0x00000100U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000200U); + ClearHasBit(_impl_._has_bits_[0], 0x00000100U); } _impl_.proposed_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); @@ -9364,14 +9295,14 @@ inline void Experiment::set_allocated_proposed_at(::google::protobuf::Timestamp* // .xtcp_treatment.v1.Evidence latest_evidence = 12 [json_name = "latestEvidence"]; inline bool Experiment::has_latest_evidence() const { - bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000400U); + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000200U); PROTOBUF_ASSUME(!value || _impl_.latest_evidence_ != nullptr); return value; } inline void Experiment::clear_latest_evidence() { ::google::protobuf::internal::TSanWrite(&_impl_); if (_impl_.latest_evidence_ != nullptr) _impl_.latest_evidence_->Clear(); - ClearHasBit(_impl_._has_bits_[0], 0x00000400U); + ClearHasBit(_impl_._has_bits_[0], 0x00000200U); } inline const ::xtcp_treatment::v1::Evidence& Experiment::_internal_latest_evidence() const { ::google::protobuf::internal::TSanRead(&_impl_); @@ -9390,16 +9321,16 @@ inline void Experiment::unsafe_arena_set_allocated_latest_evidence( } _impl_.latest_evidence_ = reinterpret_cast<::xtcp_treatment::v1::Evidence*>(value); if (value != nullptr) { - SetHasBit(_impl_._has_bits_[0], 0x00000400U); + SetHasBit(_impl_._has_bits_[0], 0x00000200U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000400U); + ClearHasBit(_impl_._has_bits_[0], 0x00000200U); } // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.Experiment.latest_evidence) } inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE Experiment::release_latest_evidence() { ::google::protobuf::internal::TSanWrite(&_impl_); - ClearHasBit(_impl_._has_bits_[0], 0x00000400U); + ClearHasBit(_impl_._has_bits_[0], 0x00000200U); ::xtcp_treatment::v1::Evidence* released = _impl_.latest_evidence_; _impl_.latest_evidence_ = nullptr; if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { @@ -9419,7 +9350,7 @@ inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE Experiment::unsafe_aren ::google::protobuf::internal::TSanWrite(&_impl_); // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Experiment.latest_evidence) - ClearHasBit(_impl_._has_bits_[0], 0x00000400U); + ClearHasBit(_impl_._has_bits_[0], 0x00000200U); ::xtcp_treatment::v1::Evidence* temp = _impl_.latest_evidence_; _impl_.latest_evidence_ = nullptr; return temp; @@ -9434,7 +9365,7 @@ inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NONNULL Experiment::_internal_mu } inline ::xtcp_treatment::v1::Evidence* PROTOBUF_NONNULL Experiment::mutable_latest_evidence() ABSL_ATTRIBUTE_LIFETIME_BOUND { - SetHasBit(_impl_._has_bits_[0], 0x00000400U); + SetHasBit(_impl_._has_bits_[0], 0x00000200U); ::xtcp_treatment::v1::Evidence* _msg = _internal_mutable_latest_evidence(); // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Experiment.latest_evidence) return _msg; @@ -9451,77 +9382,37 @@ inline void Experiment::set_allocated_latest_evidence(::xtcp_treatment::v1::Evid if (message_arena != submessage_arena) { value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); } - SetHasBit(_impl_._has_bits_[0], 0x00000400U); + SetHasBit(_impl_._has_bits_[0], 0x00000200U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000400U); + ClearHasBit(_impl_._has_bits_[0], 0x00000200U); } _impl_.latest_evidence_ = reinterpret_cast<::xtcp_treatment::v1::Evidence*>(value); // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Experiment.latest_evidence) } -// string latest_outcome = 13 [json_name = "latestOutcome", (.buf.validate.field) = { +// .xtcp_treatment.v1.Outcome latest_outcome = 13 [json_name = "latestOutcome", (.buf.validate.field) = { inline void Experiment::clear_latest_outcome() { ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.latest_outcome_.ClearToEmpty(); - ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + _impl_.latest_outcome_ = 0; + ClearHasBit(_impl_._has_bits_[0], 0x00001000U); } -inline const ::std::string& Experiment::latest_outcome() const - ABSL_ATTRIBUTE_LIFETIME_BOUND { +inline ::xtcp_treatment::v1::Outcome Experiment::latest_outcome() const { // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Experiment.latest_outcome) return _internal_latest_outcome(); } -template -PROTOBUF_ALWAYS_INLINE void Experiment::set_latest_outcome(Arg_&& arg, Args_... args) { - ::google::protobuf::internal::TSanWrite(&_impl_); - SetHasBit(_impl_._has_bits_[0], 0x00000020U); - _impl_.latest_outcome_.Set(static_cast(arg), args..., GetArena()); +inline void Experiment::set_latest_outcome(::xtcp_treatment::v1::Outcome value) { + _internal_set_latest_outcome(value); + SetHasBit(_impl_._has_bits_[0], 0x00001000U); // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Experiment.latest_outcome) } -inline ::std::string* PROTOBUF_NONNULL Experiment::mutable_latest_outcome() - ABSL_ATTRIBUTE_LIFETIME_BOUND { - SetHasBit(_impl_._has_bits_[0], 0x00000020U); - ::std::string* _s = _internal_mutable_latest_outcome(); - // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Experiment.latest_outcome) - return _s; -} -inline const ::std::string& Experiment::_internal_latest_outcome() const { +inline ::xtcp_treatment::v1::Outcome Experiment::_internal_latest_outcome() const { ::google::protobuf::internal::TSanRead(&_impl_); - return _impl_.latest_outcome_.Get(); -} -inline void Experiment::_internal_set_latest_outcome(const ::std::string& value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - _impl_.latest_outcome_.Set(value, GetArena()); + return static_cast<::xtcp_treatment::v1::Outcome>(_impl_.latest_outcome_); } -inline ::std::string* PROTOBUF_NONNULL Experiment::_internal_mutable_latest_outcome() { +inline void Experiment::_internal_set_latest_outcome(::xtcp_treatment::v1::Outcome value) { ::google::protobuf::internal::TSanWrite(&_impl_); - return _impl_.latest_outcome_.Mutable( GetArena()); -} -inline ::std::string* PROTOBUF_NULLABLE Experiment::release_latest_outcome() { - ::google::protobuf::internal::TSanWrite(&_impl_); - // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Experiment.latest_outcome) - if (!CheckHasBit(_impl_._has_bits_[0], 0x00000020U)) { - return nullptr; - } - ClearHasBit(_impl_._has_bits_[0], 0x00000020U); - auto* released = _impl_.latest_outcome_.Release(); - if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { - _impl_.latest_outcome_.Set("", GetArena()); - } - return released; -} -inline void Experiment::set_allocated_latest_outcome(::std::string* PROTOBUF_NULLABLE value) { - ::google::protobuf::internal::TSanWrite(&_impl_); - if (value != nullptr) { - SetHasBit(_impl_._has_bits_[0], 0x00000020U); - } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000020U); - } - _impl_.latest_outcome_.SetAllocated(value, GetArena()); - if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.latest_outcome_.IsDefault()) { - _impl_.latest_outcome_.Set("", GetArena()); - } - // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Experiment.latest_outcome) + _impl_.latest_outcome_ = value; } #ifdef __GNUC__ @@ -9555,6 +9446,12 @@ inline const EnumDescriptor* PROTOBUF_NONNULL GetEnumDescriptor<::xtcp_treatment return ::xtcp_treatment::v1::ApplyStatus_descriptor(); } template <> +struct is_proto_enum<::xtcp_treatment::v1::Outcome> : std::true_type {}; +template <> +inline const EnumDescriptor* PROTOBUF_NONNULL GetEnumDescriptor<::xtcp_treatment::v1::Outcome>() { + return ::xtcp_treatment::v1::Outcome_descriptor(); +} +template <> struct is_proto_enum<::xtcp_treatment::v1::ExperimentStatus> : std::true_type {}; template <> inline const EnumDescriptor* PROTOBUF_NONNULL GetEnumDescriptor<::xtcp_treatment::v1::ExperimentStatus>() { diff --git a/gen/dart/xtcp_treatment/v1/operator.pb.dart b/gen/dart/xtcp_treatment/v1/operator.pb.dart index e8f0e5455..07ef060e8 100644 --- a/gen/dart/xtcp_treatment/v1/operator.pb.dart +++ b/gen/dart/xtcp_treatment/v1/operator.pb.dart @@ -232,7 +232,7 @@ class AnalyzeRequest extends $pb.GeneratedMessage { class AnalyzeResponse extends $pb.GeneratedMessage { factory AnalyzeResponse({ $1.Evidence? evidence, - $core.String? outcome, + $1.Outcome? outcome, }) { final result = AnalyzeResponse._(); if (evidence != null) result.evidence = evidence; @@ -256,7 +256,8 @@ class AnalyzeResponse extends $pb.GeneratedMessage { createEmptyInstance: AnalyzeResponse.$_createMessage) ..aOM<$1.Evidence>(1, _omitFieldNames ? '' : 'evidence', subBuilder: $1.Evidence.$_createMessage) - ..aOS(2, _omitFieldNames ? '' : 'outcome') + ..aE<$1.Outcome>(2, _omitFieldNames ? '' : 'outcome', + enumValues: $1.Outcome.values) ..hasRequiredFields = false; @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @@ -293,9 +294,9 @@ class AnalyzeResponse extends $pb.GeneratedMessage { $1.Evidence ensureEvidence() => $_ensure(0); @$pb.TagNumber(2) - $core.String get outcome => $_getSZ(1); + $1.Outcome get outcome => $_getN(1); @$pb.TagNumber(2) - set outcome($core.String value) => $_setString(1, value); + set outcome($1.Outcome value) => $_setField(2, value); @$pb.TagNumber(2) $core.bool hasOutcome() => $_has(1); @$pb.TagNumber(2) diff --git a/gen/dart/xtcp_treatment/v1/operator.pbjson.dart b/gen/dart/xtcp_treatment/v1/operator.pbjson.dart index 432ed42cf..9f8dda8b2 100644 --- a/gen/dart/xtcp_treatment/v1/operator.pbjson.dart +++ b/gen/dart/xtcp_treatment/v1/operator.pbjson.dart @@ -122,14 +122,22 @@ const AnalyzeResponse$json = { '6': '.xtcp_treatment.v1.Evidence', '10': 'evidence' }, - {'1': 'outcome', '3': 2, '4': 1, '5': 9, '10': 'outcome'}, + { + '1': 'outcome', + '3': 2, + '4': 1, + '5': 14, + '6': '.xtcp_treatment.v1.Outcome', + '10': 'outcome' + }, ], }; /// Descriptor for `AnalyzeResponse`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List analyzeResponseDescriptor = $convert.base64Decode( 'Cg9BbmFseXplUmVzcG9uc2USNwoIZXZpZGVuY2UYASABKAsyGy54dGNwX3RyZWF0bWVudC52MS' - '5FdmlkZW5jZVIIZXZpZGVuY2USGAoHb3V0Y29tZRgCIAEoCVIHb3V0Y29tZQ=='); + '5FdmlkZW5jZVIIZXZpZGVuY2USNAoHb3V0Y29tZRgCIAEoDjIaLnh0Y3BfdHJlYXRtZW50LnYx' + 'Lk91dGNvbWVSB291dGNvbWU='); @$core.Deprecated('Use reviewRequestDescriptor instead') const ReviewRequest$json = { diff --git a/gen/dart/xtcp_treatment/v1/treatment.pb.dart b/gen/dart/xtcp_treatment/v1/treatment.pb.dart index fd2fc0426..e8d92b984 100644 --- a/gen/dart/xtcp_treatment/v1/treatment.pb.dart +++ b/gen/dart/xtcp_treatment/v1/treatment.pb.dart @@ -275,7 +275,6 @@ class Treatment extends $pb.GeneratedMessage { class Assignment extends $pb.GeneratedMessage { factory Assignment({ - $core.String? id, $core.String? experimentId, Treatment? treatment, AssignmentMode? mode, @@ -284,7 +283,6 @@ class Assignment extends $pb.GeneratedMessage { $fixnum.Int64? generation, }) { final result = Assignment._(); - if (id != null) result.id = id; if (experimentId != null) result.experimentId = experimentId; if (treatment != null) result.treatment = treatment; if (mode != null) result.mode = mode; @@ -308,7 +306,6 @@ class Assignment extends $pb.GeneratedMessage { package: const $pb.PackageName(_omitMessageNames ? '' : 'xtcp_treatment.v1'), createEmptyInstance: Assignment.$_createMessage) - ..aOS(1, _omitFieldNames ? '' : 'id') ..aOS(2, _omitFieldNames ? '' : 'experimentId') ..aOM(3, _omitFieldNames ? '' : 'treatment', subBuilder: Treatment.$_createMessage) @@ -343,74 +340,65 @@ class Assignment extends $pb.GeneratedMessage { $pb.GeneratedMessage.$_defaultFor(Assignment.$_createMessage); static Assignment? _defaultInstance; - @$pb.TagNumber(1) - $core.String get id => $_getSZ(0); - @$pb.TagNumber(1) - set id($core.String value) => $_setString(0, value); - @$pb.TagNumber(1) - $core.bool hasId() => $_has(0); - @$pb.TagNumber(1) - void clearId() => $_clearField(1); - @$pb.TagNumber(2) - $core.String get experimentId => $_getSZ(1); + $core.String get experimentId => $_getSZ(0); @$pb.TagNumber(2) - set experimentId($core.String value) => $_setString(1, value); + set experimentId($core.String value) => $_setString(0, value); @$pb.TagNumber(2) - $core.bool hasExperimentId() => $_has(1); + $core.bool hasExperimentId() => $_has(0); @$pb.TagNumber(2) void clearExperimentId() => $_clearField(2); @$pb.TagNumber(3) - Treatment get treatment => $_getN(2); + Treatment get treatment => $_getN(1); @$pb.TagNumber(3) set treatment(Treatment value) => $_setField(3, value); @$pb.TagNumber(3) - $core.bool hasTreatment() => $_has(2); + $core.bool hasTreatment() => $_has(1); @$pb.TagNumber(3) void clearTreatment() => $_clearField(3); @$pb.TagNumber(3) - Treatment ensureTreatment() => $_ensure(2); + Treatment ensureTreatment() => $_ensure(1); @$pb.TagNumber(4) - AssignmentMode get mode => $_getN(3); + AssignmentMode get mode => $_getN(2); @$pb.TagNumber(4) set mode(AssignmentMode value) => $_setField(4, value); @$pb.TagNumber(4) - $core.bool hasMode() => $_has(3); + $core.bool hasMode() => $_has(2); @$pb.TagNumber(4) void clearMode() => $_clearField(4); @$pb.TagNumber(5) - $0.Timestamp get leaseExpiresAt => $_getN(4); + $0.Timestamp get leaseExpiresAt => $_getN(3); @$pb.TagNumber(5) set leaseExpiresAt($0.Timestamp value) => $_setField(5, value); @$pb.TagNumber(5) - $core.bool hasLeaseExpiresAt() => $_has(4); + $core.bool hasLeaseExpiresAt() => $_has(3); @$pb.TagNumber(5) void clearLeaseExpiresAt() => $_clearField(5); @$pb.TagNumber(5) - $0.Timestamp ensureLeaseExpiresAt() => $_ensure(4); + $0.Timestamp ensureLeaseExpiresAt() => $_ensure(3); /// Previous approved stable treatment, restored on trial expiry/rollback. @$pb.TagNumber(6) - Treatment get predecessor => $_getN(5); + Treatment get predecessor => $_getN(4); @$pb.TagNumber(6) set predecessor(Treatment value) => $_setField(6, value); @$pb.TagNumber(6) - $core.bool hasPredecessor() => $_has(5); + $core.bool hasPredecessor() => $_has(4); @$pb.TagNumber(6) void clearPredecessor() => $_clearField(6); @$pb.TagNumber(6) - Treatment ensurePredecessor() => $_ensure(5); + Treatment ensurePredecessor() => $_ensure(4); /// Increment only on an explicitly approved reactivation, not lease renewal. @$pb.TagNumber(7) - $fixnum.Int64 get generation => $_getI64(6); + $fixnum.Int64 get generation => $_getI64(5); @$pb.TagNumber(7) - set generation($fixnum.Int64 value) => $_setInt64(6, value); + set generation($fixnum.Int64 value) => $_setInt64(5, value); @$pb.TagNumber(7) - $core.bool hasGeneration() => $_has(6); + $core.bool hasGeneration() => $_has(5); @$pb.TagNumber(7) void clearGeneration() => $_clearField(7); } @@ -1424,7 +1412,7 @@ class Phase extends $pb.GeneratedMessage { $0.Timestamp? observationStartedAt, $0.Timestamp? evaluatedAt, Evidence? evidence, - $core.String? outcome, + Outcome? outcome, $core.String? reviewer, $core.String? reason, }) { @@ -1465,7 +1453,8 @@ class Phase extends $pb.GeneratedMessage { subBuilder: $0.Timestamp.$_createMessage) ..aOM(5, _omitFieldNames ? '' : 'evidence', subBuilder: Evidence.$_createMessage) - ..aOS(6, _omitFieldNames ? '' : 'outcome') + ..aE(6, _omitFieldNames ? '' : 'outcome', + enumValues: Outcome.values) ..aOS(7, _omitFieldNames ? '' : 'reviewer') ..aOS(8, _omitFieldNames ? '' : 'reason') ..hasRequiredFields = false; @@ -1544,9 +1533,9 @@ class Phase extends $pb.GeneratedMessage { Evidence ensureEvidence() => $_ensure(4); @$pb.TagNumber(6) - $core.String get outcome => $_getSZ(5); + Outcome get outcome => $_getN(5); @$pb.TagNumber(6) - set outcome($core.String value) => $_setString(5, value); + set outcome(Outcome value) => $_setField(6, value); @$pb.TagNumber(6) $core.bool hasOutcome() => $_has(5); @$pb.TagNumber(6) @@ -1585,7 +1574,7 @@ class Experiment extends $pb.GeneratedMessage { $fixnum.Int64? revision, $0.Timestamp? proposedAt, Evidence? latestEvidence, - $core.String? latestOutcome, + Outcome? latestOutcome, }) { final result = Experiment._(); if (id != null) result.id = id; @@ -1639,7 +1628,8 @@ class Experiment extends $pb.GeneratedMessage { subBuilder: $0.Timestamp.$_createMessage) ..aOM(12, _omitFieldNames ? '' : 'latestEvidence', subBuilder: Evidence.$_createMessage) - ..aOS(13, _omitFieldNames ? '' : 'latestOutcome') + ..aE(13, _omitFieldNames ? '' : 'latestOutcome', + enumValues: Outcome.values) ..hasRequiredFields = false; @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @@ -1770,9 +1760,9 @@ class Experiment extends $pb.GeneratedMessage { Evidence ensureLatestEvidence() => $_ensure(11); @$pb.TagNumber(13) - $core.String get latestOutcome => $_getSZ(12); + Outcome get latestOutcome => $_getN(12); @$pb.TagNumber(13) - set latestOutcome($core.String value) => $_setString(12, value); + set latestOutcome(Outcome value) => $_setField(13, value); @$pb.TagNumber(13) $core.bool hasLatestOutcome() => $_has(12); @$pb.TagNumber(13) diff --git a/gen/dart/xtcp_treatment/v1/treatment.pbenum.dart b/gen/dart/xtcp_treatment/v1/treatment.pbenum.dart index 6ce55507b..d108c6737 100644 --- a/gen/dart/xtcp_treatment/v1/treatment.pbenum.dart +++ b/gen/dart/xtcp_treatment/v1/treatment.pbenum.dart @@ -101,6 +101,97 @@ class ApplyStatus extends $pb.ProtobufEnum { const ApplyStatus._(super.value, super.name); } +/// Outcome is the result of evaluating one evidence window against a phase, +/// or the standing of a completed treatment under drift monitoring. Only +/// BENEFICIAL, SAFETY_ONLY and FINAL_SAFETY can advance a phase, and only +/// after an operator review; HARMFUL and EXPIRED withdraw automatically. +class Outcome extends $pb.ProtobufEnum { + static const Outcome OUTCOME_UNSPECIFIED = + Outcome._(0, _omitEnumNames ? '' : 'OUTCOME_UNSPECIFIED'); + + /// Analysis of a proposal that has no phase yet. + static const Outcome OUTCOME_BASELINE = + Outcome._(1, _omitEnumNames ? '' : 'OUTCOME_BASELINE'); + + /// Not every treated host has verified the assignment. + static const Outcome OUTCOME_AWAITING_APPLICATION = + Outcome._(2, _omitEnumNames ? '' : 'OUTCOME_AWAITING_APPLICATION'); + + /// Treated hosts have not yet carried enough exposed sockets. + static const Outcome OUTCOME_AWAITING_EXPOSURE = + Outcome._(3, _omitEnumNames ? '' : 'OUTCOME_AWAITING_EXPOSURE'); + static const Outcome OUTCOME_INSUFFICIENT_DURATION = + Outcome._(4, _omitEnumNames ? '' : 'OUTCOME_INSUFFICIENT_DURATION'); + static const Outcome OUTCOME_INSUFFICIENT_DATA = + Outcome._(5, _omitEnumNames ? '' : 'OUTCOME_INSUFFICIENT_DATA'); + static const Outcome OUTCOME_INSUFFICIENT_CONTROLS = + Outcome._(6, _omitEnumNames ? '' : 'OUTCOME_INSUFFICIENT_CONTROLS'); + static const Outcome OUTCOME_STALE = + Outcome._(7, _omitEnumNames ? '' : 'OUTCOME_STALE'); + + /// No evidence source was available for this maintenance pass. + static const Outcome OUTCOME_UNAVAILABLE = + Outcome._(8, _omitEnumNames ? '' : 'OUTCOME_UNAVAILABLE'); + static const Outcome OUTCOME_INCONCLUSIVE = + Outcome._(9, _omitEnumNames ? '' : 'OUTCOME_INCONCLUSIVE'); + + /// Cohort too small for a causal estimate; no regression observed. + static const Outcome OUTCOME_SAFETY_ONLY = + Outcome._(10, _omitEnumNames ? '' : 'OUTCOME_SAFETY_ONLY'); + + /// Final 100% phase: no controls remain; no regression observed. + static const Outcome OUTCOME_FINAL_SAFETY = + Outcome._(11, _omitEnumNames ? '' : 'OUTCOME_FINAL_SAFETY'); + static const Outcome OUTCOME_BENEFICIAL = + Outcome._(12, _omitEnumNames ? '' : 'OUTCOME_BENEFICIAL'); + static const Outcome OUTCOME_HARMFUL = + Outcome._(13, _omitEnumNames ? '' : 'OUTCOME_HARMFUL'); + static const Outcome OUTCOME_EXPIRED = + Outcome._(14, _omitEnumNames ? '' : 'OUTCOME_EXPIRED'); + + /// Completed treatment under drift monitoring. + static const Outcome OUTCOME_STABLE_OBSERVED = + Outcome._(15, _omitEnumNames ? '' : 'OUTCOME_STABLE_OBSERVED'); + static const Outcome OUTCOME_STABLE_EVIDENCE_INCOMPLETE = + Outcome._(16, _omitEnumNames ? '' : 'OUTCOME_STABLE_EVIDENCE_INCOMPLETE'); + static const Outcome OUTCOME_STABLE_REGRESSION_REVIEW_REQUIRED = Outcome._( + 17, _omitEnumNames ? '' : 'OUTCOME_STABLE_REGRESSION_REVIEW_REQUIRED'); + + /// Every treated host has verified the assignment; the observation window + /// is open and no evidence has been evaluated yet. + static const Outcome OUTCOME_OBSERVING = + Outcome._(18, _omitEnumNames ? '' : 'OUTCOME_OBSERVING'); + + static const $core.List values = [ + OUTCOME_UNSPECIFIED, + OUTCOME_BASELINE, + OUTCOME_AWAITING_APPLICATION, + OUTCOME_AWAITING_EXPOSURE, + OUTCOME_INSUFFICIENT_DURATION, + OUTCOME_INSUFFICIENT_DATA, + OUTCOME_INSUFFICIENT_CONTROLS, + OUTCOME_STALE, + OUTCOME_UNAVAILABLE, + OUTCOME_INCONCLUSIVE, + OUTCOME_SAFETY_ONLY, + OUTCOME_FINAL_SAFETY, + OUTCOME_BENEFICIAL, + OUTCOME_HARMFUL, + OUTCOME_EXPIRED, + OUTCOME_STABLE_OBSERVED, + OUTCOME_STABLE_EVIDENCE_INCOMPLETE, + OUTCOME_STABLE_REGRESSION_REVIEW_REQUIRED, + OUTCOME_OBSERVING, + ]; + + static final $core.List _byValue = + $pb.ProtobufEnum.$_initByValueList(values, 18); + static Outcome? valueOf($core.int value) => + value < 0 || value >= _byValue.length ? null : _byValue[value]; + + const Outcome._(super.value, super.name); +} + class ExperimentStatus extends $pb.ProtobufEnum { static const ExperimentStatus EXPERIMENT_STATUS_UNSPECIFIED = ExperimentStatus._( diff --git a/gen/dart/xtcp_treatment/v1/treatment.pbjson.dart b/gen/dart/xtcp_treatment/v1/treatment.pbjson.dart index 488fc1d5c..a42e61cb0 100644 --- a/gen/dart/xtcp_treatment/v1/treatment.pbjson.dart +++ b/gen/dart/xtcp_treatment/v1/treatment.pbjson.dart @@ -68,6 +68,45 @@ final $typed_data.Uint8List applyStatusDescriptor = $convert.base64Decode( 'U19SRU1PVkVEEAMSGAoUQVBQTFlfU1RBVFVTX0VYUElSRUQQBBIXChNBUFBMWV9TVEFUVVNfRk' 'FJTEVEEAU='); +@$core.Deprecated('Use outcomeDescriptor instead') +const Outcome$json = { + '1': 'Outcome', + '2': [ + {'1': 'OUTCOME_UNSPECIFIED', '2': 0}, + {'1': 'OUTCOME_BASELINE', '2': 1}, + {'1': 'OUTCOME_AWAITING_APPLICATION', '2': 2}, + {'1': 'OUTCOME_AWAITING_EXPOSURE', '2': 3}, + {'1': 'OUTCOME_INSUFFICIENT_DURATION', '2': 4}, + {'1': 'OUTCOME_INSUFFICIENT_DATA', '2': 5}, + {'1': 'OUTCOME_INSUFFICIENT_CONTROLS', '2': 6}, + {'1': 'OUTCOME_STALE', '2': 7}, + {'1': 'OUTCOME_UNAVAILABLE', '2': 8}, + {'1': 'OUTCOME_INCONCLUSIVE', '2': 9}, + {'1': 'OUTCOME_SAFETY_ONLY', '2': 10}, + {'1': 'OUTCOME_FINAL_SAFETY', '2': 11}, + {'1': 'OUTCOME_BENEFICIAL', '2': 12}, + {'1': 'OUTCOME_HARMFUL', '2': 13}, + {'1': 'OUTCOME_EXPIRED', '2': 14}, + {'1': 'OUTCOME_STABLE_OBSERVED', '2': 15}, + {'1': 'OUTCOME_STABLE_EVIDENCE_INCOMPLETE', '2': 16}, + {'1': 'OUTCOME_STABLE_REGRESSION_REVIEW_REQUIRED', '2': 17}, + {'1': 'OUTCOME_OBSERVING', '2': 18}, + ], +}; + +/// Descriptor for `Outcome`. Decode as a `google.protobuf.EnumDescriptorProto`. +final $typed_data.Uint8List outcomeDescriptor = $convert.base64Decode( + 'CgdPdXRjb21lEhcKE09VVENPTUVfVU5TUEVDSUZJRUQQABIUChBPVVRDT01FX0JBU0VMSU5FEA' + 'ESIAocT1VUQ09NRV9BV0FJVElOR19BUFBMSUNBVElPThACEh0KGU9VVENPTUVfQVdBSVRJTkdf' + 'RVhQT1NVUkUQAxIhCh1PVVRDT01FX0lOU1VGRklDSUVOVF9EVVJBVElPThAEEh0KGU9VVENPTU' + 'VfSU5TVUZGSUNJRU5UX0RBVEEQBRIhCh1PVVRDT01FX0lOU1VGRklDSUVOVF9DT05UUk9MUxAG' + 'EhEKDU9VVENPTUVfU1RBTEUQBxIXChNPVVRDT01FX1VOQVZBSUxBQkxFEAgSGAoUT1VUQ09NRV' + '9JTkNPTkNMVVNJVkUQCRIXChNPVVRDT01FX1NBRkVUWV9PTkxZEAoSGAoUT1VUQ09NRV9GSU5B' + 'TF9TQUZFVFkQCxIWChJPVVRDT01FX0JFTkVGSUNJQUwQDBITCg9PVVRDT01FX0hBUk1GVUwQDR' + 'ITCg9PVVRDT01FX0VYUElSRUQQDhIbChdPVVRDT01FX1NUQUJMRV9PQlNFUlZFRBAPEiYKIk9V' + 'VENPTUVfU1RBQkxFX0VWSURFTkNFX0lOQ09NUExFVEUQEBItCilPVVRDT01FX1NUQUJMRV9SRU' + 'dSRVNTSU9OX1JFVklFV19SRVFVSVJFRBAREhUKEU9VVENPTUVfT0JTRVJWSU5HEBI='); + @$core.Deprecated('Use experimentStatusDescriptor instead') const ExperimentStatus$json = { '1': 'ExperimentStatus', @@ -222,7 +261,6 @@ final $typed_data.Uint8List treatmentDescriptor = $convert.base64Decode( const Assignment$json = { '1': 'Assignment', '2': [ - {'1': 'id', '3': 1, '4': 1, '5': 9, '8': {}, '10': 'id'}, { '1': 'experiment_id', '3': 2, @@ -268,24 +306,27 @@ const Assignment$json = { {'1': 'generation', '3': 7, '4': 1, '5': 4, '8': {}, '10': 'generation'}, ], '7': {}, + '9': [ + {'1': 1, '2': 2}, + ], + '10': ['id'], }; /// Descriptor for `Assignment`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List assignmentDescriptor = $convert.base64Decode( - 'CgpBc3NpZ25tZW50EjkKAmlkGAEgASgJQim6SCZyJDIiXlthLXpBLVowLTldW2EtekEtWjAtOV' - '8uLV17MCwxMjd9JFICaWQSTgoNZXhwZXJpbWVudF9pZBgCIAEoCUIpukgmciQyIl5bYS16QS1a' - 'MC05XVthLXpBLVowLTlfLi1dezAsMTI3fSRSDGV4cGVyaW1lbnRJZBJCCgl0cmVhdG1lbnQYAy' - 'ABKAsyHC54dGNwX3RyZWF0bWVudC52MS5UcmVhdG1lbnRCBrpIA8gBAVIJdHJlYXRtZW50EkEK' - 'BG1vZGUYBCABKA4yIS54dGNwX3RyZWF0bWVudC52MS5Bc3NpZ25tZW50TW9kZUIKukgHggEEEA' - 'EgAFIEbW9kZRJEChBsZWFzZV9leHBpcmVzX2F0GAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRp' - 'bWVzdGFtcFIObGVhc2VFeHBpcmVzQXQSPgoLcHJlZGVjZXNzb3IYBiABKAsyHC54dGNwX3RyZW' - 'F0bWVudC52MS5UcmVhdG1lbnRSC3ByZWRlY2Vzc29yEicKCmdlbmVyYXRpb24YByABKARCB7pI' - 'BDICIABSCmdlbmVyYXRpb246rQK6SKkCGn0KH2Fzc2lnbm1lbnQudHJpYWxfcmVxdWlyZXNfbG' - 'Vhc2USLGEgdHJpYWwgYXNzaWdubWVudCByZXF1aXJlcyBsZWFzZV9leHBpcmVzX2F0Gix0aGlz' - 'Lm1vZGUgIT0gMSB8fCBoYXModGhpcy5sZWFzZV9leHBpcmVzX2F0KRqnAQoiYXNzaWdubWVudC' - '5wcmVkZWNlc3Nvcl9kZXN0aW5hdGlvbhIrcHJlZGVjZXNzb3IgbXVzdCBjb3ZlciB0aGUgc2Ft' - 'ZSBkZXN0aW5hdGlvbhpUIWhhcyh0aGlzLnByZWRlY2Vzc29yKSB8fCB0aGlzLnByZWRlY2Vzc2' - '9yLmRlc3RpbmF0aW9uID09IHRoaXMudHJlYXRtZW50LmRlc3RpbmF0aW9u'); + 'CgpBc3NpZ25tZW50Ek4KDWV4cGVyaW1lbnRfaWQYAiABKAlCKbpIJnIkMiJeW2EtekEtWjAtOV' + '1bYS16QS1aMC05Xy4tXXswLDEyN30kUgxleHBlcmltZW50SWQSQgoJdHJlYXRtZW50GAMgASgL' + 'MhwueHRjcF90cmVhdG1lbnQudjEuVHJlYXRtZW50Qga6SAPIAQFSCXRyZWF0bWVudBJBCgRtb2' + 'RlGAQgASgOMiEueHRjcF90cmVhdG1lbnQudjEuQXNzaWdubWVudE1vZGVCCrpIB4IBBBABIABS' + 'BG1vZGUSRAoQbGVhc2VfZXhwaXJlc19hdBgFIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3' + 'RhbXBSDmxlYXNlRXhwaXJlc0F0Ej4KC3ByZWRlY2Vzc29yGAYgASgLMhwueHRjcF90cmVhdG1l' + 'bnQudjEuVHJlYXRtZW50UgtwcmVkZWNlc3NvchInCgpnZW5lcmF0aW9uGAcgASgEQge6SAQyAi' + 'AAUgpnZW5lcmF0aW9uOq0CukipAhp9Ch9hc3NpZ25tZW50LnRyaWFsX3JlcXVpcmVzX2xlYXNl' + 'EixhIHRyaWFsIGFzc2lnbm1lbnQgcmVxdWlyZXMgbGVhc2VfZXhwaXJlc19hdBosdGhpcy5tb2' + 'RlICE9IDEgfHwgaGFzKHRoaXMubGVhc2VfZXhwaXJlc19hdCkapwEKImFzc2lnbm1lbnQucHJl' + 'ZGVjZXNzb3JfZGVzdGluYXRpb24SK3ByZWRlY2Vzc29yIG11c3QgY292ZXIgdGhlIHNhbWUgZG' + 'VzdGluYXRpb24aVCFoYXModGhpcy5wcmVkZWNlc3NvcikgfHwgdGhpcy5wcmVkZWNlc3Nvci5k' + 'ZXN0aW5hdGlvbiA9PSB0aGlzLnRyZWF0bWVudC5kZXN0aW5hdGlvbkoECAEQAlICaWQ='); @$core.Deprecated('Use desiredStateDescriptor instead') const DesiredState$json = { @@ -794,7 +835,15 @@ const Phase$json = { '6': '.xtcp_treatment.v1.Evidence', '10': 'evidence' }, - {'1': 'outcome', '3': 6, '4': 1, '5': 9, '8': {}, '10': 'outcome'}, + { + '1': 'outcome', + '3': 6, + '4': 1, + '5': 14, + '6': '.xtcp_treatment.v1.Outcome', + '8': {}, + '10': 'outcome' + }, {'1': 'reviewer', '3': 7, '4': 1, '5': 9, '8': {}, '10': 'reviewer'}, {'1': 'reason', '3': 8, '4': 1, '5': 9, '8': {}, '10': 'reason'}, ], @@ -807,9 +856,10 @@ final $typed_data.Uint8List phaseDescriptor = $convert.base64Decode( 'ElAKFm9ic2VydmF0aW9uX3N0YXJ0ZWRfYXQYAyABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZX' 'N0YW1wUhRvYnNlcnZhdGlvblN0YXJ0ZWRBdBI9CgxldmFsdWF0ZWRfYXQYBCABKAsyGi5nb29n' 'bGUucHJvdG9idWYuVGltZXN0YW1wUgtldmFsdWF0ZWRBdBI3CghldmlkZW5jZRgFIAEoCzIbLn' - 'h0Y3BfdHJlYXRtZW50LnYxLkV2aWRlbmNlUghldmlkZW5jZRIhCgdvdXRjb21lGAYgASgJQge6' - 'SARyAhhAUgdvdXRjb21lEiQKCHJldmlld2VyGAcgASgJQgi6SAVyAxiAAVIIcmV2aWV3ZXISIA' - 'oGcmVhc29uGAggASgJQgi6SAVyAxiAIFIGcmVhc29u'); + 'h0Y3BfdHJlYXRtZW50LnYxLkV2aWRlbmNlUghldmlkZW5jZRI+CgdvdXRjb21lGAYgASgOMhou' + 'eHRjcF90cmVhdG1lbnQudjEuT3V0Y29tZUIIukgFggECEAFSB291dGNvbWUSJAoIcmV2aWV3ZX' + 'IYByABKAlCCLpIBXIDGIABUghyZXZpZXdlchIgCgZyZWFzb24YCCABKAlCCLpIBXIDGIAgUgZy' + 'ZWFzb24='); @$core.Deprecated('Use experimentDescriptor instead') const Experiment$json = { @@ -885,7 +935,8 @@ const Experiment$json = { '1': 'latest_outcome', '3': 13, '4': 1, - '5': 9, + '5': 14, + '6': '.xtcp_treatment.v1.Outcome', '8': {}, '10': 'latestOutcome' }, @@ -907,5 +958,6 @@ final $typed_data.Uint8List experimentDescriptor = $convert.base64Decode( 'Iy54dGNwX3RyZWF0bWVudC52MS5FeHBlcmltZW50U3RhdHVzQgi6SAWCAQIQAVIGc3RhdHVzEh' 'oKCHJldmlzaW9uGAogASgEUghyZXZpc2lvbhI7Cgtwcm9wb3NlZF9hdBgLIAEoCzIaLmdvb2ds' 'ZS5wcm90b2J1Zi5UaW1lc3RhbXBSCnByb3Bvc2VkQXQSRAoPbGF0ZXN0X2V2aWRlbmNlGAwgAS' - 'gLMhsueHRjcF90cmVhdG1lbnQudjEuRXZpZGVuY2VSDmxhdGVzdEV2aWRlbmNlEi4KDmxhdGVz' - 'dF9vdXRjb21lGA0gASgJQge6SARyAhhAUg1sYXRlc3RPdXRjb21l'); + 'gLMhsueHRjcF90cmVhdG1lbnQudjEuRXZpZGVuY2VSDmxhdGVzdEV2aWRlbmNlEksKDmxhdGVz' + 'dF9vdXRjb21lGA0gASgOMhoueHRjcF90cmVhdG1lbnQudjEuT3V0Y29tZUIIukgFggECEAFSDW' + 'xhdGVzdE91dGNvbWU='); diff --git a/gen/go/xtcp_treatment/operator.pb.go b/gen/go/xtcp_treatment/operator.pb.go index ff9f7de58..be280cd22 100644 --- a/gen/go/xtcp_treatment/operator.pb.go +++ b/gen/go/xtcp_treatment/operator.pb.go @@ -239,7 +239,7 @@ func (x *AnalyzeRequest) GetExpectedRevision() uint64 { type AnalyzeResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Evidence *Evidence `protobuf:"bytes,1,opt,name=evidence,proto3" json:"evidence,omitempty"` - Outcome string `protobuf:"bytes,2,opt,name=outcome,proto3" json:"outcome,omitempty"` + Outcome Outcome `protobuf:"varint,2,opt,name=outcome,proto3,enum=xtcp_treatment.v1.Outcome" json:"outcome,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -281,11 +281,11 @@ func (x *AnalyzeResponse) GetEvidence() *Evidence { return nil } -func (x *AnalyzeResponse) GetOutcome() string { +func (x *AnalyzeResponse) GetOutcome() Outcome { if x != nil { return x.Outcome } - return "" + return Outcome_OUTCOME_UNSPECIFIED } type ReviewRequest struct { @@ -683,10 +683,10 @@ const file_xtcp_treatment_v1_operator_proto_rawDesc = "" + "experiment\"\x8d\x01\n" + "\x0eAnalyzeRequest\x12N\n" + "\rexperiment_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\fexperimentId\x12+\n" + - "\x11expected_revision\x18\x02 \x01(\x04R\x10expectedRevision\"d\n" + + "\x11expected_revision\x18\x02 \x01(\x04R\x10expectedRevision\"\x80\x01\n" + "\x0fAnalyzeResponse\x127\n" + - "\bevidence\x18\x01 \x01(\v2\x1b.xtcp_treatment.v1.EvidenceR\bevidence\x12\x18\n" + - "\aoutcome\x18\x02 \x01(\tR\aoutcome\"\xe6\x03\n" + + "\bevidence\x18\x01 \x01(\v2\x1b.xtcp_treatment.v1.EvidenceR\bevidence\x124\n" + + "\aoutcome\x18\x02 \x01(\x0e2\x1a.xtcp_treatment.v1.OutcomeR\aoutcome\"\xe6\x03\n" + "\rReviewRequest\x12N\n" + "\rexperiment_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\fexperimentId\x12+\n" + "\x11expected_revision\x18\x02 \x01(\x04R\x10expectedRevision\x12\x18\n" + @@ -766,34 +766,36 @@ var file_xtcp_treatment_v1_operator_proto_goTypes = []any{ (*InspectResponse)(nil), // 10: xtcp_treatment.v1.InspectResponse (*Experiment)(nil), // 11: xtcp_treatment.v1.Experiment (*Evidence)(nil), // 12: xtcp_treatment.v1.Evidence - (*DesiredState)(nil), // 13: xtcp_treatment.v1.DesiredState - (*CheckInRequest)(nil), // 14: xtcp_treatment.v1.CheckInRequest + (Outcome)(0), // 13: xtcp_treatment.v1.Outcome + (*DesiredState)(nil), // 14: xtcp_treatment.v1.DesiredState + (*CheckInRequest)(nil), // 15: xtcp_treatment.v1.CheckInRequest } var file_xtcp_treatment_v1_operator_proto_depIdxs = []int32{ 11, // 0: xtcp_treatment.v1.ProposeRequest.experiment:type_name -> xtcp_treatment.v1.Experiment 11, // 1: xtcp_treatment.v1.ProposeResponse.experiment:type_name -> xtcp_treatment.v1.Experiment 12, // 2: xtcp_treatment.v1.AnalyzeResponse.evidence:type_name -> xtcp_treatment.v1.Evidence - 12, // 3: xtcp_treatment.v1.ReviewRequest.evidence:type_name -> xtcp_treatment.v1.Evidence - 11, // 4: xtcp_treatment.v1.ReviewResponse.experiment:type_name -> xtcp_treatment.v1.Experiment - 0, // 5: xtcp_treatment.v1.ControlRequest.action:type_name -> xtcp_treatment.v1.ControlAction - 11, // 6: xtcp_treatment.v1.InspectResponse.experiments:type_name -> xtcp_treatment.v1.Experiment - 13, // 7: xtcp_treatment.v1.InspectResponse.desired:type_name -> xtcp_treatment.v1.DesiredState - 14, // 8: xtcp_treatment.v1.InspectResponse.observed:type_name -> xtcp_treatment.v1.CheckInRequest - 1, // 9: xtcp_treatment.v1.OperatorService.Propose:input_type -> xtcp_treatment.v1.ProposeRequest - 3, // 10: xtcp_treatment.v1.OperatorService.Analyze:input_type -> xtcp_treatment.v1.AnalyzeRequest - 5, // 11: xtcp_treatment.v1.OperatorService.Review:input_type -> xtcp_treatment.v1.ReviewRequest - 7, // 12: xtcp_treatment.v1.OperatorService.Control:input_type -> xtcp_treatment.v1.ControlRequest - 9, // 13: xtcp_treatment.v1.OperatorService.Inspect:input_type -> xtcp_treatment.v1.InspectRequest - 2, // 14: xtcp_treatment.v1.OperatorService.Propose:output_type -> xtcp_treatment.v1.ProposeResponse - 4, // 15: xtcp_treatment.v1.OperatorService.Analyze:output_type -> xtcp_treatment.v1.AnalyzeResponse - 6, // 16: xtcp_treatment.v1.OperatorService.Review:output_type -> xtcp_treatment.v1.ReviewResponse - 8, // 17: xtcp_treatment.v1.OperatorService.Control:output_type -> xtcp_treatment.v1.ControlResponse - 10, // 18: xtcp_treatment.v1.OperatorService.Inspect:output_type -> xtcp_treatment.v1.InspectResponse - 14, // [14:19] is the sub-list for method output_type - 9, // [9:14] is the sub-list for method input_type - 9, // [9:9] is the sub-list for extension type_name - 9, // [9:9] is the sub-list for extension extendee - 0, // [0:9] is the sub-list for field type_name + 13, // 3: xtcp_treatment.v1.AnalyzeResponse.outcome:type_name -> xtcp_treatment.v1.Outcome + 12, // 4: xtcp_treatment.v1.ReviewRequest.evidence:type_name -> xtcp_treatment.v1.Evidence + 11, // 5: xtcp_treatment.v1.ReviewResponse.experiment:type_name -> xtcp_treatment.v1.Experiment + 0, // 6: xtcp_treatment.v1.ControlRequest.action:type_name -> xtcp_treatment.v1.ControlAction + 11, // 7: xtcp_treatment.v1.InspectResponse.experiments:type_name -> xtcp_treatment.v1.Experiment + 14, // 8: xtcp_treatment.v1.InspectResponse.desired:type_name -> xtcp_treatment.v1.DesiredState + 15, // 9: xtcp_treatment.v1.InspectResponse.observed:type_name -> xtcp_treatment.v1.CheckInRequest + 1, // 10: xtcp_treatment.v1.OperatorService.Propose:input_type -> xtcp_treatment.v1.ProposeRequest + 3, // 11: xtcp_treatment.v1.OperatorService.Analyze:input_type -> xtcp_treatment.v1.AnalyzeRequest + 5, // 12: xtcp_treatment.v1.OperatorService.Review:input_type -> xtcp_treatment.v1.ReviewRequest + 7, // 13: xtcp_treatment.v1.OperatorService.Control:input_type -> xtcp_treatment.v1.ControlRequest + 9, // 14: xtcp_treatment.v1.OperatorService.Inspect:input_type -> xtcp_treatment.v1.InspectRequest + 2, // 15: xtcp_treatment.v1.OperatorService.Propose:output_type -> xtcp_treatment.v1.ProposeResponse + 4, // 16: xtcp_treatment.v1.OperatorService.Analyze:output_type -> xtcp_treatment.v1.AnalyzeResponse + 6, // 17: xtcp_treatment.v1.OperatorService.Review:output_type -> xtcp_treatment.v1.ReviewResponse + 8, // 18: xtcp_treatment.v1.OperatorService.Control:output_type -> xtcp_treatment.v1.ControlResponse + 10, // 19: xtcp_treatment.v1.OperatorService.Inspect:output_type -> xtcp_treatment.v1.InspectResponse + 15, // [15:20] is the sub-list for method output_type + 10, // [10:15] is the sub-list for method input_type + 10, // [10:10] is the sub-list for extension type_name + 10, // [10:10] is the sub-list for extension extendee + 0, // [0:10] is the sub-list for field type_name } func init() { file_xtcp_treatment_v1_operator_proto_init() } diff --git a/gen/go/xtcp_treatment/operator_vtproto.pb.go b/gen/go/xtcp_treatment/operator_vtproto.pb.go index c88aa9ec3..f0811f73b 100644 --- a/gen/go/xtcp_treatment/operator_vtproto.pb.go +++ b/gen/go/xtcp_treatment/operator_vtproto.pb.go @@ -186,12 +186,10 @@ func (m *AnalyzeResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } - if len(m.Outcome) > 0 { - i -= len(m.Outcome) - copy(dAtA[i:], m.Outcome) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Outcome))) + if m.Outcome != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Outcome)) i-- - dAtA[i] = 0x12 + dAtA[i] = 0x10 } if m.Evidence != nil { size, err := m.Evidence.MarshalToSizedBufferVT(dAtA[:i]) @@ -624,9 +622,8 @@ func (m *AnalyzeResponse) SizeVT() (n int) { l = m.Evidence.SizeVT() n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - l = len(m.Outcome) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + if m.Outcome != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Outcome)) } n += len(m.unknownFields) return n @@ -1147,10 +1144,10 @@ func (m *AnalyzeResponse) UnmarshalVT(dAtA []byte) error { } iNdEx = postIndex case 2: - if wireType != 2 { + if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Outcome", wireType) } - var stringLen uint64 + m.Outcome = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -1160,24 +1157,11 @@ func (m *AnalyzeResponse) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + m.Outcome |= Outcome(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Outcome = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/gen/go/xtcp_treatment/treatment.pb.go b/gen/go/xtcp_treatment/treatment.pb.go index 119041b0e..32b4eb861 100644 --- a/gen/go/xtcp_treatment/treatment.pb.go +++ b/gen/go/xtcp_treatment/treatment.pb.go @@ -210,6 +210,116 @@ func (ApplyStatus) EnumDescriptor() ([]byte, []int) { return file_xtcp_treatment_v1_treatment_proto_rawDescGZIP(), []int{2} } +// Outcome is the result of evaluating one evidence window against a phase, +// or the standing of a completed treatment under drift monitoring. Only +// BENEFICIAL, SAFETY_ONLY and FINAL_SAFETY can advance a phase, and only +// after an operator review; HARMFUL and EXPIRED withdraw automatically. +type Outcome int32 + +const ( + Outcome_OUTCOME_UNSPECIFIED Outcome = 0 + // Analysis of a proposal that has no phase yet. + Outcome_OUTCOME_BASELINE Outcome = 1 + // Not every treated host has verified the assignment. + Outcome_OUTCOME_AWAITING_APPLICATION Outcome = 2 + // Treated hosts have not yet carried enough exposed sockets. + Outcome_OUTCOME_AWAITING_EXPOSURE Outcome = 3 + Outcome_OUTCOME_INSUFFICIENT_DURATION Outcome = 4 + Outcome_OUTCOME_INSUFFICIENT_DATA Outcome = 5 + Outcome_OUTCOME_INSUFFICIENT_CONTROLS Outcome = 6 + Outcome_OUTCOME_STALE Outcome = 7 + // No evidence source was available for this maintenance pass. + Outcome_OUTCOME_UNAVAILABLE Outcome = 8 + Outcome_OUTCOME_INCONCLUSIVE Outcome = 9 + // Cohort too small for a causal estimate; no regression observed. + Outcome_OUTCOME_SAFETY_ONLY Outcome = 10 + // Final 100% phase: no controls remain; no regression observed. + Outcome_OUTCOME_FINAL_SAFETY Outcome = 11 + Outcome_OUTCOME_BENEFICIAL Outcome = 12 + Outcome_OUTCOME_HARMFUL Outcome = 13 + Outcome_OUTCOME_EXPIRED Outcome = 14 + // Completed treatment under drift monitoring. + Outcome_OUTCOME_STABLE_OBSERVED Outcome = 15 + Outcome_OUTCOME_STABLE_EVIDENCE_INCOMPLETE Outcome = 16 + Outcome_OUTCOME_STABLE_REGRESSION_REVIEW_REQUIRED Outcome = 17 + // Every treated host has verified the assignment; the observation window + // is open and no evidence has been evaluated yet. + Outcome_OUTCOME_OBSERVING Outcome = 18 +) + +// Enum value maps for Outcome. +var ( + Outcome_name = map[int32]string{ + 0: "OUTCOME_UNSPECIFIED", + 1: "OUTCOME_BASELINE", + 2: "OUTCOME_AWAITING_APPLICATION", + 3: "OUTCOME_AWAITING_EXPOSURE", + 4: "OUTCOME_INSUFFICIENT_DURATION", + 5: "OUTCOME_INSUFFICIENT_DATA", + 6: "OUTCOME_INSUFFICIENT_CONTROLS", + 7: "OUTCOME_STALE", + 8: "OUTCOME_UNAVAILABLE", + 9: "OUTCOME_INCONCLUSIVE", + 10: "OUTCOME_SAFETY_ONLY", + 11: "OUTCOME_FINAL_SAFETY", + 12: "OUTCOME_BENEFICIAL", + 13: "OUTCOME_HARMFUL", + 14: "OUTCOME_EXPIRED", + 15: "OUTCOME_STABLE_OBSERVED", + 16: "OUTCOME_STABLE_EVIDENCE_INCOMPLETE", + 17: "OUTCOME_STABLE_REGRESSION_REVIEW_REQUIRED", + 18: "OUTCOME_OBSERVING", + } + Outcome_value = map[string]int32{ + "OUTCOME_UNSPECIFIED": 0, + "OUTCOME_BASELINE": 1, + "OUTCOME_AWAITING_APPLICATION": 2, + "OUTCOME_AWAITING_EXPOSURE": 3, + "OUTCOME_INSUFFICIENT_DURATION": 4, + "OUTCOME_INSUFFICIENT_DATA": 5, + "OUTCOME_INSUFFICIENT_CONTROLS": 6, + "OUTCOME_STALE": 7, + "OUTCOME_UNAVAILABLE": 8, + "OUTCOME_INCONCLUSIVE": 9, + "OUTCOME_SAFETY_ONLY": 10, + "OUTCOME_FINAL_SAFETY": 11, + "OUTCOME_BENEFICIAL": 12, + "OUTCOME_HARMFUL": 13, + "OUTCOME_EXPIRED": 14, + "OUTCOME_STABLE_OBSERVED": 15, + "OUTCOME_STABLE_EVIDENCE_INCOMPLETE": 16, + "OUTCOME_STABLE_REGRESSION_REVIEW_REQUIRED": 17, + "OUTCOME_OBSERVING": 18, + } +) + +func (x Outcome) Enum() *Outcome { + p := new(Outcome) + *p = x + return p +} + +func (x Outcome) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Outcome) Descriptor() protoreflect.EnumDescriptor { + return file_xtcp_treatment_v1_treatment_proto_enumTypes[3].Descriptor() +} + +func (Outcome) Type() protoreflect.EnumType { + return &file_xtcp_treatment_v1_treatment_proto_enumTypes[3] +} + +func (x Outcome) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Outcome.Descriptor instead. +func (Outcome) EnumDescriptor() ([]byte, []int) { + return file_xtcp_treatment_v1_treatment_proto_rawDescGZIP(), []int{3} +} + type ExperimentStatus int32 const ( @@ -258,11 +368,11 @@ func (x ExperimentStatus) String() string { } func (ExperimentStatus) Descriptor() protoreflect.EnumDescriptor { - return file_xtcp_treatment_v1_treatment_proto_enumTypes[3].Descriptor() + return file_xtcp_treatment_v1_treatment_proto_enumTypes[4].Descriptor() } func (ExperimentStatus) Type() protoreflect.EnumType { - return &file_xtcp_treatment_v1_treatment_proto_enumTypes[3] + return &file_xtcp_treatment_v1_treatment_proto_enumTypes[4] } func (x ExperimentStatus) Number() protoreflect.EnumNumber { @@ -271,7 +381,7 @@ func (x ExperimentStatus) Number() protoreflect.EnumNumber { // Deprecated: Use ExperimentStatus.Descriptor instead. func (ExperimentStatus) EnumDescriptor() ([]byte, []int) { - return file_xtcp_treatment_v1_treatment_proto_rawDescGZIP(), []int{3} + return file_xtcp_treatment_v1_treatment_proto_rawDescGZIP(), []int{4} } // TCPSettings are the per-route TCP parameters a treatment installs. Every @@ -449,7 +559,6 @@ func (x *Treatment) GetEcnFabricVerified() bool { type Assignment struct { state protoimpl.MessageState `protogen:"open.v1"` - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` ExperimentId string `protobuf:"bytes,2,opt,name=experiment_id,json=experimentId,proto3" json:"experiment_id,omitempty"` Treatment *Treatment `protobuf:"bytes,3,opt,name=treatment,proto3" json:"treatment,omitempty"` Mode AssignmentMode `protobuf:"varint,4,opt,name=mode,proto3,enum=xtcp_treatment.v1.AssignmentMode" json:"mode,omitempty"` @@ -492,13 +601,6 @@ func (*Assignment) Descriptor() ([]byte, []int) { return file_xtcp_treatment_v1_treatment_proto_rawDescGZIP(), []int{2} } -func (x *Assignment) GetId() string { - if x != nil { - return x.Id - } - return "" -} - func (x *Assignment) GetExperimentId() string { if x != nil { return x.ExperimentId @@ -1225,7 +1327,7 @@ type Phase struct { ObservationStartedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=observation_started_at,json=observationStartedAt,proto3" json:"observation_started_at,omitempty"` EvaluatedAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=evaluated_at,json=evaluatedAt,proto3" json:"evaluated_at,omitempty"` Evidence *Evidence `protobuf:"bytes,5,opt,name=evidence,proto3" json:"evidence,omitempty"` - Outcome string `protobuf:"bytes,6,opt,name=outcome,proto3" json:"outcome,omitempty"` + Outcome Outcome `protobuf:"varint,6,opt,name=outcome,proto3,enum=xtcp_treatment.v1.Outcome" json:"outcome,omitempty"` Reviewer string `protobuf:"bytes,7,opt,name=reviewer,proto3" json:"reviewer,omitempty"` Reason string `protobuf:"bytes,8,opt,name=reason,proto3" json:"reason,omitempty"` unknownFields protoimpl.UnknownFields @@ -1297,11 +1399,11 @@ func (x *Phase) GetEvidence() *Evidence { return nil } -func (x *Phase) GetOutcome() string { +func (x *Phase) GetOutcome() Outcome { if x != nil { return x.Outcome } - return "" + return Outcome_OUTCOME_UNSPECIFIED } func (x *Phase) GetReviewer() string { @@ -1333,7 +1435,7 @@ type Experiment struct { Revision uint64 `protobuf:"varint,10,opt,name=revision,proto3" json:"revision,omitempty"` ProposedAt *timestamppb.Timestamp `protobuf:"bytes,11,opt,name=proposed_at,json=proposedAt,proto3" json:"proposed_at,omitempty"` LatestEvidence *Evidence `protobuf:"bytes,12,opt,name=latest_evidence,json=latestEvidence,proto3" json:"latest_evidence,omitempty"` - LatestOutcome string `protobuf:"bytes,13,opt,name=latest_outcome,json=latestOutcome,proto3" json:"latest_outcome,omitempty"` + LatestOutcome Outcome `protobuf:"varint,13,opt,name=latest_outcome,json=latestOutcome,proto3,enum=xtcp_treatment.v1.Outcome" json:"latest_outcome,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1452,11 +1554,11 @@ func (x *Experiment) GetLatestEvidence() *Evidence { return nil } -func (x *Experiment) GetLatestOutcome() string { +func (x *Experiment) GetLatestOutcome() Outcome { if x != nil { return x.LatestOutcome } - return "" + return Outcome_OUTCOME_UNSPECIFIED } var file_xtcp_treatment_v1_treatment_proto_extTypes = []protoimpl.ExtensionInfo{ @@ -1511,10 +1613,9 @@ const file_xtcp_treatment_v1_treatment_proto_rawDesc = "" + "\xbaH\ar\x05\x181\xe8\x01\x01R\vdestination\x12B\n" + "\bsettings\x18\x04 \x01(\v2\x1e.xtcp_treatment.v1.TCPSettingsB\x06\xbaH\x03\xc8\x01\x01R\bsettings\x12.\n" + "\x13ecn_fabric_verified\x18\x05 \x01(\bR\x11ecnFabricVerified:\xc8\x01\xbaH\xc4\x01\x1a\xc1\x01\n" + - "\x1ctreatment.dctcp_requires_ecn\x12ADCTCP requires independently verified ECN fabric and explicit ECN\x1a^this.settings.congestion_control != 'dctcp' || (this.ecn_fabric_verified && this.settings.ecn)\"\xfd\x05\n" + + "\x1ctreatment.dctcp_requires_ecn\x12ADCTCP requires independently verified ECN fabric and explicit ECN\x1a^this.settings.congestion_control != 'dctcp' || (this.ecn_fabric_verified && this.settings.ecn)\"\xcc\x05\n" + "\n" + - "Assignment\x129\n" + - "\x02id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x02id\x12N\n" + + "Assignment\x12N\n" + "\rexperiment_id\x18\x02 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\fexperimentId\x12B\n" + "\ttreatment\x18\x03 \x01(\v2\x1c.xtcp_treatment.v1.TreatmentB\x06\xbaH\x03\xc8\x01\x01R\ttreatment\x12A\n" + "\x04mode\x18\x04 \x01(\x0e2!.xtcp_treatment.v1.AssignmentModeB\n" + @@ -1525,7 +1626,7 @@ const file_xtcp_treatment_v1_treatment_proto_rawDesc = "" + "generation\x18\a \x01(\x04B\a\xbaH\x042\x02 \x00R\n" + "generation:\xad\x02\xbaH\xa9\x02\x1a}\n" + "\x1fassignment.trial_requires_lease\x12,a trial assignment requires lease_expires_at\x1a,this.mode != 1 || has(this.lease_expires_at)\x1a\xa7\x01\n" + - "\"assignment.predecessor_destination\x12+predecessor must cover the same destination\x1aT!has(this.predecessor) || this.predecessor.destination == this.treatment.destination\"\xa4\x02\n" + + "\"assignment.predecessor_destination\x12+predecessor must cover the same destination\x1aT!has(this.predecessor) || this.predecessor.destination == this.treatment.destinationJ\x04\b\x01\x10\x02R\x02id\"\xa4\x02\n" + "\fDesiredState\x12D\n" + "\bagent_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\aagentId\x12#\n" + "\brevision\x18\x02 \x01(\x04B\a\xbaH\x042\x02 \x00R\brevision\x12\x1c\n" + @@ -1603,17 +1704,17 @@ const file_xtcp_treatment_v1_treatment_proto_rawDesc = "" + "\x1fmaximum_retransmission_increase\x18\a \x01(\x01B\x19\xbaH\x16\x12\x14@\x01\x19\x00\x00\x00\x00\x00\x00\xf0?)\x00\x00\x00\x00\x00\x00\x00\x00R\x1dmaximumRetransmissionIncrease\x12K\n" + "\x14maximum_rtt_increase\x18\b \x01(\x01B\x19\xbaH\x16\x12\x14@\x01\x19\x00\x00\x00\x00\x00\x00\xf0?)\x00\x00\x00\x00\x00\x00\x00\x00R\x12maximumRttIncrease\x12^\n" + "\x14maximum_evidence_age\x18\t \x01(\v2\x19.google.protobuf.DurationB\x11\xbaH\x0e\xc8\x01\x01\xaa\x01\b\"\x04\b\x80\xf5$*\x00R\x12maximumEvidenceAge:\x89\x01\xbaH\x85\x01\x1a\x82\x01\n" + - "\x1fpolicy.observation_within_phase\x12/minimum_observation cannot exceed maximum_phase\x1a.this.minimum_observation <= this.maximum_phase\"\xa4\x03\n" + + "\x1fpolicy.observation_within_phase\x12/minimum_observation cannot exceed maximum_phase\x1a.this.minimum_observation <= this.maximum_phase\"\xc1\x03\n" + "\x05Phase\x12-\n" + "\ftarget_hosts\x18\x01 \x01(\rB\n" + "\xbaH\a*\x05\x18\x90N(\x01R\vtargetHosts\x127\n" + "\tissued_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\bissuedAt\x12P\n" + "\x16observation_started_at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\x14observationStartedAt\x12=\n" + "\fevaluated_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\vevaluatedAt\x127\n" + - "\bevidence\x18\x05 \x01(\v2\x1b.xtcp_treatment.v1.EvidenceR\bevidence\x12!\n" + - "\aoutcome\x18\x06 \x01(\tB\a\xbaH\x04r\x02\x18@R\aoutcome\x12$\n" + + "\bevidence\x18\x05 \x01(\v2\x1b.xtcp_treatment.v1.EvidenceR\bevidence\x12>\n" + + "\aoutcome\x18\x06 \x01(\x0e2\x1a.xtcp_treatment.v1.OutcomeB\b\xbaH\x05\x82\x01\x02\x10\x01R\aoutcome\x12$\n" + "\breviewer\x18\a \x01(\tB\b\xbaH\x05r\x03\x18\x80\x01R\breviewer\x12 \n" + - "\x06reason\x18\b \x01(\tB\b\xbaH\x05r\x03\x18\x80 R\x06reason\"\xb0\x06\n" + + "\x06reason\x18\b \x01(\tB\b\xbaH\x05r\x03\x18\x80 R\x06reason\"\xcd\x06\n" + "\n" + "Experiment\x129\n" + "\x02id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x02id\x12E\n" + @@ -1632,8 +1733,8 @@ const file_xtcp_treatment_v1_treatment_proto_rawDesc = "" + " \x01(\x04R\brevision\x12;\n" + "\vproposed_at\x18\v \x01(\v2\x1a.google.protobuf.TimestampR\n" + "proposedAt\x12D\n" + - "\x0flatest_evidence\x18\f \x01(\v2\x1b.xtcp_treatment.v1.EvidenceR\x0elatestEvidence\x12.\n" + - "\x0elatest_outcome\x18\r \x01(\tB\a\xbaH\x04r\x02\x18@R\rlatestOutcome*?\n" + + "\x0flatest_evidence\x18\f \x01(\v2\x1b.xtcp_treatment.v1.EvidenceR\x0elatestEvidence\x12K\n" + + "\x0elatest_outcome\x18\r \x01(\x0e2\x1a.xtcp_treatment.v1.OutcomeB\b\xbaH\x05\x82\x01\x02\x10\x01R\rlatestOutcome*?\n" + "\x04Role\x12\x14\n" + "\x10ROLE_UNSPECIFIED\x10\x00\x12\x0e\n" + "\n" + @@ -1651,7 +1752,28 @@ const file_xtcp_treatment_v1_treatment_proto_rawDesc = "" + "\x15APPLY_STATUS_VERIFIED\x10\x02\x12\x18\n" + "\x14APPLY_STATUS_REMOVED\x10\x03\x12\x18\n" + "\x14APPLY_STATUS_EXPIRED\x10\x04\x12\x17\n" + - "\x13APPLY_STATUS_FAILED\x10\x05*\x95\x02\n" + + "\x13APPLY_STATUS_FAILED\x10\x05*\xa4\x04\n" + + "\aOutcome\x12\x17\n" + + "\x13OUTCOME_UNSPECIFIED\x10\x00\x12\x14\n" + + "\x10OUTCOME_BASELINE\x10\x01\x12 \n" + + "\x1cOUTCOME_AWAITING_APPLICATION\x10\x02\x12\x1d\n" + + "\x19OUTCOME_AWAITING_EXPOSURE\x10\x03\x12!\n" + + "\x1dOUTCOME_INSUFFICIENT_DURATION\x10\x04\x12\x1d\n" + + "\x19OUTCOME_INSUFFICIENT_DATA\x10\x05\x12!\n" + + "\x1dOUTCOME_INSUFFICIENT_CONTROLS\x10\x06\x12\x11\n" + + "\rOUTCOME_STALE\x10\a\x12\x17\n" + + "\x13OUTCOME_UNAVAILABLE\x10\b\x12\x18\n" + + "\x14OUTCOME_INCONCLUSIVE\x10\t\x12\x17\n" + + "\x13OUTCOME_SAFETY_ONLY\x10\n" + + "\x12\x18\n" + + "\x14OUTCOME_FINAL_SAFETY\x10\v\x12\x16\n" + + "\x12OUTCOME_BENEFICIAL\x10\f\x12\x13\n" + + "\x0fOUTCOME_HARMFUL\x10\r\x12\x13\n" + + "\x0fOUTCOME_EXPIRED\x10\x0e\x12\x1b\n" + + "\x17OUTCOME_STABLE_OBSERVED\x10\x0f\x12&\n" + + "\"OUTCOME_STABLE_EVIDENCE_INCOMPLETE\x10\x10\x12-\n" + + ")OUTCOME_STABLE_REGRESSION_REVIEW_REQUIRED\x10\x11\x12\x15\n" + + "\x11OUTCOME_OBSERVING\x10\x12*\x95\x02\n" + "\x10ExperimentStatus\x12!\n" + "\x1dEXPERIMENT_STATUS_UNSPECIFIED\x10\x00\x12\x1e\n" + "\x1aEXPERIMENT_STATUS_PROPOSED\x10\x01\x12\x1d\n" + @@ -1676,72 +1798,75 @@ func file_xtcp_treatment_v1_treatment_proto_rawDescGZIP() []byte { return file_xtcp_treatment_v1_treatment_proto_rawDescData } -var file_xtcp_treatment_v1_treatment_proto_enumTypes = make([]protoimpl.EnumInfo, 4) +var file_xtcp_treatment_v1_treatment_proto_enumTypes = make([]protoimpl.EnumInfo, 5) var file_xtcp_treatment_v1_treatment_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_xtcp_treatment_v1_treatment_proto_goTypes = []any{ (Role)(0), // 0: xtcp_treatment.v1.Role (AssignmentMode)(0), // 1: xtcp_treatment.v1.AssignmentMode (ApplyStatus)(0), // 2: xtcp_treatment.v1.ApplyStatus - (ExperimentStatus)(0), // 3: xtcp_treatment.v1.ExperimentStatus - (*TCPSettings)(nil), // 4: xtcp_treatment.v1.TCPSettings - (*Treatment)(nil), // 5: xtcp_treatment.v1.Treatment - (*Assignment)(nil), // 6: xtcp_treatment.v1.Assignment - (*DesiredState)(nil), // 7: xtcp_treatment.v1.DesiredState - (*Capabilities)(nil), // 8: xtcp_treatment.v1.Capabilities - (*ApplicationRecord)(nil), // 9: xtcp_treatment.v1.ApplicationRecord - (*LocalState)(nil), // 10: xtcp_treatment.v1.LocalState - (*HostEvidence)(nil), // 11: xtcp_treatment.v1.HostEvidence - (*Evidence)(nil), // 12: xtcp_treatment.v1.Evidence - (*ExperimentPolicy)(nil), // 13: xtcp_treatment.v1.ExperimentPolicy - (*Phase)(nil), // 14: xtcp_treatment.v1.Phase - (*Experiment)(nil), // 15: xtcp_treatment.v1.Experiment - (*timestamppb.Timestamp)(nil), // 16: google.protobuf.Timestamp - (*durationpb.Duration)(nil), // 17: google.protobuf.Duration - (*descriptorpb.MethodOptions)(nil), // 18: google.protobuf.MethodOptions + (Outcome)(0), // 3: xtcp_treatment.v1.Outcome + (ExperimentStatus)(0), // 4: xtcp_treatment.v1.ExperimentStatus + (*TCPSettings)(nil), // 5: xtcp_treatment.v1.TCPSettings + (*Treatment)(nil), // 6: xtcp_treatment.v1.Treatment + (*Assignment)(nil), // 7: xtcp_treatment.v1.Assignment + (*DesiredState)(nil), // 8: xtcp_treatment.v1.DesiredState + (*Capabilities)(nil), // 9: xtcp_treatment.v1.Capabilities + (*ApplicationRecord)(nil), // 10: xtcp_treatment.v1.ApplicationRecord + (*LocalState)(nil), // 11: xtcp_treatment.v1.LocalState + (*HostEvidence)(nil), // 12: xtcp_treatment.v1.HostEvidence + (*Evidence)(nil), // 13: xtcp_treatment.v1.Evidence + (*ExperimentPolicy)(nil), // 14: xtcp_treatment.v1.ExperimentPolicy + (*Phase)(nil), // 15: xtcp_treatment.v1.Phase + (*Experiment)(nil), // 16: xtcp_treatment.v1.Experiment + (*timestamppb.Timestamp)(nil), // 17: google.protobuf.Timestamp + (*durationpb.Duration)(nil), // 18: google.protobuf.Duration + (*descriptorpb.MethodOptions)(nil), // 19: google.protobuf.MethodOptions } var file_xtcp_treatment_v1_treatment_proto_depIdxs = []int32{ - 4, // 0: xtcp_treatment.v1.Treatment.settings:type_name -> xtcp_treatment.v1.TCPSettings - 5, // 1: xtcp_treatment.v1.Assignment.treatment:type_name -> xtcp_treatment.v1.Treatment + 5, // 0: xtcp_treatment.v1.Treatment.settings:type_name -> xtcp_treatment.v1.TCPSettings + 6, // 1: xtcp_treatment.v1.Assignment.treatment:type_name -> xtcp_treatment.v1.Treatment 1, // 2: xtcp_treatment.v1.Assignment.mode:type_name -> xtcp_treatment.v1.AssignmentMode - 16, // 3: xtcp_treatment.v1.Assignment.lease_expires_at:type_name -> google.protobuf.Timestamp - 5, // 4: xtcp_treatment.v1.Assignment.predecessor:type_name -> xtcp_treatment.v1.Treatment - 6, // 5: xtcp_treatment.v1.DesiredState.assignments:type_name -> xtcp_treatment.v1.Assignment - 16, // 6: xtcp_treatment.v1.DesiredState.issued_at:type_name -> google.protobuf.Timestamp - 6, // 7: xtcp_treatment.v1.ApplicationRecord.assignment:type_name -> xtcp_treatment.v1.Assignment + 17, // 3: xtcp_treatment.v1.Assignment.lease_expires_at:type_name -> google.protobuf.Timestamp + 6, // 4: xtcp_treatment.v1.Assignment.predecessor:type_name -> xtcp_treatment.v1.Treatment + 7, // 5: xtcp_treatment.v1.DesiredState.assignments:type_name -> xtcp_treatment.v1.Assignment + 17, // 6: xtcp_treatment.v1.DesiredState.issued_at:type_name -> google.protobuf.Timestamp + 7, // 7: xtcp_treatment.v1.ApplicationRecord.assignment:type_name -> xtcp_treatment.v1.Assignment 2, // 8: xtcp_treatment.v1.ApplicationRecord.status:type_name -> xtcp_treatment.v1.ApplyStatus - 16, // 9: xtcp_treatment.v1.ApplicationRecord.received_at:type_name -> google.protobuf.Timestamp - 16, // 10: xtcp_treatment.v1.ApplicationRecord.attempted_at:type_name -> google.protobuf.Timestamp - 16, // 11: xtcp_treatment.v1.ApplicationRecord.applied_at:type_name -> google.protobuf.Timestamp - 16, // 12: xtcp_treatment.v1.ApplicationRecord.verified_at:type_name -> google.protobuf.Timestamp - 16, // 13: xtcp_treatment.v1.ApplicationRecord.removed_at:type_name -> google.protobuf.Timestamp - 7, // 14: xtcp_treatment.v1.LocalState.desired:type_name -> xtcp_treatment.v1.DesiredState - 9, // 15: xtcp_treatment.v1.LocalState.records:type_name -> xtcp_treatment.v1.ApplicationRecord - 16, // 16: xtcp_treatment.v1.LocalState.updated_at:type_name -> google.protobuf.Timestamp - 16, // 17: xtcp_treatment.v1.Evidence.analyzed_at:type_name -> google.protobuf.Timestamp - 16, // 18: xtcp_treatment.v1.Evidence.window_start:type_name -> google.protobuf.Timestamp - 16, // 19: xtcp_treatment.v1.Evidence.window_end:type_name -> google.protobuf.Timestamp - 11, // 20: xtcp_treatment.v1.Evidence.hosts:type_name -> xtcp_treatment.v1.HostEvidence - 17, // 21: xtcp_treatment.v1.ExperimentPolicy.minimum_observation:type_name -> google.protobuf.Duration - 17, // 22: xtcp_treatment.v1.ExperimentPolicy.maximum_phase:type_name -> google.protobuf.Duration - 17, // 23: xtcp_treatment.v1.ExperimentPolicy.maximum_evidence_age:type_name -> google.protobuf.Duration - 16, // 24: xtcp_treatment.v1.Phase.issued_at:type_name -> google.protobuf.Timestamp - 16, // 25: xtcp_treatment.v1.Phase.observation_started_at:type_name -> google.protobuf.Timestamp - 16, // 26: xtcp_treatment.v1.Phase.evaluated_at:type_name -> google.protobuf.Timestamp - 12, // 27: xtcp_treatment.v1.Phase.evidence:type_name -> xtcp_treatment.v1.Evidence - 5, // 28: xtcp_treatment.v1.Experiment.treatment:type_name -> xtcp_treatment.v1.Treatment - 13, // 29: xtcp_treatment.v1.Experiment.policy:type_name -> xtcp_treatment.v1.ExperimentPolicy - 12, // 30: xtcp_treatment.v1.Experiment.baseline:type_name -> xtcp_treatment.v1.Evidence - 14, // 31: xtcp_treatment.v1.Experiment.phases:type_name -> xtcp_treatment.v1.Phase - 3, // 32: xtcp_treatment.v1.Experiment.status:type_name -> xtcp_treatment.v1.ExperimentStatus - 16, // 33: xtcp_treatment.v1.Experiment.proposed_at:type_name -> google.protobuf.Timestamp - 12, // 34: xtcp_treatment.v1.Experiment.latest_evidence:type_name -> xtcp_treatment.v1.Evidence - 18, // 35: xtcp_treatment.v1.role:extendee -> google.protobuf.MethodOptions - 0, // 36: xtcp_treatment.v1.role:type_name -> xtcp_treatment.v1.Role - 37, // [37:37] is the sub-list for method output_type - 37, // [37:37] is the sub-list for method input_type - 36, // [36:37] is the sub-list for extension type_name - 35, // [35:36] is the sub-list for extension extendee - 0, // [0:35] is the sub-list for field type_name + 17, // 9: xtcp_treatment.v1.ApplicationRecord.received_at:type_name -> google.protobuf.Timestamp + 17, // 10: xtcp_treatment.v1.ApplicationRecord.attempted_at:type_name -> google.protobuf.Timestamp + 17, // 11: xtcp_treatment.v1.ApplicationRecord.applied_at:type_name -> google.protobuf.Timestamp + 17, // 12: xtcp_treatment.v1.ApplicationRecord.verified_at:type_name -> google.protobuf.Timestamp + 17, // 13: xtcp_treatment.v1.ApplicationRecord.removed_at:type_name -> google.protobuf.Timestamp + 8, // 14: xtcp_treatment.v1.LocalState.desired:type_name -> xtcp_treatment.v1.DesiredState + 10, // 15: xtcp_treatment.v1.LocalState.records:type_name -> xtcp_treatment.v1.ApplicationRecord + 17, // 16: xtcp_treatment.v1.LocalState.updated_at:type_name -> google.protobuf.Timestamp + 17, // 17: xtcp_treatment.v1.Evidence.analyzed_at:type_name -> google.protobuf.Timestamp + 17, // 18: xtcp_treatment.v1.Evidence.window_start:type_name -> google.protobuf.Timestamp + 17, // 19: xtcp_treatment.v1.Evidence.window_end:type_name -> google.protobuf.Timestamp + 12, // 20: xtcp_treatment.v1.Evidence.hosts:type_name -> xtcp_treatment.v1.HostEvidence + 18, // 21: xtcp_treatment.v1.ExperimentPolicy.minimum_observation:type_name -> google.protobuf.Duration + 18, // 22: xtcp_treatment.v1.ExperimentPolicy.maximum_phase:type_name -> google.protobuf.Duration + 18, // 23: xtcp_treatment.v1.ExperimentPolicy.maximum_evidence_age:type_name -> google.protobuf.Duration + 17, // 24: xtcp_treatment.v1.Phase.issued_at:type_name -> google.protobuf.Timestamp + 17, // 25: xtcp_treatment.v1.Phase.observation_started_at:type_name -> google.protobuf.Timestamp + 17, // 26: xtcp_treatment.v1.Phase.evaluated_at:type_name -> google.protobuf.Timestamp + 13, // 27: xtcp_treatment.v1.Phase.evidence:type_name -> xtcp_treatment.v1.Evidence + 3, // 28: xtcp_treatment.v1.Phase.outcome:type_name -> xtcp_treatment.v1.Outcome + 6, // 29: xtcp_treatment.v1.Experiment.treatment:type_name -> xtcp_treatment.v1.Treatment + 14, // 30: xtcp_treatment.v1.Experiment.policy:type_name -> xtcp_treatment.v1.ExperimentPolicy + 13, // 31: xtcp_treatment.v1.Experiment.baseline:type_name -> xtcp_treatment.v1.Evidence + 15, // 32: xtcp_treatment.v1.Experiment.phases:type_name -> xtcp_treatment.v1.Phase + 4, // 33: xtcp_treatment.v1.Experiment.status:type_name -> xtcp_treatment.v1.ExperimentStatus + 17, // 34: xtcp_treatment.v1.Experiment.proposed_at:type_name -> google.protobuf.Timestamp + 13, // 35: xtcp_treatment.v1.Experiment.latest_evidence:type_name -> xtcp_treatment.v1.Evidence + 3, // 36: xtcp_treatment.v1.Experiment.latest_outcome:type_name -> xtcp_treatment.v1.Outcome + 19, // 37: xtcp_treatment.v1.role:extendee -> google.protobuf.MethodOptions + 0, // 38: xtcp_treatment.v1.role:type_name -> xtcp_treatment.v1.Role + 39, // [39:39] is the sub-list for method output_type + 39, // [39:39] is the sub-list for method input_type + 38, // [38:39] is the sub-list for extension type_name + 37, // [37:38] is the sub-list for extension extendee + 0, // [0:37] is the sub-list for field type_name } func init() { file_xtcp_treatment_v1_treatment_proto_init() } @@ -1755,7 +1880,7 @@ func file_xtcp_treatment_v1_treatment_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_xtcp_treatment_v1_treatment_proto_rawDesc), len(file_xtcp_treatment_v1_treatment_proto_rawDesc)), - NumEnums: 4, + NumEnums: 5, NumMessages: 12, NumExtensions: 1, NumServices: 0, diff --git a/gen/go/xtcp_treatment/treatment_vtproto.pb.go b/gen/go/xtcp_treatment/treatment_vtproto.pb.go index 72165a706..d69cae78d 100644 --- a/gen/go/xtcp_treatment/treatment_vtproto.pb.go +++ b/gen/go/xtcp_treatment/treatment_vtproto.pb.go @@ -258,13 +258,6 @@ func (m *Assignment) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i-- dAtA[i] = 0x12 } - if len(m.Id) > 0 { - i -= len(m.Id) - copy(dAtA[i:], m.Id) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Id))) - i-- - dAtA[i] = 0xa - } return len(dAtA) - i, nil } @@ -963,12 +956,10 @@ func (m *Phase) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i-- dAtA[i] = 0x3a } - if len(m.Outcome) > 0 { - i -= len(m.Outcome) - copy(dAtA[i:], m.Outcome) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Outcome))) + if m.Outcome != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Outcome)) i-- - dAtA[i] = 0x32 + dAtA[i] = 0x30 } if m.Evidence != nil { size, err := m.Evidence.MarshalToSizedBufferVT(dAtA[:i]) @@ -1048,12 +1039,10 @@ func (m *Experiment) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } - if len(m.LatestOutcome) > 0 { - i -= len(m.LatestOutcome) - copy(dAtA[i:], m.LatestOutcome) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.LatestOutcome))) + if m.LatestOutcome != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.LatestOutcome)) i-- - dAtA[i] = 0x6a + dAtA[i] = 0x68 } if m.LatestEvidence != nil { size, err := m.LatestEvidence.MarshalToSizedBufferVT(dAtA[:i]) @@ -1226,10 +1215,6 @@ func (m *Assignment) SizeVT() (n int) { } var l int _ = l - l = len(m.Id) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } l = len(m.ExperimentId) if l > 0 { n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) @@ -1546,9 +1531,8 @@ func (m *Phase) SizeVT() (n int) { l = m.Evidence.SizeVT() n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - l = len(m.Outcome) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + if m.Outcome != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Outcome)) } l = len(m.Reviewer) if l > 0 { @@ -1618,9 +1602,8 @@ func (m *Experiment) SizeVT() (n int) { l = m.LatestEvidence.SizeVT() n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } - l = len(m.LatestOutcome) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + if m.LatestOutcome != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.LatestOutcome)) } n += len(m.unknownFields) return n @@ -2051,38 +2034,6 @@ func (m *Assignment) UnmarshalVT(dAtA []byte) error { return fmt.Errorf("proto: Assignment: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Id = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ExperimentId", wireType) @@ -4250,10 +4201,10 @@ func (m *Phase) UnmarshalVT(dAtA []byte) error { } iNdEx = postIndex case 6: - if wireType != 2 { + if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Outcome", wireType) } - var stringLen uint64 + m.Outcome = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -4263,24 +4214,11 @@ func (m *Phase) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + m.Outcome |= Outcome(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Outcome = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex case 7: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Reviewer", wireType) @@ -4777,10 +4715,10 @@ func (m *Experiment) UnmarshalVT(dAtA []byte) error { } iNdEx = postIndex case 13: - if wireType != 2 { + if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field LatestOutcome", wireType) } - var stringLen uint64 + m.LatestOutcome = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return protohelpers.ErrIntOverflow @@ -4790,24 +4728,11 @@ func (m *Experiment) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + m.LatestOutcome |= Outcome(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.LatestOutcome = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/gen/python/xtcp_treatment/v1/operator_pb2.py b/gen/python/xtcp_treatment/v1/operator_pb2.py index 8c145d282..0f2e0d22f 100644 --- a/gen/python/xtcp_treatment/v1/operator_pb2.py +++ b/gen/python/xtcp_treatment/v1/operator_pb2.py @@ -27,7 +27,7 @@ from xtcp_treatment.v1 import treatment_pb2 as xtcp__treatment_dot_v1_dot_treatment__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n xtcp_treatment/v1/operator.proto\x12\x11xtcp_treatment.v1\x1a\x1b\x62uf/validate/validate.proto\x1a\x1dxtcp_treatment/v1/agent.proto\x1a!xtcp_treatment/v1/treatment.proto\"\xbb\x02\n\x0eProposeRequest\x12\x45\n\nexperiment\x18\x01 \x01(\x0b\x32\x1d.xtcp_treatment.v1.ExperimentB\x06\xbaH\x03\xc8\x01\x01R\nexperiment\x12H\n\nrequest_id\x18\x02 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\trequestId:\x97\x01\xbaH\x93\x01\x1a\x90\x01\n\x19propose.request_id_suffix\x12Mrequest_id must not end with .result (reserved for the paired response event)\x1a$!this.request_id.endsWith(\'.result\')\"P\n\x0fProposeResponse\x12=\n\nexperiment\x18\x01 \x01(\x0b\x32\x1d.xtcp_treatment.v1.ExperimentR\nexperiment\"\x8d\x01\n\x0e\x41nalyzeRequest\x12N\n\rexperiment_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x0c\x65xperimentId\x12+\n\x11\x65xpected_revision\x18\x02 \x01(\x04R\x10\x65xpectedRevision\"d\n\x0f\x41nalyzeResponse\x12\x37\n\x08\x65vidence\x18\x01 \x01(\x0b\x32\x1b.xtcp_treatment.v1.EvidenceR\x08\x65vidence\x12\x18\n\x07outcome\x18\x02 \x01(\tR\x07outcome\"\xe6\x03\n\rReviewRequest\x12N\n\rexperiment_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x0c\x65xperimentId\x12+\n\x11\x65xpected_revision\x18\x02 \x01(\x04R\x10\x65xpectedRevision\x12\x18\n\x07\x61pprove\x18\x03 \x01(\x08R\x07\x61pprove\x12\"\n\x06reason\x18\x04 \x01(\tB\n\xbaH\x07r\x05\x10\x01\x18\x80 R\x06reason\x12H\n\nrequest_id\x18\x05 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\trequestId\x12\x37\n\x08\x65vidence\x18\x06 \x01(\x0b\x32\x1b.xtcp_treatment.v1.EvidenceR\x08\x65vidence:\x96\x01\xbaH\x92\x01\x1a\x8f\x01\n\x18review.request_id_suffix\x12Mrequest_id must not end with .result (reserved for the paired response event)\x1a$!this.request_id.endsWith(\'.result\')\"O\n\x0eReviewResponse\x12=\n\nexperiment\x18\x01 \x01(\x0b\x32\x1d.xtcp_treatment.v1.ExperimentR\nexperiment\"\xba\x06\n\x0e\x43ontrolRequest\x12Q\n\rexperiment_id\x18\x01 \x01(\tB,\xbaH)r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$\xd8\x01\x01R\x0c\x65xperimentId\x12G\n\x08\x61gent_id\x18\x02 \x01(\tB,\xbaH)r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$\xd8\x01\x01R\x07\x61gentId\x12H\n\x08location\x18\x03 \x01(\tB,\xbaH)r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$\xd8\x01\x01R\x08location\x12\x14\n\x05\x66leet\x18\x04 \x01(\x08R\x05\x66leet\x12\x44\n\x06\x61\x63tion\x18\x05 \x01(\x0e\x32 .xtcp_treatment.v1.ControlActionB\n\xbaH\x07\x82\x01\x04\x10\x01 \x00R\x06\x61\x63tion\x12\"\n\x06reason\x18\x06 \x01(\tB\n\xbaH\x07r\x05\x10\x01\x18\x80 R\x06reason\x12H\n\nrequest_id\x18\x07 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\trequestId:\xf7\x02\xbaH\xf3\x02\x1a\x90\x01\n\x19\x63ontrol.request_id_suffix\x12Mrequest_id must not end with .result (reserved for the paired response event)\x1a$!this.request_id.endsWith(\'.result\')\x1a\xdd\x01\n\x14\x63ontrol.one_selector\x12\x45\x65xactly one of experiment_id, agent_id, location or fleet is required\x1a~(this.fleet ? 1 : 0) + (this.experiment_id != \'\' ? 1 : 0) + (this.agent_id != \'\' ? 1 : 0) + (this.location != \'\' ? 1 : 0) == 1\":\n\x0f\x43ontrolResponse\x12\'\n\x0f\x61\x66\x66\x65\x63ted_agents\x18\x01 \x01(\rR\x0e\x61\x66\x66\x65\x63tedAgents\"\xac\x01\n\x0eInspectRequest\x12Q\n\rexperiment_id\x18\x01 \x01(\tB,\xbaH)r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$\xd8\x01\x01R\x0c\x65xperimentId\x12G\n\x08\x61gent_id\x18\x02 \x01(\tB,\xbaH)r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$\xd8\x01\x01R\x07\x61gentId\"\xcc\x01\n\x0fInspectResponse\x12?\n\x0b\x65xperiments\x18\x01 \x03(\x0b\x32\x1d.xtcp_treatment.v1.ExperimentR\x0b\x65xperiments\x12\x39\n\x07\x64\x65sired\x18\x02 \x03(\x0b\x32\x1f.xtcp_treatment.v1.DesiredStateR\x07\x64\x65sired\x12=\n\x08observed\x18\x03 \x03(\x0b\x32!.xtcp_treatment.v1.CheckInRequestR\x08observed*\xb8\x01\n\rControlAction\x12\x1e\n\x1a\x43ONTROL_ACTION_UNSPECIFIED\x10\x00\x12\x18\n\x14\x43ONTROL_ACTION_PAUSE\x10\x01\x12\x1a\n\x16\x43ONTROL_ACTION_SUSPEND\x10\x02\x12\x19\n\x15\x43ONTROL_ACTION_RESUME\x10\x03\x12\x1b\n\x17\x43ONTROL_ACTION_ROLLBACK\x10\x04\x12\x19\n\x15\x43ONTROL_ACTION_RETIRE\x10\x05\x32\xc6\x03\n\x0fOperatorService\x12V\n\x07Propose\x12!.xtcp_treatment.v1.ProposeRequest\x1a\".xtcp_treatment.v1.ProposeResponse\"\x04\xd0\xf3\x18\x02\x12V\n\x07\x41nalyze\x12!.xtcp_treatment.v1.AnalyzeRequest\x1a\".xtcp_treatment.v1.AnalyzeResponse\"\x04\xd0\xf3\x18\x02\x12S\n\x06Review\x12 .xtcp_treatment.v1.ReviewRequest\x1a!.xtcp_treatment.v1.ReviewResponse\"\x04\xd0\xf3\x18\x02\x12V\n\x07\x43ontrol\x12!.xtcp_treatment.v1.ControlRequest\x1a\".xtcp_treatment.v1.ControlResponse\"\x04\xd0\xf3\x18\x02\x12V\n\x07Inspect\x12!.xtcp_treatment.v1.InspectRequest\x1a\".xtcp_treatment.v1.InspectResponse\"\x04\xd0\xf3\x18\x02\x42\xbf\x01\n\x15\x63om.xtcp_treatment.v1B\rOperatorProtoP\x01Z6github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment\xa2\x02\x03XXX\xaa\x02\x10XtcpTreatment.V1\xca\x02\x10XtcpTreatment\\V1\xe2\x02\x1cXtcpTreatment\\V1\\GPBMetadata\xea\x02\x11XtcpTreatment::V1b\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n xtcp_treatment/v1/operator.proto\x12\x11xtcp_treatment.v1\x1a\x1b\x62uf/validate/validate.proto\x1a\x1dxtcp_treatment/v1/agent.proto\x1a!xtcp_treatment/v1/treatment.proto\"\xbb\x02\n\x0eProposeRequest\x12\x45\n\nexperiment\x18\x01 \x01(\x0b\x32\x1d.xtcp_treatment.v1.ExperimentB\x06\xbaH\x03\xc8\x01\x01R\nexperiment\x12H\n\nrequest_id\x18\x02 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\trequestId:\x97\x01\xbaH\x93\x01\x1a\x90\x01\n\x19propose.request_id_suffix\x12Mrequest_id must not end with .result (reserved for the paired response event)\x1a$!this.request_id.endsWith(\'.result\')\"P\n\x0fProposeResponse\x12=\n\nexperiment\x18\x01 \x01(\x0b\x32\x1d.xtcp_treatment.v1.ExperimentR\nexperiment\"\x8d\x01\n\x0e\x41nalyzeRequest\x12N\n\rexperiment_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x0c\x65xperimentId\x12+\n\x11\x65xpected_revision\x18\x02 \x01(\x04R\x10\x65xpectedRevision\"\x80\x01\n\x0f\x41nalyzeResponse\x12\x37\n\x08\x65vidence\x18\x01 \x01(\x0b\x32\x1b.xtcp_treatment.v1.EvidenceR\x08\x65vidence\x12\x34\n\x07outcome\x18\x02 \x01(\x0e\x32\x1a.xtcp_treatment.v1.OutcomeR\x07outcome\"\xe6\x03\n\rReviewRequest\x12N\n\rexperiment_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x0c\x65xperimentId\x12+\n\x11\x65xpected_revision\x18\x02 \x01(\x04R\x10\x65xpectedRevision\x12\x18\n\x07\x61pprove\x18\x03 \x01(\x08R\x07\x61pprove\x12\"\n\x06reason\x18\x04 \x01(\tB\n\xbaH\x07r\x05\x10\x01\x18\x80 R\x06reason\x12H\n\nrequest_id\x18\x05 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\trequestId\x12\x37\n\x08\x65vidence\x18\x06 \x01(\x0b\x32\x1b.xtcp_treatment.v1.EvidenceR\x08\x65vidence:\x96\x01\xbaH\x92\x01\x1a\x8f\x01\n\x18review.request_id_suffix\x12Mrequest_id must not end with .result (reserved for the paired response event)\x1a$!this.request_id.endsWith(\'.result\')\"O\n\x0eReviewResponse\x12=\n\nexperiment\x18\x01 \x01(\x0b\x32\x1d.xtcp_treatment.v1.ExperimentR\nexperiment\"\xba\x06\n\x0e\x43ontrolRequest\x12Q\n\rexperiment_id\x18\x01 \x01(\tB,\xbaH)r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$\xd8\x01\x01R\x0c\x65xperimentId\x12G\n\x08\x61gent_id\x18\x02 \x01(\tB,\xbaH)r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$\xd8\x01\x01R\x07\x61gentId\x12H\n\x08location\x18\x03 \x01(\tB,\xbaH)r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$\xd8\x01\x01R\x08location\x12\x14\n\x05\x66leet\x18\x04 \x01(\x08R\x05\x66leet\x12\x44\n\x06\x61\x63tion\x18\x05 \x01(\x0e\x32 .xtcp_treatment.v1.ControlActionB\n\xbaH\x07\x82\x01\x04\x10\x01 \x00R\x06\x61\x63tion\x12\"\n\x06reason\x18\x06 \x01(\tB\n\xbaH\x07r\x05\x10\x01\x18\x80 R\x06reason\x12H\n\nrequest_id\x18\x07 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\trequestId:\xf7\x02\xbaH\xf3\x02\x1a\x90\x01\n\x19\x63ontrol.request_id_suffix\x12Mrequest_id must not end with .result (reserved for the paired response event)\x1a$!this.request_id.endsWith(\'.result\')\x1a\xdd\x01\n\x14\x63ontrol.one_selector\x12\x45\x65xactly one of experiment_id, agent_id, location or fleet is required\x1a~(this.fleet ? 1 : 0) + (this.experiment_id != \'\' ? 1 : 0) + (this.agent_id != \'\' ? 1 : 0) + (this.location != \'\' ? 1 : 0) == 1\":\n\x0f\x43ontrolResponse\x12\'\n\x0f\x61\x66\x66\x65\x63ted_agents\x18\x01 \x01(\rR\x0e\x61\x66\x66\x65\x63tedAgents\"\xac\x01\n\x0eInspectRequest\x12Q\n\rexperiment_id\x18\x01 \x01(\tB,\xbaH)r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$\xd8\x01\x01R\x0c\x65xperimentId\x12G\n\x08\x61gent_id\x18\x02 \x01(\tB,\xbaH)r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$\xd8\x01\x01R\x07\x61gentId\"\xcc\x01\n\x0fInspectResponse\x12?\n\x0b\x65xperiments\x18\x01 \x03(\x0b\x32\x1d.xtcp_treatment.v1.ExperimentR\x0b\x65xperiments\x12\x39\n\x07\x64\x65sired\x18\x02 \x03(\x0b\x32\x1f.xtcp_treatment.v1.DesiredStateR\x07\x64\x65sired\x12=\n\x08observed\x18\x03 \x03(\x0b\x32!.xtcp_treatment.v1.CheckInRequestR\x08observed*\xb8\x01\n\rControlAction\x12\x1e\n\x1a\x43ONTROL_ACTION_UNSPECIFIED\x10\x00\x12\x18\n\x14\x43ONTROL_ACTION_PAUSE\x10\x01\x12\x1a\n\x16\x43ONTROL_ACTION_SUSPEND\x10\x02\x12\x19\n\x15\x43ONTROL_ACTION_RESUME\x10\x03\x12\x1b\n\x17\x43ONTROL_ACTION_ROLLBACK\x10\x04\x12\x19\n\x15\x43ONTROL_ACTION_RETIRE\x10\x05\x32\xc6\x03\n\x0fOperatorService\x12V\n\x07Propose\x12!.xtcp_treatment.v1.ProposeRequest\x1a\".xtcp_treatment.v1.ProposeResponse\"\x04\xd0\xf3\x18\x02\x12V\n\x07\x41nalyze\x12!.xtcp_treatment.v1.AnalyzeRequest\x1a\".xtcp_treatment.v1.AnalyzeResponse\"\x04\xd0\xf3\x18\x02\x12S\n\x06Review\x12 .xtcp_treatment.v1.ReviewRequest\x1a!.xtcp_treatment.v1.ReviewResponse\"\x04\xd0\xf3\x18\x02\x12V\n\x07\x43ontrol\x12!.xtcp_treatment.v1.ControlRequest\x1a\".xtcp_treatment.v1.ControlResponse\"\x04\xd0\xf3\x18\x02\x12V\n\x07Inspect\x12!.xtcp_treatment.v1.InspectRequest\x1a\".xtcp_treatment.v1.InspectResponse\"\x04\xd0\xf3\x18\x02\x42\xbf\x01\n\x15\x63om.xtcp_treatment.v1B\rOperatorProtoP\x01Z6github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment\xa2\x02\x03XXX\xaa\x02\x10XtcpTreatment.V1\xca\x02\x10XtcpTreatment\\V1\xe2\x02\x1cXtcpTreatment\\V1\\GPBMetadata\xea\x02\x11XtcpTreatment::V1b\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -79,28 +79,28 @@ _globals['_OPERATORSERVICE'].methods_by_name['Control']._serialized_options = b'\320\363\030\002' _globals['_OPERATORSERVICE'].methods_by_name['Inspect']._loaded_options = None _globals['_OPERATORSERVICE'].methods_by_name['Inspect']._serialized_options = b'\320\363\030\002' - _globals['_CONTROLACTION']._serialized_start=2638 - _globals['_CONTROLACTION']._serialized_end=2822 + _globals['_CONTROLACTION']._serialized_start=2667 + _globals['_CONTROLACTION']._serialized_end=2851 _globals['_PROPOSEREQUEST']._serialized_start=151 _globals['_PROPOSEREQUEST']._serialized_end=466 _globals['_PROPOSERESPONSE']._serialized_start=468 _globals['_PROPOSERESPONSE']._serialized_end=548 _globals['_ANALYZEREQUEST']._serialized_start=551 _globals['_ANALYZEREQUEST']._serialized_end=692 - _globals['_ANALYZERESPONSE']._serialized_start=694 - _globals['_ANALYZERESPONSE']._serialized_end=794 - _globals['_REVIEWREQUEST']._serialized_start=797 - _globals['_REVIEWREQUEST']._serialized_end=1283 - _globals['_REVIEWRESPONSE']._serialized_start=1285 - _globals['_REVIEWRESPONSE']._serialized_end=1364 - _globals['_CONTROLREQUEST']._serialized_start=1367 - _globals['_CONTROLREQUEST']._serialized_end=2193 - _globals['_CONTROLRESPONSE']._serialized_start=2195 - _globals['_CONTROLRESPONSE']._serialized_end=2253 - _globals['_INSPECTREQUEST']._serialized_start=2256 - _globals['_INSPECTREQUEST']._serialized_end=2428 - _globals['_INSPECTRESPONSE']._serialized_start=2431 - _globals['_INSPECTRESPONSE']._serialized_end=2635 - _globals['_OPERATORSERVICE']._serialized_start=2825 - _globals['_OPERATORSERVICE']._serialized_end=3279 + _globals['_ANALYZERESPONSE']._serialized_start=695 + _globals['_ANALYZERESPONSE']._serialized_end=823 + _globals['_REVIEWREQUEST']._serialized_start=826 + _globals['_REVIEWREQUEST']._serialized_end=1312 + _globals['_REVIEWRESPONSE']._serialized_start=1314 + _globals['_REVIEWRESPONSE']._serialized_end=1393 + _globals['_CONTROLREQUEST']._serialized_start=1396 + _globals['_CONTROLREQUEST']._serialized_end=2222 + _globals['_CONTROLRESPONSE']._serialized_start=2224 + _globals['_CONTROLRESPONSE']._serialized_end=2282 + _globals['_INSPECTREQUEST']._serialized_start=2285 + _globals['_INSPECTREQUEST']._serialized_end=2457 + _globals['_INSPECTRESPONSE']._serialized_start=2460 + _globals['_INSPECTRESPONSE']._serialized_end=2664 + _globals['_OPERATORSERVICE']._serialized_start=2854 + _globals['_OPERATORSERVICE']._serialized_end=3308 # @@protoc_insertion_point(module_scope) diff --git a/gen/python/xtcp_treatment/v1/operator_pb2.pyi b/gen/python/xtcp_treatment/v1/operator_pb2.pyi index ba43e23e3..08dbfc580 100644 --- a/gen/python/xtcp_treatment/v1/operator_pb2.pyi +++ b/gen/python/xtcp_treatment/v1/operator_pb2.pyi @@ -52,8 +52,8 @@ class AnalyzeResponse(_message.Message): EVIDENCE_FIELD_NUMBER: _ClassVar[int] OUTCOME_FIELD_NUMBER: _ClassVar[int] evidence: _treatment_pb2.Evidence - outcome: str - def __init__(self, evidence: _Optional[_Union[_treatment_pb2.Evidence, _Mapping]] = ..., outcome: _Optional[str] = ...) -> None: ... + outcome: _treatment_pb2.Outcome + def __init__(self, evidence: _Optional[_Union[_treatment_pb2.Evidence, _Mapping]] = ..., outcome: _Optional[_Union[_treatment_pb2.Outcome, str]] = ...) -> None: ... class ReviewRequest(_message.Message): __slots__ = ("experiment_id", "expected_revision", "approve", "reason", "request_id", "evidence") diff --git a/gen/python/xtcp_treatment/v1/treatment_pb2.py b/gen/python/xtcp_treatment/v1/treatment_pb2.py index 0abee4601..4437747fb 100644 --- a/gen/python/xtcp_treatment/v1/treatment_pb2.py +++ b/gen/python/xtcp_treatment/v1/treatment_pb2.py @@ -28,7 +28,7 @@ from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!xtcp_treatment/v1/treatment.proto\x12\x11xtcp_treatment.v1\x1a\x1b\x62uf/validate/validate.proto\x1a google/protobuf/descriptor.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\x8d\x07\n\x0bTCPSettings\x12Y\n\x12\x63ongestion_control\x18\x01 \x01(\tB%\xbaH\"r \x18\x0f\x32\x1c^[a-zA-Z0-9][a-zA-Z0-9_.-]*$H\x00R\x11\x63ongestionControl\x88\x01\x01\x12\'\n\x0f\x63ongestion_lock\x18\x02 \x01(\x08R\x0e\x63ongestionLock\x12.\n\nrto_min_ms\x18\x03 \x01(\rB\x0b\xbaH\x08*\x06\x18\xc0\xa9\x07(\x01H\x01R\x08rtoMinMs\x88\x01\x01\x12K\n\x19initial_congestion_window\x18\x04 \x01(\rB\n\xbaH\x07*\x05\x18\x80\x08(\x01H\x02R\x17initialCongestionWindow\x88\x01\x01\x12\x45\n\x16initial_receive_window\x18\x05 \x01(\rB\n\xbaH\x07*\x05\x18\x80\x08(\x01H\x03R\x14initialReceiveWindow\x88\x01\x01\x12 \n\tquick_ack\x18\x06 \x01(\x08H\x04R\x08quickAck\x88\x01\x01\x12\x15\n\x03\x65\x63n\x18\x07 \x01(\x08H\x05R\x03\x65\x63n\x88\x01\x01:\x87\x03\xbaH\x83\x03\x1a\xf3\x01\n\x16tcp_settings.non_empty\x12/a treatment must set at least one TCP parameter\x1a\xa7\x01has(this.congestion_control) || has(this.rto_min_ms) || has(this.initial_congestion_window) || has(this.initial_receive_window) || has(this.quick_ack) || has(this.ecn)\x1a\x8a\x01\n$tcp_settings.lock_requires_algorithm\x12+congestion_lock requires congestion_control\x1a\x35!this.congestion_lock || has(this.congestion_control)B\x15\n\x13_congestion_controlB\r\n\x0b_rto_min_msB\x1c\n\x1a_initial_congestion_windowB\x19\n\x17_initial_receive_windowB\x0c\n\n_quick_ackB\x06\n\x04_ecn\"\xd8\x03\n\tTreatment\x12\x39\n\x02id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x02id\x12#\n\x08revision\x18\x02 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\x08revision\x12,\n\x0b\x64\x65stination\x18\x03 \x01(\tB\n\xbaH\x07r\x05\x18\x31\xe8\x01\x01R\x0b\x64\x65stination\x12\x42\n\x08settings\x18\x04 \x01(\x0b\x32\x1e.xtcp_treatment.v1.TCPSettingsB\x06\xbaH\x03\xc8\x01\x01R\x08settings\x12.\n\x13\x65\x63n_fabric_verified\x18\x05 \x01(\x08R\x11\x65\x63nFabricVerified:\xc8\x01\xbaH\xc4\x01\x1a\xc1\x01\n\x1ctreatment.dctcp_requires_ecn\x12\x41\x44\x43TCP requires independently verified ECN fabric and explicit ECN\x1a^this.settings.congestion_control != \'dctcp\' || (this.ecn_fabric_verified && this.settings.ecn)\"\xfd\x05\n\nAssignment\x12\x39\n\x02id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x02id\x12N\n\rexperiment_id\x18\x02 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x0c\x65xperimentId\x12\x42\n\ttreatment\x18\x03 \x01(\x0b\x32\x1c.xtcp_treatment.v1.TreatmentB\x06\xbaH\x03\xc8\x01\x01R\ttreatment\x12\x41\n\x04mode\x18\x04 \x01(\x0e\x32!.xtcp_treatment.v1.AssignmentModeB\n\xbaH\x07\x82\x01\x04\x10\x01 \x00R\x04mode\x12\x44\n\x10lease_expires_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0eleaseExpiresAt\x12>\n\x0bpredecessor\x18\x06 \x01(\x0b\x32\x1c.xtcp_treatment.v1.TreatmentR\x0bpredecessor\x12\'\n\ngeneration\x18\x07 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\ngeneration:\xad\x02\xbaH\xa9\x02\x1a}\n\x1f\x61ssignment.trial_requires_lease\x12,a trial assignment requires lease_expires_at\x1a,this.mode != 1 || has(this.lease_expires_at)\x1a\xa7\x01\n\"assignment.predecessor_destination\x12+predecessor must cover the same destination\x1aT!has(this.predecessor) || this.predecessor.destination == this.treatment.destination\"\xa4\x02\n\x0c\x44\x65siredState\x12\x44\n\x08\x61gent_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x07\x61gentId\x12#\n\x08revision\x18\x02 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\x08revision\x12\x1c\n\tsuspended\x18\x03 \x01(\x08R\tsuspended\x12J\n\x0b\x61ssignments\x18\x04 \x03(\x0b\x32\x1d.xtcp_treatment.v1.AssignmentB\t\xbaH\x06\x92\x01\x03\x10\x80 R\x0b\x61ssignments\x12?\n\tissued_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\x08issuedAt\"\xa3\x02\n\x0c\x43\x61pabilities\x12\x42\n\x07\x62oot_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x06\x62ootId\x12/\n\x0ekernel_release\x18\x02 \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x02R\rkernelRelease\x12\x43\n\x15\x63ongestion_algorithms\x18\x03 \x03(\tB\x0e\xbaH\x0b\x92\x01\x08\x10@\"\x04r\x02\x18\x0fR\x14\x63ongestionAlgorithms\x12.\n\x13simple_host_routing\x18\x04 \x01(\x08R\x11simpleHostRouting\x12)\n\x10\x61\x63tuator_enabled\x18\x05 \x01(\x08R\x0f\x61\x63tuatorEnabled\"\xa8\x05\n\x11\x41pplicationRecord\x12\x45\n\nassignment\x18\x01 \x01(\x0b\x32\x1d.xtcp_treatment.v1.AssignmentB\x06\xbaH\x03\xc8\x01\x01R\nassignment\x12)\n\x10\x64\x65sired_revision\x18\x02 \x01(\x04R\x0f\x64\x65siredRevision\x12@\n\x06status\x18\x03 \x01(\x0e\x32\x1e.xtcp_treatment.v1.ApplyStatusB\x08\xbaH\x05\x82\x01\x02\x10\x01R\x06status\x12;\n\x0breceived_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\nreceivedAt\x12=\n\x0c\x61ttempted_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0b\x61ttemptedAt\x12\x39\n\napplied_at\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tappliedAt\x12;\n\x0bverified_at\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\nverifiedAt\x12\x39\n\nremoved_at\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tremovedAt\x12\x1e\n\x05\x65rror\x18\t \x01(\tB\x08\xbaH\x05r\x03\x18\x80 R\x05\x65rror\x12\x31\n\x0finstalled_route\x18\n \x01(\x0c\x42\x08\xbaH\x05z\x03\x18\x80 R\x0einstalledRoute\x12\x34\n\x11installed_boot_id\x18\x0b \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x01R\x0finstalledBootId\x12\'\n\x0f\x65xpired_latched\x18\x0c \x01(\x08R\x0e\x65xpiredLatched\"\xb1\x02\n\nLocalState\x12\x39\n\x07\x64\x65sired\x18\x01 \x01(\x0b\x32\x1f.xtcp_treatment.v1.DesiredStateR\x07\x64\x65sired\x12+\n\x11locally_suspended\x18\x02 \x01(\x08R\x10locallySuspended\x12\x35\n\x11suspension_reason\x18\x03 \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x08R\x10suspensionReason\x12I\n\x07records\x18\x04 \x03(\x0b\x32$.xtcp_treatment.v1.ApplicationRecordB\t\xbaH\x06\x92\x01\x03\x10\x80@R\x07records\x12\x39\n\nupdated_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tupdatedAt\"\x89\x04\n\x0cHostEvidence\x12\x44\n\x08\x61gent_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x07\x61gentId\x12\x18\n\x07sockets\x18\x02 \x01(\x04R\x07sockets\x12\x1f\n\x0b\x62ytes_acked\x18\x03 \x01(\x04R\nbytesAcked\x12\x31\n\x0bgoodput_bps\x18\x04 \x01(\x01\x42\x10\xbaH\r\x12\x0b)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\ngoodputBps\x12\x43\n\x14retransmission_ratio\x18\x05 \x01(\x01\x42\x10\xbaH\r\x12\x0b)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x13retransmissionRatio\x12.\n\nrtt_p95_ms\x18\x06 \x01(\x01\x42\x10\xbaH\r\x12\x0b)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x08rttP95Ms\x12\'\n\x0f\x65xposed_sockets\x18\x07 \x01(\x04R\x0e\x65xposedSockets\x12-\n\x12\x63ollection_healthy\x18\x08 \x01(\x08R\x11\x63ollectionHealthy:x\xbaHu\x1as\n$host_evidence.exposed_within_sockets\x12%exposed_sockets cannot exceed sockets\x1a$this.exposed_sockets <= this.sockets\"\xbc\x05\n\x08\x45vidence\x12\x39\n\x02id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x02id\x12\x43\n\x0b\x61nalyzed_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\nanalyzedAt\x12\x45\n\x0cwindow_start\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\x0bwindowStart\x12\x41\n\nwindow_end\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\twindowEnd\x12/\n\rquery_version\x18\x05 \x01(\tB\n\xbaH\x07r\x05\x10\x01\x18\x80\x02R\x0cqueryVersion\x12\x37\n\x12\x65nrichment_version\x18\x06 \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x02R\x11\x65nrichmentVersion\x12\x42\n\x05hosts\x18\x07 \x03(\x0b\x32\x1f.xtcp_treatment.v1.HostEvidenceB\x0b\xbaH\x08\x92\x01\x05\x08\x01\x10\x90NR\x05hosts\x12>\n\x0f\x61rtifact_sha256\x18\x08 \x01(\tB\x15\xbaH\x12r\x10\x32\x0e^[0-9a-f]{64}$R\x0e\x61rtifactSha256:\xb7\x01\xbaH\xb3\x01\x1a\xb0\x01\n\x11\x65vidence.interval\x12Owindow_end must follow window_start and analyzed_at must not precede window_end\x1aJthis.window_end > this.window_start && this.analyzed_at >= this.window_end\"\xec\x06\n\x10\x45xperimentPolicy\x12]\n\x13minimum_observation\x18\x01 \x01(\x0b\x32\x19.google.protobuf.DurationB\x11\xbaH\x0e\xaa\x01\x08\"\x04\x08\x80\xf5$*\x00\xc8\x01\x01R\x12minimumObservation\x12Q\n\rmaximum_phase\x18\x02 \x01(\x0b\x32\x19.google.protobuf.DurationB\x11\xbaH\x0e\xaa\x01\x08\"\x04\x08\x80\xf5$*\x00\xc8\x01\x01R\x0cmaximumPhase\x12@\n\x18minimum_sockets_per_host\x18\x03 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\x15minimumSocketsPerHost\x12<\n\x16minimum_bytes_per_host\x18\x04 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\x13minimumBytesPerHost\x12>\n\x15minimum_treated_hosts\x18\x05 \x01(\rB\n\xbaH\x07*\x05\x18\x90N(\x02R\x13minimumTreatedHosts\x12J\n\x13minimum_improvement\x18\x06 \x01(\x01\x42\x19\xbaH\x16\x12\x14\x19\x00\x00\x00\x00\x00\x00\xf0?)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x12minimumImprovement\x12\x61\n\x1fmaximum_retransmission_increase\x18\x07 \x01(\x01\x42\x19\xbaH\x16\x12\x14\x19\x00\x00\x00\x00\x00\x00\xf0?)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x1dmaximumRetransmissionIncrease\x12K\n\x14maximum_rtt_increase\x18\x08 \x01(\x01\x42\x19\xbaH\x16\x12\x14\x19\x00\x00\x00\x00\x00\x00\xf0?)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x12maximumRttIncrease\x12^\n\x14maximum_evidence_age\x18\t \x01(\x0b\x32\x19.google.protobuf.DurationB\x11\xbaH\x0e\xaa\x01\x08\"\x04\x08\x80\xf5$*\x00\xc8\x01\x01R\x12maximumEvidenceAge:\x89\x01\xbaH\x85\x01\x1a\x82\x01\n\x1fpolicy.observation_within_phase\x12/minimum_observation cannot exceed maximum_phase\x1a.this.minimum_observation <= this.maximum_phase\"\xa4\x03\n\x05Phase\x12-\n\x0ctarget_hosts\x18\x01 \x01(\rB\n\xbaH\x07*\x05\x18\x90N(\x01R\x0btargetHosts\x12\x37\n\tissued_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x08issuedAt\x12P\n\x16observation_started_at\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x14observationStartedAt\x12=\n\x0c\x65valuated_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0b\x65valuatedAt\x12\x37\n\x08\x65vidence\x18\x05 \x01(\x0b\x32\x1b.xtcp_treatment.v1.EvidenceR\x08\x65vidence\x12!\n\x07outcome\x18\x06 \x01(\tB\x07\xbaH\x04r\x02\x18@R\x07outcome\x12$\n\x08reviewer\x18\x07 \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x01R\x08reviewer\x12 \n\x06reason\x18\x08 \x01(\tB\x08\xbaH\x05r\x03\x18\x80 R\x06reason\"\xb0\x06\n\nExperiment\x12\x39\n\x02id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x02id\x12\x45\n\x08location\x18\x02 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x08location\x12*\n\nhypothesis\x18\x03 \x01(\tB\n\xbaH\x07r\x05\x10\x01\x18\x80@R\nhypothesis\x12\x42\n\ttreatment\x18\x04 \x01(\x0b\x32\x1c.xtcp_treatment.v1.TreatmentB\x06\xbaH\x03\xc8\x01\x01R\ttreatment\x12\x43\n\x06policy\x18\x05 \x01(\x0b\x32#.xtcp_treatment.v1.ExperimentPolicyB\x06\xbaH\x03\xc8\x01\x01R\x06policy\x12?\n\x08\x62\x61seline\x18\x06 \x01(\x0b\x32\x1b.xtcp_treatment.v1.EvidenceB\x06\xbaH\x03\xc8\x01\x01R\x08\x62\x61seline\x12X\n\x0c\x63ohort_order\x18\x07 \x03(\tB5\xbaH2\x92\x01/\x08\x03\x10\x90N\x18\x01\"&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x0b\x63ohortOrder\x12:\n\x06phases\x18\x08 \x03(\x0b\x32\x18.xtcp_treatment.v1.PhaseB\x08\xbaH\x05\x92\x01\x02\x10@R\x06phases\x12\x45\n\x06status\x18\t \x01(\x0e\x32#.xtcp_treatment.v1.ExperimentStatusB\x08\xbaH\x05\x82\x01\x02\x10\x01R\x06status\x12\x1a\n\x08revision\x18\n \x01(\x04R\x08revision\x12;\n\x0bproposed_at\x18\x0b \x01(\x0b\x32\x1a.google.protobuf.TimestampR\nproposedAt\x12\x44\n\x0flatest_evidence\x18\x0c \x01(\x0b\x32\x1b.xtcp_treatment.v1.EvidenceR\x0elatestEvidence\x12.\n\x0elatest_outcome\x18\r \x01(\tB\x07\xbaH\x04r\x02\x18@R\rlatestOutcome*?\n\x04Role\x12\x14\n\x10ROLE_UNSPECIFIED\x10\x00\x12\x0e\n\nROLE_AGENT\x10\x01\x12\x11\n\rROLE_OPERATOR\x10\x02*\xa4\x01\n\x0e\x41ssignmentMode\x12\x1f\n\x1b\x41SSIGNMENT_MODE_UNSPECIFIED\x10\x00\x12\x19\n\x15\x41SSIGNMENT_MODE_TRIAL\x10\x01\x12\x1a\n\x16\x41SSIGNMENT_MODE_STABLE\x10\x02\x12\x1d\n\x19\x41SSIGNMENT_MODE_SUSPENDED\x10\x03\x12\x1b\n\x17\x41SSIGNMENT_MODE_RETIRED\x10\x04*\xad\x01\n\x0b\x41pplyStatus\x12\x1c\n\x18\x41PPLY_STATUS_UNSPECIFIED\x10\x00\x12\x18\n\x14\x41PPLY_STATUS_PENDING\x10\x01\x12\x19\n\x15\x41PPLY_STATUS_VERIFIED\x10\x02\x12\x18\n\x14\x41PPLY_STATUS_REMOVED\x10\x03\x12\x18\n\x14\x41PPLY_STATUS_EXPIRED\x10\x04\x12\x17\n\x13\x41PPLY_STATUS_FAILED\x10\x05*\x95\x02\n\x10\x45xperimentStatus\x12!\n\x1d\x45XPERIMENT_STATUS_UNSPECIFIED\x10\x00\x12\x1e\n\x1a\x45XPERIMENT_STATUS_PROPOSED\x10\x01\x12\x1d\n\x19\x45XPERIMENT_STATUS_RUNNING\x10\x02\x12\x1c\n\x18\x45XPERIMENT_STATUS_PAUSED\x10\x03\x12\x1f\n\x1b\x45XPERIMENT_STATUS_SUSPENDED\x10\x04\x12\x1e\n\x1a\x45XPERIMENT_STATUS_COMPLETE\x10\x05\x12\x1d\n\x19\x45XPERIMENT_STATUS_RETIRED\x10\x06\x12!\n\x1d\x45XPERIMENT_STATUS_ROLLED_BACK\x10\x07:M\n\x04role\x12\x1e.google.protobuf.MethodOptions\x18\xba\x8e\x03 \x01(\x0e\x32\x17.xtcp_treatment.v1.RoleR\x04roleB\xc0\x01\n\x15\x63om.xtcp_treatment.v1B\x0eTreatmentProtoP\x01Z6github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment\xa2\x02\x03XXX\xaa\x02\x10XtcpTreatment.V1\xca\x02\x10XtcpTreatment\\V1\xe2\x02\x1cXtcpTreatment\\V1\\GPBMetadata\xea\x02\x11XtcpTreatment::V1b\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!xtcp_treatment/v1/treatment.proto\x12\x11xtcp_treatment.v1\x1a\x1b\x62uf/validate/validate.proto\x1a google/protobuf/descriptor.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\x8d\x07\n\x0bTCPSettings\x12Y\n\x12\x63ongestion_control\x18\x01 \x01(\tB%\xbaH\"r \x18\x0f\x32\x1c^[a-zA-Z0-9][a-zA-Z0-9_.-]*$H\x00R\x11\x63ongestionControl\x88\x01\x01\x12\'\n\x0f\x63ongestion_lock\x18\x02 \x01(\x08R\x0e\x63ongestionLock\x12.\n\nrto_min_ms\x18\x03 \x01(\rB\x0b\xbaH\x08*\x06\x18\xc0\xa9\x07(\x01H\x01R\x08rtoMinMs\x88\x01\x01\x12K\n\x19initial_congestion_window\x18\x04 \x01(\rB\n\xbaH\x07*\x05\x18\x80\x08(\x01H\x02R\x17initialCongestionWindow\x88\x01\x01\x12\x45\n\x16initial_receive_window\x18\x05 \x01(\rB\n\xbaH\x07*\x05\x18\x80\x08(\x01H\x03R\x14initialReceiveWindow\x88\x01\x01\x12 \n\tquick_ack\x18\x06 \x01(\x08H\x04R\x08quickAck\x88\x01\x01\x12\x15\n\x03\x65\x63n\x18\x07 \x01(\x08H\x05R\x03\x65\x63n\x88\x01\x01:\x87\x03\xbaH\x83\x03\x1a\xf3\x01\n\x16tcp_settings.non_empty\x12/a treatment must set at least one TCP parameter\x1a\xa7\x01has(this.congestion_control) || has(this.rto_min_ms) || has(this.initial_congestion_window) || has(this.initial_receive_window) || has(this.quick_ack) || has(this.ecn)\x1a\x8a\x01\n$tcp_settings.lock_requires_algorithm\x12+congestion_lock requires congestion_control\x1a\x35!this.congestion_lock || has(this.congestion_control)B\x15\n\x13_congestion_controlB\r\n\x0b_rto_min_msB\x1c\n\x1a_initial_congestion_windowB\x19\n\x17_initial_receive_windowB\x0c\n\n_quick_ackB\x06\n\x04_ecn\"\xd8\x03\n\tTreatment\x12\x39\n\x02id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x02id\x12#\n\x08revision\x18\x02 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\x08revision\x12,\n\x0b\x64\x65stination\x18\x03 \x01(\tB\n\xbaH\x07r\x05\x18\x31\xe8\x01\x01R\x0b\x64\x65stination\x12\x42\n\x08settings\x18\x04 \x01(\x0b\x32\x1e.xtcp_treatment.v1.TCPSettingsB\x06\xbaH\x03\xc8\x01\x01R\x08settings\x12.\n\x13\x65\x63n_fabric_verified\x18\x05 \x01(\x08R\x11\x65\x63nFabricVerified:\xc8\x01\xbaH\xc4\x01\x1a\xc1\x01\n\x1ctreatment.dctcp_requires_ecn\x12\x41\x44\x43TCP requires independently verified ECN fabric and explicit ECN\x1a^this.settings.congestion_control != \'dctcp\' || (this.ecn_fabric_verified && this.settings.ecn)\"\xcc\x05\n\nAssignment\x12N\n\rexperiment_id\x18\x02 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x0c\x65xperimentId\x12\x42\n\ttreatment\x18\x03 \x01(\x0b\x32\x1c.xtcp_treatment.v1.TreatmentB\x06\xbaH\x03\xc8\x01\x01R\ttreatment\x12\x41\n\x04mode\x18\x04 \x01(\x0e\x32!.xtcp_treatment.v1.AssignmentModeB\n\xbaH\x07\x82\x01\x04\x10\x01 \x00R\x04mode\x12\x44\n\x10lease_expires_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0eleaseExpiresAt\x12>\n\x0bpredecessor\x18\x06 \x01(\x0b\x32\x1c.xtcp_treatment.v1.TreatmentR\x0bpredecessor\x12\'\n\ngeneration\x18\x07 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\ngeneration:\xad\x02\xbaH\xa9\x02\x1a}\n\x1f\x61ssignment.trial_requires_lease\x12,a trial assignment requires lease_expires_at\x1a,this.mode != 1 || has(this.lease_expires_at)\x1a\xa7\x01\n\"assignment.predecessor_destination\x12+predecessor must cover the same destination\x1aT!has(this.predecessor) || this.predecessor.destination == this.treatment.destinationJ\x04\x08\x01\x10\x02R\x02id\"\xa4\x02\n\x0c\x44\x65siredState\x12\x44\n\x08\x61gent_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x07\x61gentId\x12#\n\x08revision\x18\x02 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\x08revision\x12\x1c\n\tsuspended\x18\x03 \x01(\x08R\tsuspended\x12J\n\x0b\x61ssignments\x18\x04 \x03(\x0b\x32\x1d.xtcp_treatment.v1.AssignmentB\t\xbaH\x06\x92\x01\x03\x10\x80 R\x0b\x61ssignments\x12?\n\tissued_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\x08issuedAt\"\xa3\x02\n\x0c\x43\x61pabilities\x12\x42\n\x07\x62oot_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x06\x62ootId\x12/\n\x0ekernel_release\x18\x02 \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x02R\rkernelRelease\x12\x43\n\x15\x63ongestion_algorithms\x18\x03 \x03(\tB\x0e\xbaH\x0b\x92\x01\x08\x10@\"\x04r\x02\x18\x0fR\x14\x63ongestionAlgorithms\x12.\n\x13simple_host_routing\x18\x04 \x01(\x08R\x11simpleHostRouting\x12)\n\x10\x61\x63tuator_enabled\x18\x05 \x01(\x08R\x0f\x61\x63tuatorEnabled\"\xa8\x05\n\x11\x41pplicationRecord\x12\x45\n\nassignment\x18\x01 \x01(\x0b\x32\x1d.xtcp_treatment.v1.AssignmentB\x06\xbaH\x03\xc8\x01\x01R\nassignment\x12)\n\x10\x64\x65sired_revision\x18\x02 \x01(\x04R\x0f\x64\x65siredRevision\x12@\n\x06status\x18\x03 \x01(\x0e\x32\x1e.xtcp_treatment.v1.ApplyStatusB\x08\xbaH\x05\x82\x01\x02\x10\x01R\x06status\x12;\n\x0breceived_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\nreceivedAt\x12=\n\x0c\x61ttempted_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0b\x61ttemptedAt\x12\x39\n\napplied_at\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tappliedAt\x12;\n\x0bverified_at\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\nverifiedAt\x12\x39\n\nremoved_at\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tremovedAt\x12\x1e\n\x05\x65rror\x18\t \x01(\tB\x08\xbaH\x05r\x03\x18\x80 R\x05\x65rror\x12\x31\n\x0finstalled_route\x18\n \x01(\x0c\x42\x08\xbaH\x05z\x03\x18\x80 R\x0einstalledRoute\x12\x34\n\x11installed_boot_id\x18\x0b \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x01R\x0finstalledBootId\x12\'\n\x0f\x65xpired_latched\x18\x0c \x01(\x08R\x0e\x65xpiredLatched\"\xb1\x02\n\nLocalState\x12\x39\n\x07\x64\x65sired\x18\x01 \x01(\x0b\x32\x1f.xtcp_treatment.v1.DesiredStateR\x07\x64\x65sired\x12+\n\x11locally_suspended\x18\x02 \x01(\x08R\x10locallySuspended\x12\x35\n\x11suspension_reason\x18\x03 \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x08R\x10suspensionReason\x12I\n\x07records\x18\x04 \x03(\x0b\x32$.xtcp_treatment.v1.ApplicationRecordB\t\xbaH\x06\x92\x01\x03\x10\x80@R\x07records\x12\x39\n\nupdated_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tupdatedAt\"\x89\x04\n\x0cHostEvidence\x12\x44\n\x08\x61gent_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x07\x61gentId\x12\x18\n\x07sockets\x18\x02 \x01(\x04R\x07sockets\x12\x1f\n\x0b\x62ytes_acked\x18\x03 \x01(\x04R\nbytesAcked\x12\x31\n\x0bgoodput_bps\x18\x04 \x01(\x01\x42\x10\xbaH\r\x12\x0b)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\ngoodputBps\x12\x43\n\x14retransmission_ratio\x18\x05 \x01(\x01\x42\x10\xbaH\r\x12\x0b)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x13retransmissionRatio\x12.\n\nrtt_p95_ms\x18\x06 \x01(\x01\x42\x10\xbaH\r\x12\x0b)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x08rttP95Ms\x12\'\n\x0f\x65xposed_sockets\x18\x07 \x01(\x04R\x0e\x65xposedSockets\x12-\n\x12\x63ollection_healthy\x18\x08 \x01(\x08R\x11\x63ollectionHealthy:x\xbaHu\x1as\n$host_evidence.exposed_within_sockets\x12%exposed_sockets cannot exceed sockets\x1a$this.exposed_sockets <= this.sockets\"\xbc\x05\n\x08\x45vidence\x12\x39\n\x02id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x02id\x12\x43\n\x0b\x61nalyzed_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\nanalyzedAt\x12\x45\n\x0cwindow_start\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\x0bwindowStart\x12\x41\n\nwindow_end\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\twindowEnd\x12/\n\rquery_version\x18\x05 \x01(\tB\n\xbaH\x07r\x05\x10\x01\x18\x80\x02R\x0cqueryVersion\x12\x37\n\x12\x65nrichment_version\x18\x06 \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x02R\x11\x65nrichmentVersion\x12\x42\n\x05hosts\x18\x07 \x03(\x0b\x32\x1f.xtcp_treatment.v1.HostEvidenceB\x0b\xbaH\x08\x92\x01\x05\x08\x01\x10\x90NR\x05hosts\x12>\n\x0f\x61rtifact_sha256\x18\x08 \x01(\tB\x15\xbaH\x12r\x10\x32\x0e^[0-9a-f]{64}$R\x0e\x61rtifactSha256:\xb7\x01\xbaH\xb3\x01\x1a\xb0\x01\n\x11\x65vidence.interval\x12Owindow_end must follow window_start and analyzed_at must not precede window_end\x1aJthis.window_end > this.window_start && this.analyzed_at >= this.window_end\"\xec\x06\n\x10\x45xperimentPolicy\x12]\n\x13minimum_observation\x18\x01 \x01(\x0b\x32\x19.google.protobuf.DurationB\x11\xbaH\x0e\xaa\x01\x08\"\x04\x08\x80\xf5$*\x00\xc8\x01\x01R\x12minimumObservation\x12Q\n\rmaximum_phase\x18\x02 \x01(\x0b\x32\x19.google.protobuf.DurationB\x11\xbaH\x0e\xaa\x01\x08\"\x04\x08\x80\xf5$*\x00\xc8\x01\x01R\x0cmaximumPhase\x12@\n\x18minimum_sockets_per_host\x18\x03 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\x15minimumSocketsPerHost\x12<\n\x16minimum_bytes_per_host\x18\x04 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\x13minimumBytesPerHost\x12>\n\x15minimum_treated_hosts\x18\x05 \x01(\rB\n\xbaH\x07*\x05\x18\x90N(\x02R\x13minimumTreatedHosts\x12J\n\x13minimum_improvement\x18\x06 \x01(\x01\x42\x19\xbaH\x16\x12\x14\x19\x00\x00\x00\x00\x00\x00\xf0?)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x12minimumImprovement\x12\x61\n\x1fmaximum_retransmission_increase\x18\x07 \x01(\x01\x42\x19\xbaH\x16\x12\x14\x19\x00\x00\x00\x00\x00\x00\xf0?)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x1dmaximumRetransmissionIncrease\x12K\n\x14maximum_rtt_increase\x18\x08 \x01(\x01\x42\x19\xbaH\x16\x12\x14\x19\x00\x00\x00\x00\x00\x00\xf0?)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x12maximumRttIncrease\x12^\n\x14maximum_evidence_age\x18\t \x01(\x0b\x32\x19.google.protobuf.DurationB\x11\xbaH\x0e\xaa\x01\x08\"\x04\x08\x80\xf5$*\x00\xc8\x01\x01R\x12maximumEvidenceAge:\x89\x01\xbaH\x85\x01\x1a\x82\x01\n\x1fpolicy.observation_within_phase\x12/minimum_observation cannot exceed maximum_phase\x1a.this.minimum_observation <= this.maximum_phase\"\xc1\x03\n\x05Phase\x12-\n\x0ctarget_hosts\x18\x01 \x01(\rB\n\xbaH\x07*\x05\x18\x90N(\x01R\x0btargetHosts\x12\x37\n\tissued_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x08issuedAt\x12P\n\x16observation_started_at\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x14observationStartedAt\x12=\n\x0c\x65valuated_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0b\x65valuatedAt\x12\x37\n\x08\x65vidence\x18\x05 \x01(\x0b\x32\x1b.xtcp_treatment.v1.EvidenceR\x08\x65vidence\x12>\n\x07outcome\x18\x06 \x01(\x0e\x32\x1a.xtcp_treatment.v1.OutcomeB\x08\xbaH\x05\x82\x01\x02\x10\x01R\x07outcome\x12$\n\x08reviewer\x18\x07 \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x01R\x08reviewer\x12 \n\x06reason\x18\x08 \x01(\tB\x08\xbaH\x05r\x03\x18\x80 R\x06reason\"\xcd\x06\n\nExperiment\x12\x39\n\x02id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x02id\x12\x45\n\x08location\x18\x02 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x08location\x12*\n\nhypothesis\x18\x03 \x01(\tB\n\xbaH\x07r\x05\x10\x01\x18\x80@R\nhypothesis\x12\x42\n\ttreatment\x18\x04 \x01(\x0b\x32\x1c.xtcp_treatment.v1.TreatmentB\x06\xbaH\x03\xc8\x01\x01R\ttreatment\x12\x43\n\x06policy\x18\x05 \x01(\x0b\x32#.xtcp_treatment.v1.ExperimentPolicyB\x06\xbaH\x03\xc8\x01\x01R\x06policy\x12?\n\x08\x62\x61seline\x18\x06 \x01(\x0b\x32\x1b.xtcp_treatment.v1.EvidenceB\x06\xbaH\x03\xc8\x01\x01R\x08\x62\x61seline\x12X\n\x0c\x63ohort_order\x18\x07 \x03(\tB5\xbaH2\x92\x01/\x08\x03\x10\x90N\x18\x01\"&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x0b\x63ohortOrder\x12:\n\x06phases\x18\x08 \x03(\x0b\x32\x18.xtcp_treatment.v1.PhaseB\x08\xbaH\x05\x92\x01\x02\x10@R\x06phases\x12\x45\n\x06status\x18\t \x01(\x0e\x32#.xtcp_treatment.v1.ExperimentStatusB\x08\xbaH\x05\x82\x01\x02\x10\x01R\x06status\x12\x1a\n\x08revision\x18\n \x01(\x04R\x08revision\x12;\n\x0bproposed_at\x18\x0b \x01(\x0b\x32\x1a.google.protobuf.TimestampR\nproposedAt\x12\x44\n\x0flatest_evidence\x18\x0c \x01(\x0b\x32\x1b.xtcp_treatment.v1.EvidenceR\x0elatestEvidence\x12K\n\x0elatest_outcome\x18\r \x01(\x0e\x32\x1a.xtcp_treatment.v1.OutcomeB\x08\xbaH\x05\x82\x01\x02\x10\x01R\rlatestOutcome*?\n\x04Role\x12\x14\n\x10ROLE_UNSPECIFIED\x10\x00\x12\x0e\n\nROLE_AGENT\x10\x01\x12\x11\n\rROLE_OPERATOR\x10\x02*\xa4\x01\n\x0e\x41ssignmentMode\x12\x1f\n\x1b\x41SSIGNMENT_MODE_UNSPECIFIED\x10\x00\x12\x19\n\x15\x41SSIGNMENT_MODE_TRIAL\x10\x01\x12\x1a\n\x16\x41SSIGNMENT_MODE_STABLE\x10\x02\x12\x1d\n\x19\x41SSIGNMENT_MODE_SUSPENDED\x10\x03\x12\x1b\n\x17\x41SSIGNMENT_MODE_RETIRED\x10\x04*\xad\x01\n\x0b\x41pplyStatus\x12\x1c\n\x18\x41PPLY_STATUS_UNSPECIFIED\x10\x00\x12\x18\n\x14\x41PPLY_STATUS_PENDING\x10\x01\x12\x19\n\x15\x41PPLY_STATUS_VERIFIED\x10\x02\x12\x18\n\x14\x41PPLY_STATUS_REMOVED\x10\x03\x12\x18\n\x14\x41PPLY_STATUS_EXPIRED\x10\x04\x12\x17\n\x13\x41PPLY_STATUS_FAILED\x10\x05*\xa4\x04\n\x07Outcome\x12\x17\n\x13OUTCOME_UNSPECIFIED\x10\x00\x12\x14\n\x10OUTCOME_BASELINE\x10\x01\x12 \n\x1cOUTCOME_AWAITING_APPLICATION\x10\x02\x12\x1d\n\x19OUTCOME_AWAITING_EXPOSURE\x10\x03\x12!\n\x1dOUTCOME_INSUFFICIENT_DURATION\x10\x04\x12\x1d\n\x19OUTCOME_INSUFFICIENT_DATA\x10\x05\x12!\n\x1dOUTCOME_INSUFFICIENT_CONTROLS\x10\x06\x12\x11\n\rOUTCOME_STALE\x10\x07\x12\x17\n\x13OUTCOME_UNAVAILABLE\x10\x08\x12\x18\n\x14OUTCOME_INCONCLUSIVE\x10\t\x12\x17\n\x13OUTCOME_SAFETY_ONLY\x10\n\x12\x18\n\x14OUTCOME_FINAL_SAFETY\x10\x0b\x12\x16\n\x12OUTCOME_BENEFICIAL\x10\x0c\x12\x13\n\x0fOUTCOME_HARMFUL\x10\r\x12\x13\n\x0fOUTCOME_EXPIRED\x10\x0e\x12\x1b\n\x17OUTCOME_STABLE_OBSERVED\x10\x0f\x12&\n\"OUTCOME_STABLE_EVIDENCE_INCOMPLETE\x10\x10\x12-\n)OUTCOME_STABLE_REGRESSION_REVIEW_REQUIRED\x10\x11\x12\x15\n\x11OUTCOME_OBSERVING\x10\x12*\x95\x02\n\x10\x45xperimentStatus\x12!\n\x1d\x45XPERIMENT_STATUS_UNSPECIFIED\x10\x00\x12\x1e\n\x1a\x45XPERIMENT_STATUS_PROPOSED\x10\x01\x12\x1d\n\x19\x45XPERIMENT_STATUS_RUNNING\x10\x02\x12\x1c\n\x18\x45XPERIMENT_STATUS_PAUSED\x10\x03\x12\x1f\n\x1b\x45XPERIMENT_STATUS_SUSPENDED\x10\x04\x12\x1e\n\x1a\x45XPERIMENT_STATUS_COMPLETE\x10\x05\x12\x1d\n\x19\x45XPERIMENT_STATUS_RETIRED\x10\x06\x12!\n\x1d\x45XPERIMENT_STATUS_ROLLED_BACK\x10\x07:M\n\x04role\x12\x1e.google.protobuf.MethodOptions\x18\xba\x8e\x03 \x01(\x0e\x32\x17.xtcp_treatment.v1.RoleR\x04roleB\xc0\x01\n\x15\x63om.xtcp_treatment.v1B\x0eTreatmentProtoP\x01Z6github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment\xa2\x02\x03XXX\xaa\x02\x10XtcpTreatment.V1\xca\x02\x10XtcpTreatment\\V1\xe2\x02\x1cXtcpTreatment\\V1\\GPBMetadata\xea\x02\x11XtcpTreatment::V1b\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -56,8 +56,6 @@ _globals['_TREATMENT'].fields_by_name['settings']._serialized_options = b'\272H\003\310\001\001' _globals['_TREATMENT']._loaded_options = None _globals['_TREATMENT']._serialized_options = b'\272H\304\001\032\301\001\n\034treatment.dctcp_requires_ecn\022ADCTCP requires independently verified ECN fabric and explicit ECN\032^this.settings.congestion_control != \'dctcp\' || (this.ecn_fabric_verified && this.settings.ecn)' - _globals['_ASSIGNMENT'].fields_by_name['id']._loaded_options = None - _globals['_ASSIGNMENT'].fields_by_name['id']._serialized_options = b'\272H&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$' _globals['_ASSIGNMENT'].fields_by_name['experiment_id']._loaded_options = None _globals['_ASSIGNMENT'].fields_by_name['experiment_id']._serialized_options = b'\272H&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$' _globals['_ASSIGNMENT'].fields_by_name['treatment']._loaded_options = None @@ -147,7 +145,7 @@ _globals['_PHASE'].fields_by_name['target_hosts']._loaded_options = None _globals['_PHASE'].fields_by_name['target_hosts']._serialized_options = b'\272H\007*\005\030\220N(\001' _globals['_PHASE'].fields_by_name['outcome']._loaded_options = None - _globals['_PHASE'].fields_by_name['outcome']._serialized_options = b'\272H\004r\002\030@' + _globals['_PHASE'].fields_by_name['outcome']._serialized_options = b'\272H\005\202\001\002\020\001' _globals['_PHASE'].fields_by_name['reviewer']._loaded_options = None _globals['_PHASE'].fields_by_name['reviewer']._serialized_options = b'\272H\005r\003\030\200\001' _globals['_PHASE'].fields_by_name['reason']._loaded_options = None @@ -171,37 +169,39 @@ _globals['_EXPERIMENT'].fields_by_name['status']._loaded_options = None _globals['_EXPERIMENT'].fields_by_name['status']._serialized_options = b'\272H\005\202\001\002\020\001' _globals['_EXPERIMENT'].fields_by_name['latest_outcome']._loaded_options = None - _globals['_EXPERIMENT'].fields_by_name['latest_outcome']._serialized_options = b'\272H\004r\002\030@' - _globals['_ROLE']._serialized_start=7267 - _globals['_ROLE']._serialized_end=7330 - _globals['_ASSIGNMENTMODE']._serialized_start=7333 - _globals['_ASSIGNMENTMODE']._serialized_end=7497 - _globals['_APPLYSTATUS']._serialized_start=7500 - _globals['_APPLYSTATUS']._serialized_end=7673 - _globals['_EXPERIMENTSTATUS']._serialized_start=7676 - _globals['_EXPERIMENTSTATUS']._serialized_end=7953 + _globals['_EXPERIMENT'].fields_by_name['latest_outcome']._serialized_options = b'\272H\005\202\001\002\020\001' + _globals['_ROLE']._serialized_start=7276 + _globals['_ROLE']._serialized_end=7339 + _globals['_ASSIGNMENTMODE']._serialized_start=7342 + _globals['_ASSIGNMENTMODE']._serialized_end=7506 + _globals['_APPLYSTATUS']._serialized_start=7509 + _globals['_APPLYSTATUS']._serialized_end=7682 + _globals['_OUTCOME']._serialized_start=7685 + _globals['_OUTCOME']._serialized_end=8233 + _globals['_EXPERIMENTSTATUS']._serialized_start=8236 + _globals['_EXPERIMENTSTATUS']._serialized_end=8513 _globals['_TCPSETTINGS']._serialized_start=185 _globals['_TCPSETTINGS']._serialized_end=1094 _globals['_TREATMENT']._serialized_start=1097 _globals['_TREATMENT']._serialized_end=1569 _globals['_ASSIGNMENT']._serialized_start=1572 - _globals['_ASSIGNMENT']._serialized_end=2337 - _globals['_DESIREDSTATE']._serialized_start=2340 - _globals['_DESIREDSTATE']._serialized_end=2632 - _globals['_CAPABILITIES']._serialized_start=2635 - _globals['_CAPABILITIES']._serialized_end=2926 - _globals['_APPLICATIONRECORD']._serialized_start=2929 - _globals['_APPLICATIONRECORD']._serialized_end=3609 - _globals['_LOCALSTATE']._serialized_start=3612 - _globals['_LOCALSTATE']._serialized_end=3917 - _globals['_HOSTEVIDENCE']._serialized_start=3920 - _globals['_HOSTEVIDENCE']._serialized_end=4441 - _globals['_EVIDENCE']._serialized_start=4444 - _globals['_EVIDENCE']._serialized_end=5144 - _globals['_EXPERIMENTPOLICY']._serialized_start=5147 - _globals['_EXPERIMENTPOLICY']._serialized_end=6023 - _globals['_PHASE']._serialized_start=6026 - _globals['_PHASE']._serialized_end=6446 - _globals['_EXPERIMENT']._serialized_start=6449 - _globals['_EXPERIMENT']._serialized_end=7265 + _globals['_ASSIGNMENT']._serialized_end=2288 + _globals['_DESIREDSTATE']._serialized_start=2291 + _globals['_DESIREDSTATE']._serialized_end=2583 + _globals['_CAPABILITIES']._serialized_start=2586 + _globals['_CAPABILITIES']._serialized_end=2877 + _globals['_APPLICATIONRECORD']._serialized_start=2880 + _globals['_APPLICATIONRECORD']._serialized_end=3560 + _globals['_LOCALSTATE']._serialized_start=3563 + _globals['_LOCALSTATE']._serialized_end=3868 + _globals['_HOSTEVIDENCE']._serialized_start=3871 + _globals['_HOSTEVIDENCE']._serialized_end=4392 + _globals['_EVIDENCE']._serialized_start=4395 + _globals['_EVIDENCE']._serialized_end=5095 + _globals['_EXPERIMENTPOLICY']._serialized_start=5098 + _globals['_EXPERIMENTPOLICY']._serialized_end=5974 + _globals['_PHASE']._serialized_start=5977 + _globals['_PHASE']._serialized_end=6426 + _globals['_EXPERIMENT']._serialized_start=6429 + _globals['_EXPERIMENT']._serialized_end=7274 # @@protoc_insertion_point(module_scope) diff --git a/gen/python/xtcp_treatment/v1/treatment_pb2.pyi b/gen/python/xtcp_treatment/v1/treatment_pb2.pyi index 326e1eff1..012838723 100644 --- a/gen/python/xtcp_treatment/v1/treatment_pb2.pyi +++ b/gen/python/xtcp_treatment/v1/treatment_pb2.pyi @@ -36,6 +36,28 @@ class ApplyStatus(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): APPLY_STATUS_EXPIRED: _ClassVar[ApplyStatus] APPLY_STATUS_FAILED: _ClassVar[ApplyStatus] +class Outcome(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + OUTCOME_UNSPECIFIED: _ClassVar[Outcome] + OUTCOME_BASELINE: _ClassVar[Outcome] + OUTCOME_AWAITING_APPLICATION: _ClassVar[Outcome] + OUTCOME_AWAITING_EXPOSURE: _ClassVar[Outcome] + OUTCOME_INSUFFICIENT_DURATION: _ClassVar[Outcome] + OUTCOME_INSUFFICIENT_DATA: _ClassVar[Outcome] + OUTCOME_INSUFFICIENT_CONTROLS: _ClassVar[Outcome] + OUTCOME_STALE: _ClassVar[Outcome] + OUTCOME_UNAVAILABLE: _ClassVar[Outcome] + OUTCOME_INCONCLUSIVE: _ClassVar[Outcome] + OUTCOME_SAFETY_ONLY: _ClassVar[Outcome] + OUTCOME_FINAL_SAFETY: _ClassVar[Outcome] + OUTCOME_BENEFICIAL: _ClassVar[Outcome] + OUTCOME_HARMFUL: _ClassVar[Outcome] + OUTCOME_EXPIRED: _ClassVar[Outcome] + OUTCOME_STABLE_OBSERVED: _ClassVar[Outcome] + OUTCOME_STABLE_EVIDENCE_INCOMPLETE: _ClassVar[Outcome] + OUTCOME_STABLE_REGRESSION_REVIEW_REQUIRED: _ClassVar[Outcome] + OUTCOME_OBSERVING: _ClassVar[Outcome] + class ExperimentStatus(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): __slots__ = () EXPERIMENT_STATUS_UNSPECIFIED: _ClassVar[ExperimentStatus] @@ -60,6 +82,25 @@ APPLY_STATUS_VERIFIED: ApplyStatus APPLY_STATUS_REMOVED: ApplyStatus APPLY_STATUS_EXPIRED: ApplyStatus APPLY_STATUS_FAILED: ApplyStatus +OUTCOME_UNSPECIFIED: Outcome +OUTCOME_BASELINE: Outcome +OUTCOME_AWAITING_APPLICATION: Outcome +OUTCOME_AWAITING_EXPOSURE: Outcome +OUTCOME_INSUFFICIENT_DURATION: Outcome +OUTCOME_INSUFFICIENT_DATA: Outcome +OUTCOME_INSUFFICIENT_CONTROLS: Outcome +OUTCOME_STALE: Outcome +OUTCOME_UNAVAILABLE: Outcome +OUTCOME_INCONCLUSIVE: Outcome +OUTCOME_SAFETY_ONLY: Outcome +OUTCOME_FINAL_SAFETY: Outcome +OUTCOME_BENEFICIAL: Outcome +OUTCOME_HARMFUL: Outcome +OUTCOME_EXPIRED: Outcome +OUTCOME_STABLE_OBSERVED: Outcome +OUTCOME_STABLE_EVIDENCE_INCOMPLETE: Outcome +OUTCOME_STABLE_REGRESSION_REVIEW_REQUIRED: Outcome +OUTCOME_OBSERVING: Outcome EXPERIMENT_STATUS_UNSPECIFIED: ExperimentStatus EXPERIMENT_STATUS_PROPOSED: ExperimentStatus EXPERIMENT_STATUS_RUNNING: ExperimentStatus @@ -104,22 +145,20 @@ class Treatment(_message.Message): def __init__(self, id: _Optional[str] = ..., revision: _Optional[int] = ..., destination: _Optional[str] = ..., settings: _Optional[_Union[TCPSettings, _Mapping]] = ..., ecn_fabric_verified: _Optional[bool] = ...) -> None: ... class Assignment(_message.Message): - __slots__ = ("id", "experiment_id", "treatment", "mode", "lease_expires_at", "predecessor", "generation") - ID_FIELD_NUMBER: _ClassVar[int] + __slots__ = ("experiment_id", "treatment", "mode", "lease_expires_at", "predecessor", "generation") EXPERIMENT_ID_FIELD_NUMBER: _ClassVar[int] TREATMENT_FIELD_NUMBER: _ClassVar[int] MODE_FIELD_NUMBER: _ClassVar[int] LEASE_EXPIRES_AT_FIELD_NUMBER: _ClassVar[int] PREDECESSOR_FIELD_NUMBER: _ClassVar[int] GENERATION_FIELD_NUMBER: _ClassVar[int] - id: str experiment_id: str treatment: Treatment mode: AssignmentMode lease_expires_at: _timestamp_pb2.Timestamp predecessor: Treatment generation: int - def __init__(self, id: _Optional[str] = ..., experiment_id: _Optional[str] = ..., treatment: _Optional[_Union[Treatment, _Mapping]] = ..., mode: _Optional[_Union[AssignmentMode, str]] = ..., lease_expires_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., predecessor: _Optional[_Union[Treatment, _Mapping]] = ..., generation: _Optional[int] = ...) -> None: ... + def __init__(self, experiment_id: _Optional[str] = ..., treatment: _Optional[_Union[Treatment, _Mapping]] = ..., mode: _Optional[_Union[AssignmentMode, str]] = ..., lease_expires_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., predecessor: _Optional[_Union[Treatment, _Mapping]] = ..., generation: _Optional[int] = ...) -> None: ... class DesiredState(_message.Message): __slots__ = ("agent_id", "revision", "suspended", "assignments", "issued_at") @@ -268,10 +307,10 @@ class Phase(_message.Message): observation_started_at: _timestamp_pb2.Timestamp evaluated_at: _timestamp_pb2.Timestamp evidence: Evidence - outcome: str + outcome: Outcome reviewer: str reason: str - def __init__(self, target_hosts: _Optional[int] = ..., issued_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., observation_started_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., evaluated_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., evidence: _Optional[_Union[Evidence, _Mapping]] = ..., outcome: _Optional[str] = ..., reviewer: _Optional[str] = ..., reason: _Optional[str] = ...) -> None: ... + def __init__(self, target_hosts: _Optional[int] = ..., issued_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., observation_started_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., evaluated_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., evidence: _Optional[_Union[Evidence, _Mapping]] = ..., outcome: _Optional[_Union[Outcome, str]] = ..., reviewer: _Optional[str] = ..., reason: _Optional[str] = ...) -> None: ... class Experiment(_message.Message): __slots__ = ("id", "location", "hypothesis", "treatment", "policy", "baseline", "cohort_order", "phases", "status", "revision", "proposed_at", "latest_evidence", "latest_outcome") @@ -300,5 +339,5 @@ class Experiment(_message.Message): revision: int proposed_at: _timestamp_pb2.Timestamp latest_evidence: Evidence - latest_outcome: str - def __init__(self, id: _Optional[str] = ..., location: _Optional[str] = ..., hypothesis: _Optional[str] = ..., treatment: _Optional[_Union[Treatment, _Mapping]] = ..., policy: _Optional[_Union[ExperimentPolicy, _Mapping]] = ..., baseline: _Optional[_Union[Evidence, _Mapping]] = ..., cohort_order: _Optional[_Iterable[str]] = ..., phases: _Optional[_Iterable[_Union[Phase, _Mapping]]] = ..., status: _Optional[_Union[ExperimentStatus, str]] = ..., revision: _Optional[int] = ..., proposed_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., latest_evidence: _Optional[_Union[Evidence, _Mapping]] = ..., latest_outcome: _Optional[str] = ...) -> None: ... + latest_outcome: Outcome + def __init__(self, id: _Optional[str] = ..., location: _Optional[str] = ..., hypothesis: _Optional[str] = ..., treatment: _Optional[_Union[Treatment, _Mapping]] = ..., policy: _Optional[_Union[ExperimentPolicy, _Mapping]] = ..., baseline: _Optional[_Union[Evidence, _Mapping]] = ..., cohort_order: _Optional[_Iterable[str]] = ..., phases: _Optional[_Iterable[_Union[Phase, _Mapping]]] = ..., status: _Optional[_Union[ExperimentStatus, str]] = ..., revision: _Optional[int] = ..., proposed_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., latest_evidence: _Optional[_Union[Evidence, _Mapping]] = ..., latest_outcome: _Optional[_Union[Outcome, str]] = ...) -> None: ... diff --git a/pkg/treatment/agent/agent.go b/pkg/treatment/agent/agent.go index 8c2c29db5..c947668ea 100644 --- a/pkg/treatment/agent/agent.go +++ b/pkg/treatment/agent/agent.go @@ -370,7 +370,7 @@ func (a *Agent) Stamp(r *flat.XtcpFlatRecord) { if a.Mode == pb.AssignmentMode_ASSIGNMENT_MODE_TRIAL && (a.LeaseExpiresAt == nil || !a.LeaseExpiresAt.AsTime().After(time.Now())) { return } - r.TreatmentAssignmentId = a.Id + r.TreatmentAssignmentId = a.ExperimentId r.TreatmentGeneration = a.Generation r.TreatmentAppliedTimestampNs = v.AppliedAt.AsTime().UnixNano() r.TreatmentAlgorithmExposed = a.Treatment.Settings.GetCongestionControl() != "" && r.InetDiagCong == a.Treatment.Settings.GetCongestionControl() diff --git a/pkg/treatment/agent/agent_test.go b/pkg/treatment/agent/agent_test.go index f7949498f..344db2331 100644 --- a/pkg/treatment/agent/agent_test.go +++ b/pkg/treatment/agent/agent_test.go @@ -13,7 +13,7 @@ import ( func TestStampDistinguishesAssignedAndActuallyExposed(t *testing.T) { a := &Agent{Config: Config{AgentID: "host-1"}, namespace: 123, collectionID: "session", Reconciler: &reconcile.Reconciler{BootID: "boot-1"}} - s := &pb.LocalState{Desired: &pb.DesiredState{Revision: 2}, Records: []*pb.ApplicationRecord{{Status: pb.ApplyStatus_APPLY_STATUS_VERIFIED, AppliedAt: timestamppb.Now(), Assignment: &pb.Assignment{Id: "exp-1", Generation: 1, Mode: pb.AssignmentMode_ASSIGNMENT_MODE_STABLE, Treatment: &pb.Treatment{Destination: "192.0.2.1/32", Settings: &pb.TCPSettings{CongestionControl: proto.String("cubic")}}}}}} + s := &pb.LocalState{Desired: &pb.DesiredState{Revision: 2}, Records: []*pb.ApplicationRecord{{Status: pb.ApplyStatus_APPLY_STATUS_VERIFIED, AppliedAt: timestamppb.Now(), Assignment: &pb.Assignment{ExperimentId: "exp-1", Generation: 1, Mode: pb.AssignmentMode_ASSIGNMENT_MODE_STABLE, Treatment: &pb.Treatment{Destination: "192.0.2.1/32", Settings: &pb.TCPSettings{CongestionControl: proto.String("cubic")}}}}}} a.publish(s) r := &flat.XtcpFlatRecord{NetnsInode: 123, InetDiagMsgFamily: 2, InetDiagMsgSocketDestination: []byte{192, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, InetDiagCong: "bbr"} a.Stamp(r) diff --git a/pkg/treatment/agent/benchmark_test.go b/pkg/treatment/agent/benchmark_test.go index 1297c1d57..c49d789de 100644 --- a/pkg/treatment/agent/benchmark_test.go +++ b/pkg/treatment/agent/benchmark_test.go @@ -18,7 +18,7 @@ func stampFixture(n int) (*Agent, *pb.LocalState, *flat.XtcpFlatRecord) { s := &pb.LocalState{Desired: &pb.DesiredState{Revision: 1}} for i := range n { ip := netip.AddrFrom4([4]byte{192, 0, byte(i >> 8), byte(i)}) - s.Records = append(s.Records, &pb.ApplicationRecord{Status: pb.ApplyStatus_APPLY_STATUS_VERIFIED, AppliedAt: timestamppb.New(time.Unix(1700000000, 0)), Assignment: &pb.Assignment{Id: fmt.Sprintf("assignment-%d", i), Generation: 1, Mode: pb.AssignmentMode_ASSIGNMENT_MODE_STABLE, Treatment: &pb.Treatment{Destination: ip.String() + "/32", Settings: &pb.TCPSettings{CongestionControl: proto.String("cubic")}}}}) + s.Records = append(s.Records, &pb.ApplicationRecord{Status: pb.ApplyStatus_APPLY_STATUS_VERIFIED, AppliedAt: timestamppb.New(time.Unix(1700000000, 0)), Assignment: &pb.Assignment{ExperimentId: fmt.Sprintf("assignment-%d", i), Generation: 1, Mode: pb.AssignmentMode_ASSIGNMENT_MODE_STABLE, Treatment: &pb.Treatment{Destination: ip.String() + "/32", Settings: &pb.TCPSettings{CongestionControl: proto.String("cubic")}}}}) } a.publish(s) r := &flat.XtcpFlatRecord{NetnsInode: 123, InetDiagMsgFamily: 2, InetDiagMsgSocketDestination: []byte{192, 0, 0, 0}, InetDiagCong: "cubic"} diff --git a/pkg/treatment/agent/contracts_test.go b/pkg/treatment/agent/contracts_test.go index 8f4e831be..7c8fe2810 100644 --- a/pkg/treatment/agent/contracts_test.go +++ b/pkg/treatment/agent/contracts_test.go @@ -136,13 +136,13 @@ func TestConcurrentPublishStampState(t *testing.T) { return } state := a.State() - state.Records[0].Assignment.Id = "caller mutation" + state.Records[0].Assignment.ExperimentId = "caller mutation" a.Wake() } }) } wg.Wait() - if a.State().Records[0].Assignment.Id != "assignment-0" { + if a.State().Records[0].Assignment.ExperimentId != "assignment-0" { t.Fatal("State leaked mutable internal snapshot") } select { diff --git a/pkg/treatment/controller/analyze.go b/pkg/treatment/controller/analyze.go index df12c915a..4528c1867 100644 --- a/pkg/treatment/controller/analyze.go +++ b/pkg/treatment/controller/analyze.go @@ -59,13 +59,13 @@ func (s *Server) Analyze(ctx context.Context, r *pb.AnalyzeRequest) (*pb.Analyze if e != nil { return nil, failure(e) } - out := &pb.AnalyzeResponse{Evidence: ev, Outcome: "baseline"} + out := &pb.AnalyzeResponse{Evidence: ev, Outcome: pb.Outcome_OUTCOME_BASELINE} if len(n.Phases) > 0 { v, e := experiment.Evaluate(input, ev, s.now()) if e != nil { return nil, failure(e) } - out.Outcome = v.Outcome + out.Outcome = v.Outcome.Wire() } e = s.Store.mutate(ctx, func(tx *sql.Tx) error { current, e := loadExperiment(ctx, tx, n.Id) diff --git a/pkg/treatment/controller/maintenance.go b/pkg/treatment/controller/maintenance.go index d8c83aaab..ddfc97f84 100644 --- a/pkg/treatment/controller/maintenance.go +++ b/pkg/treatment/controller/maintenance.go @@ -94,7 +94,7 @@ func (s *Server) Maintain(ctx context.Context, source EvidenceSource) error { } phase := n.Phases[len(n.Phases)-1] now := s.now() - outcome := "unavailable" + outcome := experiment.OutcomeUnavailable if n.Status == pb.ExperimentStatus_EXPERIMENT_STATUS_COMPLETE { if ev != nil { if e = saveEvidence(ctx, tx, id, ev); e != nil { @@ -102,12 +102,12 @@ func (s *Server) Maintain(ctx context.Context, source EvidenceSource) error { } healthy, harmful := experiment.HealthyEvidence(n, ev, now) n.LatestEvidence = ev - n.LatestOutcome = "stable_observed" + n.LatestOutcome = pb.Outcome_OUTCOME_STABLE_OBSERVED if !healthy { - n.LatestOutcome = "stable_evidence_incomplete" + n.LatestOutcome = pb.Outcome_OUTCOME_STABLE_EVIDENCE_INCOMPLETE } if harmful { - n.LatestOutcome = "stable_regression_review_required" + n.LatestOutcome = pb.Outcome_OUTCOME_STABLE_REGRESSION_REVIEW_REQUIRED if e = audit(ctx, tx, "drift-monitor", "", "stable_regression", ev); e != nil { return e } @@ -116,7 +116,7 @@ func (s *Server) Maintain(ctx context.Context, source EvidenceSource) error { return saveExperiment(ctx, tx, n) } if now.Sub(phase.IssuedAt.AsTime()) >= n.Policy.MaximumPhase.AsDuration() { - outcome = "expired" + outcome = experiment.OutcomeExpired } else if ev != nil { if e = saveEvidence(ctx, tx, id, ev); e != nil { return e @@ -130,14 +130,20 @@ func (s *Server) Maintain(ctx context.Context, source EvidenceSource) error { // approved/evaluated. Only Review writes reviewer and outcome. n.LatestEvidence = ev if _, harmful := experiment.HealthyEvidence(n, ev, now); harmful { - outcome = "harmful" + outcome = experiment.OutcomeHarmful } } - n.LatestOutcome = outcome - if outcome == "harmful" || outcome == "expired" { - phase.Outcome = outcome + n.LatestOutcome = outcome.Wire() + if outcome.Withdraws() { + event := experiment.EventExpire + if outcome == experiment.OutcomeHarmful { + event = experiment.EventHarm + } + if n.Status, e = experiment.Transition(n.Status, event); e != nil { + return e + } + phase.Outcome = outcome.Wire() phase.EvaluatedAt = timestamppb.New(now) - n.Status = pb.ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK n.Revision++ if e = s.assign(ctx, tx, n, false); e != nil { return e @@ -149,7 +155,7 @@ func (s *Server) Maintain(ctx context.Context, source EvidenceSource) error { // Before the 30-minute decision window completes, fresh health // evidence can renew the existing phase but cannot expand it. healthy, _ := experiment.HealthyEvidence(n, ev, now) - if healthy && outcome != "stale" && outcome != "insufficient_data" { + if healthy && outcome.RenewsLease() { if e = s.renew(ctx, tx, n, now); e != nil { return e } @@ -191,7 +197,7 @@ func (s *Server) renew(ctx context.Context, tx *sql.Tx, n *pb.Experiment, now ti } verified := false for _, r := range o.Records { - if r.Assignment.Id == a.Id && r.Assignment.Generation == a.Generation && r.Status == pb.ApplyStatus_APPLY_STATUS_VERIFIED { + if r.Assignment.ExperimentId == a.ExperimentId && r.Assignment.Generation == a.Generation && r.Status == pb.ApplyStatus_APPLY_STATUS_VERIFIED { verified = true } } diff --git a/pkg/treatment/controller/rpc_contracts_test.go b/pkg/treatment/controller/rpc_contracts_test.go index 8303f6995..f25d07cf8 100644 --- a/pkg/treatment/controller/rpc_contracts_test.go +++ b/pkg/treatment/controller/rpc_contracts_test.go @@ -32,7 +32,7 @@ func TestCheckInValidation(t *testing.T) { {"long-algorithm", "algorithm names fit kernel ABI", func(r *pb.CheckInRequest) { r.Capabilities.CongestionAlgorithms = []string{strings.Repeat("a", 16)} }, codes.InvalidArgument}, {"nil-record", "nil application record fails closed", func(r *pb.CheckInRequest) { r.Records = []*pb.ApplicationRecord{nil} }, codes.InvalidArgument}, {"kernel-bytes", "kernel route bytes must remain local", func(r *pb.CheckInRequest) { - r.Records = []*pb.ApplicationRecord{{Assignment: &pb.Assignment{Id: "assignment"}, InstalledRoute: []byte{1}}} + r.Records = []*pb.ApplicationRecord{{Assignment: &pb.Assignment{ExperimentId: "assignment"}, InstalledRoute: []byte{1}}} }, codes.InvalidArgument}, } { t.Run(tc.name, func(t *testing.T) { diff --git a/pkg/treatment/controller/server.go b/pkg/treatment/controller/server.go index a2f980340..82680c06b 100644 --- a/pkg/treatment/controller/server.go +++ b/pkg/treatment/controller/server.go @@ -76,10 +76,10 @@ func (s *Server) CheckIn(ctx context.Context, r *pb.CheckInRequest) (*pb.CheckIn } seen := map[string]bool{} for _, v := range r.Records { - if v == nil || v.Assignment == nil || !treatment.ValidID(v.Assignment.Id) || seen[v.Assignment.Id] || len(v.Error) > 4096 || len(v.InstalledRoute) > 0 { + if v == nil || v.Assignment == nil || !treatment.ValidID(v.Assignment.ExperimentId) || seen[v.Assignment.ExperimentId] || len(v.Error) > 4096 || len(v.InstalledRoute) > 0 { return nil, status.Error(codes.InvalidArgument, "invalid application record (kernel bytes are local only)") } - seen[v.Assignment.Id] = true + seen[v.Assignment.ExperimentId] = true if e := treatment.ValidateTreatment(v.Assignment.Treatment); e != nil { return nil, status.Error(codes.InvalidArgument, e.Error()) } @@ -243,7 +243,7 @@ func (s *Server) propose(ctx context.Context, r *pb.ProposeRequest, actor string n.ProposedAt = timestamppb.New(s.now()) n.Phases = nil n.LatestEvidence = nil - n.LatestOutcome = "" + n.LatestOutcome = pb.Outcome_OUTCOME_UNSPECIFIED active, e := hasActiveExperiment(ctx, tx, n.Location) if e != nil { return e @@ -335,7 +335,9 @@ func (s *Server) Review(ctx context.Context, r *pb.ReviewRequest) (out *pb.Revie return errors.New("experiment revision changed; review fresh evidence") } if !r.Approve { - n.Status = pb.ExperimentStatus_EXPERIMENT_STATUS_RETIRED + if n.Status, e = experiment.Transition(n.Status, experiment.EventReject); e != nil { + return e + } n.Revision++ } else { if len(n.Phases) == 0 { @@ -356,7 +358,7 @@ func (s *Server) Review(ctx context.Context, r *pb.ReviewRequest) (out *pb.Revie if e = s.startObservation(ctx, tx, n); e != nil { return e } - if n.LatestOutcome == "awaiting_application" { + if n.LatestOutcome == pb.Outcome_OUTCOME_AWAITING_APPLICATION { return errors.New("cohort is not currently verified") } if e = saveEvidence(ctx, tx, n.Id, r.Evidence); e != nil { @@ -417,7 +419,7 @@ func (s *Server) startObservation(ctx context.Context, tx *sql.Tx, e *pb.Experim } p := e.Phases[len(e.Phases)-1] latest := p.IssuedAt.AsTime() - notReady := func() error { e.LatestOutcome = "awaiting_application"; return nil } + notReady := func() error { e.LatestOutcome = pb.Outcome_OUTCOME_AWAITING_APPLICATION; return nil } for _, id := range e.CohortOrder[:p.TargetHosts] { var raw []byte var seen time.Time @@ -449,7 +451,7 @@ func (s *Server) startObservation(ctx context.Context, tx *sql.Tx, e *pb.Experim } found := false for _, a := range o.Records { - if a.Assignment.Id == expected.Id && a.Assignment.Generation == expected.Generation && a.Status == pb.ApplyStatus_APPLY_STATUS_VERIFIED && !a.ExpiredLatched && a.AppliedAt != nil && a.AppliedAt.CheckValid() == nil && proto.Equal(a.Assignment.Treatment, expected.Treatment) { + if a.Assignment.ExperimentId == expected.ExperimentId && a.Assignment.Generation == expected.Generation && a.Status == pb.ApplyStatus_APPLY_STATUS_VERIFIED && !a.ExpiredLatched && a.AppliedAt != nil && a.AppliedAt.CheckValid() == nil && proto.Equal(a.Assignment.Treatment, expected.Treatment) { found = true t := a.AppliedAt.AsTime() if t.After(s.now().Add(30 * time.Second)) { @@ -467,8 +469,8 @@ func (s *Server) startObservation(ctx context.Context, tx *sql.Tx, e *pb.Experim if p.ObservationStartedAt == nil { p.ObservationStartedAt = timestamppb.New(latest) } - if e.LatestOutcome == "awaiting_application" { - e.LatestOutcome = "observing" + if e.LatestOutcome == pb.Outcome_OUTCOME_AWAITING_APPLICATION { + e.LatestOutcome = pb.Outcome_OUTCOME_OBSERVING } return nil } @@ -523,7 +525,7 @@ func (s *Server) assign(ctx context.Context, tx *sql.Tx, e *pb.Experiment, react x.Mode = pb.AssignmentMode_ASSIGNMENT_MODE_RETIRED } } - a = &pb.Assignment{Id: e.Id, ExperimentId: e.Id, Treatment: proto.Clone(e.Treatment).(*pb.Treatment), Generation: 1} + a = &pb.Assignment{ExperimentId: e.Id, Treatment: proto.Clone(e.Treatment).(*pb.Treatment), Generation: 1} d.Assignments = append(d.Assignments, a) a.Predecessor = predecessor } @@ -594,35 +596,19 @@ func (s *Server) Control(ctx context.Context, r *pb.ControlRequest) (out *pb.Con if e != nil { return e } - if n.Status == pb.ExperimentStatus_EXPERIMENT_STATUS_RETIRED || n.Status == pb.ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK { + if experiment.Terminal(n.Status) { return errors.New("terminal experiment cannot be reactivated") } - switch r.Action { - case pb.ControlAction_CONTROL_ACTION_PAUSE: - if n.Status != pb.ExperimentStatus_EXPERIMENT_STATUS_RUNNING { - return errors.New("only running experiment can pause") - } - n.Status = pb.ExperimentStatus_EXPERIMENT_STATUS_PAUSED - case pb.ControlAction_CONTROL_ACTION_SUSPEND: - n.Status = pb.ExperimentStatus_EXPERIMENT_STATUS_SUSPENDED - case pb.ControlAction_CONTROL_ACTION_RETIRE: - n.Status = pb.ExperimentStatus_EXPERIMENT_STATUS_RETIRED - case pb.ControlAction_CONTROL_ACTION_ROLLBACK: - n.Status = pb.ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK - case pb.ControlAction_CONTROL_ACTION_RESUME: - if n.Status != pb.ExperimentStatus_EXPERIMENT_STATUS_PAUSED && n.Status != pb.ExperimentStatus_EXPERIMENT_STATUS_SUSPENDED { - return errors.New("experiment is not held") - } - if len(n.Phases) == 0 { - return errors.New("proposal requires initial review") - } - n.Status = pb.ExperimentStatus_EXPERIMENT_STATUS_RUNNING + event, e := controlEvent(n, r.Action) + if e != nil { + return e + } + if n.Status, e = experiment.Transition(n.Status, event); e != nil { + return e + } + if event == experiment.EventResume { p := n.Phases[len(n.Phases)-1] - if p.Outcome == "final_safety" { - n.Status = pb.ExperimentStatus_EXPERIMENT_STATUS_COMPLETE - } else { - n.Phases = append(n.Phases, &pb.Phase{TargetHosts: p.TargetHosts, IssuedAt: timestamppb.New(s.now()), Reviewer: actor, Reason: r.Reason}) - } + n.Phases = append(n.Phases, &pb.Phase{TargetHosts: p.TargetHosts, IssuedAt: timestamppb.New(s.now()), Reviewer: actor, Reason: r.Reason}) } n.Revision++ if e = s.assign(ctx, tx, n, r.Action == pb.ControlAction_CONTROL_ACTION_RESUME); e != nil { @@ -695,6 +681,31 @@ func (s *Server) Control(ctx context.Context, r *pb.ControlRequest) (out *pb.Con return out, failure(err) } +// controlEvent maps an operator action onto a state-machine event. Resume +// needs the experiment: a held completed treatment resumes as stable, a held +// trial resumes with a fresh phase, and a held proposal cannot resume at all. +func controlEvent(n *pb.Experiment, action pb.ControlAction) (experiment.Event, error) { + switch action { + case pb.ControlAction_CONTROL_ACTION_PAUSE: + return experiment.EventPause, nil + case pb.ControlAction_CONTROL_ACTION_SUSPEND: + return experiment.EventSuspend, nil + case pb.ControlAction_CONTROL_ACTION_RETIRE: + return experiment.EventRetire, nil + case pb.ControlAction_CONTROL_ACTION_ROLLBACK: + return experiment.EventRollback, nil + case pb.ControlAction_CONTROL_ACTION_RESUME: + if len(n.Phases) == 0 { + return 0, errors.New("proposal requires initial review") + } + if n.Phases[len(n.Phases)-1].Outcome == pb.Outcome_OUTCOME_FINAL_SAFETY { + return experiment.EventResumeStable, nil + } + return experiment.EventResume, nil + } + return 0, fmt.Errorf("unknown control action %s", action) +} + func (s *Server) Inspect(ctx context.Context, r *pb.InspectRequest) (*pb.InspectResponse, error) { if _, e := identity.Operator(ctx); e != nil { return nil, e diff --git a/pkg/treatment/controller/server_test.go b/pkg/treatment/controller/server_test.go index 9402369e7..8f531a278 100644 --- a/pkg/treatment/controller/server_test.go +++ b/pkg/treatment/controller/server_test.go @@ -368,7 +368,7 @@ func TestPostgresAnalyzeRefreshesWithoutApproval(t *testing.T) { ev.Id = "refresh-1" s.EvidenceSource = fixedEvidence{ev} out, e := s.Analyze(operator, &pb.AnalyzeRequest{ExperimentId: p.Experiment.Id, ExpectedRevision: 1}) - if e != nil || out.Outcome != "baseline" { + if e != nil || out.Outcome != pb.Outcome_OUTCOME_BASELINE { t.Fatal("refresh", out, e) } inspected, e := s.Inspect(operator, &pb.InspectRequest{ExperimentId: p.Experiment.Id}) diff --git a/pkg/treatment/experiment/benchmark_test.go b/pkg/treatment/experiment/benchmark_test.go index 20dfb87ca..e2e90f031 100644 --- a/pkg/treatment/experiment/benchmark_test.go +++ b/pkg/treatment/experiment/benchmark_test.go @@ -47,7 +47,7 @@ func BenchmarkFleetEvidence(b *testing.B) { }}, {"Evaluate", func() error { got, err := Evaluate(e, v, now) - if err == nil && got.Outcome != "beneficial" { + if err == nil && got.Outcome != OutcomeBeneficial { return fmt.Errorf("unexpected outcome %s", got.Outcome) } return err diff --git a/pkg/treatment/experiment/boundaries_test.go b/pkg/treatment/experiment/boundaries_test.go index f8292cd34..15a7c3948 100644 --- a/pkg/treatment/experiment/boundaries_test.go +++ b/pkg/treatment/experiment/boundaries_test.go @@ -14,57 +14,58 @@ import ( func TestEvaluationBoundaries(t *testing.T) { now := time.Unix(1700000000, 0) for _, tc := range []struct { - name, description, want string - wantError bool - change func(*pb.Experiment, *pb.Evidence) + name, description string + want Outcome + wantError bool + change func(*pb.Experiment, *pb.Evidence) }{ - {"no-phase", "unstarted experiment cannot be evaluated", "", true, func(e *pb.Experiment, _ *pb.Evidence) { e.Phases = nil }}, - {"no-observation", "all canaries must acknowledge exposure", "awaiting_exposure", false, func(e *pb.Experiment, _ *pb.Evidence) { e.Phases[0].ObservationStartedAt = nil }}, - {"expiry-equality", "phase expires exactly at its deadline", "expired", false, func(e *pb.Experiment, _ *pb.Evidence) { + {"no-phase", "unstarted experiment cannot be evaluated", OutcomeUnspecified, true, func(e *pb.Experiment, _ *pb.Evidence) { e.Phases = nil }}, + {"no-observation", "all canaries must acknowledge exposure", OutcomeAwaitingExposure, false, func(e *pb.Experiment, _ *pb.Evidence) { e.Phases[0].ObservationStartedAt = nil }}, + {"expiry-equality", "phase expires exactly at its deadline", OutcomeExpired, false, func(e *pb.Experiment, _ *pb.Evidence) { e.Phases[0].IssuedAt = timestamppb.New(now.Add(-24 * time.Hour)) }}, - {"freshness-equality", "evidence at maximum age is usable", "beneficial", false, func(_ *pb.Experiment, v *pb.Evidence) { v.WindowEnd = timestamppb.New(now.Add(-2 * time.Minute)) }}, - {"freshness-over", "one nanosecond beyond maximum age is stale", "stale", false, func(_ *pb.Experiment, v *pb.Evidence) { + {"freshness-equality", "evidence at maximum age is usable", OutcomeBeneficial, false, func(_ *pb.Experiment, v *pb.Evidence) { v.WindowEnd = timestamppb.New(now.Add(-2 * time.Minute)) }}, + {"freshness-over", "one nanosecond beyond maximum age is stale", OutcomeStale, false, func(_ *pb.Experiment, v *pb.Evidence) { v.WindowEnd = timestamppb.New(now.Add(-2*time.Minute - time.Nanosecond)) }}, - {"clock-equality", "30 seconds of forward clock skew is allowed", "beneficial", false, func(_ *pb.Experiment, v *pb.Evidence) { v.AnalyzedAt = timestamppb.New(now.Add(30 * time.Second)) }}, - {"clock-over", "excess forward skew is rejected", "stale", false, func(_ *pb.Experiment, v *pb.Evidence) { + {"clock-equality", "30 seconds of forward clock skew is allowed", OutcomeBeneficial, false, func(_ *pb.Experiment, v *pb.Evidence) { v.AnalyzedAt = timestamppb.New(now.Add(30 * time.Second)) }}, + {"clock-over", "excess forward skew is rejected", OutcomeStale, false, func(_ *pb.Experiment, v *pb.Evidence) { v.AnalyzedAt = timestamppb.New(now.Add(30*time.Second + time.Nanosecond)) }}, - {"duration-equality", "exactly minimum observation is sufficient", "beneficial", false, func(_ *pb.Experiment, v *pb.Evidence) { + {"duration-equality", "exactly minimum observation is sufficient", OutcomeBeneficial, false, func(_ *pb.Experiment, v *pb.Evidence) { v.WindowStart = timestamppb.New(v.WindowEnd.AsTime().Add(-30 * time.Minute)) }}, - {"duration-under", "one nanosecond short is insufficient", "insufficient_duration", false, func(_ *pb.Experiment, v *pb.Evidence) { + {"duration-under", "one nanosecond short is insufficient", OutcomeInsufficientDuration, false, func(_ *pb.Experiment, v *pb.Evidence) { v.WindowStart = timestamppb.New(v.WindowEnd.AsTime().Add(-30*time.Minute + time.Nanosecond)) }}, - {"old-window", "pre-treatment data cannot support promotion", "insufficient_duration", false, func(e *pb.Experiment, v *pb.Evidence) { + {"old-window", "pre-treatment data cannot support promotion", OutcomeInsufficientDuration, false, func(e *pb.Experiment, v *pb.Evidence) { v.WindowStart = timestamppb.New(e.Phases[0].ObservationStartedAt.AsTime().Add(-time.Nanosecond)) }}, - {"zero-target", "phase must have at least one canary", "", true, func(e *pb.Experiment, _ *pb.Evidence) { e.Phases[0].TargetHosts = 0 }}, - {"over-target", "phase cannot exceed fleet size", "", true, func(e *pb.Experiment, _ *pb.Evidence) { e.Phases[0].TargetHosts = 17 }}, - {"sample-equality", "exact sample and exposure thresholds suffice", "beneficial", false, func(e *pb.Experiment, v *pb.Evidence) { + {"zero-target", "phase must have at least one canary", OutcomeUnspecified, true, func(e *pb.Experiment, _ *pb.Evidence) { e.Phases[0].TargetHosts = 0 }}, + {"over-target", "phase cannot exceed fleet size", OutcomeUnspecified, true, func(e *pb.Experiment, _ *pb.Evidence) { e.Phases[0].TargetHosts = 17 }}, + {"sample-equality", "exact sample and exposure thresholds suffice", OutcomeBeneficial, false, func(e *pb.Experiment, v *pb.Evidence) { for _, h := range v.Hosts { h.Sockets = e.Policy.MinimumSocketsPerHost h.ExposedSockets = h.Sockets h.BytesAcked = e.Policy.MinimumBytesPerHost } }}, - {"missing-host", "missing cohort host blocks evaluation", "insufficient_data", false, func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts = v.Hosts[1:] }}, - {"missing-baseline", "missing baseline host blocks evaluation", "insufficient_data", false, func(e *pb.Experiment, _ *pb.Evidence) { e.Baseline.Hosts = e.Baseline.Hosts[1:] }}, - {"low-bytes", "too few bytes blocks evaluation", "insufficient_data", false, func(e *pb.Experiment, v *pb.Evidence) { v.Hosts[0].BytesAcked = e.Policy.MinimumBytesPerHost - 1 }}, - {"zero-goodput", "zero goodput cannot enter log comparison", "insufficient_data", false, func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts[0].GoodputBps = 0 }}, - {"zero-baseline-rtt", "zero baseline RTT cannot enter ratio", "insufficient_data", false, func(e *pb.Experiment, _ *pb.Evidence) { e.Baseline.Hosts[0].RttP95Ms = 0 }}, - {"first-canary", "small cohorts permit safety review only", "safety_only", false, func(e *pb.Experiment, _ *pb.Evidence) { e.Phases[0].TargetHosts = 1 }}, - {"small-cohort-regression", "large goodput loss blocks small cohort promotion", "harmful", false, func(e *pb.Experiment, v *pb.Evidence) { e.Phases[0].TargetHosts = 1; v.Hosts[0].GoodputBps = 1 }}, - {"one-control", "one control cannot estimate variance", "insufficient_controls", false, func(e *pb.Experiment, _ *pb.Evidence) { e.Phases[0].TargetHosts = 15 }}, - {"full-fleet", "last phase checks safety without controls", "final_safety", false, func(e *pb.Experiment, _ *pb.Evidence) { e.Phases[0].TargetHosts = 16 }}, - {"full-fleet-harm", "last phase still rejects goodput regression", "harmful", false, func(e *pb.Experiment, v *pb.Evidence) { + {"missing-host", "missing cohort host blocks evaluation", OutcomeInsufficientData, false, func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts = v.Hosts[1:] }}, + {"missing-baseline", "missing baseline host blocks evaluation", OutcomeInsufficientData, false, func(e *pb.Experiment, _ *pb.Evidence) { e.Baseline.Hosts = e.Baseline.Hosts[1:] }}, + {"low-bytes", "too few bytes blocks evaluation", OutcomeInsufficientData, false, func(e *pb.Experiment, v *pb.Evidence) { v.Hosts[0].BytesAcked = e.Policy.MinimumBytesPerHost - 1 }}, + {"zero-goodput", "zero goodput cannot enter log comparison", OutcomeInsufficientData, false, func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts[0].GoodputBps = 0 }}, + {"zero-baseline-rtt", "zero baseline RTT cannot enter ratio", OutcomeInsufficientData, false, func(e *pb.Experiment, _ *pb.Evidence) { e.Baseline.Hosts[0].RttP95Ms = 0 }}, + {"first-canary", "small cohorts permit safety review only", OutcomeSafetyOnly, false, func(e *pb.Experiment, _ *pb.Evidence) { e.Phases[0].TargetHosts = 1 }}, + {"small-cohort-regression", "large goodput loss blocks small cohort promotion", OutcomeHarmful, false, func(e *pb.Experiment, v *pb.Evidence) { e.Phases[0].TargetHosts = 1; v.Hosts[0].GoodputBps = 1 }}, + {"one-control", "one control cannot estimate variance", OutcomeInsufficientControls, false, func(e *pb.Experiment, _ *pb.Evidence) { e.Phases[0].TargetHosts = 15 }}, + {"full-fleet", "last phase checks safety without controls", OutcomeFinalSafety, false, func(e *pb.Experiment, _ *pb.Evidence) { e.Phases[0].TargetHosts = 16 }}, + {"full-fleet-harm", "last phase still rejects goodput regression", OutcomeHarmful, false, func(e *pb.Experiment, v *pb.Evidence) { e.Phases[0].TargetHosts = 16 for _, h := range v.Hosts { h.GoodputBps = 1 } }}, - {"invalid-evidence", "malformed evidence fails before decision", "", true, func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts[0] = nil }}, + {"invalid-evidence", "malformed evidence fails before decision", OutcomeUnspecified, true, func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts[0] = nil }}, } { t.Run(tc.name, func(t *testing.T) { e, v := fixtures.Experiment(now, treatment.DefaultPolicy()) @@ -179,11 +180,11 @@ func TestDefaultRTTGuardExactBoundary(t *testing.T) { for _, tc := range []struct { description string rtt float64 - want string + want Outcome healthy bool }{ - {"exactly ten percent increase is within the configured guard", 5.5, "beneficial", true}, - {"measurable increase above ten percent is harmful", 5.500001, "harmful", false}, + {"exactly ten percent increase is within the configured guard", 5.5, OutcomeBeneficial, true}, + {"measurable increase above ten percent is harmful", 5.500001, OutcomeHarmful, false}, } { t.Run(tc.description, func(t *testing.T) { now := time.Unix(1700000000, 0) @@ -213,7 +214,7 @@ func TestRTTGuardFleetBoundary(t *testing.T) { h.RttP95Ms = 5.5 } got, err := Evaluate(e, v, now) - if err != nil || got.Outcome != "beneficial" { + if err != nil || got.Outcome != OutcomeBeneficial { t.Errorf("%d hosts at exactly the RTT guard: %v %v", n, got, err) } } diff --git a/pkg/treatment/experiment/experiment.go b/pkg/treatment/experiment/experiment.go index f4398bfc2..afaac2730 100644 --- a/pkg/treatment/experiment/experiment.go +++ b/pkg/treatment/experiment/experiment.go @@ -13,7 +13,7 @@ import ( ) type Evaluation struct { - Outcome string + Outcome Outcome Improvement, LowerBound float64 } @@ -31,16 +31,16 @@ func Evaluate(e *pb.Experiment, ev *pb.Evidence, now time.Time) (Evaluation, err p := e.Policy phase := e.Phases[len(e.Phases)-1] if phase.ObservationStartedAt == nil { - return Evaluation{Outcome: "awaiting_exposure"}, nil + return Evaluation{Outcome: OutcomeAwaitingExposure}, nil } if now.Sub(phase.IssuedAt.AsTime()) >= p.MaximumPhase.AsDuration() { - return Evaluation{Outcome: "expired"}, nil + return Evaluation{Outcome: OutcomeExpired}, nil } if ev.AnalyzedAt.AsTime().After(now.Add(30*time.Second)) || now.Sub(ev.WindowEnd.AsTime()) > p.MaximumEvidenceAge.AsDuration() { - return Evaluation{Outcome: "stale"}, nil + return Evaluation{Outcome: OutcomeStale}, nil } if ev.WindowStart.AsTime().Before(phase.ObservationStartedAt.AsTime()) || ev.WindowEnd.AsTime().Sub(ev.WindowStart.AsTime()) < p.MinimumObservation.AsDuration() { - return Evaluation{Outcome: "insufficient_duration"}, nil + return Evaluation{Outcome: OutcomeInsufficientDuration}, nil } base := make(map[string]*pb.HostEvidence, len(e.Baseline.Hosts)) current := make(map[string]*pb.HostEvidence, len(ev.Hosts)) @@ -60,10 +60,10 @@ func Evaluate(e *pb.Experiment, ev *pb.Evidence, now time.Time) (Evaluation, err for i, id := range e.CohortOrder { h, b := current[id], base[id] if h == nil || b == nil || !h.CollectionHealthy || !b.CollectionHealthy || h.Sockets < p.MinimumSocketsPerHost || h.BytesAcked < p.MinimumBytesPerHost || h.GoodputBps <= 0 || b.GoodputBps <= 0 || b.RttP95Ms <= 0 { - return Evaluation{Outcome: "insufficient_data"}, nil + return Evaluation{Outcome: OutcomeInsufficientData}, nil } if i < target && h.ExposedSockets < p.MinimumSocketsPerHost { - return Evaluation{Outcome: "awaiting_exposure"}, nil + return Evaluation{Outcome: OutcomeAwaitingExposure}, nil } x := math.Log(h.GoodputBps / b.GoodputBps) if i < target { @@ -79,25 +79,25 @@ func Evaluate(e *pb.Experiment, ev *pb.Evidence, now time.Time) (Evaluation, err // Guardrails protect controls as well as treated traffic. A shared-link // regression is harmful even if treated hosts outperform their peers. if mean(tr) > p.MaximumRetransmissionIncrease || mean(cr) > p.MaximumRetransmissionIncrease || mean(tlat) > math.Log1p(p.MaximumRttIncrease) || mean(clat) > math.Log1p(p.MaximumRttIncrease) { - return Evaluation{Outcome: "harmful"}, nil + return Evaluation{Outcome: OutcomeHarmful}, nil } effect := mean(treated) - mean(control) if len(control) == 0 { // Final 100% phase is a safety check; causal benefit was established // before the last control cohort received the treatment. if effect < -math.Log1p(p.MinimumImprovement) { - return Evaluation{Outcome: "harmful"}, nil + return Evaluation{Outcome: OutcomeHarmful}, nil } - return Evaluation{Outcome: "final_safety", Improvement: math.Expm1(effect)}, nil + return Evaluation{Outcome: OutcomeFinalSafety, Improvement: math.Expm1(effect)}, nil } if len(treated) < int(p.MinimumTreatedHosts) { if effect < -math.Log1p(p.MinimumImprovement) { - return Evaluation{Outcome: "harmful"}, nil + return Evaluation{Outcome: OutcomeHarmful}, nil } - return Evaluation{Outcome: "safety_only", Improvement: math.Expm1(effect)}, nil + return Evaluation{Outcome: OutcomeSafetyOnly, Improvement: math.Expm1(effect)}, nil } if len(control) < 2 { - return Evaluation{Outcome: "insufficient_controls"}, nil + return Evaluation{Outcome: OutcomeInsufficientControls}, nil } a, b := variance(treated)/float64(len(treated)), variance(control)/float64(len(control)) se := math.Sqrt(a + b) @@ -107,9 +107,9 @@ func Evaluate(e *pb.Experiment, ev *pb.Evidence, now time.Time) (Evaluation, err looks := (math.Ceil(math.Log2(float64(len(e.CohortOrder)))) + 2) * math.Ceil(p.MaximumPhase.AsDuration().Minutes()) lower -= studentQuantile(1-.05/looks, df) * se } - out := Evaluation{Outcome: "inconclusive", Improvement: math.Expm1(effect), LowerBound: math.Expm1(lower)} + out := Evaluation{Outcome: OutcomeInconclusive, Improvement: math.Expm1(effect), LowerBound: math.Expm1(lower)} if lower > math.Log1p(p.MinimumImprovement) { - out.Outcome = "beneficial" + out.Outcome = OutcomeBeneficial } return out, nil } @@ -239,8 +239,10 @@ func NextCohort(current, total int) int { // assignments in one PostgreSQL transaction after authenticating the reviewer. func Advance(e *pb.Experiment, ev *pb.Evidence, now time.Time, reviewer, reason string) (*pb.Experiment, error) { n := proto.Clone(e).(*pb.Experiment) - if n.Status != pb.ExperimentStatus_EXPERIMENT_STATUS_PROPOSED && n.Status != pb.ExperimentStatus_EXPERIMENT_STATUS_RUNNING { - return nil, errors.New("experiment cannot advance in its current state") + // Both outcomes of approval are checked up front so an illegal status + // fails before any evidence is attached to the phase. + if _, err := Transition(n.Status, EventApprove); err != nil { + return nil, err } target := 0 if len(n.Phases) > 0 { @@ -250,22 +252,30 @@ func Advance(e *pb.Experiment, ev *pb.Evidence, now time.Time, reviewer, reason if err != nil { return nil, err } - if v.Outcome != "beneficial" && v.Outcome != "safety_only" && v.Outcome != "final_safety" { + if !v.Outcome.Promotable() { return nil, fmt.Errorf("promotion blocked: %s", v.Outcome) } phase.Evidence = proto.Clone(ev).(*pb.Evidence) - phase.Outcome = v.Outcome + phase.Outcome = v.Outcome.Wire() phase.EvaluatedAt = timestamppb.New(now) phase.Reviewer = reviewer phase.Reason = reason if target == len(n.CohortOrder) { - n.Status = pb.ExperimentStatus_EXPERIMENT_STATUS_COMPLETE + status, err := Transition(n.Status, EventApproveFinal) + if err != nil { + return nil, err + } + n.Status = status n.Revision++ return n, nil } } n.Phases = append(n.Phases, &pb.Phase{TargetHosts: uint32(NextCohort(target, len(n.CohortOrder))), IssuedAt: timestamppb.New(now), Reviewer: reviewer, Reason: reason}) - n.Status = pb.ExperimentStatus_EXPERIMENT_STATUS_RUNNING + status, err := Transition(n.Status, EventApprove) + if err != nil { + return nil, err + } + n.Status = status n.Revision++ return n, nil } diff --git a/pkg/treatment/experiment/experiment_test.go b/pkg/treatment/experiment/experiment_test.go index 4aaaf21df..8c0860c68 100644 --- a/pkg/treatment/experiment/experiment_test.go +++ b/pkg/treatment/experiment/experiment_test.go @@ -16,21 +16,22 @@ func TestEvaluationBenefitHarmAndEvidenceGates(t *testing.T) { now := time.Now() e, v := fixtures.Experiment(now, treatment.DefaultPolicy()) for _, tc := range []struct { - name, want string - change func(*pb.Experiment, *pb.Evidence) + name string + want Outcome + change func(*pb.Experiment, *pb.Evidence) }{ - {"benefit", "beneficial", func(*pb.Experiment, *pb.Evidence) {}}, - {"loss", "harmful", func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts[0].RetransmissionRatio = .2 }}, - {"control harm", "harmful", func(_ *pb.Experiment, v *pb.Evidence) { + {"benefit", OutcomeBeneficial, func(*pb.Experiment, *pb.Evidence) {}}, + {"loss", OutcomeHarmful, func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts[0].RetransmissionRatio = .2 }}, + {"control harm", OutcomeHarmful, func(_ *pb.Experiment, v *pb.Evidence) { for _, h := range v.Hosts[4:] { h.RttP95Ms = 3 } }}, - {"missing exposure", "awaiting_exposure", func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts[0].ExposedSockets = 0 }}, - {"stale", "stale", func(_ *pb.Experiment, v *pb.Evidence) { v.WindowEnd = timestamppb.New(now.Add(-3 * time.Minute)) }}, - {"missing data", "insufficient_data", func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts[0].CollectionHealthy = false }}, - {"too early", "insufficient_duration", func(_ *pb.Experiment, v *pb.Evidence) { v.WindowStart = timestamppb.New(now.Add(-5 * time.Minute)) }}, - {"no benefit", "inconclusive", func(_ *pb.Experiment, v *pb.Evidence) { + {"missing exposure", OutcomeAwaitingExposure, func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts[0].ExposedSockets = 0 }}, + {"stale", OutcomeStale, func(_ *pb.Experiment, v *pb.Evidence) { v.WindowEnd = timestamppb.New(now.Add(-3 * time.Minute)) }}, + {"missing data", OutcomeInsufficientData, func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts[0].CollectionHealthy = false }}, + {"too early", OutcomeInsufficientDuration, func(_ *pb.Experiment, v *pb.Evidence) { v.WindowStart = timestamppb.New(now.Add(-5 * time.Minute)) }}, + {"no benefit", OutcomeInconclusive, func(_ *pb.Experiment, v *pb.Evidence) { for _, h := range v.Hosts { h.GoodputBps = 1e8 } diff --git a/pkg/treatment/experiment/outcome.go b/pkg/treatment/experiment/outcome.go new file mode 100644 index 000000000..f2c8ea006 --- /dev/null +++ b/pkg/treatment/experiment/outcome.go @@ -0,0 +1,106 @@ +package experiment + +import ( + "fmt" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" +) + +// Outcome is the domain result of evaluating evidence against a phase, or of +// drift monitoring a completed treatment. Values are numerically identical to +// xtcp_treatment.v1.Outcome so that Wire and OutcomeFromWire are checked +// casts; TestOutcomeMirrorsWire pins that correspondence. +type Outcome uint8 + +const ( + OutcomeUnspecified Outcome = iota + OutcomeBaseline + OutcomeAwaitingApplication + OutcomeAwaitingExposure + OutcomeInsufficientDuration + OutcomeInsufficientData + OutcomeInsufficientControls + OutcomeStale + OutcomeUnavailable + OutcomeInconclusive + OutcomeSafetyOnly + OutcomeFinalSafety + OutcomeBeneficial + OutcomeHarmful + OutcomeExpired + OutcomeStableObserved + OutcomeStableEvidenceIncomplete + OutcomeStableRegressionReviewRequired + OutcomeObserving + outcomeCount // sentinel +) + +var outcomeNames = [outcomeCount]string{ + "unspecified", + "baseline", + "awaiting_application", + "awaiting_exposure", + "insufficient_duration", + "insufficient_data", + "insufficient_controls", + "stale", + "unavailable", + "inconclusive", + "safety_only", + "final_safety", + "beneficial", + "harmful", + "expired", + "stable_observed", + "stable_evidence_incomplete", + "stable_regression_review_required", + "observing", +} + +func (o Outcome) String() string { + if o < outcomeCount { + return outcomeNames[o] + } + return fmt.Sprintf("outcome(%d)", uint8(o)) +} + +// Valid reports whether o is a known value. Unspecified is valid: it is the +// state of an experiment nothing has evaluated yet. +func (o Outcome) Valid() bool { return o < outcomeCount } + +// Promotable reports whether a reviewer may advance the phase on this +// outcome. Every other outcome blocks promotion. +func (o Outcome) Promotable() bool { + return o == OutcomeBeneficial || o == OutcomeSafetyOnly || o == OutcomeFinalSafety +} + +// Withdraws reports whether the safety controller must roll the experiment +// back on this outcome without waiting for a reviewer. +func (o Outcome) Withdraws() bool { return o == OutcomeHarmful || o == OutcomeExpired } + +// RenewsLease reports whether healthy evidence with this outcome may renew +// the current phase's trial leases. Stale or missing data cannot. +func (o Outcome) RenewsLease() bool { return o != OutcomeStale && o != OutcomeInsufficientData } + +// ParseOutcome accepts the snake_case names used in documentation, logs and +// the pre-enum journal format. +func ParseOutcome(s string) (Outcome, error) { + for i, n := range outcomeNames { + if n == s { + return Outcome(i), nil + } + } + return OutcomeUnspecified, fmt.Errorf("unknown outcome %q", s) +} + +// Wire converts to the protobuf enum. +func (o Outcome) Wire() pb.Outcome { return pb.Outcome(o) } + +// OutcomeFromWire converts from the protobuf enum, rejecting values this +// build does not know so a newer peer cannot smuggle an unclassified result. +func OutcomeFromWire(w pb.Outcome) (Outcome, error) { + if w < 0 || int(w) >= int(outcomeCount) { + return OutcomeUnspecified, fmt.Errorf("unknown wire outcome %d", int32(w)) + } + return Outcome(w), nil +} diff --git a/pkg/treatment/experiment/outcome_test.go b/pkg/treatment/experiment/outcome_test.go new file mode 100644 index 000000000..c54db8980 --- /dev/null +++ b/pkg/treatment/experiment/outcome_test.go @@ -0,0 +1,90 @@ +package experiment + +import ( + "strings" + "testing" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" +) + +// TestOutcomeMirrorsWire pins the one-to-one correspondence between the Go +// Outcome constants and xtcp_treatment.v1.Outcome that Wire and +// OutcomeFromWire rely on being a plain cast. +func TestOutcomeMirrorsWire(t *testing.T) { + if len(pb.Outcome_name) != int(outcomeCount) { + t.Fatalf("proto defines %d outcomes, Go defines %d", len(pb.Outcome_name), outcomeCount) + } + for o := OutcomeUnspecified; o < outcomeCount; o++ { + name := o.String() + if name == "" || strings.HasPrefix(name, "outcome(") { + t.Errorf("Outcome(%d) has no name", uint8(o)) + } + if wire := pb.Outcome_name[int32(o)]; wire != "OUTCOME_"+strings.ToUpper(name) { + t.Errorf("Outcome %s mirrors wire value %q", name, wire) + } + if parsed, err := ParseOutcome(name); err != nil || parsed != o { + t.Errorf("ParseOutcome(%q)=%s,%v", name, parsed, err) + } + if back, err := OutcomeFromWire(o.Wire()); err != nil || back != o { + t.Errorf("OutcomeFromWire(Wire(%s))=%s,%v", name, back, err) + } + if !o.Valid() { + t.Errorf("%s must be valid", name) + } + } +} + +func TestOutcomeConversions(t *testing.T) { + for _, tc := range []struct { + name, description string + run func() (Outcome, error) + expected Outcome + expectError bool + }{ + {"parse-beneficial", "positive: documented name parses", func() (Outcome, error) { return ParseOutcome("beneficial") }, OutcomeBeneficial, false}, + {"wire-observing", "positive: the newest wire value converts", func() (Outcome, error) { return OutcomeFromWire(pb.Outcome_OUTCOME_OBSERVING) }, OutcomeObserving, false}, + {"parse-legacy-free-text", "negative: a free string from the pre-enum format is an error, not unspecified", func() (Outcome, error) { return ParseOutcome("promoted") }, OutcomeUnspecified, true}, + {"parse-wire-spelling", "negative: the proto spelling is not accepted by the domain parser", func() (Outcome, error) { return ParseOutcome("OUTCOME_BENEFICIAL") }, OutcomeUnspecified, true}, + {"parse-empty", "boundary: empty string is an error", func() (Outcome, error) { return ParseOutcome("") }, OutcomeUnspecified, true}, + {"zero-value", "boundary: the zero wire value is the explicit unspecified outcome", func() (Outcome, error) { return OutcomeFromWire(0) }, OutcomeUnspecified, false}, + {"wire-count-sentinel", "boundary: the first undefined wire value is rejected", func() (Outcome, error) { return OutcomeFromWire(pb.Outcome(outcomeCount)) }, OutcomeUnspecified, true}, + {"wire-high", "corner: a value from a newer peer is rejected with its number", func() (Outcome, error) { return OutcomeFromWire(255) }, OutcomeUnspecified, true}, + {"wire-negative", "corner: negative int32 enum values are rejected", func() (Outcome, error) { return OutcomeFromWire(-1) }, OutcomeUnspecified, true}, + } { + t.Run(tc.name, func(t *testing.T) { + got, err := tc.run() + if (err != nil) != tc.expectError || got != tc.expected { + t.Fatalf("%s: got %s err=%v; expected %s error=%t", tc.description, got, err, tc.expected, tc.expectError) + } + }) + } +} + +func TestOutcomeClassification(t *testing.T) { + for _, tc := range []struct { + description string + outcome Outcome + promotable bool + withdraws bool + renews bool + valid bool + str string + }{ + {"positive: beneficial promotes and renews", OutcomeBeneficial, true, false, true, true, "beneficial"}, + {"positive: safety-only promotes small cohorts", OutcomeSafetyOnly, true, false, true, true, "safety_only"}, + {"positive: final safety promotes the 100% phase", OutcomeFinalSafety, true, false, true, true, "final_safety"}, + {"negative: harmful withdraws and never promotes", OutcomeHarmful, false, true, true, true, "harmful"}, + {"negative: expired withdraws", OutcomeExpired, false, true, true, true, "expired"}, + {"negative: stale evidence cannot renew a lease", OutcomeStale, false, false, false, true, "stale"}, + {"negative: missing data cannot renew a lease", OutcomeInsufficientData, false, false, false, true, "insufficient_data"}, + {"boundary: zero value is valid, inert, and renews nothing it decides", OutcomeUnspecified, false, false, true, true, "unspecified"}, + {"boundary: last defined value", OutcomeObserving, false, false, true, true, "observing"}, + {"corner: sentinel is invalid and prints its number", outcomeCount, false, false, true, false, "outcome(19)"}, + {"corner: stable regression is a review flag, not an automatic withdrawal", OutcomeStableRegressionReviewRequired, false, false, true, true, "stable_regression_review_required"}, + } { + o := tc.outcome + if o.Promotable() != tc.promotable || o.Withdraws() != tc.withdraws || o.RenewsLease() != tc.renews || o.Valid() != tc.valid || o.String() != tc.str { + t.Errorf("%s: promotable=%t withdraws=%t renews=%t valid=%t str=%q", tc.description, o.Promotable(), o.Withdraws(), o.RenewsLease(), o.Valid(), o.String()) + } + } +} diff --git a/pkg/treatment/experiment/statemachine.go b/pkg/treatment/experiment/statemachine.go new file mode 100644 index 000000000..a051580ec --- /dev/null +++ b/pkg/treatment/experiment/statemachine.go @@ -0,0 +1,146 @@ +package experiment + +import ( + "errors" + "fmt" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" +) + +// Event is something that can change an experiment's status. Review, Control +// and Maintain all go through Transition, so the complete set of legal status +// changes is the table below and nothing else. +type Event uint8 + +const ( + eventUnspecified Event = iota + // EventApprove: a reviewer approved the proposal or the current phase; + // the next phase is issued. + EventApprove + // EventApproveFinal: a reviewer approved the last (100%) phase. + EventApproveFinal + // EventReject: a reviewer declined; the treatment is retired. + EventReject + // EventPause: an operator paused a running trial; routes stay in place. + EventPause + // EventSuspend: an operator suspended; routes are withdrawn but the + // experiment may be resumed. + EventSuspend + // EventResume: an operator resumed a held trial; a fresh phase of the + // same size is issued. + EventResume + // EventResumeStable: an operator resumed a held completed treatment. + EventResumeStable + // EventRollback: an operator or the safety controller withdrew the + // treatment and restored any predecessor. + EventRollback + // EventRetire: an operator ended the experiment without restoring a + // predecessor. + EventRetire + // EventExpire: the phase exceeded the policy's maximum duration. + EventExpire + // EventHarm: evidence crossed a guardrail. + EventHarm + eventCount +) + +var eventNames = [eventCount]string{"unspecified", "approve", "approve_final", "reject", "pause", "suspend", "resume", "resume_stable", "rollback", "retire", "expire", "harm"} + +func (e Event) String() string { + if e < eventCount { + return eventNames[e] + } + return fmt.Sprintf("event(%d)", uint8(e)) +} + +// Events lists every defined event, for exhaustive tests. +func Events() []Event { + out := make([]Event, 0, eventCount-1) + for e := EventApprove; e < eventCount; e++ { + out = append(out, e) + } + return out +} + +// Statuses lists every defined status, for exhaustive tests. +func Statuses() []pb.ExperimentStatus { + out := make([]pb.ExperimentStatus, 0, len(pb.ExperimentStatus_name)) + for i := range int32(len(pb.ExperimentStatus_name)) { + out = append(out, pb.ExperimentStatus(i)) + } + return out +} + +// ErrIllegalTransition is returned for any (status, event) pair not in the +// table. Callers map it to FailedPrecondition. +var ErrIllegalTransition = errors.New("illegal experiment transition") + +type transitionKey struct { + from pb.ExperimentStatus + event Event +} + +const ( + proposed = pb.ExperimentStatus_EXPERIMENT_STATUS_PROPOSED + running = pb.ExperimentStatus_EXPERIMENT_STATUS_RUNNING + paused = pb.ExperimentStatus_EXPERIMENT_STATUS_PAUSED + suspended = pb.ExperimentStatus_EXPERIMENT_STATUS_SUSPENDED + complete = pb.ExperimentStatus_EXPERIMENT_STATUS_COMPLETE + retired = pb.ExperimentStatus_EXPERIMENT_STATUS_RETIRED + rolledBack = pb.ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK +) + +// transitions is the whole state machine. Retired and rolled back are +// terminal: nothing reactivates them. A new experiment is proposed instead. +var transitions = map[transitionKey]pb.ExperimentStatus{ + {proposed, EventApprove}: running, + {proposed, EventReject}: retired, + {proposed, EventSuspend}: suspended, + {proposed, EventRollback}: rolledBack, + {proposed, EventRetire}: retired, + + {running, EventApprove}: running, + {running, EventApproveFinal}: complete, + {running, EventReject}: retired, + {running, EventPause}: paused, + {running, EventSuspend}: suspended, + {running, EventRollback}: rolledBack, + {running, EventRetire}: retired, + {running, EventExpire}: rolledBack, + {running, EventHarm}: rolledBack, + + {paused, EventReject}: retired, + {paused, EventSuspend}: suspended, + {paused, EventResume}: running, + {paused, EventResumeStable}: complete, + {paused, EventRollback}: rolledBack, + {paused, EventRetire}: retired, + {paused, EventExpire}: rolledBack, + + {suspended, EventReject}: retired, + {suspended, EventSuspend}: suspended, // re-suspend with a new reason is a no-op change + {suspended, EventResume}: running, + {suspended, EventResumeStable}: complete, + {suspended, EventRollback}: rolledBack, + {suspended, EventRetire}: retired, + + {complete, EventReject}: retired, + {complete, EventSuspend}: suspended, + {complete, EventRollback}: rolledBack, + {complete, EventRetire}: retired, +} + +// Transition returns the status that follows from applying event to from, or +// ErrIllegalTransition. It is pure; callers bump the revision and persist. +func Transition(from pb.ExperimentStatus, event Event) (pb.ExperimentStatus, error) { + to, ok := transitions[transitionKey{from, event}] + if !ok { + return from, fmt.Errorf("%w: %s on %s", ErrIllegalTransition, event, from) + } + return to, nil +} + +// Terminal reports whether no event can move the experiment again. +func Terminal(s pb.ExperimentStatus) bool { + return s == retired || s == rolledBack +} diff --git a/pkg/treatment/experiment/statemachine_test.go b/pkg/treatment/experiment/statemachine_test.go new file mode 100644 index 000000000..026f76c7b --- /dev/null +++ b/pkg/treatment/experiment/statemachine_test.go @@ -0,0 +1,162 @@ +package experiment + +import ( + "errors" + "testing" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" +) + +// legal is the expected state machine written out independently of the +// implementation table. A pair missing here must return ErrIllegalTransition. +var legal = map[pb.ExperimentStatus]map[Event]pb.ExperimentStatus{ + proposed: { + EventApprove: running, + EventReject: retired, + EventSuspend: suspended, + EventRollback: rolledBack, + EventRetire: retired, + }, + running: { + EventApprove: running, + EventApproveFinal: complete, + EventReject: retired, + EventPause: paused, + EventSuspend: suspended, + EventRollback: rolledBack, + EventRetire: retired, + EventExpire: rolledBack, + EventHarm: rolledBack, + }, + paused: { + EventReject: retired, + EventSuspend: suspended, + EventResume: running, + EventResumeStable: complete, + EventRollback: rolledBack, + EventRetire: retired, + EventExpire: rolledBack, + }, + suspended: { + EventReject: retired, + EventSuspend: suspended, + EventResume: running, + EventResumeStable: complete, + EventRollback: rolledBack, + EventRetire: retired, + }, + complete: { + EventReject: retired, + EventSuspend: suspended, + EventRollback: rolledBack, + EventRetire: retired, + }, + retired: {}, + rolledBack: {}, + pb.ExperimentStatus_EXPERIMENT_STATUS_UNSPECIFIED: {}, +} + +// TestTransitionExhaustive visits every (status, event) pair the generated +// enums and Events() can produce and checks it against the legal table, so +// adding a status, an event or a transition without updating both fails. +func TestTransitionExhaustive(t *testing.T) { + statuses, events := Statuses(), Events() + if len(statuses) != len(legal) { + t.Fatalf("legal table covers %d statuses, proto defines %d", len(legal), len(statuses)) + } + if len(events) != int(eventCount)-1 { + t.Fatalf("Events() returned %d, want %d", len(events), eventCount-1) + } + visited, legalCount := 0, 0 + for _, from := range statuses { + row, ok := legal[from] + if !ok { + t.Fatalf("status %s has no row in the legal table", from) + } + for _, ev := range events { + visited++ + got, err := Transition(from, ev) + want, allowed := row[ev] + switch { + case allowed: + legalCount++ + if err != nil || got != want { + t.Errorf("%s on %s: got %s err=%v; want %s", ev, from, got, err, want) + } + case !errors.Is(err, ErrIllegalTransition): + t.Errorf("%s on %s: want ErrIllegalTransition, got %s err=%v", ev, from, got, err) + case got != from: + t.Errorf("%s on %s: illegal transition must leave the status unchanged, got %s", ev, from, got) + } + } + } + if visited != len(statuses)*len(events) { + t.Fatalf("visited %d pairs, want %d", visited, len(statuses)*len(events)) + } + if legalCount != len(transitions) { + t.Fatalf("implementation has %d transitions, legal table has %d", len(transitions), legalCount) + } +} + +func TestTransitionCases(t *testing.T) { + for _, tc := range []struct { + name, description string + from pb.ExperimentStatus + event Event + expected pb.ExperimentStatus + expectError bool + }{ + {"proposed-approve", "positive: first review starts the trial", proposed, EventApprove, running, false}, + {"running-approve", "positive: phase approval keeps running while cohorts remain", running, EventApprove, running, false}, + {"running-approve-final", "positive: approving the 100% phase completes", running, EventApproveFinal, complete, false}, + {"paused-resume-stable", "positive: a paused completed treatment resumes to complete", paused, EventResumeStable, complete, false}, + {"retired-approve", "negative: terminal status cannot be approved", retired, EventApprove, retired, true}, + {"rolled-back-resume", "negative: rollback is final", rolledBack, EventResume, rolledBack, true}, + {"proposed-pause", "negative: nothing to pause before the first phase", proposed, EventPause, proposed, true}, + {"complete-expire", "negative: completed treatments have no lease to expire", complete, EventExpire, complete, true}, + {"unspecified-status", "boundary: zero status accepts no event", pb.ExperimentStatus_EXPERIMENT_STATUS_UNSPECIFIED, EventApprove, pb.ExperimentStatus_EXPERIMENT_STATUS_UNSPECIFIED, true}, + {"unspecified-event", "boundary: zero event is never legal", running, eventUnspecified, running, true}, + {"event-count-sentinel", "boundary: the sentinel just past the last event is illegal", running, eventCount, running, true}, + {"unknown-event", "corner: an event value from a newer build is rejected, not defaulted", running, Event(200), running, true}, + {"suspended-suspend", "corner: re-suspending with a new reason is a self loop", suspended, EventSuspend, suspended, false}, + {"suspended-harm", "corner: no routes are installed while suspended, so harm cannot withdraw", suspended, EventHarm, suspended, true}, + } { + t.Run(tc.name, func(t *testing.T) { + got, err := Transition(tc.from, tc.event) + if (err != nil) != tc.expectError || got != tc.expected { + t.Fatalf("%s: got %s err=%v; expected %s error=%t", tc.description, got, err, tc.expected, tc.expectError) + } + if tc.expectError && !errors.Is(err, ErrIllegalTransition) { + t.Fatalf("%s: error %v is not ErrIllegalTransition", tc.description, err) + } + }) + } +} + +func TestTerminal(t *testing.T) { + for _, s := range Statuses() { + got := Terminal(s) + expected := len(legal[s]) == 0 && s != pb.ExperimentStatus_EXPERIMENT_STATUS_UNSPECIFIED + if got != expected { + t.Errorf("Terminal(%s)=%t, expected %t", s, got, expected) + } + } +} + +func TestEventString(t *testing.T) { + for _, tc := range []struct { + description string + event Event + expected string + }{ + {"positive: first event", EventApprove, "approve"}, + {"positive: last event", EventHarm, "harm"}, + {"boundary: zero value", eventUnspecified, "unspecified"}, + {"boundary: sentinel", eventCount, "event(12)"}, + {"corner: far out of range", Event(255), "event(255)"}, + } { + if got := tc.event.String(); got != tc.expected { + t.Errorf("%s: got %q expected %q", tc.description, got, tc.expected) + } + } +} diff --git a/pkg/treatment/internal/fixtures/fixtures.go b/pkg/treatment/internal/fixtures/fixtures.go index af7edd2aa..d6752fc7b 100644 --- a/pkg/treatment/internal/fixtures/fixtures.go +++ b/pkg/treatment/internal/fixtures/fixtures.go @@ -21,7 +21,6 @@ func Desired(now time.Time) *pb.DesiredState { Revision: 1, IssuedAt: timestamppb.New(now), Assignments: []*pb.Assignment{{ - Id: "trial-1", ExperimentId: "trial-1", Generation: 1, Mode: pb.AssignmentMode_ASSIGNMENT_MODE_TRIAL, diff --git a/pkg/treatment/reconcile/journal.go b/pkg/treatment/reconcile/journal.go index c5e3cf3b0..eb712f8e9 100644 --- a/pkg/treatment/reconcile/journal.go +++ b/pkg/treatment/reconcile/journal.go @@ -59,13 +59,13 @@ func (j Journal) WithLock(fn func(*pb.LocalState, func() error) error) error { } seen := map[string]bool{} for _, r := range s.Records { - if r.Assignment == nil || !treatment.ValidID(r.Assignment.Id) { + if r.Assignment == nil || !treatment.ValidID(r.Assignment.ExperimentId) { return errors.New("invalid journal record") } - if seen[r.Assignment.Id] { + if seen[r.Assignment.ExperimentId] { return errors.New("duplicate journal record") } - seen[r.Assignment.Id] = true + seen[r.Assignment.ExperimentId] = true if err = treatment.ValidateTreatment(r.Assignment.Treatment); err != nil { return fmt.Errorf("invalid journal treatment: %w", err) } @@ -95,7 +95,7 @@ func (j Journal) WithLock(fn func(*pb.LocalState, func() error) error) error { if err != nil { return errors.Join(result, err) } - name := "assignment-" + r.Assignment.Id + ".textproto" + name := "assignment-" + r.Assignment.ExperimentId + ".textproto" old, _ := os.ReadFile(filepath.Join(j.Dir, name)) if bytes.Equal(old, b) { continue diff --git a/pkg/treatment/reconcile/journal_test.go b/pkg/treatment/reconcile/journal_test.go index 103bdac29..195c94fab 100644 --- a/pkg/treatment/reconcile/journal_test.go +++ b/pkg/treatment/reconcile/journal_test.go @@ -24,7 +24,7 @@ func TestJournalRejectsInvalidSnapshots(t *testing.T) { {"valid", "valid snapshot can be reopened", func(*pb.LocalState) {}, ""}, {"bad-desired", "invalid desired state is not recovered", func(s *pb.LocalState) { s.Desired.Revision = 0 }, "invalid persisted"}, {"missing-assignment", "records require an assignment", func(s *pb.LocalState) { s.Records[0].Assignment = nil }, "invalid journal record"}, - {"bad-id", "projection names cannot contain paths", func(s *pb.LocalState) { s.Records[0].Assignment.Id = "../escape"; s.Desired = nil }, "invalid journal record"}, + {"bad-id", "projection names cannot contain paths", func(s *pb.LocalState) { s.Records[0].Assignment.ExperimentId = "../escape"; s.Desired = nil }, "invalid journal record"}, {"duplicate", "duplicate records cannot shadow each other", func(s *pb.LocalState) { s.Records = append(s.Records, s.Records[0]) }, "duplicate"}, {"bad-treatment", "invalid treatment cannot be recovered", func(s *pb.LocalState) { s.Records[0].Assignment.Treatment = nil; s.Desired = nil }, "invalid journal treatment"}, } { diff --git a/pkg/treatment/reconcile/reconcile.go b/pkg/treatment/reconcile/reconcile.go index eb96b7d88..2d5e9203b 100644 --- a/pkg/treatment/reconcile/reconcile.go +++ b/pkg/treatment/reconcile/reconcile.go @@ -104,7 +104,7 @@ func (r *Reconciler) Run(ctx context.Context, in *pb.DesiredState, mode Mode) (* } for _, a := range in.Assignments { for _, record := range s.Records { - if record.Assignment.Id == a.Id && a.Generation < record.Assignment.Generation { + if record.Assignment.ExperimentId == a.ExperimentId && a.Generation < record.Assignment.Generation { return errors.New("stale assignment generation") } } @@ -124,7 +124,7 @@ func (r *Reconciler) Run(ctx context.Context, in *pb.DesiredState, mode Mode) (* } byID := map[string]*pb.ApplicationRecord{} for _, v := range s.Records { - byID[v.Assignment.Id] = v + byID[v.Assignment.ExperimentId] = v } active := map[string]bool{} var errs []error @@ -140,12 +140,12 @@ func (r *Reconciler) Run(ctx context.Context, in *pb.DesiredState, mode Mode) (* if err := ctx.Err(); err != nil { return errors.Join(err, save()) } - active[a.Id] = true - v := byID[a.Id] + active[a.ExperimentId] = true + v := byID[a.ExperimentId] if v == nil { v = &pb.ApplicationRecord{Assignment: proto.Clone(a).(*pb.Assignment), ReceivedAt: stamp(), Status: pb.ApplyStatus_APPLY_STATUS_PENDING} s.Records = append(s.Records, v) - byID[a.Id] = v + byID[a.ExperimentId] = v } if a.Generation < v.Assignment.Generation { return errors.New("stale assignment generation") @@ -193,7 +193,7 @@ func (r *Reconciler) Run(ctx context.Context, in *pb.DesiredState, mode Mode) (* if err != nil { v.Status = pb.ApplyStatus_APPLY_STATUS_FAILED v.Error = err.Error() - errs = append(errs, fmt.Errorf("%s: %w", a.Id, err)) + errs = append(errs, fmt.Errorf("%s: %w", a.ExperimentId, err)) } else { v.Error = "" } @@ -201,7 +201,7 @@ func (r *Reconciler) Run(ctx context.Context, in *pb.DesiredState, mode Mode) (* // Entries absent from the committed snapshot are tombstones, never // candidates for startup application. for _, v := range s.Records { - if active[v.Assignment.Id] || len(v.InstalledRoute) == 0 { + if active[v.Assignment.ExperimentId] || len(v.InstalledRoute) == 0 { continue } if v.InstalledBootId == r.BootID { diff --git a/pkg/treatment/reconcile/reconcile_test.go b/pkg/treatment/reconcile/reconcile_test.go index 9b69a6ffa..9d1c324e1 100644 --- a/pkg/treatment/reconcile/reconcile_test.go +++ b/pkg/treatment/reconcile/reconcile_test.go @@ -221,7 +221,7 @@ func TestPredecessorExpiryAndGenerationRejectionPreserveJournal(t *testing.T) { r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot-1", Now: func() time.Time { return now }} d := fixtures.Desired(now) a := d.Assignments[0] - a.Id = "state" + a.ExperimentId = "state" a.Generation = 2 a.Predecessor = proto.Clone(a.Treatment).(*pb.Treatment) a.Predecessor.Settings.CongestionControl = proto.String("bbr") diff --git a/pkg/treatment/validate.go b/pkg/treatment/validate.go index 018381a03..685fa1808 100644 --- a/pkg/treatment/validate.go +++ b/pkg/treatment/validate.go @@ -72,10 +72,10 @@ func ValidateDesired(d *pb.DesiredState) error { } ids, destinations := map[string]bool{}, map[string]bool{} for _, a := range d.Assignments { - if a == nil || !ValidID(a.Id) || !ValidID(a.ExperimentId) || a.Generation == 0 || ids[a.Id] { + if a == nil || !ValidID(a.ExperimentId) || a.Generation == 0 || ids[a.ExperimentId] { return errors.New("invalid or duplicate assignment identity") } - ids[a.Id] = true + ids[a.ExperimentId] = true if err := ValidateTreatment(a.Treatment); err != nil { return err } diff --git a/pkg/treatment/validate_test.go b/pkg/treatment/validate_test.go index 633085c75..8834cd6e0 100644 --- a/pkg/treatment/validate_test.go +++ b/pkg/treatment/validate_test.go @@ -257,12 +257,12 @@ func TestDesiredValidation(t *testing.T) { }, "identity"}, {"duplicate-destination", "active routes cannot overlap", func(p **pb.DesiredState) { a := proto.Clone((*p).Assignments[0]).(*pb.Assignment) - a.Id = "other" + a.ExperimentId = "other" (*p).Assignments = append((*p).Assignments, a) }, "overlapping"}, {"retired-overlap", "retired routes may overlap active routes", func(p **pb.DesiredState) { a := proto.Clone((*p).Assignments[0]).(*pb.Assignment) - a.Id = "other" + a.ExperimentId = "other" a.Mode = pb.AssignmentMode_ASSIGNMENT_MODE_RETIRED (*p).Assignments = append((*p).Assignments, a) }, ""}, @@ -293,7 +293,7 @@ func TestDesiredValidation(t *testing.T) { (*p).Assignments = nil for i := range n { a := proto.Clone(base).(*pb.Assignment) - a.Id = fmt.Sprintf("a-%d", i) + a.ExperimentId = fmt.Sprintf("a-%d", i) a.Treatment.Destination = fmt.Sprintf("198.18.%d.%d/32", i/256, i%256) (*p).Assignments = append((*p).Assignments, a) } diff --git a/proto/xtcp_treatment/v1/operator.proto b/proto/xtcp_treatment/v1/operator.proto index 8eb1c777c..f4e0c8253 100644 --- a/proto/xtcp_treatment/v1/operator.proto +++ b/proto/xtcp_treatment/v1/operator.proto @@ -55,7 +55,7 @@ message AnalyzeRequest { message AnalyzeResponse { Evidence evidence = 1; - string outcome = 2; + Outcome outcome = 2; } message ReviewRequest { diff --git a/proto/xtcp_treatment/v1/treatment.proto b/proto/xtcp_treatment/v1/treatment.proto index 347998737..23fefcdb4 100644 --- a/proto/xtcp_treatment/v1/treatment.proto +++ b/proto/xtcp_treatment/v1/treatment.proto @@ -64,6 +64,41 @@ enum ApplyStatus { APPLY_STATUS_FAILED = 5; } +// Outcome is the result of evaluating one evidence window against a phase, +// or the standing of a completed treatment under drift monitoring. Only +// BENEFICIAL, SAFETY_ONLY and FINAL_SAFETY can advance a phase, and only +// after an operator review; HARMFUL and EXPIRED withdraw automatically. +enum Outcome { + OUTCOME_UNSPECIFIED = 0; + // Analysis of a proposal that has no phase yet. + OUTCOME_BASELINE = 1; + // Not every treated host has verified the assignment. + OUTCOME_AWAITING_APPLICATION = 2; + // Treated hosts have not yet carried enough exposed sockets. + OUTCOME_AWAITING_EXPOSURE = 3; + OUTCOME_INSUFFICIENT_DURATION = 4; + OUTCOME_INSUFFICIENT_DATA = 5; + OUTCOME_INSUFFICIENT_CONTROLS = 6; + OUTCOME_STALE = 7; + // No evidence source was available for this maintenance pass. + OUTCOME_UNAVAILABLE = 8; + OUTCOME_INCONCLUSIVE = 9; + // Cohort too small for a causal estimate; no regression observed. + OUTCOME_SAFETY_ONLY = 10; + // Final 100% phase: no controls remain; no regression observed. + OUTCOME_FINAL_SAFETY = 11; + OUTCOME_BENEFICIAL = 12; + OUTCOME_HARMFUL = 13; + OUTCOME_EXPIRED = 14; + // Completed treatment under drift monitoring. + OUTCOME_STABLE_OBSERVED = 15; + OUTCOME_STABLE_EVIDENCE_INCOMPLETE = 16; + OUTCOME_STABLE_REGRESSION_REVIEW_REQUIRED = 17; + // Every treated host has verified the assignment; the observation window + // is open and no evidence has been evaluated yet. + OUTCOME_OBSERVING = 18; +} + enum ExperimentStatus { EXPERIMENT_STATUS_UNSPECIFIED = 0; EXPERIMENT_STATUS_PROPOSED = 1; @@ -127,7 +162,11 @@ message Assignment { message: "predecessor must cover the same destination" expression: "!has(this.predecessor) || this.predecessor.destination == this.treatment.destination" }; - string id = 1 [(buf.validate.field).string.pattern = "^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$"]; + // Field 1 was a separate assignment id that always equalled experiment_id. + // An agent holds at most one assignment per experiment, so the experiment + // is the identity; journals and telemetry key on experiment_id. + reserved 1; + reserved "id"; string experiment_id = 2 [(buf.validate.field).string.pattern = "^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$"]; Treatment treatment = 3 [(buf.validate.field).required = true]; AssignmentMode mode = 4 [(buf.validate.field).enum = {defined_only: true, not_in: [0]}]; @@ -253,7 +292,7 @@ message Phase { google.protobuf.Timestamp observation_started_at = 3; google.protobuf.Timestamp evaluated_at = 4; Evidence evidence = 5; - string outcome = 6 [(buf.validate.field).string.max_len = 64]; + Outcome outcome = 6 [(buf.validate.field).enum.defined_only = true]; string reviewer = 7 [(buf.validate.field).string.max_len = 128]; string reason = 8 [(buf.validate.field).string.max_len = 4096]; } @@ -277,5 +316,5 @@ message Experiment { uint64 revision = 10; google.protobuf.Timestamp proposed_at = 11; Evidence latest_evidence = 12; - string latest_outcome = 13 [(buf.validate.field).string.max_len = 64]; + Outcome latest_outcome = 13 [(buf.validate.field).enum.defined_only = true]; } diff --git a/tools/tcp-controller-integration/main.go b/tools/tcp-controller-integration/main.go index b5ef7777e..7ab7c30ac 100644 --- a/tools/tcp-controller-integration/main.go +++ b/tools/tcp-controller-integration/main.go @@ -383,7 +383,7 @@ func runController(soak bool) { defer cancel() analysis = must(op.Analyze(ctx, &pb.AnalyzeRequest{ExperimentId: e.Id, ExpectedRevision: e.Revision})) log.Printf("analysis: %s", analysis.Outcome) - return analysis.Outcome == "safety_only" + return analysis.Outcome == pb.Outcome_OUTCOME_SAFETY_ONLY }) eventually("approve second host with current check-ins", time.Minute, func() bool { e = review(op, inspect(op, e.Id), "approve-second", analysis.Evidence); return true }) require(e.Phases[len(e.Phases)-1].TargetHosts == 2, "wrong phase target") @@ -395,7 +395,7 @@ func runController(soak bool) { eventually("automatic rollback on measured netem harm", 5*time.Minute, func() bool { return inspect(op, e.Id).Status == pb.ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK }) eventually("all overrides withdrawn", time.Minute, func() bool { return routeCount() == 0 }) e = inspect(op, e.Id) - require(e.Phases[len(e.Phases)-1].Outcome == "harmful", "wrong rollback reason") + require(e.Phases[len(e.Phases)-1].Outcome == pb.Outcome_OUTCOME_HARMFUL, "wrong rollback reason") pass("ROLLBACK") remote(second, action{Name: "delay", Seconds: 5}, nil) for _, h := range hosts { diff --git a/tools/tcp-controller-integration/soak.go b/tools/tcp-controller-integration/soak.go index ba47b8bab..0224525b1 100644 --- a/tools/tcp-controller-integration/soak.go +++ b/tools/tcp-controller-integration/soak.go @@ -167,7 +167,7 @@ func (s *soakRun) assertMeasuredRTT() { // A low netem delay does not guarantee this aggressive profile is beneficial. // Accept an early safety withdrawal only with valid, exposed, harmful evidence. func validateSoakRollback(e *pb.Experiment, injectedLatency bool) error { - if e == nil || e.Status != pb.ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK || len(e.Phases) != 1 || e.Phases[0].Outcome != "harmful" || len(e.CohortOrder) == 0 || e.Policy == nil || e.Baseline == nil || e.LatestEvidence == nil { + if e == nil || e.Status != pb.ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK || len(e.Phases) != 1 || e.Phases[0].Outcome != pb.Outcome_OUTCOME_HARMFUL || len(e.CohortOrder) == 0 || e.Policy == nil || e.Baseline == nil || e.LatestEvidence == nil { return fmt.Errorf("rollback is missing its harmful phase or evidence") } if err := treatment.ValidateEvidence(e.Baseline); err != nil { diff --git a/tools/tcp-controller-integration/soak_test.go b/tools/tcp-controller-integration/soak_test.go index 7808af3dd..6dc9b73c3 100644 --- a/tools/tcp-controller-integration/soak_test.go +++ b/tools/tcp-controller-integration/soak_test.go @@ -22,7 +22,7 @@ func TestSoakRollbackEvidence(t *testing.T) { latest.Hosts[0].ExposedSockets = 32 policy := treatment.DefaultPolicy() policy.MaximumRttIncrease = 1 - fixture := &pb.Experiment{Status: pb.ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK, CohortOrder: []string{"canary"}, Policy: policy, Baseline: baseline, LatestEvidence: latest, Phases: []*pb.Phase{{Outcome: "harmful"}}} + fixture := &pb.Experiment{Status: pb.ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK, CohortOrder: []string{"canary"}, Policy: policy, Baseline: baseline, LatestEvidence: latest, Phases: []*pb.Phase{{Outcome: pb.Outcome_OUTCOME_HARMFUL}}} for _, tc := range []struct { name string injected, valid bool @@ -37,7 +37,7 @@ func TestSoakRollbackEvidence(t *testing.T) { e.LatestEvidence.Hosts[0].ExposedSockets = 0 }}, {"missing-evidence", false, false, func(e *pb.Experiment) { e.LatestEvidence = nil }}, - {"wrong-outcome", false, false, func(e *pb.Experiment) { e.Phases[0].Outcome = "expired" }}, + {"wrong-outcome", false, false, func(e *pb.Experiment) { e.Phases[0].Outcome = pb.Outcome_OUTCOME_EXPIRED }}, } { t.Run(tc.name, func(t *testing.T) { e := proto.Clone(fixture).(*pb.Experiment) From eae1d87d800c8c21ee0a7e1203e89ea27d61c8b8 Mon Sep 17 00:00:00 2001 From: "randomizedcoder dave.seddon.ca@gmail.com" Date: Sun, 25 Oct 2026 18:55:51 -0700 Subject: [PATCH 06/27] refactor(treatment): repository ports, relational schema and pgx Item 4 of the architecture review (B3, B4): the proto is now wire-only and PostgreSQL is relational. Domain and wire - pkg/treatment holds proto-free Go domain types (Treatment, Experiment, Evidence, Assignment, DesiredState, ApplicationRecord, ...). - pkg/treatment/wire converts domain <-> xtcp_treatment.v1 at the gRPC boundary; round-trip tests cover populated, unset-optional and enum coverage cases. Store ports - pkg/treatment/controller/store defines Agents, Experiments, Evidence, Holds, Audit and Metrics repositories, Repos, and Store{Repos, Tx, Ping, Close} with ErrNotFound/ErrNotEnrolled/ErrExists/ ErrEvidenceConflict/ErrRequestConflict sentinels. - store/postgres: hand-written SQL only, pgx v5 through database/sql, relational schema (agents, agent_algorithms, experiments, experiment_cohort, phases, assignments, application_records, evidence, evidence_hosts, holds, events). Tx takes the advisory lock. Evidence is stored once and referenced by ID; the digest hashes timestamps at microsecond precision so it agrees with timestamptz storage. - store/memory: in-memory implementation with snapshot rollback so handlers test without a database. - store/storetest: conformance suite run by both implementations, with table-driven rows across positive/negative/boundary/corner cases (desired revision fence, cohort immutability, max cohort and evidence_hosts 10000, cidr /32 and /128, advisory-lock serialisation, request-ID replay with same and different bodies, nanosecond clocks). - store/pgtest: per-test schema fixture keyed off XTCP_CONTROLLER_TEST_DSN, dropped on cleanup. Controller - server.go, status.go, maintenance.go, discovery.go no longer contain SQL; all mutations go through Store.Tx. - Experiment.UpdatedAt is stamped by the controller clock on save, so discovery cooldown follows the injected clock. - Handler tests run as /memory and /postgres subtests; TestPostgresConcurrentReviewFencesReplicas stays Postgres-only for the Nix harness grep. The index EXPLAIN test moved to store/postgres. - cmd/xtcp2-controller opens the store through postgres.Open and Migrate. Boundaries - lib/pq removed from go.mod; github.com/jackc/pgx/v5 added. - boundaries_test tightens rules: domain and experiment packages are proto-free; the daemon, memory store and identity forbid database/sql, pgx and ClickHouse; SQL lives only in store/postgres. Docs - docs/tcp-controller.md package table, schema/index names and test instructions updated. - docs/status-tcp-controller.md added with progress and TODOs (journal still pb prototext, Experiments.List N+1, Observe rewrites records per heartbeat, benchmark refresh). - nix/versions.nix goVendorHash updated for the pgx dependency. Co-Authored-By: Claude Fable 5.1 --- cmd/xtcp2-controller/main.go | 9 +- docs/status-tcp-controller.md | 55 ++ docs/tcp-controller.md | 73 +- gen/cpp/xtcp_treatment/v1/treatment.pb.cc | 188 +++-- gen/cpp/xtcp_treatment/v1/treatment.pb.h | 126 ++- gen/dart/xtcp_treatment/v1/treatment.pb.dart | 17 + .../xtcp_treatment/v1/treatment.pbjson.dart | 11 +- gen/go/xtcp_treatment/treatment.pb.go | 35 +- gen/go/xtcp_treatment/treatment_vtproto.pb.go | 50 ++ gen/python/xtcp_treatment/v1/treatment_pb2.py | 24 +- .../xtcp_treatment/v1/treatment_pb2.pyi | 6 +- go.mod | 6 +- go.sum | 14 +- nix/versions.nix | 2 +- pkg/treatment/agent/backend_linux.go | 4 +- pkg/treatment/boundaries_test.go | 61 +- pkg/treatment/controller/analyze.go | 40 +- pkg/treatment/controller/clickhouse.go | 49 +- pkg/treatment/controller/clickhouse_test.go | 6 +- pkg/treatment/controller/contracts_test.go | 19 +- pkg/treatment/controller/discovery.go | 27 +- .../controller/discovery_contracts_test.go | 230 ++++-- .../controller/discovery_store_test.go | 55 -- pkg/treatment/controller/discovery_test.go | 41 +- pkg/treatment/controller/helpers_test.go | 180 ++++ pkg/treatment/controller/maintenance.go | 144 ++-- pkg/treatment/controller/notifications.go | 12 +- .../controller/postgres_indexes_test.go | 210 ----- .../controller/rpc_contracts_test.go | 229 +++--- pkg/treatment/controller/schema.sql | 45 - pkg/treatment/controller/server.go | 535 ++++++------ pkg/treatment/controller/server_test.go | 479 +++++------ pkg/treatment/controller/status.go | 38 +- pkg/treatment/controller/store.go | 175 ---- .../controller/store/memory/memory.go | 459 +++++++++++ .../controller/store/memory/memory_test.go | 13 + .../controller/store/pgtest/pgtest.go | 78 ++ .../controller/store/postgres/agents.go | 210 +++++ .../controller/store/postgres/columns.go | 155 ++++ .../controller/store/postgres/evidence.go | 98 +++ .../controller/store/postgres/experiments.go | 300 +++++++ .../controller/store/postgres/export_test.go | 7 + .../controller/store/postgres/holds.go | 87 ++ .../controller/store/postgres/postgres.go | 242 ++++++ .../store/postgres/postgres_test.go | 131 +++ .../controller/store/postgres/schema.sql | 207 +++++ pkg/treatment/controller/store/store.go | 168 ++++ .../controller/store/storetest/storetest.go | 767 ++++++++++++++++++ pkg/treatment/enums.go | 220 +++++ pkg/treatment/experiment/benchmark_test.go | 10 +- pkg/treatment/experiment/boundaries_test.go | 149 ++-- pkg/treatment/experiment/experiment.go | 103 ++- pkg/treatment/experiment/experiment_test.go | 41 +- pkg/treatment/experiment/outcome.go | 106 --- pkg/treatment/experiment/outcome_test.go | 90 -- pkg/treatment/experiment/statemachine.go | 36 +- pkg/treatment/experiment/statemachine_test.go | 30 +- pkg/treatment/internal/fixtures/fixtures.go | 72 +- pkg/treatment/model.go | 265 ++++++ pkg/treatment/reconcile/contracts_test.go | 6 +- pkg/treatment/reconcile/journal.go | 5 +- pkg/treatment/reconcile/journal_test.go | 2 +- pkg/treatment/reconcile/reconcile.go | 4 +- pkg/treatment/reconcile/reconcile_test.go | 14 +- pkg/treatment/validate.go | 122 +-- pkg/treatment/validate_test.go | 290 ++++--- .../rules_test.go} | 7 +- pkg/treatment/wire/wire.go | 468 +++++++++++ pkg/treatment/wire/wire_test.go | 337 ++++++++ proto/xtcp_treatment/v1/treatment.proto | 3 + tools/tcp-controller-integration/main.go | 4 +- tools/tcp-controller-integration/soak.go | 13 +- tools/tcp-controller-integration/soak_test.go | 3 +- 73 files changed, 6329 insertions(+), 2188 deletions(-) create mode 100644 docs/status-tcp-controller.md delete mode 100644 pkg/treatment/controller/discovery_store_test.go create mode 100644 pkg/treatment/controller/helpers_test.go delete mode 100644 pkg/treatment/controller/postgres_indexes_test.go delete mode 100644 pkg/treatment/controller/schema.sql delete mode 100644 pkg/treatment/controller/store.go create mode 100644 pkg/treatment/controller/store/memory/memory.go create mode 100644 pkg/treatment/controller/store/memory/memory_test.go create mode 100644 pkg/treatment/controller/store/pgtest/pgtest.go create mode 100644 pkg/treatment/controller/store/postgres/agents.go create mode 100644 pkg/treatment/controller/store/postgres/columns.go create mode 100644 pkg/treatment/controller/store/postgres/evidence.go create mode 100644 pkg/treatment/controller/store/postgres/experiments.go create mode 100644 pkg/treatment/controller/store/postgres/export_test.go create mode 100644 pkg/treatment/controller/store/postgres/holds.go create mode 100644 pkg/treatment/controller/store/postgres/postgres.go create mode 100644 pkg/treatment/controller/store/postgres/postgres_test.go create mode 100644 pkg/treatment/controller/store/postgres/schema.sql create mode 100644 pkg/treatment/controller/store/store.go create mode 100644 pkg/treatment/controller/store/storetest/storetest.go create mode 100644 pkg/treatment/enums.go delete mode 100644 pkg/treatment/experiment/outcome.go delete mode 100644 pkg/treatment/experiment/outcome_test.go create mode 100644 pkg/treatment/model.go rename pkg/treatment/{proto_rules_test.go => wire/rules_test.go} (98%) create mode 100644 pkg/treatment/wire/wire.go create mode 100644 pkg/treatment/wire/wire_test.go diff --git a/cmd/xtcp2-controller/main.go b/cmd/xtcp2-controller/main.go index 7403df5e0..08651b290 100644 --- a/cmd/xtcp2-controller/main.go +++ b/cmd/xtcp2-controller/main.go @@ -19,6 +19,7 @@ import ( pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/controller" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store/postgres" "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -64,17 +65,17 @@ func run() error { if e != nil { return e } - store, e := controller.Open(ctx, dsn) + db, e := postgres.Open(ctx, dsn) if e != nil { return e } - defer store.DB.Close() + defer db.Close() if *migrate { - if e = store.Migrate(ctx); e != nil { + if e = db.Migrate(ctx); e != nil { return e } } - s := &controller.Server{Store: store, Hub: controller.NewNotificationHub(), DiscoveryWindow: *discoveryWindow} + s := &controller.Server{Store: db, Hub: controller.NewNotificationHub(), DiscoveryWindow: *discoveryWindow} var analysis *controller.ClickHouse var source controller.EvidenceSource if *clickhouse != "" { diff --git a/docs/status-tcp-controller.md b/docs/status-tcp-controller.md new file mode 100644 index 000000000..015d9172b --- /dev/null +++ b/docs/status-tcp-controller.md @@ -0,0 +1,55 @@ +# TCP route treatment controller: rework status + +Tracking document for the architecture-review rework of the treatment control +plane (PR #282, branch `feat/tcp-route-controller`). The plan is the review +itself: 12 work items applied as separate commits so the PR history reads as the +review being addressed. Items are listed in execution order. + +| # | Item | State | Commit | +| --- | --- | --- | --- | +| 1 | `pkg/treatment/identity` extracted; import-boundary test | done | 7dc118ca | +| 2 | `pkg/treatment` split by audience (`reconcile`, `experiment`, `agent`) | done | 2ca932f8 | +| 8 | proto moved to `xtcp_treatment/v1` with `buf.validate` rules | done | 460164b1 | +| 3 | explicit state machine; `Outcome` enum | done | 85b2cf59 | +| 4 | repository ports, relational schema, pgx, domain types, `wire` | done | this commit | +| 5 | evidence port (`CandidateSource`), ClickHouse as one adapter | next | | +| 6 | auth as interceptor with per-RPC role option | pending | | +| 7 | typed `xtcpnl.RuleWrite`; backend to `internal/goip/actuate` | pending | | +| 9 | identity/location startup check | pending | | +| 10 | blocking journal lock | pending | | +| 11 | scale: notification watermark, heartbeat split, leader lease | pending | | +| 12 | docs: threat model, naming, SIGHUP, philosophy sentence | pending | | + +Dual-mode agent (library + `cmd/xtcp2-treatment-agent` + `treatment` build tag +in `cmd/xtcp2`, with an `attribution` journal reader for the untagged daemon) +is decided and lands after item 4; the watchdog stays a separate process. + +## Item 4 notes + +- Domain types live in `pkg/treatment/model.go` and `enums.go`; the generated + proto is wire-only and converted in `pkg/treatment/wire`. +- `Experiment.UpdatedAt` is a domain field (proto `updated_at = 14`) stamped by + the controller clock, so the discovery cooldown is testable on both stores. +- Evidence is stored once (`xtcp_treatment_evidence` + `_hosts`) with a content + digest; re-using an evidence ID for different content or another experiment + is `store.ErrEvidenceConflict`. The digest hashes timestamps at microsecond + precision because that is what `timestamptz` keeps. +- Every handler test runs against the in-memory store and, with + `XTCP_CONTROLLER_TEST_DSN`, against PostgreSQL; both stores pass the shared + conformance suite in `store/storetest`. + +## TODO + +- Journal is still prototext of the generated `LocalState`; it becomes a + journal-specific message converted from domain types with the dual-mode agent + work. Until then `pkg/treatment/reconcile` keeps importing the proto package + (and through it gRPC); the boundary test carries the TODO. +- `Experiments.List` loads each experiment with its own round trips + (cohort, phases, evidence). Fine for `inspect` limits; batch before any + fleet-wide listing path. +- `Agents.Observe` rewrites every record row per heartbeat. Item 11 splits the + heartbeat update from record writes. +- `docs/benchmarks/tcp-controller/postgres-indexes.txt` still shows the JSONB + expression-index timings; refresh from `TestPostgresExperimentQueryIndexes` + on the Nix fixture. +- Coverage table in `docs/tcp-controller-testing.md` predates the split; regenerate. diff --git a/docs/tcp-controller.md b/docs/tcp-controller.md index 514935c4c..08c592b86 100644 --- a/docs/tcp-controller.md +++ b/docs/tcp-controller.md @@ -28,16 +28,28 @@ a typed [protobuf contract](../proto/xtcp_treatment/v1/) (`xtcp_treatment.v1`: ` | Package | Used by | Contents | | --- | --- | --- | -| `pkg/treatment` | everyone | identifiers, limits and validators: the shared contract | +| `pkg/treatment` | everyone | the domain model (`Treatment`, `Experiment`, `Evidence`, `DesiredState`, enums), limits and validators; proto-free | +| `pkg/treatment/wire` | gRPC boundary | domain ↔ generated proto conversion; `Parse*` fails closed on unknown enums and invalid timestamps | | `pkg/treatment/identity` | agent, controller, tools | SPIFFE URI identity, mTLS configuration, gRPC peer resolution | | `pkg/treatment/reconcile` | agent, watchdog | intent journal, reconciler, `Backend` interface, `Mode` (full or expiry-only) | -| `pkg/treatment/experiment` | controller | evaluation statistics, cohort allocation, phase advancement | +| `pkg/treatment/experiment` | controller | evaluation statistics, cohort allocation, phase advancement, state machine; pure functions over domain types | | `pkg/treatment/agent` | `xtcp2` | check-in loop, watch stream, capabilities, telemetry stamping | -| `pkg/treatment/controller` | `xtcp2-controller` | gRPC services, PostgreSQL store, ClickHouse evidence, maintenance, discovery | +| `pkg/treatment/controller` | `xtcp2-controller` | gRPC handlers, maintenance and discovery over the store ports; ClickHouse evidence | +| `pkg/treatment/controller/store` | controller | repository interfaces (`Agents`, `Experiments`, `Evidence`, `Holds`, `Audit`, `Metrics`) and `Store.Tx` | +| `pkg/treatment/controller/store/postgres` | `xtcp2-controller` | the only package with SQL: relational schema, pgx through `database/sql`, advisory-lock transactions | +| `pkg/treatment/controller/store/memory` | handler tests | map-backed store that passes the same conformance suite (`store/storetest`) | + +The generated protobuf types are wire-only. The controller works on the Go +domain types and converts at the gRPC boundary; PostgreSQL holds a relational +schema (one column per field, `cidr` destinations, evidence stored once and +referenced by ID), so a proto field rename can never break SQL or an index. The +only JSON column is the append-only audit payload. `pkg/treatment/boundaries_test.go` enforces the layering: `xtcp2` and the agent -never link the controller, a SQL driver or the ClickHouse client; the controller -never links the host-side reconcile package or goip. The binaries are: +never link the controller, a SQL driver or the ClickHouse client; the domain +and experiment packages never import the proto or gRPC; SQL lives only in the +postgres store; the controller never links the host-side reconcile package or +goip. The binaries are: | Binary | Responsibility | | --- | --- | @@ -242,26 +254,29 @@ destination check under the existing advisory lock, and repeats the DC budget check, so concurrent controllers or changes during ClickHouse analysis cannot insert competing automatic proposals. A database lookup failure stops discovery. -Two B-tree expression indexes support these paths: +Two B-tree indexes on plain columns support these paths: | Index | Keys | Purpose | | --- | --- | --- | -| `xtcp_control_experiments_destination` | location, treatment destination, updated_at descending | Existing-treatment and cooldown lookup without scanning unrelated history. | -| `xtcp_control_experiments_status_location` | experiment status, location | DC budget lookup and status-based maintenance selection. | - -The budget query uses `EXISTS` rather than counting every match. The large JSONB -state/evidence document is not indexed wholesale. Agent primary-key and location -indexes already cover their current lookups; no extra heartbeat indexes were -added. An all-agent notification scan still needs to read all agents, so an -additional index alone would not eliminate that work. - -`TestPostgresExperimentQueryIndexes` loads 100,000 retired experiments and five -active experiments, runs `ANALYZE`, and compares `EXPLAIN (ANALYZE, BUFFERS)` with -and without these indexes. It checks that PostgreSQL chooses an appropriate -index without disabling sequential scans; execution times are logged, not used -as brittle pass/fail thresholds. Eligibility tables cover all lifecycle states, -cooldown boundaries and location/destination isolation. Concurrent proposal tests -verify that only one automatic proposal is persisted. +| `xtcp_treatment_experiments_destination` | location, treatment destination (`cidr`), updated_at | Existing-treatment and cooldown lookup without scanning unrelated history. | +| `xtcp_treatment_experiments_location_status` | location, experiment status | DC budget lookup and status-based maintenance selection. | + +The budget query uses `EXISTS` rather than counting every match. Evidence host +rows live in `xtcp_treatment_evidence_hosts`, keyed by evidence ID, so an +experiment row stays small no matter how many hosts were measured. Agent +primary-key and location indexes already cover their current lookups; no extra +heartbeat indexes were added. An all-agent notification scan still needs to +read all agents, so an additional index alone would not eliminate that work. + +`TestPostgresExperimentQueryIndexes` (in `store/postgres`) loads 100,000 retired +experiments and five active experiments, runs `ANALYZE`, and compares +`EXPLAIN (ANALYZE, BUFFERS)` with and without these indexes. It checks that +PostgreSQL chooses an appropriate index without disabling sequential scans; +execution times are logged, not used as brittle pass/fail thresholds. The store +conformance suite (`store/storetest`, run against both PostgreSQL and the +in-memory store) covers all lifecycle states, cooldown boundaries and +location/destination isolation. Concurrent proposal tests verify that only one +automatic proposal is persisted. On the local PostgreSQL 15.19 fixture, three repeated runs measured a median 25.34 ms without indexes versus 0.023 ms with them for an old destination's @@ -274,11 +289,10 @@ database, prebuild these indexes before restarting controllers: ```sql -- Run outside a transaction, using the controller database/schema. -CREATE INDEX CONCURRENTLY IF NOT EXISTS xtcp_control_experiments_destination - ON xtcp_control_experiments - (location, (state->'treatment'->>'destination'), updated_at DESC); -CREATE INDEX CONCURRENTLY IF NOT EXISTS xtcp_control_experiments_status_location - ON xtcp_control_experiments ((state->>'status'), location); +CREATE INDEX CONCURRENTLY IF NOT EXISTS xtcp_treatment_experiments_destination + ON xtcp_treatment_experiments (location, treatment_destination, updated_at); +CREATE INDEX CONCURRENTLY IF NOT EXISTS xtcp_treatment_experiments_location_status + ON xtcp_treatment_experiments (location, status); ``` Normal index creation blocks writes while building; PostgreSQL's @@ -474,12 +488,15 @@ and IPv4/IPv6 netlink mutation in a disposable namespace: ```sh go test ./pkg/treatment/... ./internal/goip/... XTCP_CONTROLLER_TEST_DSN='host=... dbname=... user=... sslmode=...' \ - go test ./pkg/treatment/controller -run Postgres + go test ./pkg/treatment/controller/... XTCP_CLICKHOUSE_TEST_BINARY=/path/to/clickhouse \ go test ./pkg/treatment/controller -run ClickHouse XTCP_TREATMENT_LIVE_TEST=1 go test ./internal/goip -run '^TestTreatmentLiveNamespace$' ``` +Every controller handler test runs twice, as `/memory` and +`/postgres`; without the DSN the PostgreSQL half skips. The store +conformance suite runs the same way from `store/memory` and `store/postgres`. The PostgreSQL tests create and drop only unique test schemas. The route test creates its own user/network namespace and verifies that it differs from the parent namespace before any write. It requires `ip`, `unshare` and a kernel that diff --git a/gen/cpp/xtcp_treatment/v1/treatment.pb.cc b/gen/cpp/xtcp_treatment/v1/treatment.pb.cc index a0f22ac86..75647b733 100644 --- a/gen/cpp/xtcp_treatment/v1/treatment.pb.cc +++ b/gen/cpp/xtcp_treatment/v1/treatment.pb.cc @@ -1684,12 +1684,12 @@ constexpr Experiment::ParseTableT_ Experiment::InternalGenerateParseTable_(const { PROTOBUF_FIELD_OFFSET(Experiment, _impl_._has_bits_), 0, // no _extensions_ - 13, 120, // max_field_number, fast_idx_mask + 14, 120, // max_field_number, fast_idx_mask offsetof(ParseTableT_, field_lookup_table), - 4294959104, // skipmap + 4294950912, // skipmap offsetof(ParseTableT_, field_entries), - 13, // num_field_entries - 6, // num_aux_entries + 14, // num_field_entries + 7, // num_aux_entries offsetof(ParseTableT_, aux_entries), class_data, nullptr, // post_loop_handler @@ -1732,12 +1732,12 @@ constexpr Experiment::ParseTableT_ Experiment::InternalGenerateParseTable_(const {66, 1, 3, PROTOBUF_FIELD_OFFSET(Experiment, _impl_.phases_)}}, // .xtcp_treatment.v1.ExperimentStatus status = 9 [json_name = "status", (.buf.validate.field) = { - {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(Experiment, _impl_.status_), 11>(), - {72, 11, 0, + {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(Experiment, _impl_.status_), 12>(), + {72, 12, 0, PROTOBUF_FIELD_OFFSET(Experiment, _impl_.status_)}}, // uint64 revision = 10 [json_name = "revision"]; - {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(Experiment, _impl_.revision_), 10>(), - {80, 10, 0, + {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(Experiment, _impl_.revision_), 11>(), + {80, 11, 0, PROTOBUF_FIELD_OFFSET(Experiment, _impl_.revision_)}}, // .google.protobuf.Timestamp proposed_at = 11 [json_name = "proposedAt"]; {::_pbi::TcParser::FastMtS1, @@ -1748,10 +1748,13 @@ constexpr Experiment::ParseTableT_ Experiment::InternalGenerateParseTable_(const {98, 9, 5, PROTOBUF_FIELD_OFFSET(Experiment, _impl_.latest_evidence_)}}, // .xtcp_treatment.v1.Outcome latest_outcome = 13 [json_name = "latestOutcome", (.buf.validate.field) = { - {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(Experiment, _impl_.latest_outcome_), 12>(), - {104, 12, 0, + {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(Experiment, _impl_.latest_outcome_), 13>(), + {104, 13, 0, PROTOBUF_FIELD_OFFSET(Experiment, _impl_.latest_outcome_)}}, - {::_pbi::TcParser::MiniParse, {}}, + // .google.protobuf.Timestamp updated_at = 14 [json_name = "updatedAt"]; + {::_pbi::TcParser::FastMtS1, + {114, 10, 6, + PROTOBUF_FIELD_OFFSET(Experiment, _impl_.updated_at_)}}, {::_pbi::TcParser::MiniParse, {}}, }}, {{ 65535, 65535 @@ -1773,15 +1776,17 @@ constexpr Experiment::ParseTableT_ Experiment::InternalGenerateParseTable_(const // repeated .xtcp_treatment.v1.Phase phases = 8 [json_name = "phases", (.buf.validate.field) = { {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.phases_), _Internal::kHasBitsOffset + 1, 3, (0 | ::_fl::kFcRepeated | ::_fl::kMessage | ::_fl::kTvTable)}, // .xtcp_treatment.v1.ExperimentStatus status = 9 [json_name = "status", (.buf.validate.field) = { - {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.status_), _Internal::kHasBitsOffset + 11, 0, (0 | ::_fl::kFcOptional | ::_fl::kOpenEnum)}, + {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.status_), _Internal::kHasBitsOffset + 12, 0, (0 | ::_fl::kFcOptional | ::_fl::kOpenEnum)}, // uint64 revision = 10 [json_name = "revision"]; - {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.revision_), _Internal::kHasBitsOffset + 10, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt64)}, + {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.revision_), _Internal::kHasBitsOffset + 11, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt64)}, // .google.protobuf.Timestamp proposed_at = 11 [json_name = "proposedAt"]; {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.proposed_at_), _Internal::kHasBitsOffset + 8, 4, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, // .xtcp_treatment.v1.Evidence latest_evidence = 12 [json_name = "latestEvidence"]; {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.latest_evidence_), _Internal::kHasBitsOffset + 9, 5, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, // .xtcp_treatment.v1.Outcome latest_outcome = 13 [json_name = "latestOutcome", (.buf.validate.field) = { - {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.latest_outcome_), _Internal::kHasBitsOffset + 12, 0, (0 | ::_fl::kFcOptional | ::_fl::kOpenEnum)}, + {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.latest_outcome_), _Internal::kHasBitsOffset + 13, 0, (0 | ::_fl::kFcOptional | ::_fl::kOpenEnum)}, + // .google.protobuf.Timestamp updated_at = 14 [json_name = "updatedAt"]; + {PROTOBUF_FIELD_OFFSET(Experiment, _impl_.updated_at_), _Internal::kHasBitsOffset + 10, 6, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, }}, {{ #ifndef PROTOBUF_MESSAGE_GLOBALS @@ -1814,6 +1819,11 @@ constexpr Experiment::ParseTableT_ Experiment::InternalGenerateParseTable_(const #else {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment::v1::Evidence_globals_}, #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::google::protobuf::Timestamp>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::google::protobuf::Timestamp_globals_}, + #endif }}, {{ "\34\2\10\12\0\0\0\14\0\0\0\0\0\0\0\0" @@ -1854,6 +1864,7 @@ inline constexpr Experiment::Impl_::Impl_( baseline_{nullptr}, proposed_at_{nullptr}, latest_evidence_{nullptr}, + updated_at_{nullptr}, revision_{::uint64_t{0u}}, status_{static_cast< ::xtcp_treatment::v1::ExperimentStatus >(0)}, latest_outcome_{static_cast< ::xtcp_treatment::v1::Outcome >(0)} {} @@ -2799,7 +2810,7 @@ const ::uint32_t 1, 0x081, // bitmap PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Experiment, _impl_._has_bits_), - 16, // hasbit index offset + 17, // hasbit index offset PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Experiment, _impl_.id_), PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Experiment, _impl_.location_), PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Experiment, _impl_.hypothesis_), @@ -2813,6 +2824,7 @@ const ::uint32_t PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Experiment, _impl_.proposed_at_), PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Experiment, _impl_.latest_evidence_), PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Experiment, _impl_.latest_outcome_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Experiment, _impl_.updated_at_), 2, 3, 4, @@ -2821,11 +2833,12 @@ const ::uint32_t 7, 0, 1, + 12, 11, - 10, 8, 9, - 12, + 13, + 10, }; static const ::_pbi::MigrationSchema @@ -3020,7 +3033,7 @@ const char descriptor_table_protodef_xtcp_5ftreatment_2fv1_2ftreatment_2eproto[] "R\010evidence\022>\n\007outcome\030\006 \001(\0162\032.xtcp_treat" "ment.v1.OutcomeB\010\272H\005\202\001\002\020\001R\007outcome\022$\n\010re" "viewer\030\007 \001(\tB\010\272H\005r\003\030\200\001R\010reviewer\022 \n\006reas" - "on\030\010 \001(\tB\010\272H\005r\003\030\200 R\006reason\"\315\006\n\nExperimen" + "on\030\010 \001(\tB\010\272H\005r\003\030\200 R\006reason\"\210\007\n\nExperimen" "t\0229\n\002id\030\001 \001(\tB)\272H&r$2\"^[a-zA-Z0-9][a-zA-" "Z0-9_.-]{0,127}$R\002id\022E\n\010location\030\002 \001(\tB)" "\272H&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}" @@ -3041,45 +3054,47 @@ const char descriptor_table_protodef_xtcp_5ftreatment_2fv1_2ftreatment_2eproto[] "posedAt\022D\n\017latest_evidence\030\014 \001(\0132\033.xtcp_" "treatment.v1.EvidenceR\016latestEvidence\022K\n" "\016latest_outcome\030\r \001(\0162\032.xtcp_treatment.v" - "1.OutcomeB\010\272H\005\202\001\002\020\001R\rlatestOutcome*\?\n\004Ro" - "le\022\024\n\020ROLE_UNSPECIFIED\020\000\022\016\n\nROLE_AGENT\020\001" - "\022\021\n\rROLE_OPERATOR\020\002*\244\001\n\016AssignmentMode\022\037" - "\n\033ASSIGNMENT_MODE_UNSPECIFIED\020\000\022\031\n\025ASSIG" - "NMENT_MODE_TRIAL\020\001\022\032\n\026ASSIGNMENT_MODE_ST" - "ABLE\020\002\022\035\n\031ASSIGNMENT_MODE_SUSPENDED\020\003\022\033\n" - "\027ASSIGNMENT_MODE_RETIRED\020\004*\255\001\n\013ApplyStat" - "us\022\034\n\030APPLY_STATUS_UNSPECIFIED\020\000\022\030\n\024APPL" - "Y_STATUS_PENDING\020\001\022\031\n\025APPLY_STATUS_VERIF" - "IED\020\002\022\030\n\024APPLY_STATUS_REMOVED\020\003\022\030\n\024APPLY" - "_STATUS_EXPIRED\020\004\022\027\n\023APPLY_STATUS_FAILED" - "\020\005*\244\004\n\007Outcome\022\027\n\023OUTCOME_UNSPECIFIED\020\000\022" - "\024\n\020OUTCOME_BASELINE\020\001\022 \n\034OUTCOME_AWAITIN" - "G_APPLICATION\020\002\022\035\n\031OUTCOME_AWAITING_EXPO" - "SURE\020\003\022!\n\035OUTCOME_INSUFFICIENT_DURATION\020" - "\004\022\035\n\031OUTCOME_INSUFFICIENT_DATA\020\005\022!\n\035OUTC" - "OME_INSUFFICIENT_CONTROLS\020\006\022\021\n\rOUTCOME_S" - "TALE\020\007\022\027\n\023OUTCOME_UNAVAILABLE\020\010\022\030\n\024OUTCO" - "ME_INCONCLUSIVE\020\t\022\027\n\023OUTCOME_SAFETY_ONLY" - "\020\n\022\030\n\024OUTCOME_FINAL_SAFETY\020\013\022\026\n\022OUTCOME_" - "BENEFICIAL\020\014\022\023\n\017OUTCOME_HARMFUL\020\r\022\023\n\017OUT" - "COME_EXPIRED\020\016\022\033\n\027OUTCOME_STABLE_OBSERVE" - "D\020\017\022&\n\"OUTCOME_STABLE_EVIDENCE_INCOMPLET" - "E\020\020\022-\n)OUTCOME_STABLE_REGRESSION_REVIEW_" - "REQUIRED\020\021\022\025\n\021OUTCOME_OBSERVING\020\022*\225\002\n\020Ex" - "perimentStatus\022!\n\035EXPERIMENT_STATUS_UNSP" - "ECIFIED\020\000\022\036\n\032EXPERIMENT_STATUS_PROPOSED\020" - "\001\022\035\n\031EXPERIMENT_STATUS_RUNNING\020\002\022\034\n\030EXPE" - "RIMENT_STATUS_PAUSED\020\003\022\037\n\033EXPERIMENT_STA" - "TUS_SUSPENDED\020\004\022\036\n\032EXPERIMENT_STATUS_COM" - "PLETE\020\005\022\035\n\031EXPERIMENT_STATUS_RETIRED\020\006\022!" - "\n\035EXPERIMENT_STATUS_ROLLED_BACK\020\007:M\n\004rol" - "e\022\036.google.protobuf.MethodOptions\030\272\216\003 \001(" - "\0162\027.xtcp_treatment.v1.RoleR\004roleB\300\001\n\025com" - ".xtcp_treatment.v1B\016TreatmentProtoP\001Z6gi" - "thub.com/randomizedcoder/xtcp2/gen/go/xt" - "cp_treatment\242\002\003XXX\252\002\020XtcpTreatment.V1\312\002\020" - "XtcpTreatment\\V1\342\002\034XtcpTreatment\\V1\\GPBM" - "etadata\352\002\021XtcpTreatment::V1b\006proto3" + "1.OutcomeB\010\272H\005\202\001\002\020\001R\rlatestOutcome\0229\n\nup" + "dated_at\030\016 \001(\0132\032.google.protobuf.Timesta" + "mpR\tupdatedAt*\?\n\004Role\022\024\n\020ROLE_UNSPECIFIE" + "D\020\000\022\016\n\nROLE_AGENT\020\001\022\021\n\rROLE_OPERATOR\020\002*\244" + "\001\n\016AssignmentMode\022\037\n\033ASSIGNMENT_MODE_UNS" + "PECIFIED\020\000\022\031\n\025ASSIGNMENT_MODE_TRIAL\020\001\022\032\n" + "\026ASSIGNMENT_MODE_STABLE\020\002\022\035\n\031ASSIGNMENT_" + "MODE_SUSPENDED\020\003\022\033\n\027ASSIGNMENT_MODE_RETI" + "RED\020\004*\255\001\n\013ApplyStatus\022\034\n\030APPLY_STATUS_UN" + "SPECIFIED\020\000\022\030\n\024APPLY_STATUS_PENDING\020\001\022\031\n" + "\025APPLY_STATUS_VERIFIED\020\002\022\030\n\024APPLY_STATUS" + "_REMOVED\020\003\022\030\n\024APPLY_STATUS_EXPIRED\020\004\022\027\n\023" + "APPLY_STATUS_FAILED\020\005*\244\004\n\007Outcome\022\027\n\023OUT" + "COME_UNSPECIFIED\020\000\022\024\n\020OUTCOME_BASELINE\020\001" + "\022 \n\034OUTCOME_AWAITING_APPLICATION\020\002\022\035\n\031OU" + "TCOME_AWAITING_EXPOSURE\020\003\022!\n\035OUTCOME_INS" + "UFFICIENT_DURATION\020\004\022\035\n\031OUTCOME_INSUFFIC" + "IENT_DATA\020\005\022!\n\035OUTCOME_INSUFFICIENT_CONT" + "ROLS\020\006\022\021\n\rOUTCOME_STALE\020\007\022\027\n\023OUTCOME_UNA" + "VAILABLE\020\010\022\030\n\024OUTCOME_INCONCLUSIVE\020\t\022\027\n\023" + "OUTCOME_SAFETY_ONLY\020\n\022\030\n\024OUTCOME_FINAL_S" + "AFETY\020\013\022\026\n\022OUTCOME_BENEFICIAL\020\014\022\023\n\017OUTCO" + "ME_HARMFUL\020\r\022\023\n\017OUTCOME_EXPIRED\020\016\022\033\n\027OUT" + "COME_STABLE_OBSERVED\020\017\022&\n\"OUTCOME_STABLE" + "_EVIDENCE_INCOMPLETE\020\020\022-\n)OUTCOME_STABLE" + "_REGRESSION_REVIEW_REQUIRED\020\021\022\025\n\021OUTCOME" + "_OBSERVING\020\022*\225\002\n\020ExperimentStatus\022!\n\035EXP" + "ERIMENT_STATUS_UNSPECIFIED\020\000\022\036\n\032EXPERIME" + "NT_STATUS_PROPOSED\020\001\022\035\n\031EXPERIMENT_STATU" + "S_RUNNING\020\002\022\034\n\030EXPERIMENT_STATUS_PAUSED\020" + "\003\022\037\n\033EXPERIMENT_STATUS_SUSPENDED\020\004\022\036\n\032EX" + "PERIMENT_STATUS_COMPLETE\020\005\022\035\n\031EXPERIMENT" + "_STATUS_RETIRED\020\006\022!\n\035EXPERIMENT_STATUS_R" + "OLLED_BACK\020\007:M\n\004role\022\036.google.protobuf.M" + "ethodOptions\030\272\216\003 \001(\0162\027.xtcp_treatment.v1" + ".RoleR\004roleB\300\001\n\025com.xtcp_treatment.v1B\016T" + "reatmentProtoP\001Z6github.com/randomizedco" + "der/xtcp2/gen/go/xtcp_treatment\242\002\003XXX\252\002\020" + "XtcpTreatment.V1\312\002\020XtcpTreatment\\V1\342\002\034Xt" + "cpTreatment\\V1\\GPBMetadata\352\002\021XtcpTreatme" + "nt::V1b\006proto3" }; static const ::_pbi::DescriptorTable* PROTOBUF_NONNULL const descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto_deps[4] = { @@ -3092,7 +3107,7 @@ static ::absl::once_flag descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2epr PROTOBUF_CONSTINIT const ::_pbi::DescriptorTable descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto = { false, false, - 8795, + 8854, descriptor_table_protodef_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, "xtcp_treatment/v1/treatment.proto", &descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto_once, @@ -7528,6 +7543,11 @@ void Experiment::clear_proposed_at() { if (_impl_.proposed_at_ != nullptr) _impl_.proposed_at_->Clear(); ClearHasBit(_impl_._has_bits_[0], 0x00000100U); } +void Experiment::clear_updated_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.updated_at_ != nullptr) _impl_.updated_at_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000400U); +} Experiment::Experiment(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) #if defined(PROTOBUF_CUSTOM_VTABLE) : Super_(arena, Experiment_get_class_data()) { @@ -7590,6 +7610,9 @@ Experiment::Experiment( _impl_.latest_evidence_ = (CheckHasBit(cached_has_bits, 0x00000200U)) ? Super_::CopyConstruct(arena, *from._impl_.latest_evidence_) : nullptr; + _impl_.updated_at_ = (CheckHasBit(cached_has_bits, 0x00000400U)) + ? Super_::CopyConstruct(arena, *from._impl_.updated_at_) + : nullptr; ::memcpy(reinterpret_cast(&_impl_) + offsetof(Impl_, revision_), reinterpret_cast(&from._impl_) + @@ -7645,6 +7668,7 @@ inline void Experiment::SharedDtor(MessageLite& self) { delete this_._impl_.baseline_; delete this_._impl_.proposed_at_; delete this_._impl_.latest_evidence_; + delete this_._impl_.updated_at_; this_._impl_.~Impl_(); } @@ -7710,7 +7734,7 @@ PROTOBUF_NOINLINE void Experiment::Clear() { this_._impl_.baseline_->Clear(); } } - if (BatchCheckHasBit(cached_has_bits, 0x00000300U)) { + if (BatchCheckHasBit(cached_has_bits, 0x00000700U)) { if (CheckHasBit(cached_has_bits, 0x00000100U)) { ABSL_DCHECK(this_._impl_.proposed_at_ != nullptr); this_._impl_.proposed_at_->Clear(); @@ -7719,8 +7743,12 @@ PROTOBUF_NOINLINE void Experiment::Clear() { ABSL_DCHECK(this_._impl_.latest_evidence_ != nullptr); this_._impl_.latest_evidence_->Clear(); } + if (CheckHasBit(cached_has_bits, 0x00000400U)) { + ABSL_DCHECK(this_._impl_.updated_at_ != nullptr); + this_._impl_.updated_at_->Clear(); + } } - if (BatchCheckHasBit(cached_has_bits, 0x00001c00U)) { + if (BatchCheckHasBit(cached_has_bits, 0x00003800U)) { ::memset(&this_._impl_.revision_, 0, static_cast<::size_t>( reinterpret_cast(&this_._impl_.latest_outcome_) - @@ -7824,7 +7852,7 @@ ::uint8_t* PROTOBUF_NONNULL Experiment::_InternalSerialize( } // .xtcp_treatment.v1.ExperimentStatus status = 9 [json_name = "status", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00000800U)) { + if (CheckHasBit(cached_has_bits, 0x00001000U)) { if (this_._internal_status() != 0) { target = stream->EnsureSpace(target); target = ::_pbi::WireFormatLite::WriteEnumToArray( @@ -7833,7 +7861,7 @@ ::uint8_t* PROTOBUF_NONNULL Experiment::_InternalSerialize( } // uint64 revision = 10 [json_name = "revision"]; - if (CheckHasBit(cached_has_bits, 0x00000400U)) { + if (CheckHasBit(cached_has_bits, 0x00000800U)) { if (this_._internal_revision() != 0) { target = stream->EnsureSpace(target); target = ::_pbi::WireFormatLite::WriteUInt64ToArray( @@ -7856,7 +7884,7 @@ ::uint8_t* PROTOBUF_NONNULL Experiment::_InternalSerialize( } // .xtcp_treatment.v1.Outcome latest_outcome = 13 [json_name = "latestOutcome", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00001000U)) { + if (CheckHasBit(cached_has_bits, 0x00002000U)) { if (this_._internal_latest_outcome() != 0) { target = stream->EnsureSpace(target); target = ::_pbi::WireFormatLite::WriteEnumToArray( @@ -7864,6 +7892,13 @@ ::uint8_t* PROTOBUF_NONNULL Experiment::_InternalSerialize( } } + // .google.protobuf.Timestamp updated_at = 14 [json_name = "updatedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000400U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 14, *this_._impl_.updated_at_, this_._impl_.updated_at_->GetCachedSize(), target, + stream); + } + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( @@ -7941,7 +7976,7 @@ ::size_t Experiment::ByteSizeLong() const { ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.baseline_); } } - if (BatchCheckHasBit(cached_has_bits, 0x00001f00U)) { + if (BatchCheckHasBit(cached_has_bits, 0x00003f00U)) { // .google.protobuf.Timestamp proposed_at = 11 [json_name = "proposedAt"]; if (CheckHasBit(cached_has_bits, 0x00000100U)) { total_size += 1 + @@ -7952,22 +7987,27 @@ ::size_t Experiment::ByteSizeLong() const { total_size += 1 + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.latest_evidence_); } - // uint64 revision = 10 [json_name = "revision"]; + // .google.protobuf.Timestamp updated_at = 14 [json_name = "updatedAt"]; if (CheckHasBit(cached_has_bits, 0x00000400U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.updated_at_); + } + // uint64 revision = 10 [json_name = "revision"]; + if (CheckHasBit(cached_has_bits, 0x00000800U)) { if (this_._internal_revision() != 0) { total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne( this_._internal_revision()); } } // .xtcp_treatment.v1.ExperimentStatus status = 9 [json_name = "status", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00000800U)) { + if (CheckHasBit(cached_has_bits, 0x00001000U)) { if (this_._internal_status() != 0) { total_size += 1 + ::_pbi::WireFormatLite::EnumSize(this_._internal_status()); } } // .xtcp_treatment.v1.Outcome latest_outcome = 13 [json_name = "latestOutcome", (.buf.validate.field) = { - if (CheckHasBit(cached_has_bits, 0x00001000U)) { + if (CheckHasBit(cached_has_bits, 0x00002000U)) { if (this_._internal_latest_outcome() != 0) { total_size += 1 + ::_pbi::WireFormatLite::EnumSize(this_._internal_latest_outcome()); @@ -8055,7 +8095,7 @@ void Experiment::MergeImpl(::google::protobuf::MessageLite& to_msg, } } } - if (BatchCheckHasBit(cached_has_bits, 0x00001f00U)) { + if (BatchCheckHasBit(cached_has_bits, 0x00003f00U)) { if (CheckHasBit(cached_has_bits, 0x00000100U)) { ABSL_DCHECK(from._impl_.proposed_at_ != nullptr); if (_this->_impl_.proposed_at_ == nullptr) { @@ -8073,16 +8113,24 @@ void Experiment::MergeImpl(::google::protobuf::MessageLite& to_msg, } } if (CheckHasBit(cached_has_bits, 0x00000400U)) { + ABSL_DCHECK(from._impl_.updated_at_ != nullptr); + if (_this->_impl_.updated_at_ == nullptr) { + _this->_impl_.updated_at_ = Super_::CopyConstruct(arena, *from._impl_.updated_at_); + } else { + _this->_impl_.updated_at_->MergeFrom(*from._impl_.updated_at_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000800U)) { if (from._internal_revision() != 0) { _this->_impl_.revision_ = from._impl_.revision_; } } - if (CheckHasBit(cached_has_bits, 0x00000800U)) { + if (CheckHasBit(cached_has_bits, 0x00001000U)) { if (from._internal_status() != 0) { _this->_impl_.status_ = from._impl_.status_; } } - if (CheckHasBit(cached_has_bits, 0x00001000U)) { + if (CheckHasBit(cached_has_bits, 0x00002000U)) { if (from._internal_latest_outcome() != 0) { _this->_impl_.latest_outcome_ = from._impl_.latest_outcome_; } diff --git a/gen/cpp/xtcp_treatment/v1/treatment.pb.h b/gen/cpp/xtcp_treatment/v1/treatment.pb.h index 0d1e6d4f5..ac98f18eb 100644 --- a/gen/cpp/xtcp_treatment/v1/treatment.pb.h +++ b/gen/cpp/xtcp_treatment/v1/treatment.pb.h @@ -2929,6 +2929,7 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Experiment final : public ::google: kBaselineFieldNumber = 6, kProposedAtFieldNumber = 11, kLatestEvidenceFieldNumber = 12, + kUpdatedAtFieldNumber = 14, kRevisionFieldNumber = 10, kStatusFieldNumber = 9, kLatestOutcomeFieldNumber = 13, @@ -3098,6 +3099,21 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Experiment final : public ::google: const ::xtcp_treatment::v1::Evidence& _internal_latest_evidence() const; ::xtcp_treatment::v1::Evidence* PROTOBUF_NONNULL _internal_mutable_latest_evidence(); + public: + // .google.protobuf.Timestamp updated_at = 14 [json_name = "updatedAt"]; + [[nodiscard]] bool has_updated_at() const; + void clear_updated_at() ; + [[nodiscard]] const ::google::protobuf::Timestamp& updated_at() const; + [[nodiscard]] ::google::protobuf::Timestamp* PROTOBUF_NULLABLE release_updated_at(); + ::google::protobuf::Timestamp* PROTOBUF_NONNULL mutable_updated_at(); + void set_allocated_updated_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_updated_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE unsafe_arena_release_updated_at(); + + private: + const ::google::protobuf::Timestamp& _internal_updated_at() const; + ::google::protobuf::Timestamp* PROTOBUF_NONNULL _internal_mutable_updated_at(); + public: // uint64 revision = 10 [json_name = "revision"]; void clear_revision() ; @@ -3133,8 +3149,8 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Experiment final : public ::google: private: class _Internal; using ParseTableT_ = - ::google::protobuf::internal::TcParseTable<4, 13, - 6, 77, + ::google::protobuf::internal::TcParseTable<4, 14, + 7, 77, 2>; static constexpr ParseTableT_ InternalGenerateParseTable_( const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); @@ -3172,6 +3188,7 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Experiment final : public ::google: ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE baseline_; ::google::protobuf::Timestamp* PROTOBUF_NULLABLE proposed_at_; ::xtcp_treatment::v1::Evidence* PROTOBUF_NULLABLE latest_evidence_; + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE updated_at_; ::uint64_t revision_; int status_; int latest_outcome_; @@ -9156,7 +9173,7 @@ Experiment::_internal_mutable_phases() { inline void Experiment::clear_status() { ::google::protobuf::internal::TSanWrite(&_impl_); _impl_.status_ = 0; - ClearHasBit(_impl_._has_bits_[0], 0x00000800U); + ClearHasBit(_impl_._has_bits_[0], 0x00001000U); } inline ::xtcp_treatment::v1::ExperimentStatus Experiment::status() const { // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Experiment.status) @@ -9164,7 +9181,7 @@ inline ::xtcp_treatment::v1::ExperimentStatus Experiment::status() const { } inline void Experiment::set_status(::xtcp_treatment::v1::ExperimentStatus value) { _internal_set_status(value); - SetHasBit(_impl_._has_bits_[0], 0x00000800U); + SetHasBit(_impl_._has_bits_[0], 0x00001000U); // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Experiment.status) } inline ::xtcp_treatment::v1::ExperimentStatus Experiment::_internal_status() const { @@ -9180,7 +9197,7 @@ inline void Experiment::_internal_set_status(::xtcp_treatment::v1::ExperimentSta inline void Experiment::clear_revision() { ::google::protobuf::internal::TSanWrite(&_impl_); _impl_.revision_ = ::uint64_t{0u}; - ClearHasBit(_impl_._has_bits_[0], 0x00000400U); + ClearHasBit(_impl_._has_bits_[0], 0x00000800U); } inline ::uint64_t Experiment::revision() const { // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Experiment.revision) @@ -9188,7 +9205,7 @@ inline ::uint64_t Experiment::revision() const { } inline void Experiment::set_revision(::uint64_t value) { _internal_set_revision(value); - SetHasBit(_impl_._has_bits_[0], 0x00000400U); + SetHasBit(_impl_._has_bits_[0], 0x00000800U); // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Experiment.revision) } inline ::uint64_t Experiment::_internal_revision() const { @@ -9395,7 +9412,7 @@ inline void Experiment::set_allocated_latest_evidence(::xtcp_treatment::v1::Evid inline void Experiment::clear_latest_outcome() { ::google::protobuf::internal::TSanWrite(&_impl_); _impl_.latest_outcome_ = 0; - ClearHasBit(_impl_._has_bits_[0], 0x00001000U); + ClearHasBit(_impl_._has_bits_[0], 0x00002000U); } inline ::xtcp_treatment::v1::Outcome Experiment::latest_outcome() const { // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Experiment.latest_outcome) @@ -9403,7 +9420,7 @@ inline ::xtcp_treatment::v1::Outcome Experiment::latest_outcome() const { } inline void Experiment::set_latest_outcome(::xtcp_treatment::v1::Outcome value) { _internal_set_latest_outcome(value); - SetHasBit(_impl_._has_bits_[0], 0x00001000U); + SetHasBit(_impl_._has_bits_[0], 0x00002000U); // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Experiment.latest_outcome) } inline ::xtcp_treatment::v1::Outcome Experiment::_internal_latest_outcome() const { @@ -9415,6 +9432,99 @@ inline void Experiment::_internal_set_latest_outcome(::xtcp_treatment::v1::Outco _impl_.latest_outcome_ = value; } +// .google.protobuf.Timestamp updated_at = 14 [json_name = "updatedAt"]; +inline bool Experiment::has_updated_at() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000400U); + PROTOBUF_ASSUME(!value || _impl_.updated_at_ != nullptr); + return value; +} +inline const ::google::protobuf::Timestamp& Experiment::_internal_updated_at() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::google::protobuf::Timestamp* p = _impl_.updated_at_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::google::protobuf::Timestamp>(&::google::protobuf::Timestamp_globals_); +} +inline const ::google::protobuf::Timestamp& Experiment::updated_at() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Experiment.updated_at) + return _internal_updated_at(); +} +inline void Experiment::unsafe_arena_set_allocated_updated_at( + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.updated_at_); + } + _impl_.updated_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000400U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000400U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment.v1.Experiment.updated_at) +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Experiment::release_updated_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000400U); + ::google::protobuf::Timestamp* released = _impl_.updated_at_; + _impl_.updated_at_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Experiment::unsafe_arena_release_updated_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Experiment.updated_at) + + ClearHasBit(_impl_._has_bits_[0], 0x00000400U); + ::google::protobuf::Timestamp* temp = _impl_.updated_at_; + _impl_.updated_at_ = nullptr; + return temp; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Experiment::_internal_mutable_updated_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.updated_at_ == nullptr) { + auto* p = Super_::DefaultConstruct<::google::protobuf::Timestamp>(GetArena()); + _impl_.updated_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(p); + } + return _impl_.updated_at_; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Experiment::mutable_updated_at() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000400U); + ::google::protobuf::Timestamp* _msg = _internal_mutable_updated_at(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Experiment.updated_at) + return _msg; +} +inline void Experiment::set_allocated_updated_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.updated_at_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000400U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000400U); + } + + _impl_.updated_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Experiment.updated_at) +} + #ifdef __GNUC__ #pragma GCC diagnostic pop #endif // __GNUC__ diff --git a/gen/dart/xtcp_treatment/v1/treatment.pb.dart b/gen/dart/xtcp_treatment/v1/treatment.pb.dart index e8d92b984..39626689d 100644 --- a/gen/dart/xtcp_treatment/v1/treatment.pb.dart +++ b/gen/dart/xtcp_treatment/v1/treatment.pb.dart @@ -1575,6 +1575,7 @@ class Experiment extends $pb.GeneratedMessage { $0.Timestamp? proposedAt, Evidence? latestEvidence, Outcome? latestOutcome, + $0.Timestamp? updatedAt, }) { final result = Experiment._(); if (id != null) result.id = id; @@ -1590,6 +1591,7 @@ class Experiment extends $pb.GeneratedMessage { if (proposedAt != null) result.proposedAt = proposedAt; if (latestEvidence != null) result.latestEvidence = latestEvidence; if (latestOutcome != null) result.latestOutcome = latestOutcome; + if (updatedAt != null) result.updatedAt = updatedAt; return result; } @@ -1630,6 +1632,8 @@ class Experiment extends $pb.GeneratedMessage { subBuilder: Evidence.$_createMessage) ..aE(13, _omitFieldNames ? '' : 'latestOutcome', enumValues: Outcome.values) + ..aOM<$0.Timestamp>(14, _omitFieldNames ? '' : 'updatedAt', + subBuilder: $0.Timestamp.$_createMessage) ..hasRequiredFields = false; @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @@ -1767,6 +1771,19 @@ class Experiment extends $pb.GeneratedMessage { $core.bool hasLatestOutcome() => $_has(12); @$pb.TagNumber(13) void clearLatestOutcome() => $_clearField(13); + + /// When the controller last changed this experiment. Terminal experiments + /// suppress rediscovery of their destination for a day after this instant. + @$pb.TagNumber(14) + $0.Timestamp get updatedAt => $_getN(13); + @$pb.TagNumber(14) + set updatedAt($0.Timestamp value) => $_setField(14, value); + @$pb.TagNumber(14) + $core.bool hasUpdatedAt() => $_has(13); + @$pb.TagNumber(14) + void clearUpdatedAt() => $_clearField(14); + @$pb.TagNumber(14) + $0.Timestamp ensureUpdatedAt() => $_ensure(13); } class TreatmentExt { diff --git a/gen/dart/xtcp_treatment/v1/treatment.pbjson.dart b/gen/dart/xtcp_treatment/v1/treatment.pbjson.dart index a42e61cb0..0a10a0873 100644 --- a/gen/dart/xtcp_treatment/v1/treatment.pbjson.dart +++ b/gen/dart/xtcp_treatment/v1/treatment.pbjson.dart @@ -940,6 +940,14 @@ const Experiment$json = { '8': {}, '10': 'latestOutcome' }, + { + '1': 'updated_at', + '3': 14, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '10': 'updatedAt' + }, ], }; @@ -960,4 +968,5 @@ final $typed_data.Uint8List experimentDescriptor = $convert.base64Decode( 'ZS5wcm90b2J1Zi5UaW1lc3RhbXBSCnByb3Bvc2VkQXQSRAoPbGF0ZXN0X2V2aWRlbmNlGAwgAS' 'gLMhsueHRjcF90cmVhdG1lbnQudjEuRXZpZGVuY2VSDmxhdGVzdEV2aWRlbmNlEksKDmxhdGVz' 'dF9vdXRjb21lGA0gASgOMhoueHRjcF90cmVhdG1lbnQudjEuT3V0Y29tZUIIukgFggECEAFSDW' - 'xhdGVzdE91dGNvbWU='); + 'xhdGVzdE91dGNvbWUSOQoKdXBkYXRlZF9hdBgOIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1l' + 'c3RhbXBSCXVwZGF0ZWRBdA=='); diff --git a/gen/go/xtcp_treatment/treatment.pb.go b/gen/go/xtcp_treatment/treatment.pb.go index 32b4eb861..df51e7597 100644 --- a/gen/go/xtcp_treatment/treatment.pb.go +++ b/gen/go/xtcp_treatment/treatment.pb.go @@ -1436,8 +1436,11 @@ type Experiment struct { ProposedAt *timestamppb.Timestamp `protobuf:"bytes,11,opt,name=proposed_at,json=proposedAt,proto3" json:"proposed_at,omitempty"` LatestEvidence *Evidence `protobuf:"bytes,12,opt,name=latest_evidence,json=latestEvidence,proto3" json:"latest_evidence,omitempty"` LatestOutcome Outcome `protobuf:"varint,13,opt,name=latest_outcome,json=latestOutcome,proto3,enum=xtcp_treatment.v1.Outcome" json:"latest_outcome,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // When the controller last changed this experiment. Terminal experiments + // suppress rediscovery of their destination for a day after this instant. + UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,14,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Experiment) Reset() { @@ -1561,6 +1564,13 @@ func (x *Experiment) GetLatestOutcome() Outcome { return Outcome_OUTCOME_UNSPECIFIED } +func (x *Experiment) GetUpdatedAt() *timestamppb.Timestamp { + if x != nil { + return x.UpdatedAt + } + return nil +} + var file_xtcp_treatment_v1_treatment_proto_extTypes = []protoimpl.ExtensionInfo{ { ExtendedType: (*descriptorpb.MethodOptions)(nil), @@ -1714,7 +1724,7 @@ const file_xtcp_treatment_v1_treatment_proto_rawDesc = "" + "\bevidence\x18\x05 \x01(\v2\x1b.xtcp_treatment.v1.EvidenceR\bevidence\x12>\n" + "\aoutcome\x18\x06 \x01(\x0e2\x1a.xtcp_treatment.v1.OutcomeB\b\xbaH\x05\x82\x01\x02\x10\x01R\aoutcome\x12$\n" + "\breviewer\x18\a \x01(\tB\b\xbaH\x05r\x03\x18\x80\x01R\breviewer\x12 \n" + - "\x06reason\x18\b \x01(\tB\b\xbaH\x05r\x03\x18\x80 R\x06reason\"\xcd\x06\n" + + "\x06reason\x18\b \x01(\tB\b\xbaH\x05r\x03\x18\x80 R\x06reason\"\x88\a\n" + "\n" + "Experiment\x129\n" + "\x02id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x02id\x12E\n" + @@ -1734,7 +1744,9 @@ const file_xtcp_treatment_v1_treatment_proto_rawDesc = "" + "\vproposed_at\x18\v \x01(\v2\x1a.google.protobuf.TimestampR\n" + "proposedAt\x12D\n" + "\x0flatest_evidence\x18\f \x01(\v2\x1b.xtcp_treatment.v1.EvidenceR\x0elatestEvidence\x12K\n" + - "\x0elatest_outcome\x18\r \x01(\x0e2\x1a.xtcp_treatment.v1.OutcomeB\b\xbaH\x05\x82\x01\x02\x10\x01R\rlatestOutcome*?\n" + + "\x0elatest_outcome\x18\r \x01(\x0e2\x1a.xtcp_treatment.v1.OutcomeB\b\xbaH\x05\x82\x01\x02\x10\x01R\rlatestOutcome\x129\n" + + "\n" + + "updated_at\x18\x0e \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt*?\n" + "\x04Role\x12\x14\n" + "\x10ROLE_UNSPECIFIED\x10\x00\x12\x0e\n" + "\n" + @@ -1860,13 +1872,14 @@ var file_xtcp_treatment_v1_treatment_proto_depIdxs = []int32{ 17, // 34: xtcp_treatment.v1.Experiment.proposed_at:type_name -> google.protobuf.Timestamp 13, // 35: xtcp_treatment.v1.Experiment.latest_evidence:type_name -> xtcp_treatment.v1.Evidence 3, // 36: xtcp_treatment.v1.Experiment.latest_outcome:type_name -> xtcp_treatment.v1.Outcome - 19, // 37: xtcp_treatment.v1.role:extendee -> google.protobuf.MethodOptions - 0, // 38: xtcp_treatment.v1.role:type_name -> xtcp_treatment.v1.Role - 39, // [39:39] is the sub-list for method output_type - 39, // [39:39] is the sub-list for method input_type - 38, // [38:39] is the sub-list for extension type_name - 37, // [37:38] is the sub-list for extension extendee - 0, // [0:37] is the sub-list for field type_name + 17, // 37: xtcp_treatment.v1.Experiment.updated_at:type_name -> google.protobuf.Timestamp + 19, // 38: xtcp_treatment.v1.role:extendee -> google.protobuf.MethodOptions + 0, // 39: xtcp_treatment.v1.role:type_name -> xtcp_treatment.v1.Role + 40, // [40:40] is the sub-list for method output_type + 40, // [40:40] is the sub-list for method input_type + 39, // [39:40] is the sub-list for extension type_name + 38, // [38:39] is the sub-list for extension extendee + 0, // [0:38] is the sub-list for field type_name } func init() { file_xtcp_treatment_v1_treatment_proto_init() } diff --git a/gen/go/xtcp_treatment/treatment_vtproto.pb.go b/gen/go/xtcp_treatment/treatment_vtproto.pb.go index d69cae78d..f8e70c3b2 100644 --- a/gen/go/xtcp_treatment/treatment_vtproto.pb.go +++ b/gen/go/xtcp_treatment/treatment_vtproto.pb.go @@ -1039,6 +1039,16 @@ func (m *Experiment) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if m.UpdatedAt != nil { + size, err := (*timestamppb.Timestamp)(m.UpdatedAt).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x72 + } if m.LatestOutcome != 0 { i = protohelpers.EncodeVarint(dAtA, i, uint64(m.LatestOutcome)) i-- @@ -1605,6 +1615,10 @@ func (m *Experiment) SizeVT() (n int) { if m.LatestOutcome != 0 { n += 1 + protohelpers.SizeOfVarint(uint64(m.LatestOutcome)) } + if m.UpdatedAt != nil { + l = (*timestamppb.Timestamp)(m.UpdatedAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } n += len(m.unknownFields) return n } @@ -4733,6 +4747,42 @@ func (m *Experiment) UnmarshalVT(dAtA []byte) error { break } } + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UpdatedAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.UpdatedAt == nil { + m.UpdatedAt = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.UpdatedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/gen/python/xtcp_treatment/v1/treatment_pb2.py b/gen/python/xtcp_treatment/v1/treatment_pb2.py index 4437747fb..903ba9d94 100644 --- a/gen/python/xtcp_treatment/v1/treatment_pb2.py +++ b/gen/python/xtcp_treatment/v1/treatment_pb2.py @@ -28,7 +28,7 @@ from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!xtcp_treatment/v1/treatment.proto\x12\x11xtcp_treatment.v1\x1a\x1b\x62uf/validate/validate.proto\x1a google/protobuf/descriptor.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\x8d\x07\n\x0bTCPSettings\x12Y\n\x12\x63ongestion_control\x18\x01 \x01(\tB%\xbaH\"r \x18\x0f\x32\x1c^[a-zA-Z0-9][a-zA-Z0-9_.-]*$H\x00R\x11\x63ongestionControl\x88\x01\x01\x12\'\n\x0f\x63ongestion_lock\x18\x02 \x01(\x08R\x0e\x63ongestionLock\x12.\n\nrto_min_ms\x18\x03 \x01(\rB\x0b\xbaH\x08*\x06\x18\xc0\xa9\x07(\x01H\x01R\x08rtoMinMs\x88\x01\x01\x12K\n\x19initial_congestion_window\x18\x04 \x01(\rB\n\xbaH\x07*\x05\x18\x80\x08(\x01H\x02R\x17initialCongestionWindow\x88\x01\x01\x12\x45\n\x16initial_receive_window\x18\x05 \x01(\rB\n\xbaH\x07*\x05\x18\x80\x08(\x01H\x03R\x14initialReceiveWindow\x88\x01\x01\x12 \n\tquick_ack\x18\x06 \x01(\x08H\x04R\x08quickAck\x88\x01\x01\x12\x15\n\x03\x65\x63n\x18\x07 \x01(\x08H\x05R\x03\x65\x63n\x88\x01\x01:\x87\x03\xbaH\x83\x03\x1a\xf3\x01\n\x16tcp_settings.non_empty\x12/a treatment must set at least one TCP parameter\x1a\xa7\x01has(this.congestion_control) || has(this.rto_min_ms) || has(this.initial_congestion_window) || has(this.initial_receive_window) || has(this.quick_ack) || has(this.ecn)\x1a\x8a\x01\n$tcp_settings.lock_requires_algorithm\x12+congestion_lock requires congestion_control\x1a\x35!this.congestion_lock || has(this.congestion_control)B\x15\n\x13_congestion_controlB\r\n\x0b_rto_min_msB\x1c\n\x1a_initial_congestion_windowB\x19\n\x17_initial_receive_windowB\x0c\n\n_quick_ackB\x06\n\x04_ecn\"\xd8\x03\n\tTreatment\x12\x39\n\x02id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x02id\x12#\n\x08revision\x18\x02 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\x08revision\x12,\n\x0b\x64\x65stination\x18\x03 \x01(\tB\n\xbaH\x07r\x05\x18\x31\xe8\x01\x01R\x0b\x64\x65stination\x12\x42\n\x08settings\x18\x04 \x01(\x0b\x32\x1e.xtcp_treatment.v1.TCPSettingsB\x06\xbaH\x03\xc8\x01\x01R\x08settings\x12.\n\x13\x65\x63n_fabric_verified\x18\x05 \x01(\x08R\x11\x65\x63nFabricVerified:\xc8\x01\xbaH\xc4\x01\x1a\xc1\x01\n\x1ctreatment.dctcp_requires_ecn\x12\x41\x44\x43TCP requires independently verified ECN fabric and explicit ECN\x1a^this.settings.congestion_control != \'dctcp\' || (this.ecn_fabric_verified && this.settings.ecn)\"\xcc\x05\n\nAssignment\x12N\n\rexperiment_id\x18\x02 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x0c\x65xperimentId\x12\x42\n\ttreatment\x18\x03 \x01(\x0b\x32\x1c.xtcp_treatment.v1.TreatmentB\x06\xbaH\x03\xc8\x01\x01R\ttreatment\x12\x41\n\x04mode\x18\x04 \x01(\x0e\x32!.xtcp_treatment.v1.AssignmentModeB\n\xbaH\x07\x82\x01\x04\x10\x01 \x00R\x04mode\x12\x44\n\x10lease_expires_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0eleaseExpiresAt\x12>\n\x0bpredecessor\x18\x06 \x01(\x0b\x32\x1c.xtcp_treatment.v1.TreatmentR\x0bpredecessor\x12\'\n\ngeneration\x18\x07 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\ngeneration:\xad\x02\xbaH\xa9\x02\x1a}\n\x1f\x61ssignment.trial_requires_lease\x12,a trial assignment requires lease_expires_at\x1a,this.mode != 1 || has(this.lease_expires_at)\x1a\xa7\x01\n\"assignment.predecessor_destination\x12+predecessor must cover the same destination\x1aT!has(this.predecessor) || this.predecessor.destination == this.treatment.destinationJ\x04\x08\x01\x10\x02R\x02id\"\xa4\x02\n\x0c\x44\x65siredState\x12\x44\n\x08\x61gent_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x07\x61gentId\x12#\n\x08revision\x18\x02 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\x08revision\x12\x1c\n\tsuspended\x18\x03 \x01(\x08R\tsuspended\x12J\n\x0b\x61ssignments\x18\x04 \x03(\x0b\x32\x1d.xtcp_treatment.v1.AssignmentB\t\xbaH\x06\x92\x01\x03\x10\x80 R\x0b\x61ssignments\x12?\n\tissued_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\x08issuedAt\"\xa3\x02\n\x0c\x43\x61pabilities\x12\x42\n\x07\x62oot_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x06\x62ootId\x12/\n\x0ekernel_release\x18\x02 \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x02R\rkernelRelease\x12\x43\n\x15\x63ongestion_algorithms\x18\x03 \x03(\tB\x0e\xbaH\x0b\x92\x01\x08\x10@\"\x04r\x02\x18\x0fR\x14\x63ongestionAlgorithms\x12.\n\x13simple_host_routing\x18\x04 \x01(\x08R\x11simpleHostRouting\x12)\n\x10\x61\x63tuator_enabled\x18\x05 \x01(\x08R\x0f\x61\x63tuatorEnabled\"\xa8\x05\n\x11\x41pplicationRecord\x12\x45\n\nassignment\x18\x01 \x01(\x0b\x32\x1d.xtcp_treatment.v1.AssignmentB\x06\xbaH\x03\xc8\x01\x01R\nassignment\x12)\n\x10\x64\x65sired_revision\x18\x02 \x01(\x04R\x0f\x64\x65siredRevision\x12@\n\x06status\x18\x03 \x01(\x0e\x32\x1e.xtcp_treatment.v1.ApplyStatusB\x08\xbaH\x05\x82\x01\x02\x10\x01R\x06status\x12;\n\x0breceived_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\nreceivedAt\x12=\n\x0c\x61ttempted_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0b\x61ttemptedAt\x12\x39\n\napplied_at\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tappliedAt\x12;\n\x0bverified_at\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\nverifiedAt\x12\x39\n\nremoved_at\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tremovedAt\x12\x1e\n\x05\x65rror\x18\t \x01(\tB\x08\xbaH\x05r\x03\x18\x80 R\x05\x65rror\x12\x31\n\x0finstalled_route\x18\n \x01(\x0c\x42\x08\xbaH\x05z\x03\x18\x80 R\x0einstalledRoute\x12\x34\n\x11installed_boot_id\x18\x0b \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x01R\x0finstalledBootId\x12\'\n\x0f\x65xpired_latched\x18\x0c \x01(\x08R\x0e\x65xpiredLatched\"\xb1\x02\n\nLocalState\x12\x39\n\x07\x64\x65sired\x18\x01 \x01(\x0b\x32\x1f.xtcp_treatment.v1.DesiredStateR\x07\x64\x65sired\x12+\n\x11locally_suspended\x18\x02 \x01(\x08R\x10locallySuspended\x12\x35\n\x11suspension_reason\x18\x03 \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x08R\x10suspensionReason\x12I\n\x07records\x18\x04 \x03(\x0b\x32$.xtcp_treatment.v1.ApplicationRecordB\t\xbaH\x06\x92\x01\x03\x10\x80@R\x07records\x12\x39\n\nupdated_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tupdatedAt\"\x89\x04\n\x0cHostEvidence\x12\x44\n\x08\x61gent_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x07\x61gentId\x12\x18\n\x07sockets\x18\x02 \x01(\x04R\x07sockets\x12\x1f\n\x0b\x62ytes_acked\x18\x03 \x01(\x04R\nbytesAcked\x12\x31\n\x0bgoodput_bps\x18\x04 \x01(\x01\x42\x10\xbaH\r\x12\x0b)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\ngoodputBps\x12\x43\n\x14retransmission_ratio\x18\x05 \x01(\x01\x42\x10\xbaH\r\x12\x0b)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x13retransmissionRatio\x12.\n\nrtt_p95_ms\x18\x06 \x01(\x01\x42\x10\xbaH\r\x12\x0b)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x08rttP95Ms\x12\'\n\x0f\x65xposed_sockets\x18\x07 \x01(\x04R\x0e\x65xposedSockets\x12-\n\x12\x63ollection_healthy\x18\x08 \x01(\x08R\x11\x63ollectionHealthy:x\xbaHu\x1as\n$host_evidence.exposed_within_sockets\x12%exposed_sockets cannot exceed sockets\x1a$this.exposed_sockets <= this.sockets\"\xbc\x05\n\x08\x45vidence\x12\x39\n\x02id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x02id\x12\x43\n\x0b\x61nalyzed_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\nanalyzedAt\x12\x45\n\x0cwindow_start\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\x0bwindowStart\x12\x41\n\nwindow_end\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\twindowEnd\x12/\n\rquery_version\x18\x05 \x01(\tB\n\xbaH\x07r\x05\x10\x01\x18\x80\x02R\x0cqueryVersion\x12\x37\n\x12\x65nrichment_version\x18\x06 \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x02R\x11\x65nrichmentVersion\x12\x42\n\x05hosts\x18\x07 \x03(\x0b\x32\x1f.xtcp_treatment.v1.HostEvidenceB\x0b\xbaH\x08\x92\x01\x05\x08\x01\x10\x90NR\x05hosts\x12>\n\x0f\x61rtifact_sha256\x18\x08 \x01(\tB\x15\xbaH\x12r\x10\x32\x0e^[0-9a-f]{64}$R\x0e\x61rtifactSha256:\xb7\x01\xbaH\xb3\x01\x1a\xb0\x01\n\x11\x65vidence.interval\x12Owindow_end must follow window_start and analyzed_at must not precede window_end\x1aJthis.window_end > this.window_start && this.analyzed_at >= this.window_end\"\xec\x06\n\x10\x45xperimentPolicy\x12]\n\x13minimum_observation\x18\x01 \x01(\x0b\x32\x19.google.protobuf.DurationB\x11\xbaH\x0e\xaa\x01\x08\"\x04\x08\x80\xf5$*\x00\xc8\x01\x01R\x12minimumObservation\x12Q\n\rmaximum_phase\x18\x02 \x01(\x0b\x32\x19.google.protobuf.DurationB\x11\xbaH\x0e\xaa\x01\x08\"\x04\x08\x80\xf5$*\x00\xc8\x01\x01R\x0cmaximumPhase\x12@\n\x18minimum_sockets_per_host\x18\x03 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\x15minimumSocketsPerHost\x12<\n\x16minimum_bytes_per_host\x18\x04 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\x13minimumBytesPerHost\x12>\n\x15minimum_treated_hosts\x18\x05 \x01(\rB\n\xbaH\x07*\x05\x18\x90N(\x02R\x13minimumTreatedHosts\x12J\n\x13minimum_improvement\x18\x06 \x01(\x01\x42\x19\xbaH\x16\x12\x14\x19\x00\x00\x00\x00\x00\x00\xf0?)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x12minimumImprovement\x12\x61\n\x1fmaximum_retransmission_increase\x18\x07 \x01(\x01\x42\x19\xbaH\x16\x12\x14\x19\x00\x00\x00\x00\x00\x00\xf0?)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x1dmaximumRetransmissionIncrease\x12K\n\x14maximum_rtt_increase\x18\x08 \x01(\x01\x42\x19\xbaH\x16\x12\x14\x19\x00\x00\x00\x00\x00\x00\xf0?)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x12maximumRttIncrease\x12^\n\x14maximum_evidence_age\x18\t \x01(\x0b\x32\x19.google.protobuf.DurationB\x11\xbaH\x0e\xaa\x01\x08\"\x04\x08\x80\xf5$*\x00\xc8\x01\x01R\x12maximumEvidenceAge:\x89\x01\xbaH\x85\x01\x1a\x82\x01\n\x1fpolicy.observation_within_phase\x12/minimum_observation cannot exceed maximum_phase\x1a.this.minimum_observation <= this.maximum_phase\"\xc1\x03\n\x05Phase\x12-\n\x0ctarget_hosts\x18\x01 \x01(\rB\n\xbaH\x07*\x05\x18\x90N(\x01R\x0btargetHosts\x12\x37\n\tissued_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x08issuedAt\x12P\n\x16observation_started_at\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x14observationStartedAt\x12=\n\x0c\x65valuated_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0b\x65valuatedAt\x12\x37\n\x08\x65vidence\x18\x05 \x01(\x0b\x32\x1b.xtcp_treatment.v1.EvidenceR\x08\x65vidence\x12>\n\x07outcome\x18\x06 \x01(\x0e\x32\x1a.xtcp_treatment.v1.OutcomeB\x08\xbaH\x05\x82\x01\x02\x10\x01R\x07outcome\x12$\n\x08reviewer\x18\x07 \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x01R\x08reviewer\x12 \n\x06reason\x18\x08 \x01(\tB\x08\xbaH\x05r\x03\x18\x80 R\x06reason\"\xcd\x06\n\nExperiment\x12\x39\n\x02id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x02id\x12\x45\n\x08location\x18\x02 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x08location\x12*\n\nhypothesis\x18\x03 \x01(\tB\n\xbaH\x07r\x05\x10\x01\x18\x80@R\nhypothesis\x12\x42\n\ttreatment\x18\x04 \x01(\x0b\x32\x1c.xtcp_treatment.v1.TreatmentB\x06\xbaH\x03\xc8\x01\x01R\ttreatment\x12\x43\n\x06policy\x18\x05 \x01(\x0b\x32#.xtcp_treatment.v1.ExperimentPolicyB\x06\xbaH\x03\xc8\x01\x01R\x06policy\x12?\n\x08\x62\x61seline\x18\x06 \x01(\x0b\x32\x1b.xtcp_treatment.v1.EvidenceB\x06\xbaH\x03\xc8\x01\x01R\x08\x62\x61seline\x12X\n\x0c\x63ohort_order\x18\x07 \x03(\tB5\xbaH2\x92\x01/\x08\x03\x10\x90N\x18\x01\"&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x0b\x63ohortOrder\x12:\n\x06phases\x18\x08 \x03(\x0b\x32\x18.xtcp_treatment.v1.PhaseB\x08\xbaH\x05\x92\x01\x02\x10@R\x06phases\x12\x45\n\x06status\x18\t \x01(\x0e\x32#.xtcp_treatment.v1.ExperimentStatusB\x08\xbaH\x05\x82\x01\x02\x10\x01R\x06status\x12\x1a\n\x08revision\x18\n \x01(\x04R\x08revision\x12;\n\x0bproposed_at\x18\x0b \x01(\x0b\x32\x1a.google.protobuf.TimestampR\nproposedAt\x12\x44\n\x0flatest_evidence\x18\x0c \x01(\x0b\x32\x1b.xtcp_treatment.v1.EvidenceR\x0elatestEvidence\x12K\n\x0elatest_outcome\x18\r \x01(\x0e\x32\x1a.xtcp_treatment.v1.OutcomeB\x08\xbaH\x05\x82\x01\x02\x10\x01R\rlatestOutcome*?\n\x04Role\x12\x14\n\x10ROLE_UNSPECIFIED\x10\x00\x12\x0e\n\nROLE_AGENT\x10\x01\x12\x11\n\rROLE_OPERATOR\x10\x02*\xa4\x01\n\x0e\x41ssignmentMode\x12\x1f\n\x1b\x41SSIGNMENT_MODE_UNSPECIFIED\x10\x00\x12\x19\n\x15\x41SSIGNMENT_MODE_TRIAL\x10\x01\x12\x1a\n\x16\x41SSIGNMENT_MODE_STABLE\x10\x02\x12\x1d\n\x19\x41SSIGNMENT_MODE_SUSPENDED\x10\x03\x12\x1b\n\x17\x41SSIGNMENT_MODE_RETIRED\x10\x04*\xad\x01\n\x0b\x41pplyStatus\x12\x1c\n\x18\x41PPLY_STATUS_UNSPECIFIED\x10\x00\x12\x18\n\x14\x41PPLY_STATUS_PENDING\x10\x01\x12\x19\n\x15\x41PPLY_STATUS_VERIFIED\x10\x02\x12\x18\n\x14\x41PPLY_STATUS_REMOVED\x10\x03\x12\x18\n\x14\x41PPLY_STATUS_EXPIRED\x10\x04\x12\x17\n\x13\x41PPLY_STATUS_FAILED\x10\x05*\xa4\x04\n\x07Outcome\x12\x17\n\x13OUTCOME_UNSPECIFIED\x10\x00\x12\x14\n\x10OUTCOME_BASELINE\x10\x01\x12 \n\x1cOUTCOME_AWAITING_APPLICATION\x10\x02\x12\x1d\n\x19OUTCOME_AWAITING_EXPOSURE\x10\x03\x12!\n\x1dOUTCOME_INSUFFICIENT_DURATION\x10\x04\x12\x1d\n\x19OUTCOME_INSUFFICIENT_DATA\x10\x05\x12!\n\x1dOUTCOME_INSUFFICIENT_CONTROLS\x10\x06\x12\x11\n\rOUTCOME_STALE\x10\x07\x12\x17\n\x13OUTCOME_UNAVAILABLE\x10\x08\x12\x18\n\x14OUTCOME_INCONCLUSIVE\x10\t\x12\x17\n\x13OUTCOME_SAFETY_ONLY\x10\n\x12\x18\n\x14OUTCOME_FINAL_SAFETY\x10\x0b\x12\x16\n\x12OUTCOME_BENEFICIAL\x10\x0c\x12\x13\n\x0fOUTCOME_HARMFUL\x10\r\x12\x13\n\x0fOUTCOME_EXPIRED\x10\x0e\x12\x1b\n\x17OUTCOME_STABLE_OBSERVED\x10\x0f\x12&\n\"OUTCOME_STABLE_EVIDENCE_INCOMPLETE\x10\x10\x12-\n)OUTCOME_STABLE_REGRESSION_REVIEW_REQUIRED\x10\x11\x12\x15\n\x11OUTCOME_OBSERVING\x10\x12*\x95\x02\n\x10\x45xperimentStatus\x12!\n\x1d\x45XPERIMENT_STATUS_UNSPECIFIED\x10\x00\x12\x1e\n\x1a\x45XPERIMENT_STATUS_PROPOSED\x10\x01\x12\x1d\n\x19\x45XPERIMENT_STATUS_RUNNING\x10\x02\x12\x1c\n\x18\x45XPERIMENT_STATUS_PAUSED\x10\x03\x12\x1f\n\x1b\x45XPERIMENT_STATUS_SUSPENDED\x10\x04\x12\x1e\n\x1a\x45XPERIMENT_STATUS_COMPLETE\x10\x05\x12\x1d\n\x19\x45XPERIMENT_STATUS_RETIRED\x10\x06\x12!\n\x1d\x45XPERIMENT_STATUS_ROLLED_BACK\x10\x07:M\n\x04role\x12\x1e.google.protobuf.MethodOptions\x18\xba\x8e\x03 \x01(\x0e\x32\x17.xtcp_treatment.v1.RoleR\x04roleB\xc0\x01\n\x15\x63om.xtcp_treatment.v1B\x0eTreatmentProtoP\x01Z6github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment\xa2\x02\x03XXX\xaa\x02\x10XtcpTreatment.V1\xca\x02\x10XtcpTreatment\\V1\xe2\x02\x1cXtcpTreatment\\V1\\GPBMetadata\xea\x02\x11XtcpTreatment::V1b\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!xtcp_treatment/v1/treatment.proto\x12\x11xtcp_treatment.v1\x1a\x1b\x62uf/validate/validate.proto\x1a google/protobuf/descriptor.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\x8d\x07\n\x0bTCPSettings\x12Y\n\x12\x63ongestion_control\x18\x01 \x01(\tB%\xbaH\"r \x18\x0f\x32\x1c^[a-zA-Z0-9][a-zA-Z0-9_.-]*$H\x00R\x11\x63ongestionControl\x88\x01\x01\x12\'\n\x0f\x63ongestion_lock\x18\x02 \x01(\x08R\x0e\x63ongestionLock\x12.\n\nrto_min_ms\x18\x03 \x01(\rB\x0b\xbaH\x08*\x06\x18\xc0\xa9\x07(\x01H\x01R\x08rtoMinMs\x88\x01\x01\x12K\n\x19initial_congestion_window\x18\x04 \x01(\rB\n\xbaH\x07*\x05\x18\x80\x08(\x01H\x02R\x17initialCongestionWindow\x88\x01\x01\x12\x45\n\x16initial_receive_window\x18\x05 \x01(\rB\n\xbaH\x07*\x05\x18\x80\x08(\x01H\x03R\x14initialReceiveWindow\x88\x01\x01\x12 \n\tquick_ack\x18\x06 \x01(\x08H\x04R\x08quickAck\x88\x01\x01\x12\x15\n\x03\x65\x63n\x18\x07 \x01(\x08H\x05R\x03\x65\x63n\x88\x01\x01:\x87\x03\xbaH\x83\x03\x1a\xf3\x01\n\x16tcp_settings.non_empty\x12/a treatment must set at least one TCP parameter\x1a\xa7\x01has(this.congestion_control) || has(this.rto_min_ms) || has(this.initial_congestion_window) || has(this.initial_receive_window) || has(this.quick_ack) || has(this.ecn)\x1a\x8a\x01\n$tcp_settings.lock_requires_algorithm\x12+congestion_lock requires congestion_control\x1a\x35!this.congestion_lock || has(this.congestion_control)B\x15\n\x13_congestion_controlB\r\n\x0b_rto_min_msB\x1c\n\x1a_initial_congestion_windowB\x19\n\x17_initial_receive_windowB\x0c\n\n_quick_ackB\x06\n\x04_ecn\"\xd8\x03\n\tTreatment\x12\x39\n\x02id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x02id\x12#\n\x08revision\x18\x02 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\x08revision\x12,\n\x0b\x64\x65stination\x18\x03 \x01(\tB\n\xbaH\x07r\x05\x18\x31\xe8\x01\x01R\x0b\x64\x65stination\x12\x42\n\x08settings\x18\x04 \x01(\x0b\x32\x1e.xtcp_treatment.v1.TCPSettingsB\x06\xbaH\x03\xc8\x01\x01R\x08settings\x12.\n\x13\x65\x63n_fabric_verified\x18\x05 \x01(\x08R\x11\x65\x63nFabricVerified:\xc8\x01\xbaH\xc4\x01\x1a\xc1\x01\n\x1ctreatment.dctcp_requires_ecn\x12\x41\x44\x43TCP requires independently verified ECN fabric and explicit ECN\x1a^this.settings.congestion_control != \'dctcp\' || (this.ecn_fabric_verified && this.settings.ecn)\"\xcc\x05\n\nAssignment\x12N\n\rexperiment_id\x18\x02 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x0c\x65xperimentId\x12\x42\n\ttreatment\x18\x03 \x01(\x0b\x32\x1c.xtcp_treatment.v1.TreatmentB\x06\xbaH\x03\xc8\x01\x01R\ttreatment\x12\x41\n\x04mode\x18\x04 \x01(\x0e\x32!.xtcp_treatment.v1.AssignmentModeB\n\xbaH\x07\x82\x01\x04\x10\x01 \x00R\x04mode\x12\x44\n\x10lease_expires_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0eleaseExpiresAt\x12>\n\x0bpredecessor\x18\x06 \x01(\x0b\x32\x1c.xtcp_treatment.v1.TreatmentR\x0bpredecessor\x12\'\n\ngeneration\x18\x07 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\ngeneration:\xad\x02\xbaH\xa9\x02\x1a}\n\x1f\x61ssignment.trial_requires_lease\x12,a trial assignment requires lease_expires_at\x1a,this.mode != 1 || has(this.lease_expires_at)\x1a\xa7\x01\n\"assignment.predecessor_destination\x12+predecessor must cover the same destination\x1aT!has(this.predecessor) || this.predecessor.destination == this.treatment.destinationJ\x04\x08\x01\x10\x02R\x02id\"\xa4\x02\n\x0c\x44\x65siredState\x12\x44\n\x08\x61gent_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x07\x61gentId\x12#\n\x08revision\x18\x02 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\x08revision\x12\x1c\n\tsuspended\x18\x03 \x01(\x08R\tsuspended\x12J\n\x0b\x61ssignments\x18\x04 \x03(\x0b\x32\x1d.xtcp_treatment.v1.AssignmentB\t\xbaH\x06\x92\x01\x03\x10\x80 R\x0b\x61ssignments\x12?\n\tissued_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\x08issuedAt\"\xa3\x02\n\x0c\x43\x61pabilities\x12\x42\n\x07\x62oot_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x06\x62ootId\x12/\n\x0ekernel_release\x18\x02 \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x02R\rkernelRelease\x12\x43\n\x15\x63ongestion_algorithms\x18\x03 \x03(\tB\x0e\xbaH\x0b\x92\x01\x08\x10@\"\x04r\x02\x18\x0fR\x14\x63ongestionAlgorithms\x12.\n\x13simple_host_routing\x18\x04 \x01(\x08R\x11simpleHostRouting\x12)\n\x10\x61\x63tuator_enabled\x18\x05 \x01(\x08R\x0f\x61\x63tuatorEnabled\"\xa8\x05\n\x11\x41pplicationRecord\x12\x45\n\nassignment\x18\x01 \x01(\x0b\x32\x1d.xtcp_treatment.v1.AssignmentB\x06\xbaH\x03\xc8\x01\x01R\nassignment\x12)\n\x10\x64\x65sired_revision\x18\x02 \x01(\x04R\x0f\x64\x65siredRevision\x12@\n\x06status\x18\x03 \x01(\x0e\x32\x1e.xtcp_treatment.v1.ApplyStatusB\x08\xbaH\x05\x82\x01\x02\x10\x01R\x06status\x12;\n\x0breceived_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\nreceivedAt\x12=\n\x0c\x61ttempted_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0b\x61ttemptedAt\x12\x39\n\napplied_at\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tappliedAt\x12;\n\x0bverified_at\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\nverifiedAt\x12\x39\n\nremoved_at\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tremovedAt\x12\x1e\n\x05\x65rror\x18\t \x01(\tB\x08\xbaH\x05r\x03\x18\x80 R\x05\x65rror\x12\x31\n\x0finstalled_route\x18\n \x01(\x0c\x42\x08\xbaH\x05z\x03\x18\x80 R\x0einstalledRoute\x12\x34\n\x11installed_boot_id\x18\x0b \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x01R\x0finstalledBootId\x12\'\n\x0f\x65xpired_latched\x18\x0c \x01(\x08R\x0e\x65xpiredLatched\"\xb1\x02\n\nLocalState\x12\x39\n\x07\x64\x65sired\x18\x01 \x01(\x0b\x32\x1f.xtcp_treatment.v1.DesiredStateR\x07\x64\x65sired\x12+\n\x11locally_suspended\x18\x02 \x01(\x08R\x10locallySuspended\x12\x35\n\x11suspension_reason\x18\x03 \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x08R\x10suspensionReason\x12I\n\x07records\x18\x04 \x03(\x0b\x32$.xtcp_treatment.v1.ApplicationRecordB\t\xbaH\x06\x92\x01\x03\x10\x80@R\x07records\x12\x39\n\nupdated_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tupdatedAt\"\x89\x04\n\x0cHostEvidence\x12\x44\n\x08\x61gent_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x07\x61gentId\x12\x18\n\x07sockets\x18\x02 \x01(\x04R\x07sockets\x12\x1f\n\x0b\x62ytes_acked\x18\x03 \x01(\x04R\nbytesAcked\x12\x31\n\x0bgoodput_bps\x18\x04 \x01(\x01\x42\x10\xbaH\r\x12\x0b)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\ngoodputBps\x12\x43\n\x14retransmission_ratio\x18\x05 \x01(\x01\x42\x10\xbaH\r\x12\x0b)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x13retransmissionRatio\x12.\n\nrtt_p95_ms\x18\x06 \x01(\x01\x42\x10\xbaH\r\x12\x0b)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x08rttP95Ms\x12\'\n\x0f\x65xposed_sockets\x18\x07 \x01(\x04R\x0e\x65xposedSockets\x12-\n\x12\x63ollection_healthy\x18\x08 \x01(\x08R\x11\x63ollectionHealthy:x\xbaHu\x1as\n$host_evidence.exposed_within_sockets\x12%exposed_sockets cannot exceed sockets\x1a$this.exposed_sockets <= this.sockets\"\xbc\x05\n\x08\x45vidence\x12\x39\n\x02id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x02id\x12\x43\n\x0b\x61nalyzed_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\nanalyzedAt\x12\x45\n\x0cwindow_start\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\x0bwindowStart\x12\x41\n\nwindow_end\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\twindowEnd\x12/\n\rquery_version\x18\x05 \x01(\tB\n\xbaH\x07r\x05\x10\x01\x18\x80\x02R\x0cqueryVersion\x12\x37\n\x12\x65nrichment_version\x18\x06 \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x02R\x11\x65nrichmentVersion\x12\x42\n\x05hosts\x18\x07 \x03(\x0b\x32\x1f.xtcp_treatment.v1.HostEvidenceB\x0b\xbaH\x08\x92\x01\x05\x08\x01\x10\x90NR\x05hosts\x12>\n\x0f\x61rtifact_sha256\x18\x08 \x01(\tB\x15\xbaH\x12r\x10\x32\x0e^[0-9a-f]{64}$R\x0e\x61rtifactSha256:\xb7\x01\xbaH\xb3\x01\x1a\xb0\x01\n\x11\x65vidence.interval\x12Owindow_end must follow window_start and analyzed_at must not precede window_end\x1aJthis.window_end > this.window_start && this.analyzed_at >= this.window_end\"\xec\x06\n\x10\x45xperimentPolicy\x12]\n\x13minimum_observation\x18\x01 \x01(\x0b\x32\x19.google.protobuf.DurationB\x11\xbaH\x0e\xaa\x01\x08\"\x04\x08\x80\xf5$*\x00\xc8\x01\x01R\x12minimumObservation\x12Q\n\rmaximum_phase\x18\x02 \x01(\x0b\x32\x19.google.protobuf.DurationB\x11\xbaH\x0e\xaa\x01\x08\"\x04\x08\x80\xf5$*\x00\xc8\x01\x01R\x0cmaximumPhase\x12@\n\x18minimum_sockets_per_host\x18\x03 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\x15minimumSocketsPerHost\x12<\n\x16minimum_bytes_per_host\x18\x04 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\x13minimumBytesPerHost\x12>\n\x15minimum_treated_hosts\x18\x05 \x01(\rB\n\xbaH\x07*\x05\x18\x90N(\x02R\x13minimumTreatedHosts\x12J\n\x13minimum_improvement\x18\x06 \x01(\x01\x42\x19\xbaH\x16\x12\x14\x19\x00\x00\x00\x00\x00\x00\xf0?)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x12minimumImprovement\x12\x61\n\x1fmaximum_retransmission_increase\x18\x07 \x01(\x01\x42\x19\xbaH\x16\x12\x14\x19\x00\x00\x00\x00\x00\x00\xf0?)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x1dmaximumRetransmissionIncrease\x12K\n\x14maximum_rtt_increase\x18\x08 \x01(\x01\x42\x19\xbaH\x16\x12\x14\x19\x00\x00\x00\x00\x00\x00\xf0?)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x12maximumRttIncrease\x12^\n\x14maximum_evidence_age\x18\t \x01(\x0b\x32\x19.google.protobuf.DurationB\x11\xbaH\x0e\xaa\x01\x08\"\x04\x08\x80\xf5$*\x00\xc8\x01\x01R\x12maximumEvidenceAge:\x89\x01\xbaH\x85\x01\x1a\x82\x01\n\x1fpolicy.observation_within_phase\x12/minimum_observation cannot exceed maximum_phase\x1a.this.minimum_observation <= this.maximum_phase\"\xc1\x03\n\x05Phase\x12-\n\x0ctarget_hosts\x18\x01 \x01(\rB\n\xbaH\x07*\x05\x18\x90N(\x01R\x0btargetHosts\x12\x37\n\tissued_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x08issuedAt\x12P\n\x16observation_started_at\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x14observationStartedAt\x12=\n\x0c\x65valuated_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0b\x65valuatedAt\x12\x37\n\x08\x65vidence\x18\x05 \x01(\x0b\x32\x1b.xtcp_treatment.v1.EvidenceR\x08\x65vidence\x12>\n\x07outcome\x18\x06 \x01(\x0e\x32\x1a.xtcp_treatment.v1.OutcomeB\x08\xbaH\x05\x82\x01\x02\x10\x01R\x07outcome\x12$\n\x08reviewer\x18\x07 \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x01R\x08reviewer\x12 \n\x06reason\x18\x08 \x01(\tB\x08\xbaH\x05r\x03\x18\x80 R\x06reason\"\x88\x07\n\nExperiment\x12\x39\n\x02id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x02id\x12\x45\n\x08location\x18\x02 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x08location\x12*\n\nhypothesis\x18\x03 \x01(\tB\n\xbaH\x07r\x05\x10\x01\x18\x80@R\nhypothesis\x12\x42\n\ttreatment\x18\x04 \x01(\x0b\x32\x1c.xtcp_treatment.v1.TreatmentB\x06\xbaH\x03\xc8\x01\x01R\ttreatment\x12\x43\n\x06policy\x18\x05 \x01(\x0b\x32#.xtcp_treatment.v1.ExperimentPolicyB\x06\xbaH\x03\xc8\x01\x01R\x06policy\x12?\n\x08\x62\x61seline\x18\x06 \x01(\x0b\x32\x1b.xtcp_treatment.v1.EvidenceB\x06\xbaH\x03\xc8\x01\x01R\x08\x62\x61seline\x12X\n\x0c\x63ohort_order\x18\x07 \x03(\tB5\xbaH2\x92\x01/\x08\x03\x10\x90N\x18\x01\"&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x0b\x63ohortOrder\x12:\n\x06phases\x18\x08 \x03(\x0b\x32\x18.xtcp_treatment.v1.PhaseB\x08\xbaH\x05\x92\x01\x02\x10@R\x06phases\x12\x45\n\x06status\x18\t \x01(\x0e\x32#.xtcp_treatment.v1.ExperimentStatusB\x08\xbaH\x05\x82\x01\x02\x10\x01R\x06status\x12\x1a\n\x08revision\x18\n \x01(\x04R\x08revision\x12;\n\x0bproposed_at\x18\x0b \x01(\x0b\x32\x1a.google.protobuf.TimestampR\nproposedAt\x12\x44\n\x0flatest_evidence\x18\x0c \x01(\x0b\x32\x1b.xtcp_treatment.v1.EvidenceR\x0elatestEvidence\x12K\n\x0elatest_outcome\x18\r \x01(\x0e\x32\x1a.xtcp_treatment.v1.OutcomeB\x08\xbaH\x05\x82\x01\x02\x10\x01R\rlatestOutcome\x12\x39\n\nupdated_at\x18\x0e \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tupdatedAt*?\n\x04Role\x12\x14\n\x10ROLE_UNSPECIFIED\x10\x00\x12\x0e\n\nROLE_AGENT\x10\x01\x12\x11\n\rROLE_OPERATOR\x10\x02*\xa4\x01\n\x0e\x41ssignmentMode\x12\x1f\n\x1b\x41SSIGNMENT_MODE_UNSPECIFIED\x10\x00\x12\x19\n\x15\x41SSIGNMENT_MODE_TRIAL\x10\x01\x12\x1a\n\x16\x41SSIGNMENT_MODE_STABLE\x10\x02\x12\x1d\n\x19\x41SSIGNMENT_MODE_SUSPENDED\x10\x03\x12\x1b\n\x17\x41SSIGNMENT_MODE_RETIRED\x10\x04*\xad\x01\n\x0b\x41pplyStatus\x12\x1c\n\x18\x41PPLY_STATUS_UNSPECIFIED\x10\x00\x12\x18\n\x14\x41PPLY_STATUS_PENDING\x10\x01\x12\x19\n\x15\x41PPLY_STATUS_VERIFIED\x10\x02\x12\x18\n\x14\x41PPLY_STATUS_REMOVED\x10\x03\x12\x18\n\x14\x41PPLY_STATUS_EXPIRED\x10\x04\x12\x17\n\x13\x41PPLY_STATUS_FAILED\x10\x05*\xa4\x04\n\x07Outcome\x12\x17\n\x13OUTCOME_UNSPECIFIED\x10\x00\x12\x14\n\x10OUTCOME_BASELINE\x10\x01\x12 \n\x1cOUTCOME_AWAITING_APPLICATION\x10\x02\x12\x1d\n\x19OUTCOME_AWAITING_EXPOSURE\x10\x03\x12!\n\x1dOUTCOME_INSUFFICIENT_DURATION\x10\x04\x12\x1d\n\x19OUTCOME_INSUFFICIENT_DATA\x10\x05\x12!\n\x1dOUTCOME_INSUFFICIENT_CONTROLS\x10\x06\x12\x11\n\rOUTCOME_STALE\x10\x07\x12\x17\n\x13OUTCOME_UNAVAILABLE\x10\x08\x12\x18\n\x14OUTCOME_INCONCLUSIVE\x10\t\x12\x17\n\x13OUTCOME_SAFETY_ONLY\x10\n\x12\x18\n\x14OUTCOME_FINAL_SAFETY\x10\x0b\x12\x16\n\x12OUTCOME_BENEFICIAL\x10\x0c\x12\x13\n\x0fOUTCOME_HARMFUL\x10\r\x12\x13\n\x0fOUTCOME_EXPIRED\x10\x0e\x12\x1b\n\x17OUTCOME_STABLE_OBSERVED\x10\x0f\x12&\n\"OUTCOME_STABLE_EVIDENCE_INCOMPLETE\x10\x10\x12-\n)OUTCOME_STABLE_REGRESSION_REVIEW_REQUIRED\x10\x11\x12\x15\n\x11OUTCOME_OBSERVING\x10\x12*\x95\x02\n\x10\x45xperimentStatus\x12!\n\x1d\x45XPERIMENT_STATUS_UNSPECIFIED\x10\x00\x12\x1e\n\x1a\x45XPERIMENT_STATUS_PROPOSED\x10\x01\x12\x1d\n\x19\x45XPERIMENT_STATUS_RUNNING\x10\x02\x12\x1c\n\x18\x45XPERIMENT_STATUS_PAUSED\x10\x03\x12\x1f\n\x1b\x45XPERIMENT_STATUS_SUSPENDED\x10\x04\x12\x1e\n\x1a\x45XPERIMENT_STATUS_COMPLETE\x10\x05\x12\x1d\n\x19\x45XPERIMENT_STATUS_RETIRED\x10\x06\x12!\n\x1d\x45XPERIMENT_STATUS_ROLLED_BACK\x10\x07:M\n\x04role\x12\x1e.google.protobuf.MethodOptions\x18\xba\x8e\x03 \x01(\x0e\x32\x17.xtcp_treatment.v1.RoleR\x04roleB\xc0\x01\n\x15\x63om.xtcp_treatment.v1B\x0eTreatmentProtoP\x01Z6github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment\xa2\x02\x03XXX\xaa\x02\x10XtcpTreatment.V1\xca\x02\x10XtcpTreatment\\V1\xe2\x02\x1cXtcpTreatment\\V1\\GPBMetadata\xea\x02\x11XtcpTreatment::V1b\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -170,16 +170,16 @@ _globals['_EXPERIMENT'].fields_by_name['status']._serialized_options = b'\272H\005\202\001\002\020\001' _globals['_EXPERIMENT'].fields_by_name['latest_outcome']._loaded_options = None _globals['_EXPERIMENT'].fields_by_name['latest_outcome']._serialized_options = b'\272H\005\202\001\002\020\001' - _globals['_ROLE']._serialized_start=7276 - _globals['_ROLE']._serialized_end=7339 - _globals['_ASSIGNMENTMODE']._serialized_start=7342 - _globals['_ASSIGNMENTMODE']._serialized_end=7506 - _globals['_APPLYSTATUS']._serialized_start=7509 - _globals['_APPLYSTATUS']._serialized_end=7682 - _globals['_OUTCOME']._serialized_start=7685 - _globals['_OUTCOME']._serialized_end=8233 - _globals['_EXPERIMENTSTATUS']._serialized_start=8236 - _globals['_EXPERIMENTSTATUS']._serialized_end=8513 + _globals['_ROLE']._serialized_start=7335 + _globals['_ROLE']._serialized_end=7398 + _globals['_ASSIGNMENTMODE']._serialized_start=7401 + _globals['_ASSIGNMENTMODE']._serialized_end=7565 + _globals['_APPLYSTATUS']._serialized_start=7568 + _globals['_APPLYSTATUS']._serialized_end=7741 + _globals['_OUTCOME']._serialized_start=7744 + _globals['_OUTCOME']._serialized_end=8292 + _globals['_EXPERIMENTSTATUS']._serialized_start=8295 + _globals['_EXPERIMENTSTATUS']._serialized_end=8572 _globals['_TCPSETTINGS']._serialized_start=185 _globals['_TCPSETTINGS']._serialized_end=1094 _globals['_TREATMENT']._serialized_start=1097 @@ -203,5 +203,5 @@ _globals['_PHASE']._serialized_start=5977 _globals['_PHASE']._serialized_end=6426 _globals['_EXPERIMENT']._serialized_start=6429 - _globals['_EXPERIMENT']._serialized_end=7274 + _globals['_EXPERIMENT']._serialized_end=7333 # @@protoc_insertion_point(module_scope) diff --git a/gen/python/xtcp_treatment/v1/treatment_pb2.pyi b/gen/python/xtcp_treatment/v1/treatment_pb2.pyi index 012838723..b67c4d6e5 100644 --- a/gen/python/xtcp_treatment/v1/treatment_pb2.pyi +++ b/gen/python/xtcp_treatment/v1/treatment_pb2.pyi @@ -313,7 +313,7 @@ class Phase(_message.Message): def __init__(self, target_hosts: _Optional[int] = ..., issued_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., observation_started_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., evaluated_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., evidence: _Optional[_Union[Evidence, _Mapping]] = ..., outcome: _Optional[_Union[Outcome, str]] = ..., reviewer: _Optional[str] = ..., reason: _Optional[str] = ...) -> None: ... class Experiment(_message.Message): - __slots__ = ("id", "location", "hypothesis", "treatment", "policy", "baseline", "cohort_order", "phases", "status", "revision", "proposed_at", "latest_evidence", "latest_outcome") + __slots__ = ("id", "location", "hypothesis", "treatment", "policy", "baseline", "cohort_order", "phases", "status", "revision", "proposed_at", "latest_evidence", "latest_outcome", "updated_at") ID_FIELD_NUMBER: _ClassVar[int] LOCATION_FIELD_NUMBER: _ClassVar[int] HYPOTHESIS_FIELD_NUMBER: _ClassVar[int] @@ -327,6 +327,7 @@ class Experiment(_message.Message): PROPOSED_AT_FIELD_NUMBER: _ClassVar[int] LATEST_EVIDENCE_FIELD_NUMBER: _ClassVar[int] LATEST_OUTCOME_FIELD_NUMBER: _ClassVar[int] + UPDATED_AT_FIELD_NUMBER: _ClassVar[int] id: str location: str hypothesis: str @@ -340,4 +341,5 @@ class Experiment(_message.Message): proposed_at: _timestamp_pb2.Timestamp latest_evidence: Evidence latest_outcome: Outcome - def __init__(self, id: _Optional[str] = ..., location: _Optional[str] = ..., hypothesis: _Optional[str] = ..., treatment: _Optional[_Union[Treatment, _Mapping]] = ..., policy: _Optional[_Union[ExperimentPolicy, _Mapping]] = ..., baseline: _Optional[_Union[Evidence, _Mapping]] = ..., cohort_order: _Optional[_Iterable[str]] = ..., phases: _Optional[_Iterable[_Union[Phase, _Mapping]]] = ..., status: _Optional[_Union[ExperimentStatus, str]] = ..., revision: _Optional[int] = ..., proposed_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., latest_evidence: _Optional[_Union[Evidence, _Mapping]] = ..., latest_outcome: _Optional[_Union[Outcome, str]] = ...) -> None: ... + updated_at: _timestamp_pb2.Timestamp + def __init__(self, id: _Optional[str] = ..., location: _Optional[str] = ..., hypothesis: _Optional[str] = ..., treatment: _Optional[_Union[Treatment, _Mapping]] = ..., policy: _Optional[_Union[ExperimentPolicy, _Mapping]] = ..., baseline: _Optional[_Union[Evidence, _Mapping]] = ..., cohort_order: _Optional[_Iterable[str]] = ..., phases: _Optional[_Iterable[_Union[Phase, _Mapping]]] = ..., status: _Optional[_Union[ExperimentStatus, str]] = ..., revision: _Optional[int] = ..., proposed_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., latest_evidence: _Optional[_Union[Evidence, _Mapping]] = ..., latest_outcome: _Optional[_Union[Outcome, str]] = ..., updated_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ... diff --git a/go.mod b/go.mod index eda8dee2b..d97091476 100644 --- a/go.mod +++ b/go.mod @@ -8,8 +8,8 @@ require ( github.com/gaissmai/bart v0.30.0 github.com/grafana/pyroscope-go v1.4.3 github.com/grpc-ecosystem/grpc-gateway/v2 v2.32.0 + github.com/jackc/pgx/v5 v5.11.0 github.com/klauspost/compress v1.20.3 - github.com/lib/pq v1.12.3 github.com/minio/minio-go/v7 v7.4.1 github.com/nats-io/nats.go v1.54.0 github.com/nsqio/go-nsq v1.1.0 @@ -55,6 +55,9 @@ require ( github.com/google/pprof v0.0.0-20261016150608-1facf8f91b50 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grafana/pyroscope-go/godeltaprof v0.1.12 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/klauspost/cpuid/v2 v2.4.1 // indirect github.com/klauspost/crc32 v1.3.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect @@ -84,6 +87,7 @@ require ( golang.org/x/crypto v0.58.0 // indirect golang.org/x/exp v0.0.0-20261014143641-f0251eb4aa3e // indirect golang.org/x/net v0.61.0 // indirect + golang.org/x/sync v0.24.0 // indirect golang.org/x/text v0.43.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20261019180522-ce908d29299b // indirect gopkg.in/ini.v1 v1.67.3 // indirect diff --git a/go.sum b/go.sum index e4deeb083..f71397a2b 100644 --- a/go.sum +++ b/go.sum @@ -77,6 +77,14 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.11.0 h1:IzBBtyK9AHqf98cctWFifYSci2hgQR/cd56wB4p+ogg= +github.com/jackc/pgx/v5 v5.11.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/klauspost/compress v1.20.3 h1:zWNoFqq/lSuYQRim3dUXlXeA0IqwuZGicdG5ZTfY5j4= github.com/klauspost/compress v1.20.3/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= @@ -88,8 +96,6 @@ github.com/klauspost/crc32 v1.3.0/go.mod h1:D7kQaZhnkX/Y0tstFGf8VUzv2UofNGqCjnC3 github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= -github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ= -github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI= github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg= @@ -145,6 +151,8 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= @@ -213,6 +221,8 @@ golang.org/x/exp v0.0.0-20261014143641-f0251eb4aa3e h1:HgI6W1OolKK6tcV74is+rl9SM golang.org/x/exp v0.0.0-20261014143641-f0251eb4aa3e/go.mod h1:DBoxwWhjaW8DJJw/YeM4zpeOKMC1FMXSblsYy+1mFSw= golang.org/x/net v0.61.0 h1:RR+6j/BTBCrggOLugxa+66R8FvoN6MHlzDlZIbN9YFw= golang.org/x/net v0.61.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI= +golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA= +golang.org/x/sync v0.24.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/nix/versions.nix b/nix/versions.nix index 26472f17f..a332ab7f1 100644 --- a/nix/versions.nix +++ b/nix/versions.nix @@ -171,5 +171,5 @@ # Go vendor hash. Update by running `nix build .#xtcp2` and pasting the # `got:` value from the hash mismatch error. Used by every Nix check that # needs deps in the sandbox (see nix/lib/goModules.nix). - goVendorHash = "sha256-zWkXGRXmqKiugOVjzZoG1g1FVrCNXSZwaiSMultjTGI="; + goVendorHash = "sha256-23MHm3oQ4qgsIcCTKyYVBst7NuSZqBGGrbTSA3jwlf8="; } diff --git a/pkg/treatment/agent/backend_linux.go b/pkg/treatment/agent/backend_linux.go index 9b4fa704e..abb295ef3 100644 --- a/pkg/treatment/agent/backend_linux.go +++ b/pkg/treatment/agent/backend_linux.go @@ -4,13 +4,13 @@ import ( "context" pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/internal/goip" - "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/wire" ) type routeBackend struct{ *goip.TreatmentBackend } func (b *routeBackend) Plan(ctx context.Context, t *pb.Treatment) ([]byte, error) { - if e := treatment.ValidateTreatment(t); e != nil { + if e := wire.ValidateTreatment(t); e != nil { return nil, e } s := t.Settings diff --git a/pkg/treatment/boundaries_test.go b/pkg/treatment/boundaries_test.go index 6275ca316..cf0c44925 100644 --- a/pkg/treatment/boundaries_test.go +++ b/pkg/treatment/boundaries_test.go @@ -16,12 +16,14 @@ import ( // parser (no toolchain, no module cache, works outside nix exactly like // internal/goip/imports_test.go) and asserts the layering below. // -// pkg/treatment contract: IDs, limits, validators [everyone] -// pkg/treatment/identity SPIFFE identity + TLS [agent, controller, tools] -// pkg/treatment/reconcile journal + reconciler + Backend [agent, watchdog] -// pkg/treatment/experiment statistics + state machine [controller] -// pkg/treatment/agent check-in loop + stamping [daemon, agent binary] -// pkg/treatment/controller gRPC handlers, store, evidence [controller binary only] +// pkg/treatment domain types, limits, validators: proto-free [everyone] +// pkg/treatment/wire domain <-> generated proto conversion [gRPC boundary] +// pkg/treatment/identity SPIFFE identity + TLS [agent, controller, tools] +// pkg/treatment/reconcile journal + reconciler + Backend [agent, watchdog] +// pkg/treatment/experiment statistics + state machine, pure [controller] +// pkg/treatment/agent check-in loop + stamping [daemon, agent binary] +// pkg/treatment/controller gRPC handlers over store ports [controller binary only] +// pkg/treatment/controller/store ports; postgres/ and memory/ implement them [controller] // // The daemon (cmd/xtcp2) must never reach the controller, a SQL driver or the // ClickHouse client: that was the regression that prompted this file. @@ -48,37 +50,43 @@ func treatmentBoundaryRules() []boundaryRule { description: "positive: the daemon links neither the controller, a SQL driver nor ClickHouse", root: tp("cmd/xtcp2"), forbidden: []string{tp("pkg/treatment/controller"), tp("pkg/treatment/experiment")}, - forbidden3p: []string{"database/sql", "lib/pq", "jackc/pgx", "clickhouse"}, + forbidden3p: []string{"database/sql", "jackc/pgx", "clickhouse"}, expectAny: []string{tp("pkg/xtcp")}, }, { description: "positive: the agent library stands on identity and the contract, never on the controller", root: tp("pkg/treatment/agent"), forbidden: []string{tp("pkg/treatment/controller"), tp("pkg/treatment/experiment")}, - forbidden3p: []string{"database/sql", "lib/pq", "jackc/pgx"}, + forbidden3p: []string{"database/sql", "jackc/pgx"}, expectAny: []string{tp("pkg/treatment/identity"), tp("pkg/treatment")}, }, { - // TODO(item 2): add gen/go/xtcp_treatment to forbidden once the - // contract package stops importing the proto (domain types land). - description: "negative: the identity package is a leaf below the contract and gRPC credentials", + description: "positive: the domain package is proto-free and depends on nothing first-party", + root: tp("pkg/treatment"), + forbidden: []string{tp("gen/go/xtcp_treatment"), tp("pkg/treatment/wire"), tp("pkg/treatment/identity"), tp("pkg/treatment/controller")}, + forbidden3p: []string{"google.golang.org/protobuf", "google.golang.org/grpc", "database/sql"}, + expectAny: []string{tp("pkg/treatment")}, + }, + { + description: "negative: the identity package is a leaf below the domain and gRPC credentials, never the proto", root: tp("pkg/treatment/identity"), - forbidden: []string{tp("pkg/treatment/controller"), tp("pkg/treatment/agent")}, + forbidden: []string{tp("gen/go/xtcp_treatment"), tp("pkg/treatment/wire"), tp("pkg/treatment/controller"), tp("pkg/treatment/agent")}, forbidden3p: []string{"database/sql"}, expectAny: []string{tp("pkg/treatment")}, }, { - // TODO(item 2): forbid pkg/treatment/identity and google.golang.org/grpc + // TODO(dual-mode agent): forbid pkg/treatment/identity and google.golang.org/grpc // once the watchdog builds on pkg/treatment/reconcile instead of agent. description: "boundary: the watchdog never reaches the controller or a database", root: tp("cmd/xtcp2-treatmentctl"), forbidden: []string{tp("pkg/treatment/controller")}, - forbidden3p: []string{"database/sql", "lib/pq", "jackc/pgx"}, + forbidden3p: []string{"database/sql", "jackc/pgx"}, expectAny: []string{tp("pkg/treatment")}, }, { - // TODO(item 4): forbid google.golang.org/grpc once domain types - // replace the generated proto package, which bundles the stubs. + // TODO(dual-mode agent): forbid gen/go/xtcp_treatment and + // google.golang.org/grpc once the journal is a journal-specific + // message converted from domain types. description: "positive: the reconcile package is host-side only: no identity, no experiment statistics", root: tp("pkg/treatment/reconcile"), forbidden: []string{tp("pkg/treatment/identity"), tp("pkg/treatment/experiment"), tp("pkg/treatment/agent"), tp("pkg/treatment/controller")}, @@ -86,14 +94,25 @@ func treatmentBoundaryRules() []boundaryRule { expectAny: []string{tp("pkg/treatment")}, }, { - // TODO(item 4): forbid google.golang.org/grpc once domain types - // replace the generated proto package, which bundles the stubs. - description: "positive: the experiment package is pure: no journal, no identity, no syscalls", + description: "positive: the experiment package is pure: no proto, no gRPC, no journal, no identity, no syscalls", root: tp("pkg/treatment/experiment"), - forbidden: []string{tp("pkg/treatment/reconcile"), tp("pkg/treatment/identity"), tp("pkg/treatment/agent"), tp("pkg/treatment/controller"), tp("internal/goip")}, - forbidden3p: []string{"database/sql", "golang.org/x/sys"}, + forbidden: []string{tp("gen/go/xtcp_treatment"), tp("pkg/treatment/wire"), tp("pkg/treatment/reconcile"), tp("pkg/treatment/identity"), tp("pkg/treatment/agent"), tp("pkg/treatment/controller")}, + forbidden3p: []string{"google.golang.org/protobuf", "google.golang.org/grpc", "database/sql", "golang.org/x/sys"}, expectAny: []string{tp("pkg/treatment")}, }, + { + description: "positive: the store ports and the memory store know no driver and no proto", + root: tp("pkg/treatment/controller/store/memory"), + forbidden: []string{tp("gen/go/xtcp_treatment"), tp("pkg/treatment/wire"), tp("pkg/treatment/controller"), tp("pkg/treatment/controller/store/postgres")}, + forbidden3p: []string{"database/sql", "jackc/pgx", "google.golang.org/protobuf", "google.golang.org/grpc"}, + expectAny: []string{tp("pkg/treatment/controller/store"), tp("pkg/treatment")}, + }, + { + description: "corner: every SQL string lives in the postgres package, which never reaches the handlers", + root: tp("pkg/treatment/controller/store/postgres"), + forbidden: []string{tp("pkg/treatment/controller"), tp("pkg/treatment/wire"), tp("gen/go/xtcp_treatment"), tp("pkg/treatment/experiment")}, + expectAny: []string{tp("pkg/treatment/controller/store")}, + }, { description: "negative: the controller never reaches the host-side reconcile package or goip", root: tp("pkg/treatment/controller"), diff --git a/pkg/treatment/controller/analyze.go b/pkg/treatment/controller/analyze.go index 4528c1867..a198cf4d3 100644 --- a/pkg/treatment/controller/analyze.go +++ b/pkg/treatment/controller/analyze.go @@ -2,18 +2,16 @@ package controller import ( "context" - "database/sql" "errors" - "time" pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store" "github.com/randomizedcoder/xtcp2/pkg/treatment/experiment" "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" + "github.com/randomizedcoder/xtcp2/pkg/treatment/wire" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/timestamppb" ) // Analyze refreshes evidence for review without accepting or promoting it. @@ -30,18 +28,18 @@ func (s *Server) Analyze(ctx context.Context, r *pb.AnalyzeRequest) (*pb.Analyze if s.EvidenceSource == nil { return nil, status.Error(codes.Unavailable, "ClickHouse analysis is disabled") } - var n *pb.Experiment - e = s.Store.mutate(ctx, func(tx *sql.Tx) error { + var n *treatment.Experiment + e = s.Store.Tx(ctx, func(rp store.Repos) error { var e error - n, e = loadExperiment(ctx, tx, r.ExperimentId) + n, e = rp.Experiments.Get(ctx, r.ExperimentId) if e != nil { return e } if n.Revision != r.ExpectedRevision { return errors.New("experiment revision changed") } - if n.Status == pb.ExperimentStatus_EXPERIMENT_STATUS_RUNNING { - return s.startObservation(ctx, tx, n) + if n.Status == treatment.StatusRunning { + return s.startObservation(ctx, rp, n) } return nil }) @@ -49,41 +47,39 @@ func (s *Server) Analyze(ctx context.Context, r *pb.AnalyzeRequest) (*pb.Analyze return nil, failure(e) } input := n - if n.Status == pb.ExperimentStatus_EXPERIMENT_STATUS_COMPLETE { - input = proto.Clone(n).(*pb.Experiment) - p := input.Phases[len(input.Phases)-1] - p.IssuedAt = timestamppb.New(s.now().Add(-time.Hour)) - p.ObservationStartedAt = p.IssuedAt + if n.Status == treatment.StatusComplete { + input = s.driftInput(n) } ev, e := s.EvidenceSource.Analyze(ctx, input) if e != nil { return nil, failure(e) } - out := &pb.AnalyzeResponse{Evidence: ev, Outcome: pb.Outcome_OUTCOME_BASELINE} + outcome := treatment.OutcomeBaseline if len(n.Phases) > 0 { v, e := experiment.Evaluate(input, ev, s.now()) if e != nil { return nil, failure(e) } - out.Outcome = v.Outcome.Wire() + outcome = v.Outcome } - e = s.Store.mutate(ctx, func(tx *sql.Tx) error { - current, e := loadExperiment(ctx, tx, n.Id) + out := &pb.AnalyzeResponse{Evidence: wire.Evidence(ev), Outcome: wire.Outcome(outcome)} + e = s.Store.Tx(ctx, func(rp store.Repos) error { + current, e := rp.Experiments.Get(ctx, n.ID) if e != nil { return e } if current.Revision != r.ExpectedRevision { return errors.New("experiment changed during analysis; analyze again") } - if e = saveEvidence(ctx, tx, n.Id, ev); e != nil { + if e = saveEvidence(ctx, rp, n.ID, ev); e != nil { return e } current.LatestEvidence = ev - current.LatestOutcome = out.Outcome - if e = saveExperiment(ctx, tx, current); e != nil { + current.LatestOutcome = outcome + if e = s.save(ctx, rp, current); e != nil { return e } - return audit(ctx, tx, actor, "", "analysis", out) + return audit(ctx, rp, actor, "", "analysis", out) }) return out, failure(e) } diff --git a/pkg/treatment/controller/clickhouse.go b/pkg/treatment/controller/clickhouse.go index 157064735..85f528630 100644 --- a/pkg/treatment/controller/clickhouse.go +++ b/pkg/treatment/controller/clickhouse.go @@ -18,9 +18,7 @@ import ( "strings" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" - "google.golang.org/protobuf/types/known/timestamppb" ) //go:embed analysis.sql @@ -88,20 +86,32 @@ func (c *ClickHouse) query(ctx context.Context, query string, params map[string] return b, nil } -func (c *ClickHouse) Analyze(ctx context.Context, e *pb.Experiment) (*pb.Evidence, error) { - if len(e.Phases) == 0 { +func (c *ClickHouse) Analyze(ctx context.Context, e *treatment.Experiment) (*treatment.Evidence, error) { + p := e.CurrentPhase() + if p == nil { end := c.now().Add(-20 * time.Second) - return c.Evidence(ctx, e.Location, e.Treatment.Destination, e.Id, e.CohortOrder, end.Add(-time.Hour), end) + return c.Evidence(ctx, e.Location, e.Treatment.Destination, e.ID, e.CohortOrder, end.Add(-time.Hour), end) } - p := e.Phases[len(e.Phases)-1] - start := p.IssuedAt.AsTime() - if p.ObservationStartedAt != nil { - start = p.ObservationStartedAt.AsTime() + start := p.IssuedAt + if !p.ObservationStartedAt.IsZero() { + start = p.ObservationStartedAt } - return c.Evidence(ctx, e.Location, e.Treatment.Destination, e.Id, e.CohortOrder, start, c.now().Add(-20*time.Second)) + return c.Evidence(ctx, e.Location, e.Treatment.Destination, e.ID, e.CohortOrder, start, c.now().Add(-20*time.Second)) } -func (c *ClickHouse) Evidence(ctx context.Context, location, destination, assignment string, agents []string, start, end time.Time) (*pb.Evidence, error) { +// hostRow is one JSONEachRow line of analysis.sql. +type hostRow struct { + AgentID string `json:"agent_id"` + Sockets uint64 `json:"sockets"` + BytesAcked uint64 `json:"bytes_acked"` + GoodputBps float64 `json:"goodput_bps"` + RetransmissionRatio float64 `json:"retransmission_ratio"` + RttP95Ms float64 `json:"rtt_p95_ms"` + ExposedSockets uint64 `json:"exposed_sockets"` + CollectionHealthy bool `json:"collection_healthy"` +} + +func (c *ClickHouse) Evidence(ctx context.Context, location string, destination netip.Prefix, assignment string, agents []string, start, end time.Time) (*treatment.Evidence, error) { if !end.After(start) || end.Sub(start) > 7*24*time.Hour || len(agents) == 0 || len(agents) > treatment.MaxAgents { return nil, errors.New("invalid analysis window/population") } @@ -112,12 +122,11 @@ func (c *ClickHouse) Evidence(ctx context.Context, location, destination, assign } quoted[i] = "'" + id + "'" } - p, e := netip.ParsePrefix(destination) - if e != nil { - return nil, e + if !destination.IsValid() { + return nil, errors.New("invalid destination") } - addr := p.Addr().AsSlice() - if p.Addr().Is4() { + addr := destination.Addr().AsSlice() + if destination.Addr().Is4() { addr = append(addr, make([]byte, 12)...) } params := map[string]string{"start": strconv.FormatInt(start.UnixNano(), 10), "end": strconv.FormatInt(end.UnixNano(), 10), "location": location, "assignment": assignment, "agents": "[" + strings.Join(quoted, ",") + "]", "destination_hex": strings.ToUpper(hex.EncodeToString(addr))} @@ -128,18 +137,18 @@ func (c *ClickHouse) Evidence(ctx context.Context, location, destination, assign digest := sha256.Sum256(b) sha := hex.EncodeToString(digest[:]) analyzed := c.now() - v := &pb.Evidence{Id: "ev-" + sha[:24] + "-" + strconv.FormatInt(analyzed.UnixNano(), 10), AnalyzedAt: timestamppb.New(analyzed), WindowStart: timestamppb.New(start), WindowEnd: timestamppb.New(end), QueryVersion: "tcp-host-deltas-v1", EnrichmentVersion: "xtcp-schema-2", ArtifactSha256: sha} + v := &treatment.Evidence{ID: "ev-" + sha[:24] + "-" + strconv.FormatInt(analyzed.UnixNano(), 10), AnalyzedAt: analyzed, WindowStart: start, WindowEnd: end, QueryVersion: "tcp-host-deltas-v1", EnrichmentVersion: "xtcp-schema-2", ArtifactSHA256: sha} dec := json.NewDecoder(bytes.NewReader(b)) for { - h := new(pb.HostEvidence) - e := dec.Decode(h) + var h hostRow + e := dec.Decode(&h) if e == io.EOF { break } if e != nil { return nil, e } - v.Hosts = append(v.Hosts, h) + v.Hosts = append(v.Hosts, treatment.HostEvidence(h)) } if e = treatment.ValidateEvidence(v); e != nil { return nil, e diff --git a/pkg/treatment/controller/clickhouse_test.go b/pkg/treatment/controller/clickhouse_test.go index 1f20f073d..78ef105a3 100644 --- a/pkg/treatment/controller/clickhouse_test.go +++ b/pkg/treatment/controller/clickhouse_test.go @@ -7,8 +7,6 @@ import ( "path/filepath" "strings" "testing" - - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" ) // Execute the production SQL against an isolated ClickHouse local database. @@ -50,12 +48,12 @@ func TestClickHouseAnalysisCounterDeltas(t *testing.T) { } return out } - var h pb.HostEvidence + var h hostRow if e := json.Unmarshal(run(setup+query), &h); e != nil { t.Fatal(e) } if h.Sockets != 1 || h.BytesAcked != 5000 || h.ExposedSockets != 1 || !h.CollectionHealthy || h.RttP95Ms != 1 || h.RetransmissionRatio != .01 { - t.Fatalf("bad window deltas/deduplication: %v", &h) + t.Fatalf("bad window deltas/deduplication: %+v", h) } reset := `INSERT INTO default.sockets SELECT 'host-1','boot-1',1,1, fromUnixTimestamp64Nano(1700000055000000000),1,1,0,1000,500,true,'trial','dc1',1, diff --git a/pkg/treatment/controller/contracts_test.go b/pkg/treatment/controller/contracts_test.go index f0f0981dc..100342c54 100644 --- a/pkg/treatment/controller/contracts_test.go +++ b/pkg/treatment/controller/contracts_test.go @@ -5,6 +5,7 @@ import ( "errors" "io" "net/http" + "net/netip" "strings" "testing" "time" @@ -89,10 +90,12 @@ func TestClickHouseEvidenceContracts(t *testing.T) { const host = `{"agent_id":"host","sockets":30,"bytes_acked":1048576,"goodput_bps":1000,"rtt_p95_ms":5,"collection_healthy":true}` now := time.Unix(1700000000, 0) for _, tc := range []struct { - name, description, body, destination string - agents []string - window time.Duration - wantError string + name, description, body string + destination netip.Prefix + agents []string + window time.Duration + invalidDestination bool + wantError string }{ {name: "valid", description: "complete response records provenance", body: host}, {name: "empty", description: "empty response cannot renew a lease", wantError: "population"}, @@ -101,7 +104,7 @@ func TestClickHouseEvidenceContracts(t *testing.T) { {name: "bad-window", description: "empty analysis interval is rejected", window: -time.Second, wantError: "window"}, {name: "long-window", description: "analysis is limited to seven days", window: 7*24*time.Hour + time.Second, wantError: "window"}, {name: "bad-agent", description: "agent IDs cannot inject query parameters", agents: []string{"host'"}, wantError: "invalid agent"}, - {name: "bad-destination", description: "malformed destination fails before query", destination: "bad", wantError: "ParsePrefix"}, + {name: "bad-destination", description: "an invalid destination fails before the query", destination: netip.Prefix{}, invalidDestination: true, wantError: "invalid destination"}, } { t.Run(tc.name, func(t *testing.T) { window := tc.window @@ -113,8 +116,8 @@ func TestClickHouseEvidenceContracts(t *testing.T) { agents = []string{"host"} } dest := tc.destination - if dest == "" { - dest = "192.0.2.1/32" + if !tc.invalidDestination { + dest = netip.MustParsePrefix("192.0.2.1/32") } c := &ClickHouse{URL: "http://fixture.invalid", Table: "db.rows", Now: func() time.Time { return now }, Client: &http.Client{Transport: discoveryTransport(func(r *http.Request) (*http.Response, error) { if r.URL.Query().Get("param_destination_hex") != "C0000201000000000000000000000000" { @@ -132,7 +135,7 @@ func TestClickHouseEvidenceContracts(t *testing.T) { if err != nil { t.Fatal(err) } - if len(ev.Hosts) != 1 || len(ev.ArtifactSha256) != 64 || ev.QueryVersion != "tcp-host-deltas-v1" || !ev.AnalyzedAt.AsTime().Equal(now) { + if len(ev.Hosts) != 1 || len(ev.ArtifactSHA256) != 64 || ev.QueryVersion != "tcp-host-deltas-v1" || !ev.AnalyzedAt.Equal(now) { t.Fatalf("%s: bad evidence %v", tc.description, ev) } }) diff --git a/pkg/treatment/controller/discovery.go b/pkg/treatment/controller/discovery.go index 08ea83ec2..e93651742 100644 --- a/pkg/treatment/controller/discovery.go +++ b/pkg/treatment/controller/discovery.go @@ -15,7 +15,7 @@ import ( pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" - "google.golang.org/protobuf/proto" + "github.com/randomizedcoder/xtcp2/pkg/treatment/wire" ) // Templates only authorize candidate discovery. Every generated hypothesis and @@ -36,18 +36,19 @@ type DiscoveryTemplate struct { Prefixes []string `json:"prefixes"` } -func (t DiscoveryTemplate) policy() *pb.ExperimentPolicy { +func (t DiscoveryTemplate) policy() treatment.Policy { p := treatment.DefaultPolicy() if t.MaximumRTTIncrease != nil { - p.MaximumRttIncrease = *t.MaximumRTTIncrease + p.MaximumRTTIncrease = *t.MaximumRTTIncrease } return p } -func (t DiscoveryTemplate) settings() *pb.TCPSettings { - return &pb.TCPSettings{ - CongestionControl: proto.String(t.CongestionControl), - RtoMinMs: t.RtoMinMs, +func (t DiscoveryTemplate) settings() treatment.TCPSettings { + cc := t.CongestionControl + return treatment.TCPSettings{ + CongestionControl: &cc, + RTOMinMs: t.RtoMinMs, InitialCongestionWindow: t.InitialCongestionWindow, InitialReceiveWindow: t.InitialReceiveWindow, QuickAck: t.QuickAck, @@ -105,7 +106,7 @@ func (s *Server) Discover(ctx context.Context, c *ClickHouse, templates []Discov } // Avoid warehouse aggregation while this location cannot accept a // new experiment. The transaction below still rechecks the budget. - busy, e := hasActiveExperiment(ctx, s.Store.DB, t.Location) + busy, e := s.Store.Repos().Experiments.HasActive(ctx, t.Location) if e != nil { failures = append(failures, e) continue @@ -168,10 +169,10 @@ func (s *Server) Discover(ctx context.Context, c *ClickHouse, templates []Discov continue } } - destination := netip.PrefixFrom(ip, ip.BitLen()).String() + destination := netip.PrefixFrom(ip, ip.BitLen()) // Completed or held treatments and recent rejected trials suppress // repeat proposals. Cooldown is at least a day after terminal state. - exists, e := discoveryDestinationBlocked(ctx, s.Store.DB, t.Location, destination, s.now()) + exists, e := s.Store.Repos().Experiments.DestinationBlocked(ctx, t.Location, destination, s.now().Add(-24*time.Hour)) if e != nil { failures = append(failures, e) continue @@ -179,15 +180,15 @@ func (s *Server) Discover(ctx context.Context, c *ClickHouse, templates []Discov if exists { continue } - seed := sha256.Sum256([]byte(t.ID + "/" + destination + "/" + s.now().UTC().Format("2006-01-02"))) + seed := sha256.Sum256([]byte(t.ID + "/" + destination.String() + "/" + s.now().UTC().Format("2006-01-02"))) id := "hyp-" + hex.EncodeToString(seed[:12]) baseline, e := c.Evidence(ctx, t.Location, destination, id, row.Agents, start, end) if e != nil { failures = append(failures, e) continue } - n := &pb.Experiment{Id: id, Location: t.Location, Hypothesis: fmt.Sprintf("Template %s: test %s for %s (ASN %d), observed RTT %.3f ms and retransmission ratio %.6f; this is a hypothesis, not an efficacy claim", t.ID, t.CongestionControl, destination, row.ASN, row.RTT, row.Retransmission), Treatment: &pb.Treatment{Id: t.ID, Revision: 1, Destination: destination, Settings: t.settings()}, Policy: t.policy(), Baseline: baseline, CohortOrder: row.Agents} - if _, e = s.propose(ctx, &pb.ProposeRequest{Experiment: n, RequestId: id}, "candidate-discovery", true); e != nil { + n := &treatment.Experiment{ID: id, Location: t.Location, Hypothesis: fmt.Sprintf("Template %s: test %s for %s (ASN %d), observed RTT %.3f ms and retransmission ratio %.6f; this is a hypothesis, not an efficacy claim", t.ID, t.CongestionControl, destination, row.ASN, row.RTT, row.Retransmission), Treatment: treatment.Treatment{ID: t.ID, Revision: 1, Destination: destination, Settings: t.settings()}, Policy: t.policy(), Baseline: baseline, CohortOrder: row.Agents} + if _, e = s.propose(ctx, &pb.ProposeRequest{Experiment: wire.Experiment(n), RequestId: id}, "candidate-discovery", true); e != nil { if !errors.Is(e, errExperimentBudget) && !errors.Is(e, errDiscoverySuppressed) { failures = append(failures, e) } diff --git a/pkg/treatment/controller/discovery_contracts_test.go b/pkg/treatment/controller/discovery_contracts_test.go index 87e6cc62c..b34ef27a1 100644 --- a/pkg/treatment/controller/discovery_contracts_test.go +++ b/pkg/treatment/controller/discovery_contracts_test.go @@ -3,96 +3,194 @@ package controller import ( "context" "encoding/json" + "errors" "fmt" "io" "net/http" "strings" + "sync" "testing" "time" pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store/postgres" + "google.golang.org/protobuf/proto" ) -func TestPostgresDiscoveryScopeAndCooldown(t *testing.T) { +func TestDiscoveryScopeAndCooldown(t *testing.T) { for _, tc := range []struct { name, description string asns []uint64 prefixes []string - want int + expected int }{ - {"unfiltered", "eligible destination creates one reviewable proposal", nil, nil, 1}, - {"asn-match", "matching ASN permits discovery", []uint64{64512}, nil, 1}, - {"asn-miss", "foreign ASN is outside scope", []uint64{64513}, nil, 0}, - {"prefix-match", "matching destination prefix permits discovery", nil, []string{"192.0.2.0/24"}, 1}, - {"prefix-miss", "foreign destination prefix is outside scope", nil, []string{"198.51.100.0/24"}, 0}, + {"unfiltered", "positive: an eligible destination creates one reviewable proposal", nil, nil, 1}, + {"asn-match", "positive: a matching ASN permits discovery", []uint64{64512}, nil, 1}, + {"asn-miss", "negative: a foreign ASN is outside scope", []uint64{64513}, nil, 0}, + {"prefix-match", "positive: a matching destination prefix permits discovery", nil, []string{"192.0.2.0/24"}, 1}, + {"prefix-miss", "negative: a foreign destination prefix is outside scope", nil, []string{"198.51.100.0/24"}, 0}, } { t.Run(tc.name, func(t *testing.T) { - now := time.Now().UTC() - s := &Server{Store: testStore(t), Now: func() time.Time { return now }} - var agents []string - var evidence strings.Builder - for i := range 8 { - id := fmt.Sprintf("host-%d", i) - enroll(t, s, id) - agents = append(agents, id) - raw, err := json.Marshal(&pb.HostEvidence{AgentId: id, Sockets: 100, BytesAcked: 1 << 25, GoodputBps: 1e8, RttP95Ms: 5, CollectionHealthy: true}) + eachStore(t, func(t *testing.T, st store.Store) { + now := time.Now().UTC().Truncate(time.Microsecond) + s := testServer(st, &now) + var agents []string + var evidence strings.Builder + for i := range 8 { + id := fmt.Sprintf("host-%d", i) + enroll(t, s, id) + agents = append(agents, id) + raw, err := json.Marshal(hostRow{AgentID: id, Sockets: 100, BytesAcked: 1 << 25, GoodputBps: 1e8, RttP95Ms: 5, CollectionHealthy: true}) + if err != nil { + t.Fatal(err) + } + evidence.Write(raw) + evidence.WriteByte('\n') + } + row, err := json.Marshal(map[string]any{"destination_hex": "C0000201000000000000000000000000", "family": 2, "asn": 64512, "agents": agents, "rtt_ms": 5, "retransmission_ratio": 0}) if err != nil { t.Fatal(err) } - evidence.Write(raw) - evidence.WriteByte('\n') - } - row, err := json.Marshal(map[string]any{"destination_hex": "C0000201000000000000000000000000", "family": 2, "asn": 64512, "agents": agents, "rtt_ms": 5, "retransmission_ratio": 0}) - if err != nil { - t.Fatal(err) - } - c := &ClickHouse{URL: "http://fixture.invalid", Table: "db.rows", Now: s.Now, Client: &http.Client{Transport: discoveryTransport(func(r *http.Request) (*http.Response, error) { - q, err := io.ReadAll(r.Body) - if err != nil { - return nil, err + c := &ClickHouse{URL: "http://fixture.invalid", Table: "db.rows", Now: s.Now, Client: &http.Client{Transport: discoveryTransport(func(r *http.Request) (*http.Response, error) { + q, err := io.ReadAll(r.Body) + if err != nil { + return nil, err + } + body := evidence.String() + if string(q) == strings.ReplaceAll(discoverySQL, "{{TABLE}}", "db.rows") { + body = string(row) + } + return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(body)), Header: make(http.Header)}, nil + })}} + templates := []DiscoveryTemplate{{ID: "nearby", Location: "dc1", CongestionControl: "cubic", MaximumRTTMs: 15, ASNs: tc.asns, Prefixes: tc.prefixes}} + for range 2 { + if err := s.Discover(context.Background(), c, templates); err != nil { + t.Fatal(err) + } } - body := evidence.String() - if string(q) == strings.ReplaceAll(discoverySQL, "{{TABLE}}", "db.rows") { - body = string(row) + found := experimentsOf(t, st) + if len(found) != tc.expected { + t.Fatalf("%s: proposals=%d expected %d", tc.description, len(found), tc.expected) } - return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(body)), Header: make(http.Header)}, nil - })}} - templates := []DiscoveryTemplate{{ID: "nearby", Location: "dc1", CongestionControl: "cubic", MaximumRTTMs: 15, ASNs: tc.asns, Prefixes: tc.prefixes}} - for range 2 { - if err := s.Discover(context.Background(), c, templates); err != nil { + if len(found) == 0 { + return + } + if found[0].Status != treatment.StatusProposed { + t.Fatal("discovery started unreviewed experiment") + } + // A terminal destination stays suppressed for 24 hours, including + // across a controller restart (new Server, same store). + if _, err := s.Control(testIdentity("operator/alice"), &pb.ControlRequest{ExperimentId: found[0].ID, Action: pb.ControlAction_CONTROL_ACTION_ROLLBACK, Reason: "test", RequestId: "rollback-1"}); err != nil { t.Fatal(err) } - } - var count int - if err := s.Store.DB.QueryRow("SELECT count(*) FROM xtcp_control_experiments").Scan(&count); err != nil { - t.Fatal(err) - } - if count != tc.want { - t.Fatalf("%s: proposals=%d want %d", tc.description, count, tc.want) - } - if count == 0 { - return - } - var state string - if err := s.Store.DB.QueryRow("SELECT state->>'status' FROM xtcp_control_experiments").Scan(&state); err != nil { - t.Fatal(err) - } - if state != "EXPERIMENT_STATUS_PROPOSED" { - t.Fatal("discovery started unreviewed experiment") - } - // Terminal destination is still suppressed for 24 hours, including after a - // controller restart (new Server, same authoritative database). - if _, err := s.Store.DB.Exec("UPDATE xtcp_control_experiments SET state=jsonb_set(state,'{status}','\"EXPERIMENT_STATUS_ROLLED_BACK\"'),updated_at=$1", now); err != nil { - t.Fatal(err) - } - now = now.Add(23 * time.Hour) - restarted := &Server{Store: s.Store, Now: s.Now} - if err := restarted.Discover(context.Background(), c, templates); err != nil { - t.Fatal(err) - } - if err := s.Store.DB.QueryRow("SELECT count(*) FROM xtcp_control_experiments").Scan(&count); err != nil || count != 1 { - t.Fatalf("cooldown lost across restart: %d %v", count, err) - } + now = now.Add(23 * time.Hour) + restarted := &Server{Store: s.Store, Now: s.Now} + if err := restarted.Discover(context.Background(), c, templates); err != nil { + t.Fatal(err) + } + if n := len(experimentsOf(t, st)); n != 1 { + t.Fatalf("cooldown lost across restart: %d", n) + } + // After the cooldown the destination is eligible again once the + // cohort has checked in within the agent window. + now = now.Add(2 * time.Hour) + for _, id := range agents { + enroll(t, restarted, id) + } + if err := restarted.Discover(context.Background(), c, templates); err != nil { + t.Fatal(err) + } + if n := len(experimentsOf(t, st)); n != 2 { + t.Fatalf("destination not rediscovered after cooldown: %d", n) + } + }) }) } } + +func TestDiscoverySkipsBusyLocation(t *testing.T) { + eachStore(t, func(t *testing.T, st store.Store) { + now := time.Now().UTC() + seedExperiment(t, st, "active", "dc1", "192.0.2.9/32", treatment.StatusRunning, now) + s := testServer(st, &now) + called := false + ch := &ClickHouse{URL: "http://fixture.invalid", Table: "db.rows", Client: &http.Client{Transport: discoveryTransport(func(*http.Request) (*http.Response, error) { + called = true + return nil, errors.New("warehouse must not be queried") + })}} + templates := []DiscoveryTemplate{{ID: "nearby", Location: "dc1", CongestionControl: "cubic", MaximumRTTMs: 15}} + if err := s.Discover(context.Background(), ch, templates); err != nil || called { + t.Fatalf("busy location queried warehouse: called=%t err=%v", called, err) + } + pg, ok := st.(*postgres.Store) + if !ok { + return + } + if err := pg.Close(); err != nil { + t.Fatal(err) + } + if err := s.Discover(context.Background(), ch, templates); err == nil || called { + t.Fatalf("database failure must stop discovery: called=%t err=%v", called, err) + } + }) +} + +func TestDiscoveryTransactionFence(t *testing.T) { + eachStore(t, func(t *testing.T, st store.Store) { + now := time.Now().UTC() + s := testServer(st, &now) + enrollFleet(t, s, 8) + // A stable treatment appears after discovery's initial lookup. + seedExperiment(t, st, "stable", "dc1", "192.0.2.1/32", treatment.StatusComplete, now) + if _, err := s.propose(context.Background(), proposal(now), "candidate-discovery", true); !errors.Is(err, errDiscoverySuppressed) { + t.Fatalf("transaction did not suppress newly stable destination: %v", err) + } + // Operator-driven replacement remains possible; assignment keeps the + // stable predecessor and still requires review before applying. + if _, err := s.Propose(testIdentity("operator/alice"), proposal(now)); err != nil { + t.Fatalf("explicit replacement was blocked: %v", err) + } + }) +} + +func TestConcurrentDiscoveryProposals(t *testing.T) { + eachStore(t, func(t *testing.T, st store.Store) { + now := time.Now().UTC() + s := testServer(st, &now) + enrollFleet(t, s, 8) + start := make(chan struct{}) + results := make(chan error, 2) + var wg sync.WaitGroup + for i := range 2 { + wg.Go(func() { + r := proto.Clone(proposal(now)).(*pb.ProposeRequest) + r.RequestId = fmt.Sprintf("candidate-%d", i) + r.Experiment.Id = r.RequestId + <-start + _, err := s.propose(context.Background(), r, "candidate-discovery", true) + results <- err + }) + } + close(start) + wg.Wait() + close(results) + successes, suppressed := 0, 0 + for err := range results { + if err == nil { + successes++ + } else if errors.Is(err, errDiscoverySuppressed) { + suppressed++ + } else { + t.Fatal(err) + } + } + if successes != 1 || suppressed != 1 { + t.Fatalf("want one proposal and one suppressed competitor; got %d/%d", successes, suppressed) + } + if n := len(experimentsOf(t, st)); n != 1 { + t.Fatalf("duplicate persisted proposal: count=%d", n) + } + }) +} diff --git a/pkg/treatment/controller/discovery_store_test.go b/pkg/treatment/controller/discovery_store_test.go deleted file mode 100644 index 214c72bea..000000000 --- a/pkg/treatment/controller/discovery_store_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package controller - -import ( - "context" - "database/sql" - "database/sql/driver" - "errors" - "io" - "testing" -) - -// Only the cheap pre-scan budget lookup is stubbed in template-validation unit -// tests. PostgreSQL integration tests exercise the real queries and indexes. -func emptyDiscoveryStore(t *testing.T) *Store { - t.Helper() - db := sql.OpenDB(discoveryConnector{}) - t.Cleanup(func() { db.Close() }) - return &Store{DB: db} -} - -type discoveryConnector struct{} - -func (discoveryConnector) Connect(context.Context) (driver.Conn, error) { return discoveryConn{}, nil } -func (discoveryConnector) Driver() driver.Driver { return discoveryDriver{} } - -type discoveryDriver struct{} - -func (discoveryDriver) Open(string) (driver.Conn, error) { return discoveryConn{}, nil } - -type discoveryConn struct{} - -func (discoveryConn) Prepare(string) (driver.Stmt, error) { - return nil, errors.New("unexpected prepared query") -} -func (discoveryConn) Close() error { return nil } -func (discoveryConn) Begin() (driver.Tx, error) { return nil, errors.New("unexpected transaction") } -func (discoveryConn) QueryContext(_ context.Context, q string, _ []driver.NamedValue) (driver.Rows, error) { - if q != activeExperimentSQL { - return nil, errors.New("unexpected discovery query") - } - return &discoveryRows{}, nil -} - -type discoveryRows struct{ done bool } - -func (*discoveryRows) Columns() []string { return []string{"exists"} } -func (*discoveryRows) Close() error { return nil } -func (r *discoveryRows) Next(values []driver.Value) error { - if r.done { - return io.EOF - } - r.done = true - values[0] = false - return nil -} diff --git a/pkg/treatment/controller/discovery_test.go b/pkg/treatment/controller/discovery_test.go index 6c081fa9e..902d982af 100644 --- a/pkg/treatment/controller/discovery_test.go +++ b/pkg/treatment/controller/discovery_test.go @@ -6,20 +6,17 @@ import ( "io" "math" "net/http" + "net/netip" "strconv" "strings" "testing" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store/memory" "google.golang.org/protobuf/proto" ) -type discoveryTransport func(*http.Request) (*http.Response, error) - -func (f discoveryTransport) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) } - func TestDiscoveryWindow(t *testing.T) { now := time.Unix(1800000000, 0) for _, tc := range []struct { @@ -45,7 +42,7 @@ func TestDiscoveryWindow(t *testing.T) { } return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader("")), Header: make(http.Header)}, nil })}} - s := &Server{Store: emptyDiscoveryStore(t), Now: func() time.Time { return now }, DiscoveryWindow: tc.window} + s := &Server{Store: memory.New(), Now: func() time.Time { return now }, DiscoveryWindow: tc.window} err := s.Discover(context.Background(), ch, []DiscoveryTemplate{{ID: "near", Location: "test-dc", CongestionControl: "cubic", MaximumRTTMs: 15}}) if (err != nil) != tc.invalid || called == tc.invalid { t.Fatalf("err=%v query-called=%t invalid=%t", err, called, tc.invalid) @@ -75,7 +72,7 @@ func TestDiscoveryRTTPolicy(t *testing.T) { called = true return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader("")), Header: make(http.Header)}, nil })}} - err := (&Server{Store: emptyDiscoveryStore(t)}).Discover(context.Background(), ch, []DiscoveryTemplate{template}) + err := (&Server{Store: memory.New()}).Discover(context.Background(), ch, []DiscoveryTemplate{template}) if (err != nil) != tc.invalid || called == tc.invalid { t.Fatalf("err=%v queried=%t", err, called) } @@ -83,9 +80,9 @@ func TestDiscoveryRTTPolicy(t *testing.T) { return } want := treatment.DefaultPolicy() - want.MaximumRttIncrease = tc.want - if !proto.Equal(template.policy(), want) { - t.Fatalf("wrong policy: %v", template.policy()) + want.MaximumRTTIncrease = tc.want + if template.policy() != want { + t.Fatalf("wrong policy: %+v", template.policy()) } }) } @@ -94,16 +91,16 @@ func TestDiscoveryRTTPolicy(t *testing.T) { func TestDiscoveryRouteSettings(t *testing.T) { for _, tc := range []struct { name, fields string - want *pb.TCPSettings + want treatment.TCPSettings invalid bool }{ - {"algorithm-only", "", &pb.TCPSettings{CongestionControl: proto.String("cubic")}, false}, - {"nearby", `,"rto_min_ms":5,"quick_ack":true,"initial_congestion_window":100,"initial_receive_window":100`, &pb.TCPSettings{CongestionControl: proto.String("cubic"), RtoMinMs: proto.Uint32(5), QuickAck: proto.Bool(true), InitialCongestionWindow: proto.Uint32(100), InitialReceiveWindow: proto.Uint32(100)}, false}, - {"explicit-off", `,"quick_ack":false`, &pb.TCPSettings{CongestionControl: proto.String("cubic"), QuickAck: proto.Bool(false)}, false}, - {"zero-rto", `,"rto_min_ms":0`, nil, true}, - {"large-rto", `,"rto_min_ms":120001`, nil, true}, - {"zero-cwnd", `,"initial_congestion_window":0`, nil, true}, - {"large-rwnd", `,"initial_receive_window":1025`, nil, true}, + {"algorithm-only", "", treatment.TCPSettings{CongestionControl: proto.String("cubic")}, false}, + {"nearby", `,"rto_min_ms":5,"quick_ack":true,"initial_congestion_window":100,"initial_receive_window":100`, treatment.TCPSettings{CongestionControl: proto.String("cubic"), RTOMinMs: proto.Uint32(5), QuickAck: proto.Bool(true), InitialCongestionWindow: proto.Uint32(100), InitialReceiveWindow: proto.Uint32(100)}, false}, + {"explicit-off", `,"quick_ack":false`, treatment.TCPSettings{CongestionControl: proto.String("cubic"), QuickAck: proto.Bool(false)}, false}, + {"zero-rto", `,"rto_min_ms":0`, treatment.TCPSettings{}, true}, + {"large-rto", `,"rto_min_ms":120001`, treatment.TCPSettings{}, true}, + {"zero-cwnd", `,"initial_congestion_window":0`, treatment.TCPSettings{}, true}, + {"large-rwnd", `,"initial_receive_window":1025`, treatment.TCPSettings{}, true}, } { t.Run(tc.name, func(t *testing.T) { var template DiscoveryTemplate @@ -115,17 +112,17 @@ func TestDiscoveryRouteSettings(t *testing.T) { called = true return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader("")), Header: make(http.Header)}, nil })}} - err := (&Server{Store: emptyDiscoveryStore(t)}).Discover(context.Background(), ch, []DiscoveryTemplate{template}) + err := (&Server{Store: memory.New()}).Discover(context.Background(), ch, []DiscoveryTemplate{template}) if (err != nil) != tc.invalid || called == tc.invalid { t.Fatalf("err=%v query-called=%t", err, called) } if tc.invalid { return } - if !proto.Equal(template.settings(), tc.want) { - t.Fatalf("got %v, want %v", template.settings(), tc.want) + if got := template.settings(); !got.Equal(tc.want) { + t.Fatalf("got %+v, want %+v", got, tc.want) } - if err := treatment.ValidateTreatment(&pb.Treatment{Id: template.ID, Revision: 1, Destination: "192.0.2.1/32", Settings: template.settings()}); err != nil { + if err := treatment.ValidateTreatment(&treatment.Treatment{ID: template.ID, Revision: 1, Destination: netip.MustParsePrefix("192.0.2.1/32"), Settings: template.settings()}); err != nil { t.Fatal(err) } }) diff --git a/pkg/treatment/controller/helpers_test.go b/pkg/treatment/controller/helpers_test.go new file mode 100644 index 000000000..0de77d37e --- /dev/null +++ b/pkg/treatment/controller/helpers_test.go @@ -0,0 +1,180 @@ +package controller + +import ( + "context" + "crypto/tls" + "crypto/x509" + "fmt" + "net/http" + "net/http/httptest" + "net/netip" + "net/url" + "strings" + "testing" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store/memory" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store/pgtest" + "github.com/randomizedcoder/xtcp2/pkg/treatment/wire" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/peer" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func testIdentity(path string) context.Context { + u, _ := url.Parse("spiffe://xtcp/" + path) + cert := &x509.Certificate{URIs: []*url.URL{u}} + return peer.NewContext(context.Background(), &peer.Peer{AuthInfo: credentials.TLSInfo{State: tls.ConnectionState{PeerCertificates: []*x509.Certificate{cert}, VerifiedChains: [][]*x509.Certificate{{cert}}}}}) +} + +// eachStore runs fn against the in-memory store always and against +// PostgreSQL when XTCP_CONTROLLER_TEST_DSN is set, so every handler test is +// a unit test by default and an integration test in the Nix harness. +func eachStore(t *testing.T, fn func(t *testing.T, s store.Store)) { + t.Helper() + t.Run("memory", func(t *testing.T) { fn(t, memory.New()) }) + t.Run("postgres", func(t *testing.T) { fn(t, pgtest.Open(t)) }) +} + +func testServer(s store.Store, now *time.Time) *Server { + return &Server{Store: s, Hub: NewNotificationHub(), Now: func() time.Time { return *now }} +} + +func capabilities() *pb.Capabilities { + return &pb.Capabilities{BootId: "boot-1", KernelRelease: "test", CongestionAlgorithms: []string{"cubic", "bbr"}, SimpleHostRouting: true, ActuatorEnabled: true} +} + +func enroll(t *testing.T, s *Server, id string) { + t.Helper() + if _, e := s.CheckIn(testIdentity("agent/dc1/"+id), &pb.CheckInRequest{AgentId: id, Location: "dc1", Capabilities: capabilities()}); e != nil { + t.Fatal(e) + } +} + +func enrollFleet(t *testing.T, s *Server, n int) { + t.Helper() + for i := range n { + enroll(t, s, fmt.Sprintf("host-%d", i)) + } +} + +func proposal(now time.Time) *pb.ProposeRequest { + e := &pb.Experiment{Id: "experiment-1", Location: "dc1", Hypothesis: "test cubic", Policy: wire.Policy(treatment.DefaultPolicy()), Treatment: &pb.Treatment{Id: "cubic", Revision: 1, Destination: "192.0.2.1/32", Settings: &pb.TCPSettings{CongestionControl: proto.String("cubic")}}, Baseline: &pb.Evidence{Id: "baseline-1", AnalyzedAt: timestamppb.New(now), WindowStart: timestamppb.New(now.Add(-time.Hour)), WindowEnd: timestamppb.New(now.Add(-time.Second)), QueryVersion: "test", ArtifactSha256: strings.Repeat("a", 64)}} + for i := range 8 { + id := fmt.Sprintf("host-%d", i) + e.CohortOrder = append(e.CohortOrder, id) + e.Baseline.Hosts = append(e.Baseline.Hosts, &pb.HostEvidence{AgentId: id, Sockets: 100, BytesAcked: 1 << 25, GoodputBps: 1e8, RttP95Ms: 1, CollectionHealthy: true}) + } + return &pb.ProposeRequest{Experiment: e, RequestId: "proposal-1"} +} + +func desiredOf(t *testing.T, s store.Store, id string) *treatment.DesiredState { + t.Helper() + d, e := s.Repos().Agents.Desired(context.Background(), id) + if e != nil { + t.Fatal(e) + } + return d +} + +func experimentsOf(t *testing.T, s store.Store) []*treatment.Experiment { + t.Helper() + list, e := s.Repos().Experiments.List(context.Background(), "", 1000) + if e != nil { + t.Fatal(e) + } + return list +} + +func fleetAssignments(t *testing.T, s store.Store) int { + t.Helper() + agents, e := s.Repos().Agents.List(context.Background(), "") + if e != nil { + t.Fatal(e) + } + n := 0 + for _, a := range agents { + n += len(desiredOf(t, s, a.ID).Assignments) + } + return n +} + +// seedExperiment stores a bare experiment directly, standing in for history +// written by an earlier controller. +func seedExperiment(t *testing.T, s store.Store, id, location, destination string, status treatment.Status, updated time.Time) { + t.Helper() + tr := treatment.Treatment{ID: "cubic", Revision: 1, Settings: treatment.TCPSettings{CongestionControl: proto.String("cubic")}} + if destination != "" { + tr.Destination = netip.MustParsePrefix(destination) + } + e := &treatment.Experiment{ID: id, Location: location, Hypothesis: "seeded", Treatment: tr, Policy: treatment.DefaultPolicy(), Status: status, Revision: 1, ProposedAt: updated, UpdatedAt: updated} + if err := s.Repos().Experiments.Save(context.Background(), e); err != nil { + t.Fatal(err) + } +} + +type fixedEvidence struct{ value *treatment.Evidence } + +func (f fixedEvidence) Analyze(context.Context, *treatment.Experiment) (*treatment.Evidence, error) { + return f.value, nil +} + +func parseEvidence(t *testing.T, p *pb.Evidence) *treatment.Evidence { + t.Helper() + v, e := wire.ParseEvidence(p) + if e != nil { + t.Fatal(e) + } + return v +} + +// acknowledgeFleet reports every cohort host's assignments as verified at +// the latest phase's issue time. +func acknowledgeFleet(t *testing.T, s *Server, n *pb.Experiment) { + t.Helper() + for _, id := range n.CohortOrder { + d := wire.Desired(desiredOf(t, s.Store, id)) + r := &pb.CheckInRequest{AgentId: id, Location: n.Location, DesiredRevision: d.Revision, Capabilities: capabilities()} + for _, a := range d.Assignments { + r.Records = append(r.Records, &pb.ApplicationRecord{Assignment: proto.Clone(a).(*pb.Assignment), DesiredRevision: d.Revision, Status: pb.ApplyStatus_APPLY_STATUS_VERIFIED, AppliedAt: n.Phases[len(n.Phases)-1].IssuedAt}) + } + if _, e := s.CheckIn(testIdentity("agent/dc1/"+id), r); e != nil { + t.Fatal(e) + } + } +} + +type discoveryTransport func(*http.Request) (*http.Response, error) + +func (f discoveryTransport) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) } + +func operatorHTTP(method, path, identity string) *http.Request { + r := httptest.NewRequest(method, path, nil) + if identity != "" { + u, _ := url.Parse("spiffe://xtcp/" + identity) + cert := &x509.Certificate{URIs: []*url.URL{u}} + r.TLS = &tls.ConnectionState{PeerCertificates: []*x509.Certificate{cert}, VerifiedChains: [][]*x509.Certificate{{cert}}} + } + return r +} + +type desiredTestStream struct { + grpc.ServerStream + ctx context.Context + sent chan uint64 +} + +func (s *desiredTestStream) Context() context.Context { return s.ctx } +func (s *desiredTestStream) Send(r *pb.WatchDesiredStateResponse) error { + select { + case s.sent <- r.Revision: + return nil + case <-s.ctx.Done(): + return s.ctx.Err() + } +} diff --git a/pkg/treatment/controller/maintenance.go b/pkg/treatment/controller/maintenance.go index ddfc97f84..95f921bc8 100644 --- a/pkg/treatment/controller/maintenance.go +++ b/pkg/treatment/controller/maintenance.go @@ -2,66 +2,60 @@ package controller import ( "context" - "database/sql" "errors" "fmt" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store" "github.com/randomizedcoder/xtcp2/pkg/treatment/experiment" - "google.golang.org/protobuf/encoding/protojson" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/timestamppb" + "github.com/randomizedcoder/xtcp2/pkg/treatment/wire" ) // EvidenceSource must produce immutable per-host evidence for a frozen // population and treatment. Failure never authorizes lease renewal/promotion. type EvidenceSource interface { - Analyze(context.Context, *pb.Experiment) (*pb.Evidence, error) + Analyze(context.Context, *treatment.Experiment) (*treatment.Evidence, error) +} + +// driftInput rewinds the current phase an hour so a completed treatment is +// re-analyzed over a rolling window rather than since its review. +func (s *Server) driftInput(n *treatment.Experiment) *treatment.Experiment { + input := n.Clone() + p := input.CurrentPhase() + p.IssuedAt = s.now().Add(-time.Hour) + p.ObservationStartedAt = p.IssuedAt + return input } // Maintain refreshes exposure, stores evidence, renews healthy leases, and // withdraws harmful/expired trials. It NEVER promotes a phase. A fresh human // review remains necessary for each increase in exposure. func (s *Server) Maintain(ctx context.Context, source EvidenceSource) error { - rows, e := s.Store.DB.QueryContext(ctx, "SELECT id FROM xtcp_control_experiments WHERE state->>'status' IN ('EXPERIMENT_STATUS_RUNNING','EXPERIMENT_STATUS_PAUSED','EXPERIMENT_STATUS_COMPLETE')") - if e != nil { - return e - } - var ids []string - for rows.Next() { - var id string - if e = rows.Scan(&id); e != nil { - break - } - ids = append(ids, id) - } - e = errors.Join(e, rows.Err()) - rows.Close() + ids, e := s.Store.Repos().Experiments.IDs(ctx, treatment.StatusRunning, treatment.StatusPaused, treatment.StatusComplete) if e != nil { return e } var failures []error for _, id := range ids { - var snapshot *pb.Experiment - e = s.Store.mutate(ctx, func(tx *sql.Tx) error { - n, e := loadExperiment(ctx, tx, id) + var snapshot *treatment.Experiment + e = s.Store.Tx(ctx, func(rp store.Repos) error { + n, e := rp.Experiments.Get(ctx, id) if e != nil { return e } - if n.Status != pb.ExperimentStatus_EXPERIMENT_STATUS_RUNNING && n.Status != pb.ExperimentStatus_EXPERIMENT_STATUS_PAUSED && n.Status != pb.ExperimentStatus_EXPERIMENT_STATUS_COMPLETE { + if n.Status != treatment.StatusRunning && n.Status != treatment.StatusPaused && n.Status != treatment.StatusComplete { return nil } - if n.Status == pb.ExperimentStatus_EXPERIMENT_STATUS_COMPLETE { - if n.LatestEvidence != nil && s.now().Sub(n.LatestEvidence.AnalyzedAt.AsTime()) < time.Hour { + if n.Status == treatment.StatusComplete { + if n.LatestEvidence != nil && s.now().Sub(n.LatestEvidence.AnalyzedAt) < time.Hour { return nil } - } else if e = s.startObservation(ctx, tx, n); e != nil { + } else if e = s.startObservation(ctx, rp, n); e != nil { return e } snapshot = n - return saveExperiment(ctx, tx, n) + return s.save(ctx, rp, n) }) if e != nil { failures = append(failures, e) @@ -70,55 +64,52 @@ func (s *Server) Maintain(ctx context.Context, source EvidenceSource) error { if snapshot == nil || len(snapshot.Phases) == 0 { continue } - var ev *pb.Evidence - if source != nil && snapshot.Status != pb.ExperimentStatus_EXPERIMENT_STATUS_PAUSED { + var ev *treatment.Evidence + if source != nil && snapshot.Status != treatment.StatusPaused { input := snapshot - if snapshot.Status == pb.ExperimentStatus_EXPERIMENT_STATUS_COMPLETE { - input = proto.Clone(snapshot).(*pb.Experiment) - p := input.Phases[len(input.Phases)-1] - p.IssuedAt = timestamppb.New(s.now().Add(-time.Hour)) - p.ObservationStartedAt = p.IssuedAt + if snapshot.Status == treatment.StatusComplete { + input = s.driftInput(snapshot) } ev, e = source.Analyze(ctx, input) if e != nil { failures = append(failures, fmt.Errorf("%s analysis: %w", id, e)) } } - e = s.Store.mutate(ctx, func(tx *sql.Tx) error { - n, e := loadExperiment(ctx, tx, id) + e = s.Store.Tx(ctx, func(rp store.Repos) error { + n, e := rp.Experiments.Get(ctx, id) if e != nil { return e } if n.Revision != snapshot.Revision { return nil } - phase := n.Phases[len(n.Phases)-1] + phase := n.CurrentPhase() now := s.now() - outcome := experiment.OutcomeUnavailable - if n.Status == pb.ExperimentStatus_EXPERIMENT_STATUS_COMPLETE { + outcome := treatment.OutcomeUnavailable + if n.Status == treatment.StatusComplete { if ev != nil { - if e = saveEvidence(ctx, tx, id, ev); e != nil { + if e = saveEvidence(ctx, rp, id, ev); e != nil { return e } healthy, harmful := experiment.HealthyEvidence(n, ev, now) n.LatestEvidence = ev - n.LatestOutcome = pb.Outcome_OUTCOME_STABLE_OBSERVED + n.LatestOutcome = treatment.OutcomeStableObserved if !healthy { - n.LatestOutcome = pb.Outcome_OUTCOME_STABLE_EVIDENCE_INCOMPLETE + n.LatestOutcome = treatment.OutcomeStableEvidenceIncomplete } if harmful { - n.LatestOutcome = pb.Outcome_OUTCOME_STABLE_REGRESSION_REVIEW_REQUIRED - if e = audit(ctx, tx, "drift-monitor", "", "stable_regression", ev); e != nil { + n.LatestOutcome = treatment.OutcomeStableRegressionReviewRequired + if e = audit(ctx, rp, "drift-monitor", "", "stable_regression", wire.Evidence(ev)); e != nil { return e } } } - return saveExperiment(ctx, tx, n) + return s.save(ctx, rp, n) } - if now.Sub(phase.IssuedAt.AsTime()) >= n.Policy.MaximumPhase.AsDuration() { - outcome = experiment.OutcomeExpired + if now.Sub(phase.IssuedAt) >= n.Policy.MaximumPhase { + outcome = treatment.OutcomeExpired } else if ev != nil { - if e = saveEvidence(ctx, tx, id, ev); e != nil { + if e = saveEvidence(ctx, rp, id, ev); e != nil { return e } v, e := experiment.Evaluate(n, ev, now) @@ -130,38 +121,38 @@ func (s *Server) Maintain(ctx context.Context, source EvidenceSource) error { // approved/evaluated. Only Review writes reviewer and outcome. n.LatestEvidence = ev if _, harmful := experiment.HealthyEvidence(n, ev, now); harmful { - outcome = experiment.OutcomeHarmful + outcome = treatment.OutcomeHarmful } } - n.LatestOutcome = outcome.Wire() + n.LatestOutcome = outcome if outcome.Withdraws() { event := experiment.EventExpire - if outcome == experiment.OutcomeHarmful { + if outcome == treatment.OutcomeHarmful { event = experiment.EventHarm } if n.Status, e = experiment.Transition(n.Status, event); e != nil { return e } - phase.Outcome = outcome.Wire() - phase.EvaluatedAt = timestamppb.New(now) + phase.Outcome = outcome + phase.EvaluatedAt = now n.Revision++ - if e = s.assign(ctx, tx, n, false); e != nil { + if e = s.assign(ctx, rp, n, false); e != nil { return e } - if e = audit(ctx, tx, "safety-controller", "", "automatic_withdrawal", n); e != nil { + if e = audit(ctx, rp, "safety-controller", "", "automatic_withdrawal", wire.Experiment(n)); e != nil { return e } - } else if ev != nil && n.Status == pb.ExperimentStatus_EXPERIMENT_STATUS_RUNNING { + } else if ev != nil && n.Status == treatment.StatusRunning { // Before the 30-minute decision window completes, fresh health // evidence can renew the existing phase but cannot expand it. healthy, _ := experiment.HealthyEvidence(n, ev, now) if healthy && outcome.RenewsLease() { - if e = s.renew(ctx, tx, n, now); e != nil { + if e = s.renew(ctx, rp, n, now); e != nil { return e } } } - return saveExperiment(ctx, tx, n) + return s.save(ctx, rp, n) }) if e != nil { failures = append(failures, e) @@ -170,44 +161,41 @@ func (s *Server) Maintain(ctx context.Context, source EvidenceSource) error { return errors.Join(failures...) } -func (s *Server) renew(ctx context.Context, tx *sql.Tx, n *pb.Experiment, now time.Time) error { - for _, id := range n.CohortOrder[:n.Phases[len(n.Phases)-1].TargetHosts] { - d, e := desired(ctx, tx, id) +func (s *Server) renew(ctx context.Context, rp store.Repos, n *treatment.Experiment, now time.Time) error { + for _, id := range n.CohortOrder[:n.Target()] { + d, e := rp.Agents.Desired(ctx, id) if e != nil { return e } if d.Suspended { continue } - var raw []byte - var seen time.Time - if e = tx.QueryRowContext(ctx, "SELECT observed,last_seen FROM xtcp_control_agents WHERE id=$1", id).Scan(&raw, &seen); e != nil { - return e - } - o := new(pb.CheckInRequest) - if e = protojson.Unmarshal(raw, o); e != nil { + a, e := rp.Agents.Get(ctx, id) + if e != nil { return e } - if now.Sub(seen) > 2*time.Minute || o.LocallySuspended || o.DesiredRevision != d.Revision { + if !a.Seen(now, agentWindow) || a.Report.LocallySuspended || a.Report.DesiredRevision != d.Revision { continue } - for _, a := range d.Assignments { - if a.ExperimentId != n.Id || a.Mode != pb.AssignmentMode_ASSIGNMENT_MODE_TRIAL || a.LeaseExpiresAt == nil || !a.LeaseExpiresAt.AsTime().After(now) { + for i := range d.Assignments { + x := &d.Assignments[i] + if x.ExperimentID != n.ID || x.Mode != treatment.ModeTrial || x.LeaseExpiresAt.IsZero() || !x.LeaseExpiresAt.After(now) { continue } verified := false - for _, r := range o.Records { - if r.Assignment.ExperimentId == a.ExperimentId && r.Assignment.Generation == a.Generation && r.Status == pb.ApplyStatus_APPLY_STATUS_VERIFIED { + for j := range a.Report.Records { + r := &a.Report.Records[j] + if r.Assignment.ExperimentID == x.ExperimentID && r.Assignment.Generation == x.Generation && r.Status == treatment.ApplyVerified { verified = true } } - if !verified || a.LeaseExpiresAt.AsTime().After(now.Add(treatment.TrialLease-treatment.LeaseRenewInterval)) { + if !verified || x.LeaseExpiresAt.After(now.Add(treatment.TrialLease-treatment.LeaseRenewInterval)) { continue } - a.LeaseExpiresAt = timestamppb.New(now.Add(treatment.TrialLease)) + x.LeaseExpiresAt = now.Add(treatment.TrialLease) d.Revision++ - d.IssuedAt = timestamppb.New(now) - if e = saveDesired(ctx, tx, d); e != nil { + d.IssuedAt = now + if e = rp.Agents.SaveDesired(ctx, d); e != nil { return e } } diff --git a/pkg/treatment/controller/notifications.go b/pkg/treatment/controller/notifications.go index 4f7a37525..d9f622bdb 100644 --- a/pkg/treatment/controller/notifications.go +++ b/pkg/treatment/controller/notifications.go @@ -57,16 +57,10 @@ func (s *Server) RunNotifications(ctx context.Context) { t := time.NewTicker(2 * time.Second) defer t.Stop() for { - rows, e := s.Store.DB.QueryContext(ctx, "SELECT id,(desired->>'revision')::bigint FROM xtcp_control_agents") - if e == nil { - for rows.Next() { - var id string - var rev uint64 - if rows.Scan(&id, &rev) == nil { - s.Hub.publish(id, rev) - } + if revisions, e := s.Store.Repos().Agents.DesiredRevisions(ctx); e == nil { + for id, rev := range revisions { + s.Hub.publish(id, rev) } - rows.Close() } select { case <-ctx.Done(): diff --git a/pkg/treatment/controller/postgres_indexes_test.go b/pkg/treatment/controller/postgres_indexes_test.go deleted file mode 100644 index 41a9f32cf..000000000 --- a/pkg/treatment/controller/postgres_indexes_test.go +++ /dev/null @@ -1,210 +0,0 @@ -package controller - -import ( - "context" - "database/sql" - "encoding/json" - "errors" - "fmt" - "net/http" - "strings" - "sync" - "testing" - "time" - - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" - "google.golang.org/protobuf/proto" -) - -func TestPostgresDiscoveryEligibility(t *testing.T) { - for _, tc := range []struct { - name, description, status string - age time.Duration - location, destination string - wantBlocked, wantActive bool - }{ - {"proposed", "pending review reserves the destination", "PROPOSED", 48 * time.Hour, "dc1", "192.0.2.1/32", true, true}, - {"running", "an active trial is not rediscovered", "RUNNING", 48 * time.Hour, "dc1", "192.0.2.1/32", true, true}, - {"paused", "paused trials remain reserved", "PAUSED", 48 * time.Hour, "dc1", "192.0.2.1/32", true, true}, - {"suspended", "temporary holds must not be bypassed", "SUSPENDED", 48 * time.Hour, "dc1", "192.0.2.1/32", true, true}, - {"stable", "completed stable treatments remain reserved indefinitely", "COMPLETE", 48 * time.Hour, "dc1", "192.0.2.1/32", true, false}, - {"recent-rollback", "recent withdrawal is in cooldown", "ROLLED_BACK", 23 * time.Hour, "dc1", "192.0.2.1/32", true, false}, - {"recent-retirement", "recent retirement is in cooldown", "RETIRED", 23 * time.Hour, "dc1", "192.0.2.1/32", true, false}, - {"boundary", "cooldown ends at exactly 24 hours", "ROLLED_BACK", 24 * time.Hour, "dc1", "192.0.2.1/32", false, false}, - {"old-retirement", "old retirement does not reserve destination", "RETIRED", 25 * time.Hour, "dc1", "192.0.2.1/32", false, false}, - {"other-dc", "reservation is data-center specific", "RUNNING", time.Hour, "dc2", "192.0.2.1/32", false, false}, - {"other-destination", "another destination consumes the DC budget only", "RUNNING", time.Hour, "dc1", "192.0.2.2/32", false, true}, - } { - t.Run(tc.name, func(t *testing.T) { - s := testStore(t) - now := time.Now().UTC().Truncate(time.Microsecond) - state := fmt.Sprintf(`{"status":"EXPERIMENT_STATUS_%s","treatment":{"destination":%q}}`, tc.status, tc.destination) - if _, err := s.DB.Exec("INSERT INTO xtcp_control_experiments(id,location,state,updated_at) VALUES('prior',$1,$2,$3)", tc.location, state, now.Add(-tc.age)); err != nil { - t.Fatal(err) - } - blocked, err := discoveryDestinationBlocked(context.Background(), s.DB, "dc1", "192.0.2.1/32", now) - if err != nil || blocked != tc.wantBlocked { - t.Fatalf("%s: blocked=%t err=%v want=%t", tc.description, blocked, err, tc.wantBlocked) - } - active, err := hasActiveExperiment(context.Background(), s.DB, "dc1") - if err != nil || active != tc.wantActive { - t.Fatalf("%s: active=%t err=%v want=%t", tc.description, active, err, tc.wantActive) - } - }) - } -} - -func TestPostgresDiscoverySkipsBusyWarehouse(t *testing.T) { - store := testStore(t) - if _, err := store.DB.Exec(`INSERT INTO xtcp_control_experiments(id,location,state) VALUES('active','dc1','{"status":"EXPERIMENT_STATUS_RUNNING"}')`); err != nil { - t.Fatal(err) - } - s := &Server{Store: store} - called := false - ch := &ClickHouse{URL: "http://fixture.invalid", Table: "db.rows", Client: &http.Client{Transport: discoveryTransport(func(*http.Request) (*http.Response, error) { - called = true - return nil, errors.New("warehouse must not be queried") - })}} - templates := []DiscoveryTemplate{{ID: "nearby", Location: "dc1", CongestionControl: "cubic", MaximumRTTMs: 15}} - if err := s.Discover(context.Background(), ch, templates); err != nil || called { - t.Fatalf("busy location queried warehouse: called=%t err=%v", called, err) - } - if err := store.DB.Close(); err != nil { - t.Fatal(err) - } - if err := s.Discover(context.Background(), ch, templates); err == nil || called { - t.Fatalf("database failure must stop discovery: called=%t err=%v", called, err) - } -} - -func TestPostgresDiscoveryTransactionFence(t *testing.T) { - now := time.Now().UTC() - s := &Server{Store: testStore(t), Now: func() time.Time { return now }} - for i := range 8 { - enroll(t, s, fmt.Sprintf("host-%d", i)) - } - // Reproduce a stable treatment appearing after discovery's initial lookup. - if _, err := s.Store.DB.Exec(`INSERT INTO xtcp_control_experiments(id,location,state) VALUES('stable','dc1','{"status":"EXPERIMENT_STATUS_COMPLETE","treatment":{"destination":"192.0.2.1/32"}}')`); err != nil { - t.Fatal(err) - } - if _, err := s.propose(context.Background(), proposal(now), "candidate-discovery", true); !errors.Is(err, errDiscoverySuppressed) { - t.Fatalf("transaction did not suppress newly stable destination: %v", err) - } - // Operator-driven replacement remains possible; route assignment will retain - // the stable predecessor and still require review before applying anything. - if _, err := s.Propose(testIdentity("operator/alice"), proposal(now)); err != nil { - t.Fatalf("explicit replacement was blocked: %v", err) - } -} - -func TestPostgresConcurrentDiscoveryProposals(t *testing.T) { - now := time.Now().UTC() - s := &Server{Store: testStore(t), Now: func() time.Time { return now }} - for i := range 8 { - enroll(t, s, fmt.Sprintf("host-%d", i)) - } - start := make(chan struct{}) - results := make(chan error, 2) - var wg sync.WaitGroup - for i := range 2 { - wg.Go(func() { - r := proto.Clone(proposal(now)).(*pb.ProposeRequest) - r.RequestId = fmt.Sprintf("candidate-%d", i) - r.Experiment.Id = r.RequestId - <-start - _, err := s.propose(context.Background(), r, "candidate-discovery", true) - results <- err - }) - } - close(start) - wg.Wait() - close(results) - successes, suppressed := 0, 0 - for err := range results { - if err == nil { - successes++ - } else if errors.Is(err, errDiscoverySuppressed) { - suppressed++ - } else { - t.Fatal(err) - } - } - if successes != 1 || suppressed != 1 { - t.Fatalf("want one proposal and one suppressed competitor; got %d/%d", successes, suppressed) - } - var count int - if err := s.Store.DB.QueryRow("SELECT count(*) FROM xtcp_control_experiments").Scan(&count); err != nil || count != 1 { - t.Fatalf("duplicate persisted proposal: count=%d err=%v", count, err) - } -} - -func TestPostgresExperimentQueryIndexes(t *testing.T) { - s := testStore(t) - const seed = `INSERT INTO xtcp_control_experiments(id,location,state,updated_at) - SELECT 'history-'||i,'dc1',jsonb_build_object('status','EXPERIMENT_STATUS_RETIRED', - 'treatment',jsonb_build_object('destination','10.'||(i/65536)||'.'||((i/256)%256)||'.'||(i%256)||'/32')),now()-interval '48 hours' - FROM generate_series(1,100000) i; - INSERT INTO xtcp_control_experiments(id,location,state) - SELECT 'active-'||i,'active-dc-'||i,jsonb_build_object('status','EXPERIMENT_STATUS_RUNNING', - 'treatment',jsonb_build_object('destination','192.0.2.1/32')) FROM generate_series(1,5) i; - ANALYZE xtcp_control_experiments;` - if _, err := s.DB.Exec(seed); err != nil { - t.Fatal(err) - } - // Existing databases can re-run the migration without duplicating indexes. - if err := s.Migrate(context.Background()); err != nil { - t.Fatal(err) - } - for _, tc := range []struct { - name, description, query, index string - args []any - }{ - {"old-destination", "history lookup is selective by location and destination", discoveryDestinationSQL, "xtcp_control_experiments_destination", []any{"dc1", "10.0.0.1/32", time.Now().Add(-24 * time.Hour)}}, - {"absent-destination", "missing destination does not scan historical experiments", discoveryDestinationSQL, "xtcp_control_experiments_destination", []any{"dc1", "203.0.113.1/32", time.Now().Add(-24 * time.Hour)}}, - {"active-budget", "active budget lookup uses status and location", activeExperimentSQL, "xtcp_control_experiments_status_location", []any{"active-dc-1"}}, - {"idle-budget", "idle DC does not scan retired history", activeExperimentSQL, "xtcp_control_experiments_status_location", []any{"dc1"}}, - } { - t.Run(tc.name, func(t *testing.T) { - explain := func(q rowQuerier) ([]byte, float64) { - t.Helper() - var raw []byte - if err := q.QueryRowContext(context.Background(), "EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) "+tc.query, tc.args...).Scan(&raw); err != nil { - t.Fatal(err) - } - var plan []struct { - Milliseconds float64 `json:"Execution Time"` - } - if err := json.Unmarshal(raw, &plan); err != nil { - t.Fatal(err) - } - return raw, plan[0].Milliseconds - } - indexed, after := explain(s.DB) - used := tc.index - // For a location containing only one row, the destination index's - // location prefix is also an efficient budget lookup. Either plan - // is valid; don't force PostgreSQL's cost-based choice. - if tc.query == activeExperimentSQL && strings.Contains(string(indexed), `"Index Name": "xtcp_control_experiments_destination"`) { - used = "xtcp_control_experiments_destination" - } - if !strings.Contains(string(indexed), `"Index Name": "`+used+`"`) { - t.Fatalf("%s: expected planner to choose %s without forced settings: %s", tc.description, tc.index, indexed) - } - // Transactional DDL gives a real no-index comparison and rollback restores - // the production schema. Timings are evidence, never a flaky speed assertion. - tx, err := s.DB.BeginTx(context.Background(), &sql.TxOptions{}) - if err != nil { - t.Fatal(err) - } - defer tx.Rollback() - if _, err = tx.Exec("DROP INDEX xtcp_control_experiments_destination; DROP INDEX xtcp_control_experiments_status_location"); err != nil { - t.Fatal(err) - } - _, before := explain(tx) - if err = tx.Rollback(); err != nil { - t.Fatal(err) - } - t.Logf("100005 experiments: unindexed %.3f ms, indexed %.3f ms (%s)", before, after, used) - }) - } -} diff --git a/pkg/treatment/controller/rpc_contracts_test.go b/pkg/treatment/controller/rpc_contracts_test.go index f25d07cf8..fa75c8a53 100644 --- a/pkg/treatment/controller/rpc_contracts_test.go +++ b/pkg/treatment/controller/rpc_contracts_test.go @@ -2,169 +2,160 @@ package controller import ( "context" - "crypto/tls" - "crypto/x509" "errors" "net/http" "net/http/httptest" - "net/url" "strings" "testing" "time" pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" - "google.golang.org/grpc" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store/postgres" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) +// TestCheckInValidation runs without a store: every row must be rejected +// before the handler touches persistence. func TestCheckInValidation(t *testing.T) { for _, tc := range []struct { name, description string change func(*pb.CheckInRequest) - want codes.Code + expected codes.Code }{ - {"wrong-host", "certificate identity cannot report another host", func(r *pb.CheckInRequest) { r.AgentId = "other" }, codes.PermissionDenied}, - {"wrong-location", "certificate location is authoritative", func(r *pb.CheckInRequest) { r.Location = "other" }, codes.InvalidArgument}, - {"missing-capabilities", "capabilities must be present", func(r *pb.CheckInRequest) { r.Capabilities = nil }, codes.InvalidArgument}, - {"invalid-boot", "boot identity is required", func(r *pb.CheckInRequest) { r.Capabilities.BootId = "" }, codes.InvalidArgument}, - {"long-kernel", "kernel version is bounded", func(r *pb.CheckInRequest) { r.Capabilities.KernelRelease = strings.Repeat("a", 257) }, codes.InvalidArgument}, - {"long-algorithm", "algorithm names fit kernel ABI", func(r *pb.CheckInRequest) { r.Capabilities.CongestionAlgorithms = []string{strings.Repeat("a", 16)} }, codes.InvalidArgument}, - {"nil-record", "nil application record fails closed", func(r *pb.CheckInRequest) { r.Records = []*pb.ApplicationRecord{nil} }, codes.InvalidArgument}, - {"kernel-bytes", "kernel route bytes must remain local", func(r *pb.CheckInRequest) { + {"wrong-host", "negative: certificate identity cannot report another host", func(r *pb.CheckInRequest) { r.AgentId = "other" }, codes.PermissionDenied}, + {"wrong-location", "negative: certificate location is authoritative", func(r *pb.CheckInRequest) { r.Location = "other" }, codes.InvalidArgument}, + {"missing-capabilities", "negative: capabilities must be present", func(r *pb.CheckInRequest) { r.Capabilities = nil }, codes.InvalidArgument}, + {"invalid-boot", "negative: boot identity is required", func(r *pb.CheckInRequest) { r.Capabilities.BootId = "" }, codes.InvalidArgument}, + {"long-kernel", "boundary: kernel version is bounded at 256 bytes", func(r *pb.CheckInRequest) { r.Capabilities.KernelRelease = strings.Repeat("a", 257) }, codes.InvalidArgument}, + {"long-algorithm", "boundary: algorithm names fit the 15-byte kernel ABI", func(r *pb.CheckInRequest) { r.Capabilities.CongestionAlgorithms = []string{strings.Repeat("a", 16)} }, codes.InvalidArgument}, + {"nil-record", "corner: a nil application record fails closed", func(r *pb.CheckInRequest) { r.Records = []*pb.ApplicationRecord{nil} }, codes.InvalidArgument}, + {"kernel-bytes", "corner: kernel route bytes must remain local", func(r *pb.CheckInRequest) { r.Records = []*pb.ApplicationRecord{{Assignment: &pb.Assignment{ExperimentId: "assignment"}, InstalledRoute: []byte{1}}} }, codes.InvalidArgument}, + {"unknown-status", "corner: an apply status from a newer agent is rejected, not defaulted", func(r *pb.CheckInRequest) { + r.Records = []*pb.ApplicationRecord{{Assignment: &pb.Assignment{ExperimentId: "assignment", Treatment: &pb.Treatment{Id: "cubic", Revision: 1, Destination: "192.0.2.1/32", Settings: &pb.TCPSettings{}}}, Status: pb.ApplyStatus(200)}} + }, codes.InvalidArgument}, } { t.Run(tc.name, func(t *testing.T) { r := &pb.CheckInRequest{AgentId: "host", Location: "dc1", Capabilities: &pb.Capabilities{BootId: "boot", CongestionAlgorithms: []string{"cubic"}}} tc.change(r) _, err := (&Server{}).CheckIn(testIdentity("agent/dc1/host"), r) - if status.Code(err) != tc.want { - t.Fatalf("%s: got %v want %v", tc.description, err, tc.want) + if status.Code(err) != tc.expected { + t.Fatalf("%s: got %v expected %v", tc.description, err, tc.expected) } }) } } -func operatorHTTP(method, path, identity string) *http.Request { - r := httptest.NewRequest(method, path, nil) - if identity != "" { - u, _ := url.Parse("spiffe://xtcp/" + identity) - cert := &x509.Certificate{URIs: []*url.URL{u}} - r.TLS = &tls.ConnectionState{PeerCertificates: []*x509.Certificate{cert}, VerifiedChains: [][]*x509.Certificate{{cert}}} - } - return r -} func TestStatusAuthorization(t *testing.T) { for _, tc := range []struct { description, method, path, identity string - want int + expected int }{ - {"mTLS is mandatory", "GET", "/status", "", 401}, - {"agents cannot inspect fleet status", "GET", "/status", "agent/dc1/host", 403}, - {"status has no mutation endpoint", "POST", "/status", "operator/alice", 405}, - {"unknown paths return not found", "GET", "/unknown", "operator/alice", 404}, + {"negative: mTLS is mandatory", "GET", "/status", "", 401}, + {"negative: agents cannot inspect fleet status", "GET", "/status", "agent/dc1/host", 403}, + {"negative: status has no mutation endpoint", "POST", "/status", "operator/alice", 405}, + {"negative: unknown paths return not found", "GET", "/unknown", "operator/alice", 404}, } { t.Run(tc.description, func(t *testing.T) { w := httptest.NewRecorder() (&Server{}).StatusHandler().ServeHTTP(w, operatorHTTP(tc.method, tc.path, tc.identity)) - if w.Code != tc.want { - t.Fatalf("got %d want %d", w.Code, tc.want) + if w.Code != tc.expected { + t.Fatalf("got %d expected %d", w.Code, tc.expected) } }) } } -func TestPostgresStatusAndDesiredAuthorization(t *testing.T) { - s := &Server{Store: testStore(t)} - enroll(t, s, "host") - for _, tc := range []struct { - description, identity string - want codes.Code - }{ - {"enrolled identity can fetch its desired state", "agent/dc1/host", codes.OK}, - {"enrollment location cannot change", "agent/other/host", codes.PermissionDenied}, - {"different host cannot fetch desired state", "agent/dc1/other", codes.PermissionDenied}, - {"operator identity is not an agent", "operator/alice", codes.PermissionDenied}, - } { - t.Run(tc.description, func(t *testing.T) { - got, err := s.GetDesiredState(testIdentity(tc.identity), &pb.GetDesiredStateRequest{AgentId: "host"}) - if status.Code(err) != tc.want { - t.Fatalf("got %v want %v", err, tc.want) - } - if err == nil && (got.Desired.AgentId != "host" || got.Desired.Revision != 1) { - t.Fatal("wrong desired state") - } - }) - } - for _, path := range []string{"/status", "/metrics", "/readyz"} { - t.Run(path, func(t *testing.T) { - w := httptest.NewRecorder() - s.StatusHandler().ServeHTTP(w, operatorHTTP(http.MethodGet, path, "operator/alice")) - if w.Code != 200 || w.Body.Len() == 0 { - t.Fatalf("got %d %s", w.Code, w.Body.String()) - } - if path == "/status" && !strings.Contains(w.Body.String(), `"agents":1`) { - t.Fatal("enrolled agent missing from status") - } - }) - } - if err := s.Store.DB.Close(); err != nil { - t.Fatal(err) - } - w := httptest.NewRecorder() - s.StatusHandler().ServeHTTP(w, operatorHTTP(http.MethodGet, "/readyz", "operator/alice")) - if w.Code != 503 { - t.Fatalf("closed DB reported ready: %d", w.Code) - } -} - -type desiredTestStream struct { - grpc.ServerStream - ctx context.Context - sent chan uint64 -} - -func (s *desiredTestStream) Context() context.Context { return s.ctx } -func (s *desiredTestStream) Send(r *pb.WatchDesiredStateResponse) error { - select { - case s.sent <- r.Revision: - return nil - case <-s.ctx.Done(): - return s.ctx.Err() - } +func TestStatusAndDesiredAuthorization(t *testing.T) { + eachStore(t, func(t *testing.T, st store.Store) { + now := time.Now().UTC() + s := testServer(st, &now) + enroll(t, s, "host") + for _, tc := range []struct { + description, identity string + expected codes.Code + }{ + {"positive: enrolled identity can fetch its desired state", "agent/dc1/host", codes.OK}, + {"negative: enrollment location cannot change", "agent/other/host", codes.PermissionDenied}, + {"negative: a different host cannot fetch desired state", "agent/dc1/other", codes.PermissionDenied}, + {"negative: operator identity is not an agent", "operator/alice", codes.PermissionDenied}, + } { + t.Run(tc.description, func(t *testing.T) { + got, err := s.GetDesiredState(testIdentity(tc.identity), &pb.GetDesiredStateRequest{AgentId: "host"}) + if status.Code(err) != tc.expected { + t.Fatalf("got %v expected %v", err, tc.expected) + } + if err == nil && (got.Desired.AgentId != "host" || got.Desired.Revision != 1) { + t.Fatal("wrong desired state") + } + }) + } + for _, path := range []string{"/status", "/metrics", "/readyz"} { + t.Run(path, func(t *testing.T) { + w := httptest.NewRecorder() + s.StatusHandler().ServeHTTP(w, operatorHTTP(http.MethodGet, path, "operator/alice")) + if w.Code != 200 || w.Body.Len() == 0 { + t.Fatalf("got %d %s", w.Code, w.Body.String()) + } + if path == "/status" && !strings.Contains(w.Body.String(), `"agents":1`) { + t.Fatal("enrolled agent missing from status") + } + if path == "/metrics" && !strings.Contains(w.Body.String(), "xtcp_controller_agents_connected 1") { + t.Fatalf("connected agent missing from metrics: %s", w.Body.String()) + } + }) + } + pg, ok := st.(*postgres.Store) + if !ok { + return + } + if err := pg.Close(); err != nil { + t.Fatal(err) + } + w := httptest.NewRecorder() + s.StatusHandler().ServeHTTP(w, operatorHTTP(http.MethodGet, "/readyz", "operator/alice")) + if w.Code != 503 { + t.Fatalf("closed database reported ready: %d", w.Code) + } + }) } -func TestPostgresWatchLifecycle(t *testing.T) { - s := &Server{Store: testStore(t), Hub: NewNotificationHub()} - enroll(t, s, "host") - ctx, cancel := context.WithTimeout(testIdentity("agent/dc1/host"), 5*time.Second) - defer cancel() - stream := &desiredTestStream{ctx: ctx, sent: make(chan uint64, 4)} - done := make(chan error, 1) - go func() { done <- s.WatchDesiredState(&pb.WatchDesiredStateRequest{AgentId: "host"}, stream) }() - receive := func(want uint64) { - t.Helper() - select { - case got := <-stream.sent: - if got != want { - t.Fatalf("revision=%d want %d", got, want) +func TestWatchLifecycle(t *testing.T) { + eachStore(t, func(t *testing.T, st store.Store) { + now := time.Now().UTC() + s := testServer(st, &now) + enroll(t, s, "host") + ctx, cancel := context.WithTimeout(testIdentity("agent/dc1/host"), 5*time.Second) + defer cancel() + stream := &desiredTestStream{ctx: ctx, sent: make(chan uint64, 4)} + done := make(chan error, 1) + go func() { done <- s.WatchDesiredState(&pb.WatchDesiredStateRequest{AgentId: "host"}, stream) }() + receive := func(want uint64) { + t.Helper() + select { + case got := <-stream.sent: + if got != want { + t.Fatalf("revision=%d want %d", got, want) + } + case <-ctx.Done(): + t.Fatal("watch timed out") } - case <-ctx.Done(): - t.Fatal("watch timed out") } - } - receive(1) - s.Hub.publish("host", 1) - s.Hub.publish("host", 2) - receive(2) - cancel() - if err := <-done; !errors.Is(err, context.Canceled) { - t.Fatalf("watch cancellation: %v", err) - } - s.Hub.mu.Lock() - defer s.Hub.mu.Unlock() - if len(s.Hub.subs) != 0 { - t.Fatal("watch leaked subscription") - } + receive(1) + s.Hub.publish("host", 1) + s.Hub.publish("host", 2) + receive(2) + cancel() + if err := <-done; !errors.Is(err, context.Canceled) { + t.Fatalf("watch cancellation: %v", err) + } + s.Hub.mu.Lock() + defer s.Hub.mu.Unlock() + if len(s.Hub.subs) != 0 { + t.Fatal("watch leaked subscription") + } + }) } diff --git a/pkg/treatment/controller/schema.sql b/pkg/treatment/controller/schema.sql deleted file mode 100644 index 31462538a..000000000 --- a/pkg/treatment/controller/schema.sql +++ /dev/null @@ -1,45 +0,0 @@ --- Control-plane state is transactional. ClickHouse is never a coordination lock. -CREATE TABLE IF NOT EXISTS xtcp_control_agents ( - id text PRIMARY KEY, - location text NOT NULL, - desired jsonb NOT NULL, - observed jsonb, - last_seen timestamptz, - suspended boolean NOT NULL DEFAULT false -); -CREATE INDEX IF NOT EXISTS xtcp_control_agents_location ON xtcp_control_agents(location); -CREATE TABLE IF NOT EXISTS xtcp_control_experiments ( - id text PRIMARY KEY, - location text NOT NULL, - state jsonb NOT NULL -); -ALTER TABLE xtcp_control_experiments ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT now(); --- Match discovery's equality lookups and its terminal-experiment cooldown. --- Do not index the large evidence-bearing state document as a whole. -CREATE INDEX IF NOT EXISTS xtcp_control_experiments_destination - ON xtcp_control_experiments(location, (state->'treatment'->>'destination'), updated_at DESC); --- Status first also supports maintenance across all data centers; location --- narrows the one-active-experiment budget check. -CREATE INDEX IF NOT EXISTS xtcp_control_experiments_status_location - ON xtcp_control_experiments((state->>'status'), location); -CREATE TABLE IF NOT EXISTS xtcp_control_holds ( - scope text PRIMARY KEY, - reason text NOT NULL -); -CREATE TABLE IF NOT EXISTS xtcp_control_events ( - sequence bigserial PRIMARY KEY, - occurred_at timestamptz NOT NULL DEFAULT now(), - actor text NOT NULL, - request_id text UNIQUE, - kind text NOT NULL, - payload jsonb NOT NULL -); --- Durable event rows are the outbox and audit log. Watch subscribers only need --- revision hints; reconnect/check-in always fetches authoritative agent state. -CREATE INDEX IF NOT EXISTS xtcp_control_events_time ON xtcp_control_events(occurred_at); -CREATE TABLE IF NOT EXISTS xtcp_control_evidence ( - id text PRIMARY KEY, - experiment_id text NOT NULL, - received_at timestamptz NOT NULL DEFAULT now(), - state jsonb NOT NULL -); diff --git a/pkg/treatment/controller/server.go b/pkg/treatment/controller/server.go index 82680c06b..21e48ee04 100644 --- a/pkg/treatment/controller/server.go +++ b/pkg/treatment/controller/server.go @@ -1,9 +1,13 @@ +// Package controller implements the treatment control plane's gRPC services +// and its background loops over the ports in package store. Handlers decode +// wire messages into domain values at the boundary (package wire), apply the +// pure rules in package experiment, and persist through a store transaction +// that serializes every control-plane mutation. package controller import ( "context" "crypto/sha256" - "database/sql" "errors" "fmt" "sort" @@ -12,20 +16,25 @@ import ( pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store" "github.com/randomizedcoder/xtcp2/pkg/treatment/experiment" "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" + "github.com/randomizedcoder/xtcp2/pkg/treatment/wire" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/timestamppb" ) +// agentWindow is how recently an agent must have checked in to count as +// present for eligibility, observation and lease renewal. +const agentWindow = 2 * time.Minute + type Server struct { pb.UnimplementedAgentControlServiceServer pb.UnimplementedOperatorServiceServer - Store *Store + Store store.Store Hub *NotificationHub EvidenceSource EvidenceSource Now func() time.Time @@ -33,12 +42,18 @@ type Server struct { DiscoveryWindow time.Duration } +var errExperimentBudget = errors.New("data-center experiment budget exhausted (one active experiment)") +var errDiscoverySuppressed = errors.New("destination already treated, held, proposed, or in cooldown") + func (s *Server) now() time.Time { if s.Now != nil { return s.Now() } return time.Now() } + +// failure maps store and domain errors onto gRPC codes. Unknown errors are +// precondition failures: the request was well formed but the state refused it. func failure(e error) error { if e == nil { return nil @@ -46,7 +61,7 @@ func failure(e error) error { if status.Code(e) != codes.Unknown { return e } - if errors.Is(e, sql.ErrNoRows) { + if errors.Is(e, store.ErrNotFound) || errors.Is(e, store.ErrNotEnrolled) { return status.Error(codes.NotFound, "not found") } if errors.Is(e, context.Canceled) { @@ -54,6 +69,9 @@ func failure(e error) error { } return status.Error(codes.FailedPrecondition, e.Error()) } + +func invalid(e error) error { return status.Error(codes.InvalidArgument, e.Error()) } + func validRequest(id, reason string) error { if !treatment.ValidID(id) || strings.HasSuffix(id, ".result") || reason == "" || len(reason) > 4096 { return status.Error(codes.InvalidArgument, "request ID and bounded reason required") @@ -61,85 +79,114 @@ func validRequest(id, reason string) error { return nil } +func jsonMessage(m proto.Message) ([]byte, error) { return protojson.Marshal(m) } + +// audit appends one event whose payload is the protojson of a wire message. +func audit(ctx context.Context, rp store.Repos, actor, requestID, kind string, m proto.Message) error { + b, e := jsonMessage(m) + if e != nil { + return e + } + return rp.Audit.Append(ctx, actor, requestID, kind, b) +} + +// replay answers a repeated request ID with the original response. The +// repeat must be the same operation with the same body; anything else is a +// conflict rather than a second mutation. +func replay(ctx context.Context, rp store.Repos, id, kind string, request, response proto.Message) (bool, error) { + ev, e := rp.Audit.Lookup(ctx, id) + if errors.Is(e, store.ErrNotFound) { + return false, nil + } + if e != nil { + return false, e + } + if ev.Kind != kind { + return false, fmt.Errorf("%w for another operation", store.ErrRequestConflict) + } + want, e := jsonMessage(request) + if e != nil { + return false, e + } + stored := request.ProtoReflect().New().Interface() + if e = protojson.Unmarshal(ev.Payload, stored); e != nil { + return false, e + } + got, e := jsonMessage(stored) + if e != nil { + return false, e + } + if string(got) != string(want) { + return false, fmt.Errorf("%w with different body", store.ErrRequestConflict) + } + result, e := rp.Audit.Lookup(ctx, id+".result") + if e != nil { + return false, fmt.Errorf("missing idempotent response: %w", e) + } + return true, protojson.Unmarshal(result.Payload, response) +} + +func holdScopes(location, agentID string) []string { + return []string{"fleet", "location:" + location, "agent:" + agentID} +} + func (s *Server) CheckIn(ctx context.Context, r *pb.CheckInRequest) (*pb.CheckInResponse, error) { id, e := identity.Agent(ctx, r.GetAgentId()) if e != nil { return nil, e } - if id.Location != r.Location || r.Capabilities == nil || !treatment.ValidID(r.Capabilities.BootId) || len(r.Capabilities.KernelRelease) > 256 || len(r.Capabilities.CongestionAlgorithms) > 64 || len(r.Records) > treatment.MaxAssignments*2 { + report, e := wire.ParseReport(r) + if e != nil { + return nil, invalid(e) + } + if id.Location != report.Location || r.Capabilities == nil { return nil, status.Error(codes.InvalidArgument, "invalid agent report") } - for _, a := range r.Capabilities.CongestionAlgorithms { - if !treatment.ValidID(a) || len(a) > 15 { - return nil, status.Error(codes.InvalidArgument, "invalid algorithm capability") - } - } - seen := map[string]bool{} - for _, v := range r.Records { - if v == nil || v.Assignment == nil || !treatment.ValidID(v.Assignment.ExperimentId) || seen[v.Assignment.ExperimentId] || len(v.Error) > 4096 || len(v.InstalledRoute) > 0 { - return nil, status.Error(codes.InvalidArgument, "invalid application record (kernel bytes are local only)") - } - seen[v.Assignment.ExperimentId] = true - if e := treatment.ValidateTreatment(v.Assignment.Treatment); e != nil { - return nil, status.Error(codes.InvalidArgument, e.Error()) - } + if e = treatment.ValidateReport(report); e != nil { + return nil, invalid(e) } now := s.now() - initial := &pb.DesiredState{AgentId: r.AgentId, Revision: 1, IssuedAt: timestamppb.New(now)} + repos := s.Store.Repos() // New enrollment and hold inheritance are serialized against operator // controls. The overwhelmingly common heartbeat is one independent update. - var exists bool - e = s.Store.DB.QueryRowContext(ctx, "SELECT EXISTS(SELECT 1 FROM xtcp_control_agents WHERE id=$1)", r.AgentId).Scan(&exists) - if e != nil { - return nil, failure(e) - } - if !exists { - e = s.Store.mutate(ctx, func(tx *sql.Tx) error { - if err := tx.QueryRowContext(ctx, "SELECT EXISTS(SELECT 1 FROM xtcp_control_holds WHERE scope IN ('fleet',$1,$2))", "location:"+r.Location, "agent:"+r.AgentId).Scan(&initial.Suspended); err != nil { - return err - } - b, err := jsonMessage(initial) + if _, e = repos.Agents.Get(ctx, report.AgentID); errors.Is(e, store.ErrNotFound) { + e = s.Store.Tx(ctx, func(rp store.Repos) error { + held, err := rp.Holds.Held(ctx, holdScopes(report.Location, report.AgentID)...) if err != nil { return err } - _, err = tx.ExecContext(ctx, "INSERT INTO xtcp_control_agents(id,location,desired) VALUES($1,$2,$3) ON CONFLICT(id) DO NOTHING", r.AgentId, r.Location, b) - return err + return rp.Agents.Enroll(ctx, report.AgentID, report.Location, &treatment.DesiredState{AgentID: report.AgentID, Revision: 1, Suspended: held, IssuedAt: now}) }) - if e != nil { - return nil, failure(e) - } } - b, e := jsonMessage(r) if e != nil { return nil, failure(e) } - res, e := s.Store.DB.ExecContext(ctx, "UPDATE xtcp_control_agents SET observed=$2,last_seen=$3 WHERE id=$1 AND location=$4", r.AgentId, b, now, r.Location) - if e != nil { - return nil, failure(e) - } - n, e := res.RowsAffected() - if e != nil { - return nil, failure(e) - } - if n != 1 { + if e = repos.Agents.Observe(ctx, report, now); errors.Is(e, store.ErrNotEnrolled) { return nil, status.Error(codes.PermissionDenied, "agent location cannot change during enrollment") + } else if e != nil { + return nil, failure(e) } - d, e := desired(ctx, s.Store.DB, r.AgentId) + d, e := repos.Agents.Desired(ctx, report.AgentID) if e != nil { return nil, failure(e) } - if d.Revision == r.DesiredRevision { + if d.Revision == report.DesiredRevision { return &pb.CheckInResponse{}, nil } - return &pb.CheckInResponse{Desired: d}, nil + return &pb.CheckInResponse{Desired: wire.Desired(d)}, nil } + func (s *Server) GetDesiredState(ctx context.Context, r *pb.GetDesiredStateRequest) (*pb.GetDesiredStateResponse, error) { if e := s.authorizeEnrolledAgent(ctx, r.GetAgentId()); e != nil { return nil, e } - d, e := desired(ctx, s.Store.DB, r.AgentId) - return &pb.GetDesiredStateResponse{Desired: d}, failure(e) + d, e := s.Store.Repos().Agents.Desired(ctx, r.AgentId) + if e != nil { + return nil, failure(e) + } + return &pb.GetDesiredStateResponse{Desired: wire.Desired(d)}, nil } + func (s *Server) WatchDesiredState(r *pb.WatchDesiredStateRequest, stream grpc.ServerStreamingServer[pb.WatchDesiredStateResponse]) error { if e := s.authorizeEnrolledAgent(stream.Context(), r.GetAgentId()); e != nil { return e @@ -150,7 +197,7 @@ func (s *Server) WatchDesiredState(r *pb.WatchDesiredStateRequest, stream grpc.S ch, unsubscribe := s.Hub.subscribe(r.AgentId) defer unsubscribe() last := r.AfterRevision - d, e := desired(stream.Context(), s.Store.DB, r.AgentId) + d, e := s.Store.Repos().Agents.Desired(stream.Context(), r.AgentId) if e != nil { return failure(e) } @@ -180,11 +227,11 @@ func (s *Server) authorizeEnrolledAgent(ctx context.Context, want string) error if e != nil { return e } - var location string - if e = s.Store.DB.QueryRowContext(ctx, "SELECT location FROM xtcp_control_agents WHERE id=$1", want).Scan(&location); e != nil { + a, e := s.Store.Repos().Agents.Get(ctx, want) + if e != nil { return failure(e) } - if location != id.Location { + if a.Location != id.Location { return status.Error(codes.PermissionDenied, "agent location differs from enrollment") } return nil @@ -200,26 +247,29 @@ func (s *Server) Propose(ctx context.Context, r *pb.ProposeRequest) (out *pb.Pro } func (s *Server) propose(ctx context.Context, r *pb.ProposeRequest, actor string, discovered bool) (out *pb.ProposeResponse, err error) { - var e error - if e = validRequest(r.GetRequestId(), "proposal"); e != nil { + if e := validRequest(r.GetRequestId(), "proposal"); e != nil { return nil, e } - if e = treatment.ValidateProposal(r.Experiment); e != nil { - return nil, status.Error(codes.InvalidArgument, e.Error()) + proposal, e := wire.ParseExperiment(r.Experiment) + if e != nil { + return nil, invalid(e) + } + if e = treatment.ValidateProposal(proposal); e != nil { + return nil, invalid(e) } - if r.Experiment.Treatment.Settings.GetCongestionControl() == "" { + if proposal.Treatment.Settings.Algorithm() == "" { return nil, status.Error(codes.InvalidArgument, "v1 experiments require congestion_control to measure actual socket exposure") } - if len(r.Experiment.CohortOrder) < int(r.Experiment.Policy.MinimumTreatedHosts)+2 { + if len(proposal.CohortOrder) < int(proposal.Policy.MinimumTreatedHosts)+2 { return nil, status.Error(codes.InvalidArgument, "population must leave at least two controls after minimum treated hosts") } out = new(pb.ProposeResponse) - err = s.Store.mutate(ctx, func(tx *sql.Tx) error { + err = s.Store.Tx(ctx, func(rp store.Repos) error { // Discovery's earlier lookup is only an optimization. Recheck under // the controller mutation lock after warehouse analysis. Explicit // operator proposals may intentionally replace a stable predecessor. if discovered { - blocked, err := discoveryDestinationBlocked(ctx, tx, r.Experiment.Location, r.Experiment.Treatment.Destination, s.now()) + blocked, err := rp.Experiments.DestinationBlocked(ctx, proposal.Location, proposal.Treatment.Destination, s.now().Add(-24*time.Hour)) if err != nil { return err } @@ -227,24 +277,24 @@ func (s *Server) propose(ctx context.Context, r *pb.ProposeRequest, actor string return errDiscoverySuppressed } } - if yes, e := replay(ctx, tx, r.RequestId, "propose", r, out); yes || e != nil { + if yes, e := replay(ctx, rp, r.RequestId, "propose", r, out); yes || e != nil { return e } - var exists bool - if e := tx.QueryRowContext(ctx, "SELECT EXISTS(SELECT 1 FROM xtcp_control_experiments WHERE id=$1)", r.Experiment.Id).Scan(&exists); e != nil { + exists, e := rp.Experiments.Exists(ctx, proposal.ID) + if e != nil { return e } if exists { return errors.New("experiment already exists") } - n := proto.Clone(r.Experiment).(*pb.Experiment) + n := proposal.Clone() n.Revision = 1 - n.Status = pb.ExperimentStatus_EXPERIMENT_STATUS_PROPOSED - n.ProposedAt = timestamppb.New(s.now()) + n.Status = treatment.StatusProposed + n.ProposedAt = s.now() n.Phases = nil n.LatestEvidence = nil - n.LatestOutcome = pb.Outcome_OUTCOME_UNSPECIFIED - active, e := hasActiveExperiment(ctx, tx, n.Location) + n.LatestOutcome = treatment.OutcomeUnspecified + active, e := rp.Experiments.HasActive(ctx, n.Location) if e != nil { return e } @@ -255,36 +305,24 @@ func (s *Server) propose(ctx context.Context, r *pb.ProposeRequest, actor string // returned order is reviewed and persisted before any treatment occurs. strata := map[string][]string{} for _, id := range n.CohortOrder { - var location string - var observed []byte - var seen time.Time - if e := tx.QueryRowContext(ctx, "SELECT location,observed,last_seen FROM xtcp_control_agents WHERE id=$1", id).Scan(&location, &observed, &seen); e != nil { - return e - } - o := new(pb.CheckInRequest) - if e := protojson.Unmarshal(observed, o); e != nil { + a, e := rp.Agents.Get(ctx, id) + if e != nil { return e } - if location != n.Location || s.now().Sub(seen) > 2*time.Minute || !o.GetCapabilities().GetSimpleHostRouting() || !o.GetCapabilities().GetActuatorEnabled() { + if a.Location != n.Location || !a.Seen(s.now(), agentWindow) || !a.Report.Capabilities.SimpleHostRouting || !a.Report.Capabilities.ActuatorEnabled { return fmt.Errorf("agent %s is not eligible", id) } - if algo := n.Treatment.Settings.GetCongestionControl(); algo != "" { - found := false - for _, v := range o.Capabilities.CongestionAlgorithms { - found = found || v == algo - } - if !found { - return fmt.Errorf("agent %s lacks %s", id, algo) - } + if algo := n.Treatment.Settings.Algorithm(); algo != "" && !a.Report.Capabilities.Supports(algo) { + return fmt.Errorf("agent %s lacks %s", id, algo) } - strata[o.Capabilities.KernelRelease] = append(strata[o.Capabilities.KernelRelease], id) + strata[a.Report.Capabilities.KernelRelease] = append(strata[a.Report.Capabilities.KernelRelease], id) } keys := make([]string, 0, len(strata)) for k, ids := range strata { keys = append(keys, k) sort.Slice(ids, func(i, j int) bool { - a := sha256.Sum256([]byte(n.Id + "/" + ids[i])) - b := sha256.Sum256([]byte(n.Id + "/" + ids[j])) + a := sha256.Sum256([]byte(n.ID + "/" + ids[i])) + b := sha256.Sum256([]byte(n.ID + "/" + ids[j])) return string(a[:]) < string(b[:]) }) } @@ -302,14 +340,14 @@ func (s *Server) propose(ctx context.Context, r *pb.ProposeRequest, actor string break } } - if e := saveExperiment(ctx, tx, n); e != nil { + if e := s.save(ctx, rp, n); e != nil { return e } - out.Experiment = n - if e := audit(ctx, tx, actor, r.RequestId, "propose", r); e != nil { + out.Experiment = wire.Experiment(n) + if e := audit(ctx, rp, actor, r.RequestId, "propose", r); e != nil { return e } - return audit(ctx, tx, actor, r.RequestId+".result", "result", out) + return audit(ctx, rp, actor, r.RequestId+".result", "result", out) }) return out, err } @@ -322,12 +360,16 @@ func (s *Server) Review(ctx context.Context, r *pb.ReviewRequest) (out *pb.Revie if e = validRequest(r.GetRequestId(), r.GetReason()); e != nil { return nil, e } + evidence, e := wire.ParseEvidence(r.Evidence) + if e != nil { + return nil, invalid(e) + } out = new(pb.ReviewResponse) - err = s.Store.mutate(ctx, func(tx *sql.Tx) error { - if yes, e := replay(ctx, tx, r.RequestId, "review", r, out); yes || e != nil { + err = s.Store.Tx(ctx, func(rp store.Repos) error { + if yes, e := replay(ctx, rp, r.RequestId, "review", r, out); yes || e != nil { return e } - n, e := loadExperiment(ctx, tx, r.ExperimentId) + n, e := rp.Experiments.Get(ctx, r.ExperimentId) if e != nil { return e } @@ -341,124 +383,101 @@ func (s *Server) Review(ctx context.Context, r *pb.ReviewRequest) (out *pb.Revie n.Revision++ } else { if len(n.Phases) == 0 { - if r.Evidence != nil { - if e = saveEvidence(ctx, tx, n.Id, r.Evidence); e != nil { + if evidence != nil { + if e = saveEvidence(ctx, rp, n.ID, evidence); e != nil { return e } - n.Baseline = proto.Clone(r.Evidence).(*pb.Evidence) + n.Baseline = evidence.Clone() } if e = treatment.ValidateProposal(n); e != nil { return e } - if n.Baseline.AnalyzedAt.AsTime().After(s.now().Add(30*time.Second)) || s.now().Sub(n.Baseline.WindowEnd.AsTime()) > n.Policy.MaximumEvidenceAge.AsDuration() { + if n.Baseline.AnalyzedAt.After(s.now().Add(30*time.Second)) || s.now().Sub(n.Baseline.WindowEnd) > n.Policy.MaximumEvidenceAge { return errors.New("baseline stale/future; supply refreshed baseline evidence with review") } } if len(n.Phases) > 0 { - if e = s.startObservation(ctx, tx, n); e != nil { + if e = s.startObservation(ctx, rp, n); e != nil { return e } - if n.LatestOutcome == pb.Outcome_OUTCOME_AWAITING_APPLICATION { + if n.LatestOutcome == treatment.OutcomeAwaitingApplication { return errors.New("cohort is not currently verified") } - if e = saveEvidence(ctx, tx, n.Id, r.Evidence); e != nil { + if e = saveEvidence(ctx, rp, n.ID, evidence); e != nil { return e } } - n, e = experiment.Advance(n, r.Evidence, s.now(), actor, r.Reason) + n, e = experiment.Advance(n, evidence, s.now(), actor, r.Reason) if e != nil { return e } } - if e = s.assign(ctx, tx, n, false); e != nil { + if e = s.assign(ctx, rp, n, false); e != nil { return e } - if e = saveExperiment(ctx, tx, n); e != nil { + if e = s.save(ctx, rp, n); e != nil { return e } - out.Experiment = n - if e = audit(ctx, tx, actor, r.RequestId, "review", r); e != nil { + out.Experiment = wire.Experiment(n) + if e = audit(ctx, rp, actor, r.RequestId, "review", r); e != nil { return e } - return audit(ctx, tx, actor, r.RequestId+".result", "result", out) + return audit(ctx, rp, actor, r.RequestId+".result", "result", out) }) return out, failure(err) } -func saveEvidence(ctx context.Context, tx *sql.Tx, id string, v *pb.Evidence) error { +// save stamps the controller's clock on the experiment and persists it. +func (s *Server) save(ctx context.Context, rp store.Repos, e *treatment.Experiment) error { + e.UpdatedAt = s.now() + return rp.Experiments.Save(ctx, e) +} + +func saveEvidence(ctx context.Context, rp store.Repos, experimentID string, v *treatment.Evidence) error { if e := treatment.ValidateEvidence(v); e != nil { return e } - b, e := jsonMessage(v) - if e != nil { - return e - } - var old []byte - var owner string - e = tx.QueryRowContext(ctx, "SELECT state,experiment_id FROM xtcp_control_evidence WHERE id=$1", v.Id).Scan(&old, &owner) - if e == nil { - p := new(pb.Evidence) - if e = protojson.Unmarshal(old, p); e != nil { - return e - } - if owner != id || !proto.Equal(p, v) { - return errors.New("evidence IDs are immutable and experiment-specific") - } - return nil - } - if !errors.Is(e, sql.ErrNoRows) { - return e - } - _, e = tx.ExecContext(ctx, "INSERT INTO xtcp_control_evidence(id,experiment_id,state) VALUES($1,$2,$3)", v.Id, id, b) - return e + return rp.Evidence.Put(ctx, experimentID, v) } -func (s *Server) startObservation(ctx context.Context, tx *sql.Tx, e *pb.Experiment) error { - if len(e.Phases) == 0 { +// startObservation records when the whole current cohort verifiably applied +// the treatment, or marks the experiment awaiting application. +func (s *Server) startObservation(ctx context.Context, rp store.Repos, e *treatment.Experiment) error { + p := e.CurrentPhase() + if p == nil { return nil } - p := e.Phases[len(e.Phases)-1] - latest := p.IssuedAt.AsTime() - notReady := func() error { e.LatestOutcome = pb.Outcome_OUTCOME_AWAITING_APPLICATION; return nil } + latest := p.IssuedAt + notReady := func() error { e.LatestOutcome = treatment.OutcomeAwaitingApplication; return nil } for _, id := range e.CohortOrder[:p.TargetHosts] { - var raw []byte - var seen time.Time - if err := tx.QueryRowContext(ctx, "SELECT observed,last_seen FROM xtcp_control_agents WHERE id=$1", id).Scan(&raw, &seen); err != nil { - return err - } - o := new(pb.CheckInRequest) - if err := protojson.Unmarshal(raw, o); err != nil { + a, err := rp.Agents.Get(ctx, id) + if err != nil { return err } - if s.now().Sub(seen) > 2*time.Minute || o.LocallySuspended { + if !a.Seen(s.now(), agentWindow) || a.Report.LocallySuspended { return notReady() } - d, err := desired(ctx, tx, id) + d, err := rp.Agents.Desired(ctx, id) if err != nil { return err } - if d.Suspended || o.DesiredRevision != d.Revision { + if d.Suspended || a.Report.DesiredRevision != d.Revision { return notReady() } - var expected *pb.Assignment - for _, a := range d.Assignments { - if a.ExperimentId == e.Id { - expected = a - } - } - if expected == nil || expected.Mode != pb.AssignmentMode_ASSIGNMENT_MODE_TRIAL || expected.LeaseExpiresAt == nil || !expected.LeaseExpiresAt.AsTime().After(s.now()) { + expected := d.Find(e.ID) + if expected == nil || expected.Mode != treatment.ModeTrial || expected.LeaseExpiresAt.IsZero() || !expected.LeaseExpiresAt.After(s.now()) { return notReady() } found := false - for _, a := range o.Records { - if a.Assignment.ExperimentId == expected.ExperimentId && a.Assignment.Generation == expected.Generation && a.Status == pb.ApplyStatus_APPLY_STATUS_VERIFIED && !a.ExpiredLatched && a.AppliedAt != nil && a.AppliedAt.CheckValid() == nil && proto.Equal(a.Assignment.Treatment, expected.Treatment) { + for i := range a.Report.Records { + rec := &a.Report.Records[i] + if rec.Assignment.ExperimentID == expected.ExperimentID && rec.Assignment.Generation == expected.Generation && rec.Status == treatment.ApplyVerified && !rec.ExpiredLatched && !rec.AppliedAt.IsZero() && rec.Assignment.Treatment.Equal(expected.Treatment) { found = true - t := a.AppliedAt.AsTime() - if t.After(s.now().Add(30 * time.Second)) { + if rec.AppliedAt.After(s.now().Add(30 * time.Second)) { return errors.New("agent clock ahead") } - if t.After(latest) { - latest = t + if rec.AppliedAt.After(latest) { + latest = rec.AppliedAt } } } @@ -466,97 +485,83 @@ func (s *Server) startObservation(ctx context.Context, tx *sql.Tx, e *pb.Experim return notReady() } } - if p.ObservationStartedAt == nil { - p.ObservationStartedAt = timestamppb.New(latest) + if p.ObservationStartedAt.IsZero() { + p.ObservationStartedAt = latest } - if e.LatestOutcome == pb.Outcome_OUTCOME_AWAITING_APPLICATION { - e.LatestOutcome = pb.Outcome_OUTCOME_OBSERVING + if e.LatestOutcome == treatment.OutcomeAwaitingApplication { + e.LatestOutcome = treatment.OutcomeObserving } return nil } -func (s *Server) assign(ctx context.Context, tx *sql.Tx, e *pb.Experiment, reactivate bool) error { - target := 0 - if len(e.Phases) > 0 { - target = int(e.Phases[len(e.Phases)-1].TargetHosts) - } +// assign writes the desired state that follows from the experiment's status +// for every cohort host that is, or becomes, part of the current phase. +func (s *Server) assign(ctx context.Context, rp store.Repos, e *treatment.Experiment, reactivate bool) error { + target := e.Target() for i, id := range e.CohortOrder { - d, err := desired(ctx, tx, id) + d, err := rp.Agents.Desired(ctx, id) if err != nil { return err } - var a *pb.Assignment - for _, x := range d.Assignments { - if x.ExperimentId == e.Id { - a = x - break - } - } + a := d.Find(e.ID) if i >= target && a == nil { continue } if a == nil { - var report []byte - var seen time.Time - if err = tx.QueryRowContext(ctx, "SELECT observed,last_seen FROM xtcp_control_agents WHERE id=$1", id).Scan(&report, &seen); err != nil { - return err - } - o := new(pb.CheckInRequest) - if err = protojson.Unmarshal(report, o); err != nil { + agent, err := rp.Agents.Get(ctx, id) + if err != nil { return err } - if d.Suspended || o.LocallySuspended || s.now().Sub(seen) > 2*time.Minute || !o.GetCapabilities().GetSimpleHostRouting() || !o.GetCapabilities().GetActuatorEnabled() { + caps := agent.Report.Capabilities + if d.Suspended || agent.Report.LocallySuspended || !agent.Seen(s.now(), agentWindow) || !caps.SimpleHostRouting || !caps.ActuatorEnabled { return fmt.Errorf("agent %s no longer eligible", id) } - available := false - for _, cc := range o.GetCapabilities().GetCongestionAlgorithms() { - available = available || cc == e.Treatment.Settings.GetCongestionControl() - } - if !available { + if !caps.Supports(e.Treatment.Settings.Algorithm()) { return fmt.Errorf("agent %s no longer has required algorithm", id) } - var predecessor *pb.Treatment - for _, x := range d.Assignments { - if x.Mode != pb.AssignmentMode_ASSIGNMENT_MODE_RETIRED && x.Treatment.Destination == e.Treatment.Destination { - if x.Mode != pb.AssignmentMode_ASSIGNMENT_MODE_STABLE { + var predecessor *treatment.Treatment + for j := range d.Assignments { + x := &d.Assignments[j] + if x.Mode != treatment.ModeRetired && x.Treatment.Destination == e.Treatment.Destination { + if x.Mode != treatment.ModeStable { return errors.New("destination already has an active trial/hold") } - predecessor = proto.Clone(x.Treatment).(*pb.Treatment) - x.Mode = pb.AssignmentMode_ASSIGNMENT_MODE_RETIRED + t := x.Treatment + predecessor = &t + x.Mode = treatment.ModeRetired } } - a = &pb.Assignment{ExperimentId: e.Id, Treatment: proto.Clone(e.Treatment).(*pb.Treatment), Generation: 1} - d.Assignments = append(d.Assignments, a) - a.Predecessor = predecessor + d.Assignments = append(d.Assignments, treatment.Assignment{ExperimentID: e.ID, Treatment: e.Treatment, Generation: 1, Predecessor: predecessor}) + a = &d.Assignments[len(d.Assignments)-1] } switch e.Status { - case pb.ExperimentStatus_EXPERIMENT_STATUS_COMPLETE: - a.Mode = pb.AssignmentMode_ASSIGNMENT_MODE_STABLE - a.LeaseExpiresAt = nil - case pb.ExperimentStatus_EXPERIMENT_STATUS_SUSPENDED: - a.Mode = pb.AssignmentMode_ASSIGNMENT_MODE_SUSPENDED - case pb.ExperimentStatus_EXPERIMENT_STATUS_RETIRED, pb.ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK: - a.Mode = pb.AssignmentMode_ASSIGNMENT_MODE_RETIRED - if e.Status == pb.ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK && a.Predecessor != nil { - a.Treatment = proto.Clone(a.Predecessor).(*pb.Treatment) - a.Mode = pb.AssignmentMode_ASSIGNMENT_MODE_STABLE + case treatment.StatusComplete: + a.Mode = treatment.ModeStable + a.LeaseExpiresAt = time.Time{} + case treatment.StatusSuspended: + a.Mode = treatment.ModeSuspended + case treatment.StatusRetired, treatment.StatusRolledBack: + a.Mode = treatment.ModeRetired + if e.Status == treatment.StatusRolledBack && a.Predecessor != nil { + a.Treatment = *a.Predecessor + a.Mode = treatment.ModeStable a.Generation++ } default: - a.Mode = pb.AssignmentMode_ASSIGNMENT_MODE_TRIAL - if a.LeaseExpiresAt == nil || reactivate { - a.LeaseExpiresAt = timestamppb.New(s.now().Add(treatment.TrialLease)) + a.Mode = treatment.ModeTrial + if a.LeaseExpiresAt.IsZero() || reactivate { + a.LeaseExpiresAt = s.now().Add(treatment.TrialLease) } } if reactivate { a.Generation++ } d.Revision++ - d.IssuedAt = timestamppb.New(s.now()) + d.IssuedAt = s.now() if err = treatment.ValidateDesired(d); err != nil { return err } - if err = saveDesired(ctx, tx, d); err != nil { + if err = rp.Agents.SaveDesired(ctx, d); err != nil { return err } } @@ -587,16 +592,16 @@ func (s *Server) Control(ctx context.Context, r *pb.ControlRequest) (out *pb.Con return nil, status.Error(codes.InvalidArgument, "one scope and a valid action required") } out = new(pb.ControlResponse) - err = s.Store.mutate(ctx, func(tx *sql.Tx) error { - if yes, e := replay(ctx, tx, r.RequestId, "control", r, out); yes || e != nil { + err = s.Store.Tx(ctx, func(rp store.Repos) error { + if yes, e := replay(ctx, rp, r.RequestId, "control", r, out); yes || e != nil { return e } if r.ExperimentId != "" { - n, e := loadExperiment(ctx, tx, r.ExperimentId) + n, e := rp.Experiments.Get(ctx, r.ExperimentId) if e != nil { return e } - if experiment.Terminal(n.Status) { + if n.Status.Terminal() { return errors.New("terminal experiment cannot be reactivated") } event, e := controlEvent(n, r.Action) @@ -607,14 +612,14 @@ func (s *Server) Control(ctx context.Context, r *pb.ControlRequest) (out *pb.Con return e } if event == experiment.EventResume { - p := n.Phases[len(n.Phases)-1] - n.Phases = append(n.Phases, &pb.Phase{TargetHosts: p.TargetHosts, IssuedAt: timestamppb.New(s.now()), Reviewer: actor, Reason: r.Reason}) + p := n.CurrentPhase() + n.Phases = append(n.Phases, treatment.Phase{TargetHosts: p.TargetHosts, IssuedAt: s.now(), Reviewer: actor, Reason: r.Reason}) } n.Revision++ - if e = s.assign(ctx, tx, n, r.Action == pb.ControlAction_CONTROL_ACTION_RESUME); e != nil { + if e = s.assign(ctx, rp, n, r.Action == pb.ControlAction_CONTROL_ACTION_RESUME); e != nil { return e } - if e = saveExperiment(ctx, tx, n); e != nil { + if e = s.save(ctx, rp, n); e != nil { return e } out.AffectedAgents = uint32(len(n.CohortOrder)) @@ -631,52 +636,45 @@ func (s *Server) Control(ctx context.Context, r *pb.ControlRequest) (out *pb.Con } var e error if r.Action == pb.ControlAction_CONTROL_ACTION_SUSPEND { - _, e = tx.ExecContext(ctx, "INSERT INTO xtcp_control_holds(scope,reason) VALUES($1,$2) ON CONFLICT(scope) DO UPDATE SET reason=EXCLUDED.reason", scope, r.Reason) + e = rp.Holds.Set(ctx, scope, r.Reason) } else { - _, e = tx.ExecContext(ctx, "DELETE FROM xtcp_control_holds WHERE scope=$1", scope) - } - if e != nil { - return e + e = rp.Holds.Clear(ctx, scope) } - rows, e := tx.QueryContext(ctx, "SELECT id,location FROM xtcp_control_agents WHERE ($1='' OR id=$1) AND ($2='' OR location=$2)", r.AgentId, r.Location) if e != nil { return e } - type host struct{ id, loc string } - var hosts []host - for rows.Next() { - var h host - if e = rows.Scan(&h.id, &h.loc); e != nil { - rows.Close() + var hosts []store.AgentRef + if r.AgentId != "" { + a, e := rp.Agents.Get(ctx, r.AgentId) + if e != nil && !errors.Is(e, store.ErrNotFound) { return e } - hosts = append(hosts, h) - } - e = rows.Err() - rows.Close() - if e != nil { + if e == nil { + hosts = append(hosts, store.AgentRef{ID: a.ID, Location: a.Location}) + } + } else if hosts, e = rp.Agents.List(ctx, r.Location); e != nil { return e } for _, h := range hosts { - d, e := desired(ctx, tx, h.id) + d, e := rp.Agents.Desired(ctx, h.ID) if e != nil { return e } - if e = tx.QueryRowContext(ctx, "SELECT EXISTS(SELECT 1 FROM xtcp_control_holds WHERE scope IN ('fleet',$1,$2))", "location:"+h.loc, "agent:"+h.id).Scan(&d.Suspended); e != nil { + if d.Suspended, e = rp.Holds.Held(ctx, holdScopes(h.Location, h.ID)...); e != nil { return e } d.Revision++ - d.IssuedAt = timestamppb.New(s.now()) - if e = saveDesired(ctx, tx, d); e != nil { + d.IssuedAt = s.now() + if e = rp.Agents.SaveDesired(ctx, d); e != nil { return e } } out.AffectedAgents = uint32(len(hosts)) } - if e := audit(ctx, tx, actor, r.RequestId, "control", r); e != nil { + if e := audit(ctx, rp, actor, r.RequestId, "control", r); e != nil { return e } - return audit(ctx, tx, actor, r.RequestId+".result", "result", out) + return audit(ctx, rp, actor, r.RequestId+".result", "result", out) }) return out, failure(err) } @@ -684,7 +682,7 @@ func (s *Server) Control(ctx context.Context, r *pb.ControlRequest) (out *pb.Con // controlEvent maps an operator action onto a state-machine event. Resume // needs the experiment: a held completed treatment resumes as stable, a held // trial resumes with a fresh phase, and a held proposal cannot resume at all. -func controlEvent(n *pb.Experiment, action pb.ControlAction) (experiment.Event, error) { +func controlEvent(n *treatment.Experiment, action pb.ControlAction) (experiment.Event, error) { switch action { case pb.ControlAction_CONTROL_ACTION_PAUSE: return experiment.EventPause, nil @@ -695,10 +693,11 @@ func controlEvent(n *pb.Experiment, action pb.ControlAction) (experiment.Event, case pb.ControlAction_CONTROL_ACTION_ROLLBACK: return experiment.EventRollback, nil case pb.ControlAction_CONTROL_ACTION_RESUME: - if len(n.Phases) == 0 { + p := n.CurrentPhase() + if p == nil { return 0, errors.New("proposal requires initial review") } - if n.Phases[len(n.Phases)-1].Outcome == pb.Outcome_OUTCOME_FINAL_SAFETY { + if p.Outcome == treatment.OutcomeFinalSafety { return experiment.EventResumeStable, nil } return experiment.EventResume, nil @@ -710,45 +709,29 @@ func (s *Server) Inspect(ctx context.Context, r *pb.InspectRequest) (*pb.Inspect if _, e := identity.Operator(ctx); e != nil { return nil, e } + repos := s.Store.Repos() out := new(pb.InspectResponse) - rows, e := s.Store.DB.QueryContext(ctx, "SELECT state FROM xtcp_control_experiments WHERE ($1='' OR id=$1) ORDER BY id LIMIT 1000", r.ExperimentId) + list, e := repos.Experiments.List(ctx, r.ExperimentId, 1000) if e != nil { return nil, failure(e) } - for rows.Next() { - var b []byte - if e = rows.Scan(&b); e != nil { - break - } - v := new(pb.Experiment) - if e = protojson.Unmarshal(b, v); e != nil { - break - } - out.Experiments = append(out.Experiments, v) - } - e = errors.Join(e, rows.Err()) - rows.Close() - if e != nil { - return nil, failure(e) + for _, v := range list { + out.Experiments = append(out.Experiments, wire.Experiment(v)) } // Host details are deliberately explicit; a dashboard must not pull the // entire fleet's route journals on every refresh. if r.AgentId != "" { - d, e := desired(ctx, s.Store.DB, r.AgentId) + d, e := repos.Agents.Desired(ctx, r.AgentId) if e != nil { return nil, failure(e) } - out.Desired = append(out.Desired, d) - var b []byte - if e = s.Store.DB.QueryRowContext(ctx, "SELECT observed FROM xtcp_control_agents WHERE id=$1", r.AgentId).Scan(&b); e != nil { + out.Desired = append(out.Desired, wire.Desired(d)) + a, e := repos.Agents.Get(ctx, r.AgentId) + if e != nil { return nil, failure(e) } - if len(b) > 0 { - o := new(pb.CheckInRequest) - if e = protojson.Unmarshal(b, o); e != nil { - return nil, failure(e) - } - out.Observed = append(out.Observed, o) + if !a.LastSeen.IsZero() { + out.Observed = append(out.Observed, wire.Report(&a.Report)) } } return out, nil diff --git a/pkg/treatment/controller/server_test.go b/pkg/treatment/controller/server_test.go index 8f531a278..30e2bd161 100644 --- a/pkg/treatment/controller/server_test.go +++ b/pkg/treatment/controller/server_test.go @@ -2,153 +2,85 @@ package controller import ( "context" - "crypto/tls" "crypto/x509" - "database/sql" "fmt" "net/url" - "os" - "strings" "sync" "testing" "time" pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store/pgtest" "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" - "google.golang.org/grpc/credentials" - "google.golang.org/grpc/peer" + "github.com/randomizedcoder/xtcp2/pkg/treatment/wire" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" ) -func testIdentity(path string) context.Context { - u, _ := url.Parse("spiffe://xtcp/" + path) - cert := &x509.Certificate{URIs: []*url.URL{u}} - return peer.NewContext(context.Background(), &peer.Peer{AuthInfo: credentials.TLSInfo{State: tls.ConnectionState{PeerCertificates: []*x509.Certificate{cert}, VerifiedChains: [][]*x509.Certificate{{cert}}}}}) -} - -func testStore(t *testing.T) *Store { - t.Helper() - dsn := os.Getenv("XTCP_CONTROLLER_TEST_DSN") - if dsn == "" { - t.Skip("set XTCP_CONTROLLER_TEST_DSN for isolated PostgreSQL integration") - } - db, e := sql.Open("postgres", dsn) - if e != nil { - t.Fatal(e) - } - schema := fmt.Sprintf("xtcp_test_%d", time.Now().UnixNano()) - if _, e = db.Exec("CREATE SCHEMA " + schema); e != nil { - db.Close() - t.Fatal(e) - } - if strings.HasPrefix(dsn, "postgres://") || strings.HasPrefix(dsn, "postgresql://") { - u, e := url.Parse(dsn) +func TestReviewedRolloutAndPersistentFleetHold(t *testing.T) { + eachStore(t, func(t *testing.T, st store.Store) { + now := time.Now().UTC().Truncate(time.Microsecond) + s := testServer(st, &now) + operator := testIdentity("operator/alice") + enrollFleet(t, s, 8) + p, e := s.Propose(operator, proposal(now)) if e != nil { t.Fatal(e) } - q := u.Query() - q.Set("search_path", schema) - u.RawQuery = q.Encode() - dsn = u.String() - } else { - dsn += " search_path=" + schema - } - s, e := Open(context.Background(), dsn) - if e != nil { - t.Fatal(e) - } - t.Cleanup(func() { s.DB.Close(); _, _ = db.Exec("DROP SCHEMA " + schema + " CASCADE"); db.Close() }) - if e = s.Migrate(context.Background()); e != nil { - t.Fatal(e) - } - return s -} - -func enroll(t *testing.T, s *Server, id string) { - t.Helper() - _, e := s.CheckIn(testIdentity("agent/dc1/"+id), &pb.CheckInRequest{AgentId: id, Location: "dc1", Capabilities: &pb.Capabilities{BootId: "boot-1", KernelRelease: "test", CongestionAlgorithms: []string{"cubic", "bbr"}, SimpleHostRouting: true, ActuatorEnabled: true}}) - if e != nil { - t.Fatal(e) - } -} -func proposal(now time.Time) *pb.ProposeRequest { - e := &pb.Experiment{Id: "experiment-1", Location: "dc1", Hypothesis: "test cubic", Policy: treatment.DefaultPolicy(), Treatment: &pb.Treatment{Id: "cubic", Revision: 1, Destination: "192.0.2.1/32", Settings: &pb.TCPSettings{CongestionControl: proto.String("cubic")}}, Baseline: &pb.Evidence{Id: "baseline-1", AnalyzedAt: timestamppb.New(now), WindowStart: timestamppb.New(now.Add(-time.Hour)), WindowEnd: timestamppb.New(now.Add(-time.Second)), QueryVersion: "test", ArtifactSha256: strings.Repeat("a", 64)}} - for i := range 8 { - id := fmt.Sprintf("host-%d", i) - e.CohortOrder = append(e.CohortOrder, id) - e.Baseline.Hosts = append(e.Baseline.Hosts, &pb.HostEvidence{AgentId: id, Sockets: 100, BytesAcked: 1 << 25, GoodputBps: 1e8, RttP95Ms: 1, CollectionHealthy: true}) - } - return &pb.ProposeRequest{Experiment: e, RequestId: "proposal-1"} -} - -func TestPostgresReviewedRolloutAndPersistentFleetHold(t *testing.T) { - now := time.Now().UTC() - s := &Server{Store: testStore(t), Now: func() time.Time { return now }} - operator := testIdentity("operator/alice") - for i := range 8 { - enroll(t, s, fmt.Sprintf("host-%d", i)) - } - p, e := s.Propose(operator, proposal(now)) - if e != nil { - t.Fatal(e) - } - if len(p.Experiment.Phases) != 0 { - t.Fatal("proposal applied without approval") - } - r := &pb.ReviewRequest{ExperimentId: p.Experiment.Id, ExpectedRevision: 1, Approve: true, Reason: "reviewed", RequestId: "review-1"} - v, e := s.Review(operator, r) - if e != nil { - t.Fatal(e) - } - if len(v.Experiment.Phases) != 1 || v.Experiment.Phases[0].TargetHosts != 1 { - t.Fatal("wrong initial canary") - } - replay, e := s.Review(operator, r) - if e != nil || !proto.Equal(replay, v) { - t.Fatal("review retry was not idempotent", e) - } - bad := proto.Clone(r).(*pb.ReviewRequest) - bad.Reason = "changed" - if _, e = s.Review(operator, bad); e == nil { - t.Fatal("accepted reused request ID with changed body") - } - var assignments int - if e = s.Store.DB.QueryRow("SELECT sum(jsonb_array_length(coalesce(desired->'assignments','[]'))) FROM xtcp_control_agents").Scan(&assignments); e != nil { - t.Fatal(e) - } - if assignments != 1 { - t.Fatal("incorrect fleet exposure", assignments) - } - if _, e = s.Control(operator, &pb.ControlRequest{Fleet: true, Action: pb.ControlAction_CONTROL_ACTION_SUSPEND, Reason: "incident", RequestId: "hold-1"}); e != nil { - t.Fatal(e) - } - enroll(t, s, "new-host") - d, e := desired(context.Background(), s.Store.DB, "new-host") - if e != nil || !d.Suspended { - t.Fatal("new host escaped persistent fleet hold", e) - } - // A narrower resume cannot clear the fleet-wide hold. - if _, e = s.Control(operator, &pb.ControlRequest{AgentId: "new-host", Action: pb.ControlAction_CONTROL_ACTION_RESUME, Reason: "host resume", RequestId: "resume-1"}); e != nil { - t.Fatal(e) - } - d, e = desired(context.Background(), s.Store.DB, "new-host") - if e != nil || !d.Suspended { - t.Fatal("narrower resume overrode fleet hold") - } - if _, e = s.Review(testIdentity("agent/dc1/host-0"), r); e == nil { - t.Fatal("agent obtained operator authority") - } + if len(p.Experiment.Phases) != 0 { + t.Fatal("proposal applied without approval") + } + r := &pb.ReviewRequest{ExperimentId: p.Experiment.Id, ExpectedRevision: 1, Approve: true, Reason: "reviewed", RequestId: "review-1"} + v, e := s.Review(operator, r) + if e != nil { + t.Fatal(e) + } + if len(v.Experiment.Phases) != 1 || v.Experiment.Phases[0].TargetHosts != 1 { + t.Fatal("wrong initial canary") + } + replay, e := s.Review(operator, r) + if e != nil || !proto.Equal(replay, v) { + t.Fatal("review retry was not idempotent", e) + } + bad := proto.Clone(r).(*pb.ReviewRequest) + bad.Reason = "changed" + if _, e = s.Review(operator, bad); e == nil { + t.Fatal("accepted reused request ID with changed body") + } + if n := fleetAssignments(t, st); n != 1 { + t.Fatal("incorrect fleet exposure", n) + } + if _, e = s.Control(operator, &pb.ControlRequest{Fleet: true, Action: pb.ControlAction_CONTROL_ACTION_SUSPEND, Reason: "incident", RequestId: "hold-1"}); e != nil { + t.Fatal(e) + } + enroll(t, s, "new-host") + if !desiredOf(t, st, "new-host").Suspended { + t.Fatal("new host escaped persistent fleet hold") + } + // A narrower resume cannot clear the fleet-wide hold. + if _, e = s.Control(operator, &pb.ControlRequest{AgentId: "new-host", Action: pb.ControlAction_CONTROL_ACTION_RESUME, Reason: "host resume", RequestId: "resume-1"}); e != nil { + t.Fatal(e) + } + if !desiredOf(t, st, "new-host").Suspended { + t.Fatal("narrower resume overrode fleet hold") + } + if _, e = s.Review(testIdentity("agent/dc1/host-0"), r); e == nil { + t.Fatal("agent obtained operator authority") + } + }) } +// TestPostgresConcurrentReviewFencesReplicas is PostgreSQL-only: the fence +// under test is the advisory lock two controller replicas share. func TestPostgresConcurrentReviewFencesReplicas(t *testing.T) { + // Deliberately not truncated: the baseline stored with a nanosecond + // clock must still re-save after a microsecond-precision read back. now := time.Now().UTC() - s := &Server{Store: testStore(t), Now: func() time.Time { return now }} + s := testServer(pgtest.Open(t), &now) operator := testIdentity("operator/alice") - for i := range 8 { - enroll(t, s, fmt.Sprintf("host-%d", i)) - } + enrollFleet(t, s, 8) if _, e := s.Propose(operator, proposal(now)); e != nil { t.Fatal(e) } @@ -169,6 +101,8 @@ func TestPostgresConcurrentReviewFencesReplicas(t *testing.T) { for e := range errs { if e == nil { success++ + } else { + t.Log(e) } } if success != 1 { @@ -218,171 +152,180 @@ func TestNotificationFanoutTenThousandAgents(t *testing.T) { } } -type fixedEvidence struct{ value *pb.Evidence } - -func (f fixedEvidence) Analyze(context.Context, *pb.Experiment) (*pb.Evidence, error) { - return f.value, nil -} - -func acknowledgeFleet(t *testing.T, s *Server, n *pb.Experiment) { - t.Helper() - for _, id := range n.CohortOrder { - d, e := desired(context.Background(), s.Store.DB, id) - if e != nil { +func TestCompleteReviewedRolloutAndStableWithdrawal(t *testing.T) { + eachStore(t, func(t *testing.T, st store.Store) { + now := time.Now().UTC().Truncate(time.Microsecond) + s := testServer(st, &now) + operator := testIdentity("operator/alice") + enrollFleet(t, s, 8) + if _, e := s.Propose(operator, proposal(now)); e != nil { t.Fatal(e) } - r := &pb.CheckInRequest{AgentId: id, Location: n.Location, DesiredRevision: d.Revision, Capabilities: &pb.Capabilities{BootId: "boot-1", KernelRelease: "test", CongestionAlgorithms: []string{"cubic", "bbr"}, SimpleHostRouting: true, ActuatorEnabled: true}} - for _, a := range d.Assignments { - r.Records = append(r.Records, &pb.ApplicationRecord{Assignment: proto.Clone(a).(*pb.Assignment), DesiredRevision: d.Revision, Status: pb.ApplyStatus_APPLY_STATUS_VERIFIED, AppliedAt: n.Phases[len(n.Phases)-1].IssuedAt}) - } - if _, e = s.CheckIn(testIdentity("agent/dc1/"+id), r); e != nil { + approved, e := s.Review(operator, &pb.ReviewRequest{ExperimentId: "experiment-1", ExpectedRevision: 1, Approve: true, Reason: "initial review", RequestId: "approve-initial"}) + if e != nil { t.Fatal(e) } - } -} - -func TestPostgresCompleteReviewedRolloutAndStableWithdrawal(t *testing.T) { - now := time.Now().UTC() - s := &Server{Store: testStore(t), Now: func() time.Time { return now }} - operator := testIdentity("operator/alice") - for i := range 8 { - enroll(t, s, fmt.Sprintf("host-%d", i)) - } - if _, e := s.Propose(operator, proposal(now)); e != nil { - t.Fatal(e) - } - approved, e := s.Review(operator, &pb.ReviewRequest{ExperimentId: "experiment-1", ExpectedRevision: 1, Approve: true, Reason: "initial review", RequestId: "approve-initial"}) - if e != nil { - t.Fatal(e) - } - n := approved.Experiment - for phase, target := range []uint32{1, 2, 4, 6, 8} { - if n.Phases[len(n.Phases)-1].TargetHosts != target { - t.Fatal("wrong cohort", n) - } - now = now.Add(31 * time.Minute) - acknowledgeFleet(t, s, n) - ev := proto.Clone(n.Baseline).(*pb.Evidence) - ev.Id = fmt.Sprintf("phase-evidence-%d", phase) - ev.WindowStart = n.Phases[len(n.Phases)-1].IssuedAt - ev.WindowEnd = timestamppb.New(now.Add(-time.Second)) - ev.AnalyzedAt = timestamppb.New(now) - for _, h := range ev.Hosts { - for i, id := range n.CohortOrder { - if h.AgentId == id && i < int(target) { - h.GoodputBps *= 1.2 - h.ExposedSockets = h.Sockets + n := approved.Experiment + for phase, target := range []uint32{1, 2, 4, 6, 8} { + if n.Phases[len(n.Phases)-1].TargetHosts != target { + t.Fatal("wrong cohort", n) + } + now = now.Add(31 * time.Minute) + acknowledgeFleet(t, s, n) + ev := proto.Clone(n.Baseline).(*pb.Evidence) + ev.Id = fmt.Sprintf("phase-evidence-%d", phase) + ev.WindowStart = n.Phases[len(n.Phases)-1].IssuedAt + ev.WindowEnd = timestamppb.New(now.Add(-time.Second)) + ev.AnalyzedAt = timestamppb.New(now) + for _, h := range ev.Hosts { + for i, id := range n.CohortOrder { + if h.AgentId == id && i < int(target) { + h.GoodputBps *= 1.2 + h.ExposedSockets = h.Sockets + } } } + if e = s.Maintain(context.Background(), fixedEvidence{parseEvidence(t, ev)}); e != nil { + t.Fatal("maintenance", e) + } + // Maintenance may renew leases but must never expand the cohort. + inspected, e := s.Inspect(operator, &pb.InspectRequest{ExperimentId: n.Id}) + if e != nil { + t.Fatal(e) + } + if inspected.Experiments[0].Revision != n.Revision || len(inspected.Experiments[0].Phases) != len(n.Phases) { + t.Fatal("maintenance promoted without review") + } + acknowledgeFleet(t, s, n) + approved, e = s.Review(operator, &pb.ReviewRequest{ExperimentId: n.Id, ExpectedRevision: n.Revision, Approve: true, Reason: "evidence reviewed", RequestId: fmt.Sprintf("approve-phase-%d", phase), Evidence: ev}) + if e != nil { + t.Fatalf("phase %d review: %v", phase, e) + } + n = approved.Experiment } - if e = s.Maintain(context.Background(), fixedEvidence{ev}); e != nil { - t.Fatal("maintenance", e) + if n.Status != pb.ExperimentStatus_EXPERIMENT_STATUS_COMPLETE { + t.Fatal("rollout did not complete") + } + for _, id := range n.CohortOrder { + d := desiredOf(t, st, id) + if d.Assignments[0].Mode != treatment.ModeStable || !d.Assignments[0].LeaseExpiresAt.IsZero() { + t.Fatal("stable treatment still leased") + } + } + for i, action := range []pb.ControlAction{pb.ControlAction_CONTROL_ACTION_SUSPEND, pb.ControlAction_CONTROL_ACTION_RESUME} { + if _, e = s.Control(operator, &pb.ControlRequest{ExperimentId: n.Id, Action: action, Reason: "withdrawal comparison", RequestId: fmt.Sprintf("withdrawal-%d", i)}); e != nil { + t.Fatal(e) + } } - // Maintenance may renew leases but must never expand the cohort. inspected, e := s.Inspect(operator, &pb.InspectRequest{ExperimentId: n.Id}) if e != nil { t.Fatal(e) } - if inspected.Experiments[0].Revision != n.Revision || len(inspected.Experiments[0].Phases) != len(n.Phases) { - t.Fatal("maintenance promoted without review") + if inspected.Experiments[0].Status != pb.ExperimentStatus_EXPERIMENT_STATUS_COMPLETE { + t.Fatal("resume did not retain stable approval") } - acknowledgeFleet(t, s, n) - approved, e = s.Review(operator, &pb.ReviewRequest{ExperimentId: n.Id, ExpectedRevision: n.Revision, Approve: true, Reason: "evidence reviewed", RequestId: fmt.Sprintf("approve-phase-%d", phase), Evidence: ev}) - if e != nil { - t.Fatalf("phase %d review: %v", phase, e) + }) +} + +func TestFastSafetyWithdrawalBeforePromotionWindow(t *testing.T) { + eachStore(t, func(t *testing.T, st store.Store) { + now := time.Now().UTC().Truncate(time.Microsecond) + s := testServer(st, &now) + operator := testIdentity("operator/alice") + enrollFleet(t, s, 8) + if _, e := s.Propose(operator, proposal(now)); e != nil { + t.Fatal(e) } - n = approved.Experiment - } - if n.Status != pb.ExperimentStatus_EXPERIMENT_STATUS_COMPLETE { - t.Fatal("rollout did not complete") - } - for _, id := range n.CohortOrder { - d, e := desired(context.Background(), s.Store.DB, id) + r, e := s.Review(operator, &pb.ReviewRequest{ExperimentId: "experiment-1", ExpectedRevision: 1, Approve: true, Reason: "initial", RequestId: "initial"}) if e != nil { t.Fatal(e) } - if d.Assignments[0].Mode != pb.AssignmentMode_ASSIGNMENT_MODE_STABLE || d.Assignments[0].LeaseExpiresAt != nil { - t.Fatal("stable treatment still leased") + now = now.Add(5 * time.Minute) + acknowledgeFleet(t, s, r.Experiment) + ev := proto.Clone(r.Experiment.Baseline).(*pb.Evidence) + ev.Id = "harmful" + ev.WindowStart = r.Experiment.Phases[0].IssuedAt + ev.WindowEnd = timestamppb.New(now) + ev.AnalyzedAt = ev.WindowEnd + ev.Hosts[0].RetransmissionRatio = .1 + if e = s.Maintain(context.Background(), fixedEvidence{parseEvidence(t, ev)}); e != nil { + t.Fatal(e) } - } - for i, action := range []pb.ControlAction{pb.ControlAction_CONTROL_ACTION_SUSPEND, pb.ControlAction_CONTROL_ACTION_RESUME} { - if _, e = s.Control(operator, &pb.ControlRequest{ExperimentId: n.Id, Action: action, Reason: "withdrawal comparison", RequestId: fmt.Sprintf("withdrawal-%d", i)}); e != nil { + v, e := s.Inspect(operator, &pb.InspectRequest{ExperimentId: r.Experiment.Id}) + if e != nil { t.Fatal(e) } - } - inspected, e := s.Inspect(operator, &pb.InspectRequest{ExperimentId: n.Id}) - if e != nil { - t.Fatal(e) - } - if inspected.Experiments[0].Status != pb.ExperimentStatus_EXPERIMENT_STATUS_COMPLETE { - t.Fatal("resume did not retain stable approval") - } + if v.Experiments[0].Status != pb.ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK { + t.Fatal("harm was not withdrawn before thirty minutes") + } + // Withdrawal leaves a retired tombstone so the agent removes the route. + for _, id := range r.Experiment.CohortOrder[:1] { + d := desiredOf(t, st, id) + if len(d.Assignments) != 1 || d.Assignments[0].Mode != treatment.ModeRetired { + t.Fatalf("withdrawn experiment left %+v", d.Assignments) + } + } + }) } -func TestPostgresFastSafetyWithdrawalBeforePromotionWindow(t *testing.T) { - now := time.Now().UTC() - s := &Server{Store: testStore(t), Now: func() time.Time { return now }} - operator := testIdentity("operator/alice") - for i := range 8 { - enroll(t, s, fmt.Sprintf("host-%d", i)) - } - if _, e := s.Propose(operator, proposal(now)); e != nil { - t.Fatal(e) - } - r, e := s.Review(operator, &pb.ReviewRequest{ExperimentId: "experiment-1", ExpectedRevision: 1, Approve: true, Reason: "initial", RequestId: "initial"}) - if e != nil { - t.Fatal(e) - } - now = now.Add(5 * time.Minute) - acknowledgeFleet(t, s, r.Experiment) - ev := proto.Clone(r.Experiment.Baseline).(*pb.Evidence) - ev.Id = "harmful" - ev.WindowStart = r.Experiment.Phases[0].IssuedAt - ev.WindowEnd = timestamppb.New(now) - ev.AnalyzedAt = ev.WindowEnd - ev.Hosts[0].RetransmissionRatio = .1 - if e = s.Maintain(context.Background(), fixedEvidence{ev}); e != nil { - t.Fatal(e) - } - v, e := s.Inspect(operator, &pb.InspectRequest{ExperimentId: r.Experiment.Id}) - if e != nil { - t.Fatal(e) - } - if v.Experiments[0].Status != pb.ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK { - t.Fatal("harm was not withdrawn before thirty minutes") - } +func TestAnalyzeRefreshesWithoutApproval(t *testing.T) { + eachStore(t, func(t *testing.T, st store.Store) { + now := time.Now().UTC().Truncate(time.Microsecond) + s := testServer(st, &now) + operator := testIdentity("operator/alice") + enrollFleet(t, s, 8) + p := proposal(now) + if _, e := s.Propose(operator, p); e != nil { + t.Fatal(e) + } + ev := proto.Clone(p.Experiment.Baseline).(*pb.Evidence) + ev.Id = "refresh-1" + s.EvidenceSource = fixedEvidence{parseEvidence(t, ev)} + out, e := s.Analyze(operator, &pb.AnalyzeRequest{ExperimentId: p.Experiment.Id, ExpectedRevision: 1}) + if e != nil || out.Outcome != pb.Outcome_OUTCOME_BASELINE { + t.Fatal("refresh", out, e) + } + inspected, e := s.Inspect(operator, &pb.InspectRequest{ExperimentId: p.Experiment.Id}) + if e != nil { + t.Fatal(e) + } + n := inspected.Experiments[0] + if n.Status != pb.ExperimentStatus_EXPERIMENT_STATUS_PROPOSED || n.Revision != 1 || len(n.Phases) > 0 || !proto.Equal(n.LatestEvidence, ev) { + t.Fatal("analysis changed rollout authority") + } + if _, e = s.Analyze(operator, &pb.AnalyzeRequest{ExperimentId: n.Id, ExpectedRevision: 2}); e == nil { + t.Fatal("stale analysis revision accepted") + } + if _, e = s.Analyze(testIdentity("agent/dc1/host-0"), &pb.AnalyzeRequest{ExperimentId: n.Id, ExpectedRevision: 1}); e == nil { + t.Fatal("agent could analyze as operator") + } + }) } -func TestPostgresAnalyzeRefreshesWithoutApproval(t *testing.T) { - now := time.Now().UTC() - s := &Server{Store: testStore(t), Now: func() time.Time { return now }} - operator := testIdentity("operator/alice") - for i := range 8 { - enroll(t, s, fmt.Sprintf("host-%d", i)) - } - p := proposal(now) - if _, e := s.Propose(operator, p); e != nil { - t.Fatal(e) - } - ev := proto.Clone(p.Experiment.Baseline).(*pb.Evidence) - ev.Id = "refresh-1" - s.EvidenceSource = fixedEvidence{ev} - out, e := s.Analyze(operator, &pb.AnalyzeRequest{ExperimentId: p.Experiment.Id, ExpectedRevision: 1}) - if e != nil || out.Outcome != pb.Outcome_OUTCOME_BASELINE { - t.Fatal("refresh", out, e) - } - inspected, e := s.Inspect(operator, &pb.InspectRequest{ExperimentId: p.Experiment.Id}) - if e != nil { - t.Fatal(e) - } - n := inspected.Experiments[0] - if n.Status != pb.ExperimentStatus_EXPERIMENT_STATUS_PROPOSED || n.Revision != 1 || len(n.Phases) > 0 || !proto.Equal(n.LatestEvidence, ev) { - t.Fatal("analysis changed rollout authority") - } - if _, e = s.Analyze(operator, &pb.AnalyzeRequest{ExperimentId: n.Id, ExpectedRevision: 2}); e == nil { - t.Fatal("stale analysis revision accepted") - } - if _, e = s.Analyze(testIdentity("agent/dc1/host-0"), &pb.AnalyzeRequest{ExperimentId: n.Id, ExpectedRevision: 1}); e == nil { - t.Fatal("agent could analyze as operator") - } +// TestExperimentUpdatedAtFollowsControllerClock pins the cooldown input: +// every mutation stamps updated_at from the controller clock, not the database. +func TestExperimentUpdatedAtFollowsControllerClock(t *testing.T) { + eachStore(t, func(t *testing.T, st store.Store) { + now := time.Unix(1800000000, 0).UTC() + s := testServer(st, &now) + operator := testIdentity("operator/alice") + enrollFleet(t, s, 8) + if _, e := s.Propose(operator, proposal(now)); e != nil { + t.Fatal(e) + } + if got := experimentsOf(t, st)[0]; !got.UpdatedAt.Equal(now) || !got.ProposedAt.Equal(now) { + t.Fatalf("proposal stamped %s / %s, want %s", got.ProposedAt, got.UpdatedAt, now) + } + now = now.Add(time.Hour) + if _, e := s.Control(operator, &pb.ControlRequest{ExperimentId: "experiment-1", Action: pb.ControlAction_CONTROL_ACTION_ROLLBACK, Reason: "test", RequestId: "rollback-1"}); e != nil { + t.Fatal(e) + } + got := experimentsOf(t, st)[0] + if got.Status != treatment.StatusRolledBack || !got.UpdatedAt.Equal(now) { + t.Fatalf("rollback stamped %s status %s, want %s rolled_back", got.UpdatedAt, got.Status, now) + } + if !proto.Equal(wire.Experiment(got).Treatment, proposal(now).Experiment.Treatment) { + t.Fatal("rollback changed the treatment") + } + }) } diff --git a/pkg/treatment/controller/status.go b/pkg/treatment/controller/status.go index 673517b60..7f1aa2a9f 100644 --- a/pkg/treatment/controller/status.go +++ b/pkg/treatment/controller/status.go @@ -4,10 +4,11 @@ import ( "context" "encoding/json" "fmt" - "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" "net/http" "sort" "time" + + "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" ) // StatusHandler provides an operator-authenticated JSON dashboard feed and @@ -34,44 +35,29 @@ func (s *Server) StatusHandler() http.Handler { ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second) defer cancel() if r.URL.Path == "/readyz" { - if e := s.Store.DB.PingContext(ctx); e != nil { + if e := s.Store.Ping(ctx); e != nil { http.Error(w, "database unavailable", http.StatusServiceUnavailable) return } fmt.Fprintln(w, "ready") return } - queries := map[string]string{ - "agents": "SELECT count(*) FROM xtcp_control_agents", - "agents_connected": "SELECT count(*) FROM xtcp_control_agents WHERE last_seen>now()-interval '2 minutes'", - "agents_suspended": "SELECT count(*) FROM xtcp_control_agents WHERE desired->>'suspended'='true' OR observed->>'locallySuspended'='true'", - "assignments": "SELECT coalesce(sum(jsonb_array_length(coalesce(desired->'assignments','[]'))),0) FROM xtcp_control_agents", - "routes_verified": "SELECT count(*) FROM xtcp_control_agents a CROSS JOIN LATERAL jsonb_array_elements(coalesce(a.observed->'records','[]')) r WHERE r->>'status'='APPLY_STATUS_VERIFIED'", - "routes_failed": "SELECT count(*) FROM xtcp_control_agents a CROSS JOIN LATERAL jsonb_array_elements(coalesce(a.observed->'records','[]')) r WHERE r->>'status'='APPLY_STATUS_FAILED'", - "routes_expired": "SELECT count(*) FROM xtcp_control_agents a CROSS JOIN LATERAL jsonb_array_elements(coalesce(a.observed->'records','[]')) r WHERE r->>'status'='APPLY_STATUS_EXPIRED'", - "experiments_pending": "SELECT count(*) FROM xtcp_control_experiments WHERE state->>'status'='EXPERIMENT_STATUS_PROPOSED'", - "experiments_running": "SELECT count(*) FROM xtcp_control_experiments WHERE state->>'status'='EXPERIMENT_STATUS_RUNNING'", - "experiments_complete": "SELECT count(*) FROM xtcp_control_experiments WHERE state->>'status'='EXPERIMENT_STATUS_COMPLETE'", - "audit_events": "SELECT count(*) FROM xtcp_control_events", - } - values := map[string]float64{} - keys := make([]string, 0, len(queries)) - for k, q := range queries { - var v float64 - if e := s.Store.DB.QueryRowContext(ctx, q).Scan(&v); e != nil { - http.Error(w, "status query failed", http.StatusServiceUnavailable) - return - } - values[k] = v - keys = append(keys, k) + values, e := s.Store.Repos().Metrics.Snapshot(ctx, s.now()) + if e != nil { + http.Error(w, "status query failed", http.StatusServiceUnavailable) + return } if r.URL.Path == "/status" { w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(values) return } - w.Header().Set("Content-Type", "text/plain; version=0.0.4") + keys := make([]string, 0, len(values)) + for k := range values { + keys = append(keys, k) + } sort.Strings(keys) + w.Header().Set("Content-Type", "text/plain; version=0.0.4") for _, k := range keys { fmt.Fprintf(w, "# TYPE xtcp_controller_%s gauge\nxtcp_controller_%s %g\n", k, k, values[k]) } diff --git a/pkg/treatment/controller/store.go b/pkg/treatment/controller/store.go deleted file mode 100644 index f8e56e495..000000000 --- a/pkg/treatment/controller/store.go +++ /dev/null @@ -1,175 +0,0 @@ -package controller - -import ( - "context" - "database/sql" - _ "embed" - "errors" - "fmt" - "time" - - _ "github.com/lib/pq" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" - "google.golang.org/protobuf/encoding/protojson" - "google.golang.org/protobuf/proto" -) - -//go:embed schema.sql -var schema string - -type Store struct{ DB *sql.DB } - -type rowQuerier interface { - QueryRowContext(context.Context, string, ...any) *sql.Row -} - -const activeExperimentSQL = `SELECT EXISTS(SELECT 1 FROM xtcp_control_experiments - WHERE location=$1 AND state->>'status' IN - ('EXPERIMENT_STATUS_PROPOSED','EXPERIMENT_STATUS_RUNNING','EXPERIMENT_STATUS_PAUSED','EXPERIMENT_STATUS_SUSPENDED'))` - -const discoveryDestinationSQL = `SELECT EXISTS(SELECT 1 FROM xtcp_control_experiments - WHERE location=$1 AND state->'treatment'->>'destination'=$2 - AND (state->>'status' NOT IN ('EXPERIMENT_STATUS_RETIRED','EXPERIMENT_STATUS_ROLLED_BACK') OR updated_at > $3))` - -var errExperimentBudget = errors.New("data-center experiment budget exhausted (one active experiment)") -var errDiscoverySuppressed = errors.New("destination already treated, held, proposed, or in cooldown") - -func hasActiveExperiment(ctx context.Context, q rowQuerier, location string) (bool, error) { - var exists bool - err := q.QueryRowContext(ctx, activeExperimentSQL, location).Scan(&exists) - return exists, err -} - -func discoveryDestinationBlocked(ctx context.Context, q rowQuerier, location, destination string, now time.Time) (bool, error) { - var exists bool - err := q.QueryRowContext(ctx, discoveryDestinationSQL, location, destination, now.Add(-24*time.Hour)).Scan(&exists) - return exists, err -} - -func Open(ctx context.Context, dsn string) (*Store, error) { - db, e := sql.Open("postgres", dsn) - if e != nil { - return nil, e - } - db.SetMaxOpenConns(32) - db.SetMaxIdleConns(8) - db.SetConnMaxLifetime(30 * time.Minute) - if e = db.PingContext(ctx); e != nil { - db.Close() - return nil, e - } - return &Store{DB: db}, nil -} -func (s *Store) Migrate(ctx context.Context) error { _, e := s.DB.ExecContext(ctx, schema); return e } -func jsonMessage(m proto.Message) (string, error) { b, e := protojson.Marshal(m); return string(b), e } - -// Control mutations are rare and use one transaction-level advisory lock. -// This fences controller replicas without holding leadership during analysis. -// Agent heartbeats do not take this lock or rewrite experiments/fleet state. -func (s *Store) mutate(ctx context.Context, fn func(*sql.Tx) error) error { - tx, e := s.DB.BeginTx(ctx, nil) - if e != nil { - return e - } - defer tx.Rollback() - if _, e = tx.ExecContext(ctx, "SELECT pg_advisory_xact_lock(867530902)"); e != nil { - return e - } - if e = fn(tx); e != nil { - return e - } - return tx.Commit() -} - -func loadExperiment(ctx context.Context, tx *sql.Tx, id string) (*pb.Experiment, error) { - var raw []byte - if e := tx.QueryRowContext(ctx, "SELECT state FROM xtcp_control_experiments WHERE id=$1 FOR UPDATE", id).Scan(&raw); e != nil { - return nil, e - } - v := new(pb.Experiment) - if e := protojson.Unmarshal(raw, v); e != nil { - return nil, e - } - return v, nil -} -func saveExperiment(ctx context.Context, tx *sql.Tx, e *pb.Experiment) error { - b, err := jsonMessage(e) - if err != nil { - return err - } - _, err = tx.ExecContext(ctx, "INSERT INTO xtcp_control_experiments(id,location,state) VALUES($1,$2,$3) ON CONFLICT(id) DO UPDATE SET state=EXCLUDED.state,updated_at=now()", e.Id, e.Location, b) - return err -} -func desired(ctx context.Context, q interface { - QueryRowContext(context.Context, string, ...any) *sql.Row -}, id string) (*pb.DesiredState, error) { - var raw []byte - if e := q.QueryRowContext(ctx, "SELECT desired FROM xtcp_control_agents WHERE id=$1", id).Scan(&raw); e != nil { - return nil, e - } - d := new(pb.DesiredState) - if e := protojson.Unmarshal(raw, d); e != nil { - return nil, e - } - return d, nil -} -func saveDesired(ctx context.Context, tx *sql.Tx, d *pb.DesiredState) error { - b, e := jsonMessage(d) - if e != nil { - return e - } - res, e := tx.ExecContext(ctx, "UPDATE xtcp_control_agents SET desired=$2 WHERE id=$1", d.AgentId, b) - if e != nil { - return e - } - n, e := res.RowsAffected() - if e == nil && n != 1 { - return errors.New("agent not enrolled") - } - return e -} -func audit(ctx context.Context, tx *sql.Tx, actor, id, kind string, m proto.Message) error { - b, e := jsonMessage(m) - if e != nil { - return e - } - _, e = tx.ExecContext(ctx, "INSERT INTO xtcp_control_events(actor,request_id,kind,payload) VALUES($1,NULLIF($2,''),$3,$4)", actor, id, kind, b) - return e -} - -// Repeated request IDs must have the same operation and request body. The -// original response is returned rather than applying a second mutation. -func replay(ctx context.Context, tx *sql.Tx, id, kind string, request, response proto.Message) (bool, error) { - var k string - var raw []byte - e := tx.QueryRowContext(ctx, "SELECT kind,payload FROM xtcp_control_events WHERE request_id=$1", id).Scan(&k, &raw) - if errors.Is(e, sql.ErrNoRows) { - return false, nil - } - if e != nil { - return false, e - } - if k != kind { - return false, errors.New("request_id reused for another operation") - } - // Request audit is paired with a response event under request_id + '.result'. - want, e := jsonMessage(request) - if e != nil { - return false, e - } - copyReq := request.ProtoReflect().New().Interface() - if e = protojson.Unmarshal(raw, copyReq); e != nil { - return false, e - } - got, e := jsonMessage(copyReq) - if e != nil { - return false, e - } - if got != want { - return false, errors.New("request_id reused with different body") - } - if e = tx.QueryRowContext(ctx, "SELECT payload FROM xtcp_control_events WHERE request_id=$1", id+".result").Scan(&raw); e != nil { - return false, fmt.Errorf("missing idempotent response: %w", e) - } - return true, protojson.Unmarshal(raw, response) -} diff --git a/pkg/treatment/controller/store/memory/memory.go b/pkg/treatment/controller/store/memory/memory.go new file mode 100644 index 000000000..395e192e7 --- /dev/null +++ b/pkg/treatment/controller/store/memory/memory.go @@ -0,0 +1,459 @@ +// Package memory implements the controller's store ports with maps. It is +// for handler tests: it honours every sentinel error and rolls a failed Tx +// back, but it has no durability and no concurrency beyond one mutex. +package memory + +import ( + "context" + "maps" + "net/netip" + "slices" + "sort" + "sync" + "time" + + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store" +) + +type agentRow struct { + store.Agent + desired treatment.DesiredState + updatedAt time.Time +} + +type evidenceRow struct { + owner string + value *treatment.Evidence +} + +type experimentRow struct{ value *treatment.Experiment } + +type state struct { + agents map[string]*agentRow + experiments map[string]*experimentRow + evidence map[string]evidenceRow + holds map[string]string + events []store.Event + sequence int64 +} + +func (s *state) clone() *state { + c := &state{agents: map[string]*agentRow{}, experiments: map[string]*experimentRow{}, evidence: map[string]evidenceRow{}, holds: maps.Clone(s.holds), events: slices.Clone(s.events), sequence: s.sequence} + for k, v := range s.agents { + c.agents[k] = cloneAgent(v) + } + for k, v := range s.experiments { + c.experiments[k] = &experimentRow{value: v.value.Clone()} + } + for k, v := range s.evidence { + c.evidence[k] = evidenceRow{owner: v.owner, value: v.value.Clone()} + } + return c +} + +func cloneDesired(d *treatment.DesiredState) treatment.DesiredState { + c := *d + c.Assignments = slices.Clone(d.Assignments) + for i := range c.Assignments { + if p := c.Assignments[i].Predecessor; p != nil { + v := *p + c.Assignments[i].Predecessor = &v + } + } + return c +} + +func cloneReport(r *treatment.AgentReport) treatment.AgentReport { + c := *r + c.Capabilities.CongestionAlgorithms = slices.Clone(r.Capabilities.CongestionAlgorithms) + c.Records = slices.Clone(r.Records) + for i := range c.Records { + if p := c.Records[i].Assignment.Predecessor; p != nil { + v := *p + c.Records[i].Assignment.Predecessor = &v + } + } + return c +} + +func cloneAgent(a *agentRow) *agentRow { + c := *a + c.Report = cloneReport(&a.Report) + c.desired = cloneDesired(&a.desired) + return &c +} + +// Store satisfies store.Store. +type Store struct { + mu sync.Mutex + st *state + // Now stamps updated_at columns; tests may pin it. + Now func() time.Time +} + +func New() *Store { + return &Store{st: &state{agents: map[string]*agentRow{}, experiments: map[string]*experimentRow{}, evidence: map[string]evidenceRow{}, holds: map[string]string{}}} +} + +func (s *Store) now() time.Time { + if s.Now != nil { + return s.Now() + } + return time.Now() +} + +func (s *Store) Ping(context.Context) error { return nil } +func (s *Store) Close() error { return nil } + +func (s *Store) Repos() store.Repos { return s.repos(true) } + +func (s *Store) repos(lock bool) store.Repos { + r := &repo{s: s, lock: lock} + return store.Repos{Agents: agentsRepo{r}, Experiments: experimentsRepo{r}, Evidence: evidenceRepo{r}, Holds: holdsRepo{r}, Audit: auditRepo{r}, Metrics: metricsRepo{r}} +} + +// Tx serializes against every other call and restores the previous state +// when fn fails, which is what a rolled-back transaction looks like. +func (s *Store) Tx(ctx context.Context, fn func(store.Repos) error) error { + s.mu.Lock() + defer s.mu.Unlock() + if err := ctx.Err(); err != nil { + return err + } + snapshot := s.st.clone() + if err := fn(s.repos(false)); err != nil { + s.st = snapshot + return err + } + return nil +} + +type repo struct { + s *Store + lock bool +} + +// One method set per port keeps the Get/List names from colliding. +type ( + agentsRepo struct{ *repo } + experimentsRepo struct{ *repo } + evidenceRepo struct{ *repo } + holdsRepo struct{ *repo } + auditRepo struct{ *repo } + metricsRepo struct{ *repo } +) + +func (r *repo) enter() func() { + if !r.lock { + return func() {} + } + r.s.mu.Lock() + return r.s.mu.Unlock +} + +// Agents + +func (r agentsRepo) Enroll(_ context.Context, id, location string, initial *treatment.DesiredState) error { + defer r.enter()() + if _, ok := r.s.st.agents[id]; ok { + return nil + } + d := cloneDesired(initial) + d.AgentID = id + r.s.st.agents[id] = &agentRow{Agent: store.Agent{ID: id, Location: location, EnrolledAt: r.s.now()}, desired: d, updatedAt: r.s.now()} + return nil +} + +func (r agentsRepo) Get(_ context.Context, id string) (*store.Agent, error) { + defer r.enter()() + a, ok := r.s.st.agents[id] + if !ok { + return nil, store.ErrNotFound + } + c := cloneAgent(a) + c.Report.AgentID, c.Report.Location = id, a.Location + return &c.Agent, nil +} + +func (r agentsRepo) Observe(_ context.Context, report *treatment.AgentReport, seen time.Time) error { + defer r.enter()() + a, ok := r.s.st.agents[report.AgentID] + if !ok || a.Location != report.Location { + return store.ErrNotEnrolled + } + a.Report = cloneReport(report) + a.LastSeen = seen + return nil +} + +func (r agentsRepo) Desired(_ context.Context, id string) (*treatment.DesiredState, error) { + defer r.enter()() + a, ok := r.s.st.agents[id] + if !ok { + return nil, store.ErrNotEnrolled + } + d := cloneDesired(&a.desired) + return &d, nil +} + +func (r agentsRepo) SaveDesired(_ context.Context, d *treatment.DesiredState) error { + defer r.enter()() + a, ok := r.s.st.agents[d.AgentID] + if !ok { + return store.ErrNotEnrolled + } + a.desired = cloneDesired(d) + a.updatedAt = r.s.now() + return nil +} + +func (r agentsRepo) List(_ context.Context, location string) ([]store.AgentRef, error) { + defer r.enter()() + var out []store.AgentRef + for id, a := range r.s.st.agents { + if location == "" || a.Location == location { + out = append(out, store.AgentRef{ID: id, Location: a.Location}) + } + } + sort.Slice(out, func(i, j int) bool { return out[i].ID < out[j].ID }) + return out, nil +} + +func (r agentsRepo) DesiredRevisions(context.Context) (map[string]uint64, error) { + defer r.enter()() + out := make(map[string]uint64, len(r.s.st.agents)) + for id, a := range r.s.st.agents { + out[id] = a.desired.Revision + } + return out, nil +} + +// Experiments + +func (r experimentsRepo) Exists(_ context.Context, id string) (bool, error) { + defer r.enter()() + _, ok := r.s.st.experiments[id] + return ok, nil +} + +func (r *repo) getExperiment(id string) (*treatment.Experiment, error) { + e, ok := r.s.st.experiments[id] + if !ok { + return nil, store.ErrNotFound + } + return e.value.Clone(), nil +} + +func (r experimentsRepo) Get(ctx context.Context, id string) (*treatment.Experiment, error) { + defer r.enter()() + return r.getExperiment(id) +} + +func (r experimentsRepo) Save(_ context.Context, e *treatment.Experiment) error { + defer r.enter()() + for _, ev := range []*treatment.Evidence{e.Baseline, e.LatestEvidence} { + if ev != nil { + if err := r.put(e.ID, ev); err != nil { + return err + } + } + } + for i := range e.Phases { + if ev := e.Phases[i].Evidence; ev != nil { + if err := r.put(e.ID, ev); err != nil { + return err + } + } + } + n := e.Clone() + if prev, ok := r.s.st.experiments[e.ID]; ok { + // The cohort is fixed at proposal; Postgres writes it once too. + n.CohortOrder = slices.Clone(prev.value.CohortOrder) + } + r.s.st.experiments[e.ID] = &experimentRow{value: n} + return nil +} + +func (r experimentsRepo) List(_ context.Context, id string, limit int) ([]*treatment.Experiment, error) { + defer r.enter()() + ids := make([]string, 0, len(r.s.st.experiments)) + for k := range r.s.st.experiments { + if id == "" || k == id { + ids = append(ids, k) + } + } + sort.Strings(ids) + if len(ids) > limit { + ids = ids[:limit] + } + out := make([]*treatment.Experiment, 0, len(ids)) + for _, k := range ids { + out = append(out, r.s.st.experiments[k].value.Clone()) + } + return out, nil +} + +func (r experimentsRepo) IDs(_ context.Context, statuses ...treatment.Status) ([]string, error) { + defer r.enter()() + var out []string + for id, e := range r.s.st.experiments { + if slices.Contains(statuses, e.value.Status) { + out = append(out, id) + } + } + sort.Strings(out) + return out, nil +} + +func (r experimentsRepo) HasActive(_ context.Context, location string) (bool, error) { + defer r.enter()() + for _, e := range r.s.st.experiments { + if e.value.Location == location && e.value.Status.Active() { + return true, nil + } + } + return false, nil +} + +func (r experimentsRepo) DestinationBlocked(_ context.Context, location string, destination netip.Prefix, since time.Time) (bool, error) { + defer r.enter()() + for _, e := range r.s.st.experiments { + if e.value.Location == location && e.value.Treatment.Destination == destination && (!e.value.Status.Terminal() || e.value.UpdatedAt.After(since)) { + return true, nil + } + } + return false, nil +} + +// Evidence + +func (r evidenceRepo) Get(_ context.Context, id string) (*treatment.Evidence, error) { + defer r.enter()() + v, ok := r.s.st.evidence[id] + if !ok { + return nil, store.ErrNotFound + } + return v.value.Clone(), nil +} + +func (r *repo) put(owner string, e *treatment.Evidence) error { + if have, ok := r.s.st.evidence[e.ID]; ok { + if have.owner != owner || !have.value.Equal(e) { + return store.ErrEvidenceConflict + } + return nil + } + r.s.st.evidence[e.ID] = evidenceRow{owner: owner, value: e.Clone()} + return nil +} + +func (r evidenceRepo) Put(_ context.Context, experimentID string, e *treatment.Evidence) error { + defer r.enter()() + return r.put(experimentID, e) +} + +// Holds + +func (r holdsRepo) Set(_ context.Context, scope, reason string) error { + defer r.enter()() + r.s.st.holds[scope] = reason + return nil +} + +func (r holdsRepo) Clear(_ context.Context, scope string) error { + defer r.enter()() + delete(r.s.st.holds, scope) + return nil +} + +func (r holdsRepo) Held(_ context.Context, scopes ...string) (bool, error) { + defer r.enter()() + for _, s := range scopes { + if _, ok := r.s.st.holds[s]; ok { + return true, nil + } + } + return false, nil +} + +// Audit + +func (r auditRepo) Append(_ context.Context, actor, requestID, kind string, payload []byte) error { + defer r.enter()() + if requestID != "" { + for i := range r.s.st.events { + if r.s.st.events[i].RequestID == requestID { + return store.ErrExists + } + } + } + r.s.st.sequence++ + r.s.st.events = append(r.s.st.events, store.Event{Sequence: r.s.st.sequence, OccurredAt: r.s.now(), Actor: actor, RequestID: requestID, Kind: kind, Payload: slices.Clone(payload)}) + return nil +} + +func (r auditRepo) Lookup(_ context.Context, requestID string) (*store.Event, error) { + defer r.enter()() + if requestID == "" { + return nil, store.ErrNotFound + } + for i := range r.s.st.events { + if r.s.st.events[i].RequestID == requestID { + e := r.s.st.events[i] + e.Payload = slices.Clone(e.Payload) + return &e, nil + } + } + return nil, store.ErrNotFound +} + +func (r auditRepo) Count(context.Context) (int64, error) { + defer r.enter()() + return int64(len(r.s.st.events)), nil +} + +// Events returns a copy of the whole log, for assertions. +func (s *Store) Events() []store.Event { + s.mu.Lock() + defer s.mu.Unlock() + return slices.Clone(s.st.events) +} + +// Metrics + +func (r metricsRepo) Snapshot(_ context.Context, now time.Time) (map[string]float64, error) { + defer r.enter()() + m := map[string]float64{"agents": 0, "agents_connected": 0, "agents_suspended": 0, "assignments": 0, "routes_verified": 0, "routes_failed": 0, "routes_expired": 0, "experiments_pending": 0, "experiments_running": 0, "experiments_complete": 0, "audit_events": float64(len(r.s.st.events))} + for _, a := range r.s.st.agents { + m["agents"]++ + if a.Seen(now, 2*time.Minute) { + m["agents_connected"]++ + } + if a.desired.Suspended || a.Report.LocallySuspended { + m["agents_suspended"]++ + } + m["assignments"] += float64(len(a.desired.Assignments)) + for i := range a.Report.Records { + switch a.Report.Records[i].Status { + case treatment.ApplyVerified: + m["routes_verified"]++ + case treatment.ApplyFailed: + m["routes_failed"]++ + case treatment.ApplyExpired: + m["routes_expired"]++ + } + } + } + for _, e := range r.s.st.experiments { + switch e.value.Status { + case treatment.StatusProposed: + m["experiments_pending"]++ + case treatment.StatusRunning: + m["experiments_running"]++ + case treatment.StatusComplete: + m["experiments_complete"]++ + } + } + return m, nil +} diff --git a/pkg/treatment/controller/store/memory/memory_test.go b/pkg/treatment/controller/store/memory/memory_test.go new file mode 100644 index 000000000..f4ed8d9ca --- /dev/null +++ b/pkg/treatment/controller/store/memory/memory_test.go @@ -0,0 +1,13 @@ +package memory_test + +import ( + "testing" + + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store/memory" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store/storetest" +) + +func TestMemoryConformance(t *testing.T) { + storetest.Run(t, func(*testing.T) store.Store { return memory.New() }) +} diff --git a/pkg/treatment/controller/store/pgtest/pgtest.go b/pkg/treatment/controller/store/pgtest/pgtest.go new file mode 100644 index 000000000..38efe73ca --- /dev/null +++ b/pkg/treatment/controller/store/pgtest/pgtest.go @@ -0,0 +1,78 @@ +// Package pgtest opens an isolated PostgreSQL store for tests. Each call +// creates a fresh schema on the database named by XTCP_CONTROLLER_TEST_DSN, +// points the connection's search_path at it and drops it on cleanup, so +// tests share one server without sharing rows. +package pgtest + +import ( + "context" + "database/sql" + "fmt" + "net/url" + "os" + "strings" + "sync/atomic" + "testing" + "time" + + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store/postgres" +) + +// EnvDSN names the environment variable that enables PostgreSQL tests. +const EnvDSN = "XTCP_CONTROLLER_TEST_DSN" + +var sequence atomic.Int64 + +// Available reports whether a PostgreSQL DSN is configured. +func Available() bool { return os.Getenv(EnvDSN) != "" } + +// Open returns a migrated store on a private schema, or skips the test. +func Open(t testing.TB) *postgres.Store { + t.Helper() + dsn := os.Getenv(EnvDSN) + if dsn == "" { + t.Skipf("set %s for PostgreSQL integration tests", EnvDSN) + } + admin, err := sql.Open("pgx", dsn) + if err != nil { + t.Fatal(err) + } + schema := fmt.Sprintf("xtcp_test_%d_%d", time.Now().UnixNano(), sequence.Add(1)) + if _, err = admin.Exec("CREATE SCHEMA " + schema); err != nil { + admin.Close() + t.Fatal(err) + } + scoped, err := withSearchPath(dsn, schema) + if err != nil { + t.Fatal(err) + } + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + s, err := postgres.Open(ctx, scoped) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + s.Close() + _, _ = admin.Exec("DROP SCHEMA " + schema + " CASCADE") + admin.Close() + }) + if err = s.Migrate(ctx); err != nil { + t.Fatal(err) + } + return s +} + +func withSearchPath(dsn, schema string) (string, error) { + if strings.HasPrefix(dsn, "postgres://") || strings.HasPrefix(dsn, "postgresql://") { + u, err := url.Parse(dsn) + if err != nil { + return "", err + } + q := u.Query() + q.Set("search_path", schema) + u.RawQuery = q.Encode() + return u.String(), nil + } + return dsn + " search_path=" + schema, nil +} diff --git a/pkg/treatment/controller/store/postgres/agents.go b/pkg/treatment/controller/store/postgres/agents.go new file mode 100644 index 000000000..6c4f10f76 --- /dev/null +++ b/pkg/treatment/controller/store/postgres/agents.go @@ -0,0 +1,210 @@ +package postgres + +import ( + "context" + "database/sql" + "errors" + "time" + + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store" +) + +type agents struct{ *runner } + +func (a *agents) Enroll(ctx context.Context, id, location string, initial *treatment.DesiredState) error { + return a.atomic(ctx, func(q querier) error { + res, err := q.ExecContext(ctx, `INSERT INTO xtcp_treatment_agents(id,location,desired_revision,desired_suspended,desired_issued_at) + VALUES($1,$2,$3,$4,$5) ON CONFLICT(id) DO NOTHING`, id, location, int64(initial.Revision), initial.Suspended, initial.IssuedAt) + if err != nil { + return err + } + n, err := res.RowsAffected() + if err != nil || n == 0 { + return err + } + return insertAssignments(ctx, q, id, initial.Assignments) + }) +} + +func (a *agents) Get(ctx context.Context, id string) (*store.Agent, error) { + q := a.q() + out := &store.Agent{ID: id} + var seen sql.NullTime + var reported int64 + err := q.QueryRowContext(ctx, `SELECT location,enrolled_at,last_seen,boot_id,kernel_release,simple_host_routing,actuator_enabled,locally_suspended,reported_revision + FROM xtcp_treatment_agents WHERE id=$1`, id).Scan(&out.Location, &out.EnrolledAt, &seen, &out.Report.Capabilities.BootID, &out.Report.Capabilities.KernelRelease, &out.Report.Capabilities.SimpleHostRouting, &out.Report.Capabilities.ActuatorEnabled, &out.Report.LocallySuspended, &reported) + if err != nil { + return nil, notFound(err) + } + out.LastSeen = fromNullTime(seen) + out.Report.AgentID, out.Report.Location, out.Report.DesiredRevision = id, out.Location, uint64(reported) + rows, err := q.QueryContext(ctx, "SELECT name FROM xtcp_treatment_agent_algorithms WHERE agent_id=$1 ORDER BY position", id) + if err != nil { + return nil, err + } + for rows.Next() { + var name string + if err = rows.Scan(&name); err != nil { + rows.Close() + return nil, err + } + out.Report.Capabilities.CongestionAlgorithms = append(out.Report.Capabilities.CongestionAlgorithms, name) + } + if err = errors.Join(rows.Err(), rows.Close()); err != nil { + return nil, err + } + rows, err = q.QueryContext(ctx, "SELECT "+selectList(recordColumns)+" FROM xtcp_treatment_records WHERE agent_id=$1 ORDER BY position", id) + if err != nil { + return nil, err + } + for rows.Next() { + var r recordRow + if err = rows.Scan(r.targets()...); err != nil { + rows.Close() + return nil, err + } + rec, err := r.value() + if err != nil { + rows.Close() + return nil, err + } + out.Report.Records = append(out.Report.Records, rec) + } + if err = errors.Join(rows.Err(), rows.Close()); err != nil { + return nil, err + } + return out, nil +} + +func (a *agents) Observe(ctx context.Context, report *treatment.AgentReport, seen time.Time) error { + return a.atomic(ctx, func(q querier) error { + c := report.Capabilities + res, err := q.ExecContext(ctx, `UPDATE xtcp_treatment_agents SET last_seen=$3,boot_id=$4,kernel_release=$5,simple_host_routing=$6,actuator_enabled=$7,locally_suspended=$8,reported_revision=$9 + WHERE id=$1 AND location=$2`, report.AgentID, report.Location, seen, c.BootID, c.KernelRelease, c.SimpleHostRouting, c.ActuatorEnabled, report.LocallySuspended, int64(report.DesiredRevision)) + if err != nil { + return err + } + if n, err := res.RowsAffected(); err != nil || n == 0 { + if err != nil { + return err + } + return store.ErrNotEnrolled + } + if _, err = q.ExecContext(ctx, "DELETE FROM xtcp_treatment_agent_algorithms WHERE agent_id=$1", report.AgentID); err != nil { + return err + } + algos := make([][]any, len(c.CongestionAlgorithms)) + for i, name := range c.CongestionAlgorithms { + algos[i] = []any{report.AgentID, i, name} + } + if err = batchInsert(ctx, q, "xtcp_treatment_agent_algorithms", []string{"agent_id", "position", "name"}, algos); err != nil { + return err + } + if _, err = q.ExecContext(ctx, "DELETE FROM xtcp_treatment_records WHERE agent_id=$1", report.AgentID); err != nil { + return err + } + recs := make([][]any, len(report.Records)) + for i := range report.Records { + recs[i] = recordArgs(report.AgentID, i, &report.Records[i]) + } + return batchInsert(ctx, q, "xtcp_treatment_records", recordColumns, recs) + }) +} + +func (a *agents) Desired(ctx context.Context, id string) (*treatment.DesiredState, error) { + q := a.q() + d := &treatment.DesiredState{AgentID: id} + var revision int64 + err := q.QueryRowContext(ctx, "SELECT desired_revision,desired_suspended,desired_issued_at FROM xtcp_treatment_agents WHERE id=$1", id).Scan(&revision, &d.Suspended, &d.IssuedAt) + if errors.Is(err, sql.ErrNoRows) { + return nil, store.ErrNotEnrolled + } + if err != nil { + return nil, err + } + d.Revision = uint64(revision) + rows, err := q.QueryContext(ctx, "SELECT "+selectList(assignmentColumns)+" FROM xtcp_treatment_assignments WHERE agent_id=$1 ORDER BY position", id) + if err != nil { + return nil, err + } + for rows.Next() { + var r assignmentRow + if err = rows.Scan(r.targets()...); err != nil { + rows.Close() + return nil, err + } + v, err := r.value() + if err != nil { + rows.Close() + return nil, err + } + d.Assignments = append(d.Assignments, v) + } + if err = errors.Join(rows.Err(), rows.Close()); err != nil { + return nil, err + } + return d, nil +} + +func (a *agents) SaveDesired(ctx context.Context, d *treatment.DesiredState) error { + return a.atomic(ctx, func(q querier) error { + res, err := q.ExecContext(ctx, "UPDATE xtcp_treatment_agents SET desired_revision=$2,desired_suspended=$3,desired_issued_at=$4,desired_updated_at=now() WHERE id=$1", d.AgentID, int64(d.Revision), d.Suspended, d.IssuedAt) + if err != nil { + return err + } + if n, err := res.RowsAffected(); err != nil || n == 0 { + if err != nil { + return err + } + return store.ErrNotEnrolled + } + if _, err = q.ExecContext(ctx, "DELETE FROM xtcp_treatment_assignments WHERE agent_id=$1", d.AgentID); err != nil { + return err + } + return insertAssignments(ctx, q, d.AgentID, d.Assignments) + }) +} + +func insertAssignments(ctx context.Context, q querier, agentID string, assignments []treatment.Assignment) error { + rows := make([][]any, len(assignments)) + for i := range assignments { + rows[i] = assignmentArgs(agentID, i, &assignments[i]) + } + return batchInsert(ctx, q, "xtcp_treatment_assignments", assignmentColumns, rows) +} + +func (a *agents) List(ctx context.Context, location string) ([]store.AgentRef, error) { + rows, err := a.q().QueryContext(ctx, "SELECT id,location FROM xtcp_treatment_agents WHERE ($1='' OR location=$1) ORDER BY id", location) + if err != nil { + return nil, err + } + var out []store.AgentRef + for rows.Next() { + var r store.AgentRef + if err = rows.Scan(&r.ID, &r.Location); err != nil { + rows.Close() + return nil, err + } + out = append(out, r) + } + return out, errors.Join(rows.Err(), rows.Close()) +} + +func (a *agents) DesiredRevisions(ctx context.Context) (map[string]uint64, error) { + rows, err := a.q().QueryContext(ctx, "SELECT id,desired_revision FROM xtcp_treatment_agents") + if err != nil { + return nil, err + } + out := map[string]uint64{} + for rows.Next() { + var id string + var rev int64 + if err = rows.Scan(&id, &rev); err != nil { + rows.Close() + return nil, err + } + out[id] = uint64(rev) + } + return out, errors.Join(rows.Err(), rows.Close()) +} diff --git a/pkg/treatment/controller/store/postgres/columns.go b/pkg/treatment/controller/store/postgres/columns.go new file mode 100644 index 000000000..0aeff8406 --- /dev/null +++ b/pkg/treatment/controller/store/postgres/columns.go @@ -0,0 +1,155 @@ +package postgres + +import ( + "database/sql" + "fmt" + "net/netip" + + "github.com/randomizedcoder/xtcp2/pkg/treatment" +) + +// treatmentColumns are stored, with a prefix, wherever a treatment is +// embedded: experiments, assignments, records and their predecessors. +var treatmentColumns = []string{"id", "revision", "destination", "congestion_control", "congestion_lock", "rto_min_ms", "initial_congestion_window", "initial_receive_window", "quick_ack", "ecn", "ecn_fabric_verified"} + +func prefixed(prefix string, columns []string) []string { + out := make([]string, len(columns)) + for i, c := range columns { + out[i] = prefix + c + } + return out +} + +// treatmentArgs renders a treatment as insert arguments in treatmentColumns +// order; a nil treatment renders as all NULLs (an absent predecessor). +func treatmentArgs(t *treatment.Treatment) []any { + if t == nil { + return make([]any, len(treatmentColumns)) + } + s := t.Settings + return []any{t.ID, int64(t.Revision), t.Destination.String(), nullString(s.CongestionControl), s.CongestionLock, nullUint32(s.RTOMinMs), nullUint32(s.InitialCongestionWindow), nullUint32(s.InitialReceiveWindow), nullBool(s.QuickAck), nullBool(s.ECN), t.ECNFabricVerified} +} + +// treatmentRow receives treatmentColumns from a scan; every column is +// nullable so the same type reads predecessors. +type treatmentRow struct { + id, destination, cc sql.NullString + revision, rto, cwnd, rwnd sql.NullInt64 + lock, quickAck, ecn, fabric sql.NullBool +} + +func (r *treatmentRow) targets() []any { + return []any{&r.id, &r.revision, &r.destination, &r.cc, &r.lock, &r.rto, &r.cwnd, &r.rwnd, &r.quickAck, &r.ecn, &r.fabric} +} + +// value rebuilds the treatment, or nil when the id column was NULL. +func (r *treatmentRow) value() (*treatment.Treatment, error) { + if !r.id.Valid { + return nil, nil + } + dest, err := netip.ParsePrefix(r.destination.String) + if err != nil { + return nil, fmt.Errorf("stored destination %q: %w", r.destination.String, err) + } + t := &treatment.Treatment{ID: r.id.String, Revision: uint64(r.revision.Int64), Destination: dest, ECNFabricVerified: r.fabric.Bool} + t.Settings = treatment.TCPSettings{CongestionLock: r.lock.Bool, CongestionControl: optString(r.cc), RTOMinMs: optUint32(r.rto), InitialCongestionWindow: optUint32(r.cwnd), InitialReceiveWindow: optUint32(r.rwnd), QuickAck: optBool(r.quickAck), ECN: optBool(r.ecn)} + return t, nil +} + +func optString(v sql.NullString) *string { + if !v.Valid { + return nil + } + s := v.String + return &s +} + +func optUint32(v sql.NullInt64) *uint32 { + if !v.Valid { + return nil + } + u := uint32(v.Int64) + return &u +} + +func optBool(v sql.NullBool) *bool { + if !v.Valid { + return nil + } + b := v.Bool + return &b +} + +// assignmentColumns are shared by assignments and the assignment half of +// records: identity, mode, lease, treatment and predecessor. +var assignmentColumns = append(append([]string{"agent_id", "experiment_id", "position", "mode", "generation", "lease_expires_at"}, prefixed("treatment_", treatmentColumns)...), prefixed("predecessor_", treatmentColumns)...) + +func assignmentArgs(agentID string, position int, a *treatment.Assignment) []any { + args := []any{agentID, a.ExperimentID, position, a.Mode.String(), int64(a.Generation), nullTime(a.LeaseExpiresAt)} + args = append(args, treatmentArgs(&a.Treatment)...) + return append(args, treatmentArgs(a.Predecessor)...) +} + +type assignmentRow struct { + agentID, experimentID, mode string + position int + generation int64 + lease sql.NullTime + treatment, predecessor treatmentRow +} + +func (r *assignmentRow) targets() []any { + t := []any{&r.agentID, &r.experimentID, &r.position, &r.mode, &r.generation, &r.lease} + t = append(t, r.treatment.targets()...) + return append(t, r.predecessor.targets()...) +} + +func (r *assignmentRow) value() (treatment.Assignment, error) { + mode, err := treatment.ParseAssignmentMode(r.mode) + if err != nil { + return treatment.Assignment{}, err + } + t, err := r.treatment.value() + if err != nil { + return treatment.Assignment{}, err + } + if t == nil { + return treatment.Assignment{}, fmt.Errorf("assignment %s/%s has no treatment", r.agentID, r.experimentID) + } + pred, err := r.predecessor.value() + if err != nil { + return treatment.Assignment{}, err + } + return treatment.Assignment{ExperimentID: r.experimentID, Treatment: *t, Mode: mode, LeaseExpiresAt: fromNullTime(r.lease), Predecessor: pred, Generation: uint64(r.generation)}, nil +} + +var recordColumns = append(append([]string{}, assignmentColumns...), "desired_revision", "status", "received_at", "attempted_at", "applied_at", "verified_at", "removed_at", "error", "expired_latched") + +func recordArgs(agentID string, position int, rec *treatment.ApplicationRecord) []any { + args := assignmentArgs(agentID, position, &rec.Assignment) + return append(args, int64(rec.DesiredRevision), rec.Status.String(), nullTime(rec.ReceivedAt), nullTime(rec.AttemptedAt), nullTime(rec.AppliedAt), nullTime(rec.VerifiedAt), nullTime(rec.RemovedAt), rec.Error, rec.ExpiredLatched) +} + +type recordRow struct { + assignmentRow + desiredRevision int64 + status, errText string + received, attempted, applied, verified, removed sql.NullTime + expiredLatched bool +} + +func (r *recordRow) targets() []any { + return append(r.assignmentRow.targets(), &r.desiredRevision, &r.status, &r.received, &r.attempted, &r.applied, &r.verified, &r.removed, &r.errText, &r.expiredLatched) +} + +func (r *recordRow) value() (treatment.ApplicationRecord, error) { + a, err := r.assignmentRow.value() + if err != nil { + return treatment.ApplicationRecord{}, err + } + st, err := treatment.ParseApplyStatus(r.status) + if err != nil { + return treatment.ApplicationRecord{}, err + } + return treatment.ApplicationRecord{Assignment: a, DesiredRevision: uint64(r.desiredRevision), Status: st, ReceivedAt: fromNullTime(r.received), AttemptedAt: fromNullTime(r.attempted), AppliedAt: fromNullTime(r.applied), VerifiedAt: fromNullTime(r.verified), RemovedAt: fromNullTime(r.removed), Error: r.errText, ExpiredLatched: r.expiredLatched}, nil +} diff --git a/pkg/treatment/controller/store/postgres/evidence.go b/pkg/treatment/controller/store/postgres/evidence.go new file mode 100644 index 000000000..865c08b39 --- /dev/null +++ b/pkg/treatment/controller/store/postgres/evidence.go @@ -0,0 +1,98 @@ +package postgres + +import ( + "context" + "crypto/sha256" + "database/sql" + "encoding/hex" + "errors" + "fmt" + "math" + + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store" +) + +type evidence struct{ *runner } + +var hostColumns = []string{"evidence_id", "position", "agent_id", "sockets", "bytes_acked", "goodput_bps", "retransmission_ratio", "rtt_p95_ms", "exposed_sockets", "collection_healthy"} + +// digest is a content hash over the domain value so Put can detect reuse of +// an evidence ID for different content without reloading every host row. +// Timestamps hash at microsecond precision because that is what timestamptz +// keeps: evidence read back from the database must digest to the same value +// it was stored with. +func digest(e *treatment.Evidence) string { + h := sha256.New() + fmt.Fprintf(h, "%s\x00%d\x00%d\x00%d\x00%s\x00%s\x00%s\x00%d\x00", e.ID, e.AnalyzedAt.UnixMicro(), e.WindowStart.UnixMicro(), e.WindowEnd.UnixMicro(), e.QueryVersion, e.EnrichmentVersion, e.ArtifactSHA256, len(e.Hosts)) + for i := range e.Hosts { + v := &e.Hosts[i] + fmt.Fprintf(h, "%s\x00%d\x00%d\x00%d\x00%d\x00%d\x00%d\x00%t\x00", v.AgentID, v.Sockets, v.BytesAcked, math.Float64bits(v.GoodputBps), math.Float64bits(v.RetransmissionRatio), math.Float64bits(v.RttP95Ms), v.ExposedSockets, v.CollectionHealthy) + } + return hex.EncodeToString(h.Sum(nil)) +} + +func (v *evidence) Get(ctx context.Context, id string) (*treatment.Evidence, error) { + return getEvidence(ctx, v.q(), id) +} + +func getEvidence(ctx context.Context, q querier, id string) (*treatment.Evidence, error) { + e := &treatment.Evidence{ID: id} + err := q.QueryRowContext(ctx, "SELECT analyzed_at,window_start,window_end,query_version,enrichment_version,artifact_sha256 FROM xtcp_treatment_evidence WHERE id=$1", id).Scan(&e.AnalyzedAt, &e.WindowStart, &e.WindowEnd, &e.QueryVersion, &e.EnrichmentVersion, &e.ArtifactSHA256) + if err != nil { + return nil, notFound(err) + } + rows, err := q.QueryContext(ctx, "SELECT agent_id,sockets,bytes_acked,goodput_bps,retransmission_ratio,rtt_p95_ms,exposed_sockets,collection_healthy FROM xtcp_treatment_evidence_hosts WHERE evidence_id=$1 ORDER BY position", id) + if err != nil { + return nil, err + } + for rows.Next() { + var h treatment.HostEvidence + var sockets, bytes, exposed int64 + if err = rows.Scan(&h.AgentID, &sockets, &bytes, &h.GoodputBps, &h.RetransmissionRatio, &h.RttP95Ms, &exposed, &h.CollectionHealthy); err != nil { + rows.Close() + return nil, err + } + h.Sockets, h.BytesAcked, h.ExposedSockets = uint64(sockets), uint64(bytes), uint64(exposed) + e.Hosts = append(e.Hosts, h) + } + if err = errors.Join(rows.Err(), rows.Close()); err != nil { + return nil, err + } + return e, nil +} + +func (v *evidence) Put(ctx context.Context, experimentID string, e *treatment.Evidence) error { + return v.atomic(ctx, func(q querier) error { return putEvidence(ctx, q, experimentID, e) }) +} + +func putEvidence(ctx context.Context, q querier, experimentID string, e *treatment.Evidence) error { + if e == nil { + return errors.New("nil evidence") + } + want := digest(e) + var owner, have string + err := q.QueryRowContext(ctx, "SELECT experiment_id,digest FROM xtcp_treatment_evidence WHERE id=$1", e.ID).Scan(&owner, &have) + if err == nil { + if owner != experimentID || have != want { + return store.ErrEvidenceConflict + } + return nil + } + if !errors.Is(err, sql.ErrNoRows) { + return err + } + if _, err = q.ExecContext(ctx, `INSERT INTO xtcp_treatment_evidence(id,experiment_id,analyzed_at,window_start,window_end,query_version,enrichment_version,artifact_sha256,digest) + VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9)`, e.ID, experimentID, e.AnalyzedAt, e.WindowStart, e.WindowEnd, e.QueryVersion, e.EnrichmentVersion, e.ArtifactSHA256, want); err != nil { + if isUniqueViolation(err) { + return store.ErrEvidenceConflict + } + return err + } + rows := make([][]any, len(e.Hosts)) + for i := range e.Hosts { + h := &e.Hosts[i] + rows[i] = []any{e.ID, i, h.AgentID, int64(h.Sockets), int64(h.BytesAcked), h.GoodputBps, h.RetransmissionRatio, h.RttP95Ms, int64(h.ExposedSockets), h.CollectionHealthy} + } + return batchInsert(ctx, q, "xtcp_treatment_evidence_hosts", hostColumns, rows) +} diff --git a/pkg/treatment/controller/store/postgres/experiments.go b/pkg/treatment/controller/store/postgres/experiments.go new file mode 100644 index 000000000..f62c04dfc --- /dev/null +++ b/pkg/treatment/controller/store/postgres/experiments.go @@ -0,0 +1,300 @@ +package postgres + +import ( + "context" + "database/sql" + "errors" + "net/netip" + "strings" + "time" + + "github.com/randomizedcoder/xtcp2/pkg/treatment" +) + +type experiments struct{ *runner } + +var experimentColumns = append(append([]string{"id", "location", "hypothesis"}, prefixed("treatment_", treatmentColumns)...), + "minimum_observation_ns", "maximum_phase_ns", "maximum_evidence_age_ns", "minimum_sockets_per_host", "minimum_bytes_per_host", "minimum_treated_hosts", "minimum_improvement", "maximum_retransmission_increase", "maximum_rtt_increase", + "baseline_evidence_id", "status", "revision", "proposed_at", "latest_evidence_id", "latest_outcome", "updated_at") + +func evidenceID(e *treatment.Evidence) any { + if e == nil { + return nil + } + return e.ID +} + +func experimentArgs(e *treatment.Experiment) []any { + p := e.Policy + args := []any{e.ID, e.Location, e.Hypothesis} + args = append(args, treatmentArgs(&e.Treatment)...) + return append(args, int64(p.MinimumObservation), int64(p.MaximumPhase), int64(p.MaximumEvidenceAge), int64(p.MinimumSocketsPerHost), int64(p.MinimumBytesPerHost), int64(p.MinimumTreatedHosts), p.MinimumImprovement, p.MaximumRetransmissionIncrease, p.MaximumRTTIncrease, + evidenceID(e.Baseline), e.Status.String(), int64(e.Revision), nullTime(e.ProposedAt), evidenceID(e.LatestEvidence), e.LatestOutcome.String(), e.UpdatedAt) +} + +// upsertExperimentSQL updates every column but id on conflict. updated_at +// comes from the controller's clock so the discovery cooldown is testable. +var upsertExperimentSQL = func() string { + sets := make([]string, 0, len(experimentColumns)) + for _, c := range experimentColumns[1:] { + sets = append(sets, c+"=EXCLUDED."+c) + } + return insertSQL("xtcp_treatment_experiments", experimentColumns) + " ON CONFLICT(id) DO UPDATE SET " + strings.Join(sets, ",") +}() + +func (x *experiments) Exists(ctx context.Context, id string) (bool, error) { + var exists bool + err := x.q().QueryRowContext(ctx, "SELECT EXISTS(SELECT 1 FROM xtcp_treatment_experiments WHERE id=$1)", id).Scan(&exists) + return exists, err +} + +func (x *experiments) Get(ctx context.Context, id string) (*treatment.Experiment, error) { + return x.get(ctx, x.q(), id) +} + +func (x *experiments) get(ctx context.Context, q querier, id string) (*treatment.Experiment, error) { + suffix := "" + if x.locked() { + suffix = " FOR UPDATE" + } + e := &treatment.Experiment{ID: id} + var tr treatmentRow + var minObs, maxPhase, maxAge, minSockets, minBytes, minTreated, revision int64 + var baseline, latest sql.NullString + var status, outcome string + var proposed sql.NullTime + var updated time.Time + targets := []any{&e.Location, &e.Hypothesis} + targets = append(targets, tr.targets()...) + targets = append(targets, &minObs, &maxPhase, &maxAge, &minSockets, &minBytes, &minTreated, &e.Policy.MinimumImprovement, &e.Policy.MaximumRetransmissionIncrease, &e.Policy.MaximumRTTIncrease, &baseline, &status, &revision, &proposed, &latest, &outcome, &updated) + err := q.QueryRowContext(ctx, "SELECT "+selectList(experimentColumns[1:])+" FROM xtcp_treatment_experiments WHERE id=$1"+suffix, id).Scan(targets...) + if err != nil { + return nil, notFound(err) + } + t, err := tr.value() + if err != nil { + return nil, err + } + e.Treatment = *t + e.Policy.MinimumObservation, e.Policy.MaximumPhase, e.Policy.MaximumEvidenceAge = time.Duration(minObs), time.Duration(maxPhase), time.Duration(maxAge) + e.Policy.MinimumSocketsPerHost, e.Policy.MinimumBytesPerHost, e.Policy.MinimumTreatedHosts = uint64(minSockets), uint64(minBytes), uint32(minTreated) + if e.Status, err = treatment.ParseStatus(status); err != nil { + return nil, err + } + if e.LatestOutcome, err = treatment.ParseOutcome(outcome); err != nil { + return nil, err + } + e.Revision, e.ProposedAt, e.UpdatedAt = uint64(revision), fromNullTime(proposed), updated + loaded := map[string]*treatment.Evidence{} + load := func(id sql.NullString) (*treatment.Evidence, error) { + if !id.Valid { + return nil, nil + } + if v, ok := loaded[id.String]; ok { + return v.Clone(), nil + } + v, err := getEvidence(ctx, q, id.String) + if err != nil { + return nil, err + } + loaded[id.String] = v + return v.Clone(), nil + } + if e.Baseline, err = load(baseline); err != nil { + return nil, err + } + if e.LatestEvidence, err = load(latest); err != nil { + return nil, err + } + rows, err := q.QueryContext(ctx, "SELECT agent_id FROM xtcp_treatment_cohort WHERE experiment_id=$1 ORDER BY position", id) + if err != nil { + return nil, err + } + for rows.Next() { + var a string + if err = rows.Scan(&a); err != nil { + rows.Close() + return nil, err + } + e.CohortOrder = append(e.CohortOrder, a) + } + if err = errors.Join(rows.Err(), rows.Close()); err != nil { + return nil, err + } + rows, err = q.QueryContext(ctx, "SELECT target_hosts,issued_at,observation_started_at,evaluated_at,evidence_id,outcome,reviewer,reason FROM xtcp_treatment_phases WHERE experiment_id=$1 ORDER BY position", id) + if err != nil { + return nil, err + } + type phaseRow struct { + p treatment.Phase + ev sql.NullString + } + var phases []phaseRow + for rows.Next() { + var r phaseRow + var target int64 + var issued, started, evaluated sql.NullTime + var out string + if err = rows.Scan(&target, &issued, &started, &evaluated, &r.ev, &out, &r.p.Reviewer, &r.p.Reason); err != nil { + rows.Close() + return nil, err + } + r.p.TargetHosts = uint32(target) + r.p.IssuedAt, r.p.ObservationStartedAt, r.p.EvaluatedAt = fromNullTime(issued), fromNullTime(started), fromNullTime(evaluated) + if r.p.Outcome, err = treatment.ParseOutcome(out); err != nil { + rows.Close() + return nil, err + } + phases = append(phases, r) + } + if err = errors.Join(rows.Err(), rows.Close()); err != nil { + return nil, err + } + for _, r := range phases { + if r.p.Evidence, err = load(r.ev); err != nil { + return nil, err + } + e.Phases = append(e.Phases, r.p) + } + return e, nil +} + +// Save stores the evidence the experiment carries, upserts the row, writes +// the cohort on first insert (it is immutable afterwards) and upserts phases. +func (x *experiments) Save(ctx context.Context, e *treatment.Experiment) error { + return x.atomic(ctx, func(q querier) error { + for _, ev := range []*treatment.Evidence{e.Baseline, e.LatestEvidence} { + if ev != nil { + if err := putEvidence(ctx, q, e.ID, ev); err != nil { + return err + } + } + } + for i := range e.Phases { + if ev := e.Phases[i].Evidence; ev != nil { + if err := putEvidence(ctx, q, e.ID, ev); err != nil { + return err + } + } + } + var exists bool + if err := q.QueryRowContext(ctx, "SELECT EXISTS(SELECT 1 FROM xtcp_treatment_experiments WHERE id=$1)", e.ID).Scan(&exists); err != nil { + return err + } + if _, err := q.ExecContext(ctx, upsertExperimentSQL, experimentArgs(e)...); err != nil { + return err + } + if !exists { + rows := make([][]any, len(e.CohortOrder)) + for i, a := range e.CohortOrder { + rows[i] = []any{e.ID, i, a} + } + if err := batchInsert(ctx, q, "xtcp_treatment_cohort", []string{"experiment_id", "position", "agent_id"}, rows); err != nil { + return err + } + } + for i := range e.Phases { + p := &e.Phases[i] + if _, err := q.ExecContext(ctx, `INSERT INTO xtcp_treatment_phases(experiment_id,position,target_hosts,issued_at,observation_started_at,evaluated_at,evidence_id,outcome,reviewer,reason) + VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10) ON CONFLICT(experiment_id,position) DO UPDATE SET target_hosts=EXCLUDED.target_hosts,issued_at=EXCLUDED.issued_at,observation_started_at=EXCLUDED.observation_started_at,evaluated_at=EXCLUDED.evaluated_at,evidence_id=EXCLUDED.evidence_id,outcome=EXCLUDED.outcome,reviewer=EXCLUDED.reviewer,reason=EXCLUDED.reason`, + e.ID, i, int64(p.TargetHosts), nullTime(p.IssuedAt), nullTime(p.ObservationStartedAt), nullTime(p.EvaluatedAt), evidenceID(p.Evidence), p.Outcome.String(), p.Reviewer, p.Reason); err != nil { + return err + } + } + _, err := q.ExecContext(ctx, "DELETE FROM xtcp_treatment_phases WHERE experiment_id=$1 AND position>=$2", e.ID, len(e.Phases)) + return err + }) +} + +func (x *experiments) List(ctx context.Context, id string, limit int) ([]*treatment.Experiment, error) { + q := x.q() + rows, err := q.QueryContext(ctx, "SELECT id FROM xtcp_treatment_experiments WHERE ($1='' OR id=$1) ORDER BY id LIMIT $2", id, limit) + if err != nil { + return nil, err + } + var ids []string + for rows.Next() { + var v string + if err = rows.Scan(&v); err != nil { + rows.Close() + return nil, err + } + ids = append(ids, v) + } + if err = errors.Join(rows.Err(), rows.Close()); err != nil { + return nil, err + } + out := make([]*treatment.Experiment, 0, len(ids)) + for _, v := range ids { + e, err := x.get(ctx, q, v) + if err != nil { + return nil, err + } + out = append(out, e) + } + return out, nil +} + +func statusList(statuses []treatment.Status) (string, []any) { + args := make([]any, len(statuses)) + for i, s := range statuses { + args[i] = s.String() + } + return placeholders(1, len(statuses)), args +} + +func (x *experiments) IDs(ctx context.Context, statuses ...treatment.Status) ([]string, error) { + if len(statuses) == 0 { + return nil, nil + } + in, args := statusList(statuses) + rows, err := x.q().QueryContext(ctx, "SELECT id FROM xtcp_treatment_experiments WHERE status IN ("+in+") ORDER BY id", args...) + if err != nil { + return nil, err + } + var out []string + for rows.Next() { + var id string + if err = rows.Scan(&id); err != nil { + rows.Close() + return nil, err + } + out = append(out, id) + } + return out, errors.Join(rows.Err(), rows.Close()) +} + +func activeStatuses() []treatment.Status { + var out []treatment.Status + for _, s := range treatment.Statuses() { + if s.Active() { + out = append(out, s) + } + } + return out +} + +// hasActiveSQL and destinationBlockedSQL are the two hot discovery lookups; +// TestExperimentQueryIndexes pins the indexes that serve them. +var hasActiveSQL = func() string { + names := make([]string, 0, 4) + for _, s := range activeStatuses() { + names = append(names, "'"+s.String()+"'") + } + return "SELECT EXISTS(SELECT 1 FROM xtcp_treatment_experiments WHERE location=$1 AND status IN (" + strings.Join(names, ",") + "))" +}() + +const destinationBlockedSQL = `SELECT EXISTS(SELECT 1 FROM xtcp_treatment_experiments + WHERE location=$1 AND treatment_destination=$2::cidr AND (status NOT IN ('retired','rolled_back') OR updated_at > $3))` + +func (x *experiments) HasActive(ctx context.Context, location string) (bool, error) { + var exists bool + err := x.q().QueryRowContext(ctx, hasActiveSQL, location).Scan(&exists) + return exists, err +} + +func (x *experiments) DestinationBlocked(ctx context.Context, location string, destination netip.Prefix, since time.Time) (bool, error) { + var exists bool + err := x.q().QueryRowContext(ctx, destinationBlockedSQL, location, destination.String(), since).Scan(&exists) + return exists, err +} diff --git a/pkg/treatment/controller/store/postgres/export_test.go b/pkg/treatment/controller/store/postgres/export_test.go new file mode 100644 index 000000000..4864943e8 --- /dev/null +++ b/pkg/treatment/controller/store/postgres/export_test.go @@ -0,0 +1,7 @@ +package postgres + +// Query strings the index test pins to their indexes. +var ( + HasActiveSQL = hasActiveSQL + DestinationBlockedSQL = destinationBlockedSQL +) diff --git a/pkg/treatment/controller/store/postgres/holds.go b/pkg/treatment/controller/store/postgres/holds.go new file mode 100644 index 000000000..9cac6aa7d --- /dev/null +++ b/pkg/treatment/controller/store/postgres/holds.go @@ -0,0 +1,87 @@ +package postgres + +import ( + "context" + "database/sql" + "time" + + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store" +) + +type holds struct{ *runner } + +func (h *holds) Set(ctx context.Context, scope, reason string) error { + _, err := h.q().ExecContext(ctx, "INSERT INTO xtcp_treatment_holds(scope,reason) VALUES($1,$2) ON CONFLICT(scope) DO UPDATE SET reason=EXCLUDED.reason", scope, reason) + return err +} + +func (h *holds) Clear(ctx context.Context, scope string) error { + _, err := h.q().ExecContext(ctx, "DELETE FROM xtcp_treatment_holds WHERE scope=$1", scope) + return err +} + +func (h *holds) Held(ctx context.Context, scopes ...string) (bool, error) { + if len(scopes) == 0 { + return false, nil + } + args := make([]any, len(scopes)) + for i, s := range scopes { + args[i] = s + } + var held bool + err := h.q().QueryRowContext(ctx, "SELECT EXISTS(SELECT 1 FROM xtcp_treatment_holds WHERE scope IN ("+placeholders(1, len(scopes))+"))", args...).Scan(&held) + return held, err +} + +type audit struct{ *runner } + +func (a *audit) Append(ctx context.Context, actor, requestID, kind string, payload []byte) error { + if len(payload) == 0 { + payload = []byte("{}") + } + _, err := a.q().ExecContext(ctx, "INSERT INTO xtcp_treatment_events(actor,request_id,kind,payload) VALUES($1,NULLIF($2,''),$3,$4::jsonb)", actor, requestID, kind, string(payload)) + if isUniqueViolation(err) { + return store.ErrExists + } + return err +} + +func (a *audit) Lookup(ctx context.Context, requestID string) (*store.Event, error) { + e := &store.Event{} + var rid sql.NullString + err := a.q().QueryRowContext(ctx, "SELECT sequence,occurred_at,actor,request_id,kind,payload FROM xtcp_treatment_events WHERE request_id=$1", requestID).Scan(&e.Sequence, &e.OccurredAt, &e.Actor, &rid, &e.Kind, &e.Payload) + if err != nil { + return nil, notFound(err) + } + e.RequestID = rid.String + return e, nil +} + +func (a *audit) Count(ctx context.Context) (int64, error) { + var n int64 + err := a.q().QueryRowContext(ctx, "SELECT count(*) FROM xtcp_treatment_events").Scan(&n) + return n, err +} + +type metrics struct{ *runner } + +// Snapshot answers the status page in one round trip. +func (m *metrics) Snapshot(ctx context.Context, now time.Time) (map[string]float64, error) { + var agents, connected, suspended, assignments, verified, failed, expired, pending, running, complete, events float64 + err := m.q().QueryRowContext(ctx, `SELECT + (SELECT count(*) FROM xtcp_treatment_agents), + (SELECT count(*) FROM xtcp_treatment_agents WHERE last_seen > $1), + (SELECT count(*) FROM xtcp_treatment_agents WHERE desired_suspended OR locally_suspended), + (SELECT count(*) FROM xtcp_treatment_assignments), + (SELECT count(*) FROM xtcp_treatment_records WHERE status='verified'), + (SELECT count(*) FROM xtcp_treatment_records WHERE status='failed'), + (SELECT count(*) FROM xtcp_treatment_records WHERE status='expired'), + (SELECT count(*) FROM xtcp_treatment_experiments WHERE status='proposed'), + (SELECT count(*) FROM xtcp_treatment_experiments WHERE status='running'), + (SELECT count(*) FROM xtcp_treatment_experiments WHERE status='complete'), + (SELECT count(*) FROM xtcp_treatment_events)`, now.Add(-2*time.Minute)).Scan(&agents, &connected, &suspended, &assignments, &verified, &failed, &expired, &pending, &running, &complete, &events) + if err != nil { + return nil, err + } + return map[string]float64{"agents": agents, "agents_connected": connected, "agents_suspended": suspended, "assignments": assignments, "routes_verified": verified, "routes_failed": failed, "routes_expired": expired, "experiments_pending": pending, "experiments_running": running, "experiments_complete": complete, "audit_events": events}, nil +} diff --git a/pkg/treatment/controller/store/postgres/postgres.go b/pkg/treatment/controller/store/postgres/postgres.go new file mode 100644 index 000000000..750cf111e --- /dev/null +++ b/pkg/treatment/controller/store/postgres/postgres.go @@ -0,0 +1,242 @@ +// Package postgres implements the controller's store ports on PostgreSQL +// through database/sql with the pgx driver. Every SQL string in the +// controller lives in this package. +package postgres + +import ( + "context" + "database/sql" + _ "embed" + "errors" + "fmt" + "strings" + "time" + + "github.com/jackc/pgx/v5/pgconn" + _ "github.com/jackc/pgx/v5/stdlib" // registers the "pgx" database/sql driver + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store" +) + +//go:embed schema.sql +var schema string + +// mutationLock is the transaction-level advisory lock every Store.Tx takes. +// It fences controller replicas without electing a leader. +const mutationLock = 867530902 + +// Store is a connection pool. It satisfies store.Store. +type Store struct{ db *sql.DB } + +// Open connects, pings and returns the store. The DSN is anything pgx +// accepts (a URL or key=value pairs). +func Open(ctx context.Context, dsn string) (*Store, error) { + db, err := sql.Open("pgx", dsn) + if err != nil { + return nil, err + } + db.SetMaxOpenConns(32) + db.SetMaxIdleConns(8) + db.SetConnMaxLifetime(30 * time.Minute) + if err = db.PingContext(ctx); err != nil { + db.Close() + return nil, err + } + return &Store{db: db}, nil +} + +// New wraps an existing pool; tests use it with a per-test search_path. +func New(db *sql.DB) *Store { return &Store{db: db} } + +// Migrate creates every table and index that does not exist yet. +func (s *Store) Migrate(ctx context.Context) error { + _, err := s.db.ExecContext(ctx, schema) + return err +} + +func (s *Store) Ping(ctx context.Context) error { return s.db.PingContext(ctx) } +func (s *Store) Close() error { return s.db.Close() } + +// DB exposes the pool for tests that inspect query plans. +func (s *Store) DB() *sql.DB { return s.db } + +func (s *Store) Repos() store.Repos { return repos(&runner{db: s.db}) } + +// Tx runs fn in one transaction holding the mutation lock. Agent heartbeats +// go through Repos and never take it. +func (s *Store) Tx(ctx context.Context, fn func(store.Repos) error) error { + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer tx.Rollback() + if _, err = tx.ExecContext(ctx, "SELECT pg_advisory_xact_lock($1)", mutationLock); err != nil { + return err + } + if err = fn(repos(&runner{tx: tx})); err != nil { + return err + } + return tx.Commit() +} + +func repos(r *runner) store.Repos { + return store.Repos{Agents: &agents{r}, Experiments: &experiments{r}, Evidence: &evidence{r}, Holds: &holds{r}, Audit: &audit{r}, Metrics: &metrics{r}} +} + +// querier is what *sql.DB and *sql.Tx have in common. +type querier interface { + ExecContext(context.Context, string, ...any) (sql.Result, error) + QueryContext(context.Context, string, ...any) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...any) *sql.Row +} + +// runner binds a repository to either the pool or an open transaction. +type runner struct { + db *sql.DB + tx *sql.Tx +} + +func (r *runner) q() querier { + if r.tx != nil { + return r.tx + } + return r.db +} + +// locked reports whether the caller is inside Store.Tx, in which case reads +// that precede writes lock their rows. +func (r *runner) locked() bool { return r.tx != nil } + +// atomic runs fn in the open transaction, or in a short private one when the +// repository is bound to the pool, so multi-statement writes stay consistent +// without the mutation lock. +func (r *runner) atomic(ctx context.Context, fn func(querier) error) error { + if r.tx != nil { + return fn(r.tx) + } + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer tx.Rollback() + if err = fn(tx); err != nil { + return err + } + return tx.Commit() +} + +func isUniqueViolation(err error) bool { + var pg *pgconn.PgError + return errors.As(err, &pg) && pg.Code == "23505" +} + +func notFound(err error) error { + if errors.Is(err, sql.ErrNoRows) { + return store.ErrNotFound + } + return err +} + +// placeholders returns "$start,$start+1,...". +func placeholders(start, n int) string { + parts := make([]string, n) + for i := range n { + parts[i] = fmt.Sprintf("$%d", start+i) + } + return strings.Join(parts, ",") +} + +// insertSQL builds INSERT INTO table(columns) VALUES($1,...). Columns named +// *_destination are cast to cidr. +func insertSQL(table string, columns []string) string { + vals := make([]string, len(columns)) + for i, c := range columns { + vals[i] = fmt.Sprintf("$%d", i+1) + if strings.HasSuffix(c, "destination") { + vals[i] += "::cidr" + } + } + return "INSERT INTO " + table + "(" + strings.Join(columns, ",") + ") VALUES(" + strings.Join(vals, ",") + ")" +} + +// selectList renders columns for SELECT, reading cidr columns as text. +func selectList(columns []string) string { + out := make([]string, len(columns)) + for i, c := range columns { + out[i] = c + if strings.HasSuffix(c, "destination") { + out[i] = c + "::text" + } + } + return strings.Join(out, ",") +} + +// batchInsert inserts rows in chunks that stay under PostgreSQL's parameter +// limit. rows must all have len(columns) values. +func batchInsert(ctx context.Context, q querier, table string, columns []string, rows [][]any) error { + if len(rows) == 0 { + return nil + } + chunk := max(1, 60000/len(columns)) + for start := 0; start < len(rows); start += chunk { + end := min(start+chunk, len(rows)) + var sb strings.Builder + sb.WriteString("INSERT INTO " + table + "(" + strings.Join(columns, ",") + ") VALUES") + args := make([]any, 0, (end-start)*len(columns)) + for i, row := range rows[start:end] { + if i > 0 { + sb.WriteByte(',') + } + sb.WriteByte('(') + for j, c := range columns { + if j > 0 { + sb.WriteByte(',') + } + fmt.Fprintf(&sb, "$%d", len(args)+1) + if strings.HasSuffix(c, "destination") { + sb.WriteString("::cidr") + } + args = append(args, row[j]) + } + sb.WriteByte(')') + } + if _, err := q.ExecContext(ctx, sb.String(), args...); err != nil { + return err + } + } + return nil +} + +func nullTime(t time.Time) any { + if t.IsZero() { + return nil + } + return t +} + +func fromNullTime(t sql.NullTime) time.Time { + if !t.Valid { + return time.Time{} + } + return t.Time +} + +func nullString(p *string) any { + if p == nil { + return nil + } + return *p +} + +func nullUint32(p *uint32) any { + if p == nil { + return nil + } + return int64(*p) +} + +func nullBool(p *bool) any { + if p == nil { + return nil + } + return *p +} diff --git a/pkg/treatment/controller/store/postgres/postgres_test.go b/pkg/treatment/controller/store/postgres/postgres_test.go new file mode 100644 index 000000000..776e7290f --- /dev/null +++ b/pkg/treatment/controller/store/postgres/postgres_test.go @@ -0,0 +1,131 @@ +package postgres_test + +import ( + "context" + "database/sql" + "encoding/json" + "strings" + "testing" + "time" + + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store/pgtest" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store/postgres" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store/storetest" +) + +func TestPostgresConformance(t *testing.T) { + storetest.Run(t, func(t *testing.T) store.Store { return pgtest.Open(t) }) +} + +func TestPostgresMigrateIdempotent(t *testing.T) { + s := pgtest.Open(t) + ctx := context.Background() + for i := range 2 { + if err := s.Migrate(ctx); err != nil { + t.Fatalf("migration %d: %v", i+2, err) + } + } + var indexes int + if err := s.DB().QueryRow("SELECT count(*) FROM pg_indexes WHERE schemaname=current_schema() AND indexname LIKE 'xtcp_treatment_%'").Scan(&indexes); err != nil { + t.Fatal(err) + } + if indexes < 6 { + t.Fatalf("expected the six named indexes to exist, found %d", indexes) + } +} + +func TestPostgresClosedStore(t *testing.T) { + s := pgtest.Open(t) + if err := s.Close(); err != nil { + t.Fatal(err) + } + ctx := context.Background() + if err := s.Ping(ctx); err == nil { + t.Fatal("expected Ping to fail on a closed store") + } + if _, err := s.Repos().Experiments.HasActive(ctx, "dc1"); err == nil { + t.Fatal("expected repository calls to fail on a closed store") + } + if err := s.Tx(ctx, func(store.Repos) error { return nil }); err == nil { + t.Fatal("expected Tx to fail on a closed store") + } +} + +type rowQuerier interface { + QueryRowContext(context.Context, string, ...any) *sql.Row +} + +// TestPostgresExperimentQueryIndexes seeds a 100k-row history and checks the +// planner picks the discovery indexes without forced settings. +func TestPostgresExperimentQueryIndexes(t *testing.T) { + s := pgtest.Open(t) + db := s.DB() + const seed = `INSERT INTO xtcp_treatment_experiments(id,location,hypothesis,treatment_id,treatment_revision,treatment_destination,treatment_congestion_lock,treatment_ecn_fabric_verified, + minimum_observation_ns,maximum_phase_ns,maximum_evidence_age_ns,minimum_sockets_per_host,minimum_bytes_per_host,minimum_treated_hosts, + minimum_improvement,maximum_retransmission_increase,maximum_rtt_increase,status,revision,proposed_at,latest_outcome,updated_at) + SELECT 'history-'||i,'dc1','h','cubic',1,('10.'||(i/65536)||'.'||((i/256)%256)||'.'||(i%256)||'/32')::cidr,false,false, + 1,1,1,1,1,1,0,0,0,'retired',1,now(),'unspecified',now()-interval '48 hours' FROM generate_series(1,100000) i; + INSERT INTO xtcp_treatment_experiments(id,location,hypothesis,treatment_id,treatment_revision,treatment_destination,treatment_congestion_lock,treatment_ecn_fabric_verified, + minimum_observation_ns,maximum_phase_ns,maximum_evidence_age_ns,minimum_sockets_per_host,minimum_bytes_per_host,minimum_treated_hosts, + minimum_improvement,maximum_retransmission_increase,maximum_rtt_increase,status,revision,proposed_at,latest_outcome,updated_at) + SELECT 'active-'||i,'active-dc-'||i,'h','cubic',1,'192.0.2.1/32'::cidr,false,false, + 1,1,1,1,1,1,0,0,0,'running',1,now(),'unspecified',now() FROM generate_series(1,5) i; + ANALYZE xtcp_treatment_experiments;` + if _, err := db.Exec(seed); err != nil { + t.Fatal(err) + } + const destinationIndex, statusIndex = "xtcp_treatment_experiments_destination", "xtcp_treatment_experiments_location_status" + since := time.Now().Add(-24 * time.Hour) + for _, tc := range []struct { + name, description, query, index string + args []any + }{ + {"old-destination", "positive: history lookup is selective by location and destination", postgres.DestinationBlockedSQL, destinationIndex, []any{"dc1", "10.0.0.1/32", since}}, + {"absent-destination", "negative: a missing destination does not scan historical experiments", postgres.DestinationBlockedSQL, destinationIndex, []any{"dc1", "203.0.113.1/32", since}}, + {"active-budget", "positive: the budget lookup uses location and status", postgres.HasActiveSQL, statusIndex, []any{"active-dc-1"}}, + {"idle-budget", "boundary: an idle location does not scan retired history", postgres.HasActiveSQL, statusIndex, []any{"dc1"}}, + } { + t.Run(tc.name, func(t *testing.T) { + explain := func(q rowQuerier) ([]byte, float64) { + t.Helper() + var raw []byte + if err := q.QueryRowContext(context.Background(), "EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) "+tc.query, tc.args...).Scan(&raw); err != nil { + t.Fatal(err) + } + var plan []struct { + Milliseconds float64 `json:"Execution Time"` + } + if err := json.Unmarshal(raw, &plan); err != nil { + t.Fatal(err) + } + return raw, plan[0].Milliseconds + } + indexed, after := explain(db) + used := tc.index + // For a location holding one row the destination index's location + // prefix is also an efficient budget lookup; either plan is valid. + if tc.query == postgres.HasActiveSQL && strings.Contains(string(indexed), `"Index Name": "`+destinationIndex+`"`) { + used = destinationIndex + } + if !strings.Contains(string(indexed), `"Index Name": "`+used+`"`) { + t.Fatalf("%s: expected the planner to choose %s: %s", tc.description, tc.index, indexed) + } + // Transactional DDL gives a real no-index comparison; rollback restores + // the schema. Timings are evidence, never a speed assertion. + tx, err := db.BeginTx(context.Background(), &sql.TxOptions{}) + if err != nil { + t.Fatal(err) + } + defer tx.Rollback() + if _, err = tx.Exec("DROP INDEX " + destinationIndex + "; DROP INDEX " + statusIndex); err != nil { + t.Fatal(err) + } + _, before := explain(tx) + if err = tx.Rollback(); err != nil { + t.Fatal(err) + } + t.Logf("100005 experiments: unindexed %.3f ms, indexed %.3f ms (%s)", before, after, used) + }) + } +} diff --git a/pkg/treatment/controller/store/postgres/schema.sql b/pkg/treatment/controller/store/postgres/schema.sql new file mode 100644 index 000000000..f5622f98e --- /dev/null +++ b/pkg/treatment/controller/store/postgres/schema.sql @@ -0,0 +1,207 @@ +-- Relational schema for the treatment controller. Every table is a storage +-- format for the domain types in pkg/treatment; nothing here is a protobuf +-- document. Enumerations are stored as their snake_case domain names so the +-- rows read in psql and so a renamed wire enum cannot break a query. + +CREATE TABLE IF NOT EXISTS xtcp_treatment_agents ( + id text PRIMARY KEY, + location text NOT NULL, + enrolled_at timestamptz NOT NULL DEFAULT now(), + last_seen timestamptz, + boot_id text NOT NULL DEFAULT '', + kernel_release text NOT NULL DEFAULT '', + simple_host_routing boolean NOT NULL DEFAULT false, + actuator_enabled boolean NOT NULL DEFAULT false, + locally_suspended boolean NOT NULL DEFAULT false, + reported_revision bigint NOT NULL DEFAULT 0, + desired_revision bigint NOT NULL, + desired_suspended boolean NOT NULL DEFAULT false, + desired_issued_at timestamptz NOT NULL, + desired_updated_at timestamptz NOT NULL DEFAULT now() +); +CREATE INDEX IF NOT EXISTS xtcp_treatment_agents_location ON xtcp_treatment_agents(location); +CREATE INDEX IF NOT EXISTS xtcp_treatment_agents_desired_updated ON xtcp_treatment_agents(desired_updated_at); + +CREATE TABLE IF NOT EXISTS xtcp_treatment_agent_algorithms ( + agent_id text NOT NULL REFERENCES xtcp_treatment_agents(id) ON DELETE CASCADE, + position integer NOT NULL, + name text NOT NULL, + PRIMARY KEY (agent_id, position) +); + +-- Assignments are the controller's desired state per agent; records are the +-- agent's report of what it did with them. Both carry the treatment inline +-- because a record may describe an assignment that no longer exists. +CREATE TABLE IF NOT EXISTS xtcp_treatment_assignments ( + agent_id text NOT NULL REFERENCES xtcp_treatment_agents(id) ON DELETE CASCADE, + experiment_id text NOT NULL, + position integer NOT NULL, + mode text NOT NULL, + generation bigint NOT NULL, + lease_expires_at timestamptz, + treatment_id text NOT NULL, + treatment_revision bigint NOT NULL, + treatment_destination cidr NOT NULL, + treatment_congestion_control text, + treatment_congestion_lock boolean NOT NULL, + treatment_rto_min_ms integer, + treatment_initial_congestion_window integer, + treatment_initial_receive_window integer, + treatment_quick_ack boolean, + treatment_ecn boolean, + treatment_ecn_fabric_verified boolean NOT NULL, + predecessor_id text, + predecessor_revision bigint, + predecessor_destination cidr, + predecessor_congestion_control text, + predecessor_congestion_lock boolean, + predecessor_rto_min_ms integer, + predecessor_initial_congestion_window integer, + predecessor_initial_receive_window integer, + predecessor_quick_ack boolean, + predecessor_ecn boolean, + predecessor_ecn_fabric_verified boolean, + PRIMARY KEY (agent_id, experiment_id) +); +CREATE INDEX IF NOT EXISTS xtcp_treatment_assignments_experiment ON xtcp_treatment_assignments(experiment_id); + +CREATE TABLE IF NOT EXISTS xtcp_treatment_records ( + agent_id text NOT NULL REFERENCES xtcp_treatment_agents(id) ON DELETE CASCADE, + experiment_id text NOT NULL, + position integer NOT NULL, + mode text NOT NULL, + generation bigint NOT NULL, + lease_expires_at timestamptz, + treatment_id text NOT NULL, + treatment_revision bigint NOT NULL, + treatment_destination cidr NOT NULL, + treatment_congestion_control text, + treatment_congestion_lock boolean NOT NULL, + treatment_rto_min_ms integer, + treatment_initial_congestion_window integer, + treatment_initial_receive_window integer, + treatment_quick_ack boolean, + treatment_ecn boolean, + treatment_ecn_fabric_verified boolean NOT NULL, + predecessor_id text, + predecessor_revision bigint, + predecessor_destination cidr, + predecessor_congestion_control text, + predecessor_congestion_lock boolean, + predecessor_rto_min_ms integer, + predecessor_initial_congestion_window integer, + predecessor_initial_receive_window integer, + predecessor_quick_ack boolean, + predecessor_ecn boolean, + predecessor_ecn_fabric_verified boolean, + desired_revision bigint NOT NULL, + status text NOT NULL, + received_at timestamptz, + attempted_at timestamptz, + applied_at timestamptz, + verified_at timestamptz, + removed_at timestamptz, + error text NOT NULL DEFAULT '', + expired_latched boolean NOT NULL DEFAULT false, + PRIMARY KEY (agent_id, experiment_id) +); + +CREATE TABLE IF NOT EXISTS xtcp_treatment_evidence ( + id text PRIMARY KEY, + experiment_id text NOT NULL, + analyzed_at timestamptz NOT NULL, + window_start timestamptz NOT NULL, + window_end timestamptz NOT NULL, + query_version text NOT NULL, + enrichment_version text NOT NULL DEFAULT '', + artifact_sha256 text NOT NULL, + digest text NOT NULL, + received_at timestamptz NOT NULL DEFAULT now() +); +CREATE INDEX IF NOT EXISTS xtcp_treatment_evidence_experiment ON xtcp_treatment_evidence(experiment_id); + +CREATE TABLE IF NOT EXISTS xtcp_treatment_evidence_hosts ( + evidence_id text NOT NULL REFERENCES xtcp_treatment_evidence(id) ON DELETE CASCADE, + position integer NOT NULL, + agent_id text NOT NULL, + sockets bigint NOT NULL, + bytes_acked bigint NOT NULL, + goodput_bps double precision NOT NULL, + retransmission_ratio double precision NOT NULL, + rtt_p95_ms double precision NOT NULL, + exposed_sockets bigint NOT NULL, + collection_healthy boolean NOT NULL, + PRIMARY KEY (evidence_id, position) +); + +CREATE TABLE IF NOT EXISTS xtcp_treatment_experiments ( + id text PRIMARY KEY, + location text NOT NULL, + hypothesis text NOT NULL, + treatment_id text NOT NULL, + treatment_revision bigint NOT NULL, + treatment_destination cidr NOT NULL, + treatment_congestion_control text, + treatment_congestion_lock boolean NOT NULL, + treatment_rto_min_ms integer, + treatment_initial_congestion_window integer, + treatment_initial_receive_window integer, + treatment_quick_ack boolean, + treatment_ecn boolean, + treatment_ecn_fabric_verified boolean NOT NULL, + minimum_observation_ns bigint NOT NULL, + maximum_phase_ns bigint NOT NULL, + maximum_evidence_age_ns bigint NOT NULL, + minimum_sockets_per_host bigint NOT NULL, + minimum_bytes_per_host bigint NOT NULL, + minimum_treated_hosts integer NOT NULL, + minimum_improvement double precision NOT NULL, + maximum_retransmission_increase double precision NOT NULL, + maximum_rtt_increase double precision NOT NULL, + baseline_evidence_id text REFERENCES xtcp_treatment_evidence(id), + status text NOT NULL, + revision bigint NOT NULL, + proposed_at timestamptz, + latest_evidence_id text REFERENCES xtcp_treatment_evidence(id), + latest_outcome text NOT NULL, + updated_at timestamptz NOT NULL +); +CREATE INDEX IF NOT EXISTS xtcp_treatment_experiments_location_status ON xtcp_treatment_experiments(location, status); +CREATE INDEX IF NOT EXISTS xtcp_treatment_experiments_destination ON xtcp_treatment_experiments(location, treatment_destination, updated_at); + +CREATE TABLE IF NOT EXISTS xtcp_treatment_cohort ( + experiment_id text NOT NULL REFERENCES xtcp_treatment_experiments(id) ON DELETE CASCADE, + position integer NOT NULL, + agent_id text NOT NULL, + PRIMARY KEY (experiment_id, position) +); + +CREATE TABLE IF NOT EXISTS xtcp_treatment_phases ( + experiment_id text NOT NULL REFERENCES xtcp_treatment_experiments(id) ON DELETE CASCADE, + position integer NOT NULL, + target_hosts integer NOT NULL, + issued_at timestamptz, + observation_started_at timestamptz, + evaluated_at timestamptz, + evidence_id text REFERENCES xtcp_treatment_evidence(id), + outcome text NOT NULL, + reviewer text NOT NULL DEFAULT '', + reason text NOT NULL DEFAULT '', + PRIMARY KEY (experiment_id, position) +); + +CREATE TABLE IF NOT EXISTS xtcp_treatment_holds ( + scope text PRIMARY KEY, + reason text NOT NULL +); + +-- The audit log is append-only and never queried by payload field, so the +-- payload stays a JSON document (protojson of the wire request or response). +CREATE TABLE IF NOT EXISTS xtcp_treatment_events ( + sequence bigserial PRIMARY KEY, + occurred_at timestamptz NOT NULL DEFAULT now(), + actor text NOT NULL, + request_id text UNIQUE, + kind text NOT NULL, + payload jsonb NOT NULL +); diff --git a/pkg/treatment/controller/store/store.go b/pkg/treatment/controller/store/store.go new file mode 100644 index 000000000..63a4f310b --- /dev/null +++ b/pkg/treatment/controller/store/store.go @@ -0,0 +1,168 @@ +// Package store defines the controller's persistence ports. The handlers in +// package controller work only through these interfaces; package postgres +// implements them with hand-written SQL and package memory implements them +// with maps for handler tests. +// +// Every method takes a context and returns the sentinel errors below (wrapped +// or bare) for the conditions a handler must distinguish. Anything else is an +// infrastructure failure. +package store + +import ( + "context" + "errors" + "net/netip" + "time" + + "github.com/randomizedcoder/xtcp2/pkg/treatment" +) + +var ( + // ErrNotFound: the row does not exist. + ErrNotFound = errors.New("not found") + // ErrNotEnrolled: the agent has no row; desired state cannot be stored. + ErrNotEnrolled = errors.New("agent not enrolled") + // ErrExists: an insert-only operation found the key already present. + ErrExists = errors.New("already exists") + // ErrEvidenceConflict: an evidence ID was reused for different content + // or another experiment. + ErrEvidenceConflict = errors.New("evidence IDs are immutable and experiment-specific") + // ErrRequestConflict: a request ID was reused for a different operation + // or a different body. + ErrRequestConflict = errors.New("request_id reused") +) + +// Agent is an enrolled host: its certificate-bound location, when it last +// reported and what it reported. Report is zero until the first check-in +// completes. +type Agent struct { + ID string + Location string + EnrolledAt time.Time + LastSeen time.Time + Report treatment.AgentReport +} + +// Seen reports whether the agent checked in within window of now. +func (a *Agent) Seen(now time.Time, window time.Duration) bool { + return !a.LastSeen.IsZero() && now.Sub(a.LastSeen) <= window +} + +// AgentRef is the identity pair used by fleet-wide controls. +type AgentRef struct{ ID, Location string } + +// Agents stores enrollment, the latest report and the desired state per host. +type Agents interface { + // Enroll inserts the agent with its initial desired state. It is a + // no-op when the agent already exists, so concurrent first check-ins + // are safe. Enrollment never changes an existing location. + Enroll(ctx context.Context, id, location string, initial *treatment.DesiredState) error + // Get returns the agent or ErrNotFound. + Get(ctx context.Context, id string) (*Agent, error) + // Observe replaces the agent's capabilities and records and stamps + // last_seen. It returns ErrNotEnrolled when no agent has that ID and + // location: a host cannot move location by reporting a new one. + Observe(ctx context.Context, report *treatment.AgentReport, seen time.Time) error + // Desired returns the agent's desired state or ErrNotEnrolled. + Desired(ctx context.Context, id string) (*treatment.DesiredState, error) + // SaveDesired replaces the desired state, including every assignment, + // or returns ErrNotEnrolled. + SaveDesired(ctx context.Context, d *treatment.DesiredState) error + // List returns agents in the location, or every agent when location is + // empty, ordered by ID. + List(ctx context.Context, location string) ([]AgentRef, error) + // DesiredRevisions returns every agent's current desired revision. + DesiredRevisions(ctx context.Context) (map[string]uint64, error) +} + +// Experiments stores experiments with their cohort, phases and evidence +// references. Evidence content lives once, in the Evidence repository; Save +// stores whatever evidence the experiment carries through it. +type Experiments interface { + // Get returns the experiment, with evidence loaded, or ErrNotFound. + // Inside Store.Tx the row is locked for update. + Get(ctx context.Context, id string) (*treatment.Experiment, error) + Exists(ctx context.Context, id string) (bool, error) + // Save inserts or replaces the experiment. CohortOrder is written on + // first insert and immutable afterwards; evidence the experiment + // carries is stored through the Evidence repository and conflicts + // surface as ErrEvidenceConflict. + Save(ctx context.Context, e *treatment.Experiment) error + // List returns the experiment with the ID, or up to limit experiments + // ordered by ID when id is empty. + List(ctx context.Context, id string, limit int) ([]*treatment.Experiment, error) + // IDs returns the IDs of experiments in any of the statuses. + IDs(ctx context.Context, statuses ...treatment.Status) ([]string, error) + // HasActive reports whether the location has an experiment that still + // consumes its budget (treatment.Status.Active). + HasActive(ctx context.Context, location string) (bool, error) + // DestinationBlocked reports whether the destination in the location + // has an experiment that is not terminal, or a terminal one updated + // after since. + DestinationBlocked(ctx context.Context, location string, destination netip.Prefix, since time.Time) (bool, error) +} + +// Evidence is the single copy of every evidence set. +type Evidence interface { + // Get returns the evidence or ErrNotFound. + Get(ctx context.Context, id string) (*treatment.Evidence, error) + // Put stores evidence for the experiment. Storing the same ID again is + // a no-op when the content and owner match and ErrEvidenceConflict + // otherwise. + Put(ctx context.Context, experimentID string, e *treatment.Evidence) error +} + +// Holds are operator suspensions by scope ("fleet", "location:", "agent:"). +type Holds interface { + Set(ctx context.Context, scope, reason string) error + Clear(ctx context.Context, scope string) error + // Held reports whether any of the scopes is held. + Held(ctx context.Context, scopes ...string) (bool, error) +} + +// Event is one audit row. +type Event struct { + Sequence int64 + OccurredAt time.Time + Actor string + RequestID string + Kind string + Payload []byte +} + +// Audit is the append-only event log. Payloads are opaque JSON documents. +type Audit interface { + // Append records an event. requestID may be empty; non-empty IDs are + // unique and a duplicate returns ErrExists. + Append(ctx context.Context, actor, requestID, kind string, payload []byte) error + // Lookup returns the event with the request ID or ErrNotFound. + Lookup(ctx context.Context, requestID string) (*Event, error) + // Count returns the number of events. + Count(ctx context.Context) (int64, error) +} + +// Metrics are the fleet gauges the status page serves. +type Metrics interface { + Snapshot(ctx context.Context, now time.Time) (map[string]float64, error) +} + +// Repos bundles the repositories bound to one connection or transaction. +type Repos struct { + Agents Agents + Experiments Experiments + Evidence Evidence + Holds Holds + Audit Audit + Metrics Metrics +} + +// Store is the root. Repos performs each call in its own implicit +// transaction. Tx runs fn inside one transaction that holds the controller's +// mutation lock, so every control-plane change is serialized across replicas; +// agent heartbeats never take it. +type Store interface { + Repos() Repos + Tx(ctx context.Context, fn func(Repos) error) error + Ping(ctx context.Context) error + Close() error +} diff --git a/pkg/treatment/controller/store/storetest/storetest.go b/pkg/treatment/controller/store/storetest/storetest.go new file mode 100644 index 000000000..a513df912 --- /dev/null +++ b/pkg/treatment/controller/store/storetest/storetest.go @@ -0,0 +1,767 @@ +// Package storetest is the conformance suite every store.Store implementation +// must pass. The memory and postgres packages both run it, so a handler +// tested against the memory store sees the same contract production sees. +package storetest + +import ( + "context" + "errors" + "fmt" + "net/netip" + "slices" + "strings" + "sync" + "testing" + "time" + + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store" + "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" + "github.com/randomizedcoder/xtcp2/pkg/treatment/wire" + "google.golang.org/protobuf/proto" +) + +// Opener returns a fresh, empty store for one test. +type Opener func(t *testing.T) store.Store + +// now is microsecond-aligned and in UTC, the precision timestamptz keeps. +var now = time.Unix(1700000000, 0).UTC() + +func ptr[T any](v T) *T { return &v } + +// Run executes the suite against stores from open. +func Run(t *testing.T, open Opener) { + t.Run("Agents", func(t *testing.T) { agents(t, open) }) + t.Run("Experiments", func(t *testing.T) { experiments(t, open) }) + t.Run("Eligibility", func(t *testing.T) { eligibility(t, open) }) + t.Run("Evidence", func(t *testing.T) { evidence(t, open) }) + t.Run("Holds", func(t *testing.T) { holds(t, open) }) + t.Run("Audit", func(t *testing.T) { audit(t, open) }) + t.Run("Tx", func(t *testing.T) { tx(t, open) }) + t.Run("Metrics", func(t *testing.T) { metrics(t, open) }) +} + +func report(id, location string) *treatment.AgentReport { + d := fixtures.Desired(now) + return &treatment.AgentReport{ + AgentID: id, + Location: location, + Capabilities: treatment.Capabilities{BootID: "boot-1", KernelRelease: "6.12.0", CongestionAlgorithms: []string{"cubic", "bbr", "dctcp"}, SimpleHostRouting: true, ActuatorEnabled: true}, + DesiredRevision: 3, + LocallySuspended: true, + Records: []treatment.ApplicationRecord{{ + Assignment: d.Assignments[0], DesiredRevision: 3, Status: treatment.ApplyVerified, + ReceivedAt: now, AttemptedAt: now.Add(time.Second), AppliedAt: now.Add(2 * time.Second), VerifiedAt: now.Add(3 * time.Second), + }, { + Assignment: treatment.Assignment{ExperimentID: "old", Generation: 2, Mode: treatment.ModeRetired, Treatment: fixtures.Treatment()}, + DesiredRevision: 2, Status: treatment.ApplyFailed, Error: "ENETUNREACH", ExpiredLatched: true, RemovedAt: now, + }}, + } +} + +func sameDesired(t *testing.T, description string, got, want *treatment.DesiredState) { + t.Helper() + if !proto.Equal(wire.Desired(got), wire.Desired(want)) { + t.Fatalf("%s:\n got %+v\nwant %+v", description, got, want) + } +} + +func sameReport(t *testing.T, description string, got, want *treatment.AgentReport) { + t.Helper() + if !proto.Equal(wire.Report(got), wire.Report(want)) { + t.Fatalf("%s:\n got %+v\nwant %+v", description, got, want) + } +} + +func sameExperiment(t *testing.T, description string, got, want *treatment.Experiment) { + t.Helper() + if !proto.Equal(wire.Experiment(got), wire.Experiment(want)) { + t.Fatalf("%s:\n got %+v\nwant %+v", description, got, want) + } +} + +func sameEvidence(t *testing.T, description string, got, want *treatment.Evidence) { + t.Helper() + if !proto.Equal(wire.Evidence(got), wire.Evidence(want)) { + t.Fatalf("%s:\n got %+v\nwant %+v", description, got, want) + } +} + +func agents(t *testing.T, open Opener) { + ctx := context.Background() + initial := &treatment.DesiredState{AgentID: "host-1", Revision: 1, Suspended: true, IssuedAt: now} + for _, tc := range []struct { + name, description string + run func(t *testing.T, a store.Agents) + }{ + {"enroll-roundtrip", "positive: enrollment stores location and the initial desired state", func(t *testing.T, a store.Agents) { + must(t, a.Enroll(ctx, "host-1", "dc1", initial)) + got, err := a.Get(ctx, "host-1") + must(t, err) + if got.ID != "host-1" || got.Location != "dc1" || got.EnrolledAt.IsZero() || !got.LastSeen.IsZero() || got.Report.AgentID != "host-1" || got.Report.Location != "dc1" { + t.Fatalf("expected an enrolled agent without a report, got %+v", got) + } + d, err := a.Desired(ctx, "host-1") + must(t, err) + sameDesired(t, "expected the initial desired state", d, initial) + }}, + {"observe-roundtrip", "positive: a report is stored with every capability and record column", func(t *testing.T, a store.Agents) { + must(t, a.Enroll(ctx, "host-1", "dc1", initial)) + r := report("host-1", "dc1") + must(t, a.Observe(ctx, r, now.Add(time.Minute))) + got, err := a.Get(ctx, "host-1") + must(t, err) + if !got.LastSeen.Equal(now.Add(time.Minute)) || !got.Seen(now.Add(2*time.Minute), 2*time.Minute) || got.Seen(now.Add(4*time.Minute), 2*time.Minute) { + t.Fatalf("expected last_seen %s, got %s", now.Add(time.Minute), got.LastSeen) + } + sameReport(t, "expected the observed report", &got.Report, r) + }}, + {"desired-roundtrip", "positive: every assignment column, predecessor and lease survives", func(t *testing.T, a store.Agents) { + must(t, a.Enroll(ctx, "host-1", "dc1", initial)) + d := fixtures.Desired(now) + d.Revision = 7 + pred := fixtures.Treatment() + pred.Revision = 2 + pred.Settings = treatment.TCPSettings{CongestionControl: ptr("bbr"), CongestionLock: true, RTOMinMs: ptr(uint32(5)), InitialCongestionWindow: ptr(uint32(100)), InitialReceiveWindow: ptr(uint32(0)), QuickAck: ptr(false), ECN: ptr(true)} + d.Assignments = append(d.Assignments, treatment.Assignment{ExperimentID: "stable-1", Generation: 4, Mode: treatment.ModeStable, Treatment: treatment.Treatment{ID: "v6", Revision: 3, Destination: netip.MustParsePrefix("2001:db8::1/128"), Settings: pred.Settings, ECNFabricVerified: true}, Predecessor: &pred}) + must(t, a.SaveDesired(ctx, d)) + got, err := a.Desired(ctx, "host-1") + must(t, err) + sameDesired(t, "expected the saved desired state", got, d) + revs, err := a.DesiredRevisions(ctx) + must(t, err) + if len(revs) != 1 || revs["host-1"] != 7 { + t.Fatalf("expected revision 7 for host-1, got %v", revs) + } + }}, + {"enroll-idempotent", "corner: a second enrollment never changes location or desired state", func(t *testing.T, a store.Agents) { + must(t, a.Enroll(ctx, "host-1", "dc1", initial)) + must(t, a.Enroll(ctx, "host-1", "dc2", &treatment.DesiredState{AgentID: "host-1", Revision: 9, IssuedAt: now})) + got, err := a.Get(ctx, "host-1") + must(t, err) + d, err := a.Desired(ctx, "host-1") + must(t, err) + if got.Location != "dc1" || d.Revision != 1 || !d.Suspended { + t.Fatalf("expected the first enrollment to win, got %s revision %d", got.Location, d.Revision) + } + }}, + {"observe-location-change", "negative: a host cannot move location by reporting a new one", func(t *testing.T, a store.Agents) { + must(t, a.Enroll(ctx, "host-1", "dc1", initial)) + if err := a.Observe(ctx, report("host-1", "dc2"), now); !errors.Is(err, store.ErrNotEnrolled) { + t.Fatalf("expected ErrNotEnrolled, got %v", err) + } + got, err := a.Get(ctx, "host-1") + must(t, err) + if !got.LastSeen.IsZero() || len(got.Report.Records) != 0 { + t.Fatal("expected the rejected report to leave no trace") + } + }}, + {"unknown-agent", "negative: lookups of an unenrolled agent return the documented sentinels", func(t *testing.T, a store.Agents) { + if _, err := a.Get(ctx, "ghost"); !errors.Is(err, store.ErrNotFound) { + t.Fatalf("expected ErrNotFound from Get, got %v", err) + } + if _, err := a.Desired(ctx, "ghost"); !errors.Is(err, store.ErrNotEnrolled) { + t.Fatalf("expected ErrNotEnrolled from Desired, got %v", err) + } + if err := a.SaveDesired(ctx, &treatment.DesiredState{AgentID: "ghost", Revision: 1, IssuedAt: now}); !errors.Is(err, store.ErrNotEnrolled) { + t.Fatalf("expected ErrNotEnrolled from SaveDesired, got %v", err) + } + if err := a.Observe(ctx, report("ghost", "dc1"), now); !errors.Is(err, store.ErrNotEnrolled) { + t.Fatalf("expected ErrNotEnrolled from Observe, got %v", err) + } + }}, + {"empty-desired", "boundary: a desired state with no assignments replaces a populated one", func(t *testing.T, a store.Agents) { + must(t, a.Enroll(ctx, "host-1", "dc1", fixtures.Desired(now))) + empty := &treatment.DesiredState{AgentID: "host-1", Revision: 2, IssuedAt: now.Add(time.Minute)} + must(t, a.SaveDesired(ctx, empty)) + got, err := a.Desired(ctx, "host-1") + must(t, err) + sameDesired(t, "expected no assignments", got, empty) + }}, + {"max-assignments", "boundary: MaxAssignments assignments are stored in order", func(t *testing.T, a store.Agents) { + must(t, a.Enroll(ctx, "host-1", "dc1", initial)) + d := &treatment.DesiredState{AgentID: "host-1", Revision: 2, IssuedAt: now} + for i := range treatment.MaxAssignments { + d.Assignments = append(d.Assignments, treatment.Assignment{ExperimentID: fmt.Sprintf("exp-%05d", i), Generation: uint64(i), Mode: treatment.ModeTrial, LeaseExpiresAt: now.Add(time.Hour), Treatment: fixtures.Treatment()}) + } + must(t, a.SaveDesired(ctx, d)) + got, err := a.Desired(ctx, "host-1") + must(t, err) + if len(got.Assignments) != treatment.MaxAssignments { + t.Fatalf("expected %d assignments, got %d", treatment.MaxAssignments, len(got.Assignments)) + } + for i := range got.Assignments { + if got.Assignments[i].ExperimentID != d.Assignments[i].ExperimentID { + t.Fatalf("expected position %d to hold %s, got %s", i, d.Assignments[i].ExperimentID, got.Assignments[i].ExperimentID) + } + } + }}, + {"list-by-location", "positive: List filters by location and orders by ID; empty location lists everyone", func(t *testing.T, a store.Agents) { + for _, h := range []struct{ id, loc string }{{"c", "dc2"}, {"a", "dc1"}, {"b", "dc1"}} { + must(t, a.Enroll(ctx, h.id, h.loc, &treatment.DesiredState{AgentID: h.id, Revision: 1, IssuedAt: now})) + } + dc1, err := a.List(ctx, "dc1") + must(t, err) + all, err := a.List(ctx, "") + must(t, err) + none, err := a.List(ctx, "dc3") + must(t, err) + want := []store.AgentRef{{ID: "a", Location: "dc1"}, {ID: "b", Location: "dc1"}} + if !slices.Equal(dc1, want) || len(all) != 3 || all[2].ID != "c" || len(none) != 0 { + t.Fatalf("expected %v / 3 / 0, got %v / %d / %d", want, dc1, len(all), len(none)) + } + }}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Log(tc.description) + tc.run(t, open(t).Repos().Agents) + }) + } +} + +func populated() *treatment.Experiment { + e, after := fixtures.Experiment(now, treatment.DefaultPolicy()) + e.ProposedAt = now.Add(-2 * time.Hour) + e.UpdatedAt = now + e.LatestEvidence = after + e.LatestOutcome = treatment.OutcomeBeneficial + e.Phases[0].EvaluatedAt = now + e.Phases[0].Evidence = after + e.Phases[0].Outcome = treatment.OutcomeBeneficial + e.Phases[0].Reviewer = "operator/alice" + e.Phases[0].Reason = "reviewed" + e.Phases = append(e.Phases, treatment.Phase{TargetHosts: 8, IssuedAt: now}) + return e +} + +func experiments(t *testing.T, open Opener) { + ctx := context.Background() + for _, tc := range []struct { + name, description string + run func(t *testing.T, rp store.Repos) + }{ + {"insert-relational", "positive: cohort, phases, baseline and evidence references reconstruct the experiment exactly", func(t *testing.T, rp store.Repos) { + e := populated() + must(t, rp.Experiments.Save(ctx, e)) + got, err := rp.Experiments.Get(ctx, e.ID) + must(t, err) + sameExperiment(t, "expected the saved experiment", got, e) + exists, err := rp.Experiments.Exists(ctx, e.ID) + must(t, err) + if !exists { + t.Fatal("expected Exists to be true") + } + baseline, err := rp.Evidence.Get(ctx, "baseline") + must(t, err) + sameEvidence(t, "expected the baseline stored once in the evidence repository", baseline, e.Baseline) + }}, + {"update-replaces", "positive: saving again replaces status, revision, phases and evidence references", func(t *testing.T, rp store.Repos) { + e := populated() + must(t, rp.Experiments.Save(ctx, e)) + n := e.Clone() + n.Status = treatment.StatusComplete + n.Revision = 3 + n.UpdatedAt = now.Add(time.Hour) + n.Phases = n.Phases[:1] + n.Phases[0].Outcome = treatment.OutcomeStableObserved + must(t, rp.Experiments.Save(ctx, n)) + got, err := rp.Experiments.Get(ctx, e.ID) + must(t, err) + sameExperiment(t, "expected the replacement with the trailing phase removed", got, n) + }}, + {"cohort-immutable", "corner: the cohort order written first is kept even if a later save differs", func(t *testing.T, rp store.Repos) { + e := populated() + must(t, rp.Experiments.Save(ctx, e)) + n := e.Clone() + n.CohortOrder = n.CohortOrder[:2] + must(t, rp.Experiments.Save(ctx, n)) + got, err := rp.Experiments.Get(ctx, e.ID) + must(t, err) + if len(got.CohortOrder) != 16 { + t.Fatalf("expected the 16-host cohort to survive, got %d", len(got.CohortOrder)) + } + }}, + {"no-evidence", "boundary: an experiment without baseline, latest evidence or phases round-trips", func(t *testing.T, rp store.Repos) { + e := &treatment.Experiment{ID: "bare", Location: "dc1", Hypothesis: "h", Treatment: fixtures.Treatment(), Policy: treatment.DefaultPolicy(), Status: treatment.StatusProposed, Revision: 1, ProposedAt: now, UpdatedAt: now} + must(t, rp.Experiments.Save(ctx, e)) + got, err := rp.Experiments.Get(ctx, "bare") + must(t, err) + sameExperiment(t, "expected the bare experiment", got, e) + }}, + {"max-cohort", "boundary: a cohort of MaxAgents positions is stored in order", func(t *testing.T, rp store.Repos) { + e := &treatment.Experiment{ID: "big", Location: "dc1", Hypothesis: "h", Treatment: fixtures.Treatment(), Policy: treatment.DefaultPolicy(), Status: treatment.StatusProposed, Revision: 1, ProposedAt: now, UpdatedAt: now} + for i := range treatment.MaxAgents { + e.CohortOrder = append(e.CohortOrder, fmt.Sprintf("host-%05d", i)) + } + must(t, rp.Experiments.Save(ctx, e)) + got, err := rp.Experiments.Get(ctx, "big") + must(t, err) + if !slices.Equal(got.CohortOrder, e.CohortOrder) { + t.Fatalf("expected %d ordered cohort members, got %d", len(e.CohortOrder), len(got.CohortOrder)) + } + }}, + {"missing", "negative: an unknown experiment is ErrNotFound and does not exist", func(t *testing.T, rp store.Repos) { + if _, err := rp.Experiments.Get(ctx, "ghost"); !errors.Is(err, store.ErrNotFound) { + t.Fatalf("expected ErrNotFound, got %v", err) + } + exists, err := rp.Experiments.Exists(ctx, "ghost") + must(t, err) + if exists { + t.Fatal("expected Exists to be false") + } + list, err := rp.Experiments.List(ctx, "ghost", 10) + must(t, err) + if len(list) != 0 { + t.Fatalf("expected an empty list, got %d", len(list)) + } + }}, + {"evidence-conflict", "negative: an experiment cannot claim evidence another experiment stored", func(t *testing.T, rp store.Repos) { + e := populated() + must(t, rp.Experiments.Save(ctx, e)) + other := e.Clone() + other.ID = "other" + if err := rp.Experiments.Save(ctx, other); !errors.Is(err, store.ErrEvidenceConflict) { + t.Fatalf("expected ErrEvidenceConflict, got %v", err) + } + exists, err := rp.Experiments.Exists(ctx, "other") + must(t, err) + if exists { + t.Fatal("expected the conflicting save to leave no row") + } + }}, + {"list-and-ids", "positive: List orders by ID and honours the limit; IDs filters by status", func(t *testing.T, rp store.Repos) { + for i, s := range []treatment.Status{treatment.StatusRunning, treatment.StatusProposed, treatment.StatusRetired, treatment.StatusRunning} { + e := &treatment.Experiment{ID: fmt.Sprintf("e%d", 3-i), Location: "dc1", Hypothesis: "h", Treatment: fixtures.Treatment(), Policy: treatment.DefaultPolicy(), Status: s, Revision: 1, ProposedAt: now, UpdatedAt: now} + must(t, rp.Experiments.Save(ctx, e)) + } + list, err := rp.Experiments.List(ctx, "", 3) + must(t, err) + ids := make([]string, len(list)) + for i, e := range list { + ids[i] = e.ID + } + if !slices.Equal(ids, []string{"e0", "e1", "e2"}) { + t.Fatalf("expected e0,e1,e2, got %v", ids) + } + one, err := rp.Experiments.List(ctx, "e3", 3) + must(t, err) + if len(one) != 1 || one[0].ID != "e3" { + t.Fatalf("expected only e3, got %d", len(one)) + } + running, err := rp.Experiments.IDs(ctx, treatment.StatusRunning, treatment.StatusPaused) + must(t, err) + none, err := rp.Experiments.IDs(ctx) + must(t, err) + if !slices.Equal(running, []string{"e0", "e3"}) || len(none) != 0 { + t.Fatalf("expected [e0 e3] and nothing for no statuses, got %v %v", running, none) + } + }}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Log(tc.description) + tc.run(t, open(t).Repos()) + }) + } +} + +// eligibility is the discovery contract: which prior experiments reserve a +// destination or consume the location's single-experiment budget. +func eligibility(t *testing.T, open Opener) { + ctx := context.Background() + dst := netip.MustParsePrefix("192.0.2.1/32") + for _, tc := range []struct { + name, description string + status treatment.Status + age time.Duration + location, destination string + expectBlocked bool + expectActive bool + }{ + {"proposed", "positive: pending review reserves the destination", treatment.StatusProposed, 48 * time.Hour, "dc1", "192.0.2.1/32", true, true}, + {"running", "positive: an active trial is not rediscovered", treatment.StatusRunning, 48 * time.Hour, "dc1", "192.0.2.1/32", true, true}, + {"paused", "positive: paused trials remain reserved", treatment.StatusPaused, 48 * time.Hour, "dc1", "192.0.2.1/32", true, true}, + {"suspended", "positive: temporary holds are not bypassed", treatment.StatusSuspended, 48 * time.Hour, "dc1", "192.0.2.1/32", true, true}, + {"stable", "positive: completed stable treatments stay reserved and free the budget", treatment.StatusComplete, 48 * time.Hour, "dc1", "192.0.2.1/32", true, false}, + {"recent-rollback", "negative: a recent withdrawal is in cooldown", treatment.StatusRolledBack, 23 * time.Hour, "dc1", "192.0.2.1/32", true, false}, + {"recent-retirement", "negative: a recent retirement is in cooldown", treatment.StatusRetired, 23 * time.Hour, "dc1", "192.0.2.1/32", true, false}, + {"cooldown-boundary", "boundary: cooldown ends at exactly 24 hours", treatment.StatusRolledBack, 24 * time.Hour, "dc1", "192.0.2.1/32", false, false}, + {"old-retirement", "boundary: an old retirement does not reserve the destination", treatment.StatusRetired, 25 * time.Hour, "dc1", "192.0.2.1/32", false, false}, + {"other-location", "corner: reservation is location specific", treatment.StatusRunning, time.Hour, "dc2", "192.0.2.1/32", false, false}, + {"other-destination", "corner: another destination consumes the budget only", treatment.StatusRunning, time.Hour, "dc1", "192.0.2.2/32", false, true}, + {"covering-prefix", "corner: a /24 experiment does not reserve a /32 inside it", treatment.StatusRunning, time.Hour, "dc1", "192.0.2.0/24", false, true}, + } { + t.Run(tc.name, func(t *testing.T) { + rp := open(t).Repos() + tr := fixtures.Treatment() + tr.Destination = netip.MustParsePrefix(tc.destination) + must(t, rp.Experiments.Save(ctx, &treatment.Experiment{ID: "prior", Location: tc.location, Hypothesis: "h", Treatment: tr, Policy: treatment.DefaultPolicy(), Status: tc.status, Revision: 1, ProposedAt: now.Add(-tc.age), UpdatedAt: now.Add(-tc.age)})) + blocked, err := rp.Experiments.DestinationBlocked(ctx, "dc1", dst, now.Add(-24*time.Hour)) + must(t, err) + active, err := rp.Experiments.HasActive(ctx, "dc1") + must(t, err) + if blocked != tc.expectBlocked || active != tc.expectActive { + t.Fatalf("%s: blocked=%t active=%t, expected %t/%t", tc.description, blocked, active, tc.expectBlocked, tc.expectActive) + } + }) + } + t.Run("v6-destination", func(t *testing.T) { + t.Log("corner: a /128 destination is reserved by exact match") + rp := open(t).Repos() + tr := fixtures.Treatment() + tr.Destination = netip.MustParsePrefix("2001:db8::1/128") + must(t, rp.Experiments.Save(ctx, &treatment.Experiment{ID: "v6", Location: "dc1", Hypothesis: "h", Treatment: tr, Policy: treatment.DefaultPolicy(), Status: treatment.StatusRunning, Revision: 1, ProposedAt: now, UpdatedAt: now})) + hit, err := rp.Experiments.DestinationBlocked(ctx, "dc1", tr.Destination, now) + must(t, err) + miss, err := rp.Experiments.DestinationBlocked(ctx, "dc1", netip.MustParsePrefix("2001:db8::2/128"), now) + must(t, err) + if !hit || miss { + t.Fatalf("expected exact /128 match only, got hit=%t miss=%t", hit, miss) + } + }) +} + +func evidence(t *testing.T, open Opener) { + ctx := context.Background() + base := func() *treatment.Evidence { + return &treatment.Evidence{ID: "ev-1", AnalyzedAt: now, WindowStart: now.Add(-time.Hour), WindowEnd: now, QueryVersion: "v1", EnrichmentVersion: "e1", ArtifactSHA256: strings.Repeat("b", 64), Hosts: []treatment.HostEvidence{{AgentID: "host-1", Sockets: 10, BytesAcked: 1 << 20, GoodputBps: 1e6, RetransmissionRatio: .001, RttP95Ms: 2.5, ExposedSockets: 9, CollectionHealthy: true}}} + } + for _, tc := range []struct { + name, description string + run func(t *testing.T, v store.Evidence) + }{ + {"roundtrip", "positive: every column and host row survives", func(t *testing.T, v store.Evidence) { + e := base() + must(t, v.Put(ctx, "exp", e)) + got, err := v.Get(ctx, "ev-1") + must(t, err) + sameEvidence(t, "expected the stored evidence", got, e) + }}, + {"idempotent", "positive: storing identical content again under the same owner is a no-op", func(t *testing.T, v store.Evidence) { + must(t, v.Put(ctx, "exp", base())) + must(t, v.Put(ctx, "exp", base())) + }}, + {"content-conflict", "negative: the same ID with different content is rejected", func(t *testing.T, v store.Evidence) { + must(t, v.Put(ctx, "exp", base())) + e := base() + e.Hosts[0].GoodputBps++ + if err := v.Put(ctx, "exp", e); !errors.Is(err, store.ErrEvidenceConflict) { + t.Fatalf("expected ErrEvidenceConflict, got %v", err) + } + got, err := v.Get(ctx, "ev-1") + must(t, err) + sameEvidence(t, "expected the original content to remain", got, base()) + }}, + {"owner-conflict", "negative: another experiment cannot adopt the evidence", func(t *testing.T, v store.Evidence) { + must(t, v.Put(ctx, "exp", base())) + if err := v.Put(ctx, "other", base()); !errors.Is(err, store.ErrEvidenceConflict) { + t.Fatalf("expected ErrEvidenceConflict, got %v", err) + } + }}, + {"nanosecond-clock", "corner: evidence stamped with a nanosecond clock re-saves after a microsecond read back", func(t *testing.T, v store.Evidence) { + e := base() + e.AnalyzedAt = e.AnalyzedAt.Add(1500 * time.Nanosecond) + e.WindowEnd = e.WindowEnd.Add(999 * time.Nanosecond) + must(t, v.Put(ctx, "exp", e)) + got, err := v.Get(ctx, "ev-1") + must(t, err) + must(t, v.Put(ctx, "exp", got)) + must(t, v.Put(ctx, "exp", e)) + }}, + {"missing", "negative: unknown evidence is ErrNotFound", func(t *testing.T, v store.Evidence) { + if _, err := v.Get(ctx, "ghost"); !errors.Is(err, store.ErrNotFound) { + t.Fatalf("expected ErrNotFound, got %v", err) + } + }}, + {"no-hosts", "boundary: evidence with an empty population stores and reads back empty", func(t *testing.T, v store.Evidence) { + e := base() + e.Hosts = nil + must(t, v.Put(ctx, "exp", e)) + got, err := v.Get(ctx, "ev-1") + must(t, err) + if len(got.Hosts) != 0 { + t.Fatalf("expected no hosts, got %d", len(got.Hosts)) + } + }}, + {"max-hosts", "boundary: MaxAgents host rows are stored in order in one call", func(t *testing.T, v store.Evidence) { + e := base() + e.Hosts = nil + for i := range treatment.MaxAgents { + e.Hosts = append(e.Hosts, treatment.HostEvidence{AgentID: fmt.Sprintf("host-%05d", i), Sockets: uint64(i), BytesAcked: 1 << 20, GoodputBps: float64(i), RttP95Ms: 1, CollectionHealthy: i%2 == 0}) + } + must(t, v.Put(ctx, "exp", e)) + got, err := v.Get(ctx, "ev-1") + must(t, err) + if len(got.Hosts) != treatment.MaxAgents { + t.Fatalf("expected %d hosts, got %d", treatment.MaxAgents, len(got.Hosts)) + } + for i := range got.Hosts { + if got.Hosts[i] != e.Hosts[i] { + t.Fatalf("expected host %d to be %+v, got %+v", i, e.Hosts[i], got.Hosts[i]) + } + } + }}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Log(tc.description) + tc.run(t, open(t).Repos().Evidence) + }) + } +} + +func holds(t *testing.T, open Opener) { + ctx := context.Background() + for _, tc := range []struct { + name, description string + set, clear, ask []string + expected bool + }{ + {"fleet", "positive: a fleet hold answers any scope list containing it", []string{"fleet"}, nil, []string{"fleet", "location:dc1", "agent:h"}, true}, + {"narrower", "positive: an agent hold is seen when asked for that agent", []string{"agent:h"}, nil, []string{"fleet", "location:dc1", "agent:h"}, true}, + {"cleared", "negative: a cleared hold no longer answers", []string{"fleet"}, []string{"fleet"}, []string{"fleet"}, false}, + {"other-scope", "negative: holds on other scopes do not answer", []string{"location:dc2"}, nil, []string{"fleet", "location:dc1"}, false}, + {"no-scopes", "boundary: asking for no scopes is never held", []string{"fleet"}, nil, nil, false}, + {"clear-missing", "corner: clearing an absent scope is not an error and leaves others set", []string{"fleet"}, []string{"agent:ghost"}, []string{"fleet"}, true}, + {"reset-reason", "corner: setting a held scope again keeps it held", []string{"fleet", "fleet"}, nil, []string{"fleet"}, true}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Log(tc.description) + h := open(t).Repos().Holds + for _, s := range tc.set { + must(t, h.Set(ctx, s, "reason "+s)) + } + for _, s := range tc.clear { + must(t, h.Clear(ctx, s)) + } + got, err := h.Held(ctx, tc.ask...) + must(t, err) + if got != tc.expected { + t.Fatalf("expected held=%t, got %t", tc.expected, got) + } + }) + } +} + +func audit(t *testing.T, open Opener) { + ctx := context.Background() + for _, tc := range []struct { + name, description string + run func(t *testing.T, a store.Audit) + }{ + {"append-lookup", "positive: an event is found by request ID with its payload intact", func(t *testing.T, a store.Audit) { + must(t, a.Append(ctx, "operator/alice", "req-1", "review", []byte(`{"approve":true}`))) + got, err := a.Lookup(ctx, "req-1") + must(t, err) + if got.Actor != "operator/alice" || got.Kind != "review" || got.RequestID != "req-1" || got.Sequence < 1 || got.OccurredAt.IsZero() || !strings.Contains(string(got.Payload), `"approve"`) { + t.Fatalf("expected the appended event, got %+v", got) + } + }}, + {"duplicate-request", "negative: a request ID can be appended once", func(t *testing.T, a store.Audit) { + must(t, a.Append(ctx, "x", "req-1", "review", []byte(`{}`))) + if err := a.Append(ctx, "x", "req-1", "control", []byte(`{}`)); !errors.Is(err, store.ErrExists) { + t.Fatalf("expected ErrExists, got %v", err) + } + n, err := a.Count(ctx) + must(t, err) + if n != 1 { + t.Fatalf("expected one event, got %d", n) + } + }}, + {"anonymous-events", "boundary: events without a request ID never collide", func(t *testing.T, a store.Audit) { + must(t, a.Append(ctx, "maintenance", "", "renew", []byte(`{}`))) + must(t, a.Append(ctx, "maintenance", "", "renew", []byte(`{}`))) + n, err := a.Count(ctx) + must(t, err) + if n != 2 { + t.Fatalf("expected two events, got %d", n) + } + if _, err := a.Lookup(ctx, ""); !errors.Is(err, store.ErrNotFound) { + t.Fatalf("expected ErrNotFound for the empty request ID, got %v", err) + } + }}, + {"missing", "negative: an unknown request ID is ErrNotFound", func(t *testing.T, a store.Audit) { + if _, err := a.Lookup(ctx, "ghost"); !errors.Is(err, store.ErrNotFound) { + t.Fatalf("expected ErrNotFound, got %v", err) + } + }}, + {"monotonic", "corner: sequence numbers increase in append order", func(t *testing.T, a store.Audit) { + var last int64 + for i := range 5 { + id := fmt.Sprintf("req-%d", i) + must(t, a.Append(ctx, "x", id, "k", []byte(`{}`))) + got, err := a.Lookup(ctx, id) + must(t, err) + if got.Sequence <= last { + t.Fatalf("expected sequence above %d, got %d", last, got.Sequence) + } + last = got.Sequence + } + }}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Log(tc.description) + tc.run(t, open(t).Repos().Audit) + }) + } +} + +func tx(t *testing.T, open Opener) { + ctx := context.Background() + t.Run("commit", func(t *testing.T) { + t.Log("positive: writes inside a successful Tx are visible afterwards") + s := open(t) + must(t, s.Tx(ctx, func(rp store.Repos) error { + if err := rp.Holds.Set(ctx, "fleet", "incident"); err != nil { + return err + } + return rp.Agents.Enroll(ctx, "host-1", "dc1", fixtures.Desired(now)) + })) + held, err := s.Repos().Holds.Held(ctx, "fleet") + must(t, err) + if _, err := s.Repos().Agents.Get(ctx, "host-1"); err != nil || !held { + t.Fatalf("expected committed hold and agent, got held=%t err=%v", held, err) + } + }) + t.Run("rollback", func(t *testing.T) { + t.Log("negative: a failing Tx leaves no trace of its writes") + s := open(t) + boom := errors.New("boom") + err := s.Tx(ctx, func(rp store.Repos) error { + if err := rp.Holds.Set(ctx, "fleet", "incident"); err != nil { + return err + } + if err := rp.Audit.Append(ctx, "x", "req-1", "k", []byte(`{}`)); err != nil { + return err + } + return boom + }) + if !errors.Is(err, boom) { + t.Fatalf("expected the callback error, got %v", err) + } + held, err := s.Repos().Holds.Held(ctx, "fleet") + must(t, err) + n, err := s.Repos().Audit.Count(ctx) + must(t, err) + if held || n != 0 { + t.Fatalf("expected nothing persisted, got held=%t events=%d", held, n) + } + }) + t.Run("read-your-writes", func(t *testing.T) { + t.Log("positive: a Tx sees its own uncommitted writes") + s := open(t) + must(t, s.Tx(ctx, func(rp store.Repos) error { + if err := rp.Experiments.Save(ctx, populated()); err != nil { + return err + } + got, err := rp.Experiments.Get(ctx, "exp") + if err != nil { + return err + } + if got.Revision != 2 { + return fmt.Errorf("expected revision 2 inside the transaction, got %d", got.Revision) + } + return nil + })) + }) + t.Run("cancelled-context", func(t *testing.T) { + t.Log("boundary: a cancelled context fails the Tx and persists nothing") + s := open(t) + cancelled, cancel := context.WithCancel(ctx) + cancel() + if err := s.Tx(cancelled, func(rp store.Repos) error { return rp.Holds.Set(ctx, "fleet", "x") }); err == nil { + t.Fatal("expected an error from a cancelled context") + } + held, err := s.Repos().Holds.Held(ctx, "fleet") + must(t, err) + if held { + t.Fatal("expected no hold after a cancelled transaction") + } + }) + t.Run("serialises", func(t *testing.T) { + t.Log("corner: two concurrent Tx calls run one after the other, both commit, sequences stay monotonic") + s := open(t) + entered := make(chan struct{}) + var mu sync.Mutex + var order []string + var wg sync.WaitGroup + wg.Add(2) + go func() { + defer wg.Done() + must(t, s.Tx(ctx, func(rp store.Repos) error { + close(entered) + mu.Lock() + order = append(order, "first-start") + mu.Unlock() + time.Sleep(200 * time.Millisecond) + mu.Lock() + order = append(order, "first-end") + mu.Unlock() + return rp.Audit.Append(ctx, "x", "first", "k", []byte(`{}`)) + })) + }() + go func() { + defer wg.Done() + <-entered + must(t, s.Tx(ctx, func(rp store.Repos) error { + mu.Lock() + order = append(order, "second-start") + mu.Unlock() + return rp.Audit.Append(ctx, "x", "second", "k", []byte(`{}`)) + })) + }() + wg.Wait() + if !slices.Equal(order, []string{"first-start", "first-end", "second-start"}) { + t.Fatalf("expected the second transaction to wait for the first, got %v", order) + } + first, err := s.Repos().Audit.Lookup(ctx, "first") + must(t, err) + second, err := s.Repos().Audit.Lookup(ctx, "second") + must(t, err) + if first.Sequence >= second.Sequence { + t.Fatalf("expected sequence %d < %d", first.Sequence, second.Sequence) + } + }) +} + +func metrics(t *testing.T, open Opener) { + ctx := context.Background() + t.Run("empty", func(t *testing.T) { + t.Log("boundary: an empty store reports every gauge as zero") + got, err := open(t).Repos().Metrics.Snapshot(ctx, now) + must(t, err) + for _, k := range []string{"agents", "agents_connected", "agents_suspended", "assignments", "routes_verified", "routes_failed", "routes_expired", "experiments_pending", "experiments_running", "experiments_complete", "audit_events"} { + v, ok := got[k] + if !ok || v != 0 { + t.Fatalf("expected %s=0, got %v (present=%t)", k, v, ok) + } + } + }) + t.Run("populated", func(t *testing.T) { + t.Log("positive: gauges reflect agents, records, experiments and events") + s := open(t) + rp := s.Repos() + must(t, rp.Agents.Enroll(ctx, "host-1", "dc1", fixtures.Desired(now))) + must(t, rp.Agents.Observe(ctx, report("host-1", "dc1"), now.Add(-time.Minute))) + must(t, rp.Agents.Enroll(ctx, "host-2", "dc1", &treatment.DesiredState{AgentID: "host-2", Revision: 1, Suspended: true, IssuedAt: now})) + must(t, rp.Agents.Enroll(ctx, "host-3", "dc1", &treatment.DesiredState{AgentID: "host-3", Revision: 1, IssuedAt: now})) + must(t, rp.Agents.Observe(ctx, &treatment.AgentReport{AgentID: "host-3", Location: "dc1", Capabilities: treatment.Capabilities{BootID: "b"}, Records: []treatment.ApplicationRecord{{Assignment: fixtures.Desired(now).Assignments[0], Status: treatment.ApplyExpired, ExpiredLatched: true}}}, now.Add(-10*time.Minute))) + for id, st := range map[string]treatment.Status{"p": treatment.StatusProposed, "r": treatment.StatusRunning, "c": treatment.StatusComplete, "x": treatment.StatusRetired} { + must(t, rp.Experiments.Save(ctx, &treatment.Experiment{ID: id, Location: "dc1", Hypothesis: "h", Treatment: fixtures.Treatment(), Policy: treatment.DefaultPolicy(), Status: st, Revision: 1, ProposedAt: now, UpdatedAt: now})) + } + must(t, rp.Audit.Append(ctx, "x", "", "k", []byte(`{}`))) + got, err := rp.Metrics.Snapshot(ctx, now) + must(t, err) + expected := map[string]float64{"agents": 3, "agents_connected": 1, "agents_suspended": 2, "assignments": 1, "routes_verified": 1, "routes_failed": 1, "routes_expired": 1, "experiments_pending": 1, "experiments_running": 1, "experiments_complete": 1, "audit_events": 1} + for k, v := range expected { + if got[k] != v { + t.Errorf("expected %s=%g, got %g", k, v, got[k]) + } + } + }) +} + +func must(t *testing.T, err error) { + t.Helper() + if err != nil { + t.Fatal(err) + } +} diff --git a/pkg/treatment/enums.go b/pkg/treatment/enums.go new file mode 100644 index 000000000..7c97d3ce2 --- /dev/null +++ b/pkg/treatment/enums.go @@ -0,0 +1,220 @@ +package treatment + +import "fmt" + +// The enumerations below are the domain's own. Their numeric values mirror +// the xtcp_treatment.v1 protobuf enums one-for-one so the wire package can +// convert with a range-checked cast; wire's mirror tests pin that +// correspondence. Names are the snake_case forms used in SQL, logs and docs. + +// Status is an experiment's lifecycle state. Legal changes are the state +// machine in package experiment. +type Status uint8 + +const ( + StatusUnspecified Status = iota + StatusProposed + StatusRunning + StatusPaused + StatusSuspended + StatusComplete + StatusRetired + StatusRolledBack + statusCount +) + +var statusNames = [statusCount]string{"unspecified", "proposed", "running", "paused", "suspended", "complete", "retired", "rolled_back"} + +func (s Status) String() string { return enumName(uint8(s), statusNames[:], "status") } + +// Valid reports whether s is a defined, non-zero status. +func (s Status) Valid() bool { return s > StatusUnspecified && s < statusCount } + +// Active reports whether the experiment still consumes the location's budget. +func (s Status) Active() bool { + return s == StatusProposed || s == StatusRunning || s == StatusPaused || s == StatusSuspended +} + +// Terminal reports whether no event can move the experiment again. +func (s Status) Terminal() bool { return s == StatusRetired || s == StatusRolledBack } + +// ParseStatus accepts the snake_case name. +func ParseStatus(s string) (Status, error) { + i, err := parseEnum(s, statusNames[:], "status") + return Status(i), err +} + +// Statuses lists every defined status including unspecified, for exhaustive tests. +func Statuses() []Status { + out := make([]Status, 0, statusCount) + for s := StatusUnspecified; s < statusCount; s++ { + out = append(out, s) + } + return out +} + +// AssignmentMode says what an agent must do with an assignment. +type AssignmentMode uint8 + +const ( + ModeUnspecified AssignmentMode = iota + // ModeTrial installs the treatment until the lease expires. + ModeTrial + // ModeStable installs the treatment without a lease. + ModeStable + // ModeSuspended withdraws the treatment but keeps the assignment. + ModeSuspended + // ModeRetired withdraws the treatment; the assignment is a tombstone. + ModeRetired + modeCount +) + +var modeNames = [modeCount]string{"unspecified", "trial", "stable", "suspended", "retired"} + +func (m AssignmentMode) String() string { return enumName(uint8(m), modeNames[:], "mode") } + +// Valid reports whether m is a defined, non-zero mode. +func (m AssignmentMode) Valid() bool { return m > ModeUnspecified && m < modeCount } + +// Installs reports whether the mode asks the agent to keep a route present. +func (m AssignmentMode) Installs() bool { return m == ModeTrial || m == ModeStable } + +// ParseAssignmentMode accepts the snake_case name. +func ParseAssignmentMode(s string) (AssignmentMode, error) { + i, err := parseEnum(s, modeNames[:], "assignment mode") + return AssignmentMode(i), err +} + +// ApplyStatus is an agent's report of one assignment's kernel state. +type ApplyStatus uint8 + +const ( + ApplyUnspecified ApplyStatus = iota + ApplyPending + ApplyVerified + ApplyRemoved + ApplyExpired + ApplyFailed + applyCount +) + +var applyNames = [applyCount]string{"unspecified", "pending", "verified", "removed", "expired", "failed"} + +func (a ApplyStatus) String() string { return enumName(uint8(a), applyNames[:], "apply status") } + +// Valid reports whether a is a defined value. Unspecified is accepted: an +// agent may report an assignment it has not acted on yet. +func (a ApplyStatus) Valid() bool { return a < applyCount } + +// ParseApplyStatus accepts the snake_case name. +func ParseApplyStatus(s string) (ApplyStatus, error) { + i, err := parseEnum(s, applyNames[:], "apply status") + return ApplyStatus(i), err +} + +// Outcome is the result of evaluating evidence against a phase, or of drift +// monitoring a completed treatment. +type Outcome uint8 + +const ( + OutcomeUnspecified Outcome = iota + OutcomeBaseline + OutcomeAwaitingApplication + OutcomeAwaitingExposure + OutcomeInsufficientDuration + OutcomeInsufficientData + OutcomeInsufficientControls + OutcomeStale + OutcomeUnavailable + OutcomeInconclusive + OutcomeSafetyOnly + OutcomeFinalSafety + OutcomeBeneficial + OutcomeHarmful + OutcomeExpired + OutcomeStableObserved + OutcomeStableEvidenceIncomplete + OutcomeStableRegressionReviewRequired + OutcomeObserving + outcomeCount +) + +var outcomeNames = [outcomeCount]string{ + "unspecified", + "baseline", + "awaiting_application", + "awaiting_exposure", + "insufficient_duration", + "insufficient_data", + "insufficient_controls", + "stale", + "unavailable", + "inconclusive", + "safety_only", + "final_safety", + "beneficial", + "harmful", + "expired", + "stable_observed", + "stable_evidence_incomplete", + "stable_regression_review_required", + "observing", +} + +func (o Outcome) String() string { return enumName(uint8(o), outcomeNames[:], "outcome") } + +// Valid reports whether o is a known value. Unspecified is valid: it is the +// state of an experiment nothing has evaluated yet. +func (o Outcome) Valid() bool { return o < outcomeCount } + +// Promotable reports whether a reviewer may advance the phase on this +// outcome. Every other outcome blocks promotion. +func (o Outcome) Promotable() bool { + return o == OutcomeBeneficial || o == OutcomeSafetyOnly || o == OutcomeFinalSafety +} + +// Withdraws reports whether the safety controller must roll the experiment +// back on this outcome without waiting for a reviewer. +func (o Outcome) Withdraws() bool { return o == OutcomeHarmful || o == OutcomeExpired } + +// RenewsLease reports whether healthy evidence with this outcome may renew +// the current phase's trial leases. Stale or missing data cannot. +func (o Outcome) RenewsLease() bool { return o != OutcomeStale && o != OutcomeInsufficientData } + +// ParseOutcome accepts the snake_case names used in documentation, logs and +// the pre-enum journal format. +func ParseOutcome(s string) (Outcome, error) { + i, err := parseEnum(s, outcomeNames[:], "outcome") + return Outcome(i), err +} + +// Outcomes lists every defined outcome including unspecified. +func Outcomes() []Outcome { + out := make([]Outcome, 0, outcomeCount) + for o := OutcomeUnspecified; o < outcomeCount; o++ { + out = append(out, o) + } + return out +} + +// EnumCounts reports how many values each domain enumeration defines, so the +// wire package can assert the protobuf enums have exactly the same size. +func EnumCounts() (statuses, modes, applyStatuses, outcomes int) { + return int(statusCount), int(modeCount), int(applyCount), int(outcomeCount) +} + +func enumName(v uint8, names []string, kind string) string { + if int(v) < len(names) { + return names[v] + } + return fmt.Sprintf("%s(%d)", kind, v) +} + +func parseEnum(s string, names []string, kind string) (uint8, error) { + for i, n := range names { + if n == s { + return uint8(i), nil + } + } + return 0, fmt.Errorf("unknown %s %q", kind, s) +} diff --git a/pkg/treatment/experiment/benchmark_test.go b/pkg/treatment/experiment/benchmark_test.go index e2e90f031..b9c6c7a64 100644 --- a/pkg/treatment/experiment/benchmark_test.go +++ b/pkg/treatment/experiment/benchmark_test.go @@ -5,23 +5,21 @@ import ( "testing" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" - "google.golang.org/protobuf/proto" ) -func fleetFixture(n int) (*pb.Experiment, *pb.Evidence, time.Time) { +func fleetFixture(n int) (*treatment.Experiment, *treatment.Evidence, time.Time) { now := time.Unix(1700000000, 0) e, v := fixtures.Experiment(now, treatment.DefaultPolicy()) e.CohortOrder, e.Baseline.Hosts, v.Hosts = nil, nil, nil e.Phases[0].TargetHosts = uint32(n / 2) for i := range n { id := fmt.Sprintf("host-%d", i) - h := &pb.HostEvidence{AgentId: id, Sockets: 100, ExposedSockets: 100, BytesAcked: 1 << 25, GoodputBps: 1e8, RttP95Ms: 5, CollectionHealthy: true} + h := treatment.HostEvidence{AgentID: id, Sockets: 100, ExposedSockets: 100, BytesAcked: 1 << 25, GoodputBps: 1e8, RttP95Ms: 5, CollectionHealthy: true} e.CohortOrder = append(e.CohortOrder, id) e.Baseline.Hosts = append(e.Baseline.Hosts, h) - current := proto.Clone(h).(*pb.HostEvidence) + current := h if i < n/2 { current.GoodputBps *= 1.3 + float64(i%7)*.001 } @@ -47,7 +45,7 @@ func BenchmarkFleetEvidence(b *testing.B) { }}, {"Evaluate", func() error { got, err := Evaluate(e, v, now) - if err == nil && got.Outcome != OutcomeBeneficial { + if err == nil && got.Outcome != treatment.OutcomeBeneficial { return fmt.Errorf("unexpected outcome %s", got.Outcome) } return err diff --git a/pkg/treatment/experiment/boundaries_test.go b/pkg/treatment/experiment/boundaries_test.go index 15a7c3948..abc7c5607 100644 --- a/pkg/treatment/experiment/boundaries_test.go +++ b/pkg/treatment/experiment/boundaries_test.go @@ -1,82 +1,87 @@ package experiment import ( + "reflect" "testing" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/timestamppb" ) func TestEvaluationBoundaries(t *testing.T) { now := time.Unix(1700000000, 0) for _, tc := range []struct { name, description string - want Outcome + want treatment.Outcome wantError bool - change func(*pb.Experiment, *pb.Evidence) + change func(*treatment.Experiment, *treatment.Evidence) }{ - {"no-phase", "unstarted experiment cannot be evaluated", OutcomeUnspecified, true, func(e *pb.Experiment, _ *pb.Evidence) { e.Phases = nil }}, - {"no-observation", "all canaries must acknowledge exposure", OutcomeAwaitingExposure, false, func(e *pb.Experiment, _ *pb.Evidence) { e.Phases[0].ObservationStartedAt = nil }}, - {"expiry-equality", "phase expires exactly at its deadline", OutcomeExpired, false, func(e *pb.Experiment, _ *pb.Evidence) { - e.Phases[0].IssuedAt = timestamppb.New(now.Add(-24 * time.Hour)) + {"no-phase", "unstarted experiment cannot be evaluated", treatment.OutcomeUnspecified, true, func(e *treatment.Experiment, _ *treatment.Evidence) { e.Phases = nil }}, + {"no-observation", "all canaries must acknowledge exposure", treatment.OutcomeAwaitingExposure, false, func(e *treatment.Experiment, _ *treatment.Evidence) { e.Phases[0].ObservationStartedAt = time.Time{} }}, + {"expiry-equality", "phase expires exactly at its deadline", treatment.OutcomeExpired, false, func(e *treatment.Experiment, _ *treatment.Evidence) { + e.Phases[0].IssuedAt = now.Add(-24 * time.Hour) }}, - {"freshness-equality", "evidence at maximum age is usable", OutcomeBeneficial, false, func(_ *pb.Experiment, v *pb.Evidence) { v.WindowEnd = timestamppb.New(now.Add(-2 * time.Minute)) }}, - {"freshness-over", "one nanosecond beyond maximum age is stale", OutcomeStale, false, func(_ *pb.Experiment, v *pb.Evidence) { - v.WindowEnd = timestamppb.New(now.Add(-2*time.Minute - time.Nanosecond)) + {"freshness-equality", "evidence at maximum age is usable", treatment.OutcomeBeneficial, false, func(_ *treatment.Experiment, v *treatment.Evidence) { v.WindowEnd = now.Add(-2 * time.Minute) }}, + {"freshness-over", "one nanosecond beyond maximum age is stale", treatment.OutcomeStale, false, func(_ *treatment.Experiment, v *treatment.Evidence) { + v.WindowEnd = now.Add(-2*time.Minute - time.Nanosecond) }}, - {"clock-equality", "30 seconds of forward clock skew is allowed", OutcomeBeneficial, false, func(_ *pb.Experiment, v *pb.Evidence) { v.AnalyzedAt = timestamppb.New(now.Add(30 * time.Second)) }}, - {"clock-over", "excess forward skew is rejected", OutcomeStale, false, func(_ *pb.Experiment, v *pb.Evidence) { - v.AnalyzedAt = timestamppb.New(now.Add(30*time.Second + time.Nanosecond)) + {"clock-equality", "30 seconds of forward clock skew is allowed", treatment.OutcomeBeneficial, false, func(_ *treatment.Experiment, v *treatment.Evidence) { v.AnalyzedAt = now.Add(30 * time.Second) }}, + {"clock-over", "excess forward skew is rejected", treatment.OutcomeStale, false, func(_ *treatment.Experiment, v *treatment.Evidence) { + v.AnalyzedAt = now.Add(30*time.Second + time.Nanosecond) }}, - {"duration-equality", "exactly minimum observation is sufficient", OutcomeBeneficial, false, func(_ *pb.Experiment, v *pb.Evidence) { - v.WindowStart = timestamppb.New(v.WindowEnd.AsTime().Add(-30 * time.Minute)) + {"duration-equality", "exactly minimum observation is sufficient", treatment.OutcomeBeneficial, false, func(_ *treatment.Experiment, v *treatment.Evidence) { + v.WindowStart = v.WindowEnd.Add(-30 * time.Minute) }}, - {"duration-under", "one nanosecond short is insufficient", OutcomeInsufficientDuration, false, func(_ *pb.Experiment, v *pb.Evidence) { - v.WindowStart = timestamppb.New(v.WindowEnd.AsTime().Add(-30*time.Minute + time.Nanosecond)) + {"duration-under", "one nanosecond short is insufficient", treatment.OutcomeInsufficientDuration, false, func(_ *treatment.Experiment, v *treatment.Evidence) { + v.WindowStart = v.WindowEnd.Add(-30*time.Minute + time.Nanosecond) }}, - {"old-window", "pre-treatment data cannot support promotion", OutcomeInsufficientDuration, false, func(e *pb.Experiment, v *pb.Evidence) { - v.WindowStart = timestamppb.New(e.Phases[0].ObservationStartedAt.AsTime().Add(-time.Nanosecond)) + {"old-window", "pre-treatment data cannot support promotion", treatment.OutcomeInsufficientDuration, false, func(e *treatment.Experiment, v *treatment.Evidence) { + v.WindowStart = e.Phases[0].ObservationStartedAt.Add(-time.Nanosecond) }}, - {"zero-target", "phase must have at least one canary", OutcomeUnspecified, true, func(e *pb.Experiment, _ *pb.Evidence) { e.Phases[0].TargetHosts = 0 }}, - {"over-target", "phase cannot exceed fleet size", OutcomeUnspecified, true, func(e *pb.Experiment, _ *pb.Evidence) { e.Phases[0].TargetHosts = 17 }}, - {"sample-equality", "exact sample and exposure thresholds suffice", OutcomeBeneficial, false, func(e *pb.Experiment, v *pb.Evidence) { - for _, h := range v.Hosts { + {"zero-target", "phase must have at least one canary", treatment.OutcomeUnspecified, true, func(e *treatment.Experiment, _ *treatment.Evidence) { e.Phases[0].TargetHosts = 0 }}, + {"over-target", "phase cannot exceed fleet size", treatment.OutcomeUnspecified, true, func(e *treatment.Experiment, _ *treatment.Evidence) { e.Phases[0].TargetHosts = 17 }}, + {"sample-equality", "exact sample and exposure thresholds suffice", treatment.OutcomeBeneficial, false, func(e *treatment.Experiment, v *treatment.Evidence) { + for i := range v.Hosts { + h := &v.Hosts[i] h.Sockets = e.Policy.MinimumSocketsPerHost h.ExposedSockets = h.Sockets h.BytesAcked = e.Policy.MinimumBytesPerHost } }}, - {"missing-host", "missing cohort host blocks evaluation", OutcomeInsufficientData, false, func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts = v.Hosts[1:] }}, - {"missing-baseline", "missing baseline host blocks evaluation", OutcomeInsufficientData, false, func(e *pb.Experiment, _ *pb.Evidence) { e.Baseline.Hosts = e.Baseline.Hosts[1:] }}, - {"low-bytes", "too few bytes blocks evaluation", OutcomeInsufficientData, false, func(e *pb.Experiment, v *pb.Evidence) { v.Hosts[0].BytesAcked = e.Policy.MinimumBytesPerHost - 1 }}, - {"zero-goodput", "zero goodput cannot enter log comparison", OutcomeInsufficientData, false, func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts[0].GoodputBps = 0 }}, - {"zero-baseline-rtt", "zero baseline RTT cannot enter ratio", OutcomeInsufficientData, false, func(e *pb.Experiment, _ *pb.Evidence) { e.Baseline.Hosts[0].RttP95Ms = 0 }}, - {"first-canary", "small cohorts permit safety review only", OutcomeSafetyOnly, false, func(e *pb.Experiment, _ *pb.Evidence) { e.Phases[0].TargetHosts = 1 }}, - {"small-cohort-regression", "large goodput loss blocks small cohort promotion", OutcomeHarmful, false, func(e *pb.Experiment, v *pb.Evidence) { e.Phases[0].TargetHosts = 1; v.Hosts[0].GoodputBps = 1 }}, - {"one-control", "one control cannot estimate variance", OutcomeInsufficientControls, false, func(e *pb.Experiment, _ *pb.Evidence) { e.Phases[0].TargetHosts = 15 }}, - {"full-fleet", "last phase checks safety without controls", OutcomeFinalSafety, false, func(e *pb.Experiment, _ *pb.Evidence) { e.Phases[0].TargetHosts = 16 }}, - {"full-fleet-harm", "last phase still rejects goodput regression", OutcomeHarmful, false, func(e *pb.Experiment, v *pb.Evidence) { + {"missing-host", "missing cohort host blocks evaluation", treatment.OutcomeInsufficientData, false, func(_ *treatment.Experiment, v *treatment.Evidence) { v.Hosts = v.Hosts[1:] }}, + {"missing-baseline", "missing baseline host blocks evaluation", treatment.OutcomeInsufficientData, false, func(e *treatment.Experiment, _ *treatment.Evidence) { e.Baseline.Hosts = e.Baseline.Hosts[1:] }}, + {"low-bytes", "too few bytes blocks evaluation", treatment.OutcomeInsufficientData, false, func(e *treatment.Experiment, v *treatment.Evidence) { + v.Hosts[0].BytesAcked = e.Policy.MinimumBytesPerHost - 1 + }}, + {"zero-goodput", "zero goodput cannot enter log comparison", treatment.OutcomeInsufficientData, false, func(_ *treatment.Experiment, v *treatment.Evidence) { v.Hosts[0].GoodputBps = 0 }}, + {"zero-baseline-rtt", "zero baseline RTT cannot enter ratio", treatment.OutcomeInsufficientData, false, func(e *treatment.Experiment, _ *treatment.Evidence) { e.Baseline.Hosts[0].RttP95Ms = 0 }}, + {"first-canary", "small cohorts permit safety review only", treatment.OutcomeSafetyOnly, false, func(e *treatment.Experiment, _ *treatment.Evidence) { e.Phases[0].TargetHosts = 1 }}, + {"small-cohort-regression", "large goodput loss blocks small cohort promotion", treatment.OutcomeHarmful, false, func(e *treatment.Experiment, v *treatment.Evidence) { + e.Phases[0].TargetHosts = 1 + v.Hosts[0].GoodputBps = 1 + }}, + {"one-control", "one control cannot estimate variance", treatment.OutcomeInsufficientControls, false, func(e *treatment.Experiment, _ *treatment.Evidence) { e.Phases[0].TargetHosts = 15 }}, + {"full-fleet", "last phase checks safety without controls", treatment.OutcomeFinalSafety, false, func(e *treatment.Experiment, _ *treatment.Evidence) { e.Phases[0].TargetHosts = 16 }}, + {"full-fleet-harm", "last phase still rejects goodput regression", treatment.OutcomeHarmful, false, func(e *treatment.Experiment, v *treatment.Evidence) { e.Phases[0].TargetHosts = 16 - for _, h := range v.Hosts { + for i := range v.Hosts { + h := &v.Hosts[i] h.GoodputBps = 1 } }}, - {"invalid-evidence", "malformed evidence fails before decision", OutcomeUnspecified, true, func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts[0] = nil }}, + {"invalid-evidence", "malformed evidence fails before decision", treatment.OutcomeUnspecified, true, func(_ *treatment.Experiment, v *treatment.Evidence) { v.Hosts[0] = treatment.HostEvidence{} }}, } { t.Run(tc.name, func(t *testing.T) { e, v := fixtures.Experiment(now, treatment.DefaultPolicy()) tc.change(e, v) - before := proto.Clone(e) - evidenceBefore := proto.Clone(v) + before := e.Clone() + evidenceBefore := v.Clone() got, err := Evaluate(e, v, now) if (err != nil) != tc.wantError || got.Outcome != tc.want { t.Fatalf("%s: got %+v err=%v; want %s error=%t", tc.description, got, err, tc.want, tc.wantError) } - if !proto.Equal(e, before) || !proto.Equal(v, evidenceBefore) { + if !reflect.DeepEqual(e, before) || !reflect.DeepEqual(v, evidenceBefore) { t.Fatal("evaluation mutated its input") } }) @@ -87,26 +92,29 @@ func TestSafetyGateBoundaries(t *testing.T) { now := time.Unix(1700000000, 0) for _, tc := range []struct { name, description string - change func(*pb.Experiment, *pb.Evidence) + change func(*treatment.Experiment, *treatment.Evidence) healthy, harmful bool }{ - {"healthy", "complete unchanged observations renew leases", func(*pb.Experiment, *pb.Evidence) {}, true, false}, - {"missing-host", "partial evidence cannot renew leases", func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts = v.Hosts[1:] }, false, false}, - {"unhealthy-collector", "collection failures cannot renew leases", func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts[0].CollectionHealthy = false }, false, false}, - {"low-samples", "too few samples cannot renew leases", func(e *pb.Experiment, v *pb.Evidence) { + {"healthy", "complete unchanged observations renew leases", func(*treatment.Experiment, *treatment.Evidence) {}, true, false}, + {"missing-host", "partial evidence cannot renew leases", func(_ *treatment.Experiment, v *treatment.Evidence) { v.Hosts = v.Hosts[1:] }, false, false}, + {"unhealthy-collector", "collection failures cannot renew leases", func(_ *treatment.Experiment, v *treatment.Evidence) { v.Hosts[0].CollectionHealthy = false }, false, false}, + {"low-samples", "too few samples cannot renew leases", func(e *treatment.Experiment, v *treatment.Evidence) { v.Hosts[0].Sockets = e.Policy.MinimumSocketsPerHost - 1 v.Hosts[0].ExposedSockets = 0 }, false, false}, - {"stale", "old observations cannot renew leases", func(_ *pb.Experiment, v *pb.Evidence) { v.WindowEnd = timestamppb.New(now.Add(-3 * time.Minute)) }, false, false}, - {"loss-equality", "loss guard is strictly greater than its limit", func(e *pb.Experiment, v *pb.Evidence) { + {"stale", "old observations cannot renew leases", func(_ *treatment.Experiment, v *treatment.Evidence) { v.WindowEnd = now.Add(-3 * time.Minute) }, false, false}, + {"loss-equality", "loss guard is strictly greater than its limit", func(e *treatment.Experiment, v *treatment.Evidence) { v.Hosts[0].RetransmissionRatio = e.Policy.MaximumRetransmissionIncrease }, true, false}, - {"loss-over", "excess loss is harmful", func(e *pb.Experiment, v *pb.Evidence) { + {"loss-over", "excess loss is harmful", func(e *treatment.Experiment, v *treatment.Evidence) { v.Hosts[0].RetransmissionRatio = e.Policy.MaximumRetransmissionIncrease + 1e-9 }, false, true}, - {"rtt-equality", "exactly representable RTT limit is inclusive", func(e *pb.Experiment, v *pb.Evidence) { e.Policy.MaximumRttIncrease = .5; v.Hosts[0].RttP95Ms = 1.5 }, true, false}, - {"control-harm", "controls are protected too", func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts[15].RttP95Ms = 10 }, false, true}, - {"partial-harm", "missing data does not conceal observed harm", func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts = v.Hosts[1:]; v.Hosts[0].RttP95Ms = 10 }, false, true}, + {"rtt-equality", "exactly representable RTT limit is inclusive", func(e *treatment.Experiment, v *treatment.Evidence) { + e.Policy.MaximumRTTIncrease = .5 + v.Hosts[0].RttP95Ms = 1.5 + }, true, false}, + {"control-harm", "controls are protected too", func(_ *treatment.Experiment, v *treatment.Evidence) { v.Hosts[15].RttP95Ms = 10 }, false, true}, + {"partial-harm", "missing data does not conceal observed harm", func(_ *treatment.Experiment, v *treatment.Evidence) { v.Hosts = v.Hosts[1:]; v.Hosts[0].RttP95Ms = 10 }, false, true}, } { t.Run(tc.name, func(t *testing.T) { e, v := fixtures.Experiment(now, treatment.DefaultPolicy()) @@ -123,24 +131,24 @@ func TestAdvanceTransitions(t *testing.T) { now := time.Unix(1700000000, 0) for _, tc := range []struct { name, description string - change func(*pb.Experiment, *pb.Evidence) - want pb.ExperimentStatus + change func(*treatment.Experiment, *treatment.Evidence) + want treatment.Status phases int wantError bool }{ - {"initial", "approved proposal starts one canary", func(e *pb.Experiment, _ *pb.Evidence) { + {"initial", "approved proposal starts one canary", func(e *treatment.Experiment, _ *treatment.Evidence) { e.Phases = nil - e.Status = pb.ExperimentStatus_EXPERIMENT_STATUS_PROPOSED - }, pb.ExperimentStatus_EXPERIMENT_STATUS_RUNNING, 1, false}, - {"promotion", "benefit permits the next reviewed phase", func(*pb.Experiment, *pb.Evidence) {}, pb.ExperimentStatus_EXPERIMENT_STATUS_RUNNING, 2, false}, - {"completion", "full fleet safety review completes the experiment", func(e *pb.Experiment, _ *pb.Evidence) { e.Phases[0].TargetHosts = 16 }, pb.ExperimentStatus_EXPERIMENT_STATUS_COMPLETE, 1, false}, - {"rolled-back", "rolled back experiments cannot advance", func(e *pb.Experiment, _ *pb.Evidence) { e.Status = pb.ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK }, 0, 0, true}, - {"harm", "harm cannot be promoted", func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts[0].RttP95Ms = 100 }, 0, 0, true}, + e.Status = treatment.StatusProposed + }, treatment.StatusRunning, 1, false}, + {"promotion", "benefit permits the next reviewed phase", func(*treatment.Experiment, *treatment.Evidence) {}, treatment.StatusRunning, 2, false}, + {"completion", "full fleet safety review completes the experiment", func(e *treatment.Experiment, _ *treatment.Evidence) { e.Phases[0].TargetHosts = 16 }, treatment.StatusComplete, 1, false}, + {"rolled-back", "rolled back experiments cannot advance", func(e *treatment.Experiment, _ *treatment.Evidence) { e.Status = treatment.StatusRolledBack }, 0, 0, true}, + {"harm", "harm cannot be promoted", func(_ *treatment.Experiment, v *treatment.Evidence) { v.Hosts[0].RttP95Ms = 100 }, 0, 0, true}, } { t.Run(tc.name, func(t *testing.T) { e, v := fixtures.Experiment(now, treatment.DefaultPolicy()) tc.change(e, v) - before := proto.Clone(e) + before := e.Clone() got, err := Advance(e, v, now, "reviewer", "measured benefit") if (err != nil) != tc.wantError { t.Fatalf("%s: err=%v wantError=%t", tc.description, err, tc.wantError) @@ -154,7 +162,7 @@ func TestAdvanceTransitions(t *testing.T) { t.Fatal("review provenance missing") } } - if !proto.Equal(e, before) { + if !reflect.DeepEqual(e, before) { t.Fatal("advance mutated input") } }) @@ -180,19 +188,21 @@ func TestDefaultRTTGuardExactBoundary(t *testing.T) { for _, tc := range []struct { description string rtt float64 - want Outcome + want treatment.Outcome healthy bool }{ - {"exactly ten percent increase is within the configured guard", 5.5, OutcomeBeneficial, true}, - {"measurable increase above ten percent is harmful", 5.500001, OutcomeHarmful, false}, + {"exactly ten percent increase is within the configured guard", 5.5, treatment.OutcomeBeneficial, true}, + {"measurable increase above ten percent is harmful", 5.500001, treatment.OutcomeHarmful, false}, } { t.Run(tc.description, func(t *testing.T) { now := time.Unix(1700000000, 0) e, v := fixtures.Experiment(now, treatment.DefaultPolicy()) - for _, h := range e.Baseline.Hosts { + for i := range e.Baseline.Hosts { + h := &e.Baseline.Hosts[i] h.RttP95Ms = 5 } - for _, h := range v.Hosts { + for i := range v.Hosts { + h := &v.Hosts[i] h.RttP95Ms = tc.rtt } healthy, harm := HealthyEvidence(e, v, now) @@ -210,11 +220,12 @@ func TestDefaultRTTGuardExactBoundary(t *testing.T) { func TestRTTGuardFleetBoundary(t *testing.T) { for _, n := range []int{100, 1000, 10000} { e, v, now := fleetFixture(n) - for _, h := range v.Hosts { + for i := range v.Hosts { + h := &v.Hosts[i] h.RttP95Ms = 5.5 } got, err := Evaluate(e, v, now) - if err != nil || got.Outcome != OutcomeBeneficial { + if err != nil || got.Outcome != treatment.OutcomeBeneficial { t.Errorf("%d hosts at exactly the RTT guard: %v %v", n, got, err) } } diff --git a/pkg/treatment/experiment/experiment.go b/pkg/treatment/experiment/experiment.go index afaac2730..a1b397e92 100644 --- a/pkg/treatment/experiment/experiment.go +++ b/pkg/treatment/experiment/experiment.go @@ -1,3 +1,7 @@ +// Package experiment is the controller's pure decision core: evidence +// evaluation, the safety gate, cohort growth, phase advancement and the +// experiment state machine. Every function here is a function of its inputs +// and now; persistence and authorization live in package controller. package experiment import ( @@ -6,14 +10,11 @@ import ( "math" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/timestamppb" ) type Evaluation struct { - Outcome Outcome + Outcome treatment.Outcome Improvement, LowerBound float64 } @@ -21,35 +22,31 @@ type Evaluation struct { // The host is the randomization unit; polls and sockets are not replicates. // A conservative Welch interval budgets minute-by-minute looks across all // planned phases. This interval is a review aid, not an automatic promotion. -func Evaluate(e *pb.Experiment, ev *pb.Evidence, now time.Time) (Evaluation, error) { +func Evaluate(e *treatment.Experiment, ev *treatment.Evidence, now time.Time) (Evaluation, error) { if err := treatment.ValidateEvidence(ev); err != nil { return Evaluation{}, err } - if len(e.Phases) == 0 { + phase := e.CurrentPhase() + if phase == nil { return Evaluation{}, errors.New("experiment has no phase") } p := e.Policy - phase := e.Phases[len(e.Phases)-1] - if phase.ObservationStartedAt == nil { - return Evaluation{Outcome: OutcomeAwaitingExposure}, nil + if phase.ObservationStartedAt.IsZero() { + return Evaluation{Outcome: treatment.OutcomeAwaitingExposure}, nil } - if now.Sub(phase.IssuedAt.AsTime()) >= p.MaximumPhase.AsDuration() { - return Evaluation{Outcome: OutcomeExpired}, nil + if now.Sub(phase.IssuedAt) >= p.MaximumPhase { + return Evaluation{Outcome: treatment.OutcomeExpired}, nil } - if ev.AnalyzedAt.AsTime().After(now.Add(30*time.Second)) || now.Sub(ev.WindowEnd.AsTime()) > p.MaximumEvidenceAge.AsDuration() { - return Evaluation{Outcome: OutcomeStale}, nil + if ev.AnalyzedAt.After(now.Add(30*time.Second)) || now.Sub(ev.WindowEnd) > p.MaximumEvidenceAge { + return Evaluation{Outcome: treatment.OutcomeStale}, nil } - if ev.WindowStart.AsTime().Before(phase.ObservationStartedAt.AsTime()) || ev.WindowEnd.AsTime().Sub(ev.WindowStart.AsTime()) < p.MinimumObservation.AsDuration() { - return Evaluation{Outcome: OutcomeInsufficientDuration}, nil + if ev.WindowStart.Before(phase.ObservationStartedAt) || ev.WindowEnd.Sub(ev.WindowStart) < p.MinimumObservation { + return Evaluation{Outcome: treatment.OutcomeInsufficientDuration}, nil } - base := make(map[string]*pb.HostEvidence, len(e.Baseline.Hosts)) - current := make(map[string]*pb.HostEvidence, len(ev.Hosts)) - for _, h := range e.Baseline.Hosts { - base[h.AgentId] = h - } - for _, h := range ev.Hosts { - current[h.AgentId] = h + if e.Baseline == nil { + return Evaluation{}, errors.New("experiment has no baseline") } + base, current := hostIndex(e.Baseline.Hosts), hostIndex(ev.Hosts) target := int(phase.TargetHosts) if target < 1 || target > len(e.CohortOrder) { return Evaluation{}, errors.New("invalid phase cohort size") @@ -60,10 +57,10 @@ func Evaluate(e *pb.Experiment, ev *pb.Evidence, now time.Time) (Evaluation, err for i, id := range e.CohortOrder { h, b := current[id], base[id] if h == nil || b == nil || !h.CollectionHealthy || !b.CollectionHealthy || h.Sockets < p.MinimumSocketsPerHost || h.BytesAcked < p.MinimumBytesPerHost || h.GoodputBps <= 0 || b.GoodputBps <= 0 || b.RttP95Ms <= 0 { - return Evaluation{Outcome: OutcomeInsufficientData}, nil + return Evaluation{Outcome: treatment.OutcomeInsufficientData}, nil } if i < target && h.ExposedSockets < p.MinimumSocketsPerHost { - return Evaluation{Outcome: OutcomeAwaitingExposure}, nil + return Evaluation{Outcome: treatment.OutcomeAwaitingExposure}, nil } x := math.Log(h.GoodputBps / b.GoodputBps) if i < target { @@ -78,56 +75,57 @@ func Evaluate(e *pb.Experiment, ev *pb.Evidence, now time.Time) (Evaluation, err } // Guardrails protect controls as well as treated traffic. A shared-link // regression is harmful even if treated hosts outperform their peers. - if mean(tr) > p.MaximumRetransmissionIncrease || mean(cr) > p.MaximumRetransmissionIncrease || mean(tlat) > math.Log1p(p.MaximumRttIncrease) || mean(clat) > math.Log1p(p.MaximumRttIncrease) { - return Evaluation{Outcome: OutcomeHarmful}, nil + if mean(tr) > p.MaximumRetransmissionIncrease || mean(cr) > p.MaximumRetransmissionIncrease || mean(tlat) > math.Log1p(p.MaximumRTTIncrease) || mean(clat) > math.Log1p(p.MaximumRTTIncrease) { + return Evaluation{Outcome: treatment.OutcomeHarmful}, nil } effect := mean(treated) - mean(control) if len(control) == 0 { // Final 100% phase is a safety check; causal benefit was established // before the last control cohort received the treatment. if effect < -math.Log1p(p.MinimumImprovement) { - return Evaluation{Outcome: OutcomeHarmful}, nil + return Evaluation{Outcome: treatment.OutcomeHarmful}, nil } - return Evaluation{Outcome: OutcomeFinalSafety, Improvement: math.Expm1(effect)}, nil + return Evaluation{Outcome: treatment.OutcomeFinalSafety, Improvement: math.Expm1(effect)}, nil } if len(treated) < int(p.MinimumTreatedHosts) { if effect < -math.Log1p(p.MinimumImprovement) { - return Evaluation{Outcome: OutcomeHarmful}, nil + return Evaluation{Outcome: treatment.OutcomeHarmful}, nil } - return Evaluation{Outcome: OutcomeSafetyOnly, Improvement: math.Expm1(effect)}, nil + return Evaluation{Outcome: treatment.OutcomeSafetyOnly, Improvement: math.Expm1(effect)}, nil } if len(control) < 2 { - return Evaluation{Outcome: OutcomeInsufficientControls}, nil + return Evaluation{Outcome: treatment.OutcomeInsufficientControls}, nil } a, b := variance(treated)/float64(len(treated)), variance(control)/float64(len(control)) se := math.Sqrt(a + b) lower := effect if se > 0 { df := (a + b) * (a + b) / (a*a/float64(len(treated)-1) + b*b/float64(len(control)-1)) - looks := (math.Ceil(math.Log2(float64(len(e.CohortOrder)))) + 2) * math.Ceil(p.MaximumPhase.AsDuration().Minutes()) + looks := (math.Ceil(math.Log2(float64(len(e.CohortOrder)))) + 2) * math.Ceil(p.MaximumPhase.Minutes()) lower -= studentQuantile(1-.05/looks, df) * se } - out := Evaluation{Outcome: OutcomeInconclusive, Improvement: math.Expm1(effect), LowerBound: math.Expm1(lower)} + out := Evaluation{Outcome: treatment.OutcomeInconclusive, Improvement: math.Expm1(effect), LowerBound: math.Expm1(lower)} if lower > math.Log1p(p.MinimumImprovement) { - out.Outcome = OutcomeBeneficial + out.Outcome = treatment.OutcomeBeneficial } return out, nil } +func hostIndex(hosts []treatment.HostEvidence) map[string]*treatment.HostEvidence { + m := make(map[string]*treatment.HostEvidence, len(hosts)) + for i := range hosts { + m[hosts[i].AgentID] = &hosts[i] + } + return m +} + // HealthyEvidence is the fast safety gate, independent of the minimum benefit // observation window. It also prevents missing hosts from renewing leases. -func HealthyEvidence(e *pb.Experiment, v *pb.Evidence, now time.Time) (healthy, harmful bool) { - if treatment.ValidateEvidence(v) != nil || v.AnalyzedAt.AsTime().After(now.Add(30*time.Second)) || now.Sub(v.WindowEnd.AsTime()) > e.Policy.MaximumEvidenceAge.AsDuration() { +func HealthyEvidence(e *treatment.Experiment, v *treatment.Evidence, now time.Time) (healthy, harmful bool) { + if e.Baseline == nil || treatment.ValidateEvidence(v) != nil || v.AnalyzedAt.After(now.Add(30*time.Second)) || now.Sub(v.WindowEnd) > e.Policy.MaximumEvidenceAge { return false, false } - base := make(map[string]*pb.HostEvidence, len(e.Baseline.Hosts)) - current := make(map[string]*pb.HostEvidence, len(v.Hosts)) - for _, h := range e.Baseline.Hosts { - base[h.AgentId] = h - } - for _, h := range v.Hosts { - current[h.AgentId] = h - } + base, current := hostIndex(e.Baseline.Hosts), hostIndex(v.Hosts) healthy = true for _, id := range e.CohortOrder { h, b := current[id], base[id] @@ -135,7 +133,7 @@ func HealthyEvidence(e *pb.Experiment, v *pb.Evidence, now time.Time) (healthy, healthy = false continue } - if h.RetransmissionRatio-b.RetransmissionRatio > e.Policy.MaximumRetransmissionIncrease || b.RttP95Ms > 0 && (h.RttP95Ms-b.RttP95Ms)/b.RttP95Ms > e.Policy.MaximumRttIncrease { + if h.RetransmissionRatio-b.RetransmissionRatio > e.Policy.MaximumRetransmissionIncrease || b.RttP95Ms > 0 && (h.RttP95Ms-b.RttP95Ms)/b.RttP95Ms > e.Policy.MaximumRTTIncrease { harmful = true } } @@ -237,16 +235,15 @@ func NextCohort(current, total int) int { // Advance is pure and returns a new experiment. Callers persist it and all // assignments in one PostgreSQL transaction after authenticating the reviewer. -func Advance(e *pb.Experiment, ev *pb.Evidence, now time.Time, reviewer, reason string) (*pb.Experiment, error) { - n := proto.Clone(e).(*pb.Experiment) +func Advance(e *treatment.Experiment, ev *treatment.Evidence, now time.Time, reviewer, reason string) (*treatment.Experiment, error) { + n := e.Clone() // Both outcomes of approval are checked up front so an illegal status // fails before any evidence is attached to the phase. if _, err := Transition(n.Status, EventApprove); err != nil { return nil, err } target := 0 - if len(n.Phases) > 0 { - phase := n.Phases[len(n.Phases)-1] + if phase := n.CurrentPhase(); phase != nil { target = int(phase.TargetHosts) v, err := Evaluate(n, ev, now) if err != nil { @@ -255,9 +252,9 @@ func Advance(e *pb.Experiment, ev *pb.Evidence, now time.Time, reviewer, reason if !v.Outcome.Promotable() { return nil, fmt.Errorf("promotion blocked: %s", v.Outcome) } - phase.Evidence = proto.Clone(ev).(*pb.Evidence) - phase.Outcome = v.Outcome.Wire() - phase.EvaluatedAt = timestamppb.New(now) + phase.Evidence = ev.Clone() + phase.Outcome = v.Outcome + phase.EvaluatedAt = now phase.Reviewer = reviewer phase.Reason = reason if target == len(n.CohortOrder) { @@ -270,7 +267,7 @@ func Advance(e *pb.Experiment, ev *pb.Evidence, now time.Time, reviewer, reason return n, nil } } - n.Phases = append(n.Phases, &pb.Phase{TargetHosts: uint32(NextCohort(target, len(n.CohortOrder))), IssuedAt: timestamppb.New(now), Reviewer: reviewer, Reason: reason}) + n.Phases = append(n.Phases, treatment.Phase{TargetHosts: uint32(NextCohort(target, len(n.CohortOrder))), IssuedAt: now, Reviewer: reviewer, Reason: reason}) status, err := Transition(n.Status, EventApprove) if err != nil { return nil, err diff --git a/pkg/treatment/experiment/experiment_test.go b/pkg/treatment/experiment/experiment_test.go index 8c0860c68..6607528b3 100644 --- a/pkg/treatment/experiment/experiment_test.go +++ b/pkg/treatment/experiment/experiment_test.go @@ -5,11 +5,8 @@ import ( "testing" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/timestamppb" ) func TestEvaluationBenefitHarmAndEvidenceGates(t *testing.T) { @@ -17,29 +14,31 @@ func TestEvaluationBenefitHarmAndEvidenceGates(t *testing.T) { e, v := fixtures.Experiment(now, treatment.DefaultPolicy()) for _, tc := range []struct { name string - want Outcome - change func(*pb.Experiment, *pb.Evidence) + want treatment.Outcome + change func(*treatment.Experiment, *treatment.Evidence) }{ - {"benefit", OutcomeBeneficial, func(*pb.Experiment, *pb.Evidence) {}}, - {"loss", OutcomeHarmful, func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts[0].RetransmissionRatio = .2 }}, - {"control harm", OutcomeHarmful, func(_ *pb.Experiment, v *pb.Evidence) { - for _, h := range v.Hosts[4:] { + {"benefit", treatment.OutcomeBeneficial, func(*treatment.Experiment, *treatment.Evidence) {}}, + {"loss", treatment.OutcomeHarmful, func(_ *treatment.Experiment, v *treatment.Evidence) { v.Hosts[0].RetransmissionRatio = .2 }}, + {"control harm", treatment.OutcomeHarmful, func(_ *treatment.Experiment, v *treatment.Evidence) { + for i := range v.Hosts[4:] { + h := &v.Hosts[4+i] h.RttP95Ms = 3 } }}, - {"missing exposure", OutcomeAwaitingExposure, func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts[0].ExposedSockets = 0 }}, - {"stale", OutcomeStale, func(_ *pb.Experiment, v *pb.Evidence) { v.WindowEnd = timestamppb.New(now.Add(-3 * time.Minute)) }}, - {"missing data", OutcomeInsufficientData, func(_ *pb.Experiment, v *pb.Evidence) { v.Hosts[0].CollectionHealthy = false }}, - {"too early", OutcomeInsufficientDuration, func(_ *pb.Experiment, v *pb.Evidence) { v.WindowStart = timestamppb.New(now.Add(-5 * time.Minute)) }}, - {"no benefit", OutcomeInconclusive, func(_ *pb.Experiment, v *pb.Evidence) { - for _, h := range v.Hosts { + {"missing exposure", treatment.OutcomeAwaitingExposure, func(_ *treatment.Experiment, v *treatment.Evidence) { v.Hosts[0].ExposedSockets = 0 }}, + {"stale", treatment.OutcomeStale, func(_ *treatment.Experiment, v *treatment.Evidence) { v.WindowEnd = now.Add(-3 * time.Minute) }}, + {"missing data", treatment.OutcomeInsufficientData, func(_ *treatment.Experiment, v *treatment.Evidence) { v.Hosts[0].CollectionHealthy = false }}, + {"too early", treatment.OutcomeInsufficientDuration, func(_ *treatment.Experiment, v *treatment.Evidence) { v.WindowStart = now.Add(-5 * time.Minute) }}, + {"no benefit", treatment.OutcomeInconclusive, func(_ *treatment.Experiment, v *treatment.Evidence) { + for i := range v.Hosts { + h := &v.Hosts[i] h.GoodputBps = 1e8 } }}, } { t.Run(tc.name, func(t *testing.T) { - n := proto.Clone(e).(*pb.Experiment) - w := proto.Clone(v).(*pb.Evidence) + n := e.Clone() + w := v.Clone() tc.change(n, w) got, err := Evaluate(n, w, now) if err != nil || got.Outcome != tc.want { @@ -65,11 +64,13 @@ func TestHealthyEvidenceSoakRTTRegression(t *testing.T) { t.Run(tc.name, func(t *testing.T) { now := time.Now() e, v := fixtures.Experiment(now, treatment.DefaultPolicy()) - e.Policy.MaximumRttIncrease = tc.limit - for _, h := range e.Baseline.Hosts { + e.Policy.MaximumRTTIncrease = tc.limit + for i := range e.Baseline.Hosts { + h := &e.Baseline.Hosts[i] h.RttP95Ms = 5.305 } - for _, h := range v.Hosts { + for i := range v.Hosts { + h := &v.Hosts[i] h.RttP95Ms = 5.305 } v.Hosts[0].RttP95Ms, v.Hosts[0].RetransmissionRatio = tc.rtt, tc.retrans diff --git a/pkg/treatment/experiment/outcome.go b/pkg/treatment/experiment/outcome.go deleted file mode 100644 index f2c8ea006..000000000 --- a/pkg/treatment/experiment/outcome.go +++ /dev/null @@ -1,106 +0,0 @@ -package experiment - -import ( - "fmt" - - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" -) - -// Outcome is the domain result of evaluating evidence against a phase, or of -// drift monitoring a completed treatment. Values are numerically identical to -// xtcp_treatment.v1.Outcome so that Wire and OutcomeFromWire are checked -// casts; TestOutcomeMirrorsWire pins that correspondence. -type Outcome uint8 - -const ( - OutcomeUnspecified Outcome = iota - OutcomeBaseline - OutcomeAwaitingApplication - OutcomeAwaitingExposure - OutcomeInsufficientDuration - OutcomeInsufficientData - OutcomeInsufficientControls - OutcomeStale - OutcomeUnavailable - OutcomeInconclusive - OutcomeSafetyOnly - OutcomeFinalSafety - OutcomeBeneficial - OutcomeHarmful - OutcomeExpired - OutcomeStableObserved - OutcomeStableEvidenceIncomplete - OutcomeStableRegressionReviewRequired - OutcomeObserving - outcomeCount // sentinel -) - -var outcomeNames = [outcomeCount]string{ - "unspecified", - "baseline", - "awaiting_application", - "awaiting_exposure", - "insufficient_duration", - "insufficient_data", - "insufficient_controls", - "stale", - "unavailable", - "inconclusive", - "safety_only", - "final_safety", - "beneficial", - "harmful", - "expired", - "stable_observed", - "stable_evidence_incomplete", - "stable_regression_review_required", - "observing", -} - -func (o Outcome) String() string { - if o < outcomeCount { - return outcomeNames[o] - } - return fmt.Sprintf("outcome(%d)", uint8(o)) -} - -// Valid reports whether o is a known value. Unspecified is valid: it is the -// state of an experiment nothing has evaluated yet. -func (o Outcome) Valid() bool { return o < outcomeCount } - -// Promotable reports whether a reviewer may advance the phase on this -// outcome. Every other outcome blocks promotion. -func (o Outcome) Promotable() bool { - return o == OutcomeBeneficial || o == OutcomeSafetyOnly || o == OutcomeFinalSafety -} - -// Withdraws reports whether the safety controller must roll the experiment -// back on this outcome without waiting for a reviewer. -func (o Outcome) Withdraws() bool { return o == OutcomeHarmful || o == OutcomeExpired } - -// RenewsLease reports whether healthy evidence with this outcome may renew -// the current phase's trial leases. Stale or missing data cannot. -func (o Outcome) RenewsLease() bool { return o != OutcomeStale && o != OutcomeInsufficientData } - -// ParseOutcome accepts the snake_case names used in documentation, logs and -// the pre-enum journal format. -func ParseOutcome(s string) (Outcome, error) { - for i, n := range outcomeNames { - if n == s { - return Outcome(i), nil - } - } - return OutcomeUnspecified, fmt.Errorf("unknown outcome %q", s) -} - -// Wire converts to the protobuf enum. -func (o Outcome) Wire() pb.Outcome { return pb.Outcome(o) } - -// OutcomeFromWire converts from the protobuf enum, rejecting values this -// build does not know so a newer peer cannot smuggle an unclassified result. -func OutcomeFromWire(w pb.Outcome) (Outcome, error) { - if w < 0 || int(w) >= int(outcomeCount) { - return OutcomeUnspecified, fmt.Errorf("unknown wire outcome %d", int32(w)) - } - return Outcome(w), nil -} diff --git a/pkg/treatment/experiment/outcome_test.go b/pkg/treatment/experiment/outcome_test.go deleted file mode 100644 index c54db8980..000000000 --- a/pkg/treatment/experiment/outcome_test.go +++ /dev/null @@ -1,90 +0,0 @@ -package experiment - -import ( - "strings" - "testing" - - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" -) - -// TestOutcomeMirrorsWire pins the one-to-one correspondence between the Go -// Outcome constants and xtcp_treatment.v1.Outcome that Wire and -// OutcomeFromWire rely on being a plain cast. -func TestOutcomeMirrorsWire(t *testing.T) { - if len(pb.Outcome_name) != int(outcomeCount) { - t.Fatalf("proto defines %d outcomes, Go defines %d", len(pb.Outcome_name), outcomeCount) - } - for o := OutcomeUnspecified; o < outcomeCount; o++ { - name := o.String() - if name == "" || strings.HasPrefix(name, "outcome(") { - t.Errorf("Outcome(%d) has no name", uint8(o)) - } - if wire := pb.Outcome_name[int32(o)]; wire != "OUTCOME_"+strings.ToUpper(name) { - t.Errorf("Outcome %s mirrors wire value %q", name, wire) - } - if parsed, err := ParseOutcome(name); err != nil || parsed != o { - t.Errorf("ParseOutcome(%q)=%s,%v", name, parsed, err) - } - if back, err := OutcomeFromWire(o.Wire()); err != nil || back != o { - t.Errorf("OutcomeFromWire(Wire(%s))=%s,%v", name, back, err) - } - if !o.Valid() { - t.Errorf("%s must be valid", name) - } - } -} - -func TestOutcomeConversions(t *testing.T) { - for _, tc := range []struct { - name, description string - run func() (Outcome, error) - expected Outcome - expectError bool - }{ - {"parse-beneficial", "positive: documented name parses", func() (Outcome, error) { return ParseOutcome("beneficial") }, OutcomeBeneficial, false}, - {"wire-observing", "positive: the newest wire value converts", func() (Outcome, error) { return OutcomeFromWire(pb.Outcome_OUTCOME_OBSERVING) }, OutcomeObserving, false}, - {"parse-legacy-free-text", "negative: a free string from the pre-enum format is an error, not unspecified", func() (Outcome, error) { return ParseOutcome("promoted") }, OutcomeUnspecified, true}, - {"parse-wire-spelling", "negative: the proto spelling is not accepted by the domain parser", func() (Outcome, error) { return ParseOutcome("OUTCOME_BENEFICIAL") }, OutcomeUnspecified, true}, - {"parse-empty", "boundary: empty string is an error", func() (Outcome, error) { return ParseOutcome("") }, OutcomeUnspecified, true}, - {"zero-value", "boundary: the zero wire value is the explicit unspecified outcome", func() (Outcome, error) { return OutcomeFromWire(0) }, OutcomeUnspecified, false}, - {"wire-count-sentinel", "boundary: the first undefined wire value is rejected", func() (Outcome, error) { return OutcomeFromWire(pb.Outcome(outcomeCount)) }, OutcomeUnspecified, true}, - {"wire-high", "corner: a value from a newer peer is rejected with its number", func() (Outcome, error) { return OutcomeFromWire(255) }, OutcomeUnspecified, true}, - {"wire-negative", "corner: negative int32 enum values are rejected", func() (Outcome, error) { return OutcomeFromWire(-1) }, OutcomeUnspecified, true}, - } { - t.Run(tc.name, func(t *testing.T) { - got, err := tc.run() - if (err != nil) != tc.expectError || got != tc.expected { - t.Fatalf("%s: got %s err=%v; expected %s error=%t", tc.description, got, err, tc.expected, tc.expectError) - } - }) - } -} - -func TestOutcomeClassification(t *testing.T) { - for _, tc := range []struct { - description string - outcome Outcome - promotable bool - withdraws bool - renews bool - valid bool - str string - }{ - {"positive: beneficial promotes and renews", OutcomeBeneficial, true, false, true, true, "beneficial"}, - {"positive: safety-only promotes small cohorts", OutcomeSafetyOnly, true, false, true, true, "safety_only"}, - {"positive: final safety promotes the 100% phase", OutcomeFinalSafety, true, false, true, true, "final_safety"}, - {"negative: harmful withdraws and never promotes", OutcomeHarmful, false, true, true, true, "harmful"}, - {"negative: expired withdraws", OutcomeExpired, false, true, true, true, "expired"}, - {"negative: stale evidence cannot renew a lease", OutcomeStale, false, false, false, true, "stale"}, - {"negative: missing data cannot renew a lease", OutcomeInsufficientData, false, false, false, true, "insufficient_data"}, - {"boundary: zero value is valid, inert, and renews nothing it decides", OutcomeUnspecified, false, false, true, true, "unspecified"}, - {"boundary: last defined value", OutcomeObserving, false, false, true, true, "observing"}, - {"corner: sentinel is invalid and prints its number", outcomeCount, false, false, true, false, "outcome(19)"}, - {"corner: stable regression is a review flag, not an automatic withdrawal", OutcomeStableRegressionReviewRequired, false, false, true, true, "stable_regression_review_required"}, - } { - o := tc.outcome - if o.Promotable() != tc.promotable || o.Withdraws() != tc.withdraws || o.RenewsLease() != tc.renews || o.Valid() != tc.valid || o.String() != tc.str { - t.Errorf("%s: promotable=%t withdraws=%t renews=%t valid=%t str=%q", tc.description, o.Promotable(), o.Withdraws(), o.RenewsLease(), o.Valid(), o.String()) - } - } -} diff --git a/pkg/treatment/experiment/statemachine.go b/pkg/treatment/experiment/statemachine.go index a051580ec..421320f4c 100644 --- a/pkg/treatment/experiment/statemachine.go +++ b/pkg/treatment/experiment/statemachine.go @@ -4,7 +4,7 @@ import ( "errors" "fmt" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment" ) // Event is something that can change an experiment's status. Review, Control @@ -62,37 +62,28 @@ func Events() []Event { return out } -// Statuses lists every defined status, for exhaustive tests. -func Statuses() []pb.ExperimentStatus { - out := make([]pb.ExperimentStatus, 0, len(pb.ExperimentStatus_name)) - for i := range int32(len(pb.ExperimentStatus_name)) { - out = append(out, pb.ExperimentStatus(i)) - } - return out -} - // ErrIllegalTransition is returned for any (status, event) pair not in the // table. Callers map it to FailedPrecondition. var ErrIllegalTransition = errors.New("illegal experiment transition") type transitionKey struct { - from pb.ExperimentStatus + from treatment.Status event Event } const ( - proposed = pb.ExperimentStatus_EXPERIMENT_STATUS_PROPOSED - running = pb.ExperimentStatus_EXPERIMENT_STATUS_RUNNING - paused = pb.ExperimentStatus_EXPERIMENT_STATUS_PAUSED - suspended = pb.ExperimentStatus_EXPERIMENT_STATUS_SUSPENDED - complete = pb.ExperimentStatus_EXPERIMENT_STATUS_COMPLETE - retired = pb.ExperimentStatus_EXPERIMENT_STATUS_RETIRED - rolledBack = pb.ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK + proposed = treatment.StatusProposed + running = treatment.StatusRunning + paused = treatment.StatusPaused + suspended = treatment.StatusSuspended + complete = treatment.StatusComplete + retired = treatment.StatusRetired + rolledBack = treatment.StatusRolledBack ) // transitions is the whole state machine. Retired and rolled back are // terminal: nothing reactivates them. A new experiment is proposed instead. -var transitions = map[transitionKey]pb.ExperimentStatus{ +var transitions = map[transitionKey]treatment.Status{ {proposed, EventApprove}: running, {proposed, EventReject}: retired, {proposed, EventSuspend}: suspended, @@ -132,15 +123,10 @@ var transitions = map[transitionKey]pb.ExperimentStatus{ // Transition returns the status that follows from applying event to from, or // ErrIllegalTransition. It is pure; callers bump the revision and persist. -func Transition(from pb.ExperimentStatus, event Event) (pb.ExperimentStatus, error) { +func Transition(from treatment.Status, event Event) (treatment.Status, error) { to, ok := transitions[transitionKey{from, event}] if !ok { return from, fmt.Errorf("%w: %s on %s", ErrIllegalTransition, event, from) } return to, nil } - -// Terminal reports whether no event can move the experiment again. -func Terminal(s pb.ExperimentStatus) bool { - return s == retired || s == rolledBack -} diff --git a/pkg/treatment/experiment/statemachine_test.go b/pkg/treatment/experiment/statemachine_test.go index 026f76c7b..b02e542ec 100644 --- a/pkg/treatment/experiment/statemachine_test.go +++ b/pkg/treatment/experiment/statemachine_test.go @@ -4,12 +4,12 @@ import ( "errors" "testing" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment" ) // legal is the expected state machine written out independently of the // implementation table. A pair missing here must return ErrIllegalTransition. -var legal = map[pb.ExperimentStatus]map[Event]pb.ExperimentStatus{ +var legal = map[treatment.Status]map[Event]treatment.Status{ proposed: { EventApprove: running, EventReject: retired, @@ -51,18 +51,18 @@ var legal = map[pb.ExperimentStatus]map[Event]pb.ExperimentStatus{ EventRollback: rolledBack, EventRetire: retired, }, - retired: {}, - rolledBack: {}, - pb.ExperimentStatus_EXPERIMENT_STATUS_UNSPECIFIED: {}, + retired: {}, + rolledBack: {}, + treatment.StatusUnspecified: {}, } -// TestTransitionExhaustive visits every (status, event) pair the generated -// enums and Events() can produce and checks it against the legal table, so +// TestTransitionExhaustive visits every (status, event) pair treatment.Statuses() +// and Events() can produce and checks it against the legal table, so // adding a status, an event or a transition without updating both fails. func TestTransitionExhaustive(t *testing.T) { - statuses, events := Statuses(), Events() + statuses, events := treatment.Statuses(), Events() if len(statuses) != len(legal) { - t.Fatalf("legal table covers %d statuses, proto defines %d", len(legal), len(statuses)) + t.Fatalf("legal table covers %d statuses, the domain defines %d", len(legal), len(statuses)) } if len(events) != int(eventCount)-1 { t.Fatalf("Events() returned %d, want %d", len(events), eventCount-1) @@ -101,9 +101,9 @@ func TestTransitionExhaustive(t *testing.T) { func TestTransitionCases(t *testing.T) { for _, tc := range []struct { name, description string - from pb.ExperimentStatus + from treatment.Status event Event - expected pb.ExperimentStatus + expected treatment.Status expectError bool }{ {"proposed-approve", "positive: first review starts the trial", proposed, EventApprove, running, false}, @@ -114,7 +114,7 @@ func TestTransitionCases(t *testing.T) { {"rolled-back-resume", "negative: rollback is final", rolledBack, EventResume, rolledBack, true}, {"proposed-pause", "negative: nothing to pause before the first phase", proposed, EventPause, proposed, true}, {"complete-expire", "negative: completed treatments have no lease to expire", complete, EventExpire, complete, true}, - {"unspecified-status", "boundary: zero status accepts no event", pb.ExperimentStatus_EXPERIMENT_STATUS_UNSPECIFIED, EventApprove, pb.ExperimentStatus_EXPERIMENT_STATUS_UNSPECIFIED, true}, + {"unspecified-status", "boundary: zero status accepts no event", treatment.StatusUnspecified, EventApprove, treatment.StatusUnspecified, true}, {"unspecified-event", "boundary: zero event is never legal", running, eventUnspecified, running, true}, {"event-count-sentinel", "boundary: the sentinel just past the last event is illegal", running, eventCount, running, true}, {"unknown-event", "corner: an event value from a newer build is rejected, not defaulted", running, Event(200), running, true}, @@ -134,9 +134,9 @@ func TestTransitionCases(t *testing.T) { } func TestTerminal(t *testing.T) { - for _, s := range Statuses() { - got := Terminal(s) - expected := len(legal[s]) == 0 && s != pb.ExperimentStatus_EXPERIMENT_STATUS_UNSPECIFIED + for _, s := range treatment.Statuses() { + got := s.Terminal() + expected := len(legal[s]) == 0 && s != treatment.StatusUnspecified if got != expected { t.Errorf("Terminal(%s)=%t, expected %t", s, got, expected) } diff --git a/pkg/treatment/internal/fixtures/fixtures.go b/pkg/treatment/internal/fixtures/fixtures.go index d6752fc7b..bf152f4c9 100644 --- a/pkg/treatment/internal/fixtures/fixtures.go +++ b/pkg/treatment/internal/fixtures/fixtures.go @@ -1,57 +1,69 @@ -// Package fixtures holds protobuf test fixtures shared by the treatment -// packages' tests. It imports only generated types so that the contract -// package's own tests can use it without an import cycle. +// Package fixtures holds test fixtures shared by the treatment packages' +// tests, in domain form and, through package wire, in protobuf form for the +// code that still keeps messages at rest (the agent's journal). package fixtures import ( "fmt" + "net/netip" "strings" "time" pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/timestamppb" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/wire" ) +func ptr[T any](v T) *T { return &v } + +// Treatment is CUBIC on a single /32. +func Treatment() treatment.Treatment { + return treatment.Treatment{ID: "cubic", Revision: 1, Destination: netip.MustParsePrefix("192.0.2.1/32"), Settings: treatment.TCPSettings{CongestionControl: ptr("cubic")}} +} + // Desired is a minimal valid desired state for agent host-1: one trial // assignment of CUBIC to a single /32 with a one-hour lease. -func Desired(now time.Time) *pb.DesiredState { - return &pb.DesiredState{ - AgentId: "host-1", +func Desired(now time.Time) *treatment.DesiredState { + return &treatment.DesiredState{ + AgentID: "host-1", Revision: 1, - IssuedAt: timestamppb.New(now), - Assignments: []*pb.Assignment{{ - ExperimentId: "trial-1", + IssuedAt: now, + Assignments: []treatment.Assignment{{ + ExperimentID: "trial-1", Generation: 1, - Mode: pb.AssignmentMode_ASSIGNMENT_MODE_TRIAL, - LeaseExpiresAt: timestamppb.New(now.Add(time.Hour)), - Treatment: &pb.Treatment{ - Id: "cubic", - Revision: 1, - Destination: "192.0.2.1/32", - Settings: &pb.TCPSettings{CongestionControl: proto.String("cubic")}, - }, + Mode: treatment.ModeTrial, + LeaseExpiresAt: now.Add(time.Hour), + Treatment: Treatment(), }}, } } +// DesiredWire is Desired in protobuf form. +func DesiredWire(now time.Time) *pb.DesiredState { return wire.Desired(Desired(now)) } + // Experiment is a running 16-host experiment at its first 4-host phase with // baseline and a current evidence window in which the treated hosts gained 30%. -func Experiment(now time.Time, policy *pb.ExperimentPolicy) (*pb.Experiment, *pb.Evidence) { - b := &pb.Evidence{Id: "baseline", AnalyzedAt: timestamppb.New(now.Add(-time.Hour)), WindowStart: timestamppb.New(now.Add(-2 * time.Hour)), WindowEnd: timestamppb.New(now.Add(-time.Hour)), QueryVersion: "v1", ArtifactSha256: strings.Repeat("a", 64)} - e := &pb.Experiment{Id: "exp", Location: "dc1", Hypothesis: "cubic improves throughput", Treatment: Desired(now).Assignments[0].Treatment, Policy: policy, Baseline: b, Status: pb.ExperimentStatus_EXPERIMENT_STATUS_RUNNING, Revision: 2, Phases: []*pb.Phase{{TargetHosts: 4, IssuedAt: timestamppb.New(now.Add(-40 * time.Minute)), ObservationStartedAt: timestamppb.New(now.Add(-35 * time.Minute))}}} +func Experiment(now time.Time, policy treatment.Policy) (*treatment.Experiment, *treatment.Evidence) { + b := &treatment.Evidence{ID: "baseline", AnalyzedAt: now.Add(-time.Hour), WindowStart: now.Add(-2 * time.Hour), WindowEnd: now.Add(-time.Hour), QueryVersion: "v1", ArtifactSHA256: strings.Repeat("a", 64)} + e := &treatment.Experiment{ID: "exp", Location: "dc1", Hypothesis: "cubic improves throughput", Treatment: Treatment(), Policy: policy, Baseline: b, Status: treatment.StatusRunning, Revision: 2, Phases: []treatment.Phase{{TargetHosts: 4, IssuedAt: now.Add(-40 * time.Minute), ObservationStartedAt: now.Add(-35 * time.Minute)}}} for i := range 16 { id := fmt.Sprintf("host-%d", i) e.CohortOrder = append(e.CohortOrder, id) - b.Hosts = append(b.Hosts, &pb.HostEvidence{AgentId: id, Sockets: 100, ExposedSockets: 100, BytesAcked: 1 << 25, GoodputBps: 1e8, RttP95Ms: 1, CollectionHealthy: true}) + b.Hosts = append(b.Hosts, treatment.HostEvidence{AgentID: id, Sockets: 100, ExposedSockets: 100, BytesAcked: 1 << 25, GoodputBps: 1e8, RttP95Ms: 1, CollectionHealthy: true}) } - ev := proto.Clone(b).(*pb.Evidence) - ev.Id = "after" - ev.WindowStart = timestamppb.New(now.Add(-34 * time.Minute)) - ev.WindowEnd = timestamppb.New(now.Add(-time.Second)) - ev.AnalyzedAt = timestamppb.New(now) - for _, h := range ev.Hosts[:4] { - h.GoodputBps *= 1.3 + ev := b.Clone() + ev.ID = "after" + ev.WindowStart = now.Add(-34 * time.Minute) + ev.WindowEnd = now.Add(-time.Second) + ev.AnalyzedAt = now + for i := range ev.Hosts[:4] { + ev.Hosts[i].GoodputBps *= 1.3 } return e, ev } + +// ExperimentWire is Experiment in protobuf form. +func ExperimentWire(now time.Time, policy treatment.Policy) (*pb.Experiment, *pb.Evidence) { + e, v := Experiment(now, policy) + return wire.Experiment(e), wire.Evidence(v) +} diff --git a/pkg/treatment/model.go b/pkg/treatment/model.go new file mode 100644 index 000000000..6d600c688 --- /dev/null +++ b/pkg/treatment/model.go @@ -0,0 +1,265 @@ +package treatment + +import ( + "net/netip" + "slices" + "time" +) + +// The types in this file are the controller's domain model. The protobuf +// messages in gen/go/xtcp_treatment are a wire format for them (see package +// wire) and the PostgreSQL tables are a storage format for them (see +// controller/store/postgres); neither is the model. Optional fields are +// pointers so "unset" survives every conversion. A zero time.Time means a +// timestamp was not set. + +// TCPSettings are the per-route socket parameters a treatment applies. +type TCPSettings struct { + CongestionControl *string + CongestionLock bool + RTOMinMs *uint32 + InitialCongestionWindow *uint32 + InitialReceiveWindow *uint32 + QuickAck *bool + ECN *bool +} + +// Empty reports whether no parameter is set. +func (s TCPSettings) Empty() bool { + return s.CongestionControl == nil && s.RTOMinMs == nil && s.InitialCongestionWindow == nil && s.InitialReceiveWindow == nil && s.QuickAck == nil && s.ECN == nil +} + +// Algorithm returns the congestion control name or "" when unset. +func (s TCPSettings) Algorithm() string { + if s.CongestionControl == nil { + return "" + } + return *s.CongestionControl +} + +// Equal compares every parameter including whether it is set. +func (s TCPSettings) Equal(o TCPSettings) bool { + return s.CongestionLock == o.CongestionLock && eqPtr(s.CongestionControl, o.CongestionControl) && eqPtr(s.RTOMinMs, o.RTOMinMs) && eqPtr(s.InitialCongestionWindow, o.InitialCongestionWindow) && eqPtr(s.InitialReceiveWindow, o.InitialReceiveWindow) && eqPtr(s.QuickAck, o.QuickAck) && eqPtr(s.ECN, o.ECN) +} + +func eqPtr[T comparable](a, b *T) bool { + if a == nil || b == nil { + return a == b + } + return *a == *b +} + +// Treatment is a versioned set of TCP settings for one host destination. +type Treatment struct { + ID string + Revision uint64 + // Destination is a /32 or /128 host prefix; the zero value is unset. + Destination netip.Prefix + Settings TCPSettings + ECNFabricVerified bool +} + +// Equal is a field-wise comparison. +func (t Treatment) Equal(o Treatment) bool { + return t.ID == o.ID && t.Revision == o.Revision && t.Destination == o.Destination && t.ECNFabricVerified == o.ECNFabricVerified && t.Settings.Equal(o.Settings) +} + +// Assignment binds a treatment to one agent on behalf of one experiment. +type Assignment struct { + ExperimentID string + Treatment Treatment + Mode AssignmentMode + // LeaseExpiresAt is zero for assignments without a lease. + LeaseExpiresAt time.Time + // Predecessor is the stable treatment this one replaced, restored on + // rollback. Nil when the destination had none. + Predecessor *Treatment + Generation uint64 +} + +// DesiredState is everything the controller wants one agent to hold. +type DesiredState struct { + AgentID string + Revision uint64 + Suspended bool + Assignments []Assignment + IssuedAt time.Time +} + +// Find returns a pointer into d.Assignments for the experiment, or nil. +func (d *DesiredState) Find(experimentID string) *Assignment { + for i := range d.Assignments { + if d.Assignments[i].ExperimentID == experimentID { + return &d.Assignments[i] + } + } + return nil +} + +// Capabilities are the facts an agent reports about its host. +type Capabilities struct { + BootID string + KernelRelease string + CongestionAlgorithms []string + SimpleHostRouting bool + ActuatorEnabled bool +} + +// Supports reports whether the host offers the congestion algorithm. +func (c Capabilities) Supports(algorithm string) bool { + return slices.Contains(c.CongestionAlgorithms, algorithm) +} + +// ApplicationRecord is an agent's report of what it did with one assignment. +type ApplicationRecord struct { + Assignment Assignment + DesiredRevision uint64 + Status ApplyStatus + ReceivedAt time.Time + AttemptedAt time.Time + AppliedAt time.Time + VerifiedAt time.Time + RemovedAt time.Time + Error string + ExpiredLatched bool +} + +// AgentReport is one check-in: identity, capabilities and the application +// records for every assignment the agent knows about. +type AgentReport struct { + AgentID string + Location string + Capabilities Capabilities + DesiredRevision uint64 + Records []ApplicationRecord + LocallySuspended bool +} + +// Record returns the record for the experiment, or nil. +func (r *AgentReport) Record(experimentID string) *ApplicationRecord { + for i := range r.Records { + if r.Records[i].Assignment.ExperimentID == experimentID { + return &r.Records[i] + } + } + return nil +} + +// HostEvidence is one host's aggregate for one evidence window. +type HostEvidence struct { + AgentID string + Sockets uint64 + BytesAcked uint64 + GoodputBps float64 + RetransmissionRatio float64 + RttP95Ms float64 + ExposedSockets uint64 + CollectionHealthy bool +} + +// Evidence is an immutable per-host measurement set for a window. +type Evidence struct { + ID string + AnalyzedAt time.Time + WindowStart time.Time + WindowEnd time.Time + QueryVersion string + EnrichmentVersion string + Hosts []HostEvidence + ArtifactSHA256 string +} + +// Clone copies the host slice so the caller can mutate independently. +func (e *Evidence) Clone() *Evidence { + if e == nil { + return nil + } + c := *e + c.Hosts = slices.Clone(e.Hosts) + return &c +} + +// Equal compares every field and every host in order. +func (e *Evidence) Equal(o *Evidence) bool { + if e == nil || o == nil { + return e == o + } + return e.ID == o.ID && e.AnalyzedAt.Equal(o.AnalyzedAt) && e.WindowStart.Equal(o.WindowStart) && e.WindowEnd.Equal(o.WindowEnd) && e.QueryVersion == o.QueryVersion && e.EnrichmentVersion == o.EnrichmentVersion && e.ArtifactSHA256 == o.ArtifactSHA256 && slices.Equal(e.Hosts, o.Hosts) +} + +// Policy bounds how an experiment is run and judged. +type Policy struct { + MinimumObservation time.Duration + MaximumPhase time.Duration + MaximumEvidenceAge time.Duration + MinimumSocketsPerHost uint64 + MinimumBytesPerHost uint64 + MinimumTreatedHosts uint32 + MinimumImprovement float64 + MaximumRetransmissionIncrease float64 + MaximumRTTIncrease float64 +} + +// Phase is one step of exposure: the first TargetHosts of the cohort order. +type Phase struct { + TargetHosts uint32 + IssuedAt time.Time + ObservationStartedAt time.Time + EvaluatedAt time.Time + Evidence *Evidence + Outcome Outcome + Reviewer string + Reason string +} + +// Experiment is a reviewed, phased rollout of one treatment in one location. +type Experiment struct { + ID string + Location string + Hypothesis string + Treatment Treatment + Policy Policy + Baseline *Evidence + CohortOrder []string + Phases []Phase + Status Status + Revision uint64 + ProposedAt time.Time + LatestEvidence *Evidence + LatestOutcome Outcome + // UpdatedAt is when the controller last changed the experiment. The + // discovery cooldown counts from it for terminal experiments. + UpdatedAt time.Time +} + +// Clone deep-copies the experiment so pure functions can return a new value. +func (e *Experiment) Clone() *Experiment { + if e == nil { + return nil + } + c := *e + c.Baseline = e.Baseline.Clone() + c.LatestEvidence = e.LatestEvidence.Clone() + c.CohortOrder = slices.Clone(e.CohortOrder) + c.Phases = slices.Clone(e.Phases) + for i := range c.Phases { + c.Phases[i].Evidence = e.Phases[i].Evidence.Clone() + } + return &c +} + +// CurrentPhase returns a pointer to the last phase, or nil before review. +func (e *Experiment) CurrentPhase() *Phase { + if len(e.Phases) == 0 { + return nil + } + return &e.Phases[len(e.Phases)-1] +} + +// Target is the number of treated hosts in the current phase, zero before review. +func (e *Experiment) Target() int { + if p := e.CurrentPhase(); p != nil { + return int(p.TargetHosts) + } + return 0 +} diff --git a/pkg/treatment/reconcile/contracts_test.go b/pkg/treatment/reconcile/contracts_test.go index 395f15bed..44faecb3b 100644 --- a/pkg/treatment/reconcile/contracts_test.go +++ b/pkg/treatment/reconcile/contracts_test.go @@ -52,7 +52,7 @@ func TestReconcileFailureRecovery(t *testing.T) { now := time.Unix(1700000000, 0) b := &faultBackend{fakeBackend: &fakeBackend{routes: map[string]bool{}}} r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot", Now: func() time.Time { return now }} - d := fixtures.Desired(now) + d := fixtures.DesiredWire(now) if tc.name == "plan" { if _, err := r.Run(context.Background(), d, ModeFull); err != nil { t.Fatal(err) @@ -88,7 +88,7 @@ func TestReconcileRevisionContracts(t *testing.T) { now := time.Unix(1700000000, 0) b := &fakeBackend{routes: map[string]bool{}} r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot", Now: func() time.Time { return now }} - d := fixtures.Desired(now) + d := fixtures.DesiredWire(now) s, err := r.Run(context.Background(), d, ModeFull) if err != nil { t.Fatal(err) @@ -133,7 +133,7 @@ func TestReconcileLeaseBoundary(t *testing.T) { now := time.Unix(1700000000, 0) b := &fakeBackend{routes: map[string]bool{}} r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot", Now: func() time.Time { return now }} - d := fixtures.Desired(now) + d := fixtures.DesiredWire(now) d.Assignments[0].LeaseExpiresAt = timestamppb.New(now.Add(tc.offset)) s, err := r.Run(context.Background(), d, ModeFull) if err != nil || s.Records[0].Status != tc.want || len(b.routes) != tc.routes { diff --git a/pkg/treatment/reconcile/journal.go b/pkg/treatment/reconcile/journal.go index eb712f8e9..36035af49 100644 --- a/pkg/treatment/reconcile/journal.go +++ b/pkg/treatment/reconcile/journal.go @@ -10,6 +10,7 @@ import ( pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/wire" "golang.org/x/sys/unix" "google.golang.org/protobuf/encoding/prototext" ) @@ -50,7 +51,7 @@ func (j Journal) WithLock(fn func(*pb.LocalState, func() error) error) error { return fmt.Errorf("invalid treatment journal: %w", err) } if s.Desired != nil { - if err = treatment.ValidateDesired(s.Desired); err != nil { + if err = wire.ValidateDesired(s.Desired); err != nil { return fmt.Errorf("invalid persisted desired state: %w", err) } } @@ -66,7 +67,7 @@ func (j Journal) WithLock(fn func(*pb.LocalState, func() error) error) error { return errors.New("duplicate journal record") } seen[r.Assignment.ExperimentId] = true - if err = treatment.ValidateTreatment(r.Assignment.Treatment); err != nil { + if err = wire.ValidateTreatment(r.Assignment.Treatment); err != nil { return fmt.Errorf("invalid journal treatment: %w", err) } } diff --git a/pkg/treatment/reconcile/journal_test.go b/pkg/treatment/reconcile/journal_test.go index 195c94fab..dd4b5c01b 100644 --- a/pkg/treatment/reconcile/journal_test.go +++ b/pkg/treatment/reconcile/journal_test.go @@ -30,7 +30,7 @@ func TestJournalRejectsInvalidSnapshots(t *testing.T) { } { t.Run(tc.name, func(t *testing.T) { j := Journal{Dir: t.TempDir()} - d := fixtures.Desired(time.Unix(1700000000, 0)) + d := fixtures.DesiredWire(time.Unix(1700000000, 0)) s := &pb.LocalState{Desired: d, Records: []*pb.ApplicationRecord{{Assignment: d.Assignments[0]}}} tc.change(s) raw, err := prototext.Marshal(s) diff --git a/pkg/treatment/reconcile/reconcile.go b/pkg/treatment/reconcile/reconcile.go index 2d5e9203b..3f6ce58ec 100644 --- a/pkg/treatment/reconcile/reconcile.go +++ b/pkg/treatment/reconcile/reconcile.go @@ -8,7 +8,7 @@ import ( "time" pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" - "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/wire" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -88,7 +88,7 @@ func (r *Reconciler) Run(ctx context.Context, in *pb.DesiredState, mode Mode) (* if expiryOnly { return errors.New("watchdog cannot accept desired state") } - if err := treatment.ValidateDesired(in); err != nil { + if err := wire.ValidateDesired(in); err != nil { return err } if in.AgentId != r.AgentID { diff --git a/pkg/treatment/reconcile/reconcile_test.go b/pkg/treatment/reconcile/reconcile_test.go index 9d1c324e1..ebb6c0c08 100644 --- a/pkg/treatment/reconcile/reconcile_test.go +++ b/pkg/treatment/reconcile/reconcile_test.go @@ -54,7 +54,7 @@ func TestFailedWithdrawalDisablesOwnedLookup(t *testing.T) { now := time.Now() b := &fakeBackend{routes: map[string]bool{}} r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot-1", Now: func() time.Time { return now }} - if _, e := r.Run(context.Background(), fixtures.Desired(now), ModeFull); e != nil { + if _, e := r.Run(context.Background(), fixtures.DesiredWire(now), ModeFull); e != nil { t.Fatal(e) } if !b.enabled { @@ -81,7 +81,7 @@ func TestStableRouteSurvivesOfflineReboot(t *testing.T) { now := time.Now() b := &fakeBackend{routes: map[string]bool{}} r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot-1", Now: func() time.Time { return now }} - d := fixtures.Desired(now) + d := fixtures.DesiredWire(now) d.Assignments[0].Mode = pb.AssignmentMode_ASSIGNMENT_MODE_STABLE d.Assignments[0].LeaseExpiresAt = nil if _, e := r.Run(context.Background(), d, ModeFull); e != nil { @@ -110,7 +110,7 @@ func TestJournalIntentPrecedesKernelAndCrashRecovery(t *testing.T) { t.Fatal("intent not durable before install", e) } } - s, e := r.Run(context.Background(), fixtures.Desired(now), ModeFull) + s, e := r.Run(context.Background(), fixtures.DesiredWire(now), ModeFull) if e == nil || s.Records[0].Status != pb.ApplyStatus_APPLY_STATUS_FAILED { t.Fatal("must report readback failure", s, e) } @@ -128,7 +128,7 @@ func TestTrialExpiryCannotBeResurrectedByRenewalOrRestart(t *testing.T) { now := time.Now() b := &fakeBackend{routes: map[string]bool{}} r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot-1", Now: func() time.Time { return now }} - d := fixtures.Desired(now) + d := fixtures.DesiredWire(now) if _, e := r.Run(context.Background(), d, ModeFull); e != nil { t.Fatal(e) } @@ -159,7 +159,7 @@ func TestPersistentHoldAndTombstones(t *testing.T) { now := time.Now() b := &fakeBackend{routes: map[string]bool{}} r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot-1"} - d := fixtures.Desired(now) + d := fixtures.DesiredWire(now) d.Assignments[0].Mode = pb.AssignmentMode_ASSIGNMENT_MODE_STABLE if _, e := r.Run(context.Background(), d, ModeFull); e != nil { t.Fatal(e) @@ -207,7 +207,7 @@ func TestCorruptJournalDoesNotInstall(t *testing.T) { } b := &fakeBackend{routes: map[string]bool{}} r := &Reconciler{Journal: Journal{Dir: dir}, Backend: b, AgentID: "host-1"} - if _, e := r.Run(context.Background(), fixtures.Desired(time.Now()), ModeFull); e == nil { + if _, e := r.Run(context.Background(), fixtures.DesiredWire(time.Now()), ModeFull); e == nil { t.Fatal("corrupt journal overwritten") } if b.installs != 0 { @@ -219,7 +219,7 @@ func TestPredecessorExpiryAndGenerationRejectionPreserveJournal(t *testing.T) { now := time.Now() b := &fakeBackend{routes: map[string]bool{}} r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot-1", Now: func() time.Time { return now }} - d := fixtures.Desired(now) + d := fixtures.DesiredWire(now) a := d.Assignments[0] a.ExperimentId = "state" a.Generation = 2 diff --git a/pkg/treatment/validate.go b/pkg/treatment/validate.go index 685fa1808..7e3f9f2c9 100644 --- a/pkg/treatment/validate.go +++ b/pkg/treatment/validate.go @@ -1,5 +1,6 @@ -// Package treatment implements the controller's route-treatment contracts. -// Validation is shared by RPC handlers, local journals, and direct callers. +// Package treatment is the route-treatment domain: the model, its +// enumerations, and the validation rules shared by the controller, the agent +// and the watchdog. It has no I/O and does not import the wire protocol. package treatment import ( @@ -9,9 +10,6 @@ import ( "net/netip" "regexp" "time" - - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" - "google.golang.org/protobuf/types/known/durationpb" ) const MaxAssignments = 4096 @@ -24,35 +22,37 @@ var identifier = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$`) func ValidID(s string) bool { return identifier.MatchString(s) } -func ValidateTreatment(t *pb.Treatment) error { - if t == nil || !ValidID(t.Id) || t.Revision == 0 || t.Settings == nil { +// ValidDestination reports whether p is a canonical, globally routable /32 +// or /128 host prefix. +func ValidDestination(p netip.Prefix) bool { + return p.IsValid() && p == p.Masked() && p.Bits() == p.Addr().BitLen() && !p.Addr().Is4In6() && p.Addr().IsGlobalUnicast() && !p.Addr().IsLoopback() +} + +func ValidateTreatment(t *Treatment) error { + if t == nil || !ValidID(t.ID) || t.Revision == 0 { return errors.New("treatment requires id, revision and settings") } - p, err := netip.ParsePrefix(t.Destination) - if err != nil || p != p.Masked() || p.Bits() != p.Addr().BitLen() || p.Addr().Is4In6() || !p.Addr().IsGlobalUnicast() || p.Addr().IsLoopback() { + if !ValidDestination(t.Destination) { return errors.New("destination must be a canonical unicast /32 or /128 host prefix") } - return ValidateTCPSettings(t.Settings, t.EcnFabricVerified) + return ValidateTCPSettings(t.Settings, t.ECNFabricVerified) } // ValidateTCPSettings also validates discovery templates before querying traffic. -func ValidateTCPSettings(s *pb.TCPSettings, ecnFabricVerified bool) error { - if s == nil { - return errors.New("TCP settings required") - } +func ValidateTCPSettings(s TCPSettings, ecnFabricVerified bool) error { if s.CongestionControl != nil && !identifier.MatchString(*s.CongestionControl) { return errors.New("invalid congestion algorithm") } - if len(s.GetCongestionControl()) > 15 { + if len(s.Algorithm()) > 15 { return errors.New("congestion algorithm exceeds kernel name limit") } if s.CongestionLock && s.CongestionControl == nil { return errors.New("congestion_lock requires an algorithm") } - if s.GetCongestionControl() == "dctcp" && (!ecnFabricVerified || !s.GetEcn()) { + if s.Algorithm() == "dctcp" && (!ecnFabricVerified || s.ECN == nil || !*s.ECN) { return errors.New("DCTCP requires independently verified ECN fabric and explicit ECN") } - if s.RtoMinMs != nil && (*s.RtoMinMs < 1 || *s.RtoMinMs > 120000) { + if s.RTOMinMs != nil && (*s.RTOMinMs < 1 || *s.RTOMinMs > 120000) { return errors.New("rto_min_ms outside 1..120000") } for _, w := range []*uint32{s.InitialCongestionWindow, s.InitialReceiveWindow} { @@ -60,35 +60,36 @@ func ValidateTCPSettings(s *pb.TCPSettings, ecnFabricVerified bool) error { return errors.New("initial window outside 1..1024 segments") } } - if s.CongestionControl == nil && s.RtoMinMs == nil && s.InitialCongestionWindow == nil && s.InitialReceiveWindow == nil && s.QuickAck == nil && s.Ecn == nil { + if s.Empty() { return errors.New("empty treatment") } return nil } -func ValidateDesired(d *pb.DesiredState) error { - if d == nil || !ValidID(d.AgentId) || d.Revision == 0 || len(d.Assignments) > MaxAssignments || d.IssuedAt == nil || d.IssuedAt.CheckValid() != nil { +func ValidateDesired(d *DesiredState) error { + if d == nil || !ValidID(d.AgentID) || d.Revision == 0 || len(d.Assignments) > MaxAssignments || d.IssuedAt.IsZero() { return errors.New("invalid desired-state header") } - ids, destinations := map[string]bool{}, map[string]bool{} - for _, a := range d.Assignments { - if a == nil || !ValidID(a.ExperimentId) || a.Generation == 0 || ids[a.ExperimentId] { + ids, destinations := map[string]bool{}, map[netip.Prefix]bool{} + for i := range d.Assignments { + a := &d.Assignments[i] + if !ValidID(a.ExperimentID) || a.Generation == 0 || ids[a.ExperimentID] { return errors.New("invalid or duplicate assignment identity") } - ids[a.ExperimentId] = true - if err := ValidateTreatment(a.Treatment); err != nil { + ids[a.ExperimentID] = true + if err := ValidateTreatment(&a.Treatment); err != nil { return err } - if a.Mode < pb.AssignmentMode_ASSIGNMENT_MODE_TRIAL || a.Mode > pb.AssignmentMode_ASSIGNMENT_MODE_RETIRED { + if !a.Mode.Valid() { return errors.New("invalid assignment mode") } - if a.Mode != pb.AssignmentMode_ASSIGNMENT_MODE_RETIRED { + if a.Mode != ModeRetired { if destinations[a.Treatment.Destination] { return errors.New("overlapping treatments") } destinations[a.Treatment.Destination] = true } - if a.Mode == pb.AssignmentMode_ASSIGNMENT_MODE_TRIAL && (a.LeaseExpiresAt == nil || a.LeaseExpiresAt.CheckValid() != nil) { + if a.Mode == ModeTrial && a.LeaseExpiresAt.IsZero() { return errors.New("trial requires a valid expiry") } if a.Predecessor != nil { @@ -103,23 +104,20 @@ func ValidateDesired(d *pb.DesiredState) error { return nil } -func DefaultPolicy() *pb.ExperimentPolicy { - return &pb.ExperimentPolicy{MinimumObservation: durationpb.New(30 * time.Minute), MaximumPhase: durationpb.New(24 * time.Hour), MaximumEvidenceAge: durationpb.New(2 * time.Minute), MinimumSocketsPerHost: 30, MinimumBytesPerHost: 1 << 20, MinimumTreatedHosts: 4, MinimumImprovement: .05, MaximumRetransmissionIncrease: .005, MaximumRttIncrease: .1} +func DefaultPolicy() Policy { + return Policy{MinimumObservation: 30 * time.Minute, MaximumPhase: 24 * time.Hour, MaximumEvidenceAge: 2 * time.Minute, MinimumSocketsPerHost: 30, MinimumBytesPerHost: 1 << 20, MinimumTreatedHosts: 4, MinimumImprovement: .05, MaximumRetransmissionIncrease: .005, MaximumRTTIncrease: .1} } -func ValidatePolicy(p *pb.ExperimentPolicy) error { - if p == nil { - return errors.New("policy required") - } - for _, d := range []*durationpb.Duration{p.MinimumObservation, p.MaximumPhase, p.MaximumEvidenceAge} { - if d == nil || d.CheckValid() != nil || d.AsDuration() <= 0 || d.AsDuration() > 7*24*time.Hour { +func ValidatePolicy(p Policy) error { + for _, d := range []time.Duration{p.MinimumObservation, p.MaximumPhase, p.MaximumEvidenceAge} { + if d <= 0 || d > 7*24*time.Hour { return errors.New("invalid policy duration") } } - if p.MinimumObservation.AsDuration() > p.MaximumPhase.AsDuration() || p.MinimumSocketsPerHost == 0 || p.MinimumBytesPerHost == 0 || p.MinimumTreatedHosts < 2 || p.MinimumTreatedHosts > MaxAgents { + if p.MinimumObservation > p.MaximumPhase || p.MinimumSocketsPerHost == 0 || p.MinimumBytesPerHost == 0 || p.MinimumTreatedHosts < 2 || p.MinimumTreatedHosts > MaxAgents { return errors.New("invalid policy sample bounds") } - for _, v := range []float64{p.MinimumImprovement, p.MaximumRetransmissionIncrease, p.MaximumRttIncrease} { + for _, v := range []float64{p.MinimumImprovement, p.MaximumRetransmissionIncrease, p.MaximumRTTIncrease} { if math.IsNaN(v) || math.IsInf(v, 0) || v < 0 || v > 1 { return errors.New("invalid effect/guardrail bound") } @@ -127,14 +125,14 @@ func ValidatePolicy(p *pb.ExperimentPolicy) error { return nil } -func ValidateEvidence(e *pb.Evidence) error { - if e == nil || !ValidID(e.Id) || e.AnalyzedAt == nil || e.WindowStart == nil || e.WindowEnd == nil || e.AnalyzedAt.CheckValid() != nil || e.WindowStart.CheckValid() != nil || e.WindowEnd.CheckValid() != nil || !e.WindowEnd.AsTime().After(e.WindowStart.AsTime()) || e.AnalyzedAt.AsTime().Before(e.WindowEnd.AsTime()) { +func ValidateEvidence(e *Evidence) error { + if e == nil || !ValidID(e.ID) || e.AnalyzedAt.IsZero() || e.WindowStart.IsZero() || e.WindowEnd.IsZero() || !e.WindowEnd.After(e.WindowStart) || e.AnalyzedAt.Before(e.WindowEnd) { return errors.New("invalid evidence interval") } - if e.QueryVersion == "" || len(e.QueryVersion) > 256 || len(e.EnrichmentVersion) > 256 || len(e.ArtifactSha256) != 64 { + if e.QueryVersion == "" || len(e.QueryVersion) > 256 || len(e.EnrichmentVersion) > 256 || len(e.ArtifactSHA256) != 64 { return errors.New("evidence provenance required") } - for _, c := range e.ArtifactSha256 { + for _, c := range e.ArtifactSHA256 { if !(c >= '0' && c <= '9' || c >= 'a' && c <= 'f') { return errors.New("invalid evidence sha256") } @@ -143,11 +141,12 @@ func ValidateEvidence(e *pb.Evidence) error { return errors.New("invalid evidence population") } seen := make(map[string]bool, len(e.Hosts)) - for _, h := range e.Hosts { - if h == nil || !ValidID(h.AgentId) || seen[h.AgentId] || h.ExposedSockets > h.Sockets { + for i := range e.Hosts { + h := &e.Hosts[i] + if !ValidID(h.AgentID) || seen[h.AgentID] || h.ExposedSockets > h.Sockets { return errors.New("invalid/duplicate evidence host") } - seen[h.AgentId] = true + seen[h.AgentID] = true for _, v := range []float64{h.GoodputBps, h.RetransmissionRatio, h.RttP95Ms} { if math.IsNaN(v) || math.IsInf(v, 0) || v < 0 { return errors.New("invalid host measurement") @@ -157,11 +156,11 @@ func ValidateEvidence(e *pb.Evidence) error { return nil } -func ValidateProposal(e *pb.Experiment) error { - if e == nil || !ValidID(e.Id) || !ValidID(e.Location) || len(e.Hypothesis) == 0 || len(e.Hypothesis) > 8192 || len(e.CohortOrder) < 3 || len(e.CohortOrder) > MaxAgents { +func ValidateProposal(e *Experiment) error { + if e == nil || !ValidID(e.ID) || !ValidID(e.Location) || len(e.Hypothesis) == 0 || len(e.Hypothesis) > 8192 || len(e.CohortOrder) < 3 || len(e.CohortOrder) > MaxAgents { return errors.New("invalid experiment identity or population (minimum three hosts)") } - if err := ValidateTreatment(e.Treatment); err != nil { + if err := ValidateTreatment(&e.Treatment); err != nil { return err } if err := ValidatePolicy(e.Policy); err != nil { @@ -178,10 +177,35 @@ func ValidateProposal(e *pb.Experiment) error { seen[id] = true } for _, h := range e.Baseline.Hosts { - delete(seen, h.AgentId) + delete(seen, h.AgentID) } if len(seen) > 0 { return fmt.Errorf("baseline missing %d cohort hosts", len(seen)) } return nil } + +// ValidateReport checks a check-in before it is stored. Kernel bytes and boot +// IDs of installed routes are local to the host and must not be reported. +func ValidateReport(r *AgentReport) error { + if r == nil || !ValidID(r.AgentID) || !ValidID(r.Location) || !ValidID(r.Capabilities.BootID) || len(r.Capabilities.KernelRelease) > 256 || len(r.Capabilities.CongestionAlgorithms) > 64 || len(r.Records) > MaxAssignments*2 { + return errors.New("invalid agent report") + } + for _, a := range r.Capabilities.CongestionAlgorithms { + if !ValidID(a) || len(a) > 15 { + return errors.New("invalid algorithm capability") + } + } + seen := map[string]bool{} + for i := range r.Records { + v := &r.Records[i] + if !ValidID(v.Assignment.ExperimentID) || seen[v.Assignment.ExperimentID] || len(v.Error) > 4096 || !v.Status.Valid() { + return errors.New("invalid application record") + } + seen[v.Assignment.ExperimentID] = true + if err := ValidateTreatment(&v.Assignment.Treatment); err != nil { + return err + } + } + return nil +} diff --git a/pkg/treatment/validate_test.go b/pkg/treatment/validate_test.go index 8834cd6e0..edee03ff7 100644 --- a/pkg/treatment/validate_test.go +++ b/pkg/treatment/validate_test.go @@ -1,19 +1,19 @@ -package treatment +package treatment_test import ( "fmt" "math" + "net/netip" "strings" "testing" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/durationpb" - "google.golang.org/protobuf/types/known/timestamppb" ) +func ptr[T any](v T) *T { return &v } + // Each row states the contract, not just whether the implementation returned. type validationCase[T any] struct { name, description string @@ -55,7 +55,7 @@ func TestValidIDBoundaries(t *testing.T) { {"unicode", "identities are ASCII", "höst", false}, } { t.Run(tc.name, func(t *testing.T) { - if got := ValidID(tc.input); got != tc.want { + if got := treatment.ValidID(tc.input); got != tc.want { t.Fatalf("%s: got %t want %t", tc.description, got, tc.want) } }) @@ -63,46 +63,53 @@ func TestValidIDBoundaries(t *testing.T) { } func TestTreatmentValidation(t *testing.T) { - cases := []validationCase[*pb.Treatment]{ - {"valid", "the supported nearby profile is accepted", func(**pb.Treatment) {}, ""}, - {"nil", "missing treatments are rejected", func(p **pb.Treatment) { *p = nil }, "requires"}, - {"revision", "zero revision is invalid", func(p **pb.Treatment) { (*p).Revision = 0 }, "requires"}, - {"missing-settings", "settings are required", func(p **pb.Treatment) { (*p).Settings = nil }, "requires"}, + cases := []validationCase[*treatment.Treatment]{ + {"valid", "the supported nearby profile is accepted", func(**treatment.Treatment) {}, ""}, + {"nil", "missing treatments are rejected", func(p **treatment.Treatment) { *p = nil }, "requires"}, + {"revision", "zero revision is invalid", func(p **treatment.Treatment) { (*p).Revision = 0 }, "requires"}, + {"empty-settings", "a treatment with no parameter set is rejected", func(p **treatment.Treatment) { (*p).Settings = treatment.TCPSettings{} }, "empty"}, + {"zero-destination", "the zero prefix is not a destination", func(p **treatment.Treatment) { (*p).Destination = netip.Prefix{} }, "canonical unicast"}, } for _, tc := range []struct { destination string valid bool }{ - {"192.168.1.1/32", true}, {"2001:db8::1/128", true}, {"192.0.2.0/24", false}, {"192.0.2.1/24", false}, {"::ffff:192.0.2.1/128", false}, {"127.0.0.1/32", false}, {"::1/128", false}, {"224.0.0.1/32", false}, {"fe80::1/128", false}, {"0.0.0.0/32", false}, {"invalid", false}, + {"192.168.1.1/32", true}, {"2001:db8::1/128", true}, {"192.0.2.0/24", false}, {"192.0.2.1/24", false}, {"::ffff:192.0.2.1/128", false}, {"127.0.0.1/32", false}, {"::1/128", false}, {"224.0.0.1/32", false}, {"fe80::1/128", false}, {"0.0.0.0/32", false}, } { want := "" if !tc.valid { want = "canonical unicast" } - cases = append(cases, validationCase[*pb.Treatment]{tc.destination, "only unicast host destinations may be treated", func(p **pb.Treatment) { (*p).Destination = tc.destination }, want}) + cases = append(cases, validationCase[*treatment.Treatment]{tc.destination, "destinations are canonical unicast host prefixes", func(p **treatment.Treatment) { (*p).Destination = netip.MustParsePrefix(tc.destination) }, want}) } - checkValidation(t, func() *pb.Treatment { return fixtures.Desired(time.Unix(1700000000, 0)).Assignments[0].Treatment }, ValidateTreatment, cases) + checkValidation(t, func() *treatment.Treatment { + return &fixtures.Desired(time.Unix(1700000000, 0)).Assignments[0].Treatment + }, treatment.ValidateTreatment, cases) } func TestTCPSettingsValidation(t *testing.T) { type input struct { - s *pb.TCPSettings + s treatment.TCPSettings verified bool } cases := []validationCase[input]{ {"cubic", "ordinary CUBIC is accepted", func(*input) {}, ""}, - {"nil", "nil settings are rejected", func(p *input) { p.s = nil }, "required"}, - {"empty", "at least one explicit metric is required", func(p *input) { p.s = &pb.TCPSettings{} }, "empty"}, - {"explicit-false", "false quickack is a real setting", func(p *input) { p.s = &pb.TCPSettings{QuickAck: proto.Bool(false)} }, ""}, - {"lock-without-algorithm", "locking requires an algorithm", func(p *input) { p.s = &pb.TCPSettings{CongestionLock: true} }, "requires"}, - {"algorithm-max", "15 byte kernel name is accepted", func(p *input) { p.s.CongestionControl = proto.String(strings.Repeat("a", 15)) }, ""}, - {"algorithm-long", "16 byte kernel name is rejected", func(p *input) { p.s.CongestionControl = proto.String(strings.Repeat("a", 16)) }, "kernel name"}, - {"algorithm-empty", "explicit empty algorithm is invalid", func(p *input) { p.s.CongestionControl = proto.String("") }, "invalid congestion"}, - {"dctcp-unverified", "ECN fabric verification is required", func(p *input) { p.s.CongestionControl = proto.String("dctcp"); p.s.Ecn = proto.Bool(true) }, "DCTCP"}, - {"dctcp-no-ecn", "verification alone is insufficient", func(p *input) { p.s.CongestionControl = proto.String("dctcp"); p.verified = true }, "DCTCP"}, + {"empty", "at least one explicit metric is required", func(p *input) { p.s = treatment.TCPSettings{} }, "empty"}, + {"explicit-false", "false quickack is a real setting", func(p *input) { p.s = treatment.TCPSettings{QuickAck: ptr(false)} }, ""}, + {"lock-without-algorithm", "locking requires an algorithm", func(p *input) { p.s = treatment.TCPSettings{CongestionLock: true} }, "requires"}, + {"algorithm-max", "15 byte kernel name is accepted", func(p *input) { p.s.CongestionControl = ptr(strings.Repeat("a", 15)) }, ""}, + {"algorithm-long", "16 byte kernel name is rejected", func(p *input) { p.s.CongestionControl = ptr(strings.Repeat("a", 16)) }, "kernel name"}, + {"algorithm-empty", "explicit empty algorithm is invalid", func(p *input) { p.s.CongestionControl = ptr("") }, "invalid congestion"}, + {"dctcp-unverified", "ECN fabric verification is required", func(p *input) { p.s.CongestionControl = ptr("dctcp"); p.s.ECN = ptr(true) }, "DCTCP"}, + {"dctcp-no-ecn", "verification alone is insufficient", func(p *input) { p.s.CongestionControl = ptr("dctcp"); p.verified = true }, "DCTCP"}, + {"dctcp-ecn-false", "an explicit false ECN does not satisfy DCTCP", func(p *input) { + p.s.CongestionControl = ptr("dctcp") + p.s.ECN = ptr(false) + p.verified = true + }, "DCTCP"}, {"dctcp-verified", "verified fabric and explicit ECN permit DCTCP", func(p *input) { - p.s.CongestionControl = proto.String("dctcp") - p.s.Ecn = proto.Bool(true) + p.s.CongestionControl = ptr("dctcp") + p.s.ECN = ptr(true) p.verified = true }, ""}, } @@ -121,50 +128,50 @@ func TestTCPSettingsValidation(t *testing.T) { cases = append(cases, validationCase[input]{fmt.Sprintf("%s-%d", metric, value), "metric bounds are inclusive", func(p *input) { switch metric { case "rto": - p.s.RtoMinMs = proto.Uint32(value) + p.s.RTOMinMs = ptr(value) case "cwnd": - p.s.InitialCongestionWindow = proto.Uint32(value) + p.s.InitialCongestionWindow = ptr(value) case "rwnd": - p.s.InitialReceiveWindow = proto.Uint32(value) + p.s.InitialReceiveWindow = ptr(value) } }, want}) } } - checkValidation(t, func() input { return input{s: &pb.TCPSettings{CongestionControl: proto.String("cubic")}} }, func(p input) error { return ValidateTCPSettings(p.s, p.verified) }, cases) + checkValidation(t, func() input { return input{s: treatment.TCPSettings{CongestionControl: ptr("cubic")}} }, func(p input) error { return treatment.ValidateTCPSettings(p.s, p.verified) }, cases) } func TestPolicyValidation(t *testing.T) { - cases := []validationCase[*pb.ExperimentPolicy]{ - {"default", "production defaults are valid", func(**pb.ExperimentPolicy) {}, ""}, - {"nil", "policy is required", func(p **pb.ExperimentPolicy) { *p = nil }, "required"}, - {"observation-after-phase", "observation cannot exceed phase", func(p **pb.ExperimentPolicy) { (*p).MaximumPhase = durationpb.New(time.Minute) }, "sample bounds"}, - {"zero-sockets", "sample count must be positive", func(p **pb.ExperimentPolicy) { (*p).MinimumSocketsPerHost = 0 }, "sample bounds"}, - {"zero-bytes", "byte count must be positive", func(p **pb.ExperimentPolicy) { (*p).MinimumBytesPerHost = 0 }, "sample bounds"}, + cases := []validationCase[treatment.Policy]{ + {"default", "production defaults are valid", func(*treatment.Policy) {}, ""}, + {"zero", "the zero policy is rejected", func(p *treatment.Policy) { *p = treatment.Policy{} }, "duration"}, + {"observation-after-phase", "observation cannot exceed phase", func(p *treatment.Policy) { p.MaximumPhase = time.Minute }, "sample bounds"}, + {"zero-sockets", "sample count must be positive", func(p *treatment.Policy) { p.MinimumSocketsPerHost = 0 }, "sample bounds"}, + {"zero-bytes", "byte count must be positive", func(p *treatment.Policy) { p.MinimumBytesPerHost = 0 }, "sample bounds"}, } - for _, n := range []uint32{1, 2, MaxAgents, MaxAgents + 1} { + for _, n := range []uint32{1, 2, treatment.MaxAgents, treatment.MaxAgents + 1} { want := "" - if n < 2 || n > MaxAgents { + if n < 2 || n > treatment.MaxAgents { want = "sample bounds" } - cases = append(cases, validationCase[*pb.ExperimentPolicy]{fmt.Sprintf("hosts-%d", n), "treated population bounds are inclusive", func(p **pb.ExperimentPolicy) { (*p).MinimumTreatedHosts = n }, want}) + cases = append(cases, validationCase[treatment.Policy]{fmt.Sprintf("hosts-%d", n), "treated population bounds are inclusive", func(p *treatment.Policy) { p.MinimumTreatedHosts = n }, want}) } for _, field := range []string{"observation", "phase", "age"} { for _, tc := range []struct { name string - d *durationpb.Duration + d time.Duration want string }{ - {"nil", nil, "duration"}, {"zero", durationpb.New(0), "duration"}, {"negative", durationpb.New(-time.Second), "duration"}, {"maximum", durationpb.New(7 * 24 * time.Hour), ""}, {"over", durationpb.New(7*24*time.Hour + time.Nanosecond), "duration"}, {"invalid", &durationpb.Duration{Nanos: 1e9}, "duration"}, + {"zero", 0, "duration"}, {"negative", -time.Second, "duration"}, {"maximum", 7 * 24 * time.Hour, ""}, {"over", 7*24*time.Hour + time.Nanosecond, "duration"}, } { - cases = append(cases, validationCase[*pb.ExperimentPolicy]{field + "-" + tc.name, "durations must be valid and within seven days", func(p **pb.ExperimentPolicy) { - (*p).MaximumPhase = durationpb.New(7 * 24 * time.Hour) + cases = append(cases, validationCase[treatment.Policy]{field + "-" + tc.name, "durations must be positive and within seven days", func(p *treatment.Policy) { + p.MaximumPhase = 7 * 24 * time.Hour switch field { case "observation": - (*p).MinimumObservation = tc.d + p.MinimumObservation = tc.d case "phase": - (*p).MaximumPhase = tc.d + p.MaximumPhase = tc.d case "age": - (*p).MaximumEvidenceAge = tc.d + p.MaximumEvidenceAge = tc.d } }, tc.want}) } @@ -175,38 +182,38 @@ func TestPolicyValidation(t *testing.T) { if math.IsNaN(v) || math.IsInf(v, 0) || v < 0 || v > 1 { want = "guardrail" } - cases = append(cases, validationCase[*pb.ExperimentPolicy]{fmt.Sprintf("%s-%g", field, v), "effect bounds must be finite fractions in [0,1]", func(p **pb.ExperimentPolicy) { + cases = append(cases, validationCase[treatment.Policy]{fmt.Sprintf("%s-%g", field, v), "effect bounds must be finite fractions in [0,1]", func(p *treatment.Policy) { switch field { case "effect": - (*p).MinimumImprovement = v + p.MinimumImprovement = v case "loss": - (*p).MaximumRetransmissionIncrease = v + p.MaximumRetransmissionIncrease = v case "rtt": - (*p).MaximumRttIncrease = v + p.MaximumRTTIncrease = v } }, want}) } } - checkValidation(t, DefaultPolicy, ValidatePolicy, cases) + checkValidation(t, treatment.DefaultPolicy, treatment.ValidatePolicy, cases) } func TestEvidenceValidation(t *testing.T) { now := time.Unix(1700000000, 0) - cases := []validationCase[*pb.Evidence]{ - {"valid", "complete finite evidence is accepted", func(**pb.Evidence) {}, ""}, - {"nil", "evidence is required", func(p **pb.Evidence) { *p = nil }, "interval"}, - {"empty-window", "a zero length window is invalid", func(p **pb.Evidence) { (*p).WindowStart = (*p).WindowEnd }, "interval"}, - {"analysis-before-end", "analysis cannot predate data", func(p **pb.Evidence) { (*p).AnalyzedAt = (*p).WindowStart }, "interval"}, - {"missing-time", "all timestamps are required", func(p **pb.Evidence) { (*p).WindowEnd = nil }, "interval"}, - {"invalid-time", "malformed protobuf timestamps fail", func(p **pb.Evidence) { (*p).WindowEnd = ×tamppb.Timestamp{Nanos: 1e9} }, "interval"}, - {"query-version", "query provenance is required", func(p **pb.Evidence) { (*p).QueryVersion = "" }, "provenance"}, - {"long-version", "query version length is bounded", func(p **pb.Evidence) { (*p).QueryVersion = strings.Repeat("v", 257) }, "provenance"}, - {"digest-length", "digest is exactly 64 characters", func(p **pb.Evidence) { (*p).ArtifactSha256 = "a" }, "provenance"}, - {"digest-hex", "digest is lower case hexadecimal", func(p **pb.Evidence) { (*p).ArtifactSha256 = strings.Repeat("z", 64) }, "sha256"}, - {"empty-hosts", "empty evidence is not healthy evidence", func(p **pb.Evidence) { (*p).Hosts = nil }, "population"}, - {"nil-host", "nil host entries are rejected", func(p **pb.Evidence) { (*p).Hosts[0] = nil }, "host"}, - {"duplicate-host", "hosts are independent units", func(p **pb.Evidence) { (*p).Hosts[1] = (*p).Hosts[0] }, "host"}, - {"overexposure", "exposed sockets cannot exceed total sockets", func(p **pb.Evidence) { (*p).Hosts[0].ExposedSockets = 101 }, "host"}, + cases := []validationCase[*treatment.Evidence]{ + {"valid", "complete finite evidence is accepted", func(**treatment.Evidence) {}, ""}, + {"nil", "evidence is required", func(p **treatment.Evidence) { *p = nil }, "interval"}, + {"empty-window", "a zero length window is invalid", func(p **treatment.Evidence) { (*p).WindowStart = (*p).WindowEnd }, "interval"}, + {"analysis-before-end", "analysis cannot predate data", func(p **treatment.Evidence) { (*p).AnalyzedAt = (*p).WindowStart }, "interval"}, + {"missing-time", "all timestamps are required", func(p **treatment.Evidence) { (*p).WindowEnd = time.Time{} }, "interval"}, + {"analysis-equals-end", "analysis at the window end is the earliest valid instant", func(p **treatment.Evidence) { (*p).AnalyzedAt = (*p).WindowEnd }, ""}, + {"query-version", "query provenance is required", func(p **treatment.Evidence) { (*p).QueryVersion = "" }, "provenance"}, + {"long-version", "query version length is bounded", func(p **treatment.Evidence) { (*p).QueryVersion = strings.Repeat("v", 257) }, "provenance"}, + {"digest-length", "digest is exactly 64 characters", func(p **treatment.Evidence) { (*p).ArtifactSHA256 = "a" }, "provenance"}, + {"digest-hex", "digest is lower case hexadecimal", func(p **treatment.Evidence) { (*p).ArtifactSHA256 = strings.Repeat("z", 64) }, "sha256"}, + {"empty-hosts", "empty evidence is not healthy evidence", func(p **treatment.Evidence) { (*p).Hosts = nil }, "population"}, + {"zero-host", "a host without an identity is rejected", func(p **treatment.Evidence) { (*p).Hosts[0] = treatment.HostEvidence{} }, "host"}, + {"duplicate-host", "hosts are independent units", func(p **treatment.Evidence) { (*p).Hosts[1] = (*p).Hosts[0] }, "host"}, + {"overexposure", "exposed sockets cannot exceed total sockets", func(p **treatment.Evidence) { (*p).Hosts[0].ExposedSockets = 101 }, "host"}, } for _, field := range []string{"goodput", "loss", "rtt"} { for _, v := range []float64{0, -1, math.NaN(), math.Inf(1), math.Inf(-1)} { @@ -214,8 +221,8 @@ func TestEvidenceValidation(t *testing.T) { if v != 0 { want = "measurement" } - cases = append(cases, validationCase[*pb.Evidence]{fmt.Sprintf("%s-%g", field, v), "measurements must be finite and nonnegative", func(p **pb.Evidence) { - h := (*p).Hosts[0] + cases = append(cases, validationCase[*treatment.Evidence]{fmt.Sprintf("%s-%g", field, v), "measurements must be finite and nonnegative", func(p **treatment.Evidence) { + h := &(*p).Hosts[0] switch field { case "goodput": h.GoodputBps = v @@ -227,94 +234,133 @@ func TestEvidenceValidation(t *testing.T) { }, want}) } } - for _, n := range []int{MaxAgents, MaxAgents + 1} { + for _, n := range []int{treatment.MaxAgents, treatment.MaxAgents + 1} { want := "" - if n > MaxAgents { + if n > treatment.MaxAgents { want = "population" } - cases = append(cases, validationCase[*pb.Evidence]{fmt.Sprintf("population-%d", n), "fleet population is bounded", func(p **pb.Evidence) { + cases = append(cases, validationCase[*treatment.Evidence]{fmt.Sprintf("population-%d", n), "fleet population is bounded", func(p **treatment.Evidence) { (*p).Hosts = nil for i := range n { - (*p).Hosts = append((*p).Hosts, &pb.HostEvidence{AgentId: fmt.Sprintf("host-%d", i)}) + (*p).Hosts = append((*p).Hosts, treatment.HostEvidence{AgentID: fmt.Sprintf("host-%d", i)}) } }, want}) } - checkValidation(t, func() *pb.Evidence { _, v := fixtures.Experiment(now, DefaultPolicy()); return v }, ValidateEvidence, cases) + checkValidation(t, func() *treatment.Evidence { _, v := fixtures.Experiment(now, treatment.DefaultPolicy()); return v }, treatment.ValidateEvidence, cases) } func TestDesiredValidation(t *testing.T) { now := time.Unix(1700000000, 0) - cases := []validationCase[*pb.DesiredState]{ - {"valid", "leased trials are accepted", func(**pb.DesiredState) {}, ""}, - {"nil", "desired header is required", func(p **pb.DesiredState) { *p = nil }, "header"}, - {"empty", "empty assignment set withdraws treatments", func(p **pb.DesiredState) { (*p).Assignments = nil }, ""}, - {"zero-revision", "revisions start at one", func(p **pb.DesiredState) { (*p).Revision = 0 }, "header"}, - {"missing-time", "issuance time is required", func(p **pb.DesiredState) { (*p).IssuedAt = nil }, "header"}, - {"nil-assignment", "assignment cannot be nil", func(p **pb.DesiredState) { (*p).Assignments[0] = nil }, "identity"}, - {"zero-generation", "generations start at one", func(p **pb.DesiredState) { (*p).Assignments[0].Generation = 0 }, "identity"}, - {"duplicate-id", "assignment IDs must be unique", func(p **pb.DesiredState) { - (*p).Assignments = append((*p).Assignments, proto.Clone((*p).Assignments[0]).(*pb.Assignment)) + cases := []validationCase[*treatment.DesiredState]{ + {"valid", "leased trials are accepted", func(**treatment.DesiredState) {}, ""}, + {"nil", "desired header is required", func(p **treatment.DesiredState) { *p = nil }, "header"}, + {"empty", "empty assignment set withdraws treatments", func(p **treatment.DesiredState) { (*p).Assignments = nil }, ""}, + {"zero-revision", "revisions start at one", func(p **treatment.DesiredState) { (*p).Revision = 0 }, "header"}, + {"missing-time", "issuance time is required", func(p **treatment.DesiredState) { (*p).IssuedAt = time.Time{} }, "header"}, + {"zero-assignment", "an assignment without identity is rejected", func(p **treatment.DesiredState) { (*p).Assignments[0] = treatment.Assignment{} }, "identity"}, + {"zero-generation", "generations start at one", func(p **treatment.DesiredState) { (*p).Assignments[0].Generation = 0 }, "identity"}, + {"duplicate-id", "assignment IDs must be unique", func(p **treatment.DesiredState) { + (*p).Assignments = append((*p).Assignments, (*p).Assignments[0]) }, "identity"}, - {"duplicate-destination", "active routes cannot overlap", func(p **pb.DesiredState) { - a := proto.Clone((*p).Assignments[0]).(*pb.Assignment) - a.ExperimentId = "other" + {"duplicate-destination", "active routes cannot overlap", func(p **treatment.DesiredState) { + a := (*p).Assignments[0] + a.ExperimentID = "other" (*p).Assignments = append((*p).Assignments, a) }, "overlapping"}, - {"retired-overlap", "retired routes may overlap active routes", func(p **pb.DesiredState) { - a := proto.Clone((*p).Assignments[0]).(*pb.Assignment) - a.ExperimentId = "other" - a.Mode = pb.AssignmentMode_ASSIGNMENT_MODE_RETIRED + {"retired-overlap", "retired routes may overlap active routes", func(p **treatment.DesiredState) { + a := (*p).Assignments[0] + a.ExperimentID = "other" + a.Mode = treatment.ModeRetired (*p).Assignments = append((*p).Assignments, a) }, ""}, - {"invalid-mode", "unknown modes are rejected", func(p **pb.DesiredState) { (*p).Assignments[0].Mode = 99 }, "mode"}, - {"missing-lease", "trials require an expiry", func(p **pb.DesiredState) { (*p).Assignments[0].LeaseExpiresAt = nil }, "expiry"}, - {"stable-no-lease", "stable routes need no lease", func(p **pb.DesiredState) { - a := (*p).Assignments[0] - a.Mode = pb.AssignmentMode_ASSIGNMENT_MODE_STABLE - a.LeaseExpiresAt = nil + {"invalid-mode", "unknown modes are rejected", func(p **treatment.DesiredState) { (*p).Assignments[0].Mode = 99 }, "mode"}, + {"unspecified-mode", "the zero mode is not an instruction", func(p **treatment.DesiredState) { (*p).Assignments[0].Mode = treatment.ModeUnspecified }, "mode"}, + {"missing-lease", "trials require an expiry", func(p **treatment.DesiredState) { (*p).Assignments[0].LeaseExpiresAt = time.Time{} }, "expiry"}, + {"stable-no-lease", "stable routes need no lease", func(p **treatment.DesiredState) { + a := &(*p).Assignments[0] + a.Mode = treatment.ModeStable + a.LeaseExpiresAt = time.Time{} }, ""}, - {"predecessor", "matching predecessor is accepted", func(p **pb.DesiredState) { - a := (*p).Assignments[0] - a.Predecessor = proto.Clone(a.Treatment).(*pb.Treatment) + {"predecessor", "matching predecessor is accepted", func(p **treatment.DesiredState) { + a := &(*p).Assignments[0] + pred := a.Treatment + a.Predecessor = &pred }, ""}, - {"wrong-predecessor", "predecessor must cover the same destination", func(p **pb.DesiredState) { - a := (*p).Assignments[0] - a.Predecessor = proto.Clone(a.Treatment).(*pb.Treatment) - a.Predecessor.Destination = "192.0.2.2/32" + {"wrong-predecessor", "predecessor must cover the same destination", func(p **treatment.DesiredState) { + a := &(*p).Assignments[0] + pred := a.Treatment + pred.Destination = netip.MustParsePrefix("192.0.2.2/32") + a.Predecessor = &pred }, "differs"}, } - for _, n := range []int{MaxAssignments, MaxAssignments + 1} { + for _, n := range []int{treatment.MaxAssignments, treatment.MaxAssignments + 1} { want := "" - if n > MaxAssignments { + if n > treatment.MaxAssignments { want = "header" } - cases = append(cases, validationCase[*pb.DesiredState]{fmt.Sprintf("assignments-%d", n), "assignment count is bounded", func(p **pb.DesiredState) { + cases = append(cases, validationCase[*treatment.DesiredState]{fmt.Sprintf("assignments-%d", n), "assignment count is bounded", func(p **treatment.DesiredState) { base := (*p).Assignments[0] (*p).Assignments = nil for i := range n { - a := proto.Clone(base).(*pb.Assignment) - a.ExperimentId = fmt.Sprintf("a-%d", i) - a.Treatment.Destination = fmt.Sprintf("198.18.%d.%d/32", i/256, i%256) + a := base + a.ExperimentID = fmt.Sprintf("a-%d", i) + a.Treatment.Destination = netip.MustParsePrefix(fmt.Sprintf("198.18.%d.%d/32", i/256, i%256)) (*p).Assignments = append((*p).Assignments, a) } }, want}) } - checkValidation(t, func() *pb.DesiredState { return fixtures.Desired(now) }, ValidateDesired, cases) + checkValidation(t, func() *treatment.DesiredState { return fixtures.Desired(now) }, treatment.ValidateDesired, cases) } func TestProposalValidation(t *testing.T) { now := time.Unix(1700000000, 0) - checkValidation(t, func() *pb.Experiment { e, _ := fixtures.Experiment(now, DefaultPolicy()); return e }, ValidateProposal, []validationCase[*pb.Experiment]{ - {"valid", "complete proposal is reviewable", func(**pb.Experiment) {}, ""}, - {"nil", "proposal is required", func(p **pb.Experiment) { *p = nil }, "identity"}, - {"no-hypothesis", "proposal must state a hypothesis", func(p **pb.Experiment) { (*p).Hypothesis = "" }, "identity"}, - {"long-hypothesis", "hypothesis is bounded", func(p **pb.Experiment) { (*p).Hypothesis = strings.Repeat("x", 8193) }, "identity"}, - {"three-hosts", "one canary and two controls suffice", func(p **pb.Experiment) { (*p).CohortOrder = (*p).CohortOrder[:3] }, ""}, - {"two-hosts", "two hosts cannot provide a canary and two controls", func(p **pb.Experiment) { (*p).CohortOrder = (*p).CohortOrder[:2] }, "population"}, - {"duplicate", "duplicate hosts cannot inflate the cohort", func(p **pb.Experiment) { (*p).CohortOrder[1] = (*p).CohortOrder[0] }, "cohort"}, - {"missing-baseline-host", "every cohort member needs a baseline", func(p **pb.Experiment) { (*p).CohortOrder[0] = "absent" }, "baseline missing"}, - {"invalid-treatment", "proposal validates treatment", func(p **pb.Experiment) { (*p).Treatment = nil }, "requires"}, - {"invalid-policy", "proposal validates policy", func(p **pb.Experiment) { (*p).Policy = nil }, "policy"}, - {"invalid-evidence", "proposal validates evidence", func(p **pb.Experiment) { (*p).Baseline = nil }, "interval"}, + checkValidation(t, func() *treatment.Experiment { e, _ := fixtures.Experiment(now, treatment.DefaultPolicy()); return e }, treatment.ValidateProposal, []validationCase[*treatment.Experiment]{ + {"valid", "complete proposal is reviewable", func(**treatment.Experiment) {}, ""}, + {"nil", "proposal is required", func(p **treatment.Experiment) { *p = nil }, "identity"}, + {"no-hypothesis", "proposal must state a hypothesis", func(p **treatment.Experiment) { (*p).Hypothesis = "" }, "identity"}, + {"long-hypothesis", "hypothesis is bounded", func(p **treatment.Experiment) { (*p).Hypothesis = strings.Repeat("x", 8193) }, "identity"}, + {"three-hosts", "one canary and two controls suffice", func(p **treatment.Experiment) { (*p).CohortOrder = (*p).CohortOrder[:3] }, ""}, + {"two-hosts", "two hosts cannot provide a canary and two controls", func(p **treatment.Experiment) { (*p).CohortOrder = (*p).CohortOrder[:2] }, "population"}, + {"duplicate", "duplicate hosts cannot inflate the cohort", func(p **treatment.Experiment) { (*p).CohortOrder[1] = (*p).CohortOrder[0] }, "cohort"}, + {"missing-baseline-host", "every cohort member needs a baseline", func(p **treatment.Experiment) { (*p).CohortOrder[0] = "absent" }, "baseline missing"}, + {"invalid-treatment", "proposal validates treatment", func(p **treatment.Experiment) { (*p).Treatment = treatment.Treatment{} }, "requires"}, + {"invalid-policy", "proposal validates policy", func(p **treatment.Experiment) { (*p).Policy = treatment.Policy{} }, "policy"}, + {"invalid-evidence", "proposal validates evidence", func(p **treatment.Experiment) { (*p).Baseline = nil }, "interval"}, }) } + +func TestReportValidation(t *testing.T) { + now := time.Unix(1700000000, 0) + fresh := func() *treatment.AgentReport { + d := fixtures.Desired(now) + return &treatment.AgentReport{AgentID: "host-1", Location: "dc1", Capabilities: treatment.Capabilities{BootID: "boot-1", KernelRelease: "6.12", CongestionAlgorithms: []string{"cubic", "bbr"}, SimpleHostRouting: true, ActuatorEnabled: true}, DesiredRevision: 1, Records: []treatment.ApplicationRecord{{Assignment: d.Assignments[0], DesiredRevision: 1, Status: treatment.ApplyVerified, AppliedAt: now}}} + } + cases := []validationCase[*treatment.AgentReport]{ + {"valid", "a verified report is accepted", func(**treatment.AgentReport) {}, ""}, + {"nil", "a report is required", func(p **treatment.AgentReport) { *p = nil }, "agent report"}, + {"no-records", "a fresh host reports nothing", func(p **treatment.AgentReport) { (*p).Records = nil }, ""}, + {"bad-boot-id", "boot IDs are identifiers", func(p **treatment.AgentReport) { (*p).Capabilities.BootID = "" }, "agent report"}, + {"long-kernel", "kernel release is bounded", func(p **treatment.AgentReport) { (*p).Capabilities.KernelRelease = strings.Repeat("k", 257) }, "agent report"}, + {"long-algorithm", "algorithm names respect the kernel limit", func(p **treatment.AgentReport) { + (*p).Capabilities.CongestionAlgorithms = []string{strings.Repeat("a", 16)} + }, "algorithm"}, + {"duplicate-record", "one record per experiment", func(p **treatment.AgentReport) { (*p).Records = append((*p).Records, (*p).Records[0]) }, "application record"}, + {"long-error", "error text is bounded", func(p **treatment.AgentReport) { (*p).Records[0].Error = strings.Repeat("e", 4097) }, "application record"}, + {"unknown-status", "an apply status this build does not know is rejected", func(p **treatment.AgentReport) { (*p).Records[0].Status = 200 }, "application record"}, + {"unspecified-status", "an unacted assignment may be reported", func(p **treatment.AgentReport) { (*p).Records[0].Status = treatment.ApplyUnspecified }, ""}, + {"bad-treatment", "records carry valid treatments", func(p **treatment.AgentReport) { (*p).Records[0].Assignment.Treatment.Revision = 0 }, "requires"}, + } + for _, n := range []int{64, 65} { + want := "" + if n > 64 { + want = "agent report" + } + cases = append(cases, validationCase[*treatment.AgentReport]{fmt.Sprintf("algorithms-%d", n), "advertised algorithm count is bounded", func(p **treatment.AgentReport) { + (*p).Capabilities.CongestionAlgorithms = nil + for i := range n { + (*p).Capabilities.CongestionAlgorithms = append((*p).Capabilities.CongestionAlgorithms, fmt.Sprintf("cc%d", i)) + } + }, want}) + } + checkValidation(t, fresh, treatment.ValidateReport, cases) +} diff --git a/pkg/treatment/proto_rules_test.go b/pkg/treatment/wire/rules_test.go similarity index 98% rename from pkg/treatment/proto_rules_test.go rename to pkg/treatment/wire/rules_test.go index 208e2f41c..a7cf69359 100644 --- a/pkg/treatment/proto_rules_test.go +++ b/pkg/treatment/wire/rules_test.go @@ -1,4 +1,4 @@ -package treatment +package wire_test import ( "errors" @@ -9,6 +9,7 @@ import ( "buf.build/go/protovalidate" pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" @@ -27,12 +28,12 @@ func TestProtoRulesMirrorValidators(t *testing.T) { now := time.Unix(1700000000, 0) id128 := strings.Repeat("a", 128) experiment := func(mut func(*pb.Experiment)) proto.Message { - e, _ := fixtures.Experiment(now, DefaultPolicy()) + e, _ := fixtures.ExperimentWire(now, treatment.DefaultPolicy()) mut(e) return e } desired := func(mut func(*pb.DesiredState)) proto.Message { - d := fixtures.Desired(now) + d := fixtures.DesiredWire(now) mut(d) return d } diff --git a/pkg/treatment/wire/wire.go b/pkg/treatment/wire/wire.go new file mode 100644 index 000000000..41b8fa19e --- /dev/null +++ b/pkg/treatment/wire/wire.go @@ -0,0 +1,468 @@ +// Package wire converts between the treatment domain model and the +// xtcp_treatment.v1 protobuf messages. It is the only place that knows both +// shapes: the controller's gRPC handlers, the agent's journal and the CLIs +// call it at their edges and work on domain values everywhere else. +// +// Domain → wire functions never fail. Wire → domain functions are named +// Parse* and fail closed on unknown enum values, invalid timestamps and +// malformed durations so a newer or hostile peer cannot smuggle a value this +// build does not understand. +package wire + +import ( + "errors" + "fmt" + "net/netip" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func stamp(t time.Time) *timestamppb.Timestamp { + if t.IsZero() { + return nil + } + return timestamppb.New(t) +} + +func parseStamp(field string, t *timestamppb.Timestamp) (time.Time, error) { + if t == nil { + return time.Time{}, nil + } + if err := t.CheckValid(); err != nil { + return time.Time{}, fmt.Errorf("%s: %w", field, err) + } + return t.AsTime(), nil +} + +func parseDuration(field string, d *durationpb.Duration) (time.Duration, error) { + if d == nil { + return 0, nil + } + if err := d.CheckValid(); err != nil { + return 0, fmt.Errorf("%s: %w", field, err) + } + return d.AsDuration(), nil +} + +// Enumerations. The domain values mirror the wire values numerically; the +// range check is what makes the cast safe (TestEnumsMirrorWire pins it). + +func Status(s treatment.Status) pb.ExperimentStatus { return pb.ExperimentStatus(s) } + +func ParseStatus(w pb.ExperimentStatus) (treatment.Status, error) { + n, _, _, _ := treatment.EnumCounts() + if w < 0 || int(w) >= n { + return 0, fmt.Errorf("unknown wire experiment status %d", int32(w)) + } + return treatment.Status(w), nil +} + +func Mode(m treatment.AssignmentMode) pb.AssignmentMode { return pb.AssignmentMode(m) } + +func ParseMode(w pb.AssignmentMode) (treatment.AssignmentMode, error) { + _, n, _, _ := treatment.EnumCounts() + if w < 0 || int(w) >= n { + return 0, fmt.Errorf("unknown wire assignment mode %d", int32(w)) + } + return treatment.AssignmentMode(w), nil +} + +func ApplyStatus(a treatment.ApplyStatus) pb.ApplyStatus { return pb.ApplyStatus(a) } + +func ParseApplyStatus(w pb.ApplyStatus) (treatment.ApplyStatus, error) { + _, _, n, _ := treatment.EnumCounts() + if w < 0 || int(w) >= n { + return 0, fmt.Errorf("unknown wire apply status %d", int32(w)) + } + return treatment.ApplyStatus(w), nil +} + +func Outcome(o treatment.Outcome) pb.Outcome { return pb.Outcome(o) } + +func ParseOutcome(w pb.Outcome) (treatment.Outcome, error) { + _, _, _, n := treatment.EnumCounts() + if w < 0 || int(w) >= n { + return 0, fmt.Errorf("unknown wire outcome %d", int32(w)) + } + return treatment.Outcome(w), nil +} + +// Settings and treatments. + +func Settings(s treatment.TCPSettings) *pb.TCPSettings { + return &pb.TCPSettings{CongestionControl: s.CongestionControl, CongestionLock: s.CongestionLock, RtoMinMs: s.RTOMinMs, InitialCongestionWindow: s.InitialCongestionWindow, InitialReceiveWindow: s.InitialReceiveWindow, QuickAck: s.QuickAck, Ecn: s.ECN} +} + +// ParseSettings copies optional fields by value so later mutation of the +// message cannot alias the domain value. +func ParseSettings(p *pb.TCPSettings) treatment.TCPSettings { + if p == nil { + return treatment.TCPSettings{} + } + return treatment.TCPSettings{CongestionControl: clonePtr(p.CongestionControl), CongestionLock: p.CongestionLock, RTOMinMs: clonePtr(p.RtoMinMs), InitialCongestionWindow: clonePtr(p.InitialCongestionWindow), InitialReceiveWindow: clonePtr(p.InitialReceiveWindow), QuickAck: clonePtr(p.QuickAck), ECN: clonePtr(p.Ecn)} +} + +func clonePtr[T any](p *T) *T { + if p == nil { + return nil + } + v := *p + return &v +} + +func Treatment(t *treatment.Treatment) *pb.Treatment { + if t == nil { + return nil + } + destination := "" + if t.Destination.IsValid() { + destination = t.Destination.String() + } + return &pb.Treatment{Id: t.ID, Revision: t.Revision, Destination: destination, Settings: Settings(t.Settings), EcnFabricVerified: t.ECNFabricVerified} +} + +// ParseTreatment leaves an unparsable destination as the zero prefix so +// treatment.ValidateTreatment reports it with the same message as any other +// bad destination. +func ParseTreatment(p *pb.Treatment) treatment.Treatment { + if p == nil { + return treatment.Treatment{} + } + t := treatment.Treatment{ID: p.Id, Revision: p.Revision, Settings: ParseSettings(p.Settings), ECNFabricVerified: p.EcnFabricVerified} + if d, err := netip.ParsePrefix(p.Destination); err == nil { + t.Destination = d + } + return t +} + +func Assignment(a *treatment.Assignment) *pb.Assignment { + if a == nil { + return nil + } + return &pb.Assignment{ExperimentId: a.ExperimentID, Treatment: Treatment(&a.Treatment), Mode: Mode(a.Mode), LeaseExpiresAt: stamp(a.LeaseExpiresAt), Predecessor: Treatment(a.Predecessor), Generation: a.Generation} +} + +func ParseAssignment(p *pb.Assignment) (treatment.Assignment, error) { + if p == nil { + return treatment.Assignment{}, errors.New("missing assignment") + } + mode, err := ParseMode(p.Mode) + if err != nil { + return treatment.Assignment{}, err + } + lease, err := parseStamp("lease_expires_at", p.LeaseExpiresAt) + if err != nil { + return treatment.Assignment{}, err + } + a := treatment.Assignment{ExperimentID: p.ExperimentId, Treatment: ParseTreatment(p.Treatment), Mode: mode, LeaseExpiresAt: lease, Generation: p.Generation} + if p.Predecessor != nil { + pred := ParseTreatment(p.Predecessor) + a.Predecessor = &pred + } + return a, nil +} + +func Desired(d *treatment.DesiredState) *pb.DesiredState { + if d == nil { + return nil + } + out := &pb.DesiredState{AgentId: d.AgentID, Revision: d.Revision, Suspended: d.Suspended, IssuedAt: stamp(d.IssuedAt)} + for i := range d.Assignments { + out.Assignments = append(out.Assignments, Assignment(&d.Assignments[i])) + } + return out +} + +func ParseDesired(p *pb.DesiredState) (*treatment.DesiredState, error) { + if p == nil { + return nil, errors.New("missing desired state") + } + issued, err := parseStamp("issued_at", p.IssuedAt) + if err != nil { + return nil, err + } + d := &treatment.DesiredState{AgentID: p.AgentId, Revision: p.Revision, Suspended: p.Suspended, IssuedAt: issued} + for _, a := range p.Assignments { + v, err := ParseAssignment(a) + if err != nil { + return nil, err + } + d.Assignments = append(d.Assignments, v) + } + return d, nil +} + +// Agent reports. + +func Capabilities(c *treatment.Capabilities) *pb.Capabilities { + return &pb.Capabilities{BootId: c.BootID, KernelRelease: c.KernelRelease, CongestionAlgorithms: append([]string(nil), c.CongestionAlgorithms...), SimpleHostRouting: c.SimpleHostRouting, ActuatorEnabled: c.ActuatorEnabled} +} + +func ParseCapabilities(p *pb.Capabilities) treatment.Capabilities { + if p == nil { + return treatment.Capabilities{} + } + return treatment.Capabilities{BootID: p.BootId, KernelRelease: p.KernelRelease, CongestionAlgorithms: append([]string(nil), p.CongestionAlgorithms...), SimpleHostRouting: p.SimpleHostRouting, ActuatorEnabled: p.ActuatorEnabled} +} + +func Record(r *treatment.ApplicationRecord) *pb.ApplicationRecord { + return &pb.ApplicationRecord{Assignment: Assignment(&r.Assignment), DesiredRevision: r.DesiredRevision, Status: ApplyStatus(r.Status), ReceivedAt: stamp(r.ReceivedAt), AttemptedAt: stamp(r.AttemptedAt), AppliedAt: stamp(r.AppliedAt), VerifiedAt: stamp(r.VerifiedAt), RemovedAt: stamp(r.RemovedAt), Error: r.Error, ExpiredLatched: r.ExpiredLatched} +} + +// ParseRecord rejects records carrying kernel route bytes or an installed +// boot ID: those are local to the host and never cross the wire. +func ParseRecord(p *pb.ApplicationRecord) (treatment.ApplicationRecord, error) { + if p == nil { + return treatment.ApplicationRecord{}, errors.New("missing application record") + } + if len(p.InstalledRoute) > 0 || p.InstalledBootId != "" { + return treatment.ApplicationRecord{}, errors.New("application record carries local kernel state") + } + a, err := ParseAssignment(p.Assignment) + if err != nil { + return treatment.ApplicationRecord{}, err + } + st, err := ParseApplyStatus(p.Status) + if err != nil { + return treatment.ApplicationRecord{}, err + } + r := treatment.ApplicationRecord{Assignment: a, DesiredRevision: p.DesiredRevision, Status: st, Error: p.Error, ExpiredLatched: p.ExpiredLatched} + for _, f := range []struct { + name string + src *timestamppb.Timestamp + dst *time.Time + }{{"received_at", p.ReceivedAt, &r.ReceivedAt}, {"attempted_at", p.AttemptedAt, &r.AttemptedAt}, {"applied_at", p.AppliedAt, &r.AppliedAt}, {"verified_at", p.VerifiedAt, &r.VerifiedAt}, {"removed_at", p.RemovedAt, &r.RemovedAt}} { + if *f.dst, err = parseStamp(f.name, f.src); err != nil { + return treatment.ApplicationRecord{}, err + } + } + return r, nil +} + +func Report(r *treatment.AgentReport) *pb.CheckInRequest { + if r == nil { + return nil + } + out := &pb.CheckInRequest{AgentId: r.AgentID, Location: r.Location, Capabilities: Capabilities(&r.Capabilities), DesiredRevision: r.DesiredRevision, LocallySuspended: r.LocallySuspended} + for i := range r.Records { + out.Records = append(out.Records, Record(&r.Records[i])) + } + return out +} + +func ParseReport(p *pb.CheckInRequest) (*treatment.AgentReport, error) { + if p == nil { + return nil, errors.New("missing check-in") + } + r := &treatment.AgentReport{AgentID: p.AgentId, Location: p.Location, Capabilities: ParseCapabilities(p.Capabilities), DesiredRevision: p.DesiredRevision, LocallySuspended: p.LocallySuspended} + for _, v := range p.Records { + rec, err := ParseRecord(v) + if err != nil { + return nil, err + } + r.Records = append(r.Records, rec) + } + return r, nil +} + +// Evidence. + +func Host(h *treatment.HostEvidence) *pb.HostEvidence { + return &pb.HostEvidence{AgentId: h.AgentID, Sockets: h.Sockets, BytesAcked: h.BytesAcked, GoodputBps: h.GoodputBps, RetransmissionRatio: h.RetransmissionRatio, RttP95Ms: h.RttP95Ms, ExposedSockets: h.ExposedSockets, CollectionHealthy: h.CollectionHealthy} +} + +func ParseHost(p *pb.HostEvidence) treatment.HostEvidence { + if p == nil { + return treatment.HostEvidence{} + } + return treatment.HostEvidence{AgentID: p.AgentId, Sockets: p.Sockets, BytesAcked: p.BytesAcked, GoodputBps: p.GoodputBps, RetransmissionRatio: p.RetransmissionRatio, RttP95Ms: p.RttP95Ms, ExposedSockets: p.ExposedSockets, CollectionHealthy: p.CollectionHealthy} +} + +func Evidence(e *treatment.Evidence) *pb.Evidence { + if e == nil { + return nil + } + out := &pb.Evidence{Id: e.ID, AnalyzedAt: stamp(e.AnalyzedAt), WindowStart: stamp(e.WindowStart), WindowEnd: stamp(e.WindowEnd), QueryVersion: e.QueryVersion, EnrichmentVersion: e.EnrichmentVersion, ArtifactSha256: e.ArtifactSHA256} + for i := range e.Hosts { + out.Hosts = append(out.Hosts, Host(&e.Hosts[i])) + } + return out +} + +// ParseEvidence maps a nil message to nil evidence; validation decides +// whether evidence was required. +func ParseEvidence(p *pb.Evidence) (*treatment.Evidence, error) { + if p == nil { + return nil, nil + } + e := &treatment.Evidence{ID: p.Id, QueryVersion: p.QueryVersion, EnrichmentVersion: p.EnrichmentVersion, ArtifactSHA256: p.ArtifactSha256} + var err error + for _, f := range []struct { + name string + src *timestamppb.Timestamp + dst *time.Time + }{{"analyzed_at", p.AnalyzedAt, &e.AnalyzedAt}, {"window_start", p.WindowStart, &e.WindowStart}, {"window_end", p.WindowEnd, &e.WindowEnd}} { + if *f.dst, err = parseStamp(f.name, f.src); err != nil { + return nil, err + } + } + for _, h := range p.Hosts { + e.Hosts = append(e.Hosts, ParseHost(h)) + } + return e, nil +} + +// Experiments. + +func Policy(p treatment.Policy) *pb.ExperimentPolicy { + return &pb.ExperimentPolicy{MinimumObservation: durationpb.New(p.MinimumObservation), MaximumPhase: durationpb.New(p.MaximumPhase), MaximumEvidenceAge: durationpb.New(p.MaximumEvidenceAge), MinimumSocketsPerHost: p.MinimumSocketsPerHost, MinimumBytesPerHost: p.MinimumBytesPerHost, MinimumTreatedHosts: p.MinimumTreatedHosts, MinimumImprovement: p.MinimumImprovement, MaximumRetransmissionIncrease: p.MaximumRetransmissionIncrease, MaximumRttIncrease: p.MaximumRTTIncrease} +} + +func ParsePolicy(p *pb.ExperimentPolicy) (treatment.Policy, error) { + if p == nil { + return treatment.Policy{}, errors.New("policy required") + } + out := treatment.Policy{MinimumSocketsPerHost: p.MinimumSocketsPerHost, MinimumBytesPerHost: p.MinimumBytesPerHost, MinimumTreatedHosts: p.MinimumTreatedHosts, MinimumImprovement: p.MinimumImprovement, MaximumRetransmissionIncrease: p.MaximumRetransmissionIncrease, MaximumRTTIncrease: p.MaximumRttIncrease} + var err error + for _, f := range []struct { + name string + src *durationpb.Duration + dst *time.Duration + }{{"minimum_observation", p.MinimumObservation, &out.MinimumObservation}, {"maximum_phase", p.MaximumPhase, &out.MaximumPhase}, {"maximum_evidence_age", p.MaximumEvidenceAge, &out.MaximumEvidenceAge}} { + if *f.dst, err = parseDuration(f.name, f.src); err != nil { + return treatment.Policy{}, err + } + } + return out, nil +} + +func Phase(p *treatment.Phase) *pb.Phase { + return &pb.Phase{TargetHosts: p.TargetHosts, IssuedAt: stamp(p.IssuedAt), ObservationStartedAt: stamp(p.ObservationStartedAt), EvaluatedAt: stamp(p.EvaluatedAt), Evidence: Evidence(p.Evidence), Outcome: Outcome(p.Outcome), Reviewer: p.Reviewer, Reason: p.Reason} +} + +func ParsePhase(p *pb.Phase) (treatment.Phase, error) { + if p == nil { + return treatment.Phase{}, errors.New("missing phase") + } + outcome, err := ParseOutcome(p.Outcome) + if err != nil { + return treatment.Phase{}, err + } + ev, err := ParseEvidence(p.Evidence) + if err != nil { + return treatment.Phase{}, err + } + out := treatment.Phase{TargetHosts: p.TargetHosts, Evidence: ev, Outcome: outcome, Reviewer: p.Reviewer, Reason: p.Reason} + for _, f := range []struct { + name string + src *timestamppb.Timestamp + dst *time.Time + }{{"issued_at", p.IssuedAt, &out.IssuedAt}, {"observation_started_at", p.ObservationStartedAt, &out.ObservationStartedAt}, {"evaluated_at", p.EvaluatedAt, &out.EvaluatedAt}} { + if *f.dst, err = parseStamp(f.name, f.src); err != nil { + return treatment.Phase{}, err + } + } + return out, nil +} + +func Experiment(e *treatment.Experiment) *pb.Experiment { + if e == nil { + return nil + } + out := &pb.Experiment{Id: e.ID, Location: e.Location, Hypothesis: e.Hypothesis, Treatment: Treatment(&e.Treatment), Policy: Policy(e.Policy), Baseline: Evidence(e.Baseline), CohortOrder: append([]string(nil), e.CohortOrder...), Status: Status(e.Status), Revision: e.Revision, ProposedAt: stamp(e.ProposedAt), LatestEvidence: Evidence(e.LatestEvidence), LatestOutcome: Outcome(e.LatestOutcome), UpdatedAt: stamp(e.UpdatedAt)} + for i := range e.Phases { + out.Phases = append(out.Phases, Phase(&e.Phases[i])) + } + return out +} + +// ParseExperiment tolerates a missing policy (the zero policy fails +// validation) so a proposal with no policy is reported by the validator +// rather than as a decoding error. +func ParseExperiment(p *pb.Experiment) (*treatment.Experiment, error) { + if p == nil { + return nil, errors.New("missing experiment") + } + status, err := ParseStatus(p.Status) + if err != nil { + return nil, err + } + outcome, err := ParseOutcome(p.LatestOutcome) + if err != nil { + return nil, err + } + e := &treatment.Experiment{ID: p.Id, Location: p.Location, Hypothesis: p.Hypothesis, Treatment: ParseTreatment(p.Treatment), CohortOrder: append([]string(nil), p.CohortOrder...), Status: status, Revision: p.Revision, LatestOutcome: outcome} + if p.Policy != nil { + if e.Policy, err = ParsePolicy(p.Policy); err != nil { + return nil, err + } + } + if e.Baseline, err = ParseEvidence(p.Baseline); err != nil { + return nil, err + } + if e.LatestEvidence, err = ParseEvidence(p.LatestEvidence); err != nil { + return nil, err + } + if e.ProposedAt, err = parseStamp("proposed_at", p.ProposedAt); err != nil { + return nil, err + } + if e.UpdatedAt, err = parseStamp("updated_at", p.UpdatedAt); err != nil { + return nil, err + } + for _, ph := range p.Phases { + v, err := ParsePhase(ph) + if err != nil { + return nil, err + } + e.Phases = append(e.Phases, v) + } + return e, nil +} + +// Validators over wire messages, for callers that keep messages at rest (the +// agent's prototext journal). Each converts and then applies the domain rule +// so there is exactly one set of bounds. + +func ValidateTreatment(p *pb.Treatment) error { + if p == nil || p.Settings == nil { + return errors.New("treatment requires id, revision and settings") + } + t := ParseTreatment(p) + return treatment.ValidateTreatment(&t) +} + +func ValidateDesired(p *pb.DesiredState) error { + if p == nil { + return errors.New("invalid desired-state header") + } + for _, a := range p.Assignments { + if a == nil || a.Treatment == nil || a.Treatment.Settings == nil { + return errors.New("treatment requires id, revision and settings") + } + } + d, err := ParseDesired(p) + if err != nil { + return err + } + return treatment.ValidateDesired(d) +} + +func ValidateEvidence(p *pb.Evidence) error { + e, err := ParseEvidence(p) + if err != nil { + return err + } + return treatment.ValidateEvidence(e) +} + +func ValidatePolicy(p *pb.ExperimentPolicy) error { + v, err := ParsePolicy(p) + if err != nil { + return err + } + return treatment.ValidatePolicy(v) +} diff --git a/pkg/treatment/wire/wire_test.go b/pkg/treatment/wire/wire_test.go new file mode 100644 index 000000000..a7ee9c27a --- /dev/null +++ b/pkg/treatment/wire/wire_test.go @@ -0,0 +1,337 @@ +package wire_test + +import ( + "net/netip" + "reflect" + "strings" + "testing" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" + "github.com/randomizedcoder/xtcp2/pkg/treatment/wire" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func ptr[T any](v T) *T { return &v } + +// TestEnumsMirrorWire pins the one-to-one correspondence between each domain +// enumeration and its protobuf counterpart that the checked casts rely on. +func TestEnumsMirrorWire(t *testing.T) { + statuses, modes, applies, outcomes := treatment.EnumCounts() + for _, tc := range []struct { + description string + domain int + wire map[int32]string + prefix string + name func(int32) string + }{ + {"experiment status", statuses, pb.ExperimentStatus_name, "EXPERIMENT_STATUS_", func(i int32) string { return treatment.Status(i).String() }}, + {"assignment mode", modes, pb.AssignmentMode_name, "ASSIGNMENT_MODE_", func(i int32) string { return treatment.AssignmentMode(i).String() }}, + {"apply status", applies, pb.ApplyStatus_name, "APPLY_STATUS_", func(i int32) string { return treatment.ApplyStatus(i).String() }}, + {"outcome", outcomes, pb.Outcome_name, "OUTCOME_", func(i int32) string { return treatment.Outcome(i).String() }}, + } { + if len(tc.wire) != tc.domain { + t.Errorf("%s: proto defines %d values, Go defines %d", tc.description, len(tc.wire), tc.domain) + continue + } + for i := range int32(tc.domain) { + want := tc.prefix + strings.ToUpper(tc.name(i)) + if got := tc.wire[i]; got != want { + t.Errorf("%s %d: domain name %q mirrors wire %q", tc.description, i, tc.name(i), got) + } + } + } +} + +func TestEnumParsers(t *testing.T) { + statuses, modes, applies, outcomes := treatment.EnumCounts() + for _, tc := range []struct { + name, description string + run func() (any, error) + expected any + expectError bool + }{ + {"status-running", "positive: a defined status converts", func() (any, error) { return wire.ParseStatus(pb.ExperimentStatus_EXPERIMENT_STATUS_RUNNING) }, treatment.StatusRunning, false}, + {"mode-trial", "positive: a defined mode converts", func() (any, error) { return wire.ParseMode(pb.AssignmentMode_ASSIGNMENT_MODE_TRIAL) }, treatment.ModeTrial, false}, + {"apply-failed", "positive: the last apply status converts", func() (any, error) { return wire.ParseApplyStatus(pb.ApplyStatus_APPLY_STATUS_FAILED) }, treatment.ApplyFailed, false}, + {"outcome-observing", "positive: the newest outcome converts", func() (any, error) { return wire.ParseOutcome(pb.Outcome_OUTCOME_OBSERVING) }, treatment.OutcomeObserving, false}, + {"status-zero", "boundary: the zero status is the explicit unspecified value", func() (any, error) { return wire.ParseStatus(0) }, treatment.StatusUnspecified, false}, + {"status-sentinel", "boundary: the first undefined status is rejected", func() (any, error) { return wire.ParseStatus(pb.ExperimentStatus(statuses)) }, treatment.Status(0), true}, + {"mode-sentinel", "boundary: the first undefined mode is rejected", func() (any, error) { return wire.ParseMode(pb.AssignmentMode(modes)) }, treatment.AssignmentMode(0), true}, + {"apply-sentinel", "boundary: the first undefined apply status is rejected", func() (any, error) { return wire.ParseApplyStatus(pb.ApplyStatus(applies)) }, treatment.ApplyStatus(0), true}, + {"outcome-sentinel", "boundary: the first undefined outcome is rejected", func() (any, error) { return wire.ParseOutcome(pb.Outcome(outcomes)) }, treatment.Outcome(0), true}, + {"outcome-high", "corner: a value from a newer peer is rejected with its number", func() (any, error) { return wire.ParseOutcome(255) }, treatment.Outcome(0), true}, + {"outcome-negative", "corner: negative int32 enum values are rejected", func() (any, error) { return wire.ParseOutcome(-1) }, treatment.Outcome(0), true}, + {"status-negative", "corner: negative status values are rejected", func() (any, error) { return wire.ParseStatus(-1) }, treatment.Status(0), true}, + } { + t.Run(tc.name, func(t *testing.T) { + got, err := tc.run() + if (err != nil) != tc.expectError || got != tc.expected { + t.Fatalf("%s: got %v err=%v; expected %v error=%t", tc.description, got, err, tc.expected, tc.expectError) + } + }) + } +} + +func TestDomainEnumParsers(t *testing.T) { + for _, tc := range []struct { + name, description string + run func() (any, error) + expected any + expectError bool + }{ + {"status", "positive: snake_case status parses", func() (any, error) { return treatment.ParseStatus("rolled_back") }, treatment.StatusRolledBack, false}, + {"mode", "positive: snake_case mode parses", func() (any, error) { return treatment.ParseAssignmentMode("stable") }, treatment.ModeStable, false}, + {"apply", "positive: snake_case apply status parses", func() (any, error) { return treatment.ParseApplyStatus("verified") }, treatment.ApplyVerified, false}, + {"outcome", "positive: snake_case outcome parses", func() (any, error) { return treatment.ParseOutcome("beneficial") }, treatment.OutcomeBeneficial, false}, + {"legacy-free-text", "negative: a free string from the pre-enum format is an error, not unspecified", func() (any, error) { return treatment.ParseOutcome("promoted") }, treatment.OutcomeUnspecified, true}, + {"wire-spelling", "negative: the proto spelling is not accepted by the domain parser", func() (any, error) { return treatment.ParseOutcome("OUTCOME_BENEFICIAL") }, treatment.OutcomeUnspecified, true}, + {"empty", "boundary: empty string is an error", func() (any, error) { return treatment.ParseStatus("") }, treatment.StatusUnspecified, true}, + {"unspecified-name", "corner: the unspecified name parses to the zero value", func() (any, error) { return treatment.ParseAssignmentMode("unspecified") }, treatment.ModeUnspecified, false}, + } { + t.Run(tc.name, func(t *testing.T) { + got, err := tc.run() + if (err != nil) != tc.expectError || got != tc.expected { + t.Fatalf("%s: got %v err=%v; expected %v error=%t", tc.description, got, err, tc.expected, tc.expectError) + } + }) + } +} + +func TestOutcomeClassification(t *testing.T) { + _, _, _, outcomes := treatment.EnumCounts() + for _, tc := range []struct { + description string + outcome treatment.Outcome + promotable bool + withdraws bool + renews bool + valid bool + str string + }{ + {"positive: beneficial promotes and renews", treatment.OutcomeBeneficial, true, false, true, true, "beneficial"}, + {"positive: safety-only promotes small cohorts", treatment.OutcomeSafetyOnly, true, false, true, true, "safety_only"}, + {"positive: final safety promotes the 100% phase", treatment.OutcomeFinalSafety, true, false, true, true, "final_safety"}, + {"negative: harmful withdraws and never promotes", treatment.OutcomeHarmful, false, true, true, true, "harmful"}, + {"negative: expired withdraws", treatment.OutcomeExpired, false, true, true, true, "expired"}, + {"negative: stale evidence cannot renew a lease", treatment.OutcomeStale, false, false, false, true, "stale"}, + {"negative: missing data cannot renew a lease", treatment.OutcomeInsufficientData, false, false, false, true, "insufficient_data"}, + {"boundary: zero value is valid, inert, and renews nothing it decides", treatment.OutcomeUnspecified, false, false, true, true, "unspecified"}, + {"boundary: last defined value", treatment.OutcomeObserving, false, false, true, true, "observing"}, + {"corner: sentinel is invalid and prints its number", treatment.Outcome(outcomes), false, false, true, false, "outcome(19)"}, + {"corner: stable regression is a review flag, not an automatic withdrawal", treatment.OutcomeStableRegressionReviewRequired, false, false, true, true, "stable_regression_review_required"}, + } { + o := tc.outcome + if o.Promotable() != tc.promotable || o.Withdraws() != tc.withdraws || o.RenewsLease() != tc.renews || o.Valid() != tc.valid || o.String() != tc.str { + t.Errorf("%s: promotable=%t withdraws=%t renews=%t valid=%t str=%q", tc.description, o.Promotable(), o.Withdraws(), o.RenewsLease(), o.Valid(), o.String()) + } + } +} + +func populatedExperiment(now time.Time) *treatment.Experiment { + e, v := fixtures.Experiment(now, treatment.DefaultPolicy()) + e.Treatment.Settings = treatment.TCPSettings{CongestionControl: ptr("dctcp"), CongestionLock: true, RTOMinMs: ptr[uint32](20), InitialCongestionWindow: ptr[uint32](10), InitialReceiveWindow: ptr[uint32](0), QuickAck: ptr(false), ECN: ptr(true)} + e.Treatment.ECNFabricVerified = true + e.ProposedAt = now.Add(-3 * time.Hour) + e.UpdatedAt = now + e.LatestEvidence = v + e.LatestOutcome = treatment.OutcomeBeneficial + e.Phases[0].Evidence = v.Clone() + e.Phases[0].Outcome = treatment.OutcomeBeneficial + e.Phases[0].EvaluatedAt = now + e.Phases[0].Reviewer = "op" + e.Phases[0].Reason = "ok" + e.Phases = append(e.Phases, treatment.Phase{TargetHosts: 8, IssuedAt: now}) + e.Baseline.EnrichmentVersion = "xtcp-schema-2" + e.Baseline.Hosts[0].CollectionHealthy = false + return e +} + +func populatedReport(now time.Time) *treatment.AgentReport { + d := fixtures.Desired(now) + pred := d.Assignments[0].Treatment + pred.ID = "old" + d.Assignments[0].Predecessor = &pred + return &treatment.AgentReport{AgentID: "host-1", Location: "dc1", Capabilities: treatment.Capabilities{BootID: "b", KernelRelease: "6.12", CongestionAlgorithms: []string{"cubic", "bbr"}, SimpleHostRouting: true, ActuatorEnabled: true}, DesiredRevision: 7, LocallySuspended: true, Records: []treatment.ApplicationRecord{{Assignment: d.Assignments[0], DesiredRevision: 7, Status: treatment.ApplyVerified, ReceivedAt: now, AttemptedAt: now.Add(time.Second), AppliedAt: now.Add(2 * time.Second), VerifiedAt: now.Add(3 * time.Second), ExpiredLatched: false}, {Assignment: treatment.Assignment{ExperimentID: "gone", Treatment: fixtures.Treatment(), Mode: treatment.ModeRetired, Generation: 3}, Status: treatment.ApplyRemoved, RemovedAt: now, Error: "x", ExpiredLatched: true}}} +} + +// TestRoundTrip converts domain → wire → domain and requires deep equality, +// and wire → domain → wire and requires proto equality, so neither direction +// drops or smears a field. +func TestRoundTrip(t *testing.T) { + now := time.Unix(1700000000, 123456789).UTC() + for _, tc := range []struct { + name, description string + run func(t *testing.T) + }{ + {"experiment", "positive: a populated experiment with phases, evidence and every optional setting survives", func(t *testing.T) { + in := populatedExperiment(now) + w := wire.Experiment(in) + out, err := wire.ParseExperiment(w) + if err != nil || !reflect.DeepEqual(in, out) { + t.Fatalf("round trip differs: %v\nin %+v\nout %+v", err, in, out) + } + if again := wire.Experiment(out); !proto.Equal(w, again) { + t.Fatal("wire form differs after a second trip") + } + }}, + {"report", "positive: a check-in with records, predecessor and every timestamp survives", func(t *testing.T) { + in := populatedReport(now) + w := wire.Report(in) + out, err := wire.ParseReport(w) + if err != nil || !reflect.DeepEqual(in, out) { + t.Fatalf("round trip differs: %v\nin %+v\nout %+v", err, in, out) + } + if again := wire.Report(out); !proto.Equal(w, again) { + t.Fatal("wire form differs after a second trip") + } + }}, + {"desired", "positive: desired state survives", func(t *testing.T) { + in := fixtures.Desired(now) + out, err := wire.ParseDesired(wire.Desired(in)) + if err != nil || !reflect.DeepEqual(in, out) { + t.Fatalf("round trip differs: %v", err) + } + }}, + {"unset-optional", "boundary: nil optional settings and zero timestamps stay unset rather than becoming zero values", func(t *testing.T) { + in := fixtures.Desired(now) + in.Assignments[0].Mode = treatment.ModeStable + in.Assignments[0].LeaseExpiresAt = time.Time{} + w := wire.Desired(in) + if w.Assignments[0].LeaseExpiresAt != nil { + t.Fatal("zero lease became a timestamp") + } + s := w.Assignments[0].Treatment.Settings + if s.RtoMinMs != nil || s.InitialCongestionWindow != nil || s.InitialReceiveWindow != nil || s.QuickAck != nil || s.Ecn != nil { + t.Fatal("unset settings became zero values on the wire") + } + out, err := wire.ParseDesired(w) + if err != nil || !reflect.DeepEqual(in, out) { + t.Fatalf("round trip differs: %v", err) + } + }}, + {"explicit-zero", "boundary: an explicit zero window and false flag are preserved as set", func(t *testing.T) { + s := treatment.TCPSettings{InitialReceiveWindow: ptr[uint32](0), QuickAck: ptr(false)} + out := wire.ParseSettings(wire.Settings(s)) + if out.InitialReceiveWindow == nil || *out.InitialReceiveWindow != 0 || out.QuickAck == nil || *out.QuickAck { + t.Fatalf("explicit zeros lost: %+v", out) + } + }}, + {"empty-collections", "boundary: an experiment with no phases, cohort or evidence survives", func(t *testing.T) { + in := &treatment.Experiment{ID: "e", Location: "dc1", Treatment: fixtures.Treatment(), Policy: treatment.DefaultPolicy(), Status: treatment.StatusProposed, Revision: 1} + out, err := wire.ParseExperiment(wire.Experiment(in)) + if err != nil || !reflect.DeepEqual(in, out) { + t.Fatalf("round trip differs: %v\nin %+v\nout %+v", err, in, out) + } + }}, + {"alias-free", "corner: parsed optional settings do not alias the message", func(t *testing.T) { + w := wire.Settings(treatment.TCPSettings{RTOMinMs: ptr[uint32](5)}) + out := wire.ParseSettings(w) + *w.RtoMinMs = 9 + if *out.RTOMinMs != 5 { + t.Fatal("domain value aliases the message") + } + }}, + {"bad-destination", "corner: an unparsable destination becomes the zero prefix and fails validation, not decoding", func(t *testing.T) { + tr := wire.ParseTreatment(&pb.Treatment{Id: "t", Revision: 1, Destination: "nonsense", Settings: &pb.TCPSettings{CongestionControl: ptr("cubic")}}) + if tr.Destination != (netip.Prefix{}) { + t.Fatal("destination should be unset") + } + if err := treatment.ValidateTreatment(&tr); err == nil || !strings.Contains(err.Error(), "canonical unicast") { + t.Fatalf("expected destination error, got %v", err) + } + }}, + } { + t.Run(tc.name, tc.run) + } +} + +func TestParseRejections(t *testing.T) { + now := time.Unix(1700000000, 0) + e, _ := fixtures.ExperimentWire(now, treatment.DefaultPolicy()) + for _, tc := range []struct { + name, description string + run func() error + expected string + }{ + {"invalid-timestamp", "negative: a malformed timestamp is a decoding error", func() error { + m := proto.Clone(e).(*pb.Experiment) + m.ProposedAt = ×tamppb.Timestamp{Nanos: 1e9} + _, err := wire.ParseExperiment(m) + return err + }, "proposed_at"}, + {"invalid-phase-outcome", "negative: an unknown phase outcome is rejected", func() error { + m := proto.Clone(e).(*pb.Experiment) + m.Phases[0].Outcome = 250 + _, err := wire.ParseExperiment(m) + return err + }, "unknown wire outcome 250"}, + {"unknown-mode", "negative: an unknown assignment mode is rejected", func() error { + d := fixtures.DesiredWire(now) + d.Assignments[0].Mode = 77 + _, err := wire.ParseDesired(d) + return err + }, "unknown wire assignment mode 77"}, + {"kernel-bytes", "negative: a record carrying kernel route bytes is rejected", func() error { + r := &pb.CheckInRequest{AgentId: "a", Location: "l", Records: []*pb.ApplicationRecord{{Assignment: fixtures.DesiredWire(now).Assignments[0], InstalledRoute: []byte{1}}}} + _, err := wire.ParseReport(r) + return err + }, "local kernel state"}, + {"nil-experiment", "boundary: nil is a decoding error", func() error { _, err := wire.ParseExperiment(nil); return err }, "missing experiment"}, + {"nil-policy", "corner: a missing policy decodes to the zero policy and is left to validation", func() error { + m := proto.Clone(e).(*pb.Experiment) + m.Policy = nil + out, err := wire.ParseExperiment(m) + if err != nil { + return err + } + return treatment.ValidatePolicy(out.Policy) + }, "policy duration"}, + } { + t.Run(tc.name, func(t *testing.T) { + err := tc.run() + if err == nil || !strings.Contains(err.Error(), tc.expected) { + t.Fatalf("%s: expected error containing %q, got %v", tc.description, tc.expected, err) + } + }) + } +} + +func TestWireValidatorsMatchDomain(t *testing.T) { + now := time.Unix(1700000000, 0) + for _, tc := range []struct { + name, description string + err error + expected string + }{ + {"desired-valid", "positive: the fixture passes the wire validator", wire.ValidateDesired(fixtures.DesiredWire(now)), ""}, + {"treatment-valid", "positive: the fixture treatment passes", wire.ValidateTreatment(fixtures.DesiredWire(now).Assignments[0].Treatment), ""}, + {"evidence-valid", "positive: fixture evidence passes", func() error { + _, v := fixtures.ExperimentWire(now, treatment.DefaultPolicy()) + return wire.ValidateEvidence(v) + }(), ""}, + {"policy-valid", "positive: the default policy passes", wire.ValidatePolicy(wire.Policy(treatment.DefaultPolicy())), ""}, + {"treatment-nil-settings", "negative: nil settings report the same message as the old validator", wire.ValidateTreatment(&pb.Treatment{Id: "t", Revision: 1, Destination: "192.0.2.1/32"}), "requires"}, + {"desired-nil-assignment", "negative: a nil assignment is rejected before conversion", wire.ValidateDesired(&pb.DesiredState{AgentId: "a", Revision: 1, IssuedAt: timestamppb.Now(), Assignments: []*pb.Assignment{nil}}), "requires"}, + {"policy-nil", "boundary: nil policy is rejected", wire.ValidatePolicy(nil), "policy required"}, + {"evidence-nil", "boundary: nil evidence is rejected", wire.ValidateEvidence(nil), "interval"}, + {"desired-bad-lease", "corner: an invalid lease timestamp is a decoding error", func() error { + d := fixtures.DesiredWire(now) + d.Assignments[0].LeaseExpiresAt = ×tamppb.Timestamp{Nanos: -1} + return wire.ValidateDesired(d) + }(), "lease_expires_at"}, + } { + t.Run(tc.name, func(t *testing.T) { + if tc.expected == "" { + if tc.err != nil { + t.Fatalf("%s: expected acceptance, got %v", tc.description, tc.err) + } + } else if tc.err == nil || !strings.Contains(tc.err.Error(), tc.expected) { + t.Fatalf("%s: expected error containing %q, got %v", tc.description, tc.expected, tc.err) + } + }) + } +} diff --git a/proto/xtcp_treatment/v1/treatment.proto b/proto/xtcp_treatment/v1/treatment.proto index 23fefcdb4..a41d71cbe 100644 --- a/proto/xtcp_treatment/v1/treatment.proto +++ b/proto/xtcp_treatment/v1/treatment.proto @@ -317,4 +317,7 @@ message Experiment { google.protobuf.Timestamp proposed_at = 11; Evidence latest_evidence = 12; Outcome latest_outcome = 13 [(buf.validate.field).enum.defined_only = true]; + // When the controller last changed this experiment. Terminal experiments + // suppress rediscovery of their destination for a day after this instant. + google.protobuf.Timestamp updated_at = 14; } diff --git a/tools/tcp-controller-integration/main.go b/tools/tcp-controller-integration/main.go index 7ab7c30ac..6cd03c84d 100644 --- a/tools/tcp-controller-integration/main.go +++ b/tools/tcp-controller-integration/main.go @@ -11,6 +11,7 @@ import ( "io" "log" "net/http" + "net/netip" "os" "os/exec" "path/filepath" @@ -21,6 +22,7 @@ import ( pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/controller" "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" + "github.com/randomizedcoder/xtcp2/pkg/treatment/wire" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/protobuf/encoding/prototext" @@ -253,7 +255,7 @@ func propose(op pb.OperatorServiceClient, id string) *pb.Experiment { end := time.Now().Add(-20 * time.Second) ctx, cancel := rpcContext() defer cancel() - baseline = must(ch.Evidence(ctx, "test-dc", "198.18.0.1/32", id, hosts, end.Add(-60*time.Second), end)) + baseline = wire.Evidence(must(ch.Evidence(ctx, "test-dc", netip.MustParsePrefix("198.18.0.1/32"), id, hosts, end.Add(-60*time.Second), end))) require(len(baseline.Hosts) == 6, "baseline needs six hosts: %v", baseline.Hosts) for _, h := range baseline.Hosts { require(h.CollectionHealthy && h.Sockets >= 8 && h.BytesAcked >= 4096, "baseline not ready: %v", h) diff --git a/tools/tcp-controller-integration/soak.go b/tools/tcp-controller-integration/soak.go index 0224525b1..5d86b694f 100644 --- a/tools/tcp-controller-integration/soak.go +++ b/tools/tcp-controller-integration/soak.go @@ -12,6 +12,7 @@ import ( pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/experiment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/wire" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" ) @@ -170,16 +171,20 @@ func validateSoakRollback(e *pb.Experiment, injectedLatency bool) error { if e == nil || e.Status != pb.ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK || len(e.Phases) != 1 || e.Phases[0].Outcome != pb.Outcome_OUTCOME_HARMFUL || len(e.CohortOrder) == 0 || e.Policy == nil || e.Baseline == nil || e.LatestEvidence == nil { return fmt.Errorf("rollback is missing its harmful phase or evidence") } - if err := treatment.ValidateEvidence(e.Baseline); err != nil { + d, err := wire.ParseExperiment(e) + if err != nil { return err } - if err := treatment.ValidateEvidence(e.LatestEvidence); err != nil { + if err := treatment.ValidateEvidence(d.Baseline); err != nil { return err } - if err := treatment.ValidatePolicy(e.Policy); err != nil { + if err := treatment.ValidateEvidence(d.LatestEvidence); err != nil { return err } - _, harmful := experiment.HealthyEvidence(e, e.LatestEvidence, e.LatestEvidence.AnalyzedAt.AsTime()) + if err := treatment.ValidatePolicy(d.Policy); err != nil { + return err + } + _, harmful := experiment.HealthyEvidence(d, d.LatestEvidence, d.LatestEvidence.AnalyzedAt) if !harmful { return fmt.Errorf("rollback evidence does not cross a safety guard") } diff --git a/tools/tcp-controller-integration/soak_test.go b/tools/tcp-controller-integration/soak_test.go index 6dc9b73c3..4ec5e4ac1 100644 --- a/tools/tcp-controller-integration/soak_test.go +++ b/tools/tcp-controller-integration/soak_test.go @@ -7,6 +7,7 @@ import ( pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/wire" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -20,7 +21,7 @@ func TestSoakRollbackEvidence(t *testing.T) { latest.WindowStart = timestamppb.New(now.Add(-time.Minute)) latest.WindowEnd = timestamppb.New(now.Add(-20 * time.Second)) latest.Hosts[0].ExposedSockets = 32 - policy := treatment.DefaultPolicy() + policy := wire.Policy(treatment.DefaultPolicy()) policy.MaximumRttIncrease = 1 fixture := &pb.Experiment{Status: pb.ExperimentStatus_EXPERIMENT_STATUS_ROLLED_BACK, CohortOrder: []string{"canary"}, Policy: policy, Baseline: baseline, LatestEvidence: latest, Phases: []*pb.Phase{{Outcome: pb.Outcome_OUTCOME_HARMFUL}}} for _, tc := range []struct { From 6398338a09601dbba450184fbf940b152ee2ccc6 Mon Sep 17 00:00:00 2001 From: "randomizedcoder dave.seddon.ca@gmail.com" Date: Sun, 25 Oct 2026 19:14:52 -0700 Subject: [PATCH 07/27] refactor(treatment): journal-specific proto, proto-free reconcile, local and attribution packages Dual-mode agent, part 1. The host side no longer depends on the RPC proto package, so the watchdog and the journal readers can build without the gRPC service stubs that share a Go package with the RPC messages. Journal - New proto package xtcp_treatment_journal.v1 (journal.proto), generated into gen/go/xtcp_treatment_journal: Journal{agent_id, location, boot_id, desired, locally_suspended, suspension_reason, records, updated_at} with its own Treatment/Assignment/Record messages. A file format, not the model; the pre-rework LocalState text fails closed. - reconcile.State and reconcile.Record are the host-side types. Record embeds treatment.ApplicationRecord plus InstalledRoute/InstalledBootID, so State.Reports() cannot leak kernel bytes into a check-in. - reconcile.MarshalText/UnmarshalText/ReadSnapshot convert and validate; every load runs the domain validators and refuses unknown enum values. Reconciler - Backend.Plan takes *treatment.Treatment. Reconciler gains Location and writes the agent_id/location/boot_id header on every save; a journal written by another agent is refused for Run and SuspendLocal. - treatment.Assignment and DesiredState gain Clone and Equal. Packages - pkg/treatment/local: the shared controller-agent.json Config and LoadConfig, HostIdentity, Host (reconciler + goip adapter), Protect. agent.Config is an alias; agent.LoadConfig delegates. - pkg/treatment/attribution: Index built from a journal snapshot, Stamp on the hot path (one atomic load, one map lookup, no allocation), Stamper. The embedded agent publishes an index after every pass and an identity-only index before the first; a telemetry-only daemon will feed it from the journal directory in part 2. - pkg/treatment/agent builds on local and attribution; check-ins go through wire.Report and wire.ParseDesired. - cmd/xtcp2-treatmentctl builds on local and reconcile only. Tests (table-driven, description/expected, all four categories) - reconcile: journal round trip (populated, unset-optional, empty, tombstone-only), enum mirror, limits (max records, over cap, oversized, bad destination, bad lease), invalid snapshots (now also unknown mode and status, bad timestamp, bad identity, legacy format, garbage), lock-free reader under lock and against symlinks and the size limit, projections follow the snapshot, header identifies the writer, reports strip local fields. - attribution: stamp contracts (15 rows), identity modes (embedded, attribution, identity-only, nil), concurrent publish, benchmarks. - local: LoadConfig contracts, New contracts, Plan validates. - agent: lifecycle rows (connected, offline, undecodable desired state), stamp before first reconcile. - boundaries: reconcile forbids the RPC proto, wire, goip and gRPC; the watchdog forbids identity, agent, attribution, wire and the RPC proto; local and attribution rules added; the controller forbids local, attribution and the journal proto. Docs: package table, boundary sentence and journal format in docs/tcp-controller.md; D1/D2 rows and notes in docs/status-tcp-controller.md, with the xtcpnl -> flat record -> gRPC chain recorded as a TODO. Co-Authored-By: Claude Fable 5.1 --- cmd/xtcp2-treatmentctl/main.go | 27 +- docs/status-tcp-controller.md | 33 +- docs/tcp-controller.md | 23 +- .../v1/journal.grpc.pb.cc | 29 + .../v1/journal.grpc.pb.h | 59 + .../xtcp_treatment_journal/v1/journal.pb.cc | 3904 ++++++++++++++ .../xtcp_treatment_journal/v1/journal.pb.h | 4521 +++++++++++++++++ .../xtcp_treatment_journal/v1/journal.pb.dart | 856 ++++ .../v1/journal.pbenum.dart | 77 + .../v1/journal.pbjson.dart | 389 ++ gen/go/xtcp_treatment_journal/journal.pb.go | 885 ++++ .../journal_vtproto.pb.go | 2295 +++++++++ .../v1/journal.swagger.json | 44 + .../xtcp_treatment_journal/v1/journal_pb2.py | 52 + .../xtcp_treatment_journal/v1/journal_pb2.pyi | 147 + .../v1/journal_pb2_grpc.py | 4 + pkg/treatment/agent/agent.go | 265 +- pkg/treatment/agent/agent_test.go | 44 +- pkg/treatment/agent/backend_linux.go | 22 - pkg/treatment/agent/benchmark_test.go | 26 +- pkg/treatment/agent/contracts_test.go | 126 +- pkg/treatment/agent/lifecycle_test.go | 81 +- pkg/treatment/attribution/index.go | 150 + pkg/treatment/attribution/index_test.go | 200 + pkg/treatment/boundaries_test.go | 51 +- pkg/treatment/local/config.go | 83 + pkg/treatment/local/helpers_test.go | 5 + pkg/treatment/local/host.go | 107 + pkg/treatment/local/local_test.go | 110 + pkg/treatment/model.go | 49 + pkg/treatment/reconcile/contracts_test.go | 79 +- pkg/treatment/reconcile/journal.go | 65 +- pkg/treatment/reconcile/journal_test.go | 120 +- pkg/treatment/reconcile/journalpb.go | 276 + pkg/treatment/reconcile/journalpb_test.go | 164 + pkg/treatment/reconcile/reconcile.go | 184 +- pkg/treatment/reconcile/reconcile_test.go | 110 +- pkg/treatment/reconcile/state.go | 96 + proto/xtcp_treatment_journal/v1/journal.proto | 118 + tools/tcp-controller-integration/main.go | 5 +- 40 files changed, 15205 insertions(+), 676 deletions(-) create mode 100644 gen/cpp/xtcp_treatment_journal/v1/journal.grpc.pb.cc create mode 100644 gen/cpp/xtcp_treatment_journal/v1/journal.grpc.pb.h create mode 100644 gen/cpp/xtcp_treatment_journal/v1/journal.pb.cc create mode 100644 gen/cpp/xtcp_treatment_journal/v1/journal.pb.h create mode 100644 gen/dart/xtcp_treatment_journal/v1/journal.pb.dart create mode 100644 gen/dart/xtcp_treatment_journal/v1/journal.pbenum.dart create mode 100644 gen/dart/xtcp_treatment_journal/v1/journal.pbjson.dart create mode 100644 gen/go/xtcp_treatment_journal/journal.pb.go create mode 100644 gen/go/xtcp_treatment_journal/journal_vtproto.pb.go create mode 100644 gen/openapi/xtcp_treatment_journal/v1/journal.swagger.json create mode 100644 gen/python/xtcp_treatment_journal/v1/journal_pb2.py create mode 100644 gen/python/xtcp_treatment_journal/v1/journal_pb2.pyi create mode 100644 gen/python/xtcp_treatment_journal/v1/journal_pb2_grpc.py delete mode 100644 pkg/treatment/agent/backend_linux.go create mode 100644 pkg/treatment/attribution/index.go create mode 100644 pkg/treatment/attribution/index_test.go create mode 100644 pkg/treatment/local/config.go create mode 100644 pkg/treatment/local/helpers_test.go create mode 100644 pkg/treatment/local/host.go create mode 100644 pkg/treatment/local/local_test.go create mode 100644 pkg/treatment/reconcile/journalpb.go create mode 100644 pkg/treatment/reconcile/journalpb_test.go create mode 100644 pkg/treatment/reconcile/state.go create mode 100644 proto/xtcp_treatment_journal/v1/journal.proto diff --git a/cmd/xtcp2-treatmentctl/main.go b/cmd/xtcp2-treatmentctl/main.go index b1d9fef26..d9b97041b 100644 --- a/cmd/xtcp2-treatmentctl/main.go +++ b/cmd/xtcp2-treatmentctl/main.go @@ -1,5 +1,7 @@ // xtcp2-treatmentctl is the local, filesystem-permission-protected emergency -// interface and independent lease watchdog. It never needs a controller RPC. +// interface and independent lease watchdog. It never needs a controller RPC, +// a certificate or DNS: it builds on pkg/treatment/local and +// pkg/treatment/reconcile only, and the import-boundary test keeps it that way. package main import ( @@ -9,12 +11,12 @@ import ( "log" "os" "os/signal" + "path/filepath" "syscall" "time" - "github.com/randomizedcoder/xtcp2/pkg/treatment/agent" + "github.com/randomizedcoder/xtcp2/pkg/treatment/local" "github.com/randomizedcoder/xtcp2/pkg/treatment/reconcile" - "google.golang.org/protobuf/encoding/prototext" ) func main() { @@ -30,25 +32,26 @@ func run() error { flag.Parse() ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer cancel() - c, e := agent.LoadConfig(*config) + c, e := local.LoadConfig(*config) if e != nil { return e } - a, e := agent.NewLocal(c) + h, e := local.New(c, nil) if e != nil { return e } - defer a.Close() switch *mode { case "status": - b, e := os.ReadFile(c.Directory + "/state.textproto") + // Read without the lock: the snapshot is replaced atomically and + // status must answer while a reconcile pass holds the journal. + b, e := os.ReadFile(filepath.Join(c.Directory, reconcile.SnapshotFile)) if e != nil { return e } _, e = os.Stdout.Write(b) return e case "suspend", "resume": - if e = a.Reconciler.SuspendLocal(*mode == "suspend", *reason); e != nil { + if e = h.Reconciler.SuspendLocal(*mode == "suspend", *reason); e != nil { return e } case "reconcile": @@ -57,7 +60,7 @@ func run() error { defer t.Stop() for { pass, cancelPass := context.WithTimeout(ctx, 25*time.Second) - _, e = a.Reconciler.Run(pass, nil, reconcile.ModeExpiryOnly) + _, e = h.Reconciler.Run(pass, nil, reconcile.ModeExpiryOnly) cancelPass() if e != nil { log.Printf("watchdog: %v", e) @@ -71,9 +74,11 @@ func run() error { default: return fmt.Errorf("unknown mode %q", *mode) } - s, e := a.Reconciler.Run(ctx, nil, reconcile.ModeFull) + s, e := h.Reconciler.Run(ctx, nil, reconcile.ModeFull) if s != nil { - fmt.Print(prototext.Format(s)) + if b, err := reconcile.MarshalText(s); err == nil { + os.Stdout.Write(b) + } } return e } diff --git a/docs/status-tcp-controller.md b/docs/status-tcp-controller.md index 015d9172b..49023bd1e 100644 --- a/docs/status-tcp-controller.md +++ b/docs/status-tcp-controller.md @@ -11,8 +11,10 @@ review being addressed. Items are listed in execution order. | 2 | `pkg/treatment` split by audience (`reconcile`, `experiment`, `agent`) | done | 2ca932f8 | | 8 | proto moved to `xtcp_treatment/v1` with `buf.validate` rules | done | 460164b1 | | 3 | explicit state machine; `Outcome` enum | done | 85b2cf59 | -| 4 | repository ports, relational schema, pgx, domain types, `wire` | done | this commit | -| 5 | evidence port (`CandidateSource`), ClickHouse as one adapter | next | | +| 4 | repository ports, relational schema, pgx, domain types, `wire` | done | eae1d87d | +| D1 | dual-mode agent, part 1: journal-specific proto, proto-free `reconcile`, `local`, `attribution`, watchdog off the agent | done | this commit | +| D2 | dual-mode agent, part 2: `-treatmentAttribution` journal watcher, `treatment` build tag, `cmd/xtcp2-treatment-agent`, Nix and module wiring | next | | +| 5 | evidence port (`CandidateSource`), ClickHouse as one adapter | pending | | | 6 | auth as interceptor with per-RPC role option | pending | | | 7 | typed `xtcpnl.RuleWrite`; backend to `internal/goip/actuate` | pending | | | 9 | identity/location startup check | pending | | @@ -22,7 +24,23 @@ review being addressed. Items are listed in execution order. Dual-mode agent (library + `cmd/xtcp2-treatment-agent` + `treatment` build tag in `cmd/xtcp2`, with an `attribution` journal reader for the untagged daemon) -is decided and lands after item 4; the watchdog stays a separate process. +is decided and lands after item 4 as D1 and D2; the watchdog stays a separate +process. + +## D1 notes + +- The journal is `xtcp_treatment_journal.v1.Journal` (prototext), generated + into its own Go package so the host side never links the RPC stubs. Field + numbers and names differ from the old `LocalState`; an old snapshot fails + closed with "invalid treatment journal". No deployed journals exist yet. +- `reconcile.State`/`reconcile.Record` are the host-side types; `Record` + embeds `treatment.ApplicationRecord` plus the kernel-local fields, so a + check-in (`State.Reports()`) cannot leak route bytes by construction. +- `pkg/treatment/local` owns `Config`, `LoadConfig`, `HostIdentity` and the + goip adapter; `agent.Config` is an alias. The watchdog builds on `local` + and `reconcile` only. +- `pkg/treatment/attribution` holds the exposure index and `Stamp`; the agent + publishes an index after every pass (identity-only before the first). ## Item 4 notes @@ -40,10 +58,11 @@ is decided and lands after item 4; the watchdog stays a separate process. ## TODO -- Journal is still prototext of the generated `LocalState`; it becomes a - journal-specific message converted from domain types with the dual-mode agent - work. Until then `pkg/treatment/reconcile` keeps importing the proto package - (and through it gRPC); the boundary test carries the TODO. +- The watchdog and `pkg/treatment/local` still link gRPC, through + `pkg/xtcpnl` -> `gen/go/xtcp_flat_record`, whose Go package carries that + proto's gRPC and gateway stubs. Generating the flat record's service stubs + into a separate package would let the boundary test forbid gRPC for every + goip user; `reconcile` is already gRPC-free. Boundary rule carries the TODO. - `Experiments.List` loads each experiment with its own round trips (cohort, phases, evidence). Fine for `inspect` limits; batch before any fleet-wide listing path. diff --git a/docs/tcp-controller.md b/docs/tcp-controller.md index 08c592b86..8974985ba 100644 --- a/docs/tcp-controller.md +++ b/docs/tcp-controller.md @@ -24,16 +24,19 @@ flowchart LR The implementation lives under [pkg/treatment](../pkg/treatment), split by audience so that each binary links only the half of the contract it needs, with -a typed [protobuf contract](../proto/xtcp_treatment/v1/) (`xtcp_treatment.v1`: `treatment.proto` types, `agent.proto`, `operator.proto`): +a typed [protobuf contract](../proto/xtcp_treatment/v1/) (`xtcp_treatment.v1`: `treatment.proto` types, `agent.proto`, `operator.proto`) and a separate +[journal file format](../proto/xtcp_treatment_journal/v1/journal.proto) (`xtcp_treatment_journal.v1`) that the host side reads and writes without the RPC stubs: | Package | Used by | Contents | | --- | --- | --- | | `pkg/treatment` | everyone | the domain model (`Treatment`, `Experiment`, `Evidence`, `DesiredState`, enums), limits and validators; proto-free | | `pkg/treatment/wire` | gRPC boundary | domain ↔ generated proto conversion; `Parse*` fails closed on unknown enums and invalid timestamps | | `pkg/treatment/identity` | agent, controller, tools | SPIFFE URI identity, mTLS configuration, gRPC peer resolution | -| `pkg/treatment/reconcile` | agent, watchdog | intent journal, reconciler, `Backend` interface, `Mode` (full or expiry-only) | +| `pkg/treatment/reconcile` | agent, watchdog | intent journal (`State`, `Record`, the `xtcp_treatment_journal.v1` file format), reconciler, `Backend` interface, `Mode` (full or expiry-only); domain types only, no RPC proto, no gRPC | +| `pkg/treatment/local` | agent, watchdog, `treatmentctl` | the shared `controller-agent.json` config and the goip-backed reconciler built from it; no TLS, no controller | +| `pkg/treatment/attribution` | `xtcp2`, agent | the exposure index built from a journal snapshot and the hot-path telemetry stamp; the same code serves the embedded agent and a telemetry-only daemon | | `pkg/treatment/experiment` | controller | evaluation statistics, cohort allocation, phase advancement, state machine; pure functions over domain types | -| `pkg/treatment/agent` | `xtcp2` | check-in loop, watch stream, capabilities, telemetry stamping | +| `pkg/treatment/agent` | `xtcp2`, agent binary | check-in loop, watch stream, capabilities, over `local` and `attribution`; a library with two front ends | | `pkg/treatment/controller` | `xtcp2-controller` | gRPC handlers, maintenance and discovery over the store ports; ClickHouse evidence | | `pkg/treatment/controller/store` | controller | repository interfaces (`Agents`, `Experiments`, `Evidence`, `Holds`, `Audit`, `Metrics`) and `Store.Tx` | | `pkg/treatment/controller/store/postgres` | `xtcp2-controller` | the only package with SQL: relational schema, pgx through `database/sql`, advisory-lock transactions | @@ -47,9 +50,11 @@ only JSON column is the append-only audit payload. `pkg/treatment/boundaries_test.go` enforces the layering: `xtcp2` and the agent never link the controller, a SQL driver or the ClickHouse client; the domain -and experiment packages never import the proto or gRPC; SQL lives only in the -postgres store; the controller never links the host-side reconcile package or -goip. The binaries are: +and experiment packages never import the proto or gRPC; the reconcile package +never imports the RPC proto or gRPC; the watchdog never links TLS identity, the +agent or the RPC proto; SQL lives only in the postgres store; the controller +never links the host-side reconcile, local or attribution packages or goip. +The binaries are: | Binary | Responsibility | | --- | --- | @@ -311,7 +316,11 @@ occupying all ten slots. ## Persistence, restart and withdrawal -`/var/lib/xtcp2/routes/state.textproto` is the authoritative atomic snapshot. +`/var/lib/xtcp2/routes/state.textproto` is the authoritative atomic snapshot: +prototext of `xtcp_treatment_journal.v1.Journal`, a file format of its own with +a header naming the writing agent, location and boot. It is converted to and +from the domain types on every pass and validated on every load; a snapshot in +any other shape (including the pre-rework `LocalState` text) fails closed. `assignment-.textproto` files are readable projections containing receipt, attempt, application, verification and removal times. Projection files are never replayed independently. A private directory, file lock, temp-file rename and file diff --git a/gen/cpp/xtcp_treatment_journal/v1/journal.grpc.pb.cc b/gen/cpp/xtcp_treatment_journal/v1/journal.grpc.pb.cc new file mode 100644 index 000000000..5762ceb25 --- /dev/null +++ b/gen/cpp/xtcp_treatment_journal/v1/journal.grpc.pb.cc @@ -0,0 +1,29 @@ +// Generated by the gRPC C++ plugin. +// If you make any local change, they will be lost. +// source: xtcp_treatment_journal/v1/journal.proto + +#include "xtcp_treatment_journal/v1/journal.pb.h" +#include "xtcp_treatment_journal/v1/journal.grpc.pb.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +namespace xtcp_treatment_journal { +namespace v1 { + +} // namespace xtcp_treatment_journal +} // namespace v1 +#include + diff --git a/gen/cpp/xtcp_treatment_journal/v1/journal.grpc.pb.h b/gen/cpp/xtcp_treatment_journal/v1/journal.grpc.pb.h new file mode 100644 index 000000000..8bb9b7a8b --- /dev/null +++ b/gen/cpp/xtcp_treatment_journal/v1/journal.grpc.pb.h @@ -0,0 +1,59 @@ +// Generated by the gRPC C++ plugin. +// If you make any local change, they will be lost. +// source: xtcp_treatment_journal/v1/journal.proto +// Original file comments: +// +// xTCP - treatment journal +// +// The host-side intent journal of the route treatment agent: the desired +// state last accepted from the controller, the local hold, and one record per +// assignment with the exact kernel route bytes that were installed. It is a +// FILE FORMAT, read and written only on the host by the agent, the watchdog +// and the local treatmentctl commands, and never an RPC payload. +// +// It is a separate proto package from xtcp_treatment.v1 on purpose: the host +// side (pkg/treatment/reconcile, the watchdog) must be able to read and write +// the journal without linking the gRPC service stubs that share a Go package +// with the RPC messages. The messages below therefore repeat the treatment +// shape rather than importing it; pkg/treatment/reconcile converts them to +// and from the domain types, and the domain validators run on every load. +// +// Values of the two enums mirror xtcp_treatment.v1 one-for-one and the domain +// enums in pkg/treatment; reconcile's tests pin the correspondence. +// +// See docs/tcp-controller.md, "Persistence, restart and withdrawal". +// +#ifndef GRPC_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto__INCLUDED +#define GRPC_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto__INCLUDED + +#include "xtcp_treatment_journal/v1/journal.pb.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xtcp_treatment_journal { +namespace v1 { + +} // namespace v1 +} // namespace xtcp_treatment_journal + + +#include +#endif // GRPC_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto__INCLUDED diff --git a/gen/cpp/xtcp_treatment_journal/v1/journal.pb.cc b/gen/cpp/xtcp_treatment_journal/v1/journal.pb.cc new file mode 100644 index 000000000..6bc978812 --- /dev/null +++ b/gen/cpp/xtcp_treatment_journal/v1/journal.pb.cc @@ -0,0 +1,3904 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE +// source: xtcp_treatment_journal/v1/journal.proto +// Protobuf C++ Version: 7.36.2 + +#include "xtcp_treatment_journal/v1/journal.pb.h" + +#include +#include +#include "google/protobuf/io/coded_stream.h" +#include "google/protobuf/generated_message_tctable_impl.h" +#include "google/protobuf/internal_visibility.h" +#include "google/protobuf/extension_set.h" +#include "google/protobuf/generated_message_util.h" +#include "google/protobuf/wire_format_lite.h" +#include "google/protobuf/descriptor.h" +#include "google/protobuf/generated_message_reflection.h" +#include "google/protobuf/reflection_ops.h" +#include "google/protobuf/wire_format.h" +// @@protoc_insertion_point(includes) + +// Must be included last. +#include "google/protobuf/port_def.inc" +PROTOBUF_PRAGMA_INIT_SEG +namespace _pb = ::google::protobuf; +namespace _pbi = ::google::protobuf::internal; +namespace _fl = ::google::protobuf::internal::field_layout; +#ifdef PROTOBUF_MESSAGE_GLOBALS +namespace { +PROTOBUF_CONSTINIT ::google::protobuf::internal::ReflectionData + file_reflection_data[] = { + // ::xtcp_treatment_journal::v1::TCPSettings + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto, /* tracker*/ nullptr,}, + // ::xtcp_treatment_journal::v1::Treatment + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto, /* tracker*/ nullptr,}, + // ::xtcp_treatment_journal::v1::Assignment + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto, /* tracker*/ nullptr,}, + // ::xtcp_treatment_journal::v1::Desired + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto, /* tracker*/ nullptr,}, + // ::xtcp_treatment_journal::v1::Record + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto, /* tracker*/ nullptr,}, + // ::xtcp_treatment_journal::v1::Journal + {&::_pbi::kDescriptorMethods, &::descriptor_table_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto, /* tracker*/ nullptr,}, +}; +} // namespace +#endif +namespace xtcp_treatment_journal { +namespace v1 { +class TCPSettings::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_._has_bits_); +}; + +constexpr TCPSettings::ParseTableT_ TCPSettings::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_._has_bits_), + 0, // no _extensions_ + 7, 56, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294967168, // skipmap + offsetof(ParseTableT_, field_entries), + 7, // num_field_entries + 0, // num_aux_entries + offsetof(ParseTableT_, field_names), // no aux_entries + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment_journal::v1::TCPSettings>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + {::_pbi::TcParser::MiniParse, {}}, + // optional string congestion_control = 1 [json_name = "congestionControl"]; + {::_pbi::TcParser::FastUS1, + {10, 0, 0, + PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.congestion_control_)}}, + // bool congestion_lock = 2 [json_name = "congestionLock"]; + {::_pbi::TcParser::SingularVarintNoZag1(), + {16, 4, 0, + PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.congestion_lock_)}}, + // optional uint32 rto_min_ms = 3 [json_name = "rtoMinMs"]; + {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(TCPSettings, _impl_.rto_min_ms_), 1>(), + {24, 1, 0, + PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.rto_min_ms_)}}, + // optional uint32 initial_congestion_window = 4 [json_name = "initialCongestionWindow"]; + {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(TCPSettings, _impl_.initial_congestion_window_), 2>(), + {32, 2, 0, + PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.initial_congestion_window_)}}, + // optional uint32 initial_receive_window = 5 [json_name = "initialReceiveWindow"]; + {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(TCPSettings, _impl_.initial_receive_window_), 3>(), + {40, 3, 0, + PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.initial_receive_window_)}}, + // optional bool quick_ack = 6 [json_name = "quickAck"]; + {::_pbi::TcParser::SingularVarintNoZag1(), + {48, 5, 0, + PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.quick_ack_)}}, + // optional bool ecn = 7 [json_name = "ecn"]; + {::_pbi::TcParser::SingularVarintNoZag1(), + {56, 6, 0, + PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.ecn_)}}, + }}, {{ + 65535, 65535 + }}, {{ + // optional string congestion_control = 1 [json_name = "congestionControl"]; + {PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.congestion_control_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // bool congestion_lock = 2 [json_name = "congestionLock"]; + {PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.congestion_lock_), _Internal::kHasBitsOffset + 4, 0, (0 | ::_fl::kFcOptional | ::_fl::kBool)}, + // optional uint32 rto_min_ms = 3 [json_name = "rtoMinMs"]; + {PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.rto_min_ms_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt32)}, + // optional uint32 initial_congestion_window = 4 [json_name = "initialCongestionWindow"]; + {PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.initial_congestion_window_), _Internal::kHasBitsOffset + 2, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt32)}, + // optional uint32 initial_receive_window = 5 [json_name = "initialReceiveWindow"]; + {PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.initial_receive_window_), _Internal::kHasBitsOffset + 3, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt32)}, + // optional bool quick_ack = 6 [json_name = "quickAck"]; + {PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.quick_ack_), _Internal::kHasBitsOffset + 5, 0, (0 | ::_fl::kFcOptional | ::_fl::kBool)}, + // optional bool ecn = 7 [json_name = "ecn"]; + {PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.ecn_), _Internal::kHasBitsOffset + 6, 0, (0 | ::_fl::kFcOptional | ::_fl::kBool)}, + }}, + // no aux_entries + {{ + "\45\22\0\0\0\0\0\0" + "xtcp_treatment_journal.v1.TCPSettings" + "congestion_control" + }}, + }; +} + + +inline constexpr TCPSettings::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : congestion_control_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + rto_min_ms_{0u}, + initial_congestion_window_{0u}, + initial_receive_window_{0u}, + congestion_lock_{false}, + quick_ack_{false}, + ecn_{false} {} + +template +constexpr TCPSettings::TCPSettings(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL TCPSettings::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) TCPSettings(arena); +} +constexpr auto TCPSettings::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::CopyInit(sizeof(TCPSettings), alignof(TCPSettings)); +} +constexpr auto TCPSettings::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &TCPSettings::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &TCPSettings::SharedDtor, + Super_::GetClearImpl(), &TCPSettings::ByteSizeLong, + &TCPSettings::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[0], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct TCPSettingsGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr TCPSettingsGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + TCPSettings_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(TCPSettings::InternalGenerateClassData_( + _default, &TCPSettings_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~TCPSettingsGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) TCPSettings _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(TCPSettingsGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST TCPSettingsGlobalsTypeInternal TCPSettings_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* TCPSettings_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return TCPSettings_globals_.GetClassData(); +#else + return TCPSettings_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +class Treatment::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(Treatment, _impl_._has_bits_); +}; + +constexpr Treatment::ParseTableT_ Treatment::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(Treatment, _impl_._has_bits_), + 0, // no _extensions_ + 5, 56, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294967264, // skipmap + offsetof(ParseTableT_, field_entries), + 5, // num_field_entries + 1, // num_aux_entries + offsetof(ParseTableT_, aux_entries), + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment_journal::v1::Treatment>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + {::_pbi::TcParser::MiniParse, {}}, + // string id = 1 [json_name = "id"]; + {::_pbi::TcParser::FastUS1, + {10, 0, 0, + PROTOBUF_FIELD_OFFSET(Treatment, _impl_.id_)}}, + // uint64 revision = 2 [json_name = "revision"]; + {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(Treatment, _impl_.revision_), 3>(), + {16, 3, 0, + PROTOBUF_FIELD_OFFSET(Treatment, _impl_.revision_)}}, + // string destination = 3 [json_name = "destination"]; + {::_pbi::TcParser::FastUS1, + {26, 1, 0, + PROTOBUF_FIELD_OFFSET(Treatment, _impl_.destination_)}}, + // .xtcp_treatment_journal.v1.TCPSettings settings = 4 [json_name = "settings"]; + {::_pbi::TcParser::FastMtS1, + {34, 2, 0, + PROTOBUF_FIELD_OFFSET(Treatment, _impl_.settings_)}}, + // bool ecn_fabric_verified = 5 [json_name = "ecnFabricVerified"]; + {::_pbi::TcParser::SingularVarintNoZag1(), + {40, 4, 0, + PROTOBUF_FIELD_OFFSET(Treatment, _impl_.ecn_fabric_verified_)}}, + {::_pbi::TcParser::MiniParse, {}}, + {::_pbi::TcParser::MiniParse, {}}, + }}, {{ + 65535, 65535 + }}, {{ + // string id = 1 [json_name = "id"]; + {PROTOBUF_FIELD_OFFSET(Treatment, _impl_.id_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // uint64 revision = 2 [json_name = "revision"]; + {PROTOBUF_FIELD_OFFSET(Treatment, _impl_.revision_), _Internal::kHasBitsOffset + 3, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt64)}, + // string destination = 3 [json_name = "destination"]; + {PROTOBUF_FIELD_OFFSET(Treatment, _impl_.destination_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // .xtcp_treatment_journal.v1.TCPSettings settings = 4 [json_name = "settings"]; + {PROTOBUF_FIELD_OFFSET(Treatment, _impl_.settings_), _Internal::kHasBitsOffset + 2, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // bool ecn_fabric_verified = 5 [json_name = "ecnFabricVerified"]; + {PROTOBUF_FIELD_OFFSET(Treatment, _impl_.ecn_fabric_verified_), _Internal::kHasBitsOffset + 4, 0, (0 | ::_fl::kFcOptional | ::_fl::kBool)}, + }}, + {{ + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment_journal::v1::TCPSettings>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment_journal::v1::TCPSettings_globals_}, + #endif + }}, + {{ + "\43\2\0\13\0\0\0\0" + "xtcp_treatment_journal.v1.Treatment" + "id" + "destination" + }}, + }; +} + + +inline constexpr Treatment::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : id_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + destination_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + settings_{nullptr}, + revision_{::uint64_t{0u}}, + ecn_fabric_verified_{false} {} + +template +constexpr Treatment::Treatment(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL Treatment::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) Treatment(arena); +} +constexpr auto Treatment::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::CopyInit(sizeof(Treatment), alignof(Treatment)); +} +constexpr auto Treatment::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &Treatment::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &Treatment::SharedDtor, + Super_::GetClearImpl(), &Treatment::ByteSizeLong, + &Treatment::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(Treatment, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[1], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct TreatmentGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr TreatmentGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + Treatment_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(Treatment::InternalGenerateClassData_( + _default, &Treatment_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~TreatmentGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) Treatment _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(TreatmentGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST TreatmentGlobalsTypeInternal Treatment_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* Treatment_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return Treatment_globals_.GetClassData(); +#else + return Treatment_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +class Assignment::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(Assignment, _impl_._has_bits_); +}; + +constexpr Assignment::ParseTableT_ Assignment::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(Assignment, _impl_._has_bits_), + 0, // no _extensions_ + 6, 56, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294967232, // skipmap + offsetof(ParseTableT_, field_entries), + 6, // num_field_entries + 3, // num_aux_entries + offsetof(ParseTableT_, aux_entries), + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment_journal::v1::Assignment>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + {::_pbi::TcParser::MiniParse, {}}, + // string experiment_id = 1 [json_name = "experimentId"]; + {::_pbi::TcParser::FastUS1, + {10, 0, 0, + PROTOBUF_FIELD_OFFSET(Assignment, _impl_.experiment_id_)}}, + // .xtcp_treatment_journal.v1.Treatment treatment = 2 [json_name = "treatment"]; + {::_pbi::TcParser::FastMtS1, + {18, 1, 0, + PROTOBUF_FIELD_OFFSET(Assignment, _impl_.treatment_)}}, + // .xtcp_treatment_journal.v1.AssignmentMode mode = 3 [json_name = "mode"]; + {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(Assignment, _impl_.mode_), 5>(), + {24, 5, 0, + PROTOBUF_FIELD_OFFSET(Assignment, _impl_.mode_)}}, + // .google.protobuf.Timestamp lease_expires_at = 4 [json_name = "leaseExpiresAt"]; + {::_pbi::TcParser::FastMtS1, + {34, 2, 1, + PROTOBUF_FIELD_OFFSET(Assignment, _impl_.lease_expires_at_)}}, + // .xtcp_treatment_journal.v1.Treatment predecessor = 5 [json_name = "predecessor"]; + {::_pbi::TcParser::FastMtS1, + {42, 3, 2, + PROTOBUF_FIELD_OFFSET(Assignment, _impl_.predecessor_)}}, + // uint64 generation = 6 [json_name = "generation"]; + {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(Assignment, _impl_.generation_), 4>(), + {48, 4, 0, + PROTOBUF_FIELD_OFFSET(Assignment, _impl_.generation_)}}, + {::_pbi::TcParser::MiniParse, {}}, + }}, {{ + 65535, 65535 + }}, {{ + // string experiment_id = 1 [json_name = "experimentId"]; + {PROTOBUF_FIELD_OFFSET(Assignment, _impl_.experiment_id_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // .xtcp_treatment_journal.v1.Treatment treatment = 2 [json_name = "treatment"]; + {PROTOBUF_FIELD_OFFSET(Assignment, _impl_.treatment_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // .xtcp_treatment_journal.v1.AssignmentMode mode = 3 [json_name = "mode"]; + {PROTOBUF_FIELD_OFFSET(Assignment, _impl_.mode_), _Internal::kHasBitsOffset + 5, 0, (0 | ::_fl::kFcOptional | ::_fl::kOpenEnum)}, + // .google.protobuf.Timestamp lease_expires_at = 4 [json_name = "leaseExpiresAt"]; + {PROTOBUF_FIELD_OFFSET(Assignment, _impl_.lease_expires_at_), _Internal::kHasBitsOffset + 2, 1, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // .xtcp_treatment_journal.v1.Treatment predecessor = 5 [json_name = "predecessor"]; + {PROTOBUF_FIELD_OFFSET(Assignment, _impl_.predecessor_), _Internal::kHasBitsOffset + 3, 2, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // uint64 generation = 6 [json_name = "generation"]; + {PROTOBUF_FIELD_OFFSET(Assignment, _impl_.generation_), _Internal::kHasBitsOffset + 4, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt64)}, + }}, + {{ + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment_journal::v1::Treatment>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment_journal::v1::Treatment_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::google::protobuf::Timestamp>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::google::protobuf::Timestamp_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment_journal::v1::Treatment>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment_journal::v1::Treatment_globals_}, + #endif + }}, + {{ + "\44\15\0\0\0\0\0\0" + "xtcp_treatment_journal.v1.Assignment" + "experiment_id" + }}, + }; +} + + +inline constexpr Assignment::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : experiment_id_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + treatment_{nullptr}, + lease_expires_at_{nullptr}, + predecessor_{nullptr}, + generation_{::uint64_t{0u}}, + mode_{static_cast< ::xtcp_treatment_journal::v1::AssignmentMode >(0)} {} + +template +constexpr Assignment::Assignment(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL Assignment::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) Assignment(arena); +} +constexpr auto Assignment::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::CopyInit(sizeof(Assignment), alignof(Assignment)); +} +constexpr auto Assignment::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &Assignment::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &Assignment::SharedDtor, + Super_::GetClearImpl(), &Assignment::ByteSizeLong, + &Assignment::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(Assignment, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[2], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct AssignmentGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr AssignmentGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + Assignment_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(Assignment::InternalGenerateClassData_( + _default, &Assignment_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~AssignmentGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) Assignment _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(AssignmentGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST AssignmentGlobalsTypeInternal Assignment_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* Assignment_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return Assignment_globals_.GetClassData(); +#else + return Assignment_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +class Record::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(Record, _impl_._has_bits_); +}; + +constexpr Record::ParseTableT_ Record::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(Record, _impl_._has_bits_), + 0, // no _extensions_ + 12, 120, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294963200, // skipmap + offsetof(ParseTableT_, field_entries), + 12, // num_field_entries + 6, // num_aux_entries + offsetof(ParseTableT_, aux_entries), + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment_journal::v1::Record>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + {::_pbi::TcParser::MiniParse, {}}, + // .xtcp_treatment_journal.v1.Assignment assignment = 1 [json_name = "assignment"]; + {::_pbi::TcParser::FastMtS1, + {10, 3, 0, + PROTOBUF_FIELD_OFFSET(Record, _impl_.assignment_)}}, + // uint64 desired_revision = 2 [json_name = "desiredRevision"]; + {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(Record, _impl_.desired_revision_), 9>(), + {16, 9, 0, + PROTOBUF_FIELD_OFFSET(Record, _impl_.desired_revision_)}}, + // .xtcp_treatment_journal.v1.ApplyStatus status = 3 [json_name = "status"]; + {::_pbi::TcParser::SingularVarintNoZag1<::uint32_t, offsetof(Record, _impl_.status_), 10>(), + {24, 10, 0, + PROTOBUF_FIELD_OFFSET(Record, _impl_.status_)}}, + // .google.protobuf.Timestamp received_at = 4 [json_name = "receivedAt"]; + {::_pbi::TcParser::FastMtS1, + {34, 4, 1, + PROTOBUF_FIELD_OFFSET(Record, _impl_.received_at_)}}, + // .google.protobuf.Timestamp attempted_at = 5 [json_name = "attemptedAt"]; + {::_pbi::TcParser::FastMtS1, + {42, 5, 2, + PROTOBUF_FIELD_OFFSET(Record, _impl_.attempted_at_)}}, + // .google.protobuf.Timestamp applied_at = 6 [json_name = "appliedAt"]; + {::_pbi::TcParser::FastMtS1, + {50, 6, 3, + PROTOBUF_FIELD_OFFSET(Record, _impl_.applied_at_)}}, + // .google.protobuf.Timestamp verified_at = 7 [json_name = "verifiedAt"]; + {::_pbi::TcParser::FastMtS1, + {58, 7, 4, + PROTOBUF_FIELD_OFFSET(Record, _impl_.verified_at_)}}, + // .google.protobuf.Timestamp removed_at = 8 [json_name = "removedAt"]; + {::_pbi::TcParser::FastMtS1, + {66, 8, 5, + PROTOBUF_FIELD_OFFSET(Record, _impl_.removed_at_)}}, + // string error = 9 [json_name = "error"]; + {::_pbi::TcParser::FastUS1, + {74, 0, 0, + PROTOBUF_FIELD_OFFSET(Record, _impl_.error_)}}, + // bytes installed_route = 10 [json_name = "installedRoute"]; + {::_pbi::TcParser::FastBS1, + {82, 1, 0, + PROTOBUF_FIELD_OFFSET(Record, _impl_.installed_route_)}}, + // string installed_boot_id = 11 [json_name = "installedBootId"]; + {::_pbi::TcParser::FastUS1, + {90, 2, 0, + PROTOBUF_FIELD_OFFSET(Record, _impl_.installed_boot_id_)}}, + // bool expired_latched = 12 [json_name = "expiredLatched"]; + {::_pbi::TcParser::SingularVarintNoZag1(), + {96, 11, 0, + PROTOBUF_FIELD_OFFSET(Record, _impl_.expired_latched_)}}, + {::_pbi::TcParser::MiniParse, {}}, + {::_pbi::TcParser::MiniParse, {}}, + {::_pbi::TcParser::MiniParse, {}}, + }}, {{ + 65535, 65535 + }}, {{ + // .xtcp_treatment_journal.v1.Assignment assignment = 1 [json_name = "assignment"]; + {PROTOBUF_FIELD_OFFSET(Record, _impl_.assignment_), _Internal::kHasBitsOffset + 3, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // uint64 desired_revision = 2 [json_name = "desiredRevision"]; + {PROTOBUF_FIELD_OFFSET(Record, _impl_.desired_revision_), _Internal::kHasBitsOffset + 9, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt64)}, + // .xtcp_treatment_journal.v1.ApplyStatus status = 3 [json_name = "status"]; + {PROTOBUF_FIELD_OFFSET(Record, _impl_.status_), _Internal::kHasBitsOffset + 10, 0, (0 | ::_fl::kFcOptional | ::_fl::kOpenEnum)}, + // .google.protobuf.Timestamp received_at = 4 [json_name = "receivedAt"]; + {PROTOBUF_FIELD_OFFSET(Record, _impl_.received_at_), _Internal::kHasBitsOffset + 4, 1, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // .google.protobuf.Timestamp attempted_at = 5 [json_name = "attemptedAt"]; + {PROTOBUF_FIELD_OFFSET(Record, _impl_.attempted_at_), _Internal::kHasBitsOffset + 5, 2, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // .google.protobuf.Timestamp applied_at = 6 [json_name = "appliedAt"]; + {PROTOBUF_FIELD_OFFSET(Record, _impl_.applied_at_), _Internal::kHasBitsOffset + 6, 3, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // .google.protobuf.Timestamp verified_at = 7 [json_name = "verifiedAt"]; + {PROTOBUF_FIELD_OFFSET(Record, _impl_.verified_at_), _Internal::kHasBitsOffset + 7, 4, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // .google.protobuf.Timestamp removed_at = 8 [json_name = "removedAt"]; + {PROTOBUF_FIELD_OFFSET(Record, _impl_.removed_at_), _Internal::kHasBitsOffset + 8, 5, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // string error = 9 [json_name = "error"]; + {PROTOBUF_FIELD_OFFSET(Record, _impl_.error_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // bytes installed_route = 10 [json_name = "installedRoute"]; + {PROTOBUF_FIELD_OFFSET(Record, _impl_.installed_route_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kBytes | ::_fl::kRepAString)}, + // string installed_boot_id = 11 [json_name = "installedBootId"]; + {PROTOBUF_FIELD_OFFSET(Record, _impl_.installed_boot_id_), _Internal::kHasBitsOffset + 2, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // bool expired_latched = 12 [json_name = "expiredLatched"]; + {PROTOBUF_FIELD_OFFSET(Record, _impl_.expired_latched_), _Internal::kHasBitsOffset + 11, 0, (0 | ::_fl::kFcOptional | ::_fl::kBool)}, + }}, + {{ + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment_journal::v1::Assignment>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment_journal::v1::Assignment_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::google::protobuf::Timestamp>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::google::protobuf::Timestamp_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::google::protobuf::Timestamp>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::google::protobuf::Timestamp_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::google::protobuf::Timestamp>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::google::protobuf::Timestamp_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::google::protobuf::Timestamp>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::google::protobuf::Timestamp_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::google::protobuf::Timestamp>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::google::protobuf::Timestamp_globals_}, + #endif + }}, + {{ + "\40\0\0\0\0\0\0\0\0\5\0\21\0\0\0\0" + "xtcp_treatment_journal.v1.Record" + "error" + "installed_boot_id" + }}, + }; +} + + +inline constexpr Record::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : error_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + installed_route_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + installed_boot_id_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + assignment_{nullptr}, + received_at_{nullptr}, + attempted_at_{nullptr}, + applied_at_{nullptr}, + verified_at_{nullptr}, + removed_at_{nullptr}, + desired_revision_{::uint64_t{0u}}, + status_{static_cast< ::xtcp_treatment_journal::v1::ApplyStatus >(0)}, + expired_latched_{false} {} + +template +constexpr Record::Record(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL Record::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) Record(arena); +} +constexpr auto Record::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::CopyInit(sizeof(Record), alignof(Record)); +} +constexpr auto Record::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &Record::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &Record::SharedDtor, + Super_::GetClearImpl(), &Record::ByteSizeLong, + &Record::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(Record, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[4], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct RecordGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr RecordGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + Record_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(Record::InternalGenerateClassData_( + _default, &Record_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~RecordGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) Record _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(RecordGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST RecordGlobalsTypeInternal Record_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* Record_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return Record_globals_.GetClassData(); +#else + return Record_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +class Desired::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(Desired, _impl_._has_bits_); +}; + +constexpr Desired::ParseTableT_ Desired::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(Desired, _impl_._has_bits_), + 0, // no _extensions_ + 4, 24, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294967280, // skipmap + offsetof(ParseTableT_, field_entries), + 4, // num_field_entries + 2, // num_aux_entries + offsetof(ParseTableT_, aux_entries), + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment_journal::v1::Desired>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + // repeated .xtcp_treatment_journal.v1.Assignment assignments = 4 [json_name = "assignments"]; + {::_pbi::TcParser::FastMtR1, + {34, 0, 1, + PROTOBUF_FIELD_OFFSET(Desired, _impl_.assignments_)}}, + // uint64 revision = 1 [json_name = "revision"]; + {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(Desired, _impl_.revision_), 2>(), + {8, 2, 0, + PROTOBUF_FIELD_OFFSET(Desired, _impl_.revision_)}}, + // bool suspended = 2 [json_name = "suspended"]; + {::_pbi::TcParser::SingularVarintNoZag1(), + {16, 3, 0, + PROTOBUF_FIELD_OFFSET(Desired, _impl_.suspended_)}}, + // .google.protobuf.Timestamp issued_at = 3 [json_name = "issuedAt"]; + {::_pbi::TcParser::FastMtS1, + {26, 1, 0, + PROTOBUF_FIELD_OFFSET(Desired, _impl_.issued_at_)}}, + }}, {{ + 65535, 65535 + }}, {{ + // uint64 revision = 1 [json_name = "revision"]; + {PROTOBUF_FIELD_OFFSET(Desired, _impl_.revision_), _Internal::kHasBitsOffset + 2, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt64)}, + // bool suspended = 2 [json_name = "suspended"]; + {PROTOBUF_FIELD_OFFSET(Desired, _impl_.suspended_), _Internal::kHasBitsOffset + 3, 0, (0 | ::_fl::kFcOptional | ::_fl::kBool)}, + // .google.protobuf.Timestamp issued_at = 3 [json_name = "issuedAt"]; + {PROTOBUF_FIELD_OFFSET(Desired, _impl_.issued_at_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // repeated .xtcp_treatment_journal.v1.Assignment assignments = 4 [json_name = "assignments"]; + {PROTOBUF_FIELD_OFFSET(Desired, _impl_.assignments_), _Internal::kHasBitsOffset + 0, 1, (0 | ::_fl::kFcRepeated | ::_fl::kMessage | ::_fl::kTvTable)}, + }}, + {{ + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::google::protobuf::Timestamp>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::google::protobuf::Timestamp_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment_journal::v1::Assignment>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment_journal::v1::Assignment_globals_}, + #endif + }}, + {{ + }}, + }; +} + + +inline constexpr Desired::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : assignments_ { visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment_journal::v1::Desired, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Desired, _impl_.assignments_)>() + } + , + issued_at_{nullptr}, + revision_{::uint64_t{0u}}, + suspended_{false} {} + +template +constexpr Desired::Desired(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL Desired::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) Desired(arena); +} +constexpr auto Desired::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::CopyInit(sizeof(Desired), alignof(Desired)); +} +constexpr auto Desired::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &Desired::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &Desired::SharedDtor, + Super_::GetClearImpl(), &Desired::ByteSizeLong, + &Desired::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(Desired, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[3], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct DesiredGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr DesiredGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + Desired_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(Desired::InternalGenerateClassData_( + _default, &Desired_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~DesiredGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) Desired _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(DesiredGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST DesiredGlobalsTypeInternal Desired_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* Desired_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return Desired_globals_.GetClassData(); +#else + return Desired_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +class Journal::_Internal { + public: + using HasBits = decltype(::std::declval()._impl_._has_bits_); + static constexpr ::int32_t kHasBitsOffset = + 8 * PROTOBUF_FIELD_OFFSET(Journal, _impl_._has_bits_); +}; + +constexpr Journal::ParseTableT_ Journal::InternalGenerateParseTable_(const ::_pbi::ClassData* class_data) { + return ParseTableT_{ + { + PROTOBUF_FIELD_OFFSET(Journal, _impl_._has_bits_), + 0, // no _extensions_ + 8, 56, // max_field_number, fast_idx_mask + offsetof(ParseTableT_, field_lookup_table), + 4294967040, // skipmap + offsetof(ParseTableT_, field_entries), + 8, // num_field_entries + 3, // num_aux_entries + offsetof(ParseTableT_, aux_entries), + class_data, + nullptr, // post_loop_handler + ::_pbi::TcParser::MpUnknownFields, // fallback + #ifdef PROTOBUF_PREFETCH_PARSE_TABLE + ::_pbi::TcParser::GetTable<::xtcp_treatment_journal::v1::Journal>(), // to_prefetch + #endif // PROTOBUF_PREFETCH_PARSE_TABLE + }, {{ + // .google.protobuf.Timestamp updated_at = 8 [json_name = "updatedAt"]; + {::_pbi::TcParser::FastMtS1, + {66, 6, 2, + PROTOBUF_FIELD_OFFSET(Journal, _impl_.updated_at_)}}, + // string agent_id = 1 [json_name = "agentId"]; + {::_pbi::TcParser::FastUS1, + {10, 1, 0, + PROTOBUF_FIELD_OFFSET(Journal, _impl_.agent_id_)}}, + // string location = 2 [json_name = "location"]; + {::_pbi::TcParser::FastUS1, + {18, 2, 0, + PROTOBUF_FIELD_OFFSET(Journal, _impl_.location_)}}, + // string boot_id = 3 [json_name = "bootId"]; + {::_pbi::TcParser::FastUS1, + {26, 3, 0, + PROTOBUF_FIELD_OFFSET(Journal, _impl_.boot_id_)}}, + // .xtcp_treatment_journal.v1.Desired desired = 4 [json_name = "desired"]; + {::_pbi::TcParser::FastMtS1, + {34, 5, 0, + PROTOBUF_FIELD_OFFSET(Journal, _impl_.desired_)}}, + // bool locally_suspended = 5 [json_name = "locallySuspended"]; + {::_pbi::TcParser::SingularVarintNoZag1(), + {40, 7, 0, + PROTOBUF_FIELD_OFFSET(Journal, _impl_.locally_suspended_)}}, + // string suspension_reason = 6 [json_name = "suspensionReason"]; + {::_pbi::TcParser::FastUS1, + {50, 4, 0, + PROTOBUF_FIELD_OFFSET(Journal, _impl_.suspension_reason_)}}, + // repeated .xtcp_treatment_journal.v1.Record records = 7 [json_name = "records"]; + {::_pbi::TcParser::FastMtR1, + {58, 0, 1, + PROTOBUF_FIELD_OFFSET(Journal, _impl_.records_)}}, + }}, {{ + 65535, 65535 + }}, {{ + // string agent_id = 1 [json_name = "agentId"]; + {PROTOBUF_FIELD_OFFSET(Journal, _impl_.agent_id_), _Internal::kHasBitsOffset + 1, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // string location = 2 [json_name = "location"]; + {PROTOBUF_FIELD_OFFSET(Journal, _impl_.location_), _Internal::kHasBitsOffset + 2, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // string boot_id = 3 [json_name = "bootId"]; + {PROTOBUF_FIELD_OFFSET(Journal, _impl_.boot_id_), _Internal::kHasBitsOffset + 3, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // .xtcp_treatment_journal.v1.Desired desired = 4 [json_name = "desired"]; + {PROTOBUF_FIELD_OFFSET(Journal, _impl_.desired_), _Internal::kHasBitsOffset + 5, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // bool locally_suspended = 5 [json_name = "locallySuspended"]; + {PROTOBUF_FIELD_OFFSET(Journal, _impl_.locally_suspended_), _Internal::kHasBitsOffset + 7, 0, (0 | ::_fl::kFcOptional | ::_fl::kBool)}, + // string suspension_reason = 6 [json_name = "suspensionReason"]; + {PROTOBUF_FIELD_OFFSET(Journal, _impl_.suspension_reason_), _Internal::kHasBitsOffset + 4, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, + // repeated .xtcp_treatment_journal.v1.Record records = 7 [json_name = "records"]; + {PROTOBUF_FIELD_OFFSET(Journal, _impl_.records_), _Internal::kHasBitsOffset + 0, 1, (0 | ::_fl::kFcRepeated | ::_fl::kMessage | ::_fl::kTvTable)}, + // .google.protobuf.Timestamp updated_at = 8 [json_name = "updatedAt"]; + {PROTOBUF_FIELD_OFFSET(Journal, _impl_.updated_at_), _Internal::kHasBitsOffset + 6, 2, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + }}, + {{ + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment_journal::v1::Desired>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment_journal::v1::Desired_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::xtcp_treatment_journal::v1::Record>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::xtcp_treatment_journal::v1::Record_globals_}, + #endif + #ifndef PROTOBUF_MESSAGE_GLOBALS + {::_pbi::TcParser::GetTable<::google::protobuf::Timestamp>()}, + #else + {::_pbi::FieldAuxMessageGlobals(), &::google::protobuf::Timestamp_globals_}, + #endif + }}, + {{ + "\41\10\10\7\0\0\21\0\0\0\0\0\0\0\0\0" + "xtcp_treatment_journal.v1.Journal" + "agent_id" + "location" + "boot_id" + "suspension_reason" + }}, + }; +} + + +inline constexpr Journal::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + ::_pbi::ConstantInitialized) noexcept + : records_ { visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment_journal::v1::Journal, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Journal, _impl_.records_)>() + } + , + agent_id_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + location_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + boot_id_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + suspension_reason_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), + desired_{nullptr}, + updated_at_{nullptr}, + locally_suspended_{false} {} + +template +constexpr Journal::Journal(::_pbi::ConstantInitialized, + const ::_pbi::ClassData* PROTOBUF_NONNULL class_data) + : Super_( +#if defined(PROTOBUF_CUSTOM_VTABLE) + class_data +#endif // PROTOBUF_CUSTOM_VTABLE + ), + _impl_(internal_visibility(), ::_pbi::ConstantInitialized()) { +} +inline void* PROTOBUF_NONNULL Journal::PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) { + return ::new (mem) Journal(arena); +} +constexpr auto Journal::InternalNewImpl_() { + return ::google::protobuf::internal::MessageCreator::CopyInit(sizeof(Journal), alignof(Journal)); +} +constexpr auto Journal::InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* tc_table) { + return ::google::protobuf::internal::ClassDataFull{ + ::google::protobuf::internal::ClassData{ + &prototype, +#ifndef PROTOBUF_MESSAGE_GLOBALS + &_table_.header, +#else + tc_table, +#endif + nullptr, // IsInitialized + &Journal::MergeImpl, + Super_::GetNewImpl(), +#if defined(PROTOBUF_CUSTOM_VTABLE) + &Journal::SharedDtor, + Super_::GetClearImpl(), &Journal::ByteSizeLong, + &Journal::_InternalSerialize, +#endif // PROTOBUF_CUSTOM_VTABLE + PROTOBUF_FIELD_OFFSET(Journal, _impl_._cached_size_), + false, + }, +#ifdef PROTOBUF_MESSAGE_GLOBALS + &file_reflection_data[5], +#else // !PROTOBUF_MESSAGE_GLOBALS + &::_pbi::kDescriptorMethods, + &descriptor_table_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto, + nullptr, // tracker +#endif // PROTOBUF_MESSAGE_GLOBALS + }; +} +struct JournalGlobalsTypeInternal : ::_pbi::MessageGlobalsBase { + constexpr JournalGlobalsTypeInternal() + : +#ifndef PROTOBUF_MESSAGE_GLOBALS + _default(::_pbi::ConstantInitialized{}, + Journal_class_data_.base()) +#else // !PROTOBUF_MESSAGE_GLOBALS + MessageGlobalsBase(Journal::InternalGenerateClassData_( + _default, &Journal_globals_._table.header)), + _default(::_pbi::ConstantInitialized{}, GetClassData()), + _table(::_pbi::PrivateAccess::GenerateParseTable( + GetClassData())) +#endif // PROTOBUF_MESSAGE_GLOBALS + { + } + ~JournalGlobalsTypeInternal() {} + union { + alignas(::_pbi::kMaxMessageAlignment) Journal _default; + }; +#ifdef PROTOBUF_MESSAGE_GLOBALS + decltype(::_pbi::PrivateAccess::GenerateParseTable( + ::std::declval())) _table; +#endif +}; +#ifdef PROTOBUF_MESSAGE_GLOBALS +static_assert(PROTOBUF_FIELD_OFFSET(JournalGlobalsTypeInternal, _default) == + ::_pbi::MessageGlobalsBase::OffsetToDefault()); +#endif // PROTOBUF_MESSAGE_GLOBALS + +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT + PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 PROTOBUF_MESSAGE_GLOBALS_CONST JournalGlobalsTypeInternal Journal_globals_ + PROTOBUF_MESSAGE_GLOBALS_SECTION(.data.rel.ro); +#if defined(PROTOBUF_CUSTOM_VTABLE) +namespace { +const ::_pbi::ClassData* Journal_get_class_data() { +#ifdef PROTOBUF_MESSAGE_GLOBALS + return Journal_globals_.GetClassData(); +#else + return Journal_class_data_.base(); +#endif // PROTOBUF_MESSAGE_GLOBALS +} +} // namespace +#endif // PROTOBUF_CUSTOM_VTABLE +} // namespace v1 +} // namespace xtcp_treatment_journal +static const ::_pb::EnumDescriptor* PROTOBUF_NONNULL + file_level_enum_descriptors_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto[2]; +static constexpr const ::_pb::ServiceDescriptor* PROTOBUF_NONNULL* PROTOBUF_NULLABLE + file_level_service_descriptors_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto = nullptr; +const ::uint32_t + TableStruct_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto::offsets[] ABSL_ATTRIBUTE_SECTION_VARIABLE( + protodesc_cold) = { + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::TCPSettings, _impl_._has_bits_), + 10, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::TCPSettings, _impl_.congestion_control_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::TCPSettings, _impl_.congestion_lock_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::TCPSettings, _impl_.rto_min_ms_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::TCPSettings, _impl_.initial_congestion_window_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::TCPSettings, _impl_.initial_receive_window_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::TCPSettings, _impl_.quick_ack_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::TCPSettings, _impl_.ecn_), + 0, + 4, + 1, + 2, + 3, + 5, + 6, + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Treatment, _impl_._has_bits_), + 8, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Treatment, _impl_.id_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Treatment, _impl_.revision_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Treatment, _impl_.destination_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Treatment, _impl_.settings_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Treatment, _impl_.ecn_fabric_verified_), + 0, + 3, + 1, + 2, + 4, + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Assignment, _impl_._has_bits_), + 9, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Assignment, _impl_.experiment_id_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Assignment, _impl_.treatment_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Assignment, _impl_.mode_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Assignment, _impl_.lease_expires_at_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Assignment, _impl_.predecessor_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Assignment, _impl_.generation_), + 0, + 1, + 5, + 2, + 3, + 4, + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Desired, _impl_._has_bits_), + 7, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Desired, _impl_.revision_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Desired, _impl_.suspended_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Desired, _impl_.issued_at_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Desired, _impl_.assignments_), + 2, + 3, + 1, + 0, + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Record, _impl_._has_bits_), + 15, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Record, _impl_.assignment_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Record, _impl_.desired_revision_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Record, _impl_.status_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Record, _impl_.received_at_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Record, _impl_.attempted_at_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Record, _impl_.applied_at_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Record, _impl_.verified_at_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Record, _impl_.removed_at_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Record, _impl_.error_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Record, _impl_.installed_route_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Record, _impl_.installed_boot_id_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Record, _impl_.expired_latched_), + 3, + 9, + 10, + 4, + 5, + 6, + 7, + 8, + 0, + 1, + 2, + 11, + 0x081, // bitmap + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Journal, _impl_._has_bits_), + 11, // hasbit index offset + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Journal, _impl_.agent_id_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Journal, _impl_.location_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Journal, _impl_.boot_id_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Journal, _impl_.desired_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Journal, _impl_.locally_suspended_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Journal, _impl_.suspension_reason_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Journal, _impl_.records_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Journal, _impl_.updated_at_), + 1, + 2, + 3, + 5, + 7, + 4, + 0, + 6, +}; + +static const ::_pbi::MigrationSchema + schemas[] ABSL_ATTRIBUTE_SECTION_VARIABLE(protodesc_cold) = { + {0, sizeof(::xtcp_treatment_journal::v1::TCPSettings)}, + {17, sizeof(::xtcp_treatment_journal::v1::Treatment)}, + {30, sizeof(::xtcp_treatment_journal::v1::Assignment)}, + {45, sizeof(::xtcp_treatment_journal::v1::Desired)}, + {56, sizeof(::xtcp_treatment_journal::v1::Record)}, + {83, sizeof(::xtcp_treatment_journal::v1::Journal)}, +}; +static const ::_pbi::MessageGlobalsBase* PROTOBUF_NONNULL const + file_message_globals[] = { + &::xtcp_treatment_journal::v1::TCPSettings_globals_, + &::xtcp_treatment_journal::v1::Treatment_globals_, + &::xtcp_treatment_journal::v1::Assignment_globals_, + &::xtcp_treatment_journal::v1::Desired_globals_, + &::xtcp_treatment_journal::v1::Record_globals_, + &::xtcp_treatment_journal::v1::Journal_globals_, +}; +const char descriptor_table_protodef_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto[] ABSL_ATTRIBUTE_SECTION_VARIABLE( + protodesc_cold) = { + "\n\'xtcp_treatment_journal/v1/journal.prot" + "o\022\031xtcp_treatment_journal.v1\032\037google/pro" + "tobuf/timestamp.proto\"\267\003\n\013TCPSettings\0222\n" + "\022congestion_control\030\001 \001(\tH\000R\021congestionC" + "ontrol\210\001\001\022\'\n\017congestion_lock\030\002 \001(\010R\016cong" + "estionLock\022!\n\nrto_min_ms\030\003 \001(\rH\001R\010rtoMin" + "Ms\210\001\001\022\?\n\031initial_congestion_window\030\004 \001(\r" + "H\002R\027initialCongestionWindow\210\001\001\0229\n\026initia" + "l_receive_window\030\005 \001(\rH\003R\024initialReceive" + "Window\210\001\001\022 \n\tquick_ack\030\006 \001(\010H\004R\010quickAck" + "\210\001\001\022\025\n\003ecn\030\007 \001(\010H\005R\003ecn\210\001\001B\025\n\023_congestio" + "n_controlB\r\n\013_rto_min_msB\034\n\032_initial_con" + "gestion_windowB\031\n\027_initial_receive_windo" + "wB\014\n\n_quick_ackB\006\n\004_ecn\"\315\001\n\tTreatment\022\016\n" + "\002id\030\001 \001(\tR\002id\022\032\n\010revision\030\002 \001(\004R\010revisio" + "n\022 \n\013destination\030\003 \001(\tR\013destination\022B\n\010s" + "ettings\030\004 \001(\0132&.xtcp_treatment_journal.v" + "1.TCPSettingsR\010settings\022.\n\023ecn_fabric_ve" + "rified\030\005 \001(\010R\021ecnFabricVerified\"\342\002\n\nAssi" + "gnment\022#\n\rexperiment_id\030\001 \001(\tR\014experimen" + "tId\022B\n\ttreatment\030\002 \001(\0132$.xtcp_treatment_" + "journal.v1.TreatmentR\ttreatment\022=\n\004mode\030" + "\003 \001(\0162).xtcp_treatment_journal.v1.Assign" + "mentModeR\004mode\022D\n\020lease_expires_at\030\004 \001(\013" + "2\032.google.protobuf.TimestampR\016leaseExpir" + "esAt\022F\n\013predecessor\030\005 \001(\0132$.xtcp_treatme" + "nt_journal.v1.TreatmentR\013predecessor\022\036\n\n" + "generation\030\006 \001(\004R\ngeneration\"\305\001\n\007Desired" + "\022\032\n\010revision\030\001 \001(\004R\010revision\022\034\n\tsuspende" + "d\030\002 \001(\010R\tsuspended\0227\n\tissued_at\030\003 \001(\0132\032." + "google.protobuf.TimestampR\010issuedAt\022G\n\013a" + "ssignments\030\004 \003(\0132%.xtcp_treatment_journa" + "l.v1.AssignmentR\013assignments\"\375\004\n\006Record\022" + "E\n\nassignment\030\001 \001(\0132%.xtcp_treatment_jou" + "rnal.v1.AssignmentR\nassignment\022)\n\020desire" + "d_revision\030\002 \001(\004R\017desiredRevision\022>\n\006sta" + "tus\030\003 \001(\0162&.xtcp_treatment_journal.v1.Ap" + "plyStatusR\006status\022;\n\013received_at\030\004 \001(\0132\032" + ".google.protobuf.TimestampR\nreceivedAt\022=" + "\n\014attempted_at\030\005 \001(\0132\032.google.protobuf.T" + "imestampR\013attemptedAt\0229\n\napplied_at\030\006 \001(" + "\0132\032.google.protobuf.TimestampR\tappliedAt" + "\022;\n\013verified_at\030\007 \001(\0132\032.google.protobuf." + "TimestampR\nverifiedAt\0229\n\nremoved_at\030\010 \001(" + "\0132\032.google.protobuf.TimestampR\tremovedAt" + "\022\024\n\005error\030\t \001(\tR\005error\022\'\n\017installed_rout" + "e\030\n \001(\014R\016installedRoute\022*\n\021installed_boo" + "t_id\030\013 \001(\tR\017installedBootId\022\'\n\017expired_l" + "atched\030\014 \001(\010R\016expiredLatched\"\351\002\n\007Journal" + "\022\031\n\010agent_id\030\001 \001(\tR\007agentId\022\032\n\010location\030" + "\002 \001(\tR\010location\022\027\n\007boot_id\030\003 \001(\tR\006bootId" + "\022<\n\007desired\030\004 \001(\0132\".xtcp_treatment_journ" + "al.v1.DesiredR\007desired\022+\n\021locally_suspen" + "ded\030\005 \001(\010R\020locallySuspended\022+\n\021suspensio" + "n_reason\030\006 \001(\tR\020suspensionReason\022;\n\007reco" + "rds\030\007 \003(\0132!.xtcp_treatment_journal.v1.Re" + "cordR\007records\0229\n\nupdated_at\030\010 \001(\0132\032.goog" + "le.protobuf.TimestampR\tupdatedAt*\244\001\n\016Ass" + "ignmentMode\022\037\n\033ASSIGNMENT_MODE_UNSPECIFI" + "ED\020\000\022\031\n\025ASSIGNMENT_MODE_TRIAL\020\001\022\032\n\026ASSIG" + "NMENT_MODE_STABLE\020\002\022\035\n\031ASSIGNMENT_MODE_S" + "USPENDED\020\003\022\033\n\027ASSIGNMENT_MODE_RETIRED\020\004*" + "\255\001\n\013ApplyStatus\022\034\n\030APPLY_STATUS_UNSPECIF" + "IED\020\000\022\030\n\024APPLY_STATUS_PENDING\020\001\022\031\n\025APPLY" + "_STATUS_VERIFIED\020\002\022\030\n\024APPLY_STATUS_REMOV" + "ED\020\003\022\030\n\024APPLY_STATUS_EXPIRED\020\004\022\027\n\023APPLY_" + "STATUS_FAILED\020\005B\352\001\n\035com.xtcp_treatment_j" + "ournal.v1B\014JournalProtoP\001Z>github.com/ra" + "ndomizedcoder/xtcp2/gen/go/xtcp_treatmen" + "t_journal\242\002\003XXX\252\002\027XtcpTreatmentJournal.V" + "1\312\002\027XtcpTreatmentJournal\\V1\342\002#XtcpTreatm" + "entJournal\\V1\\GPBMetadata\352\002\030XtcpTreatmen" + "tJournal::V1b\006proto3" +}; +static const ::_pbi::DescriptorTable* PROTOBUF_NONNULL const + descriptor_table_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto_deps[1] = { + &::descriptor_table_google_2fprotobuf_2ftimestamp_2eproto, +}; +static ::absl::once_flag descriptor_table_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto_once; +PROTOBUF_CONSTINIT const ::_pbi::DescriptorTable descriptor_table_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto = { + false, + false, + 2900, + descriptor_table_protodef_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto, + "xtcp_treatment_journal/v1/journal.proto", + &descriptor_table_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto_once, + descriptor_table_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto_deps, + 1, + 6, + schemas, + file_message_globals, + TableStruct_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto::offsets, + file_level_enum_descriptors_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto, + file_level_service_descriptors_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto, +}; +namespace xtcp_treatment_journal { +namespace v1 { +[[nodiscard]] const ::google::protobuf::EnumDescriptor* PROTOBUF_NONNULL +AssignmentMode_descriptor() { + ::google::protobuf::internal::AssignDescriptors(&descriptor_table_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto); + return file_level_enum_descriptors_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto[0]; +} +PROTOBUF_CONSTINIT const uint32_t AssignmentMode_internal_data_[] = { + 327680u, 0u, }; +[[nodiscard]] const ::google::protobuf::EnumDescriptor* PROTOBUF_NONNULL +ApplyStatus_descriptor() { + ::google::protobuf::internal::AssignDescriptors(&descriptor_table_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto); + return file_level_enum_descriptors_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto[1]; +} +PROTOBUF_CONSTINIT const uint32_t ApplyStatus_internal_data_[] = { + 393216u, 0u, }; +// =================================================================== + +TCPSettings::TCPSettings(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, TCPSettings_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment_journal.v1.TCPSettings) +} +PROTOBUF_NDEBUG_INLINE TCPSettings::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment_journal::v1::TCPSettings& from_msg) + : _has_bits_{from._has_bits_}, + congestion_control_(arena, from.congestion_control_) {} + +TCPSettings::TCPSettings( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const TCPSettings& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, TCPSettings_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + TCPSettings* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + ::memcpy(reinterpret_cast(&_impl_) + + offsetof(Impl_, rto_min_ms_), + reinterpret_cast(&from._impl_) + + offsetof(Impl_, rto_min_ms_), + offsetof(Impl_, ecn_) - + offsetof(Impl_, rto_min_ms_) + + sizeof(Impl_::ecn_)); + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment_journal.v1.TCPSettings) +} +PROTOBUF_NDEBUG_INLINE TCPSettings::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + : congestion_control_(arena) {} + +inline void TCPSettings::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + ::memset(reinterpret_cast(&_impl_) + + offsetof(Impl_, rto_min_ms_), + 0, + offsetof(Impl_, ecn_) - + offsetof(Impl_, rto_min_ms_) + + sizeof(Impl_::ecn_)); +} +TCPSettings::~TCPSettings() { + // @@protoc_insertion_point(destructor:xtcp_treatment_journal.v1.TCPSettings) + SharedDtor(*this); +} +inline void TCPSettings::SharedDtor(MessageLite& self) { + TCPSettings& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + this_._impl_.congestion_control_.Destroy(); + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull TCPSettings_class_data_ = + TCPSettings::InternalGenerateClassData_(TCPSettings_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +TCPSettings::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&TCPSettings_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(TCPSettings_class_data_.tc_table); + return TCPSettings_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +TCPSettings::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&TCPSettings_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&TCPSettings_globals_)); + return TCPSettings_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const TCPSettings::ParseTableT_ + TCPSettings::_table_ = + TCPSettings::InternalGenerateParseTable_(TCPSettings_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void TCPSettings::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment_journal.v1.TCPSettings) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + this_._impl_.congestion_control_.ClearNonDefaultToEmpty(); + } + if (BatchCheckHasBit(cached_has_bits, 0x0000007eU)) { + ::memset(&this_._impl_.rto_min_ms_, 0, + static_cast<::size_t>( + reinterpret_cast(&this_._impl_.ecn_) - + reinterpret_cast(&this_._impl_.rto_min_ms_)) + + sizeof(_impl_.ecn_)); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL TCPSettings::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const TCPSettings& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL TCPSettings::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const TCPSettings& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment_journal.v1.TCPSettings) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // optional string congestion_control = 1 [json_name = "congestionControl"]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + const ::std::string& _s = this_._internal_congestion_control(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment_journal.v1.TCPSettings.congestion_control"); + target = stream->WriteStringMaybeAliased(1, _s, target); + } + + // bool congestion_lock = 2 [json_name = "congestionLock"]; + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (this_._internal_congestion_lock() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray( + 2, this_._internal_congestion_lock(), target); + } + } + + // optional uint32 rto_min_ms = 3 [json_name = "rtoMinMs"]; + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray( + 3, this_._internal_rto_min_ms(), target); + } + + // optional uint32 initial_congestion_window = 4 [json_name = "initialCongestionWindow"]; + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray( + 4, this_._internal_initial_congestion_window(), target); + } + + // optional uint32 initial_receive_window = 5 [json_name = "initialReceiveWindow"]; + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt32ToArray( + 5, this_._internal_initial_receive_window(), target); + } + + // optional bool quick_ack = 6 [json_name = "quickAck"]; + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray( + 6, this_._internal_quick_ack(), target); + } + + // optional bool ecn = 7 [json_name = "ecn"]; + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray( + 7, this_._internal_ecn(), target); + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment_journal.v1.TCPSettings) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t TCPSettings::ByteSizeLong(const MessageLite& base) { + const TCPSettings& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t TCPSettings::ByteSizeLong() const { + const TCPSettings& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment_journal.v1.TCPSettings) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + ::_pbi::Prefetch5LinesFrom7Lines(&this_); + cached_has_bits = this_._impl_._has_bits_[0]; + total_size += ::absl::popcount(0x00000060U & cached_has_bits) * 2; + if (BatchCheckHasBit(cached_has_bits, 0x0000001fU)) { + // optional string congestion_control = 1 [json_name = "congestionControl"]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_congestion_control()); + } + // optional uint32 rto_min_ms = 3 [json_name = "rtoMinMs"]; + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne( + this_._internal_rto_min_ms()); + } + // optional uint32 initial_congestion_window = 4 [json_name = "initialCongestionWindow"]; + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne( + this_._internal_initial_congestion_window()); + } + // optional uint32 initial_receive_window = 5 [json_name = "initialReceiveWindow"]; + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne( + this_._internal_initial_receive_window()); + } + // bool congestion_lock = 2 [json_name = "congestionLock"]; + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (this_._internal_congestion_lock() != 0) { + total_size += 2; + } + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void TCPSettings::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment_journal.v1.TCPSettings) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000007fU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + _this->_internal_set_congestion_control(from._internal_congestion_control()); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + _this->_impl_.rto_min_ms_ = from._impl_.rto_min_ms_; + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + _this->_impl_.initial_congestion_window_ = from._impl_.initial_congestion_window_; + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + _this->_impl_.initial_receive_window_ = from._impl_.initial_receive_window_; + } + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (from._internal_congestion_lock() != 0) { + _this->_impl_.congestion_lock_ = from._impl_.congestion_lock_; + } + } + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + _this->_impl_.quick_ack_ = from._impl_.quick_ack_; + } + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + _this->_impl_.ecn_ = from._impl_.ecn_; + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void TCPSettings::CopyFrom(const TCPSettings& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment_journal.v1.TCPSettings) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void TCPSettings::InternalSwap(TCPSettings* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + auto* arena = GetArena(); + ABSL_DCHECK_EQ(arena, other->GetArena()); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.congestion_control_, &other->_impl_.congestion_control_, arena); + ::google::protobuf::internal::memswap< + PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.ecn_) + + sizeof(TCPSettings::_impl_.ecn_) + - PROTOBUF_FIELD_OFFSET(TCPSettings, _impl_.rto_min_ms_)>( + reinterpret_cast(&_impl_.rto_min_ms_), + reinterpret_cast(&other->_impl_.rto_min_ms_)); +} + +::google::protobuf::Metadata TCPSettings::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +Treatment::Treatment(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, Treatment_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment_journal.v1.Treatment) +} +PROTOBUF_NDEBUG_INLINE Treatment::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment_journal::v1::Treatment& from_msg) + : _has_bits_{from._has_bits_}, + id_(arena, from.id_), + destination_(arena, from.destination_) {} + +Treatment::Treatment( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const Treatment& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, Treatment_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + Treatment* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + ::uint32_t cached_has_bits = _impl_._has_bits_[0]; + _impl_.settings_ = (CheckHasBit(cached_has_bits, 0x00000004U)) + ? Super_::CopyConstruct(arena, *from._impl_.settings_) + : nullptr; + ::memcpy(reinterpret_cast(&_impl_) + + offsetof(Impl_, revision_), + reinterpret_cast(&from._impl_) + + offsetof(Impl_, revision_), + offsetof(Impl_, ecn_fabric_verified_) - + offsetof(Impl_, revision_) + + sizeof(Impl_::ecn_fabric_verified_)); + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment_journal.v1.Treatment) +} +PROTOBUF_NDEBUG_INLINE Treatment::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + : id_(arena), + destination_(arena) {} + +inline void Treatment::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + ::memset(reinterpret_cast(&_impl_) + + offsetof(Impl_, settings_), + 0, + offsetof(Impl_, ecn_fabric_verified_) - + offsetof(Impl_, settings_) + + sizeof(Impl_::ecn_fabric_verified_)); +} +Treatment::~Treatment() { + // @@protoc_insertion_point(destructor:xtcp_treatment_journal.v1.Treatment) + SharedDtor(*this); +} +inline void Treatment::SharedDtor(MessageLite& self) { + Treatment& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + this_._impl_.id_.Destroy(); + this_._impl_.destination_.Destroy(); + delete this_._impl_.settings_; + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull Treatment_class_data_ = + Treatment::InternalGenerateClassData_(Treatment_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +Treatment::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&Treatment_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(Treatment_class_data_.tc_table); + return Treatment_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +Treatment::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&Treatment_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&Treatment_globals_)); + return Treatment_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const Treatment::ParseTableT_ + Treatment::_table_ = + Treatment::InternalGenerateParseTable_(Treatment_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void Treatment::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment_journal.v1.Treatment) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x00000007U)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + this_._impl_.id_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + this_._impl_.destination_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + ABSL_DCHECK(this_._impl_.settings_ != nullptr); + this_._impl_.settings_->Clear(); + } + } + if (BatchCheckHasBit(cached_has_bits, 0x00000018U)) { + ::memset(&this_._impl_.revision_, 0, + static_cast<::size_t>( + reinterpret_cast(&this_._impl_.ecn_fabric_verified_) - + reinterpret_cast(&this_._impl_.revision_)) + + sizeof(_impl_.ecn_fabric_verified_)); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL Treatment::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const Treatment& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL Treatment::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const Treatment& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment_journal.v1.Treatment) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // string id = 1 [json_name = "id"]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_id().empty()) { + const ::std::string& _s = this_._internal_id(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment_journal.v1.Treatment.id"); + target = stream->WriteStringMaybeAliased(1, _s, target); + } + } + + // uint64 revision = 2 [json_name = "revision"]; + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (this_._internal_revision() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray( + 2, this_._internal_revision(), target); + } + } + + // string destination = 3 [json_name = "destination"]; + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!this_._internal_destination().empty()) { + const ::std::string& _s = this_._internal_destination(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment_journal.v1.Treatment.destination"); + target = stream->WriteStringMaybeAliased(3, _s, target); + } + } + + // .xtcp_treatment_journal.v1.TCPSettings settings = 4 [json_name = "settings"]; + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 4, *this_._impl_.settings_, this_._impl_.settings_->GetCachedSize(), target, + stream); + } + + // bool ecn_fabric_verified = 5 [json_name = "ecnFabricVerified"]; + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (this_._internal_ecn_fabric_verified() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray( + 5, this_._internal_ecn_fabric_verified(), target); + } + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment_journal.v1.Treatment) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t Treatment::ByteSizeLong(const MessageLite& base) { + const Treatment& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t Treatment::ByteSizeLong() const { + const Treatment& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment_journal.v1.Treatment) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + ::_pbi::Prefetch5LinesFrom7Lines(&this_); + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000001fU)) { + // string id = 1 [json_name = "id"]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_id().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_id()); + } + } + // string destination = 3 [json_name = "destination"]; + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!this_._internal_destination().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_destination()); + } + } + // .xtcp_treatment_journal.v1.TCPSettings settings = 4 [json_name = "settings"]; + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.settings_); + } + // uint64 revision = 2 [json_name = "revision"]; + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (this_._internal_revision() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne( + this_._internal_revision()); + } + } + // bool ecn_fabric_verified = 5 [json_name = "ecnFabricVerified"]; + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (this_._internal_ecn_fabric_verified() != 0) { + total_size += 2; + } + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void Treatment::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + ::google::protobuf::Arena* arena = _this->GetArena(); + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment_journal.v1.Treatment) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000001fU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!from._internal_id().empty()) { + _this->_internal_set_id(from._internal_id()); + } else { + if (_this->_impl_.id_.IsDefault()) { + _this->_internal_set_id(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!from._internal_destination().empty()) { + _this->_internal_set_destination(from._internal_destination()); + } else { + if (_this->_impl_.destination_.IsDefault()) { + _this->_internal_set_destination(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + ABSL_DCHECK(from._impl_.settings_ != nullptr); + if (_this->_impl_.settings_ == nullptr) { + _this->_impl_.settings_ = Super_::CopyConstruct(arena, *from._impl_.settings_); + } else { + _this->_impl_.settings_->MergeFrom(*from._impl_.settings_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (from._internal_revision() != 0) { + _this->_impl_.revision_ = from._impl_.revision_; + } + } + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (from._internal_ecn_fabric_verified() != 0) { + _this->_impl_.ecn_fabric_verified_ = from._impl_.ecn_fabric_verified_; + } + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void Treatment::CopyFrom(const Treatment& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment_journal.v1.Treatment) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void Treatment::InternalSwap(Treatment* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + auto* arena = GetArena(); + ABSL_DCHECK_EQ(arena, other->GetArena()); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.id_, &other->_impl_.id_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.destination_, &other->_impl_.destination_, arena); + ::google::protobuf::internal::memswap< + PROTOBUF_FIELD_OFFSET(Treatment, _impl_.ecn_fabric_verified_) + + sizeof(Treatment::_impl_.ecn_fabric_verified_) + - PROTOBUF_FIELD_OFFSET(Treatment, _impl_.settings_)>( + reinterpret_cast(&_impl_.settings_), + reinterpret_cast(&other->_impl_.settings_)); +} + +::google::protobuf::Metadata Treatment::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +void Assignment::clear_lease_expires_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.lease_expires_at_ != nullptr) _impl_.lease_expires_at_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); +} +Assignment::Assignment(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, Assignment_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment_journal.v1.Assignment) +} +PROTOBUF_NDEBUG_INLINE Assignment::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment_journal::v1::Assignment& from_msg) + : _has_bits_{from._has_bits_}, + experiment_id_(arena, from.experiment_id_) {} + +Assignment::Assignment( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const Assignment& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, Assignment_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + Assignment* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + ::uint32_t cached_has_bits = _impl_._has_bits_[0]; + _impl_.treatment_ = (CheckHasBit(cached_has_bits, 0x00000002U)) + ? Super_::CopyConstruct(arena, *from._impl_.treatment_) + : nullptr; + _impl_.lease_expires_at_ = (CheckHasBit(cached_has_bits, 0x00000004U)) + ? Super_::CopyConstruct(arena, *from._impl_.lease_expires_at_) + : nullptr; + _impl_.predecessor_ = (CheckHasBit(cached_has_bits, 0x00000008U)) + ? Super_::CopyConstruct(arena, *from._impl_.predecessor_) + : nullptr; + ::memcpy(reinterpret_cast(&_impl_) + + offsetof(Impl_, generation_), + reinterpret_cast(&from._impl_) + + offsetof(Impl_, generation_), + offsetof(Impl_, mode_) - + offsetof(Impl_, generation_) + + sizeof(Impl_::mode_)); + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment_journal.v1.Assignment) +} +PROTOBUF_NDEBUG_INLINE Assignment::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + : experiment_id_(arena) {} + +inline void Assignment::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + ::memset(reinterpret_cast(&_impl_) + + offsetof(Impl_, treatment_), + 0, + offsetof(Impl_, mode_) - + offsetof(Impl_, treatment_) + + sizeof(Impl_::mode_)); +} +Assignment::~Assignment() { + // @@protoc_insertion_point(destructor:xtcp_treatment_journal.v1.Assignment) + SharedDtor(*this); +} +inline void Assignment::SharedDtor(MessageLite& self) { + Assignment& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + this_._impl_.experiment_id_.Destroy(); + delete this_._impl_.treatment_; + delete this_._impl_.lease_expires_at_; + delete this_._impl_.predecessor_; + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull Assignment_class_data_ = + Assignment::InternalGenerateClassData_(Assignment_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +Assignment::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&Assignment_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(Assignment_class_data_.tc_table); + return Assignment_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +Assignment::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&Assignment_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&Assignment_globals_)); + return Assignment_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const Assignment::ParseTableT_ + Assignment::_table_ = + Assignment::InternalGenerateParseTable_(Assignment_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void Assignment::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment_journal.v1.Assignment) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000000fU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + this_._impl_.experiment_id_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + ABSL_DCHECK(this_._impl_.treatment_ != nullptr); + this_._impl_.treatment_->Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + ABSL_DCHECK(this_._impl_.lease_expires_at_ != nullptr); + this_._impl_.lease_expires_at_->Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + ABSL_DCHECK(this_._impl_.predecessor_ != nullptr); + this_._impl_.predecessor_->Clear(); + } + } + if (BatchCheckHasBit(cached_has_bits, 0x00000030U)) { + ::memset(&this_._impl_.generation_, 0, + static_cast<::size_t>( + reinterpret_cast(&this_._impl_.mode_) - + reinterpret_cast(&this_._impl_.generation_)) + + sizeof(_impl_.mode_)); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL Assignment::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const Assignment& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL Assignment::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const Assignment& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment_journal.v1.Assignment) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // string experiment_id = 1 [json_name = "experimentId"]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_experiment_id().empty()) { + const ::std::string& _s = this_._internal_experiment_id(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment_journal.v1.Assignment.experiment_id"); + target = stream->WriteStringMaybeAliased(1, _s, target); + } + } + + // .xtcp_treatment_journal.v1.Treatment treatment = 2 [json_name = "treatment"]; + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 2, *this_._impl_.treatment_, this_._impl_.treatment_->GetCachedSize(), target, + stream); + } + + // .xtcp_treatment_journal.v1.AssignmentMode mode = 3 [json_name = "mode"]; + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (this_._internal_mode() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 3, this_._internal_mode(), target); + } + } + + // .google.protobuf.Timestamp lease_expires_at = 4 [json_name = "leaseExpiresAt"]; + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 4, *this_._impl_.lease_expires_at_, this_._impl_.lease_expires_at_->GetCachedSize(), target, + stream); + } + + // .xtcp_treatment_journal.v1.Treatment predecessor = 5 [json_name = "predecessor"]; + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 5, *this_._impl_.predecessor_, this_._impl_.predecessor_->GetCachedSize(), target, + stream); + } + + // uint64 generation = 6 [json_name = "generation"]; + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (this_._internal_generation() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray( + 6, this_._internal_generation(), target); + } + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment_journal.v1.Assignment) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t Assignment::ByteSizeLong(const MessageLite& base) { + const Assignment& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t Assignment::ByteSizeLong() const { + const Assignment& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment_journal.v1.Assignment) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + ::_pbi::Prefetch5LinesFrom7Lines(&this_); + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000003fU)) { + // string experiment_id = 1 [json_name = "experimentId"]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_experiment_id().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_experiment_id()); + } + } + // .xtcp_treatment_journal.v1.Treatment treatment = 2 [json_name = "treatment"]; + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.treatment_); + } + // .google.protobuf.Timestamp lease_expires_at = 4 [json_name = "leaseExpiresAt"]; + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.lease_expires_at_); + } + // .xtcp_treatment_journal.v1.Treatment predecessor = 5 [json_name = "predecessor"]; + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.predecessor_); + } + // uint64 generation = 6 [json_name = "generation"]; + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (this_._internal_generation() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne( + this_._internal_generation()); + } + } + // .xtcp_treatment_journal.v1.AssignmentMode mode = 3 [json_name = "mode"]; + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (this_._internal_mode() != 0) { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this_._internal_mode()); + } + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void Assignment::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + ::google::protobuf::Arena* arena = _this->GetArena(); + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment_journal.v1.Assignment) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000003fU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!from._internal_experiment_id().empty()) { + _this->_internal_set_experiment_id(from._internal_experiment_id()); + } else { + if (_this->_impl_.experiment_id_.IsDefault()) { + _this->_internal_set_experiment_id(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + ABSL_DCHECK(from._impl_.treatment_ != nullptr); + if (_this->_impl_.treatment_ == nullptr) { + _this->_impl_.treatment_ = Super_::CopyConstruct(arena, *from._impl_.treatment_); + } else { + _this->_impl_.treatment_->MergeFrom(*from._impl_.treatment_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + ABSL_DCHECK(from._impl_.lease_expires_at_ != nullptr); + if (_this->_impl_.lease_expires_at_ == nullptr) { + _this->_impl_.lease_expires_at_ = Super_::CopyConstruct(arena, *from._impl_.lease_expires_at_); + } else { + _this->_impl_.lease_expires_at_->MergeFrom(*from._impl_.lease_expires_at_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + ABSL_DCHECK(from._impl_.predecessor_ != nullptr); + if (_this->_impl_.predecessor_ == nullptr) { + _this->_impl_.predecessor_ = Super_::CopyConstruct(arena, *from._impl_.predecessor_); + } else { + _this->_impl_.predecessor_->MergeFrom(*from._impl_.predecessor_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (from._internal_generation() != 0) { + _this->_impl_.generation_ = from._impl_.generation_; + } + } + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (from._internal_mode() != 0) { + _this->_impl_.mode_ = from._impl_.mode_; + } + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void Assignment::CopyFrom(const Assignment& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment_journal.v1.Assignment) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void Assignment::InternalSwap(Assignment* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + auto* arena = GetArena(); + ABSL_DCHECK_EQ(arena, other->GetArena()); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.experiment_id_, &other->_impl_.experiment_id_, arena); + ::google::protobuf::internal::memswap< + PROTOBUF_FIELD_OFFSET(Assignment, _impl_.mode_) + + sizeof(Assignment::_impl_.mode_) + - PROTOBUF_FIELD_OFFSET(Assignment, _impl_.treatment_)>( + reinterpret_cast(&_impl_.treatment_), + reinterpret_cast(&other->_impl_.treatment_)); +} + +::google::protobuf::Metadata Assignment::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +void Desired::clear_issued_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.issued_at_ != nullptr) _impl_.issued_at_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); +} +Desired::Desired(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, Desired_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment_journal.v1.Desired) +} +PROTOBUF_NDEBUG_INLINE Desired::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment_journal::v1::Desired& from_msg) + : _has_bits_{from._has_bits_}, + assignments_ { + visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment_journal::v1::Desired, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Desired, _impl_.assignments_)>() + , arena, from.assignments_ + } + {} + +Desired::Desired( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const Desired& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, Desired_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + Desired* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + ::uint32_t cached_has_bits = _impl_._has_bits_[0]; + _impl_.issued_at_ = (CheckHasBit(cached_has_bits, 0x00000002U)) + ? Super_::CopyConstruct(arena, *from._impl_.issued_at_) + : nullptr; + ::memcpy(reinterpret_cast(&_impl_) + + offsetof(Impl_, revision_), + reinterpret_cast(&from._impl_) + + offsetof(Impl_, revision_), + offsetof(Impl_, suspended_) - + offsetof(Impl_, revision_) + + sizeof(Impl_::suspended_)); + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment_journal.v1.Desired) +} +PROTOBUF_NDEBUG_INLINE Desired::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + : assignments_ { visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment_journal::v1::Desired, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Desired, _impl_.assignments_)>() + } + {} + +inline void Desired::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + ::memset(reinterpret_cast(&_impl_) + + offsetof(Impl_, issued_at_), + 0, + offsetof(Impl_, suspended_) - + offsetof(Impl_, issued_at_) + + sizeof(Impl_::suspended_)); +} +Desired::~Desired() { + // @@protoc_insertion_point(destructor:xtcp_treatment_journal.v1.Desired) + SharedDtor(*this); +} +inline void Desired::SharedDtor(MessageLite& self) { + Desired& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + delete this_._impl_.issued_at_; + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull Desired_class_data_ = + Desired::InternalGenerateClassData_(Desired_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +Desired::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&Desired_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(Desired_class_data_.tc_table); + return Desired_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +Desired::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&Desired_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&Desired_globals_)); + return Desired_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const Desired::ParseTableT_ + Desired::_table_ = + Desired::InternalGenerateParseTable_(Desired_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void Desired::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment_journal.v1.Desired) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x00000003U)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + _impl_.assignments_.Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + ABSL_DCHECK(this_._impl_.issued_at_ != nullptr); + this_._impl_.issued_at_->Clear(); + } + } + if (BatchCheckHasBit(cached_has_bits, 0x0000000cU)) { + ::memset(&this_._impl_.revision_, 0, + static_cast<::size_t>( + reinterpret_cast(&this_._impl_.suspended_) - + reinterpret_cast(&this_._impl_.revision_)) + + sizeof(_impl_.suspended_)); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL Desired::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const Desired& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL Desired::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const Desired& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment_journal.v1.Desired) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // uint64 revision = 1 [json_name = "revision"]; + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (this_._internal_revision() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray( + 1, this_._internal_revision(), target); + } + } + + // bool suspended = 2 [json_name = "suspended"]; + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (this_._internal_suspended() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray( + 2, this_._internal_suspended(), target); + } + } + + // .google.protobuf.Timestamp issued_at = 3 [json_name = "issuedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 3, *this_._impl_.issued_at_, this_._impl_.issued_at_->GetCachedSize(), target, + stream); + } + + // repeated .xtcp_treatment_journal.v1.Assignment assignments = 4 [json_name = "assignments"]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + for (unsigned i = 0, n = static_cast( + this_._internal_assignments_size()); + i < n; i++) { + const auto& repfield = this_._internal_assignments().Get(i); + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 4, repfield, repfield.GetCachedSize(), target, + stream); + } + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment_journal.v1.Desired) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t Desired::ByteSizeLong(const MessageLite& base) { + const Desired& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t Desired::ByteSizeLong() const { + const Desired& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment_journal.v1.Desired) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + ::_pbi::Prefetch5LinesFrom7Lines(&this_); + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000000fU)) { + // repeated .xtcp_treatment_journal.v1.Assignment assignments = 4 [json_name = "assignments"]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + total_size += 1UL * this_._internal_assignments_size(); + for (const auto& msg : this_._internal_assignments()) { + total_size += ::google::protobuf::internal::WireFormatLite::MessageSize(msg); + } + } + // .google.protobuf.Timestamp issued_at = 3 [json_name = "issuedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.issued_at_); + } + // uint64 revision = 1 [json_name = "revision"]; + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (this_._internal_revision() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne( + this_._internal_revision()); + } + } + // bool suspended = 2 [json_name = "suspended"]; + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (this_._internal_suspended() != 0) { + total_size += 2; + } + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void Desired::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + ::google::protobuf::Arena* arena = _this->GetArena(); + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment_journal.v1.Desired) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000000fU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + _this->_internal_mutable_assignments()->InternalMergeFromWithArena( + ::google::protobuf::MessageLite::internal_visibility(), arena, + from._internal_assignments()); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + ABSL_DCHECK(from._impl_.issued_at_ != nullptr); + if (_this->_impl_.issued_at_ == nullptr) { + _this->_impl_.issued_at_ = Super_::CopyConstruct(arena, *from._impl_.issued_at_); + } else { + _this->_impl_.issued_at_->MergeFrom(*from._impl_.issued_at_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (from._internal_revision() != 0) { + _this->_impl_.revision_ = from._impl_.revision_; + } + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (from._internal_suspended() != 0) { + _this->_impl_.suspended_ = from._impl_.suspended_; + } + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void Desired::CopyFrom(const Desired& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment_journal.v1.Desired) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void Desired::InternalSwap(Desired* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + _impl_.assignments_.InternalSwap(&other->_impl_.assignments_); + ::google::protobuf::internal::memswap< + PROTOBUF_FIELD_OFFSET(Desired, _impl_.suspended_) + + sizeof(Desired::_impl_.suspended_) + - PROTOBUF_FIELD_OFFSET(Desired, _impl_.issued_at_)>( + reinterpret_cast(&_impl_.issued_at_), + reinterpret_cast(&other->_impl_.issued_at_)); +} + +::google::protobuf::Metadata Desired::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +void Record::clear_received_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.received_at_ != nullptr) _impl_.received_at_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); +} +void Record::clear_attempted_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.attempted_at_ != nullptr) _impl_.attempted_at_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); +} +void Record::clear_applied_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.applied_at_ != nullptr) _impl_.applied_at_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); +} +void Record::clear_verified_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.verified_at_ != nullptr) _impl_.verified_at_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); +} +void Record::clear_removed_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.removed_at_ != nullptr) _impl_.removed_at_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000100U); +} +Record::Record(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, Record_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment_journal.v1.Record) +} +PROTOBUF_NDEBUG_INLINE Record::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment_journal::v1::Record& from_msg) + : _has_bits_{from._has_bits_}, + error_(arena, from.error_), + installed_route_(arena, from.installed_route_), + installed_boot_id_(arena, from.installed_boot_id_) {} + +Record::Record( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const Record& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, Record_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + Record* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + ::uint32_t cached_has_bits = _impl_._has_bits_[0]; + _impl_.assignment_ = (CheckHasBit(cached_has_bits, 0x00000008U)) + ? Super_::CopyConstruct(arena, *from._impl_.assignment_) + : nullptr; + _impl_.received_at_ = (CheckHasBit(cached_has_bits, 0x00000010U)) + ? Super_::CopyConstruct(arena, *from._impl_.received_at_) + : nullptr; + _impl_.attempted_at_ = (CheckHasBit(cached_has_bits, 0x00000020U)) + ? Super_::CopyConstruct(arena, *from._impl_.attempted_at_) + : nullptr; + _impl_.applied_at_ = (CheckHasBit(cached_has_bits, 0x00000040U)) + ? Super_::CopyConstruct(arena, *from._impl_.applied_at_) + : nullptr; + _impl_.verified_at_ = (CheckHasBit(cached_has_bits, 0x00000080U)) + ? Super_::CopyConstruct(arena, *from._impl_.verified_at_) + : nullptr; + _impl_.removed_at_ = (CheckHasBit(cached_has_bits, 0x00000100U)) + ? Super_::CopyConstruct(arena, *from._impl_.removed_at_) + : nullptr; + ::memcpy(reinterpret_cast(&_impl_) + + offsetof(Impl_, desired_revision_), + reinterpret_cast(&from._impl_) + + offsetof(Impl_, desired_revision_), + offsetof(Impl_, expired_latched_) - + offsetof(Impl_, desired_revision_) + + sizeof(Impl_::expired_latched_)); + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment_journal.v1.Record) +} +PROTOBUF_NDEBUG_INLINE Record::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + : error_(arena), + installed_route_(arena), + installed_boot_id_(arena) {} + +inline void Record::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + ::memset(reinterpret_cast(&_impl_) + + offsetof(Impl_, assignment_), + 0, + offsetof(Impl_, expired_latched_) - + offsetof(Impl_, assignment_) + + sizeof(Impl_::expired_latched_)); +} +Record::~Record() { + // @@protoc_insertion_point(destructor:xtcp_treatment_journal.v1.Record) + SharedDtor(*this); +} +inline void Record::SharedDtor(MessageLite& self) { + Record& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + this_._impl_.error_.Destroy(); + this_._impl_.installed_route_.Destroy(); + this_._impl_.installed_boot_id_.Destroy(); + delete this_._impl_.assignment_; + delete this_._impl_.received_at_; + delete this_._impl_.attempted_at_; + delete this_._impl_.applied_at_; + delete this_._impl_.verified_at_; + delete this_._impl_.removed_at_; + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull Record_class_data_ = + Record::InternalGenerateClassData_(Record_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +Record::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&Record_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(Record_class_data_.tc_table); + return Record_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +Record::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&Record_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&Record_globals_)); + return Record_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const Record::ParseTableT_ + Record::_table_ = + Record::InternalGenerateParseTable_(Record_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void Record::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment_journal.v1.Record) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x000000ffU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + this_._impl_.error_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + this_._impl_.installed_route_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + this_._impl_.installed_boot_id_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + ABSL_DCHECK(this_._impl_.assignment_ != nullptr); + this_._impl_.assignment_->Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + ABSL_DCHECK(this_._impl_.received_at_ != nullptr); + this_._impl_.received_at_->Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + ABSL_DCHECK(this_._impl_.attempted_at_ != nullptr); + this_._impl_.attempted_at_->Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + ABSL_DCHECK(this_._impl_.applied_at_ != nullptr); + this_._impl_.applied_at_->Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + ABSL_DCHECK(this_._impl_.verified_at_ != nullptr); + this_._impl_.verified_at_->Clear(); + } + } + if (CheckHasBit(cached_has_bits, 0x00000100U)) { + ABSL_DCHECK(this_._impl_.removed_at_ != nullptr); + this_._impl_.removed_at_->Clear(); + } + if (BatchCheckHasBit(cached_has_bits, 0x00000e00U)) { + ::memset(&this_._impl_.desired_revision_, 0, + static_cast<::size_t>( + reinterpret_cast(&this_._impl_.expired_latched_) - + reinterpret_cast(&this_._impl_.desired_revision_)) + + sizeof(_impl_.expired_latched_)); + } + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL Record::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const Record& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL Record::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const Record& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment_journal.v1.Record) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // .xtcp_treatment_journal.v1.Assignment assignment = 1 [json_name = "assignment"]; + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 1, *this_._impl_.assignment_, this_._impl_.assignment_->GetCachedSize(), target, + stream); + } + + // uint64 desired_revision = 2 [json_name = "desiredRevision"]; + if (CheckHasBit(cached_has_bits, 0x00000200U)) { + if (this_._internal_desired_revision() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray( + 2, this_._internal_desired_revision(), target); + } + } + + // .xtcp_treatment_journal.v1.ApplyStatus status = 3 [json_name = "status"]; + if (CheckHasBit(cached_has_bits, 0x00000400U)) { + if (this_._internal_status() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteEnumToArray( + 3, this_._internal_status(), target); + } + } + + // .google.protobuf.Timestamp received_at = 4 [json_name = "receivedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 4, *this_._impl_.received_at_, this_._impl_.received_at_->GetCachedSize(), target, + stream); + } + + // .google.protobuf.Timestamp attempted_at = 5 [json_name = "attemptedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 5, *this_._impl_.attempted_at_, this_._impl_.attempted_at_->GetCachedSize(), target, + stream); + } + + // .google.protobuf.Timestamp applied_at = 6 [json_name = "appliedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 6, *this_._impl_.applied_at_, this_._impl_.applied_at_->GetCachedSize(), target, + stream); + } + + // .google.protobuf.Timestamp verified_at = 7 [json_name = "verifiedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 7, *this_._impl_.verified_at_, this_._impl_.verified_at_->GetCachedSize(), target, + stream); + } + + // .google.protobuf.Timestamp removed_at = 8 [json_name = "removedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000100U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 8, *this_._impl_.removed_at_, this_._impl_.removed_at_->GetCachedSize(), target, + stream); + } + + // string error = 9 [json_name = "error"]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_error().empty()) { + const ::std::string& _s = this_._internal_error(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment_journal.v1.Record.error"); + target = stream->WriteStringMaybeAliased(9, _s, target); + } + } + + // bytes installed_route = 10 [json_name = "installedRoute"]; + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!this_._internal_installed_route().empty()) { + const ::std::string& _s = this_._internal_installed_route(); + target = stream->WriteBytesMaybeAliased(10, _s, target); + } + } + + // string installed_boot_id = 11 [json_name = "installedBootId"]; + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (!this_._internal_installed_boot_id().empty()) { + const ::std::string& _s = this_._internal_installed_boot_id(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment_journal.v1.Record.installed_boot_id"); + target = stream->WriteStringMaybeAliased(11, _s, target); + } + } + + // bool expired_latched = 12 [json_name = "expiredLatched"]; + if (CheckHasBit(cached_has_bits, 0x00000800U)) { + if (this_._internal_expired_latched() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray( + 12, this_._internal_expired_latched(), target); + } + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment_journal.v1.Record) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t Record::ByteSizeLong(const MessageLite& base) { + const Record& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t Record::ByteSizeLong() const { + const Record& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment_journal.v1.Record) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + ::_pbi::Prefetch5LinesFrom7Lines(&this_); + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x000000ffU)) { + // string error = 9 [json_name = "error"]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!this_._internal_error().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_error()); + } + } + // bytes installed_route = 10 [json_name = "installedRoute"]; + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!this_._internal_installed_route().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::BytesSize( + this_._internal_installed_route()); + } + } + // string installed_boot_id = 11 [json_name = "installedBootId"]; + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (!this_._internal_installed_boot_id().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_installed_boot_id()); + } + } + // .xtcp_treatment_journal.v1.Assignment assignment = 1 [json_name = "assignment"]; + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.assignment_); + } + // .google.protobuf.Timestamp received_at = 4 [json_name = "receivedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.received_at_); + } + // .google.protobuf.Timestamp attempted_at = 5 [json_name = "attemptedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.attempted_at_); + } + // .google.protobuf.Timestamp applied_at = 6 [json_name = "appliedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.applied_at_); + } + // .google.protobuf.Timestamp verified_at = 7 [json_name = "verifiedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.verified_at_); + } + } + if (BatchCheckHasBit(cached_has_bits, 0x00000f00U)) { + // .google.protobuf.Timestamp removed_at = 8 [json_name = "removedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000100U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.removed_at_); + } + // uint64 desired_revision = 2 [json_name = "desiredRevision"]; + if (CheckHasBit(cached_has_bits, 0x00000200U)) { + if (this_._internal_desired_revision() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne( + this_._internal_desired_revision()); + } + } + // .xtcp_treatment_journal.v1.ApplyStatus status = 3 [json_name = "status"]; + if (CheckHasBit(cached_has_bits, 0x00000400U)) { + if (this_._internal_status() != 0) { + total_size += 1 + + ::_pbi::WireFormatLite::EnumSize(this_._internal_status()); + } + } + // bool expired_latched = 12 [json_name = "expiredLatched"]; + if (CheckHasBit(cached_has_bits, 0x00000800U)) { + if (this_._internal_expired_latched() != 0) { + total_size += 2; + } + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void Record::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + ::google::protobuf::Arena* arena = _this->GetArena(); + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment_journal.v1.Record) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x000000ffU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + if (!from._internal_error().empty()) { + _this->_internal_set_error(from._internal_error()); + } else { + if (_this->_impl_.error_.IsDefault()) { + _this->_internal_set_error(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!from._internal_installed_route().empty()) { + _this->_internal_set_installed_route(from._internal_installed_route()); + } else { + if (_this->_impl_.installed_route_.IsDefault()) { + _this->_internal_set_installed_route(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (!from._internal_installed_boot_id().empty()) { + _this->_internal_set_installed_boot_id(from._internal_installed_boot_id()); + } else { + if (_this->_impl_.installed_boot_id_.IsDefault()) { + _this->_internal_set_installed_boot_id(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + ABSL_DCHECK(from._impl_.assignment_ != nullptr); + if (_this->_impl_.assignment_ == nullptr) { + _this->_impl_.assignment_ = Super_::CopyConstruct(arena, *from._impl_.assignment_); + } else { + _this->_impl_.assignment_->MergeFrom(*from._impl_.assignment_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + ABSL_DCHECK(from._impl_.received_at_ != nullptr); + if (_this->_impl_.received_at_ == nullptr) { + _this->_impl_.received_at_ = Super_::CopyConstruct(arena, *from._impl_.received_at_); + } else { + _this->_impl_.received_at_->MergeFrom(*from._impl_.received_at_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + ABSL_DCHECK(from._impl_.attempted_at_ != nullptr); + if (_this->_impl_.attempted_at_ == nullptr) { + _this->_impl_.attempted_at_ = Super_::CopyConstruct(arena, *from._impl_.attempted_at_); + } else { + _this->_impl_.attempted_at_->MergeFrom(*from._impl_.attempted_at_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + ABSL_DCHECK(from._impl_.applied_at_ != nullptr); + if (_this->_impl_.applied_at_ == nullptr) { + _this->_impl_.applied_at_ = Super_::CopyConstruct(arena, *from._impl_.applied_at_); + } else { + _this->_impl_.applied_at_->MergeFrom(*from._impl_.applied_at_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + ABSL_DCHECK(from._impl_.verified_at_ != nullptr); + if (_this->_impl_.verified_at_ == nullptr) { + _this->_impl_.verified_at_ = Super_::CopyConstruct(arena, *from._impl_.verified_at_); + } else { + _this->_impl_.verified_at_->MergeFrom(*from._impl_.verified_at_); + } + } + } + if (BatchCheckHasBit(cached_has_bits, 0x00000f00U)) { + if (CheckHasBit(cached_has_bits, 0x00000100U)) { + ABSL_DCHECK(from._impl_.removed_at_ != nullptr); + if (_this->_impl_.removed_at_ == nullptr) { + _this->_impl_.removed_at_ = Super_::CopyConstruct(arena, *from._impl_.removed_at_); + } else { + _this->_impl_.removed_at_->MergeFrom(*from._impl_.removed_at_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000200U)) { + if (from._internal_desired_revision() != 0) { + _this->_impl_.desired_revision_ = from._impl_.desired_revision_; + } + } + if (CheckHasBit(cached_has_bits, 0x00000400U)) { + if (from._internal_status() != 0) { + _this->_impl_.status_ = from._impl_.status_; + } + } + if (CheckHasBit(cached_has_bits, 0x00000800U)) { + if (from._internal_expired_latched() != 0) { + _this->_impl_.expired_latched_ = from._impl_.expired_latched_; + } + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void Record::CopyFrom(const Record& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment_journal.v1.Record) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void Record::InternalSwap(Record* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + auto* arena = GetArena(); + ABSL_DCHECK_EQ(arena, other->GetArena()); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.error_, &other->_impl_.error_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.installed_route_, &other->_impl_.installed_route_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.installed_boot_id_, &other->_impl_.installed_boot_id_, arena); + ::google::protobuf::internal::memswap< + PROTOBUF_FIELD_OFFSET(Record, _impl_.expired_latched_) + + sizeof(Record::_impl_.expired_latched_) + - PROTOBUF_FIELD_OFFSET(Record, _impl_.assignment_)>( + reinterpret_cast(&_impl_.assignment_), + reinterpret_cast(&other->_impl_.assignment_)); +} + +::google::protobuf::Metadata Record::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// =================================================================== + +void Journal::clear_updated_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.updated_at_ != nullptr) _impl_.updated_at_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); +} +Journal::Journal(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, Journal_get_class_data()) { +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + SharedCtor(arena); + // @@protoc_insertion_point(arena_constructor:xtcp_treatment_journal.v1.Journal) +} +PROTOBUF_NDEBUG_INLINE Journal::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + [[maybe_unused]] const ::xtcp_treatment_journal::v1::Journal& from_msg) + : _has_bits_{from._has_bits_}, + records_ { + visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment_journal::v1::Journal, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Journal, _impl_.records_)>() + , arena, from.records_ + } + , + agent_id_(arena, from.agent_id_), + location_(arena, from.location_), + boot_id_(arena, from.boot_id_), + suspension_reason_(arena, from.suspension_reason_) {} + +Journal::Journal( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, + const Journal& from) +#if defined(PROTOBUF_CUSTOM_VTABLE) + : Super_(arena, Journal_get_class_data()) { + +#else // PROTOBUF_CUSTOM_VTABLE + : Super_(arena) { +#endif // PROTOBUF_CUSTOM_VTABLE + Journal* const _this = this; + (void)_this; + _internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); + new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); + ::uint32_t cached_has_bits = _impl_._has_bits_[0]; + _impl_.desired_ = (CheckHasBit(cached_has_bits, 0x00000020U)) + ? Super_::CopyConstruct(arena, *from._impl_.desired_) + : nullptr; + _impl_.updated_at_ = (CheckHasBit(cached_has_bits, 0x00000040U)) + ? Super_::CopyConstruct(arena, *from._impl_.updated_at_) + : nullptr; + _impl_.locally_suspended_ = from._impl_.locally_suspended_; + + // @@protoc_insertion_point(copy_constructor:xtcp_treatment_journal.v1.Journal) +} +PROTOBUF_NDEBUG_INLINE Journal::Impl_::Impl_( + [[maybe_unused]] ::google::protobuf::internal::InternalVisibility visibility, + [[maybe_unused]] ::google::protobuf::Arena* PROTOBUF_NULLABLE arena) + : records_ { visibility, ::_pbi::InternalMetadataOffset::Build< + ::xtcp_treatment_journal::v1::Journal, + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Journal, _impl_.records_)>() + } + , + agent_id_(arena), + location_(arena), + boot_id_(arena), + suspension_reason_(arena) {} + +inline void Journal::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { + new (&_impl_) Impl_(internal_visibility(), arena); + ::memset(reinterpret_cast(&_impl_) + + offsetof(Impl_, desired_), + 0, + offsetof(Impl_, locally_suspended_) - + offsetof(Impl_, desired_) + + sizeof(Impl_::locally_suspended_)); +} +Journal::~Journal() { + // @@protoc_insertion_point(destructor:xtcp_treatment_journal.v1.Journal) + SharedDtor(*this); +} +inline void Journal::SharedDtor(MessageLite& self) { + Journal& this_ = static_cast(self); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + this_._internal_metadata_.Delete<::google::protobuf::UnknownFieldSet>(); + ABSL_DCHECK(this_.GetArena() == nullptr); + this_._impl_.agent_id_.Destroy(); + this_._impl_.location_.Destroy(); + this_._impl_.boot_id_.Destroy(); + this_._impl_.suspension_reason_.Destroy(); + delete this_._impl_.desired_; + delete this_._impl_.updated_at_; + this_._impl_.~Impl_(); +} + +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const + ::google::protobuf::internal::ClassDataFull Journal_class_data_ = + Journal::InternalGenerateClassData_(Journal_globals_._default); + +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +Journal::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&Journal_class_data_); + ::google::protobuf::internal::PrefetchToLocalCache(Journal_class_data_.tc_table); + return Journal_class_data_.base(); +} +#else +PROTOBUF_ATTRIBUTE_WEAK const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL +Journal::GetClassData() const { + ::google::protobuf::internal::PrefetchToLocalCache(&Journal_globals_); + ::google::protobuf::internal::PrefetchToLocalCache( + ::google::protobuf::internal::MessageGlobalsBase::ToParseTableBase(&Journal_globals_)); + return Journal_globals_.GetClassData(); +} +#endif // !PROTOBUF_MESSAGE_GLOBALS +#ifndef PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_CONSTINIT +PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const Journal::ParseTableT_ + Journal::_table_ = + Journal::InternalGenerateParseTable_(Journal_class_data_.base()); +#endif // !PROTOBUF_MESSAGE_GLOBALS +PROTOBUF_NOINLINE void Journal::Clear() { + auto& this_ [[maybe_unused]] = *this; + // @@protoc_insertion_point(message_clear_start:xtcp_treatment_journal.v1.Journal) + ::google::protobuf::internal::TSanWrite(&_impl_); + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x0000007fU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + _impl_.records_.Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + this_._impl_.agent_id_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + this_._impl_.location_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + this_._impl_.boot_id_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + this_._impl_.suspension_reason_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + ABSL_DCHECK(this_._impl_.desired_ != nullptr); + this_._impl_.desired_->Clear(); + } + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + ABSL_DCHECK(this_._impl_.updated_at_ != nullptr); + this_._impl_.updated_at_->Clear(); + } + } + this_._impl_.locally_suspended_ = false; + _impl_._has_bits_.Clear(); + _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::uint8_t* PROTOBUF_NONNULL Journal::_InternalSerialize( + const ::google::protobuf::MessageLite& base, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) { + const Journal& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::uint8_t* PROTOBUF_NONNULL Journal::_InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + const Journal& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + this_.CheckHasBitConsistency(); + } + // @@protoc_insertion_point(serialize_to_array_start:xtcp_treatment_journal.v1.Journal) + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = this_._impl_._has_bits_[0]; + // string agent_id = 1 [json_name = "agentId"]; + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!this_._internal_agent_id().empty()) { + const ::std::string& _s = this_._internal_agent_id(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment_journal.v1.Journal.agent_id"); + target = stream->WriteStringMaybeAliased(1, _s, target); + } + } + + // string location = 2 [json_name = "location"]; + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (!this_._internal_location().empty()) { + const ::std::string& _s = this_._internal_location(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment_journal.v1.Journal.location"); + target = stream->WriteStringMaybeAliased(2, _s, target); + } + } + + // string boot_id = 3 [json_name = "bootId"]; + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (!this_._internal_boot_id().empty()) { + const ::std::string& _s = this_._internal_boot_id(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment_journal.v1.Journal.boot_id"); + target = stream->WriteStringMaybeAliased(3, _s, target); + } + } + + // .xtcp_treatment_journal.v1.Desired desired = 4 [json_name = "desired"]; + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 4, *this_._impl_.desired_, this_._impl_.desired_->GetCachedSize(), target, + stream); + } + + // bool locally_suspended = 5 [json_name = "locallySuspended"]; + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + if (this_._internal_locally_suspended() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteBoolToArray( + 5, this_._internal_locally_suspended(), target); + } + } + + // string suspension_reason = 6 [json_name = "suspensionReason"]; + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (!this_._internal_suspension_reason().empty()) { + const ::std::string& _s = this_._internal_suspension_reason(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment_journal.v1.Journal.suspension_reason"); + target = stream->WriteStringMaybeAliased(6, _s, target); + } + } + + // repeated .xtcp_treatment_journal.v1.Record records = 7 [json_name = "records"]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + for (unsigned i = 0, n = static_cast( + this_._internal_records_size()); + i < n; i++) { + const auto& repfield = this_._internal_records().Get(i); + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 7, repfield, repfield.GetCachedSize(), target, + stream); + } + } + + // .google.protobuf.Timestamp updated_at = 8 [json_name = "updatedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( + 8, *this_._impl_.updated_at_, this_._impl_.updated_at_->GetCachedSize(), target, + stream); + } + + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { + target = + ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + this_._internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:xtcp_treatment_journal.v1.Journal) + return target; +} + +#if defined(PROTOBUF_CUSTOM_VTABLE) +::size_t Journal::ByteSizeLong(const MessageLite& base) { + const Journal& this_ = static_cast(base); +#else // PROTOBUF_CUSTOM_VTABLE +::size_t Journal::ByteSizeLong() const { + const Journal& this_ = *this; +#endif // PROTOBUF_CUSTOM_VTABLE + // @@protoc_insertion_point(message_byte_size_start:xtcp_treatment_journal.v1.Journal) + ::size_t total_size = 0; + + ::uint32_t cached_has_bits [[maybe_unused]] = 0; + + ::_pbi::Prefetch5LinesFrom7Lines(&this_); + cached_has_bits = this_._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x000000ffU)) { + // repeated .xtcp_treatment_journal.v1.Record records = 7 [json_name = "records"]; + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + total_size += 1UL * this_._internal_records_size(); + for (const auto& msg : this_._internal_records()) { + total_size += ::google::protobuf::internal::WireFormatLite::MessageSize(msg); + } + } + // string agent_id = 1 [json_name = "agentId"]; + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!this_._internal_agent_id().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_agent_id()); + } + } + // string location = 2 [json_name = "location"]; + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (!this_._internal_location().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_location()); + } + } + // string boot_id = 3 [json_name = "bootId"]; + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (!this_._internal_boot_id().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_boot_id()); + } + } + // string suspension_reason = 6 [json_name = "suspensionReason"]; + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (!this_._internal_suspension_reason().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_suspension_reason()); + } + } + // .xtcp_treatment_journal.v1.Desired desired = 4 [json_name = "desired"]; + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.desired_); + } + // .google.protobuf.Timestamp updated_at = 8 [json_name = "updatedAt"]; + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.updated_at_); + } + // bool locally_suspended = 5 [json_name = "locallySuspended"]; + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + if (this_._internal_locally_suspended() != 0) { + total_size += 2; + } + } + } + return this_.MaybeComputeUnknownFieldsSize(total_size, + &this_._impl_._cached_size_); +} + +void Journal::MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + if constexpr (::_pbi::DebugHardenCheckHasBitConsistency()) { + from.CheckHasBitConsistency(); + } + ::google::protobuf::Arena* arena = _this->GetArena(); + // @@protoc_insertion_point(class_specific_merge_from_start:xtcp_treatment_journal.v1.Journal) + ABSL_DCHECK_NE(&from, _this); + ::uint32_t cached_has_bits = 0; + (void)cached_has_bits; + + cached_has_bits = from._impl_._has_bits_[0]; + if (BatchCheckHasBit(cached_has_bits, 0x000000ffU)) { + if (CheckHasBit(cached_has_bits, 0x00000001U)) { + _this->_internal_mutable_records()->InternalMergeFromWithArena( + ::google::protobuf::MessageLite::internal_visibility(), arena, + from._internal_records()); + } + if (CheckHasBit(cached_has_bits, 0x00000002U)) { + if (!from._internal_agent_id().empty()) { + _this->_internal_set_agent_id(from._internal_agent_id()); + } else { + if (_this->_impl_.agent_id_.IsDefault()) { + _this->_internal_set_agent_id(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000004U)) { + if (!from._internal_location().empty()) { + _this->_internal_set_location(from._internal_location()); + } else { + if (_this->_impl_.location_.IsDefault()) { + _this->_internal_set_location(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (!from._internal_boot_id().empty()) { + _this->_internal_set_boot_id(from._internal_boot_id()); + } else { + if (_this->_impl_.boot_id_.IsDefault()) { + _this->_internal_set_boot_id(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (!from._internal_suspension_reason().empty()) { + _this->_internal_set_suspension_reason(from._internal_suspension_reason()); + } else { + if (_this->_impl_.suspension_reason_.IsDefault()) { + _this->_internal_set_suspension_reason(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + ABSL_DCHECK(from._impl_.desired_ != nullptr); + if (_this->_impl_.desired_ == nullptr) { + _this->_impl_.desired_ = Super_::CopyConstruct(arena, *from._impl_.desired_); + } else { + _this->_impl_.desired_->MergeFrom(*from._impl_.desired_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000040U)) { + ABSL_DCHECK(from._impl_.updated_at_ != nullptr); + if (_this->_impl_.updated_at_ == nullptr) { + _this->_impl_.updated_at_ = Super_::CopyConstruct(arena, *from._impl_.updated_at_); + } else { + _this->_impl_.updated_at_->MergeFrom(*from._impl_.updated_at_); + } + } + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + if (from._internal_locally_suspended() != 0) { + _this->_impl_.locally_suspended_ = from._impl_.locally_suspended_; + } + } + } + _this->_impl_._has_bits_[0] |= cached_has_bits; + _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( + from._internal_metadata_); +} + +void Journal::CopyFrom(const Journal& from) { + // @@protoc_insertion_point(class_specific_copy_from_start:xtcp_treatment_journal.v1.Journal) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + + +void Journal::InternalSwap(Journal* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { + using ::std::swap; + auto* arena = GetArena(); + ABSL_DCHECK_EQ(arena, other->GetArena()); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]); + _impl_.records_.InternalSwap(&other->_impl_.records_); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.agent_id_, &other->_impl_.agent_id_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.location_, &other->_impl_.location_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.boot_id_, &other->_impl_.boot_id_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.suspension_reason_, &other->_impl_.suspension_reason_, arena); + ::google::protobuf::internal::memswap< + PROTOBUF_FIELD_OFFSET(Journal, _impl_.locally_suspended_) + + sizeof(Journal::_impl_.locally_suspended_) + - PROTOBUF_FIELD_OFFSET(Journal, _impl_.desired_)>( + reinterpret_cast(&_impl_.desired_), + reinterpret_cast(&other->_impl_.desired_)); +} + +::google::protobuf::Metadata Journal::GetMetadata() const { + return Super_::GetMetadataImpl(GetClassData()->full()); +} +// @@protoc_insertion_point(namespace_scope) +} // namespace v1 +} // namespace xtcp_treatment_journal +namespace google { +namespace protobuf { +} // namespace protobuf +} // namespace google +// @@protoc_insertion_point(global_scope) +PROTOBUF_ATTRIBUTE_INIT_PRIORITY2 static ::std::false_type + _static_init2_ [[maybe_unused]] = + (::_pbi::AddDescriptors(&descriptor_table_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto), + ::std::false_type{}); +#include "google/protobuf/port_undef.inc" diff --git a/gen/cpp/xtcp_treatment_journal/v1/journal.pb.h b/gen/cpp/xtcp_treatment_journal/v1/journal.pb.h new file mode 100644 index 000000000..d5a53f057 --- /dev/null +++ b/gen/cpp/xtcp_treatment_journal/v1/journal.pb.h @@ -0,0 +1,4521 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE +// source: xtcp_treatment_journal/v1/journal.proto +// Protobuf C++ Version: 7.36.2 + +#ifndef xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto_2epb_2eh +#define xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto_2epb_2eh + +#include +#include +#include +#include + +// clang-format off +#include "google/protobuf/runtime_version.h" +#if PROTOBUF_VERSION != 7036002 +#error "Protobuf C++ gencode is built with an incompatible version of" +#error "Protobuf C++ headers/runtime. See" +#error "https://protobuf.dev/support/cross-version-runtime-guarantee/#cpp" +#endif +#include "google/protobuf/io/coded_stream.h" +#include "google/protobuf/arena.h" +#include "google/protobuf/arenastring.h" +#include "google/protobuf/generated_message_tctable_decl.h" +#include "google/protobuf/generated_message_util.h" +#include "google/protobuf/metadata_lite.h" +#include "google/protobuf/generated_message_reflection.h" +#include "google/protobuf/message.h" +#include "google/protobuf/message_lite.h" +#include "google/protobuf/repeated_field.h" // IWYU pragma: export +#include "google/protobuf/extension_set.h" // IWYU pragma: export +#include "google/protobuf/generated_enum_reflection.h" +#include "google/protobuf/unknown_field_set.h" +#include "google/protobuf/timestamp.pb.h" +// @@protoc_insertion_point(includes) + +// Must be included last. +#include "google/protobuf/port_def.inc" + +#define PROTOBUF_INTERNAL_EXPORT_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto + +// Internal implementation detail -- do not use these members. +struct TableStruct_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto { + static const ::uint32_t offsets[]; +}; +extern "C" { +extern const ::google::protobuf::internal::DescriptorTable descriptor_table_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto; +} // extern "C" +namespace xtcp_treatment_journal { +namespace v1 { +enum ApplyStatus : int; +extern const uint32_t ApplyStatus_internal_data_[]; +enum AssignmentMode : int; +extern const uint32_t AssignmentMode_internal_data_[]; +class Assignment; +struct AssignmentGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern AssignmentGlobalsTypeInternal Assignment_globals_; +extern const ::google::protobuf::internal::ClassDataFull Assignment_class_data_; +#else +extern const AssignmentGlobalsTypeInternal Assignment_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +class Desired; +struct DesiredGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern DesiredGlobalsTypeInternal Desired_globals_; +extern const ::google::protobuf::internal::ClassDataFull Desired_class_data_; +#else +extern const DesiredGlobalsTypeInternal Desired_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +class Journal; +struct JournalGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern JournalGlobalsTypeInternal Journal_globals_; +extern const ::google::protobuf::internal::ClassDataFull Journal_class_data_; +#else +extern const JournalGlobalsTypeInternal Journal_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +class Record; +struct RecordGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern RecordGlobalsTypeInternal Record_globals_; +extern const ::google::protobuf::internal::ClassDataFull Record_class_data_; +#else +extern const RecordGlobalsTypeInternal Record_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +class TCPSettings; +struct TCPSettingsGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern TCPSettingsGlobalsTypeInternal TCPSettings_globals_; +extern const ::google::protobuf::internal::ClassDataFull TCPSettings_class_data_; +#else +extern const TCPSettingsGlobalsTypeInternal TCPSettings_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +class Treatment; +struct TreatmentGlobalsTypeInternal; +#ifndef PROTOBUF_MESSAGE_GLOBALS +extern TreatmentGlobalsTypeInternal Treatment_globals_; +extern const ::google::protobuf::internal::ClassDataFull Treatment_class_data_; +#else +extern const TreatmentGlobalsTypeInternal Treatment_globals_; +#endif // PROTOBUF_MESSAGE_GLOBALS +} // namespace v1 +} // namespace xtcp_treatment_journal +namespace google { +namespace protobuf { +template <> +internal::EnumTraitsT<::xtcp_treatment_journal::v1::ApplyStatus_internal_data_> + internal::EnumTraitsImpl::value<::xtcp_treatment_journal::v1::ApplyStatus>; +template <> +internal::EnumTraitsT<::xtcp_treatment_journal::v1::AssignmentMode_internal_data_> + internal::EnumTraitsImpl::value<::xtcp_treatment_journal::v1::AssignmentMode>; +} // namespace protobuf +} // namespace google + +namespace xtcp_treatment_journal { +namespace v1 { +enum AssignmentMode : int { + ASSIGNMENT_MODE_UNSPECIFIED = 0, + ASSIGNMENT_MODE_TRIAL = 1, + ASSIGNMENT_MODE_STABLE = 2, + ASSIGNMENT_MODE_SUSPENDED = 3, + ASSIGNMENT_MODE_RETIRED = 4, + AssignmentMode_INT_MIN_SENTINEL_DO_NOT_USE_ = + ::std::numeric_limits<::int32_t>::min(), + AssignmentMode_INT_MAX_SENTINEL_DO_NOT_USE_ = + ::std::numeric_limits<::int32_t>::max(), +}; + +extern const uint32_t AssignmentMode_internal_data_[]; +inline constexpr AssignmentMode AssignmentMode_MIN = + static_cast(0); +inline constexpr AssignmentMode AssignmentMode_MAX = + static_cast(4); +[[nodiscard]] inline bool AssignmentMode_IsValid(int value) { + return 0 <= value && value <= 4; +} +inline constexpr int AssignmentMode_ARRAYSIZE = 4 + 1; +[[nodiscard]] const ::google::protobuf::EnumDescriptor* PROTOBUF_NONNULL +AssignmentMode_descriptor(); +[[nodiscard]] inline auto ProtobufInternalGetEnumDescriptor(AssignmentMode) { + return AssignmentMode_descriptor(); +} +template +[[nodiscard]] const ::std::string& AssignmentMode_Name(T value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to AssignmentMode_Name()."); + return AssignmentMode_Name(static_cast(value)); +} +template <> +[[nodiscard]] inline const ::std::string& AssignmentMode_Name(AssignmentMode value) { + return ::google::protobuf::internal::NameOfDenseEnum( + static_cast(value)); +} +[[nodiscard]] inline bool AssignmentMode_Parse( + ::absl::string_view name, AssignmentMode* PROTOBUF_NONNULL value) { + return ::google::protobuf::internal::ParseNamedEnum(AssignmentMode_descriptor(), name, + value); +} +enum ApplyStatus : int { + APPLY_STATUS_UNSPECIFIED = 0, + APPLY_STATUS_PENDING = 1, + APPLY_STATUS_VERIFIED = 2, + APPLY_STATUS_REMOVED = 3, + APPLY_STATUS_EXPIRED = 4, + APPLY_STATUS_FAILED = 5, + ApplyStatus_INT_MIN_SENTINEL_DO_NOT_USE_ = + ::std::numeric_limits<::int32_t>::min(), + ApplyStatus_INT_MAX_SENTINEL_DO_NOT_USE_ = + ::std::numeric_limits<::int32_t>::max(), +}; + +extern const uint32_t ApplyStatus_internal_data_[]; +inline constexpr ApplyStatus ApplyStatus_MIN = + static_cast(0); +inline constexpr ApplyStatus ApplyStatus_MAX = + static_cast(5); +[[nodiscard]] inline bool ApplyStatus_IsValid(int value) { + return 0 <= value && value <= 5; +} +inline constexpr int ApplyStatus_ARRAYSIZE = 5 + 1; +[[nodiscard]] const ::google::protobuf::EnumDescriptor* PROTOBUF_NONNULL +ApplyStatus_descriptor(); +[[nodiscard]] inline auto ProtobufInternalGetEnumDescriptor(ApplyStatus) { + return ApplyStatus_descriptor(); +} +template +[[nodiscard]] const ::std::string& ApplyStatus_Name(T value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to ApplyStatus_Name()."); + return ApplyStatus_Name(static_cast(value)); +} +template <> +[[nodiscard]] inline const ::std::string& ApplyStatus_Name(ApplyStatus value) { + return ::google::protobuf::internal::NameOfDenseEnum( + static_cast(value)); +} +[[nodiscard]] inline bool ApplyStatus_Parse( + ::absl::string_view name, ApplyStatus* PROTOBUF_NONNULL value) { + return ::google::protobuf::internal::ParseNamedEnum(ApplyStatus_descriptor(), name, + value); +} +using ::google::protobuf::internal::generated_enum::AbslParseFlag; +using ::google::protobuf::internal::generated_enum::AbslUnparseFlag; + +// =================================================================== + + +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED TCPSettings final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment_journal.v1.TCPSettings) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline TCPSettings() : TCPSettings(nullptr) {} + ~TCPSettings() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(TCPSettings* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(TCPSettings)); + } +#endif + + template + explicit constexpr TCPSettings(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline TCPSettings(const TCPSettings& from) : TCPSettings(nullptr, from) {} + inline TCPSettings(TCPSettings&& from) noexcept : TCPSettings(nullptr, ::std::move(from)) {} + inline TCPSettings& operator=(const TCPSettings& from) { + CopyFrom(from); + return *this; + } + inline TCPSettings& operator=(TCPSettings&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const TCPSettings& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&TCPSettings_globals_); + } + static constexpr int kIndexInFileMessages = 0; + friend void swap(TCPSettings& a, TCPSettings& b) { a.Swap(&b); } + inline void Swap(TCPSettings* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(TCPSettings* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] TCPSettings* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const TCPSettings& from); + using Super_::MergeFrom; + void MergeFrom(const TCPSettings& from) { TCPSettings::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(TCPSettings* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment_journal.v1.TCPSettings"; } + + explicit TCPSettings(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + TCPSettings(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const TCPSettings& from); + TCPSettings( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, TCPSettings&& from) noexcept + : TCPSettings(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kCongestionControlFieldNumber = 1, + kRtoMinMsFieldNumber = 3, + kInitialCongestionWindowFieldNumber = 4, + kInitialReceiveWindowFieldNumber = 5, + kCongestionLockFieldNumber = 2, + kQuickAckFieldNumber = 6, + kEcnFieldNumber = 7, + }; + // optional string congestion_control = 1 [json_name = "congestionControl"]; + [[nodiscard]] bool has_congestion_control() const; + void clear_congestion_control() ; + [[nodiscard]] const ::std::string& congestion_control() const; + template + void set_congestion_control(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_congestion_control(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_congestion_control(); + void set_allocated_congestion_control(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_congestion_control() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_congestion_control(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_congestion_control(); + + public: + // optional uint32 rto_min_ms = 3 [json_name = "rtoMinMs"]; + [[nodiscard]] bool has_rto_min_ms() const; + void clear_rto_min_ms() ; + [[nodiscard]] ::uint32_t rto_min_ms() const; + void set_rto_min_ms(::uint32_t value); + + private: + ::uint32_t _internal_rto_min_ms() const; + void _internal_set_rto_min_ms(::uint32_t value); + + public: + // optional uint32 initial_congestion_window = 4 [json_name = "initialCongestionWindow"]; + [[nodiscard]] bool has_initial_congestion_window() const; + void clear_initial_congestion_window() ; + [[nodiscard]] ::uint32_t initial_congestion_window() const; + void set_initial_congestion_window(::uint32_t value); + + private: + ::uint32_t _internal_initial_congestion_window() const; + void _internal_set_initial_congestion_window(::uint32_t value); + + public: + // optional uint32 initial_receive_window = 5 [json_name = "initialReceiveWindow"]; + [[nodiscard]] bool has_initial_receive_window() const; + void clear_initial_receive_window() ; + [[nodiscard]] ::uint32_t initial_receive_window() const; + void set_initial_receive_window(::uint32_t value); + + private: + ::uint32_t _internal_initial_receive_window() const; + void _internal_set_initial_receive_window(::uint32_t value); + + public: + // bool congestion_lock = 2 [json_name = "congestionLock"]; + void clear_congestion_lock() ; + [[nodiscard]] bool congestion_lock() const; + void set_congestion_lock(bool value); + + private: + bool _internal_congestion_lock() const; + void _internal_set_congestion_lock(bool value); + + public: + // optional bool quick_ack = 6 [json_name = "quickAck"]; + [[nodiscard]] bool has_quick_ack() const; + void clear_quick_ack() ; + [[nodiscard]] bool quick_ack() const; + void set_quick_ack(bool value); + + private: + bool _internal_quick_ack() const; + void _internal_set_quick_ack(bool value); + + public: + // optional bool ecn = 7 [json_name = "ecn"]; + [[nodiscard]] bool has_ecn() const; + void clear_ecn() ; + [[nodiscard]] bool ecn() const; + void set_ecn(bool value); + + private: + bool _internal_ecn() const; + void _internal_set_ecn(bool value); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment_journal.v1.TCPSettings) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<3, 7, + 0, 64, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const TCPSettings& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::google::protobuf::internal::ArenaStringPtr congestion_control_; + ::uint32_t rto_min_ms_; + ::uint32_t initial_congestion_window_; + ::uint32_t initial_receive_window_; + bool congestion_lock_; + bool quick_ack_; + bool ecn_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto; +}; +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Treatment final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment_journal.v1.Treatment) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline Treatment() : Treatment(nullptr) {} + ~Treatment() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(Treatment* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(Treatment)); + } +#endif + + template + explicit constexpr Treatment(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline Treatment(const Treatment& from) : Treatment(nullptr, from) {} + inline Treatment(Treatment&& from) noexcept : Treatment(nullptr, ::std::move(from)) {} + inline Treatment& operator=(const Treatment& from) { + CopyFrom(from); + return *this; + } + inline Treatment& operator=(Treatment&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const Treatment& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&Treatment_globals_); + } + static constexpr int kIndexInFileMessages = 1; + friend void swap(Treatment& a, Treatment& b) { a.Swap(&b); } + inline void Swap(Treatment* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(Treatment* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] Treatment* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const Treatment& from); + using Super_::MergeFrom; + void MergeFrom(const Treatment& from) { Treatment::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(Treatment* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment_journal.v1.Treatment"; } + + explicit Treatment(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + Treatment(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Treatment& from); + Treatment( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, Treatment&& from) noexcept + : Treatment(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kIdFieldNumber = 1, + kDestinationFieldNumber = 3, + kSettingsFieldNumber = 4, + kRevisionFieldNumber = 2, + kEcnFabricVerifiedFieldNumber = 5, + }; + // string id = 1 [json_name = "id"]; + void clear_id() ; + [[nodiscard]] const ::std::string& id() const; + template + void set_id(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_id(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_id(); + void set_allocated_id(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_id() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_id(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_id(); + + public: + // string destination = 3 [json_name = "destination"]; + void clear_destination() ; + [[nodiscard]] const ::std::string& destination() const; + template + void set_destination(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_destination(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_destination(); + void set_allocated_destination(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_destination() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_destination(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_destination(); + + public: + // .xtcp_treatment_journal.v1.TCPSettings settings = 4 [json_name = "settings"]; + [[nodiscard]] bool has_settings() const; + void clear_settings() ; + [[nodiscard]] const ::xtcp_treatment_journal::v1::TCPSettings& settings() const; + [[nodiscard]] ::xtcp_treatment_journal::v1::TCPSettings* PROTOBUF_NULLABLE release_settings(); + ::xtcp_treatment_journal::v1::TCPSettings* PROTOBUF_NONNULL mutable_settings(); + void set_allocated_settings(::xtcp_treatment_journal::v1::TCPSettings* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_settings(::xtcp_treatment_journal::v1::TCPSettings* PROTOBUF_NULLABLE value); + ::xtcp_treatment_journal::v1::TCPSettings* PROTOBUF_NULLABLE unsafe_arena_release_settings(); + + private: + const ::xtcp_treatment_journal::v1::TCPSettings& _internal_settings() const; + ::xtcp_treatment_journal::v1::TCPSettings* PROTOBUF_NONNULL _internal_mutable_settings(); + + public: + // uint64 revision = 2 [json_name = "revision"]; + void clear_revision() ; + [[nodiscard]] ::uint64_t revision() const; + void set_revision(::uint64_t value); + + private: + ::uint64_t _internal_revision() const; + void _internal_set_revision(::uint64_t value); + + public: + // bool ecn_fabric_verified = 5 [json_name = "ecnFabricVerified"]; + void clear_ecn_fabric_verified() ; + [[nodiscard]] bool ecn_fabric_verified() const; + void set_ecn_fabric_verified(bool value); + + private: + bool _internal_ecn_fabric_verified() const; + void _internal_set_ecn_fabric_verified(bool value); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment_journal.v1.Treatment) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<3, 5, + 1, 57, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const Treatment& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::google::protobuf::internal::ArenaStringPtr id_; + ::google::protobuf::internal::ArenaStringPtr destination_; + ::xtcp_treatment_journal::v1::TCPSettings* PROTOBUF_NULLABLE settings_; + ::uint64_t revision_; + bool ecn_fabric_verified_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto; +}; +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Assignment final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment_journal.v1.Assignment) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline Assignment() : Assignment(nullptr) {} + ~Assignment() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(Assignment* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(Assignment)); + } +#endif + + template + explicit constexpr Assignment(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline Assignment(const Assignment& from) : Assignment(nullptr, from) {} + inline Assignment(Assignment&& from) noexcept : Assignment(nullptr, ::std::move(from)) {} + inline Assignment& operator=(const Assignment& from) { + CopyFrom(from); + return *this; + } + inline Assignment& operator=(Assignment&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const Assignment& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&Assignment_globals_); + } + static constexpr int kIndexInFileMessages = 2; + friend void swap(Assignment& a, Assignment& b) { a.Swap(&b); } + inline void Swap(Assignment* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(Assignment* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] Assignment* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const Assignment& from); + using Super_::MergeFrom; + void MergeFrom(const Assignment& from) { Assignment::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(Assignment* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment_journal.v1.Assignment"; } + + explicit Assignment(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + Assignment(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Assignment& from); + Assignment( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, Assignment&& from) noexcept + : Assignment(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kExperimentIdFieldNumber = 1, + kTreatmentFieldNumber = 2, + kLeaseExpiresAtFieldNumber = 4, + kPredecessorFieldNumber = 5, + kGenerationFieldNumber = 6, + kModeFieldNumber = 3, + }; + // string experiment_id = 1 [json_name = "experimentId"]; + void clear_experiment_id() ; + [[nodiscard]] const ::std::string& experiment_id() const; + template + void set_experiment_id(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_experiment_id(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_experiment_id(); + void set_allocated_experiment_id(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_experiment_id() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_experiment_id(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_experiment_id(); + + public: + // .xtcp_treatment_journal.v1.Treatment treatment = 2 [json_name = "treatment"]; + [[nodiscard]] bool has_treatment() const; + void clear_treatment() ; + [[nodiscard]] const ::xtcp_treatment_journal::v1::Treatment& treatment() const; + [[nodiscard]] ::xtcp_treatment_journal::v1::Treatment* PROTOBUF_NULLABLE release_treatment(); + ::xtcp_treatment_journal::v1::Treatment* PROTOBUF_NONNULL mutable_treatment(); + void set_allocated_treatment(::xtcp_treatment_journal::v1::Treatment* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_treatment(::xtcp_treatment_journal::v1::Treatment* PROTOBUF_NULLABLE value); + ::xtcp_treatment_journal::v1::Treatment* PROTOBUF_NULLABLE unsafe_arena_release_treatment(); + + private: + const ::xtcp_treatment_journal::v1::Treatment& _internal_treatment() const; + ::xtcp_treatment_journal::v1::Treatment* PROTOBUF_NONNULL _internal_mutable_treatment(); + + public: + // .google.protobuf.Timestamp lease_expires_at = 4 [json_name = "leaseExpiresAt"]; + [[nodiscard]] bool has_lease_expires_at() const; + void clear_lease_expires_at() ; + [[nodiscard]] const ::google::protobuf::Timestamp& lease_expires_at() const; + [[nodiscard]] ::google::protobuf::Timestamp* PROTOBUF_NULLABLE release_lease_expires_at(); + ::google::protobuf::Timestamp* PROTOBUF_NONNULL mutable_lease_expires_at(); + void set_allocated_lease_expires_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_lease_expires_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE unsafe_arena_release_lease_expires_at(); + + private: + const ::google::protobuf::Timestamp& _internal_lease_expires_at() const; + ::google::protobuf::Timestamp* PROTOBUF_NONNULL _internal_mutable_lease_expires_at(); + + public: + // .xtcp_treatment_journal.v1.Treatment predecessor = 5 [json_name = "predecessor"]; + [[nodiscard]] bool has_predecessor() const; + void clear_predecessor() ; + [[nodiscard]] const ::xtcp_treatment_journal::v1::Treatment& predecessor() const; + [[nodiscard]] ::xtcp_treatment_journal::v1::Treatment* PROTOBUF_NULLABLE release_predecessor(); + ::xtcp_treatment_journal::v1::Treatment* PROTOBUF_NONNULL mutable_predecessor(); + void set_allocated_predecessor(::xtcp_treatment_journal::v1::Treatment* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_predecessor(::xtcp_treatment_journal::v1::Treatment* PROTOBUF_NULLABLE value); + ::xtcp_treatment_journal::v1::Treatment* PROTOBUF_NULLABLE unsafe_arena_release_predecessor(); + + private: + const ::xtcp_treatment_journal::v1::Treatment& _internal_predecessor() const; + ::xtcp_treatment_journal::v1::Treatment* PROTOBUF_NONNULL _internal_mutable_predecessor(); + + public: + // uint64 generation = 6 [json_name = "generation"]; + void clear_generation() ; + [[nodiscard]] ::uint64_t generation() const; + void set_generation(::uint64_t value); + + private: + ::uint64_t _internal_generation() const; + void _internal_set_generation(::uint64_t value); + + public: + // .xtcp_treatment_journal.v1.AssignmentMode mode = 3 [json_name = "mode"]; + void clear_mode() ; + [[nodiscard]] ::xtcp_treatment_journal::v1::AssignmentMode mode() const; + void set_mode(::xtcp_treatment_journal::v1::AssignmentMode value); + + private: + ::xtcp_treatment_journal::v1::AssignmentMode _internal_mode() const; + void _internal_set_mode(::xtcp_treatment_journal::v1::AssignmentMode value); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment_journal.v1.Assignment) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<3, 6, + 3, 58, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const Assignment& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::google::protobuf::internal::ArenaStringPtr experiment_id_; + ::xtcp_treatment_journal::v1::Treatment* PROTOBUF_NULLABLE treatment_; + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE lease_expires_at_; + ::xtcp_treatment_journal::v1::Treatment* PROTOBUF_NULLABLE predecessor_; + ::uint64_t generation_; + int mode_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto; +}; +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Record final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment_journal.v1.Record) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline Record() : Record(nullptr) {} + ~Record() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(Record* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(Record)); + } +#endif + + template + explicit constexpr Record(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline Record(const Record& from) : Record(nullptr, from) {} + inline Record(Record&& from) noexcept : Record(nullptr, ::std::move(from)) {} + inline Record& operator=(const Record& from) { + CopyFrom(from); + return *this; + } + inline Record& operator=(Record&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const Record& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&Record_globals_); + } + static constexpr int kIndexInFileMessages = 4; + friend void swap(Record& a, Record& b) { a.Swap(&b); } + inline void Swap(Record* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(Record* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] Record* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const Record& from); + using Super_::MergeFrom; + void MergeFrom(const Record& from) { Record::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(Record* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment_journal.v1.Record"; } + + explicit Record(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + Record(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Record& from); + Record( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, Record&& from) noexcept + : Record(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kErrorFieldNumber = 9, + kInstalledRouteFieldNumber = 10, + kInstalledBootIdFieldNumber = 11, + kAssignmentFieldNumber = 1, + kReceivedAtFieldNumber = 4, + kAttemptedAtFieldNumber = 5, + kAppliedAtFieldNumber = 6, + kVerifiedAtFieldNumber = 7, + kRemovedAtFieldNumber = 8, + kDesiredRevisionFieldNumber = 2, + kStatusFieldNumber = 3, + kExpiredLatchedFieldNumber = 12, + }; + // string error = 9 [json_name = "error"]; + void clear_error() ; + [[nodiscard]] const ::std::string& error() const; + template + void set_error(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_error(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_error(); + void set_allocated_error(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_error() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_error(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_error(); + + public: + // bytes installed_route = 10 [json_name = "installedRoute"]; + void clear_installed_route() ; + [[nodiscard]] const ::std::string& installed_route() const; + template + void set_installed_route(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_installed_route(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_installed_route(); + void set_allocated_installed_route(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_installed_route() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_installed_route(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_installed_route(); + + public: + // string installed_boot_id = 11 [json_name = "installedBootId"]; + void clear_installed_boot_id() ; + [[nodiscard]] const ::std::string& installed_boot_id() const; + template + void set_installed_boot_id(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_installed_boot_id(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_installed_boot_id(); + void set_allocated_installed_boot_id(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_installed_boot_id() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_installed_boot_id(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_installed_boot_id(); + + public: + // .xtcp_treatment_journal.v1.Assignment assignment = 1 [json_name = "assignment"]; + [[nodiscard]] bool has_assignment() const; + void clear_assignment() ; + [[nodiscard]] const ::xtcp_treatment_journal::v1::Assignment& assignment() const; + [[nodiscard]] ::xtcp_treatment_journal::v1::Assignment* PROTOBUF_NULLABLE release_assignment(); + ::xtcp_treatment_journal::v1::Assignment* PROTOBUF_NONNULL mutable_assignment(); + void set_allocated_assignment(::xtcp_treatment_journal::v1::Assignment* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_assignment(::xtcp_treatment_journal::v1::Assignment* PROTOBUF_NULLABLE value); + ::xtcp_treatment_journal::v1::Assignment* PROTOBUF_NULLABLE unsafe_arena_release_assignment(); + + private: + const ::xtcp_treatment_journal::v1::Assignment& _internal_assignment() const; + ::xtcp_treatment_journal::v1::Assignment* PROTOBUF_NONNULL _internal_mutable_assignment(); + + public: + // .google.protobuf.Timestamp received_at = 4 [json_name = "receivedAt"]; + [[nodiscard]] bool has_received_at() const; + void clear_received_at() ; + [[nodiscard]] const ::google::protobuf::Timestamp& received_at() const; + [[nodiscard]] ::google::protobuf::Timestamp* PROTOBUF_NULLABLE release_received_at(); + ::google::protobuf::Timestamp* PROTOBUF_NONNULL mutable_received_at(); + void set_allocated_received_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_received_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE unsafe_arena_release_received_at(); + + private: + const ::google::protobuf::Timestamp& _internal_received_at() const; + ::google::protobuf::Timestamp* PROTOBUF_NONNULL _internal_mutable_received_at(); + + public: + // .google.protobuf.Timestamp attempted_at = 5 [json_name = "attemptedAt"]; + [[nodiscard]] bool has_attempted_at() const; + void clear_attempted_at() ; + [[nodiscard]] const ::google::protobuf::Timestamp& attempted_at() const; + [[nodiscard]] ::google::protobuf::Timestamp* PROTOBUF_NULLABLE release_attempted_at(); + ::google::protobuf::Timestamp* PROTOBUF_NONNULL mutable_attempted_at(); + void set_allocated_attempted_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_attempted_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE unsafe_arena_release_attempted_at(); + + private: + const ::google::protobuf::Timestamp& _internal_attempted_at() const; + ::google::protobuf::Timestamp* PROTOBUF_NONNULL _internal_mutable_attempted_at(); + + public: + // .google.protobuf.Timestamp applied_at = 6 [json_name = "appliedAt"]; + [[nodiscard]] bool has_applied_at() const; + void clear_applied_at() ; + [[nodiscard]] const ::google::protobuf::Timestamp& applied_at() const; + [[nodiscard]] ::google::protobuf::Timestamp* PROTOBUF_NULLABLE release_applied_at(); + ::google::protobuf::Timestamp* PROTOBUF_NONNULL mutable_applied_at(); + void set_allocated_applied_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_applied_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE unsafe_arena_release_applied_at(); + + private: + const ::google::protobuf::Timestamp& _internal_applied_at() const; + ::google::protobuf::Timestamp* PROTOBUF_NONNULL _internal_mutable_applied_at(); + + public: + // .google.protobuf.Timestamp verified_at = 7 [json_name = "verifiedAt"]; + [[nodiscard]] bool has_verified_at() const; + void clear_verified_at() ; + [[nodiscard]] const ::google::protobuf::Timestamp& verified_at() const; + [[nodiscard]] ::google::protobuf::Timestamp* PROTOBUF_NULLABLE release_verified_at(); + ::google::protobuf::Timestamp* PROTOBUF_NONNULL mutable_verified_at(); + void set_allocated_verified_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_verified_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE unsafe_arena_release_verified_at(); + + private: + const ::google::protobuf::Timestamp& _internal_verified_at() const; + ::google::protobuf::Timestamp* PROTOBUF_NONNULL _internal_mutable_verified_at(); + + public: + // .google.protobuf.Timestamp removed_at = 8 [json_name = "removedAt"]; + [[nodiscard]] bool has_removed_at() const; + void clear_removed_at() ; + [[nodiscard]] const ::google::protobuf::Timestamp& removed_at() const; + [[nodiscard]] ::google::protobuf::Timestamp* PROTOBUF_NULLABLE release_removed_at(); + ::google::protobuf::Timestamp* PROTOBUF_NONNULL mutable_removed_at(); + void set_allocated_removed_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_removed_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE unsafe_arena_release_removed_at(); + + private: + const ::google::protobuf::Timestamp& _internal_removed_at() const; + ::google::protobuf::Timestamp* PROTOBUF_NONNULL _internal_mutable_removed_at(); + + public: + // uint64 desired_revision = 2 [json_name = "desiredRevision"]; + void clear_desired_revision() ; + [[nodiscard]] ::uint64_t desired_revision() const; + void set_desired_revision(::uint64_t value); + + private: + ::uint64_t _internal_desired_revision() const; + void _internal_set_desired_revision(::uint64_t value); + + public: + // .xtcp_treatment_journal.v1.ApplyStatus status = 3 [json_name = "status"]; + void clear_status() ; + [[nodiscard]] ::xtcp_treatment_journal::v1::ApplyStatus status() const; + void set_status(::xtcp_treatment_journal::v1::ApplyStatus value); + + private: + ::xtcp_treatment_journal::v1::ApplyStatus _internal_status() const; + void _internal_set_status(::xtcp_treatment_journal::v1::ApplyStatus value); + + public: + // bool expired_latched = 12 [json_name = "expiredLatched"]; + void clear_expired_latched() ; + [[nodiscard]] bool expired_latched() const; + void set_expired_latched(bool value); + + private: + bool _internal_expired_latched() const; + void _internal_set_expired_latched(bool value); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment_journal.v1.Record) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<4, 12, + 6, 71, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const Record& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::google::protobuf::internal::ArenaStringPtr error_; + ::google::protobuf::internal::ArenaStringPtr installed_route_; + ::google::protobuf::internal::ArenaStringPtr installed_boot_id_; + ::xtcp_treatment_journal::v1::Assignment* PROTOBUF_NULLABLE assignment_; + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE received_at_; + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE attempted_at_; + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE applied_at_; + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE verified_at_; + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE removed_at_; + ::uint64_t desired_revision_; + int status_; + bool expired_latched_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto; +}; +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Desired final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment_journal.v1.Desired) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline Desired() : Desired(nullptr) {} + ~Desired() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(Desired* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(Desired)); + } +#endif + + template + explicit constexpr Desired(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline Desired(const Desired& from) : Desired(nullptr, from) {} + inline Desired(Desired&& from) noexcept : Desired(nullptr, ::std::move(from)) {} + inline Desired& operator=(const Desired& from) { + CopyFrom(from); + return *this; + } + inline Desired& operator=(Desired&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const Desired& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&Desired_globals_); + } + static constexpr int kIndexInFileMessages = 3; + friend void swap(Desired& a, Desired& b) { a.Swap(&b); } + inline void Swap(Desired* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(Desired* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] Desired* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const Desired& from); + using Super_::MergeFrom; + void MergeFrom(const Desired& from) { Desired::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(Desired* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment_journal.v1.Desired"; } + + explicit Desired(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + Desired(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Desired& from); + Desired( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, Desired&& from) noexcept + : Desired(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kAssignmentsFieldNumber = 4, + kIssuedAtFieldNumber = 3, + kRevisionFieldNumber = 1, + kSuspendedFieldNumber = 2, + }; + // repeated .xtcp_treatment_journal.v1.Assignment assignments = 4 [json_name = "assignments"]; + [[nodiscard]] int assignments_size() const; + private: + int _internal_assignments_size() const; + + public: + void clear_assignments() ; + [[nodiscard]] const ::xtcp_treatment_journal::v1::Assignment& assignments(int index) const; + [[nodiscard]] ::xtcp_treatment_journal::v1::Assignment* PROTOBUF_NONNULL mutable_assignments(int index); + ::xtcp_treatment_journal::v1::Assignment* PROTOBUF_NONNULL add_assignments(); + [[nodiscard]] const ::google::protobuf::RepeatedPtrField<::xtcp_treatment_journal::v1::Assignment>& + assignments() const; + [[nodiscard]] ::google::protobuf::RepeatedPtrField<::xtcp_treatment_journal::v1::Assignment>* PROTOBUF_NONNULL + mutable_assignments(); + + private: + const ::google::protobuf::RepeatedPtrField<::xtcp_treatment_journal::v1::Assignment>& _internal_assignments() const; + ::google::protobuf::RepeatedPtrField<::xtcp_treatment_journal::v1::Assignment>* PROTOBUF_NONNULL _internal_mutable_assignments(); + + public: + // .google.protobuf.Timestamp issued_at = 3 [json_name = "issuedAt"]; + [[nodiscard]] bool has_issued_at() const; + void clear_issued_at() ; + [[nodiscard]] const ::google::protobuf::Timestamp& issued_at() const; + [[nodiscard]] ::google::protobuf::Timestamp* PROTOBUF_NULLABLE release_issued_at(); + ::google::protobuf::Timestamp* PROTOBUF_NONNULL mutable_issued_at(); + void set_allocated_issued_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_issued_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE unsafe_arena_release_issued_at(); + + private: + const ::google::protobuf::Timestamp& _internal_issued_at() const; + ::google::protobuf::Timestamp* PROTOBUF_NONNULL _internal_mutable_issued_at(); + + public: + // uint64 revision = 1 [json_name = "revision"]; + void clear_revision() ; + [[nodiscard]] ::uint64_t revision() const; + void set_revision(::uint64_t value); + + private: + ::uint64_t _internal_revision() const; + void _internal_set_revision(::uint64_t value); + + public: + // bool suspended = 2 [json_name = "suspended"]; + void clear_suspended() ; + [[nodiscard]] bool suspended() const; + void set_suspended(bool value); + + private: + bool _internal_suspended() const; + void _internal_set_suspended(bool value); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment_journal.v1.Desired) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<2, 4, + 2, 0, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const Desired& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::google::protobuf::RepeatedPtrField< ::xtcp_treatment_journal::v1::Assignment > assignments_; + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE issued_at_; + ::uint64_t revision_; + bool suspended_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto; +}; +// ------------------------------------------------------------------- + +class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Journal final : public ::google::protobuf::Message +/* @@protoc_insertion_point(class_definition:xtcp_treatment_journal.v1.Journal) */ { + using Super_ = ::google::protobuf::Message; + + public: + inline Journal() : Journal(nullptr) {} + ~Journal() PROTOBUF_FINAL; + +#if defined(PROTOBUF_CUSTOM_VTABLE) + void operator delete(Journal* PROTOBUF_NONNULL msg, ::std::destroying_delete_t) { + SharedDtor(*msg); + ::google::protobuf::internal::SizedDelete(msg, sizeof(Journal)); + } +#endif + + template + explicit constexpr Journal(::google::protobuf::internal::ConstantInitialized, + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL + class_data); + + inline Journal(const Journal& from) : Journal(nullptr, from) {} + inline Journal(Journal&& from) noexcept : Journal(nullptr, ::std::move(from)) {} + inline Journal& operator=(const Journal& from) { + CopyFrom(from); + return *this; + } + inline Journal& operator=(Journal&& from) noexcept { + if (this == &from) return *this; + if (::google::protobuf::internal::CanMoveWithInternalSwap(GetArena(), from.GetArena())) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + [[nodiscard]] inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.unknown_fields<::google::protobuf::UnknownFieldSet>(::google::protobuf::UnknownFieldSet::default_instance); + } + [[nodiscard]] inline ::google::protobuf::UnknownFieldSet* PROTOBUF_NONNULL + mutable_unknown_fields() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return _internal_metadata_.mutable_unknown_fields<::google::protobuf::UnknownFieldSet>(); + } + + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL descriptor() { + return GetDescriptor(); + } + [[nodiscard]] static const ::google::protobuf::Descriptor* PROTOBUF_NONNULL + GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + [[nodiscard]] static const ::google::protobuf::Reflection* PROTOBUF_NONNULL GetReflection() { + return default_instance().GetMetadata().reflection; + } + [[nodiscard]] static const Journal& default_instance() { + return *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance(&Journal_globals_); + } + static constexpr int kIndexInFileMessages = 5; + friend void swap(Journal& a, Journal& b) { a.Swap(&b); } + inline void Swap(Journal* PROTOBUF_NONNULL other) { + if (other == this) return; + if (::google::protobuf::internal::CanUseInternalSwap(GetArena(), other->GetArena())) { + InternalSwap(other); + } else { + ::google::protobuf::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(Journal* PROTOBUF_NONNULL other) { + if (other == this) return; + ABSL_DCHECK(GetArena() == other->GetArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + [[nodiscard]] Journal* PROTOBUF_NONNULL + New(::google::protobuf::Arena* PROTOBUF_NULLABLE arena = nullptr) const { + return Super_::DefaultConstruct(arena); + } + using Super_::CopyFrom; + void CopyFrom(const Journal& from); + using Super_::MergeFrom; + void MergeFrom(const Journal& from) { Journal::MergeImpl(*this, from); } + + private: + static void MergeImpl(::google::protobuf::MessageLite& to_msg, + const ::google::protobuf::MessageLite& from_msg); + + public: + [[nodiscard]] bool IsInitialized() const { + return true; + } + ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL; + #if defined(PROTOBUF_CUSTOM_VTABLE) + private: + [[nodiscard]] static ::size_t ByteSizeLong(const ::google::protobuf::MessageLite& msg); + [[nodiscard]] static ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + const ::google::protobuf::MessageLite& msg, ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream); + + public: + [[nodiscard]] ::size_t ByteSizeLong() const { return ByteSizeLong(*this); } + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const { + return _InternalSerialize(*this, target, stream); + } + #else // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] ::size_t ByteSizeLong() const final; + [[nodiscard]] ::uint8_t* PROTOBUF_NONNULL _InternalSerialize( + ::uint8_t* PROTOBUF_NONNULL target, + ::google::protobuf::io::EpsCopyOutputStream* PROTOBUF_NONNULL stream) const final; + #endif // PROTOBUF_CUSTOM_VTABLE + [[nodiscard]] int GetCachedSize() const { + return _impl_._cached_size_.Get(); + } + + private: + void SharedCtor(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static void SharedDtor(MessageLite& self); + void InternalSwap(Journal* PROTOBUF_NONNULL other); + private: + static ::absl::string_view FullMessageName() { return "xtcp_treatment_journal.v1.Journal"; } + + explicit Journal(::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + Journal(::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Journal& from); + Journal( + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, Journal&& from) noexcept + : Journal(arena) { + *this = ::std::move(from); + } + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL GetClassData() const PROTOBUF_FINAL; + static void* PROTOBUF_NONNULL PlacementNew_( + const void* PROTOBUF_NONNULL, void* PROTOBUF_NONNULL mem, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + static constexpr auto InternalNewImpl_(); + + public: + static constexpr auto InternalGenerateClassData_( + const MessageLite& prototype, + const ::google::protobuf::internal::TcParseTableBase* PROTOBUF_NULLABLE tc_table = nullptr); + + [[nodiscard]] ::google::protobuf::Metadata GetMetadata() const; + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + enum : int { + kRecordsFieldNumber = 7, + kAgentIdFieldNumber = 1, + kLocationFieldNumber = 2, + kBootIdFieldNumber = 3, + kSuspensionReasonFieldNumber = 6, + kDesiredFieldNumber = 4, + kUpdatedAtFieldNumber = 8, + kLocallySuspendedFieldNumber = 5, + }; + // repeated .xtcp_treatment_journal.v1.Record records = 7 [json_name = "records"]; + [[nodiscard]] int records_size() const; + private: + int _internal_records_size() const; + + public: + void clear_records() ; + [[nodiscard]] const ::xtcp_treatment_journal::v1::Record& records(int index) const; + [[nodiscard]] ::xtcp_treatment_journal::v1::Record* PROTOBUF_NONNULL mutable_records(int index); + ::xtcp_treatment_journal::v1::Record* PROTOBUF_NONNULL add_records(); + [[nodiscard]] const ::google::protobuf::RepeatedPtrField<::xtcp_treatment_journal::v1::Record>& + records() const; + [[nodiscard]] ::google::protobuf::RepeatedPtrField<::xtcp_treatment_journal::v1::Record>* PROTOBUF_NONNULL + mutable_records(); + + private: + const ::google::protobuf::RepeatedPtrField<::xtcp_treatment_journal::v1::Record>& _internal_records() const; + ::google::protobuf::RepeatedPtrField<::xtcp_treatment_journal::v1::Record>* PROTOBUF_NONNULL _internal_mutable_records(); + + public: + // string agent_id = 1 [json_name = "agentId"]; + void clear_agent_id() ; + [[nodiscard]] const ::std::string& agent_id() const; + template + void set_agent_id(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_agent_id(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_agent_id(); + void set_allocated_agent_id(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_agent_id() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_agent_id(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_agent_id(); + + public: + // string location = 2 [json_name = "location"]; + void clear_location() ; + [[nodiscard]] const ::std::string& location() const; + template + void set_location(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_location(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_location(); + void set_allocated_location(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_location() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_location(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_location(); + + public: + // string boot_id = 3 [json_name = "bootId"]; + void clear_boot_id() ; + [[nodiscard]] const ::std::string& boot_id() const; + template + void set_boot_id(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_boot_id(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_boot_id(); + void set_allocated_boot_id(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_boot_id() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_boot_id(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_boot_id(); + + public: + // string suspension_reason = 6 [json_name = "suspensionReason"]; + void clear_suspension_reason() ; + [[nodiscard]] const ::std::string& suspension_reason() const; + template + void set_suspension_reason(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_suspension_reason(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_suspension_reason(); + void set_allocated_suspension_reason(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_suspension_reason() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_suspension_reason(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_suspension_reason(); + + public: + // .xtcp_treatment_journal.v1.Desired desired = 4 [json_name = "desired"]; + [[nodiscard]] bool has_desired() const; + void clear_desired() ; + [[nodiscard]] const ::xtcp_treatment_journal::v1::Desired& desired() const; + [[nodiscard]] ::xtcp_treatment_journal::v1::Desired* PROTOBUF_NULLABLE release_desired(); + ::xtcp_treatment_journal::v1::Desired* PROTOBUF_NONNULL mutable_desired(); + void set_allocated_desired(::xtcp_treatment_journal::v1::Desired* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_desired(::xtcp_treatment_journal::v1::Desired* PROTOBUF_NULLABLE value); + ::xtcp_treatment_journal::v1::Desired* PROTOBUF_NULLABLE unsafe_arena_release_desired(); + + private: + const ::xtcp_treatment_journal::v1::Desired& _internal_desired() const; + ::xtcp_treatment_journal::v1::Desired* PROTOBUF_NONNULL _internal_mutable_desired(); + + public: + // .google.protobuf.Timestamp updated_at = 8 [json_name = "updatedAt"]; + [[nodiscard]] bool has_updated_at() const; + void clear_updated_at() ; + [[nodiscard]] const ::google::protobuf::Timestamp& updated_at() const; + [[nodiscard]] ::google::protobuf::Timestamp* PROTOBUF_NULLABLE release_updated_at(); + ::google::protobuf::Timestamp* PROTOBUF_NONNULL mutable_updated_at(); + void set_allocated_updated_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + void unsafe_arena_set_allocated_updated_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value); + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE unsafe_arena_release_updated_at(); + + private: + const ::google::protobuf::Timestamp& _internal_updated_at() const; + ::google::protobuf::Timestamp* PROTOBUF_NONNULL _internal_mutable_updated_at(); + + public: + // bool locally_suspended = 5 [json_name = "locallySuspended"]; + void clear_locally_suspended() ; + [[nodiscard]] bool locally_suspended() const; + void set_locally_suspended(bool value); + + private: + bool _internal_locally_suspended() const; + void _internal_set_locally_suspended(bool value); + + public: + // @@protoc_insertion_point(class_scope:xtcp_treatment_journal.v1.Journal) + private: + class _Internal; + using ParseTableT_ = + ::google::protobuf::internal::TcParseTable<3, 8, + 3, 90, + 2>; + static constexpr ParseTableT_ InternalGenerateParseTable_( + const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); + friend class ::google::protobuf::internal::TcParser; + #ifndef PROTOBUF_MESSAGE_GLOBALS + static const ParseTableT_ _table_; + #endif + + friend class ::google::protobuf::MessageLite; + friend class ::google::protobuf::Arena; + friend ::google::protobuf::internal::PrivateAccess; + template + friend class ::google::protobuf::Arena::InternalHelper; + using InternalArenaConstructable_ = void; + using DestructorSkippable_ = void; + struct Impl_ { + inline explicit constexpr Impl_(::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::internal::ConstantInitialized) noexcept; + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena); + inline explicit Impl_( + ::google::protobuf::internal::InternalVisibility visibility, + ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, const Impl_& from, + const Journal& from_msg); + ::google::protobuf::internal::HasBits<1> _has_bits_; + ::google::protobuf::internal::CachedSize _cached_size_; + ::google::protobuf::RepeatedPtrField< ::xtcp_treatment_journal::v1::Record > records_; + ::google::protobuf::internal::ArenaStringPtr agent_id_; + ::google::protobuf::internal::ArenaStringPtr location_; + ::google::protobuf::internal::ArenaStringPtr boot_id_; + ::google::protobuf::internal::ArenaStringPtr suspension_reason_; + ::xtcp_treatment_journal::v1::Desired* PROTOBUF_NULLABLE desired_; + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE updated_at_; + bool locally_suspended_; + PROTOBUF_TSAN_DECLARE_MEMBER + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto; +}; + +// =================================================================== + + + + +// =================================================================== + + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif // __GNUC__ +// ------------------------------------------------------------------- + +// TCPSettings + +// optional string congestion_control = 1 [json_name = "congestionControl"]; +inline bool TCPSettings::has_congestion_control() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000001U); + return value; +} +inline void TCPSettings::clear_congestion_control() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.congestion_control_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +inline const ::std::string& TCPSettings::congestion_control() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.TCPSettings.congestion_control) + return _internal_congestion_control(); +} +template +PROTOBUF_ALWAYS_INLINE void TCPSettings::set_congestion_control(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + _impl_.congestion_control_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment_journal.v1.TCPSettings.congestion_control) +} +inline ::std::string* PROTOBUF_NONNULL TCPSettings::mutable_congestion_control() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + ::std::string* _s = _internal_mutable_congestion_control(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment_journal.v1.TCPSettings.congestion_control) + return _s; +} +inline const ::std::string& TCPSettings::_internal_congestion_control() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.congestion_control_.Get(); +} +inline void TCPSettings::_internal_set_congestion_control(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.congestion_control_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL TCPSettings::_internal_mutable_congestion_control() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.congestion_control_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE TCPSettings::release_congestion_control() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment_journal.v1.TCPSettings.congestion_control) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000001U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + auto* released = _impl_.congestion_control_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.congestion_control_.Set("", GetArena()); + } + return released; +} +inline void TCPSettings::set_allocated_congestion_control(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + } + _impl_.congestion_control_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.congestion_control_.IsDefault()) { + _impl_.congestion_control_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment_journal.v1.TCPSettings.congestion_control) +} + +// bool congestion_lock = 2 [json_name = "congestionLock"]; +inline void TCPSettings::clear_congestion_lock() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.congestion_lock_ = false; + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); +} +inline bool TCPSettings::congestion_lock() const { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.TCPSettings.congestion_lock) + return _internal_congestion_lock(); +} +inline void TCPSettings::set_congestion_lock(bool value) { + _internal_set_congestion_lock(value); + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + // @@protoc_insertion_point(field_set:xtcp_treatment_journal.v1.TCPSettings.congestion_lock) +} +inline bool TCPSettings::_internal_congestion_lock() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.congestion_lock_; +} +inline void TCPSettings::_internal_set_congestion_lock(bool value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.congestion_lock_ = value; +} + +// optional uint32 rto_min_ms = 3 [json_name = "rtoMinMs"]; +inline bool TCPSettings::has_rto_min_ms() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000002U); + return value; +} +inline void TCPSettings::clear_rto_min_ms() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.rto_min_ms_ = 0u; + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); +} +inline ::uint32_t TCPSettings::rto_min_ms() const { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.TCPSettings.rto_min_ms) + return _internal_rto_min_ms(); +} +inline void TCPSettings::set_rto_min_ms(::uint32_t value) { + _internal_set_rto_min_ms(value); + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + // @@protoc_insertion_point(field_set:xtcp_treatment_journal.v1.TCPSettings.rto_min_ms) +} +inline ::uint32_t TCPSettings::_internal_rto_min_ms() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.rto_min_ms_; +} +inline void TCPSettings::_internal_set_rto_min_ms(::uint32_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.rto_min_ms_ = value; +} + +// optional uint32 initial_congestion_window = 4 [json_name = "initialCongestionWindow"]; +inline bool TCPSettings::has_initial_congestion_window() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000004U); + return value; +} +inline void TCPSettings::clear_initial_congestion_window() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.initial_congestion_window_ = 0u; + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); +} +inline ::uint32_t TCPSettings::initial_congestion_window() const { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.TCPSettings.initial_congestion_window) + return _internal_initial_congestion_window(); +} +inline void TCPSettings::set_initial_congestion_window(::uint32_t value) { + _internal_set_initial_congestion_window(value); + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + // @@protoc_insertion_point(field_set:xtcp_treatment_journal.v1.TCPSettings.initial_congestion_window) +} +inline ::uint32_t TCPSettings::_internal_initial_congestion_window() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.initial_congestion_window_; +} +inline void TCPSettings::_internal_set_initial_congestion_window(::uint32_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.initial_congestion_window_ = value; +} + +// optional uint32 initial_receive_window = 5 [json_name = "initialReceiveWindow"]; +inline bool TCPSettings::has_initial_receive_window() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000008U); + return value; +} +inline void TCPSettings::clear_initial_receive_window() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.initial_receive_window_ = 0u; + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); +} +inline ::uint32_t TCPSettings::initial_receive_window() const { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.TCPSettings.initial_receive_window) + return _internal_initial_receive_window(); +} +inline void TCPSettings::set_initial_receive_window(::uint32_t value) { + _internal_set_initial_receive_window(value); + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + // @@protoc_insertion_point(field_set:xtcp_treatment_journal.v1.TCPSettings.initial_receive_window) +} +inline ::uint32_t TCPSettings::_internal_initial_receive_window() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.initial_receive_window_; +} +inline void TCPSettings::_internal_set_initial_receive_window(::uint32_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.initial_receive_window_ = value; +} + +// optional bool quick_ack = 6 [json_name = "quickAck"]; +inline bool TCPSettings::has_quick_ack() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000020U); + return value; +} +inline void TCPSettings::clear_quick_ack() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.quick_ack_ = false; + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); +} +inline bool TCPSettings::quick_ack() const { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.TCPSettings.quick_ack) + return _internal_quick_ack(); +} +inline void TCPSettings::set_quick_ack(bool value) { + _internal_set_quick_ack(value); + SetHasBit(_impl_._has_bits_[0], 0x00000020U); + // @@protoc_insertion_point(field_set:xtcp_treatment_journal.v1.TCPSettings.quick_ack) +} +inline bool TCPSettings::_internal_quick_ack() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.quick_ack_; +} +inline void TCPSettings::_internal_set_quick_ack(bool value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.quick_ack_ = value; +} + +// optional bool ecn = 7 [json_name = "ecn"]; +inline bool TCPSettings::has_ecn() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000040U); + return value; +} +inline void TCPSettings::clear_ecn() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.ecn_ = false; + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); +} +inline bool TCPSettings::ecn() const { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.TCPSettings.ecn) + return _internal_ecn(); +} +inline void TCPSettings::set_ecn(bool value) { + _internal_set_ecn(value); + SetHasBit(_impl_._has_bits_[0], 0x00000040U); + // @@protoc_insertion_point(field_set:xtcp_treatment_journal.v1.TCPSettings.ecn) +} +inline bool TCPSettings::_internal_ecn() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.ecn_; +} +inline void TCPSettings::_internal_set_ecn(bool value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.ecn_ = value; +} + +// ------------------------------------------------------------------- + +// Treatment + +// string id = 1 [json_name = "id"]; +inline void Treatment::clear_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.id_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +inline const ::std::string& Treatment::id() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Treatment.id) + return _internal_id(); +} +template +PROTOBUF_ALWAYS_INLINE void Treatment::set_id(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + _impl_.id_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment_journal.v1.Treatment.id) +} +inline ::std::string* PROTOBUF_NONNULL Treatment::mutable_id() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + ::std::string* _s = _internal_mutable_id(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment_journal.v1.Treatment.id) + return _s; +} +inline const ::std::string& Treatment::_internal_id() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.id_.Get(); +} +inline void Treatment::_internal_set_id(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.id_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL Treatment::_internal_mutable_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.id_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE Treatment::release_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment_journal.v1.Treatment.id) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000001U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + auto* released = _impl_.id_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.id_.Set("", GetArena()); + } + return released; +} +inline void Treatment::set_allocated_id(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + } + _impl_.id_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.id_.IsDefault()) { + _impl_.id_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment_journal.v1.Treatment.id) +} + +// uint64 revision = 2 [json_name = "revision"]; +inline void Treatment::clear_revision() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.revision_ = ::uint64_t{0u}; + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); +} +inline ::uint64_t Treatment::revision() const { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Treatment.revision) + return _internal_revision(); +} +inline void Treatment::set_revision(::uint64_t value) { + _internal_set_revision(value); + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + // @@protoc_insertion_point(field_set:xtcp_treatment_journal.v1.Treatment.revision) +} +inline ::uint64_t Treatment::_internal_revision() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.revision_; +} +inline void Treatment::_internal_set_revision(::uint64_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.revision_ = value; +} + +// string destination = 3 [json_name = "destination"]; +inline void Treatment::clear_destination() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.destination_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); +} +inline const ::std::string& Treatment::destination() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Treatment.destination) + return _internal_destination(); +} +template +PROTOBUF_ALWAYS_INLINE void Treatment::set_destination(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + _impl_.destination_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment_journal.v1.Treatment.destination) +} +inline ::std::string* PROTOBUF_NONNULL Treatment::mutable_destination() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + ::std::string* _s = _internal_mutable_destination(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment_journal.v1.Treatment.destination) + return _s; +} +inline const ::std::string& Treatment::_internal_destination() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.destination_.Get(); +} +inline void Treatment::_internal_set_destination(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.destination_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL Treatment::_internal_mutable_destination() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.destination_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE Treatment::release_destination() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment_journal.v1.Treatment.destination) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000002U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + auto* released = _impl_.destination_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.destination_.Set("", GetArena()); + } + return released; +} +inline void Treatment::set_allocated_destination(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + } + _impl_.destination_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.destination_.IsDefault()) { + _impl_.destination_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment_journal.v1.Treatment.destination) +} + +// .xtcp_treatment_journal.v1.TCPSettings settings = 4 [json_name = "settings"]; +inline bool Treatment::has_settings() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000004U); + PROTOBUF_ASSUME(!value || _impl_.settings_ != nullptr); + return value; +} +inline void Treatment::clear_settings() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.settings_ != nullptr) _impl_.settings_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); +} +inline const ::xtcp_treatment_journal::v1::TCPSettings& Treatment::_internal_settings() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::xtcp_treatment_journal::v1::TCPSettings* p = _impl_.settings_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::xtcp_treatment_journal::v1::TCPSettings>(&::xtcp_treatment_journal::v1::TCPSettings_globals_); +} +inline const ::xtcp_treatment_journal::v1::TCPSettings& Treatment::settings() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Treatment.settings) + return _internal_settings(); +} +inline void Treatment::unsafe_arena_set_allocated_settings( + ::xtcp_treatment_journal::v1::TCPSettings* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.settings_); + } + _impl_.settings_ = reinterpret_cast<::xtcp_treatment_journal::v1::TCPSettings*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment_journal.v1.Treatment.settings) +} +inline ::xtcp_treatment_journal::v1::TCPSettings* PROTOBUF_NULLABLE Treatment::release_settings() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + ::xtcp_treatment_journal::v1::TCPSettings* released = _impl_.settings_; + _impl_.settings_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::xtcp_treatment_journal::v1::TCPSettings* PROTOBUF_NULLABLE Treatment::unsafe_arena_release_settings() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment_journal.v1.Treatment.settings) + + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + ::xtcp_treatment_journal::v1::TCPSettings* temp = _impl_.settings_; + _impl_.settings_ = nullptr; + return temp; +} +inline ::xtcp_treatment_journal::v1::TCPSettings* PROTOBUF_NONNULL Treatment::_internal_mutable_settings() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.settings_ == nullptr) { + auto* p = Super_::DefaultConstruct<::xtcp_treatment_journal::v1::TCPSettings>(GetArena()); + _impl_.settings_ = reinterpret_cast<::xtcp_treatment_journal::v1::TCPSettings*>(p); + } + return _impl_.settings_; +} +inline ::xtcp_treatment_journal::v1::TCPSettings* PROTOBUF_NONNULL Treatment::mutable_settings() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + ::xtcp_treatment_journal::v1::TCPSettings* _msg = _internal_mutable_settings(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment_journal.v1.Treatment.settings) + return _msg; +} +inline void Treatment::set_allocated_settings(::xtcp_treatment_journal::v1::TCPSettings* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.settings_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = value->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + } + + _impl_.settings_ = reinterpret_cast<::xtcp_treatment_journal::v1::TCPSettings*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment_journal.v1.Treatment.settings) +} + +// bool ecn_fabric_verified = 5 [json_name = "ecnFabricVerified"]; +inline void Treatment::clear_ecn_fabric_verified() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.ecn_fabric_verified_ = false; + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); +} +inline bool Treatment::ecn_fabric_verified() const { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Treatment.ecn_fabric_verified) + return _internal_ecn_fabric_verified(); +} +inline void Treatment::set_ecn_fabric_verified(bool value) { + _internal_set_ecn_fabric_verified(value); + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + // @@protoc_insertion_point(field_set:xtcp_treatment_journal.v1.Treatment.ecn_fabric_verified) +} +inline bool Treatment::_internal_ecn_fabric_verified() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.ecn_fabric_verified_; +} +inline void Treatment::_internal_set_ecn_fabric_verified(bool value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.ecn_fabric_verified_ = value; +} + +// ------------------------------------------------------------------- + +// Assignment + +// string experiment_id = 1 [json_name = "experimentId"]; +inline void Assignment::clear_experiment_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.experiment_id_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +inline const ::std::string& Assignment::experiment_id() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Assignment.experiment_id) + return _internal_experiment_id(); +} +template +PROTOBUF_ALWAYS_INLINE void Assignment::set_experiment_id(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + _impl_.experiment_id_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment_journal.v1.Assignment.experiment_id) +} +inline ::std::string* PROTOBUF_NONNULL Assignment::mutable_experiment_id() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + ::std::string* _s = _internal_mutable_experiment_id(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment_journal.v1.Assignment.experiment_id) + return _s; +} +inline const ::std::string& Assignment::_internal_experiment_id() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.experiment_id_.Get(); +} +inline void Assignment::_internal_set_experiment_id(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.experiment_id_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL Assignment::_internal_mutable_experiment_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.experiment_id_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE Assignment::release_experiment_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment_journal.v1.Assignment.experiment_id) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000001U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + auto* released = _impl_.experiment_id_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.experiment_id_.Set("", GetArena()); + } + return released; +} +inline void Assignment::set_allocated_experiment_id(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + } + _impl_.experiment_id_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.experiment_id_.IsDefault()) { + _impl_.experiment_id_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment_journal.v1.Assignment.experiment_id) +} + +// .xtcp_treatment_journal.v1.Treatment treatment = 2 [json_name = "treatment"]; +inline bool Assignment::has_treatment() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000002U); + PROTOBUF_ASSUME(!value || _impl_.treatment_ != nullptr); + return value; +} +inline void Assignment::clear_treatment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.treatment_ != nullptr) _impl_.treatment_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); +} +inline const ::xtcp_treatment_journal::v1::Treatment& Assignment::_internal_treatment() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::xtcp_treatment_journal::v1::Treatment* p = _impl_.treatment_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::xtcp_treatment_journal::v1::Treatment>(&::xtcp_treatment_journal::v1::Treatment_globals_); +} +inline const ::xtcp_treatment_journal::v1::Treatment& Assignment::treatment() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Assignment.treatment) + return _internal_treatment(); +} +inline void Assignment::unsafe_arena_set_allocated_treatment( + ::xtcp_treatment_journal::v1::Treatment* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.treatment_); + } + _impl_.treatment_ = reinterpret_cast<::xtcp_treatment_journal::v1::Treatment*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment_journal.v1.Assignment.treatment) +} +inline ::xtcp_treatment_journal::v1::Treatment* PROTOBUF_NULLABLE Assignment::release_treatment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + ::xtcp_treatment_journal::v1::Treatment* released = _impl_.treatment_; + _impl_.treatment_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::xtcp_treatment_journal::v1::Treatment* PROTOBUF_NULLABLE Assignment::unsafe_arena_release_treatment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment_journal.v1.Assignment.treatment) + + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + ::xtcp_treatment_journal::v1::Treatment* temp = _impl_.treatment_; + _impl_.treatment_ = nullptr; + return temp; +} +inline ::xtcp_treatment_journal::v1::Treatment* PROTOBUF_NONNULL Assignment::_internal_mutable_treatment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.treatment_ == nullptr) { + auto* p = Super_::DefaultConstruct<::xtcp_treatment_journal::v1::Treatment>(GetArena()); + _impl_.treatment_ = reinterpret_cast<::xtcp_treatment_journal::v1::Treatment*>(p); + } + return _impl_.treatment_; +} +inline ::xtcp_treatment_journal::v1::Treatment* PROTOBUF_NONNULL Assignment::mutable_treatment() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + ::xtcp_treatment_journal::v1::Treatment* _msg = _internal_mutable_treatment(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment_journal.v1.Assignment.treatment) + return _msg; +} +inline void Assignment::set_allocated_treatment(::xtcp_treatment_journal::v1::Treatment* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.treatment_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = value->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + } + + _impl_.treatment_ = reinterpret_cast<::xtcp_treatment_journal::v1::Treatment*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment_journal.v1.Assignment.treatment) +} + +// .xtcp_treatment_journal.v1.AssignmentMode mode = 3 [json_name = "mode"]; +inline void Assignment::clear_mode() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.mode_ = 0; + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); +} +inline ::xtcp_treatment_journal::v1::AssignmentMode Assignment::mode() const { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Assignment.mode) + return _internal_mode(); +} +inline void Assignment::set_mode(::xtcp_treatment_journal::v1::AssignmentMode value) { + _internal_set_mode(value); + SetHasBit(_impl_._has_bits_[0], 0x00000020U); + // @@protoc_insertion_point(field_set:xtcp_treatment_journal.v1.Assignment.mode) +} +inline ::xtcp_treatment_journal::v1::AssignmentMode Assignment::_internal_mode() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return static_cast<::xtcp_treatment_journal::v1::AssignmentMode>(_impl_.mode_); +} +inline void Assignment::_internal_set_mode(::xtcp_treatment_journal::v1::AssignmentMode value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.mode_ = value; +} + +// .google.protobuf.Timestamp lease_expires_at = 4 [json_name = "leaseExpiresAt"]; +inline bool Assignment::has_lease_expires_at() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000004U); + PROTOBUF_ASSUME(!value || _impl_.lease_expires_at_ != nullptr); + return value; +} +inline const ::google::protobuf::Timestamp& Assignment::_internal_lease_expires_at() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::google::protobuf::Timestamp* p = _impl_.lease_expires_at_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::google::protobuf::Timestamp>(&::google::protobuf::Timestamp_globals_); +} +inline const ::google::protobuf::Timestamp& Assignment::lease_expires_at() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Assignment.lease_expires_at) + return _internal_lease_expires_at(); +} +inline void Assignment::unsafe_arena_set_allocated_lease_expires_at( + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.lease_expires_at_); + } + _impl_.lease_expires_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment_journal.v1.Assignment.lease_expires_at) +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Assignment::release_lease_expires_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + ::google::protobuf::Timestamp* released = _impl_.lease_expires_at_; + _impl_.lease_expires_at_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Assignment::unsafe_arena_release_lease_expires_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment_journal.v1.Assignment.lease_expires_at) + + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + ::google::protobuf::Timestamp* temp = _impl_.lease_expires_at_; + _impl_.lease_expires_at_ = nullptr; + return temp; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Assignment::_internal_mutable_lease_expires_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.lease_expires_at_ == nullptr) { + auto* p = Super_::DefaultConstruct<::google::protobuf::Timestamp>(GetArena()); + _impl_.lease_expires_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(p); + } + return _impl_.lease_expires_at_; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Assignment::mutable_lease_expires_at() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + ::google::protobuf::Timestamp* _msg = _internal_mutable_lease_expires_at(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment_journal.v1.Assignment.lease_expires_at) + return _msg; +} +inline void Assignment::set_allocated_lease_expires_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.lease_expires_at_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + } + + _impl_.lease_expires_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment_journal.v1.Assignment.lease_expires_at) +} + +// .xtcp_treatment_journal.v1.Treatment predecessor = 5 [json_name = "predecessor"]; +inline bool Assignment::has_predecessor() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000008U); + PROTOBUF_ASSUME(!value || _impl_.predecessor_ != nullptr); + return value; +} +inline void Assignment::clear_predecessor() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.predecessor_ != nullptr) _impl_.predecessor_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); +} +inline const ::xtcp_treatment_journal::v1::Treatment& Assignment::_internal_predecessor() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::xtcp_treatment_journal::v1::Treatment* p = _impl_.predecessor_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::xtcp_treatment_journal::v1::Treatment>(&::xtcp_treatment_journal::v1::Treatment_globals_); +} +inline const ::xtcp_treatment_journal::v1::Treatment& Assignment::predecessor() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Assignment.predecessor) + return _internal_predecessor(); +} +inline void Assignment::unsafe_arena_set_allocated_predecessor( + ::xtcp_treatment_journal::v1::Treatment* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.predecessor_); + } + _impl_.predecessor_ = reinterpret_cast<::xtcp_treatment_journal::v1::Treatment*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment_journal.v1.Assignment.predecessor) +} +inline ::xtcp_treatment_journal::v1::Treatment* PROTOBUF_NULLABLE Assignment::release_predecessor() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + ::xtcp_treatment_journal::v1::Treatment* released = _impl_.predecessor_; + _impl_.predecessor_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::xtcp_treatment_journal::v1::Treatment* PROTOBUF_NULLABLE Assignment::unsafe_arena_release_predecessor() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment_journal.v1.Assignment.predecessor) + + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + ::xtcp_treatment_journal::v1::Treatment* temp = _impl_.predecessor_; + _impl_.predecessor_ = nullptr; + return temp; +} +inline ::xtcp_treatment_journal::v1::Treatment* PROTOBUF_NONNULL Assignment::_internal_mutable_predecessor() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.predecessor_ == nullptr) { + auto* p = Super_::DefaultConstruct<::xtcp_treatment_journal::v1::Treatment>(GetArena()); + _impl_.predecessor_ = reinterpret_cast<::xtcp_treatment_journal::v1::Treatment*>(p); + } + return _impl_.predecessor_; +} +inline ::xtcp_treatment_journal::v1::Treatment* PROTOBUF_NONNULL Assignment::mutable_predecessor() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + ::xtcp_treatment_journal::v1::Treatment* _msg = _internal_mutable_predecessor(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment_journal.v1.Assignment.predecessor) + return _msg; +} +inline void Assignment::set_allocated_predecessor(::xtcp_treatment_journal::v1::Treatment* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.predecessor_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = value->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + } + + _impl_.predecessor_ = reinterpret_cast<::xtcp_treatment_journal::v1::Treatment*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment_journal.v1.Assignment.predecessor) +} + +// uint64 generation = 6 [json_name = "generation"]; +inline void Assignment::clear_generation() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.generation_ = ::uint64_t{0u}; + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); +} +inline ::uint64_t Assignment::generation() const { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Assignment.generation) + return _internal_generation(); +} +inline void Assignment::set_generation(::uint64_t value) { + _internal_set_generation(value); + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + // @@protoc_insertion_point(field_set:xtcp_treatment_journal.v1.Assignment.generation) +} +inline ::uint64_t Assignment::_internal_generation() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.generation_; +} +inline void Assignment::_internal_set_generation(::uint64_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.generation_ = value; +} + +// ------------------------------------------------------------------- + +// Desired + +// uint64 revision = 1 [json_name = "revision"]; +inline void Desired::clear_revision() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.revision_ = ::uint64_t{0u}; + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); +} +inline ::uint64_t Desired::revision() const { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Desired.revision) + return _internal_revision(); +} +inline void Desired::set_revision(::uint64_t value) { + _internal_set_revision(value); + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + // @@protoc_insertion_point(field_set:xtcp_treatment_journal.v1.Desired.revision) +} +inline ::uint64_t Desired::_internal_revision() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.revision_; +} +inline void Desired::_internal_set_revision(::uint64_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.revision_ = value; +} + +// bool suspended = 2 [json_name = "suspended"]; +inline void Desired::clear_suspended() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.suspended_ = false; + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); +} +inline bool Desired::suspended() const { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Desired.suspended) + return _internal_suspended(); +} +inline void Desired::set_suspended(bool value) { + _internal_set_suspended(value); + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + // @@protoc_insertion_point(field_set:xtcp_treatment_journal.v1.Desired.suspended) +} +inline bool Desired::_internal_suspended() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.suspended_; +} +inline void Desired::_internal_set_suspended(bool value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.suspended_ = value; +} + +// .google.protobuf.Timestamp issued_at = 3 [json_name = "issuedAt"]; +inline bool Desired::has_issued_at() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000002U); + PROTOBUF_ASSUME(!value || _impl_.issued_at_ != nullptr); + return value; +} +inline const ::google::protobuf::Timestamp& Desired::_internal_issued_at() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::google::protobuf::Timestamp* p = _impl_.issued_at_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::google::protobuf::Timestamp>(&::google::protobuf::Timestamp_globals_); +} +inline const ::google::protobuf::Timestamp& Desired::issued_at() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Desired.issued_at) + return _internal_issued_at(); +} +inline void Desired::unsafe_arena_set_allocated_issued_at( + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.issued_at_); + } + _impl_.issued_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment_journal.v1.Desired.issued_at) +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Desired::release_issued_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + ::google::protobuf::Timestamp* released = _impl_.issued_at_; + _impl_.issued_at_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Desired::unsafe_arena_release_issued_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment_journal.v1.Desired.issued_at) + + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + ::google::protobuf::Timestamp* temp = _impl_.issued_at_; + _impl_.issued_at_ = nullptr; + return temp; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Desired::_internal_mutable_issued_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.issued_at_ == nullptr) { + auto* p = Super_::DefaultConstruct<::google::protobuf::Timestamp>(GetArena()); + _impl_.issued_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(p); + } + return _impl_.issued_at_; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Desired::mutable_issued_at() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + ::google::protobuf::Timestamp* _msg = _internal_mutable_issued_at(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment_journal.v1.Desired.issued_at) + return _msg; +} +inline void Desired::set_allocated_issued_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.issued_at_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + } + + _impl_.issued_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment_journal.v1.Desired.issued_at) +} + +// repeated .xtcp_treatment_journal.v1.Assignment assignments = 4 [json_name = "assignments"]; +inline int Desired::_internal_assignments_size() const { + return _internal_assignments().size(); +} +inline int Desired::assignments_size() const { + return _internal_assignments_size(); +} +inline void Desired::clear_assignments() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.assignments_.Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +inline const ::xtcp_treatment_journal::v1::Assignment& Desired::assignments(int index) const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Desired.assignments) + return _internal_assignments().Get(index); +} +inline ::xtcp_treatment_journal::v1::Assignment* PROTOBUF_NONNULL Desired::mutable_assignments(int index) + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_mutable:xtcp_treatment_journal.v1.Desired.assignments) + return _internal_mutable_assignments()->Mutable(index); +} +inline ::xtcp_treatment_journal::v1::Assignment* PROTOBUF_NONNULL Desired::add_assignments() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + ::google::protobuf::internal::TSanWrite(&_impl_); + ::xtcp_treatment_journal::v1::Assignment* _add = + _internal_mutable_assignments()->InternalAddWithArena( + ::google::protobuf::MessageLite::internal_visibility(), GetArena()); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + // @@protoc_insertion_point(field_add:xtcp_treatment_journal.v1.Desired.assignments) + return _add; +} +inline const ::google::protobuf::RepeatedPtrField<::xtcp_treatment_journal::v1::Assignment>& Desired::assignments() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_list:xtcp_treatment_journal.v1.Desired.assignments) + return _internal_assignments(); +} +inline ::google::protobuf::RepeatedPtrField<::xtcp_treatment_journal::v1::Assignment>* PROTOBUF_NONNULL +Desired::mutable_assignments() ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + // @@protoc_insertion_point(field_mutable_list:xtcp_treatment_journal.v1.Desired.assignments) + ::google::protobuf::internal::TSanWrite(&_impl_); + return _internal_mutable_assignments(); +} +inline const ::google::protobuf::RepeatedPtrField<::xtcp_treatment_journal::v1::Assignment>& +Desired::_internal_assignments() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.assignments_; +} +inline ::google::protobuf::RepeatedPtrField<::xtcp_treatment_journal::v1::Assignment>* PROTOBUF_NONNULL +Desired::_internal_mutable_assignments() { + ::google::protobuf::internal::TSanRead(&_impl_); + return &_impl_.assignments_; +} + +// ------------------------------------------------------------------- + +// Record + +// .xtcp_treatment_journal.v1.Assignment assignment = 1 [json_name = "assignment"]; +inline bool Record::has_assignment() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000008U); + PROTOBUF_ASSUME(!value || _impl_.assignment_ != nullptr); + return value; +} +inline void Record::clear_assignment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.assignment_ != nullptr) _impl_.assignment_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); +} +inline const ::xtcp_treatment_journal::v1::Assignment& Record::_internal_assignment() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::xtcp_treatment_journal::v1::Assignment* p = _impl_.assignment_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::xtcp_treatment_journal::v1::Assignment>(&::xtcp_treatment_journal::v1::Assignment_globals_); +} +inline const ::xtcp_treatment_journal::v1::Assignment& Record::assignment() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Record.assignment) + return _internal_assignment(); +} +inline void Record::unsafe_arena_set_allocated_assignment( + ::xtcp_treatment_journal::v1::Assignment* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.assignment_); + } + _impl_.assignment_ = reinterpret_cast<::xtcp_treatment_journal::v1::Assignment*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment_journal.v1.Record.assignment) +} +inline ::xtcp_treatment_journal::v1::Assignment* PROTOBUF_NULLABLE Record::release_assignment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + ::xtcp_treatment_journal::v1::Assignment* released = _impl_.assignment_; + _impl_.assignment_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::xtcp_treatment_journal::v1::Assignment* PROTOBUF_NULLABLE Record::unsafe_arena_release_assignment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment_journal.v1.Record.assignment) + + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + ::xtcp_treatment_journal::v1::Assignment* temp = _impl_.assignment_; + _impl_.assignment_ = nullptr; + return temp; +} +inline ::xtcp_treatment_journal::v1::Assignment* PROTOBUF_NONNULL Record::_internal_mutable_assignment() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.assignment_ == nullptr) { + auto* p = Super_::DefaultConstruct<::xtcp_treatment_journal::v1::Assignment>(GetArena()); + _impl_.assignment_ = reinterpret_cast<::xtcp_treatment_journal::v1::Assignment*>(p); + } + return _impl_.assignment_; +} +inline ::xtcp_treatment_journal::v1::Assignment* PROTOBUF_NONNULL Record::mutable_assignment() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + ::xtcp_treatment_journal::v1::Assignment* _msg = _internal_mutable_assignment(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment_journal.v1.Record.assignment) + return _msg; +} +inline void Record::set_allocated_assignment(::xtcp_treatment_journal::v1::Assignment* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.assignment_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = value->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + } + + _impl_.assignment_ = reinterpret_cast<::xtcp_treatment_journal::v1::Assignment*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment_journal.v1.Record.assignment) +} + +// uint64 desired_revision = 2 [json_name = "desiredRevision"]; +inline void Record::clear_desired_revision() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.desired_revision_ = ::uint64_t{0u}; + ClearHasBit(_impl_._has_bits_[0], 0x00000200U); +} +inline ::uint64_t Record::desired_revision() const { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Record.desired_revision) + return _internal_desired_revision(); +} +inline void Record::set_desired_revision(::uint64_t value) { + _internal_set_desired_revision(value); + SetHasBit(_impl_._has_bits_[0], 0x00000200U); + // @@protoc_insertion_point(field_set:xtcp_treatment_journal.v1.Record.desired_revision) +} +inline ::uint64_t Record::_internal_desired_revision() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.desired_revision_; +} +inline void Record::_internal_set_desired_revision(::uint64_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.desired_revision_ = value; +} + +// .xtcp_treatment_journal.v1.ApplyStatus status = 3 [json_name = "status"]; +inline void Record::clear_status() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.status_ = 0; + ClearHasBit(_impl_._has_bits_[0], 0x00000400U); +} +inline ::xtcp_treatment_journal::v1::ApplyStatus Record::status() const { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Record.status) + return _internal_status(); +} +inline void Record::set_status(::xtcp_treatment_journal::v1::ApplyStatus value) { + _internal_set_status(value); + SetHasBit(_impl_._has_bits_[0], 0x00000400U); + // @@protoc_insertion_point(field_set:xtcp_treatment_journal.v1.Record.status) +} +inline ::xtcp_treatment_journal::v1::ApplyStatus Record::_internal_status() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return static_cast<::xtcp_treatment_journal::v1::ApplyStatus>(_impl_.status_); +} +inline void Record::_internal_set_status(::xtcp_treatment_journal::v1::ApplyStatus value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.status_ = value; +} + +// .google.protobuf.Timestamp received_at = 4 [json_name = "receivedAt"]; +inline bool Record::has_received_at() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000010U); + PROTOBUF_ASSUME(!value || _impl_.received_at_ != nullptr); + return value; +} +inline const ::google::protobuf::Timestamp& Record::_internal_received_at() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::google::protobuf::Timestamp* p = _impl_.received_at_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::google::protobuf::Timestamp>(&::google::protobuf::Timestamp_globals_); +} +inline const ::google::protobuf::Timestamp& Record::received_at() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Record.received_at) + return _internal_received_at(); +} +inline void Record::unsafe_arena_set_allocated_received_at( + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.received_at_); + } + _impl_.received_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment_journal.v1.Record.received_at) +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Record::release_received_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + ::google::protobuf::Timestamp* released = _impl_.received_at_; + _impl_.received_at_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Record::unsafe_arena_release_received_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment_journal.v1.Record.received_at) + + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + ::google::protobuf::Timestamp* temp = _impl_.received_at_; + _impl_.received_at_ = nullptr; + return temp; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Record::_internal_mutable_received_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.received_at_ == nullptr) { + auto* p = Super_::DefaultConstruct<::google::protobuf::Timestamp>(GetArena()); + _impl_.received_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(p); + } + return _impl_.received_at_; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Record::mutable_received_at() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + ::google::protobuf::Timestamp* _msg = _internal_mutable_received_at(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment_journal.v1.Record.received_at) + return _msg; +} +inline void Record::set_allocated_received_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.received_at_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + } + + _impl_.received_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment_journal.v1.Record.received_at) +} + +// .google.protobuf.Timestamp attempted_at = 5 [json_name = "attemptedAt"]; +inline bool Record::has_attempted_at() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000020U); + PROTOBUF_ASSUME(!value || _impl_.attempted_at_ != nullptr); + return value; +} +inline const ::google::protobuf::Timestamp& Record::_internal_attempted_at() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::google::protobuf::Timestamp* p = _impl_.attempted_at_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::google::protobuf::Timestamp>(&::google::protobuf::Timestamp_globals_); +} +inline const ::google::protobuf::Timestamp& Record::attempted_at() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Record.attempted_at) + return _internal_attempted_at(); +} +inline void Record::unsafe_arena_set_allocated_attempted_at( + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.attempted_at_); + } + _impl_.attempted_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000020U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment_journal.v1.Record.attempted_at) +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Record::release_attempted_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + ::google::protobuf::Timestamp* released = _impl_.attempted_at_; + _impl_.attempted_at_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Record::unsafe_arena_release_attempted_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment_journal.v1.Record.attempted_at) + + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + ::google::protobuf::Timestamp* temp = _impl_.attempted_at_; + _impl_.attempted_at_ = nullptr; + return temp; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Record::_internal_mutable_attempted_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.attempted_at_ == nullptr) { + auto* p = Super_::DefaultConstruct<::google::protobuf::Timestamp>(GetArena()); + _impl_.attempted_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(p); + } + return _impl_.attempted_at_; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Record::mutable_attempted_at() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000020U); + ::google::protobuf::Timestamp* _msg = _internal_mutable_attempted_at(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment_journal.v1.Record.attempted_at) + return _msg; +} +inline void Record::set_allocated_attempted_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.attempted_at_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000020U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + } + + _impl_.attempted_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment_journal.v1.Record.attempted_at) +} + +// .google.protobuf.Timestamp applied_at = 6 [json_name = "appliedAt"]; +inline bool Record::has_applied_at() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000040U); + PROTOBUF_ASSUME(!value || _impl_.applied_at_ != nullptr); + return value; +} +inline const ::google::protobuf::Timestamp& Record::_internal_applied_at() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::google::protobuf::Timestamp* p = _impl_.applied_at_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::google::protobuf::Timestamp>(&::google::protobuf::Timestamp_globals_); +} +inline const ::google::protobuf::Timestamp& Record::applied_at() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Record.applied_at) + return _internal_applied_at(); +} +inline void Record::unsafe_arena_set_allocated_applied_at( + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.applied_at_); + } + _impl_.applied_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000040U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment_journal.v1.Record.applied_at) +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Record::release_applied_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + ::google::protobuf::Timestamp* released = _impl_.applied_at_; + _impl_.applied_at_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Record::unsafe_arena_release_applied_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment_journal.v1.Record.applied_at) + + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + ::google::protobuf::Timestamp* temp = _impl_.applied_at_; + _impl_.applied_at_ = nullptr; + return temp; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Record::_internal_mutable_applied_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.applied_at_ == nullptr) { + auto* p = Super_::DefaultConstruct<::google::protobuf::Timestamp>(GetArena()); + _impl_.applied_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(p); + } + return _impl_.applied_at_; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Record::mutable_applied_at() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000040U); + ::google::protobuf::Timestamp* _msg = _internal_mutable_applied_at(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment_journal.v1.Record.applied_at) + return _msg; +} +inline void Record::set_allocated_applied_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.applied_at_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000040U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + } + + _impl_.applied_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment_journal.v1.Record.applied_at) +} + +// .google.protobuf.Timestamp verified_at = 7 [json_name = "verifiedAt"]; +inline bool Record::has_verified_at() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000080U); + PROTOBUF_ASSUME(!value || _impl_.verified_at_ != nullptr); + return value; +} +inline const ::google::protobuf::Timestamp& Record::_internal_verified_at() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::google::protobuf::Timestamp* p = _impl_.verified_at_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::google::protobuf::Timestamp>(&::google::protobuf::Timestamp_globals_); +} +inline const ::google::protobuf::Timestamp& Record::verified_at() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Record.verified_at) + return _internal_verified_at(); +} +inline void Record::unsafe_arena_set_allocated_verified_at( + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.verified_at_); + } + _impl_.verified_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000080U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment_journal.v1.Record.verified_at) +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Record::release_verified_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); + ::google::protobuf::Timestamp* released = _impl_.verified_at_; + _impl_.verified_at_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Record::unsafe_arena_release_verified_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment_journal.v1.Record.verified_at) + + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); + ::google::protobuf::Timestamp* temp = _impl_.verified_at_; + _impl_.verified_at_ = nullptr; + return temp; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Record::_internal_mutable_verified_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.verified_at_ == nullptr) { + auto* p = Super_::DefaultConstruct<::google::protobuf::Timestamp>(GetArena()); + _impl_.verified_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(p); + } + return _impl_.verified_at_; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Record::mutable_verified_at() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000080U); + ::google::protobuf::Timestamp* _msg = _internal_mutable_verified_at(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment_journal.v1.Record.verified_at) + return _msg; +} +inline void Record::set_allocated_verified_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.verified_at_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000080U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); + } + + _impl_.verified_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment_journal.v1.Record.verified_at) +} + +// .google.protobuf.Timestamp removed_at = 8 [json_name = "removedAt"]; +inline bool Record::has_removed_at() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000100U); + PROTOBUF_ASSUME(!value || _impl_.removed_at_ != nullptr); + return value; +} +inline const ::google::protobuf::Timestamp& Record::_internal_removed_at() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::google::protobuf::Timestamp* p = _impl_.removed_at_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::google::protobuf::Timestamp>(&::google::protobuf::Timestamp_globals_); +} +inline const ::google::protobuf::Timestamp& Record::removed_at() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Record.removed_at) + return _internal_removed_at(); +} +inline void Record::unsafe_arena_set_allocated_removed_at( + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.removed_at_); + } + _impl_.removed_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000100U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000100U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment_journal.v1.Record.removed_at) +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Record::release_removed_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000100U); + ::google::protobuf::Timestamp* released = _impl_.removed_at_; + _impl_.removed_at_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Record::unsafe_arena_release_removed_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment_journal.v1.Record.removed_at) + + ClearHasBit(_impl_._has_bits_[0], 0x00000100U); + ::google::protobuf::Timestamp* temp = _impl_.removed_at_; + _impl_.removed_at_ = nullptr; + return temp; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Record::_internal_mutable_removed_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.removed_at_ == nullptr) { + auto* p = Super_::DefaultConstruct<::google::protobuf::Timestamp>(GetArena()); + _impl_.removed_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(p); + } + return _impl_.removed_at_; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Record::mutable_removed_at() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000100U); + ::google::protobuf::Timestamp* _msg = _internal_mutable_removed_at(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment_journal.v1.Record.removed_at) + return _msg; +} +inline void Record::set_allocated_removed_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.removed_at_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000100U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000100U); + } + + _impl_.removed_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment_journal.v1.Record.removed_at) +} + +// string error = 9 [json_name = "error"]; +inline void Record::clear_error() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.error_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +inline const ::std::string& Record::error() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Record.error) + return _internal_error(); +} +template +PROTOBUF_ALWAYS_INLINE void Record::set_error(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + _impl_.error_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment_journal.v1.Record.error) +} +inline ::std::string* PROTOBUF_NONNULL Record::mutable_error() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + ::std::string* _s = _internal_mutable_error(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment_journal.v1.Record.error) + return _s; +} +inline const ::std::string& Record::_internal_error() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.error_.Get(); +} +inline void Record::_internal_set_error(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.error_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL Record::_internal_mutable_error() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.error_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE Record::release_error() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment_journal.v1.Record.error) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000001U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + auto* released = _impl_.error_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.error_.Set("", GetArena()); + } + return released; +} +inline void Record::set_allocated_error(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); + } + _impl_.error_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.error_.IsDefault()) { + _impl_.error_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment_journal.v1.Record.error) +} + +// bytes installed_route = 10 [json_name = "installedRoute"]; +inline void Record::clear_installed_route() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.installed_route_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); +} +inline const ::std::string& Record::installed_route() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Record.installed_route) + return _internal_installed_route(); +} +template +PROTOBUF_ALWAYS_INLINE void Record::set_installed_route(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + _impl_.installed_route_.SetBytes(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment_journal.v1.Record.installed_route) +} +inline ::std::string* PROTOBUF_NONNULL Record::mutable_installed_route() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + ::std::string* _s = _internal_mutable_installed_route(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment_journal.v1.Record.installed_route) + return _s; +} +inline const ::std::string& Record::_internal_installed_route() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.installed_route_.Get(); +} +inline void Record::_internal_set_installed_route(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.installed_route_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL Record::_internal_mutable_installed_route() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.installed_route_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE Record::release_installed_route() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment_journal.v1.Record.installed_route) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000002U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + auto* released = _impl_.installed_route_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.installed_route_.Set("", GetArena()); + } + return released; +} +inline void Record::set_allocated_installed_route(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + } + _impl_.installed_route_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.installed_route_.IsDefault()) { + _impl_.installed_route_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment_journal.v1.Record.installed_route) +} + +// string installed_boot_id = 11 [json_name = "installedBootId"]; +inline void Record::clear_installed_boot_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.installed_boot_id_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); +} +inline const ::std::string& Record::installed_boot_id() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Record.installed_boot_id) + return _internal_installed_boot_id(); +} +template +PROTOBUF_ALWAYS_INLINE void Record::set_installed_boot_id(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + _impl_.installed_boot_id_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment_journal.v1.Record.installed_boot_id) +} +inline ::std::string* PROTOBUF_NONNULL Record::mutable_installed_boot_id() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + ::std::string* _s = _internal_mutable_installed_boot_id(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment_journal.v1.Record.installed_boot_id) + return _s; +} +inline const ::std::string& Record::_internal_installed_boot_id() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.installed_boot_id_.Get(); +} +inline void Record::_internal_set_installed_boot_id(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.installed_boot_id_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL Record::_internal_mutable_installed_boot_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.installed_boot_id_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE Record::release_installed_boot_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment_journal.v1.Record.installed_boot_id) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000004U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + auto* released = _impl_.installed_boot_id_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.installed_boot_id_.Set("", GetArena()); + } + return released; +} +inline void Record::set_allocated_installed_boot_id(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + } + _impl_.installed_boot_id_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.installed_boot_id_.IsDefault()) { + _impl_.installed_boot_id_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment_journal.v1.Record.installed_boot_id) +} + +// bool expired_latched = 12 [json_name = "expiredLatched"]; +inline void Record::clear_expired_latched() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.expired_latched_ = false; + ClearHasBit(_impl_._has_bits_[0], 0x00000800U); +} +inline bool Record::expired_latched() const { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Record.expired_latched) + return _internal_expired_latched(); +} +inline void Record::set_expired_latched(bool value) { + _internal_set_expired_latched(value); + SetHasBit(_impl_._has_bits_[0], 0x00000800U); + // @@protoc_insertion_point(field_set:xtcp_treatment_journal.v1.Record.expired_latched) +} +inline bool Record::_internal_expired_latched() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.expired_latched_; +} +inline void Record::_internal_set_expired_latched(bool value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.expired_latched_ = value; +} + +// ------------------------------------------------------------------- + +// Journal + +// string agent_id = 1 [json_name = "agentId"]; +inline void Journal::clear_agent_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.agent_id_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); +} +inline const ::std::string& Journal::agent_id() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Journal.agent_id) + return _internal_agent_id(); +} +template +PROTOBUF_ALWAYS_INLINE void Journal::set_agent_id(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + _impl_.agent_id_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment_journal.v1.Journal.agent_id) +} +inline ::std::string* PROTOBUF_NONNULL Journal::mutable_agent_id() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + ::std::string* _s = _internal_mutable_agent_id(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment_journal.v1.Journal.agent_id) + return _s; +} +inline const ::std::string& Journal::_internal_agent_id() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.agent_id_.Get(); +} +inline void Journal::_internal_set_agent_id(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.agent_id_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL Journal::_internal_mutable_agent_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.agent_id_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE Journal::release_agent_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment_journal.v1.Journal.agent_id) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000002U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + auto* released = _impl_.agent_id_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.agent_id_.Set("", GetArena()); + } + return released; +} +inline void Journal::set_allocated_agent_id(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000002U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000002U); + } + _impl_.agent_id_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.agent_id_.IsDefault()) { + _impl_.agent_id_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment_journal.v1.Journal.agent_id) +} + +// string location = 2 [json_name = "location"]; +inline void Journal::clear_location() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.location_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); +} +inline const ::std::string& Journal::location() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Journal.location) + return _internal_location(); +} +template +PROTOBUF_ALWAYS_INLINE void Journal::set_location(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + _impl_.location_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment_journal.v1.Journal.location) +} +inline ::std::string* PROTOBUF_NONNULL Journal::mutable_location() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + ::std::string* _s = _internal_mutable_location(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment_journal.v1.Journal.location) + return _s; +} +inline const ::std::string& Journal::_internal_location() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.location_.Get(); +} +inline void Journal::_internal_set_location(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.location_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL Journal::_internal_mutable_location() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.location_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE Journal::release_location() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment_journal.v1.Journal.location) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000004U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + auto* released = _impl_.location_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.location_.Set("", GetArena()); + } + return released; +} +inline void Journal::set_allocated_location(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000004U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000004U); + } + _impl_.location_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.location_.IsDefault()) { + _impl_.location_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment_journal.v1.Journal.location) +} + +// string boot_id = 3 [json_name = "bootId"]; +inline void Journal::clear_boot_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.boot_id_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); +} +inline const ::std::string& Journal::boot_id() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Journal.boot_id) + return _internal_boot_id(); +} +template +PROTOBUF_ALWAYS_INLINE void Journal::set_boot_id(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + _impl_.boot_id_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment_journal.v1.Journal.boot_id) +} +inline ::std::string* PROTOBUF_NONNULL Journal::mutable_boot_id() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + ::std::string* _s = _internal_mutable_boot_id(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment_journal.v1.Journal.boot_id) + return _s; +} +inline const ::std::string& Journal::_internal_boot_id() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.boot_id_.Get(); +} +inline void Journal::_internal_set_boot_id(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.boot_id_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL Journal::_internal_mutable_boot_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.boot_id_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE Journal::release_boot_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment_journal.v1.Journal.boot_id) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000008U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + auto* released = _impl_.boot_id_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.boot_id_.Set("", GetArena()); + } + return released; +} +inline void Journal::set_allocated_boot_id(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + } + _impl_.boot_id_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.boot_id_.IsDefault()) { + _impl_.boot_id_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment_journal.v1.Journal.boot_id) +} + +// .xtcp_treatment_journal.v1.Desired desired = 4 [json_name = "desired"]; +inline bool Journal::has_desired() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000020U); + PROTOBUF_ASSUME(!value || _impl_.desired_ != nullptr); + return value; +} +inline void Journal::clear_desired() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.desired_ != nullptr) _impl_.desired_->Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); +} +inline const ::xtcp_treatment_journal::v1::Desired& Journal::_internal_desired() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::xtcp_treatment_journal::v1::Desired* p = _impl_.desired_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::xtcp_treatment_journal::v1::Desired>(&::xtcp_treatment_journal::v1::Desired_globals_); +} +inline const ::xtcp_treatment_journal::v1::Desired& Journal::desired() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Journal.desired) + return _internal_desired(); +} +inline void Journal::unsafe_arena_set_allocated_desired( + ::xtcp_treatment_journal::v1::Desired* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.desired_); + } + _impl_.desired_ = reinterpret_cast<::xtcp_treatment_journal::v1::Desired*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000020U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment_journal.v1.Journal.desired) +} +inline ::xtcp_treatment_journal::v1::Desired* PROTOBUF_NULLABLE Journal::release_desired() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + ::xtcp_treatment_journal::v1::Desired* released = _impl_.desired_; + _impl_.desired_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::xtcp_treatment_journal::v1::Desired* PROTOBUF_NULLABLE Journal::unsafe_arena_release_desired() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment_journal.v1.Journal.desired) + + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + ::xtcp_treatment_journal::v1::Desired* temp = _impl_.desired_; + _impl_.desired_ = nullptr; + return temp; +} +inline ::xtcp_treatment_journal::v1::Desired* PROTOBUF_NONNULL Journal::_internal_mutable_desired() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.desired_ == nullptr) { + auto* p = Super_::DefaultConstruct<::xtcp_treatment_journal::v1::Desired>(GetArena()); + _impl_.desired_ = reinterpret_cast<::xtcp_treatment_journal::v1::Desired*>(p); + } + return _impl_.desired_; +} +inline ::xtcp_treatment_journal::v1::Desired* PROTOBUF_NONNULL Journal::mutable_desired() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000020U); + ::xtcp_treatment_journal::v1::Desired* _msg = _internal_mutable_desired(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment_journal.v1.Journal.desired) + return _msg; +} +inline void Journal::set_allocated_desired(::xtcp_treatment_journal::v1::Desired* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.desired_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = value->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000020U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + } + + _impl_.desired_ = reinterpret_cast<::xtcp_treatment_journal::v1::Desired*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment_journal.v1.Journal.desired) +} + +// bool locally_suspended = 5 [json_name = "locallySuspended"]; +inline void Journal::clear_locally_suspended() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.locally_suspended_ = false; + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); +} +inline bool Journal::locally_suspended() const { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Journal.locally_suspended) + return _internal_locally_suspended(); +} +inline void Journal::set_locally_suspended(bool value) { + _internal_set_locally_suspended(value); + SetHasBit(_impl_._has_bits_[0], 0x00000080U); + // @@protoc_insertion_point(field_set:xtcp_treatment_journal.v1.Journal.locally_suspended) +} +inline bool Journal::_internal_locally_suspended() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.locally_suspended_; +} +inline void Journal::_internal_set_locally_suspended(bool value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.locally_suspended_ = value; +} + +// string suspension_reason = 6 [json_name = "suspensionReason"]; +inline void Journal::clear_suspension_reason() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.suspension_reason_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); +} +inline const ::std::string& Journal::suspension_reason() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Journal.suspension_reason) + return _internal_suspension_reason(); +} +template +PROTOBUF_ALWAYS_INLINE void Journal::set_suspension_reason(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + _impl_.suspension_reason_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment_journal.v1.Journal.suspension_reason) +} +inline ::std::string* PROTOBUF_NONNULL Journal::mutable_suspension_reason() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + ::std::string* _s = _internal_mutable_suspension_reason(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment_journal.v1.Journal.suspension_reason) + return _s; +} +inline const ::std::string& Journal::_internal_suspension_reason() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.suspension_reason_.Get(); +} +inline void Journal::_internal_set_suspension_reason(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.suspension_reason_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL Journal::_internal_mutable_suspension_reason() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.suspension_reason_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE Journal::release_suspension_reason() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment_journal.v1.Journal.suspension_reason) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000010U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + auto* released = _impl_.suspension_reason_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.suspension_reason_.Set("", GetArena()); + } + return released; +} +inline void Journal::set_allocated_suspension_reason(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000010U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + } + _impl_.suspension_reason_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.suspension_reason_.IsDefault()) { + _impl_.suspension_reason_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment_journal.v1.Journal.suspension_reason) +} + +// repeated .xtcp_treatment_journal.v1.Record records = 7 [json_name = "records"]; +inline int Journal::_internal_records_size() const { + return _internal_records().size(); +} +inline int Journal::records_size() const { + return _internal_records_size(); +} +inline void Journal::clear_records() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.records_.Clear(); + ClearHasBit(_impl_._has_bits_[0], 0x00000001U); +} +inline const ::xtcp_treatment_journal::v1::Record& Journal::records(int index) const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Journal.records) + return _internal_records().Get(index); +} +inline ::xtcp_treatment_journal::v1::Record* PROTOBUF_NONNULL Journal::mutable_records(int index) + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_mutable:xtcp_treatment_journal.v1.Journal.records) + return _internal_mutable_records()->Mutable(index); +} +inline ::xtcp_treatment_journal::v1::Record* PROTOBUF_NONNULL Journal::add_records() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + ::google::protobuf::internal::TSanWrite(&_impl_); + ::xtcp_treatment_journal::v1::Record* _add = + _internal_mutable_records()->InternalAddWithArena( + ::google::protobuf::MessageLite::internal_visibility(), GetArena()); + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + // @@protoc_insertion_point(field_add:xtcp_treatment_journal.v1.Journal.records) + return _add; +} +inline const ::google::protobuf::RepeatedPtrField<::xtcp_treatment_journal::v1::Record>& Journal::records() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_list:xtcp_treatment_journal.v1.Journal.records) + return _internal_records(); +} +inline ::google::protobuf::RepeatedPtrField<::xtcp_treatment_journal::v1::Record>* PROTOBUF_NONNULL +Journal::mutable_records() ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000001U); + // @@protoc_insertion_point(field_mutable_list:xtcp_treatment_journal.v1.Journal.records) + ::google::protobuf::internal::TSanWrite(&_impl_); + return _internal_mutable_records(); +} +inline const ::google::protobuf::RepeatedPtrField<::xtcp_treatment_journal::v1::Record>& +Journal::_internal_records() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.records_; +} +inline ::google::protobuf::RepeatedPtrField<::xtcp_treatment_journal::v1::Record>* PROTOBUF_NONNULL +Journal::_internal_mutable_records() { + ::google::protobuf::internal::TSanRead(&_impl_); + return &_impl_.records_; +} + +// .google.protobuf.Timestamp updated_at = 8 [json_name = "updatedAt"]; +inline bool Journal::has_updated_at() const { + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000040U); + PROTOBUF_ASSUME(!value || _impl_.updated_at_ != nullptr); + return value; +} +inline const ::google::protobuf::Timestamp& Journal::_internal_updated_at() const { + ::google::protobuf::internal::TSanRead(&_impl_); + const ::google::protobuf::Timestamp* p = _impl_.updated_at_; + return p != nullptr ? *p : *::google::protobuf::internal::MessageGlobalsBase::ToDefaultInstance<::google::protobuf::Timestamp>(&::google::protobuf::Timestamp_globals_); +} +inline const ::google::protobuf::Timestamp& Journal::updated_at() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Journal.updated_at) + return _internal_updated_at(); +} +inline void Journal::unsafe_arena_set_allocated_updated_at( + ::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (GetArena() == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.updated_at_); + } + _impl_.updated_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000040U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment_journal.v1.Journal.updated_at) +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Journal::release_updated_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + ::google::protobuf::Timestamp* released = _impl_.updated_at_; + _impl_.updated_at_ = nullptr; + if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { + auto* old = reinterpret_cast<::google::protobuf::MessageLite*>(released); + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + if (GetArena() == nullptr) { + delete old; + } + } else { + if (GetArena() != nullptr) { + released = ::google::protobuf::internal::DuplicateIfNonNull(released); + } + } + return released; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Journal::unsafe_arena_release_updated_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment_journal.v1.Journal.updated_at) + + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + ::google::protobuf::Timestamp* temp = _impl_.updated_at_; + _impl_.updated_at_ = nullptr; + return temp; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Journal::_internal_mutable_updated_at() { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (_impl_.updated_at_ == nullptr) { + auto* p = Super_::DefaultConstruct<::google::protobuf::Timestamp>(GetArena()); + _impl_.updated_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(p); + } + return _impl_.updated_at_; +} +inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Journal::mutable_updated_at() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000040U); + ::google::protobuf::Timestamp* _msg = _internal_mutable_updated_at(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment_journal.v1.Journal.updated_at) + return _msg; +} +inline void Journal::set_allocated_updated_at(::google::protobuf::Timestamp* PROTOBUF_NULLABLE value) { + ::google::protobuf::Arena* message_arena = GetArena(); + ::google::protobuf::internal::TSanWrite(&_impl_); + if (message_arena == nullptr) { + delete reinterpret_cast<::google::protobuf::MessageLite*>(_impl_.updated_at_); + } + + if (value != nullptr) { + ::google::protobuf::Arena* submessage_arena = reinterpret_cast<::google::protobuf::Message*>(value)->GetArena(); + if (message_arena != submessage_arena) { + value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); + } + SetHasBit(_impl_._has_bits_[0], 0x00000040U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + } + + _impl_.updated_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment_journal.v1.Journal.updated_at) +} + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif // __GNUC__ + +// @@protoc_insertion_point(namespace_scope) +} // namespace v1 +} // namespace xtcp_treatment_journal + + +namespace google { +namespace protobuf { + +template <> +struct is_proto_enum<::xtcp_treatment_journal::v1::AssignmentMode> : std::true_type {}; +template <> +inline const EnumDescriptor* PROTOBUF_NONNULL GetEnumDescriptor<::xtcp_treatment_journal::v1::AssignmentMode>() { + return ::xtcp_treatment_journal::v1::AssignmentMode_descriptor(); +} +template <> +struct is_proto_enum<::xtcp_treatment_journal::v1::ApplyStatus> : std::true_type {}; +template <> +inline const EnumDescriptor* PROTOBUF_NONNULL GetEnumDescriptor<::xtcp_treatment_journal::v1::ApplyStatus>() { + return ::xtcp_treatment_journal::v1::ApplyStatus_descriptor(); +} + +} // namespace protobuf +} // namespace google + +// @@protoc_insertion_point(global_scope) + +#include "google/protobuf/port_undef.inc" +// clang-format on + +#endif // xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto_2epb_2eh diff --git a/gen/dart/xtcp_treatment_journal/v1/journal.pb.dart b/gen/dart/xtcp_treatment_journal/v1/journal.pb.dart new file mode 100644 index 000000000..6f9bef0c3 --- /dev/null +++ b/gen/dart/xtcp_treatment_journal/v1/journal.pb.dart @@ -0,0 +1,856 @@ +// This is a generated file - do not edit. +// +// Generated from xtcp_treatment_journal/v1/journal.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports + +import 'dart:core' as $core; + +import 'package:fixnum/fixnum.dart' as $fixnum; +import 'package:protobuf/protobuf.dart' as $pb; +import 'package:protobuf/well_known_types/google/protobuf/timestamp.pb.dart' + as $0; + +import 'journal.pbenum.dart'; + +export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions; + +export 'journal.pbenum.dart'; + +/// TCPSettings mirrors xtcp_treatment.v1.TCPSettings; presence is meaningful. +class TCPSettings extends $pb.GeneratedMessage { + factory TCPSettings({ + $core.String? congestionControl, + $core.bool? congestionLock, + $core.int? rtoMinMs, + $core.int? initialCongestionWindow, + $core.int? initialReceiveWindow, + $core.bool? quickAck, + $core.bool? ecn, + }) { + final result = TCPSettings._(); + if (congestionControl != null) result.congestionControl = congestionControl; + if (congestionLock != null) result.congestionLock = congestionLock; + if (rtoMinMs != null) result.rtoMinMs = rtoMinMs; + if (initialCongestionWindow != null) + result.initialCongestionWindow = initialCongestionWindow; + if (initialReceiveWindow != null) + result.initialReceiveWindow = initialReceiveWindow; + if (quickAck != null) result.quickAck = quickAck; + if (ecn != null) result.ecn = ecn; + return result; + } + + TCPSettings._(); + + factory TCPSettings.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + TCPSettings()..mergeFromBuffer(data, registry); + factory TCPSettings.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + TCPSettings()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'TCPSettings', + package: const $pb.PackageName( + _omitMessageNames ? '' : 'xtcp_treatment_journal.v1'), + createEmptyInstance: TCPSettings.$_createMessage) + ..aOS(1, _omitFieldNames ? '' : 'congestionControl') + ..aOB(2, _omitFieldNames ? '' : 'congestionLock') + ..aI(3, _omitFieldNames ? '' : 'rtoMinMs', fieldType: $pb.PbFieldType.OU3) + ..aI(4, _omitFieldNames ? '' : 'initialCongestionWindow', + fieldType: $pb.PbFieldType.OU3) + ..aI(5, _omitFieldNames ? '' : 'initialReceiveWindow', + fieldType: $pb.PbFieldType.OU3) + ..aOB(6, _omitFieldNames ? '' : 'quickAck') + ..aOB(7, _omitFieldNames ? '' : 'ecn') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + TCPSettings clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + TCPSettings copyWith(void Function(TCPSettings) updates) => + super.copyWith((message) => updates(message as TCPSettings)) + as TCPSettings; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated('Use TCPSettings() / TCPSettings.new instead') + static TCPSettings create() => TCPSettings._(); + static $pb.GeneratedMessage $_createMessage() => TCPSettings._(); + @$core.override + TCPSettings createEmptyInstance() => TCPSettings._(); + @$core.pragma('dart2js:noInline') + static TCPSettings getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor( + TCPSettings.$_createMessage); + static TCPSettings? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get congestionControl => $_getSZ(0); + @$pb.TagNumber(1) + set congestionControl($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasCongestionControl() => $_has(0); + @$pb.TagNumber(1) + void clearCongestionControl() => $_clearField(1); + + @$pb.TagNumber(2) + $core.bool get congestionLock => $_getBF(1); + @$pb.TagNumber(2) + set congestionLock($core.bool value) => $_setBool(1, value); + @$pb.TagNumber(2) + $core.bool hasCongestionLock() => $_has(1); + @$pb.TagNumber(2) + void clearCongestionLock() => $_clearField(2); + + @$pb.TagNumber(3) + $core.int get rtoMinMs => $_getIZ(2); + @$pb.TagNumber(3) + set rtoMinMs($core.int value) => $_setUnsignedInt32(2, value); + @$pb.TagNumber(3) + $core.bool hasRtoMinMs() => $_has(2); + @$pb.TagNumber(3) + void clearRtoMinMs() => $_clearField(3); + + @$pb.TagNumber(4) + $core.int get initialCongestionWindow => $_getIZ(3); + @$pb.TagNumber(4) + set initialCongestionWindow($core.int value) => $_setUnsignedInt32(3, value); + @$pb.TagNumber(4) + $core.bool hasInitialCongestionWindow() => $_has(3); + @$pb.TagNumber(4) + void clearInitialCongestionWindow() => $_clearField(4); + + @$pb.TagNumber(5) + $core.int get initialReceiveWindow => $_getIZ(4); + @$pb.TagNumber(5) + set initialReceiveWindow($core.int value) => $_setUnsignedInt32(4, value); + @$pb.TagNumber(5) + $core.bool hasInitialReceiveWindow() => $_has(4); + @$pb.TagNumber(5) + void clearInitialReceiveWindow() => $_clearField(5); + + @$pb.TagNumber(6) + $core.bool get quickAck => $_getBF(5); + @$pb.TagNumber(6) + set quickAck($core.bool value) => $_setBool(5, value); + @$pb.TagNumber(6) + $core.bool hasQuickAck() => $_has(5); + @$pb.TagNumber(6) + void clearQuickAck() => $_clearField(6); + + @$pb.TagNumber(7) + $core.bool get ecn => $_getBF(6); + @$pb.TagNumber(7) + set ecn($core.bool value) => $_setBool(6, value); + @$pb.TagNumber(7) + $core.bool hasEcn() => $_has(6); + @$pb.TagNumber(7) + void clearEcn() => $_clearField(7); +} + +class Treatment extends $pb.GeneratedMessage { + factory Treatment({ + $core.String? id, + $fixnum.Int64? revision, + $core.String? destination, + TCPSettings? settings, + $core.bool? ecnFabricVerified, + }) { + final result = Treatment._(); + if (id != null) result.id = id; + if (revision != null) result.revision = revision; + if (destination != null) result.destination = destination; + if (settings != null) result.settings = settings; + if (ecnFabricVerified != null) result.ecnFabricVerified = ecnFabricVerified; + return result; + } + + Treatment._(); + + factory Treatment.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + Treatment()..mergeFromBuffer(data, registry); + factory Treatment.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + Treatment()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'Treatment', + package: const $pb.PackageName( + _omitMessageNames ? '' : 'xtcp_treatment_journal.v1'), + createEmptyInstance: Treatment.$_createMessage) + ..aOS(1, _omitFieldNames ? '' : 'id') + ..a<$fixnum.Int64>( + 2, _omitFieldNames ? '' : 'revision', $pb.PbFieldType.OU6, + defaultOrMaker: $fixnum.Int64.ZERO) + ..aOS(3, _omitFieldNames ? '' : 'destination') + ..aOM(4, _omitFieldNames ? '' : 'settings', + subBuilder: TCPSettings.$_createMessage) + ..aOB(5, _omitFieldNames ? '' : 'ecnFabricVerified') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + Treatment clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + Treatment copyWith(void Function(Treatment) updates) => + super.copyWith((message) => updates(message as Treatment)) as Treatment; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated('Use Treatment() / Treatment.new instead') + static Treatment create() => Treatment._(); + static $pb.GeneratedMessage $_createMessage() => Treatment._(); + @$core.override + Treatment createEmptyInstance() => Treatment._(); + @$core.pragma('dart2js:noInline') + static Treatment getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(Treatment.$_createMessage); + static Treatment? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get id => $_getSZ(0); + @$pb.TagNumber(1) + set id($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasId() => $_has(0); + @$pb.TagNumber(1) + void clearId() => $_clearField(1); + + @$pb.TagNumber(2) + $fixnum.Int64 get revision => $_getI64(1); + @$pb.TagNumber(2) + set revision($fixnum.Int64 value) => $_setInt64(1, value); + @$pb.TagNumber(2) + $core.bool hasRevision() => $_has(1); + @$pb.TagNumber(2) + void clearRevision() => $_clearField(2); + + /// Canonical /32 or /128 host prefix. + @$pb.TagNumber(3) + $core.String get destination => $_getSZ(2); + @$pb.TagNumber(3) + set destination($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasDestination() => $_has(2); + @$pb.TagNumber(3) + void clearDestination() => $_clearField(3); + + @$pb.TagNumber(4) + TCPSettings get settings => $_getN(3); + @$pb.TagNumber(4) + set settings(TCPSettings value) => $_setField(4, value); + @$pb.TagNumber(4) + $core.bool hasSettings() => $_has(3); + @$pb.TagNumber(4) + void clearSettings() => $_clearField(4); + @$pb.TagNumber(4) + TCPSettings ensureSettings() => $_ensure(3); + + @$pb.TagNumber(5) + $core.bool get ecnFabricVerified => $_getBF(4); + @$pb.TagNumber(5) + set ecnFabricVerified($core.bool value) => $_setBool(4, value); + @$pb.TagNumber(5) + $core.bool hasEcnFabricVerified() => $_has(4); + @$pb.TagNumber(5) + void clearEcnFabricVerified() => $_clearField(5); +} + +class Assignment extends $pb.GeneratedMessage { + factory Assignment({ + $core.String? experimentId, + Treatment? treatment, + AssignmentMode? mode, + $0.Timestamp? leaseExpiresAt, + Treatment? predecessor, + $fixnum.Int64? generation, + }) { + final result = Assignment._(); + if (experimentId != null) result.experimentId = experimentId; + if (treatment != null) result.treatment = treatment; + if (mode != null) result.mode = mode; + if (leaseExpiresAt != null) result.leaseExpiresAt = leaseExpiresAt; + if (predecessor != null) result.predecessor = predecessor; + if (generation != null) result.generation = generation; + return result; + } + + Assignment._(); + + factory Assignment.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + Assignment()..mergeFromBuffer(data, registry); + factory Assignment.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + Assignment()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'Assignment', + package: const $pb.PackageName( + _omitMessageNames ? '' : 'xtcp_treatment_journal.v1'), + createEmptyInstance: Assignment.$_createMessage) + ..aOS(1, _omitFieldNames ? '' : 'experimentId') + ..aOM(2, _omitFieldNames ? '' : 'treatment', + subBuilder: Treatment.$_createMessage) + ..aE(3, _omitFieldNames ? '' : 'mode', + enumValues: AssignmentMode.values) + ..aOM<$0.Timestamp>(4, _omitFieldNames ? '' : 'leaseExpiresAt', + subBuilder: $0.Timestamp.$_createMessage) + ..aOM(5, _omitFieldNames ? '' : 'predecessor', + subBuilder: Treatment.$_createMessage) + ..a<$fixnum.Int64>( + 6, _omitFieldNames ? '' : 'generation', $pb.PbFieldType.OU6, + defaultOrMaker: $fixnum.Int64.ZERO) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + Assignment clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + Assignment copyWith(void Function(Assignment) updates) => + super.copyWith((message) => updates(message as Assignment)) as Assignment; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated('Use Assignment() / Assignment.new instead') + static Assignment create() => Assignment._(); + static $pb.GeneratedMessage $_createMessage() => Assignment._(); + @$core.override + Assignment createEmptyInstance() => Assignment._(); + @$core.pragma('dart2js:noInline') + static Assignment getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(Assignment.$_createMessage); + static Assignment? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get experimentId => $_getSZ(0); + @$pb.TagNumber(1) + set experimentId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasExperimentId() => $_has(0); + @$pb.TagNumber(1) + void clearExperimentId() => $_clearField(1); + + @$pb.TagNumber(2) + Treatment get treatment => $_getN(1); + @$pb.TagNumber(2) + set treatment(Treatment value) => $_setField(2, value); + @$pb.TagNumber(2) + $core.bool hasTreatment() => $_has(1); + @$pb.TagNumber(2) + void clearTreatment() => $_clearField(2); + @$pb.TagNumber(2) + Treatment ensureTreatment() => $_ensure(1); + + @$pb.TagNumber(3) + AssignmentMode get mode => $_getN(2); + @$pb.TagNumber(3) + set mode(AssignmentMode value) => $_setField(3, value); + @$pb.TagNumber(3) + $core.bool hasMode() => $_has(2); + @$pb.TagNumber(3) + void clearMode() => $_clearField(3); + + @$pb.TagNumber(4) + $0.Timestamp get leaseExpiresAt => $_getN(3); + @$pb.TagNumber(4) + set leaseExpiresAt($0.Timestamp value) => $_setField(4, value); + @$pb.TagNumber(4) + $core.bool hasLeaseExpiresAt() => $_has(3); + @$pb.TagNumber(4) + void clearLeaseExpiresAt() => $_clearField(4); + @$pb.TagNumber(4) + $0.Timestamp ensureLeaseExpiresAt() => $_ensure(3); + + @$pb.TagNumber(5) + Treatment get predecessor => $_getN(4); + @$pb.TagNumber(5) + set predecessor(Treatment value) => $_setField(5, value); + @$pb.TagNumber(5) + $core.bool hasPredecessor() => $_has(4); + @$pb.TagNumber(5) + void clearPredecessor() => $_clearField(5); + @$pb.TagNumber(5) + Treatment ensurePredecessor() => $_ensure(4); + + @$pb.TagNumber(6) + $fixnum.Int64 get generation => $_getI64(5); + @$pb.TagNumber(6) + set generation($fixnum.Int64 value) => $_setInt64(5, value); + @$pb.TagNumber(6) + $core.bool hasGeneration() => $_has(5); + @$pb.TagNumber(6) + void clearGeneration() => $_clearField(6); +} + +/// Desired is the controller's last accepted snapshot for this agent. The +/// agent identity lives on the Journal header. +class Desired extends $pb.GeneratedMessage { + factory Desired({ + $fixnum.Int64? revision, + $core.bool? suspended, + $0.Timestamp? issuedAt, + $core.Iterable? assignments, + }) { + final result = Desired._(); + if (revision != null) result.revision = revision; + if (suspended != null) result.suspended = suspended; + if (issuedAt != null) result.issuedAt = issuedAt; + if (assignments != null) result.assignments.addAll(assignments); + return result; + } + + Desired._(); + + factory Desired.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + Desired()..mergeFromBuffer(data, registry); + factory Desired.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + Desired()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'Desired', + package: const $pb.PackageName( + _omitMessageNames ? '' : 'xtcp_treatment_journal.v1'), + createEmptyInstance: Desired.$_createMessage) + ..a<$fixnum.Int64>( + 1, _omitFieldNames ? '' : 'revision', $pb.PbFieldType.OU6, + defaultOrMaker: $fixnum.Int64.ZERO) + ..aOB(2, _omitFieldNames ? '' : 'suspended') + ..aOM<$0.Timestamp>(3, _omitFieldNames ? '' : 'issuedAt', + subBuilder: $0.Timestamp.$_createMessage) + ..pPM(4, _omitFieldNames ? '' : 'assignments', + subBuilder: Assignment.$_createMessage) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + Desired clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + Desired copyWith(void Function(Desired) updates) => + super.copyWith((message) => updates(message as Desired)) as Desired; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated('Use Desired() / Desired.new instead') + static Desired create() => Desired._(); + static $pb.GeneratedMessage $_createMessage() => Desired._(); + @$core.override + Desired createEmptyInstance() => Desired._(); + @$core.pragma('dart2js:noInline') + static Desired getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(Desired.$_createMessage); + static Desired? _defaultInstance; + + @$pb.TagNumber(1) + $fixnum.Int64 get revision => $_getI64(0); + @$pb.TagNumber(1) + set revision($fixnum.Int64 value) => $_setInt64(0, value); + @$pb.TagNumber(1) + $core.bool hasRevision() => $_has(0); + @$pb.TagNumber(1) + void clearRevision() => $_clearField(1); + + @$pb.TagNumber(2) + $core.bool get suspended => $_getBF(1); + @$pb.TagNumber(2) + set suspended($core.bool value) => $_setBool(1, value); + @$pb.TagNumber(2) + $core.bool hasSuspended() => $_has(1); + @$pb.TagNumber(2) + void clearSuspended() => $_clearField(2); + + @$pb.TagNumber(3) + $0.Timestamp get issuedAt => $_getN(2); + @$pb.TagNumber(3) + set issuedAt($0.Timestamp value) => $_setField(3, value); + @$pb.TagNumber(3) + $core.bool hasIssuedAt() => $_has(2); + @$pb.TagNumber(3) + void clearIssuedAt() => $_clearField(3); + @$pb.TagNumber(3) + $0.Timestamp ensureIssuedAt() => $_ensure(2); + + @$pb.TagNumber(4) + $pb.PbList get assignments => $_getList(3); +} + +/// Record is the agent's account of one assignment plus the kernel-local +/// facts (installed route bytes, installing boot) that never leave the host. +class Record extends $pb.GeneratedMessage { + factory Record({ + Assignment? assignment, + $fixnum.Int64? desiredRevision, + ApplyStatus? status, + $0.Timestamp? receivedAt, + $0.Timestamp? attemptedAt, + $0.Timestamp? appliedAt, + $0.Timestamp? verifiedAt, + $0.Timestamp? removedAt, + $core.String? error, + $core.List<$core.int>? installedRoute, + $core.String? installedBootId, + $core.bool? expiredLatched, + }) { + final result = Record._(); + if (assignment != null) result.assignment = assignment; + if (desiredRevision != null) result.desiredRevision = desiredRevision; + if (status != null) result.status = status; + if (receivedAt != null) result.receivedAt = receivedAt; + if (attemptedAt != null) result.attemptedAt = attemptedAt; + if (appliedAt != null) result.appliedAt = appliedAt; + if (verifiedAt != null) result.verifiedAt = verifiedAt; + if (removedAt != null) result.removedAt = removedAt; + if (error != null) result.error = error; + if (installedRoute != null) result.installedRoute = installedRoute; + if (installedBootId != null) result.installedBootId = installedBootId; + if (expiredLatched != null) result.expiredLatched = expiredLatched; + return result; + } + + Record._(); + + factory Record.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + Record()..mergeFromBuffer(data, registry); + factory Record.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + Record()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'Record', + package: const $pb.PackageName( + _omitMessageNames ? '' : 'xtcp_treatment_journal.v1'), + createEmptyInstance: Record.$_createMessage) + ..aOM(1, _omitFieldNames ? '' : 'assignment', + subBuilder: Assignment.$_createMessage) + ..a<$fixnum.Int64>( + 2, _omitFieldNames ? '' : 'desiredRevision', $pb.PbFieldType.OU6, + defaultOrMaker: $fixnum.Int64.ZERO) + ..aE(3, _omitFieldNames ? '' : 'status', + enumValues: ApplyStatus.values) + ..aOM<$0.Timestamp>(4, _omitFieldNames ? '' : 'receivedAt', + subBuilder: $0.Timestamp.$_createMessage) + ..aOM<$0.Timestamp>(5, _omitFieldNames ? '' : 'attemptedAt', + subBuilder: $0.Timestamp.$_createMessage) + ..aOM<$0.Timestamp>(6, _omitFieldNames ? '' : 'appliedAt', + subBuilder: $0.Timestamp.$_createMessage) + ..aOM<$0.Timestamp>(7, _omitFieldNames ? '' : 'verifiedAt', + subBuilder: $0.Timestamp.$_createMessage) + ..aOM<$0.Timestamp>(8, _omitFieldNames ? '' : 'removedAt', + subBuilder: $0.Timestamp.$_createMessage) + ..aOS(9, _omitFieldNames ? '' : 'error') + ..a<$core.List<$core.int>>( + 10, _omitFieldNames ? '' : 'installedRoute', $pb.PbFieldType.OY) + ..aOS(11, _omitFieldNames ? '' : 'installedBootId') + ..aOB(12, _omitFieldNames ? '' : 'expiredLatched') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + Record clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + Record copyWith(void Function(Record) updates) => + super.copyWith((message) => updates(message as Record)) as Record; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated('Use Record() / Record.new instead') + static Record create() => Record._(); + static $pb.GeneratedMessage $_createMessage() => Record._(); + @$core.override + Record createEmptyInstance() => Record._(); + @$core.pragma('dart2js:noInline') + static Record getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(Record.$_createMessage); + static Record? _defaultInstance; + + @$pb.TagNumber(1) + Assignment get assignment => $_getN(0); + @$pb.TagNumber(1) + set assignment(Assignment value) => $_setField(1, value); + @$pb.TagNumber(1) + $core.bool hasAssignment() => $_has(0); + @$pb.TagNumber(1) + void clearAssignment() => $_clearField(1); + @$pb.TagNumber(1) + Assignment ensureAssignment() => $_ensure(0); + + @$pb.TagNumber(2) + $fixnum.Int64 get desiredRevision => $_getI64(1); + @$pb.TagNumber(2) + set desiredRevision($fixnum.Int64 value) => $_setInt64(1, value); + @$pb.TagNumber(2) + $core.bool hasDesiredRevision() => $_has(1); + @$pb.TagNumber(2) + void clearDesiredRevision() => $_clearField(2); + + @$pb.TagNumber(3) + ApplyStatus get status => $_getN(2); + @$pb.TagNumber(3) + set status(ApplyStatus value) => $_setField(3, value); + @$pb.TagNumber(3) + $core.bool hasStatus() => $_has(2); + @$pb.TagNumber(3) + void clearStatus() => $_clearField(3); + + @$pb.TagNumber(4) + $0.Timestamp get receivedAt => $_getN(3); + @$pb.TagNumber(4) + set receivedAt($0.Timestamp value) => $_setField(4, value); + @$pb.TagNumber(4) + $core.bool hasReceivedAt() => $_has(3); + @$pb.TagNumber(4) + void clearReceivedAt() => $_clearField(4); + @$pb.TagNumber(4) + $0.Timestamp ensureReceivedAt() => $_ensure(3); + + @$pb.TagNumber(5) + $0.Timestamp get attemptedAt => $_getN(4); + @$pb.TagNumber(5) + set attemptedAt($0.Timestamp value) => $_setField(5, value); + @$pb.TagNumber(5) + $core.bool hasAttemptedAt() => $_has(4); + @$pb.TagNumber(5) + void clearAttemptedAt() => $_clearField(5); + @$pb.TagNumber(5) + $0.Timestamp ensureAttemptedAt() => $_ensure(4); + + @$pb.TagNumber(6) + $0.Timestamp get appliedAt => $_getN(5); + @$pb.TagNumber(6) + set appliedAt($0.Timestamp value) => $_setField(6, value); + @$pb.TagNumber(6) + $core.bool hasAppliedAt() => $_has(5); + @$pb.TagNumber(6) + void clearAppliedAt() => $_clearField(6); + @$pb.TagNumber(6) + $0.Timestamp ensureAppliedAt() => $_ensure(5); + + @$pb.TagNumber(7) + $0.Timestamp get verifiedAt => $_getN(6); + @$pb.TagNumber(7) + set verifiedAt($0.Timestamp value) => $_setField(7, value); + @$pb.TagNumber(7) + $core.bool hasVerifiedAt() => $_has(6); + @$pb.TagNumber(7) + void clearVerifiedAt() => $_clearField(7); + @$pb.TagNumber(7) + $0.Timestamp ensureVerifiedAt() => $_ensure(6); + + @$pb.TagNumber(8) + $0.Timestamp get removedAt => $_getN(7); + @$pb.TagNumber(8) + set removedAt($0.Timestamp value) => $_setField(8, value); + @$pb.TagNumber(8) + $core.bool hasRemovedAt() => $_has(7); + @$pb.TagNumber(8) + void clearRemovedAt() => $_clearField(8); + @$pb.TagNumber(8) + $0.Timestamp ensureRemovedAt() => $_ensure(7); + + @$pb.TagNumber(9) + $core.String get error => $_getSZ(8); + @$pb.TagNumber(9) + set error($core.String value) => $_setString(8, value); + @$pb.TagNumber(9) + $core.bool hasError() => $_has(8); + @$pb.TagNumber(9) + void clearError() => $_clearField(9); + + /// Locally planned canonical netlink route body, the only thing Remove will + /// ever be asked to withdraw. + @$pb.TagNumber(10) + $core.List<$core.int> get installedRoute => $_getN(9); + @$pb.TagNumber(10) + set installedRoute($core.List<$core.int> value) => $_setBytes(9, value); + @$pb.TagNumber(10) + $core.bool hasInstalledRoute() => $_has(9); + @$pb.TagNumber(10) + void clearInstalledRoute() => $_clearField(10); + + @$pb.TagNumber(11) + $core.String get installedBootId => $_getSZ(10); + @$pb.TagNumber(11) + set installedBootId($core.String value) => $_setString(10, value); + @$pb.TagNumber(11) + $core.bool hasInstalledBootId() => $_has(10); + @$pb.TagNumber(11) + void clearInstalledBootId() => $_clearField(11); + + @$pb.TagNumber(12) + $core.bool get expiredLatched => $_getBF(11); + @$pb.TagNumber(12) + set expiredLatched($core.bool value) => $_setBool(11, value); + @$pb.TagNumber(12) + $core.bool hasExpiredLatched() => $_has(11); + @$pb.TagNumber(12) + void clearExpiredLatched() => $_clearField(12); +} + +/// Journal is the atomic snapshot in /state.textproto. The header +/// identifies the writer so a journal can never be reconciled for another +/// agent and so a telemetry-only daemon can attribute records from it. +class Journal extends $pb.GeneratedMessage { + factory Journal({ + $core.String? agentId, + $core.String? location, + $core.String? bootId, + Desired? desired, + $core.bool? locallySuspended, + $core.String? suspensionReason, + $core.Iterable? records, + $0.Timestamp? updatedAt, + }) { + final result = Journal._(); + if (agentId != null) result.agentId = agentId; + if (location != null) result.location = location; + if (bootId != null) result.bootId = bootId; + if (desired != null) result.desired = desired; + if (locallySuspended != null) result.locallySuspended = locallySuspended; + if (suspensionReason != null) result.suspensionReason = suspensionReason; + if (records != null) result.records.addAll(records); + if (updatedAt != null) result.updatedAt = updatedAt; + return result; + } + + Journal._(); + + factory Journal.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + Journal()..mergeFromBuffer(data, registry); + factory Journal.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + Journal()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'Journal', + package: const $pb.PackageName( + _omitMessageNames ? '' : 'xtcp_treatment_journal.v1'), + createEmptyInstance: Journal.$_createMessage) + ..aOS(1, _omitFieldNames ? '' : 'agentId') + ..aOS(2, _omitFieldNames ? '' : 'location') + ..aOS(3, _omitFieldNames ? '' : 'bootId') + ..aOM(4, _omitFieldNames ? '' : 'desired', + subBuilder: Desired.$_createMessage) + ..aOB(5, _omitFieldNames ? '' : 'locallySuspended') + ..aOS(6, _omitFieldNames ? '' : 'suspensionReason') + ..pPM(7, _omitFieldNames ? '' : 'records', + subBuilder: Record.$_createMessage) + ..aOM<$0.Timestamp>(8, _omitFieldNames ? '' : 'updatedAt', + subBuilder: $0.Timestamp.$_createMessage) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + Journal clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + Journal copyWith(void Function(Journal) updates) => + super.copyWith((message) => updates(message as Journal)) as Journal; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + @$core.Deprecated('Use Journal() / Journal.new instead') + static Journal create() => Journal._(); + static $pb.GeneratedMessage $_createMessage() => Journal._(); + @$core.override + Journal createEmptyInstance() => Journal._(); + @$core.pragma('dart2js:noInline') + static Journal getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(Journal.$_createMessage); + static Journal? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get agentId => $_getSZ(0); + @$pb.TagNumber(1) + set agentId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasAgentId() => $_has(0); + @$pb.TagNumber(1) + void clearAgentId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get location => $_getSZ(1); + @$pb.TagNumber(2) + set location($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasLocation() => $_has(1); + @$pb.TagNumber(2) + void clearLocation() => $_clearField(2); + + /// Boot that last wrote the journal; per-record installed_boot_id says + /// which boot installed each route. + @$pb.TagNumber(3) + $core.String get bootId => $_getSZ(2); + @$pb.TagNumber(3) + set bootId($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasBootId() => $_has(2); + @$pb.TagNumber(3) + void clearBootId() => $_clearField(3); + + @$pb.TagNumber(4) + Desired get desired => $_getN(3); + @$pb.TagNumber(4) + set desired(Desired value) => $_setField(4, value); + @$pb.TagNumber(4) + $core.bool hasDesired() => $_has(3); + @$pb.TagNumber(4) + void clearDesired() => $_clearField(4); + @$pb.TagNumber(4) + Desired ensureDesired() => $_ensure(3); + + @$pb.TagNumber(5) + $core.bool get locallySuspended => $_getBF(4); + @$pb.TagNumber(5) + set locallySuspended($core.bool value) => $_setBool(4, value); + @$pb.TagNumber(5) + $core.bool hasLocallySuspended() => $_has(4); + @$pb.TagNumber(5) + void clearLocallySuspended() => $_clearField(5); + + @$pb.TagNumber(6) + $core.String get suspensionReason => $_getSZ(5); + @$pb.TagNumber(6) + set suspensionReason($core.String value) => $_setString(5, value); + @$pb.TagNumber(6) + $core.bool hasSuspensionReason() => $_has(5); + @$pb.TagNumber(6) + void clearSuspensionReason() => $_clearField(6); + + @$pb.TagNumber(7) + $pb.PbList get records => $_getList(6); + + @$pb.TagNumber(8) + $0.Timestamp get updatedAt => $_getN(7); + @$pb.TagNumber(8) + set updatedAt($0.Timestamp value) => $_setField(8, value); + @$pb.TagNumber(8) + $core.bool hasUpdatedAt() => $_has(7); + @$pb.TagNumber(8) + void clearUpdatedAt() => $_clearField(8); + @$pb.TagNumber(8) + $0.Timestamp ensureUpdatedAt() => $_ensure(7); +} + +const $core.bool _omitFieldNames = + $core.bool.fromEnvironment('protobuf.omit_field_names'); +const $core.bool _omitMessageNames = + $core.bool.fromEnvironment('protobuf.omit_message_names'); diff --git a/gen/dart/xtcp_treatment_journal/v1/journal.pbenum.dart b/gen/dart/xtcp_treatment_journal/v1/journal.pbenum.dart new file mode 100644 index 000000000..ec1794e14 --- /dev/null +++ b/gen/dart/xtcp_treatment_journal/v1/journal.pbenum.dart @@ -0,0 +1,77 @@ +// This is a generated file - do not edit. +// +// Generated from xtcp_treatment_journal/v1/journal.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports + +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +class AssignmentMode extends $pb.ProtobufEnum { + static const AssignmentMode ASSIGNMENT_MODE_UNSPECIFIED = + AssignmentMode._(0, _omitEnumNames ? '' : 'ASSIGNMENT_MODE_UNSPECIFIED'); + static const AssignmentMode ASSIGNMENT_MODE_TRIAL = + AssignmentMode._(1, _omitEnumNames ? '' : 'ASSIGNMENT_MODE_TRIAL'); + static const AssignmentMode ASSIGNMENT_MODE_STABLE = + AssignmentMode._(2, _omitEnumNames ? '' : 'ASSIGNMENT_MODE_STABLE'); + static const AssignmentMode ASSIGNMENT_MODE_SUSPENDED = + AssignmentMode._(3, _omitEnumNames ? '' : 'ASSIGNMENT_MODE_SUSPENDED'); + static const AssignmentMode ASSIGNMENT_MODE_RETIRED = + AssignmentMode._(4, _omitEnumNames ? '' : 'ASSIGNMENT_MODE_RETIRED'); + + static const $core.List values = [ + ASSIGNMENT_MODE_UNSPECIFIED, + ASSIGNMENT_MODE_TRIAL, + ASSIGNMENT_MODE_STABLE, + ASSIGNMENT_MODE_SUSPENDED, + ASSIGNMENT_MODE_RETIRED, + ]; + + static final $core.List _byValue = + $pb.ProtobufEnum.$_initByValueList(values, 4); + static AssignmentMode? valueOf($core.int value) => + value < 0 || value >= _byValue.length ? null : _byValue[value]; + + const AssignmentMode._(super.value, super.name); +} + +class ApplyStatus extends $pb.ProtobufEnum { + static const ApplyStatus APPLY_STATUS_UNSPECIFIED = + ApplyStatus._(0, _omitEnumNames ? '' : 'APPLY_STATUS_UNSPECIFIED'); + static const ApplyStatus APPLY_STATUS_PENDING = + ApplyStatus._(1, _omitEnumNames ? '' : 'APPLY_STATUS_PENDING'); + static const ApplyStatus APPLY_STATUS_VERIFIED = + ApplyStatus._(2, _omitEnumNames ? '' : 'APPLY_STATUS_VERIFIED'); + static const ApplyStatus APPLY_STATUS_REMOVED = + ApplyStatus._(3, _omitEnumNames ? '' : 'APPLY_STATUS_REMOVED'); + static const ApplyStatus APPLY_STATUS_EXPIRED = + ApplyStatus._(4, _omitEnumNames ? '' : 'APPLY_STATUS_EXPIRED'); + static const ApplyStatus APPLY_STATUS_FAILED = + ApplyStatus._(5, _omitEnumNames ? '' : 'APPLY_STATUS_FAILED'); + + static const $core.List values = [ + APPLY_STATUS_UNSPECIFIED, + APPLY_STATUS_PENDING, + APPLY_STATUS_VERIFIED, + APPLY_STATUS_REMOVED, + APPLY_STATUS_EXPIRED, + APPLY_STATUS_FAILED, + ]; + + static final $core.List _byValue = + $pb.ProtobufEnum.$_initByValueList(values, 5); + static ApplyStatus? valueOf($core.int value) => + value < 0 || value >= _byValue.length ? null : _byValue[value]; + + const ApplyStatus._(super.value, super.name); +} + +const $core.bool _omitEnumNames = + $core.bool.fromEnvironment('protobuf.omit_enum_names'); diff --git a/gen/dart/xtcp_treatment_journal/v1/journal.pbjson.dart b/gen/dart/xtcp_treatment_journal/v1/journal.pbjson.dart new file mode 100644 index 000000000..1db4e94cc --- /dev/null +++ b/gen/dart/xtcp_treatment_journal/v1/journal.pbjson.dart @@ -0,0 +1,389 @@ +// This is a generated file - do not edit. +// +// Generated from xtcp_treatment_journal/v1/journal.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports +// ignore_for_file: unused_import + +import 'dart:convert' as $convert; +import 'dart:core' as $core; +import 'dart:typed_data' as $typed_data; + +@$core.Deprecated('Use assignmentModeDescriptor instead') +const AssignmentMode$json = { + '1': 'AssignmentMode', + '2': [ + {'1': 'ASSIGNMENT_MODE_UNSPECIFIED', '2': 0}, + {'1': 'ASSIGNMENT_MODE_TRIAL', '2': 1}, + {'1': 'ASSIGNMENT_MODE_STABLE', '2': 2}, + {'1': 'ASSIGNMENT_MODE_SUSPENDED', '2': 3}, + {'1': 'ASSIGNMENT_MODE_RETIRED', '2': 4}, + ], +}; + +/// Descriptor for `AssignmentMode`. Decode as a `google.protobuf.EnumDescriptorProto`. +final $typed_data.Uint8List assignmentModeDescriptor = $convert.base64Decode( + 'Cg5Bc3NpZ25tZW50TW9kZRIfChtBU1NJR05NRU5UX01PREVfVU5TUEVDSUZJRUQQABIZChVBU1' + 'NJR05NRU5UX01PREVfVFJJQUwQARIaChZBU1NJR05NRU5UX01PREVfU1RBQkxFEAISHQoZQVNT' + 'SUdOTUVOVF9NT0RFX1NVU1BFTkRFRBADEhsKF0FTU0lHTk1FTlRfTU9ERV9SRVRJUkVEEAQ='); + +@$core.Deprecated('Use applyStatusDescriptor instead') +const ApplyStatus$json = { + '1': 'ApplyStatus', + '2': [ + {'1': 'APPLY_STATUS_UNSPECIFIED', '2': 0}, + {'1': 'APPLY_STATUS_PENDING', '2': 1}, + {'1': 'APPLY_STATUS_VERIFIED', '2': 2}, + {'1': 'APPLY_STATUS_REMOVED', '2': 3}, + {'1': 'APPLY_STATUS_EXPIRED', '2': 4}, + {'1': 'APPLY_STATUS_FAILED', '2': 5}, + ], +}; + +/// Descriptor for `ApplyStatus`. Decode as a `google.protobuf.EnumDescriptorProto`. +final $typed_data.Uint8List applyStatusDescriptor = $convert.base64Decode( + 'CgtBcHBseVN0YXR1cxIcChhBUFBMWV9TVEFUVVNfVU5TUEVDSUZJRUQQABIYChRBUFBMWV9TVE' + 'FUVVNfUEVORElORxABEhkKFUFQUExZX1NUQVRVU19WRVJJRklFRBACEhgKFEFQUExZX1NUQVRV' + 'U19SRU1PVkVEEAMSGAoUQVBQTFlfU1RBVFVTX0VYUElSRUQQBBIXChNBUFBMWV9TVEFUVVNfRk' + 'FJTEVEEAU='); + +@$core.Deprecated('Use tCPSettingsDescriptor instead') +const TCPSettings$json = { + '1': 'TCPSettings', + '2': [ + { + '1': 'congestion_control', + '3': 1, + '4': 1, + '5': 9, + '9': 0, + '10': 'congestionControl', + '17': true + }, + {'1': 'congestion_lock', '3': 2, '4': 1, '5': 8, '10': 'congestionLock'}, + { + '1': 'rto_min_ms', + '3': 3, + '4': 1, + '5': 13, + '9': 1, + '10': 'rtoMinMs', + '17': true + }, + { + '1': 'initial_congestion_window', + '3': 4, + '4': 1, + '5': 13, + '9': 2, + '10': 'initialCongestionWindow', + '17': true + }, + { + '1': 'initial_receive_window', + '3': 5, + '4': 1, + '5': 13, + '9': 3, + '10': 'initialReceiveWindow', + '17': true + }, + { + '1': 'quick_ack', + '3': 6, + '4': 1, + '5': 8, + '9': 4, + '10': 'quickAck', + '17': true + }, + {'1': 'ecn', '3': 7, '4': 1, '5': 8, '9': 5, '10': 'ecn', '17': true}, + ], + '8': [ + {'1': '_congestion_control'}, + {'1': '_rto_min_ms'}, + {'1': '_initial_congestion_window'}, + {'1': '_initial_receive_window'}, + {'1': '_quick_ack'}, + {'1': '_ecn'}, + ], +}; + +/// Descriptor for `TCPSettings`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List tCPSettingsDescriptor = $convert.base64Decode( + 'CgtUQ1BTZXR0aW5ncxIyChJjb25nZXN0aW9uX2NvbnRyb2wYASABKAlIAFIRY29uZ2VzdGlvbk' + 'NvbnRyb2yIAQESJwoPY29uZ2VzdGlvbl9sb2NrGAIgASgIUg5jb25nZXN0aW9uTG9jaxIhCgpy' + 'dG9fbWluX21zGAMgASgNSAFSCHJ0b01pbk1ziAEBEj8KGWluaXRpYWxfY29uZ2VzdGlvbl93aW' + '5kb3cYBCABKA1IAlIXaW5pdGlhbENvbmdlc3Rpb25XaW5kb3eIAQESOQoWaW5pdGlhbF9yZWNl' + 'aXZlX3dpbmRvdxgFIAEoDUgDUhRpbml0aWFsUmVjZWl2ZVdpbmRvd4gBARIgCglxdWlja19hY2' + 'sYBiABKAhIBFIIcXVpY2tBY2uIAQESFQoDZWNuGAcgASgISAVSA2VjbogBAUIVChNfY29uZ2Vz' + 'dGlvbl9jb250cm9sQg0KC19ydG9fbWluX21zQhwKGl9pbml0aWFsX2Nvbmdlc3Rpb25fd2luZG' + '93QhkKF19pbml0aWFsX3JlY2VpdmVfd2luZG93QgwKCl9xdWlja19hY2tCBgoEX2Vjbg=='); + +@$core.Deprecated('Use treatmentDescriptor instead') +const Treatment$json = { + '1': 'Treatment', + '2': [ + {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'}, + {'1': 'revision', '3': 2, '4': 1, '5': 4, '10': 'revision'}, + {'1': 'destination', '3': 3, '4': 1, '5': 9, '10': 'destination'}, + { + '1': 'settings', + '3': 4, + '4': 1, + '5': 11, + '6': '.xtcp_treatment_journal.v1.TCPSettings', + '10': 'settings' + }, + { + '1': 'ecn_fabric_verified', + '3': 5, + '4': 1, + '5': 8, + '10': 'ecnFabricVerified' + }, + ], +}; + +/// Descriptor for `Treatment`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List treatmentDescriptor = $convert.base64Decode( + 'CglUcmVhdG1lbnQSDgoCaWQYASABKAlSAmlkEhoKCHJldmlzaW9uGAIgASgEUghyZXZpc2lvbh' + 'IgCgtkZXN0aW5hdGlvbhgDIAEoCVILZGVzdGluYXRpb24SQgoIc2V0dGluZ3MYBCABKAsyJi54' + 'dGNwX3RyZWF0bWVudF9qb3VybmFsLnYxLlRDUFNldHRpbmdzUghzZXR0aW5ncxIuChNlY25fZm' + 'FicmljX3ZlcmlmaWVkGAUgASgIUhFlY25GYWJyaWNWZXJpZmllZA=='); + +@$core.Deprecated('Use assignmentDescriptor instead') +const Assignment$json = { + '1': 'Assignment', + '2': [ + {'1': 'experiment_id', '3': 1, '4': 1, '5': 9, '10': 'experimentId'}, + { + '1': 'treatment', + '3': 2, + '4': 1, + '5': 11, + '6': '.xtcp_treatment_journal.v1.Treatment', + '10': 'treatment' + }, + { + '1': 'mode', + '3': 3, + '4': 1, + '5': 14, + '6': '.xtcp_treatment_journal.v1.AssignmentMode', + '10': 'mode' + }, + { + '1': 'lease_expires_at', + '3': 4, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '10': 'leaseExpiresAt' + }, + { + '1': 'predecessor', + '3': 5, + '4': 1, + '5': 11, + '6': '.xtcp_treatment_journal.v1.Treatment', + '10': 'predecessor' + }, + {'1': 'generation', '3': 6, '4': 1, '5': 4, '10': 'generation'}, + ], +}; + +/// Descriptor for `Assignment`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List assignmentDescriptor = $convert.base64Decode( + 'CgpBc3NpZ25tZW50EiMKDWV4cGVyaW1lbnRfaWQYASABKAlSDGV4cGVyaW1lbnRJZBJCCgl0cm' + 'VhdG1lbnQYAiABKAsyJC54dGNwX3RyZWF0bWVudF9qb3VybmFsLnYxLlRyZWF0bWVudFIJdHJl' + 'YXRtZW50Ej0KBG1vZGUYAyABKA4yKS54dGNwX3RyZWF0bWVudF9qb3VybmFsLnYxLkFzc2lnbm' + '1lbnRNb2RlUgRtb2RlEkQKEGxlYXNlX2V4cGlyZXNfYXQYBCABKAsyGi5nb29nbGUucHJvdG9i' + 'dWYuVGltZXN0YW1wUg5sZWFzZUV4cGlyZXNBdBJGCgtwcmVkZWNlc3NvchgFIAEoCzIkLnh0Y3' + 'BfdHJlYXRtZW50X2pvdXJuYWwudjEuVHJlYXRtZW50UgtwcmVkZWNlc3NvchIeCgpnZW5lcmF0' + 'aW9uGAYgASgEUgpnZW5lcmF0aW9u'); + +@$core.Deprecated('Use desiredDescriptor instead') +const Desired$json = { + '1': 'Desired', + '2': [ + {'1': 'revision', '3': 1, '4': 1, '5': 4, '10': 'revision'}, + {'1': 'suspended', '3': 2, '4': 1, '5': 8, '10': 'suspended'}, + { + '1': 'issued_at', + '3': 3, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '10': 'issuedAt' + }, + { + '1': 'assignments', + '3': 4, + '4': 3, + '5': 11, + '6': '.xtcp_treatment_journal.v1.Assignment', + '10': 'assignments' + }, + ], +}; + +/// Descriptor for `Desired`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List desiredDescriptor = $convert.base64Decode( + 'CgdEZXNpcmVkEhoKCHJldmlzaW9uGAEgASgEUghyZXZpc2lvbhIcCglzdXNwZW5kZWQYAiABKA' + 'hSCXN1c3BlbmRlZBI3Cglpc3N1ZWRfYXQYAyABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0' + 'YW1wUghpc3N1ZWRBdBJHCgthc3NpZ25tZW50cxgEIAMoCzIlLnh0Y3BfdHJlYXRtZW50X2pvdX' + 'JuYWwudjEuQXNzaWdubWVudFILYXNzaWdubWVudHM='); + +@$core.Deprecated('Use recordDescriptor instead') +const Record$json = { + '1': 'Record', + '2': [ + { + '1': 'assignment', + '3': 1, + '4': 1, + '5': 11, + '6': '.xtcp_treatment_journal.v1.Assignment', + '10': 'assignment' + }, + {'1': 'desired_revision', '3': 2, '4': 1, '5': 4, '10': 'desiredRevision'}, + { + '1': 'status', + '3': 3, + '4': 1, + '5': 14, + '6': '.xtcp_treatment_journal.v1.ApplyStatus', + '10': 'status' + }, + { + '1': 'received_at', + '3': 4, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '10': 'receivedAt' + }, + { + '1': 'attempted_at', + '3': 5, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '10': 'attemptedAt' + }, + { + '1': 'applied_at', + '3': 6, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '10': 'appliedAt' + }, + { + '1': 'verified_at', + '3': 7, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '10': 'verifiedAt' + }, + { + '1': 'removed_at', + '3': 8, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '10': 'removedAt' + }, + {'1': 'error', '3': 9, '4': 1, '5': 9, '10': 'error'}, + {'1': 'installed_route', '3': 10, '4': 1, '5': 12, '10': 'installedRoute'}, + { + '1': 'installed_boot_id', + '3': 11, + '4': 1, + '5': 9, + '10': 'installedBootId' + }, + {'1': 'expired_latched', '3': 12, '4': 1, '5': 8, '10': 'expiredLatched'}, + ], +}; + +/// Descriptor for `Record`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List recordDescriptor = $convert.base64Decode( + 'CgZSZWNvcmQSRQoKYXNzaWdubWVudBgBIAEoCzIlLnh0Y3BfdHJlYXRtZW50X2pvdXJuYWwudj' + 'EuQXNzaWdubWVudFIKYXNzaWdubWVudBIpChBkZXNpcmVkX3JldmlzaW9uGAIgASgEUg9kZXNp' + 'cmVkUmV2aXNpb24SPgoGc3RhdHVzGAMgASgOMiYueHRjcF90cmVhdG1lbnRfam91cm5hbC52MS' + '5BcHBseVN0YXR1c1IGc3RhdHVzEjsKC3JlY2VpdmVkX2F0GAQgASgLMhouZ29vZ2xlLnByb3Rv' + 'YnVmLlRpbWVzdGFtcFIKcmVjZWl2ZWRBdBI9CgxhdHRlbXB0ZWRfYXQYBSABKAsyGi5nb29nbG' + 'UucHJvdG9idWYuVGltZXN0YW1wUgthdHRlbXB0ZWRBdBI5CgphcHBsaWVkX2F0GAYgASgLMhou' + 'Z29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcFIJYXBwbGllZEF0EjsKC3ZlcmlmaWVkX2F0GAcgAS' + 'gLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcFIKdmVyaWZpZWRBdBI5CgpyZW1vdmVkX2F0' + 'GAggASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcFIJcmVtb3ZlZEF0EhQKBWVycm9yGA' + 'kgASgJUgVlcnJvchInCg9pbnN0YWxsZWRfcm91dGUYCiABKAxSDmluc3RhbGxlZFJvdXRlEioK' + 'EWluc3RhbGxlZF9ib290X2lkGAsgASgJUg9pbnN0YWxsZWRCb290SWQSJwoPZXhwaXJlZF9sYX' + 'RjaGVkGAwgASgIUg5leHBpcmVkTGF0Y2hlZA=='); + +@$core.Deprecated('Use journalDescriptor instead') +const Journal$json = { + '1': 'Journal', + '2': [ + {'1': 'agent_id', '3': 1, '4': 1, '5': 9, '10': 'agentId'}, + {'1': 'location', '3': 2, '4': 1, '5': 9, '10': 'location'}, + {'1': 'boot_id', '3': 3, '4': 1, '5': 9, '10': 'bootId'}, + { + '1': 'desired', + '3': 4, + '4': 1, + '5': 11, + '6': '.xtcp_treatment_journal.v1.Desired', + '10': 'desired' + }, + { + '1': 'locally_suspended', + '3': 5, + '4': 1, + '5': 8, + '10': 'locallySuspended' + }, + { + '1': 'suspension_reason', + '3': 6, + '4': 1, + '5': 9, + '10': 'suspensionReason' + }, + { + '1': 'records', + '3': 7, + '4': 3, + '5': 11, + '6': '.xtcp_treatment_journal.v1.Record', + '10': 'records' + }, + { + '1': 'updated_at', + '3': 8, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '10': 'updatedAt' + }, + ], +}; + +/// Descriptor for `Journal`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List journalDescriptor = $convert.base64Decode( + 'CgdKb3VybmFsEhkKCGFnZW50X2lkGAEgASgJUgdhZ2VudElkEhoKCGxvY2F0aW9uGAIgASgJUg' + 'hsb2NhdGlvbhIXCgdib290X2lkGAMgASgJUgZib290SWQSPAoHZGVzaXJlZBgEIAEoCzIiLnh0' + 'Y3BfdHJlYXRtZW50X2pvdXJuYWwudjEuRGVzaXJlZFIHZGVzaXJlZBIrChFsb2NhbGx5X3N1c3' + 'BlbmRlZBgFIAEoCFIQbG9jYWxseVN1c3BlbmRlZBIrChFzdXNwZW5zaW9uX3JlYXNvbhgGIAEo' + 'CVIQc3VzcGVuc2lvblJlYXNvbhI7CgdyZWNvcmRzGAcgAygLMiEueHRjcF90cmVhdG1lbnRfam' + '91cm5hbC52MS5SZWNvcmRSB3JlY29yZHMSOQoKdXBkYXRlZF9hdBgIIAEoCzIaLmdvb2dsZS5w' + 'cm90b2J1Zi5UaW1lc3RhbXBSCXVwZGF0ZWRBdA=='); diff --git a/gen/go/xtcp_treatment_journal/journal.pb.go b/gen/go/xtcp_treatment_journal/journal.pb.go new file mode 100644 index 000000000..d5c35f61a --- /dev/null +++ b/gen/go/xtcp_treatment_journal/journal.pb.go @@ -0,0 +1,885 @@ +// +// xTCP - treatment journal +// +// The host-side intent journal of the route treatment agent: the desired +// state last accepted from the controller, the local hold, and one record per +// assignment with the exact kernel route bytes that were installed. It is a +// FILE FORMAT, read and written only on the host by the agent, the watchdog +// and the local treatmentctl commands, and never an RPC payload. +// +// It is a separate proto package from xtcp_treatment.v1 on purpose: the host +// side (pkg/treatment/reconcile, the watchdog) must be able to read and write +// the journal without linking the gRPC service stubs that share a Go package +// with the RPC messages. The messages below therefore repeat the treatment +// shape rather than importing it; pkg/treatment/reconcile converts them to +// and from the domain types, and the domain validators run on every load. +// +// Values of the two enums mirror xtcp_treatment.v1 one-for-one and the domain +// enums in pkg/treatment; reconcile's tests pin the correspondence. +// +// See docs/tcp-controller.md, "Persistence, restart and withdrawal". + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.12 +// protoc (unknown) +// source: xtcp_treatment_journal/v1/journal.proto + +package xtcp_treatment_journal + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type AssignmentMode int32 + +const ( + AssignmentMode_ASSIGNMENT_MODE_UNSPECIFIED AssignmentMode = 0 + AssignmentMode_ASSIGNMENT_MODE_TRIAL AssignmentMode = 1 + AssignmentMode_ASSIGNMENT_MODE_STABLE AssignmentMode = 2 + AssignmentMode_ASSIGNMENT_MODE_SUSPENDED AssignmentMode = 3 + AssignmentMode_ASSIGNMENT_MODE_RETIRED AssignmentMode = 4 +) + +// Enum value maps for AssignmentMode. +var ( + AssignmentMode_name = map[int32]string{ + 0: "ASSIGNMENT_MODE_UNSPECIFIED", + 1: "ASSIGNMENT_MODE_TRIAL", + 2: "ASSIGNMENT_MODE_STABLE", + 3: "ASSIGNMENT_MODE_SUSPENDED", + 4: "ASSIGNMENT_MODE_RETIRED", + } + AssignmentMode_value = map[string]int32{ + "ASSIGNMENT_MODE_UNSPECIFIED": 0, + "ASSIGNMENT_MODE_TRIAL": 1, + "ASSIGNMENT_MODE_STABLE": 2, + "ASSIGNMENT_MODE_SUSPENDED": 3, + "ASSIGNMENT_MODE_RETIRED": 4, + } +) + +func (x AssignmentMode) Enum() *AssignmentMode { + p := new(AssignmentMode) + *p = x + return p +} + +func (x AssignmentMode) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (AssignmentMode) Descriptor() protoreflect.EnumDescriptor { + return file_xtcp_treatment_journal_v1_journal_proto_enumTypes[0].Descriptor() +} + +func (AssignmentMode) Type() protoreflect.EnumType { + return &file_xtcp_treatment_journal_v1_journal_proto_enumTypes[0] +} + +func (x AssignmentMode) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use AssignmentMode.Descriptor instead. +func (AssignmentMode) EnumDescriptor() ([]byte, []int) { + return file_xtcp_treatment_journal_v1_journal_proto_rawDescGZIP(), []int{0} +} + +type ApplyStatus int32 + +const ( + ApplyStatus_APPLY_STATUS_UNSPECIFIED ApplyStatus = 0 + ApplyStatus_APPLY_STATUS_PENDING ApplyStatus = 1 + ApplyStatus_APPLY_STATUS_VERIFIED ApplyStatus = 2 + ApplyStatus_APPLY_STATUS_REMOVED ApplyStatus = 3 + ApplyStatus_APPLY_STATUS_EXPIRED ApplyStatus = 4 + ApplyStatus_APPLY_STATUS_FAILED ApplyStatus = 5 +) + +// Enum value maps for ApplyStatus. +var ( + ApplyStatus_name = map[int32]string{ + 0: "APPLY_STATUS_UNSPECIFIED", + 1: "APPLY_STATUS_PENDING", + 2: "APPLY_STATUS_VERIFIED", + 3: "APPLY_STATUS_REMOVED", + 4: "APPLY_STATUS_EXPIRED", + 5: "APPLY_STATUS_FAILED", + } + ApplyStatus_value = map[string]int32{ + "APPLY_STATUS_UNSPECIFIED": 0, + "APPLY_STATUS_PENDING": 1, + "APPLY_STATUS_VERIFIED": 2, + "APPLY_STATUS_REMOVED": 3, + "APPLY_STATUS_EXPIRED": 4, + "APPLY_STATUS_FAILED": 5, + } +) + +func (x ApplyStatus) Enum() *ApplyStatus { + p := new(ApplyStatus) + *p = x + return p +} + +func (x ApplyStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ApplyStatus) Descriptor() protoreflect.EnumDescriptor { + return file_xtcp_treatment_journal_v1_journal_proto_enumTypes[1].Descriptor() +} + +func (ApplyStatus) Type() protoreflect.EnumType { + return &file_xtcp_treatment_journal_v1_journal_proto_enumTypes[1] +} + +func (x ApplyStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ApplyStatus.Descriptor instead. +func (ApplyStatus) EnumDescriptor() ([]byte, []int) { + return file_xtcp_treatment_journal_v1_journal_proto_rawDescGZIP(), []int{1} +} + +// TCPSettings mirrors xtcp_treatment.v1.TCPSettings; presence is meaningful. +type TCPSettings struct { + state protoimpl.MessageState `protogen:"open.v1"` + CongestionControl *string `protobuf:"bytes,1,opt,name=congestion_control,json=congestionControl,proto3,oneof" json:"congestion_control,omitempty"` + CongestionLock bool `protobuf:"varint,2,opt,name=congestion_lock,json=congestionLock,proto3" json:"congestion_lock,omitempty"` + RtoMinMs *uint32 `protobuf:"varint,3,opt,name=rto_min_ms,json=rtoMinMs,proto3,oneof" json:"rto_min_ms,omitempty"` + InitialCongestionWindow *uint32 `protobuf:"varint,4,opt,name=initial_congestion_window,json=initialCongestionWindow,proto3,oneof" json:"initial_congestion_window,omitempty"` + InitialReceiveWindow *uint32 `protobuf:"varint,5,opt,name=initial_receive_window,json=initialReceiveWindow,proto3,oneof" json:"initial_receive_window,omitempty"` + QuickAck *bool `protobuf:"varint,6,opt,name=quick_ack,json=quickAck,proto3,oneof" json:"quick_ack,omitempty"` + Ecn *bool `protobuf:"varint,7,opt,name=ecn,proto3,oneof" json:"ecn,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TCPSettings) Reset() { + *x = TCPSettings{} + mi := &file_xtcp_treatment_journal_v1_journal_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TCPSettings) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TCPSettings) ProtoMessage() {} + +func (x *TCPSettings) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_journal_v1_journal_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TCPSettings.ProtoReflect.Descriptor instead. +func (*TCPSettings) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_journal_v1_journal_proto_rawDescGZIP(), []int{0} +} + +func (x *TCPSettings) GetCongestionControl() string { + if x != nil && x.CongestionControl != nil { + return *x.CongestionControl + } + return "" +} + +func (x *TCPSettings) GetCongestionLock() bool { + if x != nil { + return x.CongestionLock + } + return false +} + +func (x *TCPSettings) GetRtoMinMs() uint32 { + if x != nil && x.RtoMinMs != nil { + return *x.RtoMinMs + } + return 0 +} + +func (x *TCPSettings) GetInitialCongestionWindow() uint32 { + if x != nil && x.InitialCongestionWindow != nil { + return *x.InitialCongestionWindow + } + return 0 +} + +func (x *TCPSettings) GetInitialReceiveWindow() uint32 { + if x != nil && x.InitialReceiveWindow != nil { + return *x.InitialReceiveWindow + } + return 0 +} + +func (x *TCPSettings) GetQuickAck() bool { + if x != nil && x.QuickAck != nil { + return *x.QuickAck + } + return false +} + +func (x *TCPSettings) GetEcn() bool { + if x != nil && x.Ecn != nil { + return *x.Ecn + } + return false +} + +type Treatment struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Revision uint64 `protobuf:"varint,2,opt,name=revision,proto3" json:"revision,omitempty"` + // Canonical /32 or /128 host prefix. + Destination string `protobuf:"bytes,3,opt,name=destination,proto3" json:"destination,omitempty"` + Settings *TCPSettings `protobuf:"bytes,4,opt,name=settings,proto3" json:"settings,omitempty"` + EcnFabricVerified bool `protobuf:"varint,5,opt,name=ecn_fabric_verified,json=ecnFabricVerified,proto3" json:"ecn_fabric_verified,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Treatment) Reset() { + *x = Treatment{} + mi := &file_xtcp_treatment_journal_v1_journal_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Treatment) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Treatment) ProtoMessage() {} + +func (x *Treatment) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_journal_v1_journal_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Treatment.ProtoReflect.Descriptor instead. +func (*Treatment) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_journal_v1_journal_proto_rawDescGZIP(), []int{1} +} + +func (x *Treatment) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Treatment) GetRevision() uint64 { + if x != nil { + return x.Revision + } + return 0 +} + +func (x *Treatment) GetDestination() string { + if x != nil { + return x.Destination + } + return "" +} + +func (x *Treatment) GetSettings() *TCPSettings { + if x != nil { + return x.Settings + } + return nil +} + +func (x *Treatment) GetEcnFabricVerified() bool { + if x != nil { + return x.EcnFabricVerified + } + return false +} + +type Assignment struct { + state protoimpl.MessageState `protogen:"open.v1"` + ExperimentId string `protobuf:"bytes,1,opt,name=experiment_id,json=experimentId,proto3" json:"experiment_id,omitempty"` + Treatment *Treatment `protobuf:"bytes,2,opt,name=treatment,proto3" json:"treatment,omitempty"` + Mode AssignmentMode `protobuf:"varint,3,opt,name=mode,proto3,enum=xtcp_treatment_journal.v1.AssignmentMode" json:"mode,omitempty"` + LeaseExpiresAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=lease_expires_at,json=leaseExpiresAt,proto3" json:"lease_expires_at,omitempty"` + Predecessor *Treatment `protobuf:"bytes,5,opt,name=predecessor,proto3" json:"predecessor,omitempty"` + Generation uint64 `protobuf:"varint,6,opt,name=generation,proto3" json:"generation,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Assignment) Reset() { + *x = Assignment{} + mi := &file_xtcp_treatment_journal_v1_journal_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Assignment) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Assignment) ProtoMessage() {} + +func (x *Assignment) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_journal_v1_journal_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Assignment.ProtoReflect.Descriptor instead. +func (*Assignment) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_journal_v1_journal_proto_rawDescGZIP(), []int{2} +} + +func (x *Assignment) GetExperimentId() string { + if x != nil { + return x.ExperimentId + } + return "" +} + +func (x *Assignment) GetTreatment() *Treatment { + if x != nil { + return x.Treatment + } + return nil +} + +func (x *Assignment) GetMode() AssignmentMode { + if x != nil { + return x.Mode + } + return AssignmentMode_ASSIGNMENT_MODE_UNSPECIFIED +} + +func (x *Assignment) GetLeaseExpiresAt() *timestamppb.Timestamp { + if x != nil { + return x.LeaseExpiresAt + } + return nil +} + +func (x *Assignment) GetPredecessor() *Treatment { + if x != nil { + return x.Predecessor + } + return nil +} + +func (x *Assignment) GetGeneration() uint64 { + if x != nil { + return x.Generation + } + return 0 +} + +// Desired is the controller's last accepted snapshot for this agent. The +// agent identity lives on the Journal header. +type Desired struct { + state protoimpl.MessageState `protogen:"open.v1"` + Revision uint64 `protobuf:"varint,1,opt,name=revision,proto3" json:"revision,omitempty"` + Suspended bool `protobuf:"varint,2,opt,name=suspended,proto3" json:"suspended,omitempty"` + IssuedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=issued_at,json=issuedAt,proto3" json:"issued_at,omitempty"` + Assignments []*Assignment `protobuf:"bytes,4,rep,name=assignments,proto3" json:"assignments,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Desired) Reset() { + *x = Desired{} + mi := &file_xtcp_treatment_journal_v1_journal_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Desired) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Desired) ProtoMessage() {} + +func (x *Desired) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_journal_v1_journal_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Desired.ProtoReflect.Descriptor instead. +func (*Desired) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_journal_v1_journal_proto_rawDescGZIP(), []int{3} +} + +func (x *Desired) GetRevision() uint64 { + if x != nil { + return x.Revision + } + return 0 +} + +func (x *Desired) GetSuspended() bool { + if x != nil { + return x.Suspended + } + return false +} + +func (x *Desired) GetIssuedAt() *timestamppb.Timestamp { + if x != nil { + return x.IssuedAt + } + return nil +} + +func (x *Desired) GetAssignments() []*Assignment { + if x != nil { + return x.Assignments + } + return nil +} + +// Record is the agent's account of one assignment plus the kernel-local +// facts (installed route bytes, installing boot) that never leave the host. +type Record struct { + state protoimpl.MessageState `protogen:"open.v1"` + Assignment *Assignment `protobuf:"bytes,1,opt,name=assignment,proto3" json:"assignment,omitempty"` + DesiredRevision uint64 `protobuf:"varint,2,opt,name=desired_revision,json=desiredRevision,proto3" json:"desired_revision,omitempty"` + Status ApplyStatus `protobuf:"varint,3,opt,name=status,proto3,enum=xtcp_treatment_journal.v1.ApplyStatus" json:"status,omitempty"` + ReceivedAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=received_at,json=receivedAt,proto3" json:"received_at,omitempty"` + AttemptedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=attempted_at,json=attemptedAt,proto3" json:"attempted_at,omitempty"` + AppliedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=applied_at,json=appliedAt,proto3" json:"applied_at,omitempty"` + VerifiedAt *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=verified_at,json=verifiedAt,proto3" json:"verified_at,omitempty"` + RemovedAt *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=removed_at,json=removedAt,proto3" json:"removed_at,omitempty"` + Error string `protobuf:"bytes,9,opt,name=error,proto3" json:"error,omitempty"` + // Locally planned canonical netlink route body, the only thing Remove will + // ever be asked to withdraw. + InstalledRoute []byte `protobuf:"bytes,10,opt,name=installed_route,json=installedRoute,proto3" json:"installed_route,omitempty"` + InstalledBootId string `protobuf:"bytes,11,opt,name=installed_boot_id,json=installedBootId,proto3" json:"installed_boot_id,omitempty"` + ExpiredLatched bool `protobuf:"varint,12,opt,name=expired_latched,json=expiredLatched,proto3" json:"expired_latched,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Record) Reset() { + *x = Record{} + mi := &file_xtcp_treatment_journal_v1_journal_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Record) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Record) ProtoMessage() {} + +func (x *Record) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_journal_v1_journal_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Record.ProtoReflect.Descriptor instead. +func (*Record) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_journal_v1_journal_proto_rawDescGZIP(), []int{4} +} + +func (x *Record) GetAssignment() *Assignment { + if x != nil { + return x.Assignment + } + return nil +} + +func (x *Record) GetDesiredRevision() uint64 { + if x != nil { + return x.DesiredRevision + } + return 0 +} + +func (x *Record) GetStatus() ApplyStatus { + if x != nil { + return x.Status + } + return ApplyStatus_APPLY_STATUS_UNSPECIFIED +} + +func (x *Record) GetReceivedAt() *timestamppb.Timestamp { + if x != nil { + return x.ReceivedAt + } + return nil +} + +func (x *Record) GetAttemptedAt() *timestamppb.Timestamp { + if x != nil { + return x.AttemptedAt + } + return nil +} + +func (x *Record) GetAppliedAt() *timestamppb.Timestamp { + if x != nil { + return x.AppliedAt + } + return nil +} + +func (x *Record) GetVerifiedAt() *timestamppb.Timestamp { + if x != nil { + return x.VerifiedAt + } + return nil +} + +func (x *Record) GetRemovedAt() *timestamppb.Timestamp { + if x != nil { + return x.RemovedAt + } + return nil +} + +func (x *Record) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +func (x *Record) GetInstalledRoute() []byte { + if x != nil { + return x.InstalledRoute + } + return nil +} + +func (x *Record) GetInstalledBootId() string { + if x != nil { + return x.InstalledBootId + } + return "" +} + +func (x *Record) GetExpiredLatched() bool { + if x != nil { + return x.ExpiredLatched + } + return false +} + +// Journal is the atomic snapshot in /state.textproto. The header +// identifies the writer so a journal can never be reconciled for another +// agent and so a telemetry-only daemon can attribute records from it. +type Journal struct { + state protoimpl.MessageState `protogen:"open.v1"` + AgentId string `protobuf:"bytes,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` + Location string `protobuf:"bytes,2,opt,name=location,proto3" json:"location,omitempty"` + // Boot that last wrote the journal; per-record installed_boot_id says + // which boot installed each route. + BootId string `protobuf:"bytes,3,opt,name=boot_id,json=bootId,proto3" json:"boot_id,omitempty"` + Desired *Desired `protobuf:"bytes,4,opt,name=desired,proto3" json:"desired,omitempty"` + LocallySuspended bool `protobuf:"varint,5,opt,name=locally_suspended,json=locallySuspended,proto3" json:"locally_suspended,omitempty"` + SuspensionReason string `protobuf:"bytes,6,opt,name=suspension_reason,json=suspensionReason,proto3" json:"suspension_reason,omitempty"` + Records []*Record `protobuf:"bytes,7,rep,name=records,proto3" json:"records,omitempty"` + UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Journal) Reset() { + *x = Journal{} + mi := &file_xtcp_treatment_journal_v1_journal_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Journal) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Journal) ProtoMessage() {} + +func (x *Journal) ProtoReflect() protoreflect.Message { + mi := &file_xtcp_treatment_journal_v1_journal_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Journal.ProtoReflect.Descriptor instead. +func (*Journal) Descriptor() ([]byte, []int) { + return file_xtcp_treatment_journal_v1_journal_proto_rawDescGZIP(), []int{5} +} + +func (x *Journal) GetAgentId() string { + if x != nil { + return x.AgentId + } + return "" +} + +func (x *Journal) GetLocation() string { + if x != nil { + return x.Location + } + return "" +} + +func (x *Journal) GetBootId() string { + if x != nil { + return x.BootId + } + return "" +} + +func (x *Journal) GetDesired() *Desired { + if x != nil { + return x.Desired + } + return nil +} + +func (x *Journal) GetLocallySuspended() bool { + if x != nil { + return x.LocallySuspended + } + return false +} + +func (x *Journal) GetSuspensionReason() string { + if x != nil { + return x.SuspensionReason + } + return "" +} + +func (x *Journal) GetRecords() []*Record { + if x != nil { + return x.Records + } + return nil +} + +func (x *Journal) GetUpdatedAt() *timestamppb.Timestamp { + if x != nil { + return x.UpdatedAt + } + return nil +} + +var File_xtcp_treatment_journal_v1_journal_proto protoreflect.FileDescriptor + +const file_xtcp_treatment_journal_v1_journal_proto_rawDesc = "" + + "\n" + + "'xtcp_treatment_journal/v1/journal.proto\x12\x19xtcp_treatment_journal.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"\xb7\x03\n" + + "\vTCPSettings\x122\n" + + "\x12congestion_control\x18\x01 \x01(\tH\x00R\x11congestionControl\x88\x01\x01\x12'\n" + + "\x0fcongestion_lock\x18\x02 \x01(\bR\x0econgestionLock\x12!\n" + + "\n" + + "rto_min_ms\x18\x03 \x01(\rH\x01R\brtoMinMs\x88\x01\x01\x12?\n" + + "\x19initial_congestion_window\x18\x04 \x01(\rH\x02R\x17initialCongestionWindow\x88\x01\x01\x129\n" + + "\x16initial_receive_window\x18\x05 \x01(\rH\x03R\x14initialReceiveWindow\x88\x01\x01\x12 \n" + + "\tquick_ack\x18\x06 \x01(\bH\x04R\bquickAck\x88\x01\x01\x12\x15\n" + + "\x03ecn\x18\a \x01(\bH\x05R\x03ecn\x88\x01\x01B\x15\n" + + "\x13_congestion_controlB\r\n" + + "\v_rto_min_msB\x1c\n" + + "\x1a_initial_congestion_windowB\x19\n" + + "\x17_initial_receive_windowB\f\n" + + "\n" + + "_quick_ackB\x06\n" + + "\x04_ecn\"\xcd\x01\n" + + "\tTreatment\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1a\n" + + "\brevision\x18\x02 \x01(\x04R\brevision\x12 \n" + + "\vdestination\x18\x03 \x01(\tR\vdestination\x12B\n" + + "\bsettings\x18\x04 \x01(\v2&.xtcp_treatment_journal.v1.TCPSettingsR\bsettings\x12.\n" + + "\x13ecn_fabric_verified\x18\x05 \x01(\bR\x11ecnFabricVerified\"\xe2\x02\n" + + "\n" + + "Assignment\x12#\n" + + "\rexperiment_id\x18\x01 \x01(\tR\fexperimentId\x12B\n" + + "\ttreatment\x18\x02 \x01(\v2$.xtcp_treatment_journal.v1.TreatmentR\ttreatment\x12=\n" + + "\x04mode\x18\x03 \x01(\x0e2).xtcp_treatment_journal.v1.AssignmentModeR\x04mode\x12D\n" + + "\x10lease_expires_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\x0eleaseExpiresAt\x12F\n" + + "\vpredecessor\x18\x05 \x01(\v2$.xtcp_treatment_journal.v1.TreatmentR\vpredecessor\x12\x1e\n" + + "\n" + + "generation\x18\x06 \x01(\x04R\n" + + "generation\"\xc5\x01\n" + + "\aDesired\x12\x1a\n" + + "\brevision\x18\x01 \x01(\x04R\brevision\x12\x1c\n" + + "\tsuspended\x18\x02 \x01(\bR\tsuspended\x127\n" + + "\tissued_at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\bissuedAt\x12G\n" + + "\vassignments\x18\x04 \x03(\v2%.xtcp_treatment_journal.v1.AssignmentR\vassignments\"\xfd\x04\n" + + "\x06Record\x12E\n" + + "\n" + + "assignment\x18\x01 \x01(\v2%.xtcp_treatment_journal.v1.AssignmentR\n" + + "assignment\x12)\n" + + "\x10desired_revision\x18\x02 \x01(\x04R\x0fdesiredRevision\x12>\n" + + "\x06status\x18\x03 \x01(\x0e2&.xtcp_treatment_journal.v1.ApplyStatusR\x06status\x12;\n" + + "\vreceived_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\n" + + "receivedAt\x12=\n" + + "\fattempted_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\vattemptedAt\x129\n" + + "\n" + + "applied_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\tappliedAt\x12;\n" + + "\vverified_at\x18\a \x01(\v2\x1a.google.protobuf.TimestampR\n" + + "verifiedAt\x129\n" + + "\n" + + "removed_at\x18\b \x01(\v2\x1a.google.protobuf.TimestampR\tremovedAt\x12\x14\n" + + "\x05error\x18\t \x01(\tR\x05error\x12'\n" + + "\x0finstalled_route\x18\n" + + " \x01(\fR\x0einstalledRoute\x12*\n" + + "\x11installed_boot_id\x18\v \x01(\tR\x0finstalledBootId\x12'\n" + + "\x0fexpired_latched\x18\f \x01(\bR\x0eexpiredLatched\"\xe9\x02\n" + + "\aJournal\x12\x19\n" + + "\bagent_id\x18\x01 \x01(\tR\aagentId\x12\x1a\n" + + "\blocation\x18\x02 \x01(\tR\blocation\x12\x17\n" + + "\aboot_id\x18\x03 \x01(\tR\x06bootId\x12<\n" + + "\adesired\x18\x04 \x01(\v2\".xtcp_treatment_journal.v1.DesiredR\adesired\x12+\n" + + "\x11locally_suspended\x18\x05 \x01(\bR\x10locallySuspended\x12+\n" + + "\x11suspension_reason\x18\x06 \x01(\tR\x10suspensionReason\x12;\n" + + "\arecords\x18\a \x03(\v2!.xtcp_treatment_journal.v1.RecordR\arecords\x129\n" + + "\n" + + "updated_at\x18\b \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt*\xa4\x01\n" + + "\x0eAssignmentMode\x12\x1f\n" + + "\x1bASSIGNMENT_MODE_UNSPECIFIED\x10\x00\x12\x19\n" + + "\x15ASSIGNMENT_MODE_TRIAL\x10\x01\x12\x1a\n" + + "\x16ASSIGNMENT_MODE_STABLE\x10\x02\x12\x1d\n" + + "\x19ASSIGNMENT_MODE_SUSPENDED\x10\x03\x12\x1b\n" + + "\x17ASSIGNMENT_MODE_RETIRED\x10\x04*\xad\x01\n" + + "\vApplyStatus\x12\x1c\n" + + "\x18APPLY_STATUS_UNSPECIFIED\x10\x00\x12\x18\n" + + "\x14APPLY_STATUS_PENDING\x10\x01\x12\x19\n" + + "\x15APPLY_STATUS_VERIFIED\x10\x02\x12\x18\n" + + "\x14APPLY_STATUS_REMOVED\x10\x03\x12\x18\n" + + "\x14APPLY_STATUS_EXPIRED\x10\x04\x12\x17\n" + + "\x13APPLY_STATUS_FAILED\x10\x05B\xea\x01\n" + + "\x1dcom.xtcp_treatment_journal.v1B\fJournalProtoP\x01Z>github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment_journal\xa2\x02\x03XXX\xaa\x02\x17XtcpTreatmentJournal.V1\xca\x02\x17XtcpTreatmentJournal\\V1\xe2\x02#XtcpTreatmentJournal\\V1\\GPBMetadata\xea\x02\x18XtcpTreatmentJournal::V1b\x06proto3" + +var ( + file_xtcp_treatment_journal_v1_journal_proto_rawDescOnce sync.Once + file_xtcp_treatment_journal_v1_journal_proto_rawDescData []byte +) + +func file_xtcp_treatment_journal_v1_journal_proto_rawDescGZIP() []byte { + file_xtcp_treatment_journal_v1_journal_proto_rawDescOnce.Do(func() { + file_xtcp_treatment_journal_v1_journal_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_xtcp_treatment_journal_v1_journal_proto_rawDesc), len(file_xtcp_treatment_journal_v1_journal_proto_rawDesc))) + }) + return file_xtcp_treatment_journal_v1_journal_proto_rawDescData +} + +var file_xtcp_treatment_journal_v1_journal_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_xtcp_treatment_journal_v1_journal_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_xtcp_treatment_journal_v1_journal_proto_goTypes = []any{ + (AssignmentMode)(0), // 0: xtcp_treatment_journal.v1.AssignmentMode + (ApplyStatus)(0), // 1: xtcp_treatment_journal.v1.ApplyStatus + (*TCPSettings)(nil), // 2: xtcp_treatment_journal.v1.TCPSettings + (*Treatment)(nil), // 3: xtcp_treatment_journal.v1.Treatment + (*Assignment)(nil), // 4: xtcp_treatment_journal.v1.Assignment + (*Desired)(nil), // 5: xtcp_treatment_journal.v1.Desired + (*Record)(nil), // 6: xtcp_treatment_journal.v1.Record + (*Journal)(nil), // 7: xtcp_treatment_journal.v1.Journal + (*timestamppb.Timestamp)(nil), // 8: google.protobuf.Timestamp +} +var file_xtcp_treatment_journal_v1_journal_proto_depIdxs = []int32{ + 2, // 0: xtcp_treatment_journal.v1.Treatment.settings:type_name -> xtcp_treatment_journal.v1.TCPSettings + 3, // 1: xtcp_treatment_journal.v1.Assignment.treatment:type_name -> xtcp_treatment_journal.v1.Treatment + 0, // 2: xtcp_treatment_journal.v1.Assignment.mode:type_name -> xtcp_treatment_journal.v1.AssignmentMode + 8, // 3: xtcp_treatment_journal.v1.Assignment.lease_expires_at:type_name -> google.protobuf.Timestamp + 3, // 4: xtcp_treatment_journal.v1.Assignment.predecessor:type_name -> xtcp_treatment_journal.v1.Treatment + 8, // 5: xtcp_treatment_journal.v1.Desired.issued_at:type_name -> google.protobuf.Timestamp + 4, // 6: xtcp_treatment_journal.v1.Desired.assignments:type_name -> xtcp_treatment_journal.v1.Assignment + 4, // 7: xtcp_treatment_journal.v1.Record.assignment:type_name -> xtcp_treatment_journal.v1.Assignment + 1, // 8: xtcp_treatment_journal.v1.Record.status:type_name -> xtcp_treatment_journal.v1.ApplyStatus + 8, // 9: xtcp_treatment_journal.v1.Record.received_at:type_name -> google.protobuf.Timestamp + 8, // 10: xtcp_treatment_journal.v1.Record.attempted_at:type_name -> google.protobuf.Timestamp + 8, // 11: xtcp_treatment_journal.v1.Record.applied_at:type_name -> google.protobuf.Timestamp + 8, // 12: xtcp_treatment_journal.v1.Record.verified_at:type_name -> google.protobuf.Timestamp + 8, // 13: xtcp_treatment_journal.v1.Record.removed_at:type_name -> google.protobuf.Timestamp + 5, // 14: xtcp_treatment_journal.v1.Journal.desired:type_name -> xtcp_treatment_journal.v1.Desired + 6, // 15: xtcp_treatment_journal.v1.Journal.records:type_name -> xtcp_treatment_journal.v1.Record + 8, // 16: xtcp_treatment_journal.v1.Journal.updated_at:type_name -> google.protobuf.Timestamp + 17, // [17:17] is the sub-list for method output_type + 17, // [17:17] is the sub-list for method input_type + 17, // [17:17] is the sub-list for extension type_name + 17, // [17:17] is the sub-list for extension extendee + 0, // [0:17] is the sub-list for field type_name +} + +func init() { file_xtcp_treatment_journal_v1_journal_proto_init() } +func file_xtcp_treatment_journal_v1_journal_proto_init() { + if File_xtcp_treatment_journal_v1_journal_proto != nil { + return + } + file_xtcp_treatment_journal_v1_journal_proto_msgTypes[0].OneofWrappers = []any{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_xtcp_treatment_journal_v1_journal_proto_rawDesc), len(file_xtcp_treatment_journal_v1_journal_proto_rawDesc)), + NumEnums: 2, + NumMessages: 6, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_xtcp_treatment_journal_v1_journal_proto_goTypes, + DependencyIndexes: file_xtcp_treatment_journal_v1_journal_proto_depIdxs, + EnumInfos: file_xtcp_treatment_journal_v1_journal_proto_enumTypes, + MessageInfos: file_xtcp_treatment_journal_v1_journal_proto_msgTypes, + }.Build() + File_xtcp_treatment_journal_v1_journal_proto = out.File + file_xtcp_treatment_journal_v1_journal_proto_goTypes = nil + file_xtcp_treatment_journal_v1_journal_proto_depIdxs = nil +} diff --git a/gen/go/xtcp_treatment_journal/journal_vtproto.pb.go b/gen/go/xtcp_treatment_journal/journal_vtproto.pb.go new file mode 100644 index 000000000..a57d3d035 --- /dev/null +++ b/gen/go/xtcp_treatment_journal/journal_vtproto.pb.go @@ -0,0 +1,2295 @@ +// Code generated by protoc-gen-go-vtproto. DO NOT EDIT. +// protoc-gen-go-vtproto version: (devel) +// source: xtcp_treatment_journal/v1/journal.proto + +package xtcp_treatment_journal + +import ( + fmt "fmt" + protohelpers "github.com/planetscale/vtprotobuf/protohelpers" + timestamppb "github.com/planetscale/vtprotobuf/types/known/timestamppb" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb1 "google.golang.org/protobuf/types/known/timestamppb" + io "io" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +func (m *TCPSettings) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TCPSettings) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *TCPSettings) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.Ecn != nil { + i-- + if *m.Ecn { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x38 + } + if m.QuickAck != nil { + i-- + if *m.QuickAck { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x30 + } + if m.InitialReceiveWindow != nil { + i = protohelpers.EncodeVarint(dAtA, i, uint64(*m.InitialReceiveWindow)) + i-- + dAtA[i] = 0x28 + } + if m.InitialCongestionWindow != nil { + i = protohelpers.EncodeVarint(dAtA, i, uint64(*m.InitialCongestionWindow)) + i-- + dAtA[i] = 0x20 + } + if m.RtoMinMs != nil { + i = protohelpers.EncodeVarint(dAtA, i, uint64(*m.RtoMinMs)) + i-- + dAtA[i] = 0x18 + } + if m.CongestionLock { + i-- + if m.CongestionLock { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x10 + } + if m.CongestionControl != nil { + i -= len(*m.CongestionControl) + copy(dAtA[i:], *m.CongestionControl) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(*m.CongestionControl))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Treatment) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Treatment) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *Treatment) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.EcnFabricVerified { + i-- + if m.EcnFabricVerified { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x28 + } + if m.Settings != nil { + size, err := m.Settings.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x22 + } + if len(m.Destination) > 0 { + i -= len(m.Destination) + copy(dAtA[i:], m.Destination) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Destination))) + i-- + dAtA[i] = 0x1a + } + if m.Revision != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Revision)) + i-- + dAtA[i] = 0x10 + } + if len(m.Id) > 0 { + i -= len(m.Id) + copy(dAtA[i:], m.Id) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Id))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Assignment) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Assignment) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *Assignment) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.Generation != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Generation)) + i-- + dAtA[i] = 0x30 + } + if m.Predecessor != nil { + size, err := m.Predecessor.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x2a + } + if m.LeaseExpiresAt != nil { + size, err := (*timestamppb.Timestamp)(m.LeaseExpiresAt).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x22 + } + if m.Mode != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Mode)) + i-- + dAtA[i] = 0x18 + } + if m.Treatment != nil { + size, err := m.Treatment.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x12 + } + if len(m.ExperimentId) > 0 { + i -= len(m.ExperimentId) + copy(dAtA[i:], m.ExperimentId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.ExperimentId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Desired) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Desired) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *Desired) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.Assignments) > 0 { + for iNdEx := len(m.Assignments) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.Assignments[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x22 + } + } + if m.IssuedAt != nil { + size, err := (*timestamppb.Timestamp)(m.IssuedAt).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x1a + } + if m.Suspended { + i-- + if m.Suspended { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x10 + } + if m.Revision != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Revision)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *Record) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Record) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *Record) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.ExpiredLatched { + i-- + if m.ExpiredLatched { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x60 + } + if len(m.InstalledBootId) > 0 { + i -= len(m.InstalledBootId) + copy(dAtA[i:], m.InstalledBootId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.InstalledBootId))) + i-- + dAtA[i] = 0x5a + } + if len(m.InstalledRoute) > 0 { + i -= len(m.InstalledRoute) + copy(dAtA[i:], m.InstalledRoute) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.InstalledRoute))) + i-- + dAtA[i] = 0x52 + } + if len(m.Error) > 0 { + i -= len(m.Error) + copy(dAtA[i:], m.Error) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Error))) + i-- + dAtA[i] = 0x4a + } + if m.RemovedAt != nil { + size, err := (*timestamppb.Timestamp)(m.RemovedAt).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x42 + } + if m.VerifiedAt != nil { + size, err := (*timestamppb.Timestamp)(m.VerifiedAt).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x3a + } + if m.AppliedAt != nil { + size, err := (*timestamppb.Timestamp)(m.AppliedAt).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x32 + } + if m.AttemptedAt != nil { + size, err := (*timestamppb.Timestamp)(m.AttemptedAt).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x2a + } + if m.ReceivedAt != nil { + size, err := (*timestamppb.Timestamp)(m.ReceivedAt).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x22 + } + if m.Status != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Status)) + i-- + dAtA[i] = 0x18 + } + if m.DesiredRevision != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.DesiredRevision)) + i-- + dAtA[i] = 0x10 + } + if m.Assignment != nil { + size, err := m.Assignment.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Journal) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Journal) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *Journal) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.UpdatedAt != nil { + size, err := (*timestamppb.Timestamp)(m.UpdatedAt).MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x42 + } + if len(m.Records) > 0 { + for iNdEx := len(m.Records) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.Records[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x3a + } + } + if len(m.SuspensionReason) > 0 { + i -= len(m.SuspensionReason) + copy(dAtA[i:], m.SuspensionReason) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.SuspensionReason))) + i-- + dAtA[i] = 0x32 + } + if m.LocallySuspended { + i-- + if m.LocallySuspended { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x28 + } + if m.Desired != nil { + size, err := m.Desired.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x22 + } + if len(m.BootId) > 0 { + i -= len(m.BootId) + copy(dAtA[i:], m.BootId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.BootId))) + i-- + dAtA[i] = 0x1a + } + if len(m.Location) > 0 { + i -= len(m.Location) + copy(dAtA[i:], m.Location) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Location))) + i-- + dAtA[i] = 0x12 + } + if len(m.AgentId) > 0 { + i -= len(m.AgentId) + copy(dAtA[i:], m.AgentId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.AgentId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *TCPSettings) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.CongestionControl != nil { + l = len(*m.CongestionControl) + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.CongestionLock { + n += 2 + } + if m.RtoMinMs != nil { + n += 1 + protohelpers.SizeOfVarint(uint64(*m.RtoMinMs)) + } + if m.InitialCongestionWindow != nil { + n += 1 + protohelpers.SizeOfVarint(uint64(*m.InitialCongestionWindow)) + } + if m.InitialReceiveWindow != nil { + n += 1 + protohelpers.SizeOfVarint(uint64(*m.InitialReceiveWindow)) + } + if m.QuickAck != nil { + n += 2 + } + if m.Ecn != nil { + n += 2 + } + n += len(m.unknownFields) + return n +} + +func (m *Treatment) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Id) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Revision != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Revision)) + } + l = len(m.Destination) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Settings != nil { + l = m.Settings.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.EcnFabricVerified { + n += 2 + } + n += len(m.unknownFields) + return n +} + +func (m *Assignment) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ExperimentId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Treatment != nil { + l = m.Treatment.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Mode != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Mode)) + } + if m.LeaseExpiresAt != nil { + l = (*timestamppb.Timestamp)(m.LeaseExpiresAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Predecessor != nil { + l = m.Predecessor.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Generation != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Generation)) + } + n += len(m.unknownFields) + return n +} + +func (m *Desired) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Revision != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Revision)) + } + if m.Suspended { + n += 2 + } + if m.IssuedAt != nil { + l = (*timestamppb.Timestamp)(m.IssuedAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if len(m.Assignments) > 0 { + for _, e := range m.Assignments { + l = e.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + } + n += len(m.unknownFields) + return n +} + +func (m *Record) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Assignment != nil { + l = m.Assignment.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.DesiredRevision != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.DesiredRevision)) + } + if m.Status != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Status)) + } + if m.ReceivedAt != nil { + l = (*timestamppb.Timestamp)(m.ReceivedAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.AttemptedAt != nil { + l = (*timestamppb.Timestamp)(m.AttemptedAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.AppliedAt != nil { + l = (*timestamppb.Timestamp)(m.AppliedAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.VerifiedAt != nil { + l = (*timestamppb.Timestamp)(m.VerifiedAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.RemovedAt != nil { + l = (*timestamppb.Timestamp)(m.RemovedAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.Error) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.InstalledRoute) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.InstalledBootId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.ExpiredLatched { + n += 2 + } + n += len(m.unknownFields) + return n +} + +func (m *Journal) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.AgentId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.Location) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.BootId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Desired != nil { + l = m.Desired.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.LocallySuspended { + n += 2 + } + l = len(m.SuspensionReason) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if len(m.Records) > 0 { + for _, e := range m.Records { + l = e.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + } + if m.UpdatedAt != nil { + l = (*timestamppb.Timestamp)(m.UpdatedAt).SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + +func (m *TCPSettings) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TCPSettings: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TCPSettings: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CongestionControl", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.CongestionControl = &s + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field CongestionLock", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.CongestionLock = bool(v != 0) + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RtoMinMs", wireType) + } + var v uint32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.RtoMinMs = &v + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field InitialCongestionWindow", wireType) + } + var v uint32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.InitialCongestionWindow = &v + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field InitialReceiveWindow", wireType) + } + var v uint32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.InitialReceiveWindow = &v + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field QuickAck", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + b := bool(v != 0) + m.QuickAck = &b + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Ecn", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + b := bool(v != 0) + m.Ecn = &b + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Treatment) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Treatment: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Treatment: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Id = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Revision", wireType) + } + m.Revision = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Revision |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Destination", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Destination = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Settings", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Settings == nil { + m.Settings = &TCPSettings{} + } + if err := m.Settings.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field EcnFabricVerified", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.EcnFabricVerified = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Assignment) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Assignment: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Assignment: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExperimentId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ExperimentId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Treatment", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Treatment == nil { + m.Treatment = &Treatment{} + } + if err := m.Treatment.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Mode", wireType) + } + m.Mode = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Mode |= AssignmentMode(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LeaseExpiresAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.LeaseExpiresAt == nil { + m.LeaseExpiresAt = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.LeaseExpiresAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Predecessor", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Predecessor == nil { + m.Predecessor = &Treatment{} + } + if err := m.Predecessor.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Generation", wireType) + } + m.Generation = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Generation |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Desired) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Desired: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Desired: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Revision", wireType) + } + m.Revision = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Revision |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Suspended", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Suspended = bool(v != 0) + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field IssuedAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.IssuedAt == nil { + m.IssuedAt = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.IssuedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Assignments", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Assignments = append(m.Assignments, &Assignment{}) + if err := m.Assignments[len(m.Assignments)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Record) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Record: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Record: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Assignment", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Assignment == nil { + m.Assignment = &Assignment{} + } + if err := m.Assignment.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DesiredRevision", wireType) + } + m.DesiredRevision = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.DesiredRevision |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + m.Status = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Status |= ApplyStatus(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ReceivedAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ReceivedAt == nil { + m.ReceivedAt = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.ReceivedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AttemptedAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.AttemptedAt == nil { + m.AttemptedAt = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.AttemptedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppliedAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.AppliedAt == nil { + m.AppliedAt = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.AppliedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field VerifiedAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.VerifiedAt == nil { + m.VerifiedAt = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.VerifiedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RemovedAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.RemovedAt == nil { + m.RemovedAt = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.RemovedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Error", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Error = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 10: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field InstalledRoute", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.InstalledRoute = append(m.InstalledRoute[:0], dAtA[iNdEx:postIndex]...) + if m.InstalledRoute == nil { + m.InstalledRoute = []byte{} + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field InstalledBootId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.InstalledBootId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 12: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ExpiredLatched", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.ExpiredLatched = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Journal) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Journal: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Journal: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AgentId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AgentId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Location", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Location = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BootId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.BootId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Desired", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Desired == nil { + m.Desired = &Desired{} + } + if err := m.Desired.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field LocallySuspended", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.LocallySuspended = bool(v != 0) + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SuspensionReason", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SuspensionReason = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Records", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Records = append(m.Records, &Record{}) + if err := m.Records[len(m.Records)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UpdatedAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.UpdatedAt == nil { + m.UpdatedAt = ×tamppb1.Timestamp{} + } + if err := (*timestamppb.Timestamp)(m.UpdatedAt).UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} diff --git a/gen/openapi/xtcp_treatment_journal/v1/journal.swagger.json b/gen/openapi/xtcp_treatment_journal/v1/journal.swagger.json new file mode 100644 index 000000000..20340a399 --- /dev/null +++ b/gen/openapi/xtcp_treatment_journal/v1/journal.swagger.json @@ -0,0 +1,44 @@ +{ + "swagger": "2.0", + "info": { + "title": "xtcp_treatment_journal/v1/journal.proto", + "version": "version not set" + }, + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": {}, + "definitions": { + "protobufAny": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } + }, + "additionalProperties": true + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/protobufAny" + } + } + } + } + } +} diff --git a/gen/python/xtcp_treatment_journal/v1/journal_pb2.py b/gen/python/xtcp_treatment_journal/v1/journal_pb2.py new file mode 100644 index 000000000..1e31376be --- /dev/null +++ b/gen/python/xtcp_treatment_journal/v1/journal_pb2.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: xtcp_treatment_journal/v1/journal.proto +# Protobuf Python Version: 7.36.2 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 7, + 36, + 2, + '', + 'xtcp_treatment_journal/v1/journal.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\'xtcp_treatment_journal/v1/journal.proto\x12\x19xtcp_treatment_journal.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"\xb7\x03\n\x0bTCPSettings\x12\x32\n\x12\x63ongestion_control\x18\x01 \x01(\tH\x00R\x11\x63ongestionControl\x88\x01\x01\x12\'\n\x0f\x63ongestion_lock\x18\x02 \x01(\x08R\x0e\x63ongestionLock\x12!\n\nrto_min_ms\x18\x03 \x01(\rH\x01R\x08rtoMinMs\x88\x01\x01\x12?\n\x19initial_congestion_window\x18\x04 \x01(\rH\x02R\x17initialCongestionWindow\x88\x01\x01\x12\x39\n\x16initial_receive_window\x18\x05 \x01(\rH\x03R\x14initialReceiveWindow\x88\x01\x01\x12 \n\tquick_ack\x18\x06 \x01(\x08H\x04R\x08quickAck\x88\x01\x01\x12\x15\n\x03\x65\x63n\x18\x07 \x01(\x08H\x05R\x03\x65\x63n\x88\x01\x01\x42\x15\n\x13_congestion_controlB\r\n\x0b_rto_min_msB\x1c\n\x1a_initial_congestion_windowB\x19\n\x17_initial_receive_windowB\x0c\n\n_quick_ackB\x06\n\x04_ecn\"\xcd\x01\n\tTreatment\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12\x1a\n\x08revision\x18\x02 \x01(\x04R\x08revision\x12 \n\x0b\x64\x65stination\x18\x03 \x01(\tR\x0b\x64\x65stination\x12\x42\n\x08settings\x18\x04 \x01(\x0b\x32&.xtcp_treatment_journal.v1.TCPSettingsR\x08settings\x12.\n\x13\x65\x63n_fabric_verified\x18\x05 \x01(\x08R\x11\x65\x63nFabricVerified\"\xe2\x02\n\nAssignment\x12#\n\rexperiment_id\x18\x01 \x01(\tR\x0c\x65xperimentId\x12\x42\n\ttreatment\x18\x02 \x01(\x0b\x32$.xtcp_treatment_journal.v1.TreatmentR\ttreatment\x12=\n\x04mode\x18\x03 \x01(\x0e\x32).xtcp_treatment_journal.v1.AssignmentModeR\x04mode\x12\x44\n\x10lease_expires_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0eleaseExpiresAt\x12\x46\n\x0bpredecessor\x18\x05 \x01(\x0b\x32$.xtcp_treatment_journal.v1.TreatmentR\x0bpredecessor\x12\x1e\n\ngeneration\x18\x06 \x01(\x04R\ngeneration\"\xc5\x01\n\x07\x44\x65sired\x12\x1a\n\x08revision\x18\x01 \x01(\x04R\x08revision\x12\x1c\n\tsuspended\x18\x02 \x01(\x08R\tsuspended\x12\x37\n\tissued_at\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x08issuedAt\x12G\n\x0b\x61ssignments\x18\x04 \x03(\x0b\x32%.xtcp_treatment_journal.v1.AssignmentR\x0b\x61ssignments\"\xfd\x04\n\x06Record\x12\x45\n\nassignment\x18\x01 \x01(\x0b\x32%.xtcp_treatment_journal.v1.AssignmentR\nassignment\x12)\n\x10\x64\x65sired_revision\x18\x02 \x01(\x04R\x0f\x64\x65siredRevision\x12>\n\x06status\x18\x03 \x01(\x0e\x32&.xtcp_treatment_journal.v1.ApplyStatusR\x06status\x12;\n\x0breceived_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\nreceivedAt\x12=\n\x0c\x61ttempted_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0b\x61ttemptedAt\x12\x39\n\napplied_at\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tappliedAt\x12;\n\x0bverified_at\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\nverifiedAt\x12\x39\n\nremoved_at\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tremovedAt\x12\x14\n\x05\x65rror\x18\t \x01(\tR\x05\x65rror\x12\'\n\x0finstalled_route\x18\n \x01(\x0cR\x0einstalledRoute\x12*\n\x11installed_boot_id\x18\x0b \x01(\tR\x0finstalledBootId\x12\'\n\x0f\x65xpired_latched\x18\x0c \x01(\x08R\x0e\x65xpiredLatched\"\xe9\x02\n\x07Journal\x12\x19\n\x08\x61gent_id\x18\x01 \x01(\tR\x07\x61gentId\x12\x1a\n\x08location\x18\x02 \x01(\tR\x08location\x12\x17\n\x07\x62oot_id\x18\x03 \x01(\tR\x06\x62ootId\x12<\n\x07\x64\x65sired\x18\x04 \x01(\x0b\x32\".xtcp_treatment_journal.v1.DesiredR\x07\x64\x65sired\x12+\n\x11locally_suspended\x18\x05 \x01(\x08R\x10locallySuspended\x12+\n\x11suspension_reason\x18\x06 \x01(\tR\x10suspensionReason\x12;\n\x07records\x18\x07 \x03(\x0b\x32!.xtcp_treatment_journal.v1.RecordR\x07records\x12\x39\n\nupdated_at\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tupdatedAt*\xa4\x01\n\x0e\x41ssignmentMode\x12\x1f\n\x1b\x41SSIGNMENT_MODE_UNSPECIFIED\x10\x00\x12\x19\n\x15\x41SSIGNMENT_MODE_TRIAL\x10\x01\x12\x1a\n\x16\x41SSIGNMENT_MODE_STABLE\x10\x02\x12\x1d\n\x19\x41SSIGNMENT_MODE_SUSPENDED\x10\x03\x12\x1b\n\x17\x41SSIGNMENT_MODE_RETIRED\x10\x04*\xad\x01\n\x0b\x41pplyStatus\x12\x1c\n\x18\x41PPLY_STATUS_UNSPECIFIED\x10\x00\x12\x18\n\x14\x41PPLY_STATUS_PENDING\x10\x01\x12\x19\n\x15\x41PPLY_STATUS_VERIFIED\x10\x02\x12\x18\n\x14\x41PPLY_STATUS_REMOVED\x10\x03\x12\x18\n\x14\x41PPLY_STATUS_EXPIRED\x10\x04\x12\x17\n\x13\x41PPLY_STATUS_FAILED\x10\x05\x42\xea\x01\n\x1d\x63om.xtcp_treatment_journal.v1B\x0cJournalProtoP\x01Z>github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment_journal\xa2\x02\x03XXX\xaa\x02\x17XtcpTreatmentJournal.V1\xca\x02\x17XtcpTreatmentJournal\\V1\xe2\x02#XtcpTreatmentJournal\\V1\\GPBMetadata\xea\x02\x18XtcpTreatmentJournal::V1b\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'xtcp_treatment_journal.v1.journal_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'\n\035com.xtcp_treatment_journal.v1B\014JournalProtoP\001Z>github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment_journal\242\002\003XXX\252\002\027XtcpTreatmentJournal.V1\312\002\027XtcpTreatmentJournal\\V1\342\002#XtcpTreatmentJournal\\V1\\GPBMetadata\352\002\030XtcpTreatmentJournal::V1' + _globals['_ASSIGNMENTMODE']._serialized_start=2315 + _globals['_ASSIGNMENTMODE']._serialized_end=2479 + _globals['_APPLYSTATUS']._serialized_start=2482 + _globals['_APPLYSTATUS']._serialized_end=2655 + _globals['_TCPSETTINGS']._serialized_start=104 + _globals['_TCPSETTINGS']._serialized_end=543 + _globals['_TREATMENT']._serialized_start=546 + _globals['_TREATMENT']._serialized_end=751 + _globals['_ASSIGNMENT']._serialized_start=754 + _globals['_ASSIGNMENT']._serialized_end=1108 + _globals['_DESIRED']._serialized_start=1111 + _globals['_DESIRED']._serialized_end=1308 + _globals['_RECORD']._serialized_start=1311 + _globals['_RECORD']._serialized_end=1948 + _globals['_JOURNAL']._serialized_start=1951 + _globals['_JOURNAL']._serialized_end=2312 +# @@protoc_insertion_point(module_scope) diff --git a/gen/python/xtcp_treatment_journal/v1/journal_pb2.pyi b/gen/python/xtcp_treatment_journal/v1/journal_pb2.pyi new file mode 100644 index 000000000..3afc5df1e --- /dev/null +++ b/gen/python/xtcp_treatment_journal/v1/journal_pb2.pyi @@ -0,0 +1,147 @@ +import datetime + +from google.protobuf import timestamp_pb2 as _timestamp_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class AssignmentMode(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + ASSIGNMENT_MODE_UNSPECIFIED: _ClassVar[AssignmentMode] + ASSIGNMENT_MODE_TRIAL: _ClassVar[AssignmentMode] + ASSIGNMENT_MODE_STABLE: _ClassVar[AssignmentMode] + ASSIGNMENT_MODE_SUSPENDED: _ClassVar[AssignmentMode] + ASSIGNMENT_MODE_RETIRED: _ClassVar[AssignmentMode] + +class ApplyStatus(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + APPLY_STATUS_UNSPECIFIED: _ClassVar[ApplyStatus] + APPLY_STATUS_PENDING: _ClassVar[ApplyStatus] + APPLY_STATUS_VERIFIED: _ClassVar[ApplyStatus] + APPLY_STATUS_REMOVED: _ClassVar[ApplyStatus] + APPLY_STATUS_EXPIRED: _ClassVar[ApplyStatus] + APPLY_STATUS_FAILED: _ClassVar[ApplyStatus] +ASSIGNMENT_MODE_UNSPECIFIED: AssignmentMode +ASSIGNMENT_MODE_TRIAL: AssignmentMode +ASSIGNMENT_MODE_STABLE: AssignmentMode +ASSIGNMENT_MODE_SUSPENDED: AssignmentMode +ASSIGNMENT_MODE_RETIRED: AssignmentMode +APPLY_STATUS_UNSPECIFIED: ApplyStatus +APPLY_STATUS_PENDING: ApplyStatus +APPLY_STATUS_VERIFIED: ApplyStatus +APPLY_STATUS_REMOVED: ApplyStatus +APPLY_STATUS_EXPIRED: ApplyStatus +APPLY_STATUS_FAILED: ApplyStatus + +class TCPSettings(_message.Message): + __slots__ = ("congestion_control", "congestion_lock", "rto_min_ms", "initial_congestion_window", "initial_receive_window", "quick_ack", "ecn") + CONGESTION_CONTROL_FIELD_NUMBER: _ClassVar[int] + CONGESTION_LOCK_FIELD_NUMBER: _ClassVar[int] + RTO_MIN_MS_FIELD_NUMBER: _ClassVar[int] + INITIAL_CONGESTION_WINDOW_FIELD_NUMBER: _ClassVar[int] + INITIAL_RECEIVE_WINDOW_FIELD_NUMBER: _ClassVar[int] + QUICK_ACK_FIELD_NUMBER: _ClassVar[int] + ECN_FIELD_NUMBER: _ClassVar[int] + congestion_control: str + congestion_lock: bool + rto_min_ms: int + initial_congestion_window: int + initial_receive_window: int + quick_ack: bool + ecn: bool + def __init__(self, congestion_control: _Optional[str] = ..., congestion_lock: _Optional[bool] = ..., rto_min_ms: _Optional[int] = ..., initial_congestion_window: _Optional[int] = ..., initial_receive_window: _Optional[int] = ..., quick_ack: _Optional[bool] = ..., ecn: _Optional[bool] = ...) -> None: ... + +class Treatment(_message.Message): + __slots__ = ("id", "revision", "destination", "settings", "ecn_fabric_verified") + ID_FIELD_NUMBER: _ClassVar[int] + REVISION_FIELD_NUMBER: _ClassVar[int] + DESTINATION_FIELD_NUMBER: _ClassVar[int] + SETTINGS_FIELD_NUMBER: _ClassVar[int] + ECN_FABRIC_VERIFIED_FIELD_NUMBER: _ClassVar[int] + id: str + revision: int + destination: str + settings: TCPSettings + ecn_fabric_verified: bool + def __init__(self, id: _Optional[str] = ..., revision: _Optional[int] = ..., destination: _Optional[str] = ..., settings: _Optional[_Union[TCPSettings, _Mapping]] = ..., ecn_fabric_verified: _Optional[bool] = ...) -> None: ... + +class Assignment(_message.Message): + __slots__ = ("experiment_id", "treatment", "mode", "lease_expires_at", "predecessor", "generation") + EXPERIMENT_ID_FIELD_NUMBER: _ClassVar[int] + TREATMENT_FIELD_NUMBER: _ClassVar[int] + MODE_FIELD_NUMBER: _ClassVar[int] + LEASE_EXPIRES_AT_FIELD_NUMBER: _ClassVar[int] + PREDECESSOR_FIELD_NUMBER: _ClassVar[int] + GENERATION_FIELD_NUMBER: _ClassVar[int] + experiment_id: str + treatment: Treatment + mode: AssignmentMode + lease_expires_at: _timestamp_pb2.Timestamp + predecessor: Treatment + generation: int + def __init__(self, experiment_id: _Optional[str] = ..., treatment: _Optional[_Union[Treatment, _Mapping]] = ..., mode: _Optional[_Union[AssignmentMode, str]] = ..., lease_expires_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., predecessor: _Optional[_Union[Treatment, _Mapping]] = ..., generation: _Optional[int] = ...) -> None: ... + +class Desired(_message.Message): + __slots__ = ("revision", "suspended", "issued_at", "assignments") + REVISION_FIELD_NUMBER: _ClassVar[int] + SUSPENDED_FIELD_NUMBER: _ClassVar[int] + ISSUED_AT_FIELD_NUMBER: _ClassVar[int] + ASSIGNMENTS_FIELD_NUMBER: _ClassVar[int] + revision: int + suspended: bool + issued_at: _timestamp_pb2.Timestamp + assignments: _containers.RepeatedCompositeFieldContainer[Assignment] + def __init__(self, revision: _Optional[int] = ..., suspended: _Optional[bool] = ..., issued_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., assignments: _Optional[_Iterable[_Union[Assignment, _Mapping]]] = ...) -> None: ... + +class Record(_message.Message): + __slots__ = ("assignment", "desired_revision", "status", "received_at", "attempted_at", "applied_at", "verified_at", "removed_at", "error", "installed_route", "installed_boot_id", "expired_latched") + ASSIGNMENT_FIELD_NUMBER: _ClassVar[int] + DESIRED_REVISION_FIELD_NUMBER: _ClassVar[int] + STATUS_FIELD_NUMBER: _ClassVar[int] + RECEIVED_AT_FIELD_NUMBER: _ClassVar[int] + ATTEMPTED_AT_FIELD_NUMBER: _ClassVar[int] + APPLIED_AT_FIELD_NUMBER: _ClassVar[int] + VERIFIED_AT_FIELD_NUMBER: _ClassVar[int] + REMOVED_AT_FIELD_NUMBER: _ClassVar[int] + ERROR_FIELD_NUMBER: _ClassVar[int] + INSTALLED_ROUTE_FIELD_NUMBER: _ClassVar[int] + INSTALLED_BOOT_ID_FIELD_NUMBER: _ClassVar[int] + EXPIRED_LATCHED_FIELD_NUMBER: _ClassVar[int] + assignment: Assignment + desired_revision: int + status: ApplyStatus + received_at: _timestamp_pb2.Timestamp + attempted_at: _timestamp_pb2.Timestamp + applied_at: _timestamp_pb2.Timestamp + verified_at: _timestamp_pb2.Timestamp + removed_at: _timestamp_pb2.Timestamp + error: str + installed_route: bytes + installed_boot_id: str + expired_latched: bool + def __init__(self, assignment: _Optional[_Union[Assignment, _Mapping]] = ..., desired_revision: _Optional[int] = ..., status: _Optional[_Union[ApplyStatus, str]] = ..., received_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., attempted_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., applied_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., verified_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., removed_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., error: _Optional[str] = ..., installed_route: _Optional[bytes] = ..., installed_boot_id: _Optional[str] = ..., expired_latched: _Optional[bool] = ...) -> None: ... + +class Journal(_message.Message): + __slots__ = ("agent_id", "location", "boot_id", "desired", "locally_suspended", "suspension_reason", "records", "updated_at") + AGENT_ID_FIELD_NUMBER: _ClassVar[int] + LOCATION_FIELD_NUMBER: _ClassVar[int] + BOOT_ID_FIELD_NUMBER: _ClassVar[int] + DESIRED_FIELD_NUMBER: _ClassVar[int] + LOCALLY_SUSPENDED_FIELD_NUMBER: _ClassVar[int] + SUSPENSION_REASON_FIELD_NUMBER: _ClassVar[int] + RECORDS_FIELD_NUMBER: _ClassVar[int] + UPDATED_AT_FIELD_NUMBER: _ClassVar[int] + agent_id: str + location: str + boot_id: str + desired: Desired + locally_suspended: bool + suspension_reason: str + records: _containers.RepeatedCompositeFieldContainer[Record] + updated_at: _timestamp_pb2.Timestamp + def __init__(self, agent_id: _Optional[str] = ..., location: _Optional[str] = ..., boot_id: _Optional[str] = ..., desired: _Optional[_Union[Desired, _Mapping]] = ..., locally_suspended: _Optional[bool] = ..., suspension_reason: _Optional[str] = ..., records: _Optional[_Iterable[_Union[Record, _Mapping]]] = ..., updated_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ... diff --git a/gen/python/xtcp_treatment_journal/v1/journal_pb2_grpc.py b/gen/python/xtcp_treatment_journal/v1/journal_pb2_grpc.py new file mode 100644 index 000000000..2daafffeb --- /dev/null +++ b/gen/python/xtcp_treatment_journal/v1/journal_pb2_grpc.py @@ -0,0 +1,4 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + diff --git a/pkg/treatment/agent/agent.go b/pkg/treatment/agent/agent.go index c947668ea..9b81afc4b 100644 --- a/pkg/treatment/agent/agent.go +++ b/pkg/treatment/agent/agent.go @@ -1,3 +1,8 @@ +// Package agent is the controller-facing half of a treatment host: the mTLS +// check-in loop, the desired-state watch and telemetry stamping, over the +// offline Host from package local. It is a library with two front ends: +// cmd/xtcp2-treatment-agent runs it standalone and cmd/xtcp2 embeds it when +// built with the treatment tag. package agent import ( @@ -5,10 +10,7 @@ import ( "crypto/rand" "crypto/x509" "encoding/hex" - "encoding/json" "errors" - "fmt" - "io" "log" "math/big" "net" @@ -21,136 +23,49 @@ import ( flat "github.com/randomizedcoder/xtcp2/gen/go/xtcp_flat_record" pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" - "github.com/randomizedcoder/xtcp2/internal/goip" "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/attribution" "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" + "github.com/randomizedcoder/xtcp2/pkg/treatment/local" "github.com/randomizedcoder/xtcp2/pkg/treatment/reconcile" + "github.com/randomizedcoder/xtcp2/pkg/treatment/wire" "golang.org/x/sys/unix" "google.golang.org/grpc" "google.golang.org/grpc/credentials" - "google.golang.org/protobuf/proto" ) -type Config struct { - AgentID string `json:"agent_id"` - Location string `json:"location"` - Controller string `json:"controller"` - ServerName string `json:"server_name"` - Certificate string `json:"certificate"` - PrivateKey string `json:"private_key"` - CA string `json:"ca"` - Directory string `json:"directory"` - EnableApply bool `json:"enable_apply"` - Table uint32 `json:"table"` - RulePriority uint32 `json:"rule_priority"` - Protocol uint8 `json:"protocol"` - ProtectedPrefixes []string `json:"protected_prefixes"` -} +// Config is the shared host configuration file; see local.Config. +type Config = local.Config -func LoadConfig(path string) (Config, error) { - var c Config - f, e := os.Open(path) - if e != nil { - return c, e - } - defer f.Close() - d := json.NewDecoder(io.LimitReader(f, 65537)) - d.DisallowUnknownFields() - if e = d.Decode(&c); e != nil { - return c, e - } - var extra any - if d.Decode(&extra) != io.EOF { - return c, errors.New("trailing config data") - } - if !treatment.ValidID(c.AgentID) || !treatment.ValidID(c.Location) || c.Controller == "" { - return c, errors.New("agent_id, location and controller are required") - } - if c.Directory == "" { - c.Directory = "/var/lib/xtcp2/routes" - } - if c.Table == 0 { - c.Table = 10002 - } - if c.RulePriority == 0 { - c.RulePriority = 30000 - } - if c.Protocol == 0 { - c.Protocol = 242 - } - return c, nil -} +// LoadConfig reads the shared host configuration file. +func LoadConfig(path string) (Config, error) { return local.LoadConfig(path) } +// Agent is one host's connection to the controller. type Agent struct { - Config Config - Reconciler *reconcile.Reconciler - backend *routeBackend - client pb.AgentControlServiceClient - conn *grpc.ClientConn - state atomic.Pointer[pb.LocalState] - exposure atomic.Pointer[exposureSnapshot] - wake chan struct{} + Config Config + Host *local.Host + Reconciler *reconcile.Reconciler + client pb.AgentControlServiceClient + conn *grpc.ClientConn + state atomic.Pointer[reconcile.State] + stamper attribution.Stamper + wake chan struct{} + // collectionID is a per-process nonce; see attribution.Identity. collectionID string - namespace uint64 -} - -type exposureSnapshot struct { - revision uint64 - held bool - routes map[netip.Addr]*pb.ApplicationRecord } -// NewLocal creates the offline emergency/watchdog path. It intentionally does -// not read TLS credentials, resolve DNS, or connect to the controller. -func NewLocal(c Config) (*Agent, error) { - return newLocal(c, localIdentity) -} - -// Separate host inspection from construction so configuration and lifecycle -// tests do not depend on permission to inspect PID 1's network namespace. -func localIdentity() (string, uint64, error) { - boot, err := os.ReadFile("/proc/sys/kernel/random/boot_id") +// newLocal builds the offline part. A nil identity inspects the host. +func newLocal(c Config, id *local.Identity) (*Agent, error) { + h, err := local.New(c, id) if err != nil { - return "", 0, err - } - var ns, initNS unix.Stat_t - if err = unix.Stat("/proc/self/ns/net", &ns); err != nil { - return "", 0, err - } - if err = unix.Stat("/proc/1/ns/net", &initNS); err != nil { - return "", 0, err - } - if ns.Ino != initNS.Ino { - return "", 0, errors.New("actuator must run in the host network namespace") - } - return strings.TrimSpace(string(boot)), ns.Ino, nil -} - -func newLocal(c Config, identity func() (string, uint64, error)) (*Agent, error) { - if !treatment.ValidID(c.AgentID) || !treatment.ValidID(c.Location) { - return nil, errors.New("invalid local agent identity") - } - boot, namespace, e := identity() - if e != nil { - return nil, e - } - b := &routeBackend{TreatmentBackend: &goip.TreatmentBackend{Table: c.Table, Priority: c.RulePriority, Protocol: c.Protocol, Enabled: c.EnableApply}} - for _, v := range c.ProtectedPrefixes { - p, e := netip.ParsePrefix(v) - if e != nil { - return nil, e - } - b.Protected = append(b.Protected, p) - } - if c.EnableApply && len(b.Protected) == 0 { - return nil, errors.New("protected_prefixes must cover management, DNS and telemetry networks before enabling application") + return nil, err } var nonce [16]byte - if _, e = rand.Read(nonce[:]); e != nil { - return nil, e + if _, err = rand.Read(nonce[:]); err != nil { + return nil, err } - a := &Agent{Config: c, backend: b, wake: make(chan struct{}, 1), namespace: namespace, collectionID: hex.EncodeToString(nonce[:])} - a.Reconciler = &reconcile.Reconciler{Journal: reconcile.Journal{Dir: c.Directory}, Backend: b, AgentID: c.AgentID, BootID: boot} + a := &Agent{Config: h.Config, Host: h, Reconciler: h.Reconciler, wake: make(chan struct{}, 1), collectionID: hex.EncodeToString(nonce[:])} + a.publish(nil) return a, nil } @@ -173,11 +88,10 @@ func New(ctx context.Context, c Config) (*Agent, error) { if id.Role != identity.RoleAgent || id.ID != c.AgentID || id.Location != c.Location { return nil, errors.New("agent config does not match certificate identity") } - a, e := NewLocal(c) + a, e := newLocal(c, nil) if e != nil { return nil, e } - b := a.backend host, _, e := net.SplitHostPort(c.Controller) if e != nil { return nil, e @@ -185,11 +99,11 @@ func New(ctx context.Context, c Config) (*Agent, error) { lookupCtx, cancel := context.WithTimeout(ctx, 3*time.Second) defer cancel() if ip, e := netip.ParseAddr(host); e == nil { - b.Protected = append(b.Protected, netip.PrefixFrom(ip, ip.BitLen())) + a.Host.Protect(netip.PrefixFrom(ip, ip.BitLen())) } else if ips, e := net.DefaultResolver.LookupNetIP(lookupCtx, "ip", host); e == nil { for _, ip := range ips { ip = ip.Unmap() - b.Protected = append(b.Protected, netip.PrefixFrom(ip, ip.BitLen())) + a.Host.Protect(netip.PrefixFrom(ip, ip.BitLen())) } } conn, e := grpc.NewClient(c.Controller, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)), grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(treatment.MaxStateBytes), grpc.MaxCallSendMsgSize(treatment.MaxStateBytes))) @@ -200,27 +114,31 @@ func New(ctx context.Context, c Config) (*Agent, error) { a.client = pb.NewAgentControlServiceClient(conn) return a, nil } + +// Close releases the controller connection. func (a *Agent) Close() error { if a.conn == nil { return nil } return a.conn.Close() } + +// Wake asks the run loop to check in now; concurrent wakes coalesce. func (a *Agent) Wake() { select { case a.wake <- struct{}{}: default: } } -func (a *Agent) State() *pb.LocalState { - s := a.state.Load() - if s == nil { - return nil - } - return proto.Clone(s).(*pb.LocalState) + +// State returns a copy of the last journal snapshot, nil before the first pass. +func (a *Agent) State() *reconcile.State { return a.state.Load().Clone() } + +func (a *Agent) identity() attribution.Identity { + return attribution.Identity{AgentID: a.Config.AgentID, Location: a.Config.Location, BootID: a.Host.Identity.BootID, CollectionID: a.collectionID, NetnsInode: a.Host.Identity.NetnsInode} } -func (a *Agent) reconcile(ctx context.Context, d *pb.DesiredState) { +func (a *Agent) reconcile(ctx context.Context, d *treatment.DesiredState) { ctx, cancel := context.WithTimeout(ctx, 15*time.Second) defer cancel() s, e := a.Reconciler.Run(ctx, d, reconcile.ModeFull) @@ -232,20 +150,14 @@ func (a *Agent) reconcile(ctx context.Context, d *pb.DesiredState) { } } -func (a *Agent) publish(s *pb.LocalState) { - index := &exposureSnapshot{revision: s.GetDesired().GetRevision(), held: s.LocallySuspended || s.GetDesired().GetSuspended(), routes: map[netip.Addr]*pb.ApplicationRecord{}} - for _, v := range s.Records { - if v.Status == pb.ApplyStatus_APPLY_STATUS_VERIFIED && v.Assignment != nil && v.AppliedAt != nil { - p, e := netip.ParsePrefix(v.Assignment.Treatment.Destination) - if e == nil { - index.routes[p.Addr()] = v - } - } +func (a *Agent) publish(s *reconcile.State) { + if s != nil { + a.state.Store(s) } - a.state.Store(s) - a.exposure.Store(index) + a.stamper.Publish(attribution.Build(s, a.identity(), true)) } +// Run checks in, reconciles and watches until ctx ends. func (a *Agent) Run(ctx context.Context) { var workers sync.WaitGroup workers.Add(1) @@ -256,24 +168,23 @@ func (a *Agent) Run(ctx context.Context) { for { a.reconcile(ctx, nil) callCtx, cancel := context.WithTimeout(ctx, 10*time.Second) - r := &pb.CheckInRequest{AgentId: a.Config.AgentID, Location: a.Config.Location, Capabilities: a.capabilities(callCtx)} + report := &treatment.AgentReport{AgentID: a.Config.AgentID, Location: a.Config.Location, Capabilities: a.capabilities(callCtx)} if s := a.state.Load(); s != nil { - r.DesiredRevision = s.GetDesired().GetRevision() - r.LocallySuspended = s.LocallySuspended - for _, record := range s.Records { - v := proto.Clone(record).(*pb.ApplicationRecord) - v.InstalledRoute = nil - v.InstalledBootId = "" - r.Records = append(r.Records, v) - } + report.DesiredRevision = s.Revision() + report.LocallySuspended = s.LocallySuspended + report.Records = s.Reports() } - response, e := a.client.CheckIn(callCtx, r) + response, e := a.client.CheckIn(callCtx, wire.Report(report)) cancel() if e != nil { log.Printf("treatment check-in: %v", e) } else { if response.Desired != nil { - a.reconcile(ctx, response.Desired) + if d, e := wire.ParseDesired(response.Desired); e != nil { + log.Printf("treatment check-in: invalid desired state: %v", e) + } else { + a.reconcile(ctx, d) + } } if !watchStarted { watchStarted = true @@ -300,11 +211,7 @@ func (a *Agent) Run(ctx context.Context) { func (a *Agent) watch(ctx context.Context) { for ctx.Err() == nil { - revision := uint64(0) - if s := a.state.Load(); s != nil { - revision = s.GetDesired().GetRevision() - } - stream, e := a.client.WatchDesiredState(ctx, &pb.WatchDesiredStateRequest{AgentId: a.Config.AgentID, AfterRevision: revision}) + stream, e := a.client.WatchDesiredState(ctx, &pb.WatchDesiredStateRequest{AgentId: a.Config.AgentID, AfterRevision: a.state.Load().Revision()}) if e == nil { for { _, e = stream.Recv() @@ -324,8 +231,8 @@ func (a *Agent) watch(ctx context.Context) { } } -func (a *Agent) capabilities(ctx context.Context) *pb.Capabilities { - c := &pb.Capabilities{BootId: a.Reconciler.BootID, ActuatorEnabled: a.Config.EnableApply} +func (a *Agent) capabilities(ctx context.Context) treatment.Capabilities { + c := treatment.Capabilities{BootID: a.Host.Identity.BootID, ActuatorEnabled: a.Config.EnableApply} var u unix.Utsname if unix.Uname(&u) == nil { c.KernelRelease = unix.ByteSliceToString(u.Release[:]) @@ -333,51 +240,9 @@ func (a *Agent) capabilities(ctx context.Context) *pb.Capabilities { if data, e := os.ReadFile("/proc/sys/net/ipv4/tcp_available_congestion_control"); e == nil { c.CongestionAlgorithms = strings.Fields(string(data)) } - c.SimpleHostRouting = a.backend.Check(ctx) == nil + c.SimpleHostRouting = a.Host.SimpleHostRouting(ctx) return c } -func (a *Agent) Stamp(r *flat.XtcpFlatRecord) { - r.AgentId = a.Config.AgentID - // Experiment location is certificate-bound, so it must not depend on a - // second independently configured telemetry label. - r.Location = a.Config.Location - r.HostBootId = a.Reconciler.BootID - r.CollectionId = a.collectionID - r.TreatmentStateRevision = 0 - r.TreatmentAssignmentId = "" - r.TreatmentGeneration = 0 - r.TreatmentAppliedTimestampNs = 0 - r.TreatmentAlgorithmExposed = false - s := a.exposure.Load() - if s == nil { - return - } - r.TreatmentStateRevision = s.revision - if s.held || r.NetnsInode != a.namespace { - return - } - raw := r.InetDiagMsgSocketDestination - if r.InetDiagMsgFamily == unix.AF_INET && len(raw) >= 4 { - raw = raw[:4] - } - ip, ok := netip.AddrFromSlice(raw) - if !ok { - return - } - if v := s.routes[ip]; v != nil { - a := v.Assignment - if a.Mode == pb.AssignmentMode_ASSIGNMENT_MODE_TRIAL && (a.LeaseExpiresAt == nil || !a.LeaseExpiresAt.AsTime().After(time.Now())) { - return - } - r.TreatmentAssignmentId = a.ExperimentId - r.TreatmentGeneration = a.Generation - r.TreatmentAppliedTimestampNs = v.AppliedAt.AsTime().UnixNano() - r.TreatmentAlgorithmExposed = a.Treatment.Settings.GetCongestionControl() != "" && r.InetDiagCong == a.Treatment.Settings.GetCongestionControl() - return - } -} - -func (c Config) String() string { - return fmt.Sprintf("agent=%s location=%s controller=%s apply=%t", c.AgentID, c.Location, c.Controller, c.EnableApply) -} +// Stamp is the hot-path telemetry hook; see attribution.Index.Stamp. +func (a *Agent) Stamp(r *flat.XtcpFlatRecord) { a.stamper.Stamp(r) } diff --git a/pkg/treatment/agent/agent_test.go b/pkg/treatment/agent/agent_test.go index 344db2331..c536a66b4 100644 --- a/pkg/treatment/agent/agent_test.go +++ b/pkg/treatment/agent/agent_test.go @@ -1,25 +1,41 @@ package agent import ( + "net/netip" "testing" "time" flat "github.com/randomizedcoder/xtcp2/gen/go/xtcp_flat_record" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/local" "github.com/randomizedcoder/xtcp2/pkg/treatment/reconcile" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/timestamppb" ) +func ptr[T any](v T) *T { return &v } + +var testIdentity = &local.Identity{BootID: "boot-1", NetnsInode: 123} + +func testAgent(t testing.TB) *Agent { + t.Helper() + a, err := newLocal(Config{AgentID: "host-1", Location: "dc", Directory: t.TempDir()}, testIdentity) + if err != nil { + t.Fatal(err) + } + return a +} + func TestStampDistinguishesAssignedAndActuallyExposed(t *testing.T) { - a := &Agent{Config: Config{AgentID: "host-1"}, namespace: 123, collectionID: "session", Reconciler: &reconcile.Reconciler{BootID: "boot-1"}} - s := &pb.LocalState{Desired: &pb.DesiredState{Revision: 2}, Records: []*pb.ApplicationRecord{{Status: pb.ApplyStatus_APPLY_STATUS_VERIFIED, AppliedAt: timestamppb.Now(), Assignment: &pb.Assignment{ExperimentId: "exp-1", Generation: 1, Mode: pb.AssignmentMode_ASSIGNMENT_MODE_STABLE, Treatment: &pb.Treatment{Destination: "192.0.2.1/32", Settings: &pb.TCPSettings{CongestionControl: proto.String("cubic")}}}}}} + a := testAgent(t) + s := &reconcile.State{Desired: &treatment.DesiredState{Revision: 2}, Records: []reconcile.Record{{ApplicationRecord: treatment.ApplicationRecord{Status: treatment.ApplyVerified, AppliedAt: time.Now(), Assignment: treatment.Assignment{ExperimentID: "exp-1", Generation: 1, Mode: treatment.ModeStable, Treatment: treatment.Treatment{Destination: netip.MustParsePrefix("192.0.2.1/32"), Settings: treatment.TCPSettings{CongestionControl: ptr("cubic")}}}}}}} a.publish(s) r := &flat.XtcpFlatRecord{NetnsInode: 123, InetDiagMsgFamily: 2, InetDiagMsgSocketDestination: []byte{192, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, InetDiagCong: "bbr"} a.Stamp(r) if r.TreatmentAssignmentId != "exp-1" || r.TreatmentAlgorithmExposed { t.Fatal("assignment mistaken for exposure") } + if r.AgentId != "host-1" || r.Location != "dc" || r.HostBootId != "boot-1" || len(r.CollectionId) != 32 || r.TreatmentStateRevision != 2 { + t.Fatalf("identity not stamped from the agent: %+v", r) + } r.InetDiagCong = "cubic" a.Stamp(r) if !r.TreatmentAlgorithmExposed { @@ -31,11 +47,25 @@ func TestStampDistinguishesAssignedAndActuallyExposed(t *testing.T) { t.Fatal("host route attributed to another namespace") } r.NetnsInode = 123 - s.Records[0].Assignment.Mode = pb.AssignmentMode_ASSIGNMENT_MODE_TRIAL - s.Records[0].Assignment.LeaseExpiresAt = timestamppb.New(time.Now().Add(-time.Second)) + s.Records[0].Assignment.Mode = treatment.ModeTrial + s.Records[0].Assignment.LeaseExpiresAt = time.Now().Add(-time.Second) a.publish(s) a.Stamp(r) if r.TreatmentAlgorithmExposed { t.Fatal("expired trial remained exposed") } } + +// Before the first reconcile pass the agent already stamps its identity so +// records are attributable to the host even while the controller is away. +func TestStampBeforeFirstReconcile(t *testing.T) { + a := testAgent(t) + r := &flat.XtcpFlatRecord{NetnsInode: 123, TreatmentAssignmentId: "stale"} + a.Stamp(r) + if r.AgentId != "host-1" || r.HostBootId != "boot-1" || r.TreatmentStateRevision != 0 || r.TreatmentAssignmentId != "" { + t.Fatalf("identity-only stamp wrong: %+v", r) + } + if a.State() != nil { + t.Fatal("state published before any pass") + } +} diff --git a/pkg/treatment/agent/backend_linux.go b/pkg/treatment/agent/backend_linux.go deleted file mode 100644 index abb295ef3..000000000 --- a/pkg/treatment/agent/backend_linux.go +++ /dev/null @@ -1,22 +0,0 @@ -package agent - -import ( - "context" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" - "github.com/randomizedcoder/xtcp2/internal/goip" - "github.com/randomizedcoder/xtcp2/pkg/treatment/wire" -) - -type routeBackend struct{ *goip.TreatmentBackend } - -func (b *routeBackend) Plan(ctx context.Context, t *pb.Treatment) ([]byte, error) { - if e := wire.ValidateTreatment(t); e != nil { - return nil, e - } - s := t.Settings - return b.TreatmentBackend.Plan(ctx, &goip.RouteTreatment{Destination: t.Destination, Settings: &goip.TCPRouteSettings{ - CongestionControl: s.CongestionControl, CongestionLock: s.CongestionLock, RtoMinMs: s.RtoMinMs, - InitialCongestionWindow: s.InitialCongestionWindow, InitialReceiveWindow: s.InitialReceiveWindow, - QuickAck: s.QuickAck, Ecn: s.Ecn, - }}) -} diff --git a/pkg/treatment/agent/benchmark_test.go b/pkg/treatment/agent/benchmark_test.go index c49d789de..0fac72cff 100644 --- a/pkg/treatment/agent/benchmark_test.go +++ b/pkg/treatment/agent/benchmark_test.go @@ -7,18 +7,16 @@ import ( "time" flat "github.com/randomizedcoder/xtcp2/gen/go/xtcp_flat_record" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/reconcile" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/timestamppb" ) -func stampFixture(n int) (*Agent, *pb.LocalState, *flat.XtcpFlatRecord) { - a := &Agent{Config: Config{AgentID: "host", Location: "dc"}, namespace: 123, collectionID: "session", Reconciler: &reconcile.Reconciler{BootID: "boot"}, wake: make(chan struct{}, 1)} - s := &pb.LocalState{Desired: &pb.DesiredState{Revision: 1}} +func stampFixture(t testing.TB, n int) (*Agent, *reconcile.State, *flat.XtcpFlatRecord) { + a := testAgent(t) + s := &reconcile.State{Desired: &treatment.DesiredState{Revision: 1}} for i := range n { ip := netip.AddrFrom4([4]byte{192, 0, byte(i >> 8), byte(i)}) - s.Records = append(s.Records, &pb.ApplicationRecord{Status: pb.ApplyStatus_APPLY_STATUS_VERIFIED, AppliedAt: timestamppb.New(time.Unix(1700000000, 0)), Assignment: &pb.Assignment{ExperimentId: fmt.Sprintf("assignment-%d", i), Generation: 1, Mode: pb.AssignmentMode_ASSIGNMENT_MODE_STABLE, Treatment: &pb.Treatment{Destination: ip.String() + "/32", Settings: &pb.TCPSettings{CongestionControl: proto.String("cubic")}}}}) + s.Records = append(s.Records, reconcile.Record{ApplicationRecord: treatment.ApplicationRecord{Status: treatment.ApplyVerified, AppliedAt: time.Unix(1700000000, 0), Assignment: treatment.Assignment{ExperimentID: fmt.Sprintf("assignment-%d", i), Generation: 1, Mode: treatment.ModeStable, Treatment: treatment.Treatment{Destination: netip.PrefixFrom(ip, 32), Settings: treatment.TCPSettings{CongestionControl: ptr("cubic")}}}}}) } a.publish(s) r := &flat.XtcpFlatRecord{NetnsInode: 123, InetDiagMsgFamily: 2, InetDiagMsgSocketDestination: []byte{192, 0, 0, 0}, InetDiagCong: "cubic"} @@ -29,7 +27,7 @@ func BenchmarkStamp(b *testing.B) { for _, n := range []int{1, 100, 4096} { for _, match := range []bool{true, false} { b.Run(fmt.Sprintf("routes=%d/match=%t", n, match), func(b *testing.B) { - a, _, r := stampFixture(n) + a, _, r := stampFixture(b, n) if !match { r.InetDiagMsgSocketDestination = []byte{198, 51, 100, 1} } @@ -44,3 +42,15 @@ func BenchmarkStamp(b *testing.B) { } } } + +func BenchmarkPublish(b *testing.B) { + for _, n := range []int{1, 100, 4096} { + b.Run(fmt.Sprint(n), func(b *testing.B) { + a, s, _ := stampFixture(b, n) + b.ReportAllocs() + for b.Loop() { + a.publish(s) + } + }) + } +} diff --git a/pkg/treatment/agent/contracts_test.go b/pkg/treatment/agent/contracts_test.go index 7c8fe2810..7426b9548 100644 --- a/pkg/treatment/agent/contracts_test.go +++ b/pkg/treatment/agent/contracts_test.go @@ -1,126 +1,20 @@ package agent import ( - "fmt" - "os" - "path/filepath" - "strings" "sync" "testing" - "time" flat "github.com/randomizedcoder/xtcp2/gen/go/xtcp_flat_record" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/timestamppb" ) -func TestLoadConfigContracts(t *testing.T) { - for _, tc := range []struct{ name, description, input, wantError string }{ - {"defaults", "minimal config receives safe defaults", `{"agent_id":"host","location":"dc","controller":"localhost:9000"}`, ""}, - {"explicit", "explicit private route settings are preserved", `{"agent_id":"host","location":"dc","controller":"localhost:9000","table":10003,"rule_priority":30001,"protocol":243}`, ""}, - {"unknown", "typos cannot silently disable settings", `{"agent_id":"host","location":"dc","controller":"localhost:9000","enable_aply":true}`, "unknown field"}, - {"trailing", "two JSON documents are rejected", `{} {}`, "trailing"}, - {"missing", "enrollment fields are required", `{}`, "required"}, - {"bad-id", "path-like agent identity is rejected", `{"agent_id":"../host","location":"dc","controller":"localhost:9000"}`, "required"}, - {"malformed", "truncated JSON is rejected", `{`, "EOF"}, - {"oversized", "oversized config cannot bypass the limit", `{"agent_id":"host","location":"dc","controller":"` + strings.Repeat("x", 65537) + `"}`, "EOF"}, - } { - t.Run(tc.name, func(t *testing.T) { - path := filepath.Join(t.TempDir(), "agent.json") - if err := os.WriteFile(path, []byte(tc.input), 0600); err != nil { - t.Fatal(err) - } - c, err := LoadConfig(path) - if tc.wantError != "" { - if err == nil || !strings.Contains(err.Error(), tc.wantError) { - t.Fatalf("%s: expected %q, got %v", tc.description, tc.wantError, err) - } - return - } - if err != nil { - t.Fatal(err) - } - wantTable, wantPriority, wantProtocol := uint32(10002), uint32(30000), uint8(242) - if tc.name == "explicit" { - wantTable++ - wantPriority++ - wantProtocol++ - } - if c.Table != wantTable || c.RulePriority != wantPriority || c.Protocol != wantProtocol || c.EnableApply || c.Directory != "/var/lib/xtcp2/routes" { - t.Fatalf("%s: unexpected config %+v", tc.description, c) - } - }) - } -} - -func TestStampContracts(t *testing.T) { - for _, tc := range []struct { - name, description string - change func(*pb.LocalState, *flat.XtcpFlatRecord) - wantAssignment, wantExposed bool - }{ - {"match", "verified matching algorithm is exposed", func(*pb.LocalState, *flat.XtcpFlatRecord) {}, true, true}, - {"different-algorithm", "assigned is distinct from exposed", func(_ *pb.LocalState, r *flat.XtcpFlatRecord) { r.InetDiagCong = "bbr" }, true, false}, - {"foreign-netns", "host routes do not apply in other namespaces", func(_ *pb.LocalState, r *flat.XtcpFlatRecord) { r.NetnsInode++ }, false, false}, - {"unknown-destination", "unmatched socket has no treatment", func(_ *pb.LocalState, r *flat.XtcpFlatRecord) { - r.InetDiagMsgSocketDestination = []byte{198, 51, 100, 1} - }, false, false}, - {"malformed-address", "short addresses are ignored", func(_ *pb.LocalState, r *flat.XtcpFlatRecord) { r.InetDiagMsgSocketDestination = []byte{192} }, false, false}, - {"local-hold", "local hold clears attribution", func(s *pb.LocalState, _ *flat.XtcpFlatRecord) { s.LocallySuspended = true }, false, false}, - {"fleet-hold", "controller hold clears attribution", func(s *pb.LocalState, _ *flat.XtcpFlatRecord) { s.Desired.Suspended = true }, false, false}, - {"failed", "failed application is never attributed", func(s *pb.LocalState, _ *flat.XtcpFlatRecord) { - s.Records[0].Status = pb.ApplyStatus_APPLY_STATUS_FAILED - }, false, false}, - {"no-applied-time", "unapplied record is not attributed", func(s *pb.LocalState, _ *flat.XtcpFlatRecord) { s.Records[0].AppliedAt = nil }, false, false}, - {"expired", "expired trial loses attribution", func(s *pb.LocalState, _ *flat.XtcpFlatRecord) { - a := s.Records[0].Assignment - a.Mode = pb.AssignmentMode_ASSIGNMENT_MODE_TRIAL - a.LeaseExpiresAt = timestamppb.New(time.Unix(1, 0)) - }, false, false}, - {"trial", "unexpired trial is exposed", func(s *pb.LocalState, _ *flat.XtcpFlatRecord) { - a := s.Records[0].Assignment - a.Mode = pb.AssignmentMode_ASSIGNMENT_MODE_TRIAL - a.LeaseExpiresAt = timestamppb.New(time.Now().Add(time.Hour)) - }, true, true}, - {"ipv6", "IPv6 host route matches 16-byte socket address", func(s *pb.LocalState, r *flat.XtcpFlatRecord) { - s.Records[0].Assignment.Treatment.Destination = "2001:db8::1/128" - r.InetDiagMsgFamily = 10 - r.InetDiagMsgSocketDestination = []byte{0x20, 1, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} - }, true, true}, - {"ipv4-padded", "kernel padded IPv4 addresses match", func(_ *pb.LocalState, r *flat.XtcpFlatRecord) { - r.InetDiagMsgSocketDestination = append(r.InetDiagMsgSocketDestination, make([]byte, 12)...) - }, true, true}, - } { - t.Run(tc.name, func(t *testing.T) { - a, s, r := stampFixture(1) - tc.change(s, r) - a.publish(s) - r.TreatmentAssignmentId = "old" - r.TreatmentAlgorithmExposed = true - r.TreatmentGeneration = 99 - a.Stamp(r) - if (r.TreatmentAssignmentId != "") != tc.wantAssignment || r.TreatmentAlgorithmExposed != tc.wantExposed { - t.Fatalf("%s: assignment=%q exposed=%t; want %t/%t", tc.description, r.TreatmentAssignmentId, r.TreatmentAlgorithmExposed, tc.wantAssignment, tc.wantExposed) - } - if !tc.wantAssignment && (r.TreatmentGeneration != 0 || r.TreatmentAppliedTimestampNs != 0) { - t.Fatal("stale attribution was not cleared") - } - if r.AgentId != "host" || r.Location != "dc" || r.HostBootId != "boot" || r.CollectionId != "session" || r.TreatmentStateRevision != 1 { - t.Fatal("collection identity missing") - } - }) - } -} - // Reconciliation publishes immutable snapshots while socket workers stamp // independent records and RPC check-ins clone the current state. func TestConcurrentPublishStampState(t *testing.T) { - a, s, _ := stampFixture(16) + a, s, _ := stampFixture(t, 16) var wg sync.WaitGroup wg.Go(func() { for i := range 200 { - next := proto.Clone(s).(*pb.LocalState) + next := s.Clone() next.Desired.Revision = uint64(i + 2) a.publish(next) a.Wake() @@ -136,13 +30,13 @@ func TestConcurrentPublishStampState(t *testing.T) { return } state := a.State() - state.Records[0].Assignment.ExperimentId = "caller mutation" + state.Records[0].Assignment.ExperimentID = "caller mutation" a.Wake() } }) } wg.Wait() - if a.State().Records[0].Assignment.ExperimentId != "assignment-0" { + if a.State().Records[0].Assignment.ExperimentID != "assignment-0" { t.Fatal("State leaked mutable internal snapshot") } select { @@ -156,15 +50,3 @@ func TestConcurrentPublishStampState(t *testing.T) { default: } } - -func BenchmarkPublish(b *testing.B) { - for _, n := range []int{1, 100, 4096} { - b.Run(fmt.Sprint(n), func(b *testing.B) { - a, s, _ := stampFixture(n) - b.ReportAllocs() - for b.Loop() { - a.publish(s) - } - }) - } -} diff --git a/pkg/treatment/agent/lifecycle_test.go b/pkg/treatment/agent/lifecycle_test.go index 94e3f6bd6..9fa52f158 100644 --- a/pkg/treatment/agent/lifecycle_test.go +++ b/pkg/treatment/agent/lifecycle_test.go @@ -3,7 +3,6 @@ package agent import ( "context" "errors" - "strings" "testing" "time" @@ -17,12 +16,13 @@ type lifecycleClient struct { t *testing.T cancel context.CancelFunc fail bool + badDesired bool checked, watched bool // inspected only after Run has joined all workers } func (c *lifecycleClient) CheckIn(_ context.Context, r *pb.CheckInRequest, _ ...grpc.CallOption) (*pb.CheckInResponse, error) { c.checked = true - if r.AgentId != "host" || r.Location != "dc" || r.Capabilities.BootId == "" || r.Capabilities.ActuatorEnabled { + if r.AgentId != "host-1" || r.Location != "dc" || r.Capabilities.BootId != "boot-1" || r.Capabilities.ActuatorEnabled { c.t.Error("incorrect check-in identity/capabilities") } for _, v := range r.Records { @@ -34,86 +34,53 @@ func (c *lifecycleClient) CheckIn(_ context.Context, r *pb.CheckInRequest, _ ... c.cancel() return nil, errors.New("controller offline") } - return &pb.CheckInResponse{Desired: &pb.DesiredState{AgentId: "host", Revision: 7, IssuedAt: timestamppb.Now()}}, nil + if c.badDesired { + c.cancel() + return &pb.CheckInResponse{Desired: &pb.DesiredState{AgentId: "host-1", Revision: 7, IssuedAt: ×tamppb.Timestamp{Nanos: -1}}}, nil + } + return &pb.CheckInResponse{Desired: &pb.DesiredState{AgentId: "host-1", Revision: 7, IssuedAt: timestamppb.Now()}}, nil } func (c *lifecycleClient) WatchDesiredState(_ context.Context, r *pb.WatchDesiredStateRequest, _ ...grpc.CallOption) (grpc.ServerStreamingClient[pb.WatchDesiredStateResponse], error) { c.watched = true - if r.AgentId != "host" || r.AfterRevision != 7 { + if r.AgentId != "host-1" || r.AfterRevision != 7 { c.t.Error("watch did not resume from reconciled revision") } c.cancel() return nil, errors.New("stream disconnected") } -func TestLocalAgentContracts(t *testing.T) { +func TestAgentRunCancellation(t *testing.T) { for _, tc := range []struct { name, description string - change func(*Config) - wantError string + fail, badDesired bool + wantWatch bool + wantRevision uint64 }{ - {"offline", "offline control does not require TLS or DNS", func(*Config) {}, ""}, - {"identity", "invalid agent identity fails before initialization", func(c *Config) { c.AgentID = "../host" }, "identity"}, - {"bad-prefix", "malformed protected prefixes fail closed", func(c *Config) { c.ProtectedPrefixes = []string{"bad"} }, "ParsePrefix"}, - {"no-exclusions", "application requires management exclusions", func(c *Config) { c.EnableApply = true }, "protected_prefixes"}, - {"protected", "explicit exclusions allow backend construction without writes", func(c *Config) { c.EnableApply = true; c.ProtectedPrefixes = []string{"192.0.2.0/24"} }, ""}, + {"connected", "positive: a successful check-in reconciles and starts the watch", false, false, true, 7}, + {"offline", "negative: an offline controller leaves the journal untouched and never watches", true, false, false, 0}, + {"undecodable", "corner: a desired state that fails to decode is logged, not reconciled", false, true, false, 0}, } { t.Run(tc.name, func(t *testing.T) { - c := Config{AgentID: "host", Location: "dc", Directory: t.TempDir(), Table: 10002, RulePriority: 30000, Protocol: 242} - tc.change(&c) - a, err := newLocal(c, testLocalIdentity) - if tc.wantError != "" { - if err == nil || !strings.Contains(err.Error(), tc.wantError) { - t.Fatalf("%s: got %v want %q", tc.description, err, tc.wantError) - } - return - } - if err != nil { - t.Fatal(err) - } - defer a.Close() - if a.State() != nil || a.namespace == 0 || len(a.collectionID) != 32 || a.Reconciler.BootID == "" { - t.Fatal("invalid initial identity/state") - } - if !strings.Contains(c.String(), "agent=host") { - t.Fatal("config diagnostic missing identity") - } - }) - } -} - -func TestAgentRunCancellation(t *testing.T) { - for _, fail := range []bool{false, true} { - name := "connected" - if fail { - name = "offline" - } - t.Run(name, func(t *testing.T) { - a, err := newLocal(Config{AgentID: "host", Location: "dc", Directory: t.TempDir(), Table: 10002, RulePriority: 30000, Protocol: 242}, testLocalIdentity) - if err != nil { - t.Fatal(err) - } + a := testAgent(t) defer a.Close() ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - c := &lifecycleClient{t: t, cancel: cancel, fail: fail} + c := &lifecycleClient{t: t, cancel: cancel, fail: tc.fail, badDesired: tc.badDesired} a.client = c a.Run(ctx) - if !c.checked || c.watched == fail { - t.Fatalf("check-in/watch lifecycle: checked=%t watched=%t", c.checked, c.watched) + if !c.checked || c.watched != tc.wantWatch { + t.Fatalf("%s: checked=%t watched=%t", tc.description, c.checked, c.watched) } - if !fail && a.State().GetDesired().GetRevision() != 7 { - t.Fatal("desired state was not reconciled") + if got := a.State().Revision(); got != tc.wantRevision { + t.Fatalf("%s: revision %d, want %d", tc.description, got, tc.wantRevision) } }) } } -func testLocalIdentity() (string, uint64, error) { return "boot", 123, nil } - func TestLocalIdentityFailure(t *testing.T) { - sentinel := errors.New("identity unavailable") - a, err := newLocal(Config{AgentID: "host", Location: "dc"}, func() (string, uint64, error) { return "", 0, sentinel }) - if a != nil || !errors.Is(err, sentinel) { - t.Fatalf("identity failure must prevent construction: %v %v", a, err) + a, err := newLocal(Config{AgentID: "../host", Location: "dc"}, testIdentity) + if a != nil || err == nil { + t.Fatalf("invalid identity must prevent construction: %v %v", a, err) } } diff --git a/pkg/treatment/attribution/index.go b/pkg/treatment/attribution/index.go new file mode 100644 index 000000000..b64e40304 --- /dev/null +++ b/pkg/treatment/attribution/index.go @@ -0,0 +1,150 @@ +// Package attribution stamps controller provenance onto socket telemetry: which +// agent collected a record, under which boot and desired-state revision, and +// whether the socket's destination had a verified treatment route when it +// was observed. Both agent modes feed it the same way, from a journal +// snapshot: the embedded agent after each reconcile pass, the telemetry-only +// daemon from the journal directory a standalone agent writes. +// +// Stamp runs on the socket hot path. It is one atomic load, one map lookup +// and no allocation; the index is rebuilt off the hot path on every publish. +package attribution + +import ( + "net/netip" + "sync/atomic" + "time" + + flat "github.com/randomizedcoder/xtcp2/gen/go/xtcp_flat_record" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/reconcile" + "golang.org/x/sys/unix" +) + +// Identity is the collection provenance written on every record. +type Identity struct { + AgentID string + Location string + BootID string + // CollectionID is a per-process nonce so restarts of the stamping + // process are distinguishable in evidence. + CollectionID string + // NetnsInode is the namespace the treatment routes live in; sockets in + // any other namespace are never attributed. + NetnsInode uint64 +} + +type exposure struct { + experimentID string + generation uint64 + appliedNs int64 + algorithm string + trial bool + leaseExpires time.Time +} + +// Index is an immutable snapshot of what to stamp. +type Index struct { + identity Identity + // setLocation says whether the record's location is replaced by the + // identity's: true for the embedded agent, whose location is certificate + // bound; false for a telemetry-only daemon, which keeps its own label. + setLocation bool + revision uint64 + held bool + routes map[netip.Addr]exposure +} + +// Build indexes the verified, applied routes of a snapshot. A nil snapshot +// yields an index that stamps identity only. +func Build(s *reconcile.State, id Identity, setLocation bool) *Index { + x := &Index{identity: id, setLocation: setLocation, routes: map[netip.Addr]exposure{}} + if s == nil { + return x + } + x.revision = s.Revision() + x.held = s.Held() + for i := range s.Records { + v := &s.Records[i] + if v.Status != treatment.ApplyVerified || v.AppliedAt.IsZero() || !v.Assignment.Treatment.Destination.IsValid() { + continue + } + a := &v.Assignment + x.routes[a.Treatment.Destination.Addr()] = exposure{experimentID: a.ExperimentID, generation: a.Generation, appliedNs: v.AppliedAt.UnixNano(), algorithm: a.Treatment.Settings.Algorithm(), trial: a.Mode == treatment.ModeTrial, leaseExpires: a.LeaseExpiresAt} + } + return x +} + +// Identity returns the provenance the index stamps. +func (x *Index) Identity() Identity { return x.identity } + +// Routes is the number of attributable destinations. +func (x *Index) Routes() int { return len(x.routes) } + +// Revision is the desired-state revision the index was built from. +func (x *Index) Revision() uint64 { return x.revision } + +// Held reports whether a hold suppressed attribution. +func (x *Index) Held() bool { return x.held } + +func clearTreatment(r *flat.XtcpFlatRecord) { + r.TreatmentStateRevision = 0 + r.TreatmentAssignmentId = "" + r.TreatmentGeneration = 0 + r.TreatmentAppliedTimestampNs = 0 + r.TreatmentAlgorithmExposed = false +} + +// Stamp writes provenance onto r. A nil index clears everything except the +// record's own location: with no journal there is no agent to speak for. +func (x *Index) Stamp(r *flat.XtcpFlatRecord) { + clearTreatment(r) + if x == nil { + r.AgentId = "" + r.HostBootId = "" + r.CollectionId = "" + return + } + r.AgentId = x.identity.AgentID + if x.setLocation { + // Experiment location is certificate-bound, so it must not depend + // on a second independently configured telemetry label. + r.Location = x.identity.Location + } + r.HostBootId = x.identity.BootID + r.CollectionId = x.identity.CollectionID + r.TreatmentStateRevision = x.revision + if x.held || r.NetnsInode != x.identity.NetnsInode { + return + } + raw := r.InetDiagMsgSocketDestination + if r.InetDiagMsgFamily == unix.AF_INET && len(raw) >= 4 { + raw = raw[:4] + } + ip, ok := netip.AddrFromSlice(raw) + if !ok { + return + } + v, ok := x.routes[ip] + if !ok { + return + } + if v.trial && (v.leaseExpires.IsZero() || !v.leaseExpires.After(time.Now())) { + return + } + r.TreatmentAssignmentId = v.experimentID + r.TreatmentGeneration = v.generation + r.TreatmentAppliedTimestampNs = v.appliedNs + r.TreatmentAlgorithmExposed = v.algorithm != "" && r.InetDiagCong == v.algorithm +} + +// Stamper is the hot-path handle: Publish swaps the index, Stamp reads it. +type Stamper struct{ index atomic.Pointer[Index] } + +// Publish makes x the current index for subsequent stamps. +func (s *Stamper) Publish(x *Index) { s.index.Store(x) } + +// Current returns the published index, nil before the first publish. +func (s *Stamper) Current() *Index { return s.index.Load() } + +// Stamp applies the current index to r; safe before any publish. +func (s *Stamper) Stamp(r *flat.XtcpFlatRecord) { s.index.Load().Stamp(r) } diff --git a/pkg/treatment/attribution/index_test.go b/pkg/treatment/attribution/index_test.go new file mode 100644 index 000000000..270723bb9 --- /dev/null +++ b/pkg/treatment/attribution/index_test.go @@ -0,0 +1,200 @@ +package attribution + +import ( + "fmt" + "net/netip" + "sync" + "testing" + "time" + + flat "github.com/randomizedcoder/xtcp2/gen/go/xtcp_flat_record" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/reconcile" +) + +func ptr[T any](v T) *T { return &v } + +var testIdentity = Identity{AgentID: "host", Location: "dc", BootID: "boot", CollectionID: "session", NetnsInode: 123} + +// fixture is n verified stable CUBIC routes on 192.0.x.y plus a matching record. +func fixture(n int) (*reconcile.State, *flat.XtcpFlatRecord) { + s := &reconcile.State{AgentID: "host", Desired: &treatment.DesiredState{Revision: 1}} + for i := range n { + ip := netip.AddrFrom4([4]byte{192, 0, byte(i >> 8), byte(i)}) + s.Records = append(s.Records, reconcile.Record{ApplicationRecord: treatment.ApplicationRecord{Status: treatment.ApplyVerified, AppliedAt: time.Unix(1700000000, 0), Assignment: treatment.Assignment{ExperimentID: fmt.Sprintf("assignment-%d", i), Generation: 1, Mode: treatment.ModeStable, Treatment: treatment.Treatment{Destination: netip.PrefixFrom(ip, 32), Settings: treatment.TCPSettings{CongestionControl: ptr("cubic")}}}}}) + } + r := &flat.XtcpFlatRecord{Location: "daemon-dc", NetnsInode: 123, InetDiagMsgFamily: 2, InetDiagMsgSocketDestination: []byte{192, 0, 0, 0}, InetDiagCong: "cubic"} + return s, r +} + +func TestStampContracts(t *testing.T) { + for _, tc := range []struct { + name, description string + change func(*reconcile.State, *flat.XtcpFlatRecord) + wantAssignment, wantExposed bool + }{ + {"match", "positive: verified matching algorithm is exposed", func(*reconcile.State, *flat.XtcpFlatRecord) {}, true, true}, + {"different-algorithm", "positive: assigned is distinct from exposed", func(_ *reconcile.State, r *flat.XtcpFlatRecord) { r.InetDiagCong = "bbr" }, true, false}, + {"foreign-netns", "negative: host routes do not apply in other namespaces", func(_ *reconcile.State, r *flat.XtcpFlatRecord) { r.NetnsInode++ }, false, false}, + {"unknown-destination", "negative: unmatched socket has no treatment", func(_ *reconcile.State, r *flat.XtcpFlatRecord) { + r.InetDiagMsgSocketDestination = []byte{198, 51, 100, 1} + }, false, false}, + {"malformed-address", "corner: short addresses are ignored", func(_ *reconcile.State, r *flat.XtcpFlatRecord) { r.InetDiagMsgSocketDestination = []byte{192} }, false, false}, + {"local-hold", "negative: local hold clears attribution", func(s *reconcile.State, _ *flat.XtcpFlatRecord) { s.LocallySuspended = true }, false, false}, + {"fleet-hold", "negative: controller hold clears attribution", func(s *reconcile.State, _ *flat.XtcpFlatRecord) { s.Desired.Suspended = true }, false, false}, + {"failed", "negative: failed application is never attributed", func(s *reconcile.State, _ *flat.XtcpFlatRecord) { + s.Records[0].Status = treatment.ApplyFailed + }, false, false}, + {"no-applied-time", "negative: unapplied record is not attributed", func(s *reconcile.State, _ *flat.XtcpFlatRecord) { s.Records[0].AppliedAt = time.Time{} }, false, false}, + {"expired", "boundary: expired trial loses attribution", func(s *reconcile.State, _ *flat.XtcpFlatRecord) { + a := &s.Records[0].Assignment + a.Mode = treatment.ModeTrial + a.LeaseExpiresAt = time.Unix(1, 0) + }, false, false}, + {"trial-no-lease", "boundary: a trial without a lease is never exposed", func(s *reconcile.State, _ *flat.XtcpFlatRecord) { + s.Records[0].Assignment.Mode = treatment.ModeTrial + }, false, false}, + {"trial", "positive: unexpired trial is exposed", func(s *reconcile.State, _ *flat.XtcpFlatRecord) { + a := &s.Records[0].Assignment + a.Mode = treatment.ModeTrial + a.LeaseExpiresAt = time.Now().Add(time.Hour) + }, true, true}, + {"no-algorithm", "corner: a treatment without an algorithm is attributed but never algorithm-exposed", func(s *reconcile.State, _ *flat.XtcpFlatRecord) { + s.Records[0].Assignment.Treatment.Settings = treatment.TCPSettings{QuickAck: ptr(true)} + }, true, false}, + {"ipv6", "positive: IPv6 host route matches 16-byte socket address", func(s *reconcile.State, r *flat.XtcpFlatRecord) { + s.Records[0].Assignment.Treatment.Destination = netip.MustParsePrefix("2001:db8::1/128") + r.InetDiagMsgFamily = 10 + r.InetDiagMsgSocketDestination = []byte{0x20, 1, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} + }, true, true}, + {"ipv4-padded", "corner: kernel padded IPv4 addresses match", func(_ *reconcile.State, r *flat.XtcpFlatRecord) { + r.InetDiagMsgSocketDestination = append(r.InetDiagMsgSocketDestination, make([]byte, 12)...) + }, true, true}, + } { + t.Run(tc.name, func(t *testing.T) { + s, r := fixture(1) + tc.change(s, r) + x := Build(s, testIdentity, true) + r.TreatmentAssignmentId = "old" + r.TreatmentAlgorithmExposed = true + r.TreatmentGeneration = 99 + x.Stamp(r) + if (r.TreatmentAssignmentId != "") != tc.wantAssignment || r.TreatmentAlgorithmExposed != tc.wantExposed { + t.Fatalf("%s: assignment=%q exposed=%t; want %t/%t", tc.description, r.TreatmentAssignmentId, r.TreatmentAlgorithmExposed, tc.wantAssignment, tc.wantExposed) + } + if !tc.wantAssignment && (r.TreatmentGeneration != 0 || r.TreatmentAppliedTimestampNs != 0) { + t.Fatal("stale attribution was not cleared") + } + if tc.wantAssignment && (r.TreatmentGeneration != 1 || r.TreatmentAppliedTimestampNs != 1700000000e9) { + t.Fatalf("generation/applied not stamped: %d %d", r.TreatmentGeneration, r.TreatmentAppliedTimestampNs) + } + if r.AgentId != "host" || r.Location != "dc" || r.HostBootId != "boot" || r.CollectionId != "session" || r.TreatmentStateRevision != 1 { + t.Fatal("collection identity missing") + } + }) + } +} + +func TestStampIdentityModes(t *testing.T) { + for _, tc := range []struct { + name, description string + index func() *Index + wantLocation string + wantAgent string + wantRevision uint64 + }{ + {"embedded", "positive: the embedded agent replaces the record location with the certificate-bound one", func() *Index { s, _ := fixture(1); return Build(s, testIdentity, true) }, "dc", "host", 1}, + {"attribution", "positive: a telemetry-only daemon keeps its own location label", func() *Index { s, _ := fixture(1); return Build(s, testIdentity, false) }, "daemon-dc", "host", 1}, + {"identity-only", "boundary: before the first reconcile pass only identity is stamped", func() *Index { return Build(nil, testIdentity, true) }, "dc", "host", 0}, + {"nil", "corner: with no index at all the record is left as an observation-only producer", func() *Index { return nil }, "daemon-dc", "", 0}, + } { + t.Run(tc.name, func(t *testing.T) { + _, r := fixture(1) + r.AgentId, r.HostBootId, r.CollectionId, r.TreatmentAssignmentId = "stale", "stale", "stale", "stale" + x := tc.index() + x.Stamp(r) + if r.Location != tc.wantLocation || r.AgentId != tc.wantAgent || r.TreatmentStateRevision != tc.wantRevision { + t.Fatalf("%s: location=%q agent=%q revision=%d", tc.description, r.Location, r.AgentId, r.TreatmentStateRevision) + } + if tc.wantAgent == "" && (r.HostBootId != "" || r.CollectionId != "") { + t.Fatal("nil index left stale identity") + } + if x != nil && x.Routes() != len(x.routes) || tc.wantRevision == 1 && (x.Routes() != 1 || x.Held() || x.Identity() != testIdentity) { + t.Fatalf("%s: accessors disagree", tc.description) + } + if r.TreatmentAssignmentId != "" && tc.wantRevision == 0 { + t.Fatal("attributed without routes") + } + }) + } +} + +// Publish swaps immutable indexes while socket workers stamp concurrently. +func TestStamperConcurrentPublish(t *testing.T) { + s, _ := fixture(16) + var st Stamper + st.Publish(Build(s, testIdentity, true)) + var wg sync.WaitGroup + wg.Go(func() { + for i := range 200 { + next := s.Clone() + next.Desired.Revision = uint64(i + 2) + st.Publish(Build(next, testIdentity, true)) + } + }) + for range 8 { + wg.Go(func() { + _, r := fixture(0) + for range 200 { + st.Stamp(r) + if r.TreatmentAssignmentId != "assignment-0" || !r.TreatmentAlgorithmExposed || r.TreatmentStateRevision == 0 { + t.Error("inconsistent exposure snapshot") + return + } + } + }) + } + wg.Wait() + if st.Current().Revision() != 201 { + t.Fatalf("last publish not visible: %d", st.Current().Revision()) + } + var empty Stamper + _, r := fixture(0) + empty.Stamp(r) + if empty.Current() != nil || r.AgentId != "" { + t.Fatal("unpublished stamper must behave as nil index") + } +} + +func BenchmarkStamp(b *testing.B) { + for _, n := range []int{1, 100, 4096} { + for _, match := range []bool{true, false} { + b.Run(fmt.Sprintf("routes=%d/match=%t", n, match), func(b *testing.B) { + s, r := fixture(n) + x := Build(s, testIdentity, true) + if !match { + r.InetDiagMsgSocketDestination = []byte{198, 51, 100, 1} + } + b.ReportAllocs() + for b.Loop() { + x.Stamp(r) + } + if r.TreatmentAlgorithmExposed != match { + b.Fatal("wrong exposure") + } + }) + } + } +} + +func BenchmarkBuild(b *testing.B) { + for _, n := range []int{1, 100, 4096} { + b.Run(fmt.Sprint(n), func(b *testing.B) { + s, _ := fixture(n) + b.ReportAllocs() + for b.Loop() { + Build(s, testIdentity, true) + } + }) + } +} diff --git a/pkg/treatment/boundaries_test.go b/pkg/treatment/boundaries_test.go index cf0c44925..34b209e56 100644 --- a/pkg/treatment/boundaries_test.go +++ b/pkg/treatment/boundaries_test.go @@ -19,9 +19,11 @@ import ( // pkg/treatment domain types, limits, validators: proto-free [everyone] // pkg/treatment/wire domain <-> generated proto conversion [gRPC boundary] // pkg/treatment/identity SPIFFE identity + TLS [agent, controller, tools] -// pkg/treatment/reconcile journal + reconciler + Backend [agent, watchdog] +// pkg/treatment/reconcile journal (xtcp_treatment_journal.v1) + reconciler + Backend; no RPC proto, no gRPC [agent, watchdog] +// pkg/treatment/local host config + goip-backed reconciler; no TLS, no gRPC [agent, watchdog, treatmentctl] +// pkg/treatment/attribution ExposureIndex + telemetry stamping from a journal snapshot [daemon, agent] // pkg/treatment/experiment statistics + state machine, pure [controller] -// pkg/treatment/agent check-in loop + stamping [daemon, agent binary] +// pkg/treatment/agent check-in loop + watch over local + attribution [daemon (treatment tag), agent binary] // pkg/treatment/controller gRPC handlers over store ports [controller binary only] // pkg/treatment/controller/store ports; postgres/ and memory/ implement them [controller] // @@ -75,23 +77,38 @@ func treatmentBoundaryRules() []boundaryRule { expectAny: []string{tp("pkg/treatment")}, }, { - // TODO(dual-mode agent): forbid pkg/treatment/identity and google.golang.org/grpc - // once the watchdog builds on pkg/treatment/reconcile instead of agent. - description: "boundary: the watchdog never reaches the controller or a database", + // TODO(flat-record stubs): also forbid google.golang.org/grpc here + // and for pkg/treatment/local. Today pkg/xtcpnl imports + // gen/go/xtcp_flat_record, whose Go package carries the gRPC and + // gateway stubs of that proto, so every goip user links gRPC + // through the netlink library. The treatment packages themselves + // are clean: the reconcile rule below forbids gRPC. + description: "boundary: the watchdog never reaches the controller, a database, TLS identity, the agent or the RPC proto", root: tp("cmd/xtcp2-treatmentctl"), - forbidden: []string{tp("pkg/treatment/controller")}, - forbidden3p: []string{"database/sql", "jackc/pgx"}, - expectAny: []string{tp("pkg/treatment")}, + forbidden: []string{tp("pkg/treatment/controller"), tp("pkg/treatment/identity"), tp("pkg/treatment/agent"), tp("pkg/treatment/attribution"), tp("pkg/treatment/wire"), tp("pkg/treatment/experiment"), tp("gen/go/xtcp_treatment")}, + forbidden3p: []string{"database/sql", "jackc/pgx", "clickhouse", "google.golang.org/grpc/credentials"}, + expectAny: []string{tp("pkg/treatment"), tp("pkg/treatment/local"), tp("pkg/treatment/reconcile")}, }, { - // TODO(dual-mode agent): forbid gen/go/xtcp_treatment and - // google.golang.org/grpc once the journal is a journal-specific - // message converted from domain types. - description: "positive: the reconcile package is host-side only: no identity, no experiment statistics", + description: "positive: the reconcile package is host-side only: journal proto and domain, never the RPC proto, gRPC, identity, goip or experiment statistics", root: tp("pkg/treatment/reconcile"), - forbidden: []string{tp("pkg/treatment/identity"), tp("pkg/treatment/experiment"), tp("pkg/treatment/agent"), tp("pkg/treatment/controller")}, - forbidden3p: []string{"database/sql"}, - expectAny: []string{tp("pkg/treatment")}, + forbidden: []string{tp("pkg/treatment/identity"), tp("pkg/treatment/experiment"), tp("pkg/treatment/agent"), tp("pkg/treatment/controller"), tp("pkg/treatment/wire"), tp("gen/go/xtcp_treatment"), tp("internal/goip")}, + forbidden3p: []string{"database/sql", "google.golang.org/grpc"}, + expectAny: []string{tp("pkg/treatment"), tp("gen/go/xtcp_treatment_journal")}, + }, + { + description: "positive: the local package builds the kernel-backed reconciler without TLS or the RPC proto", + root: tp("pkg/treatment/local"), + forbidden: []string{tp("pkg/treatment/identity"), tp("pkg/treatment/agent"), tp("pkg/treatment/attribution"), tp("pkg/treatment/controller"), tp("pkg/treatment/experiment"), tp("pkg/treatment/wire"), tp("gen/go/xtcp_treatment")}, + forbidden3p: []string{"database/sql", "google.golang.org/grpc/credentials"}, + expectAny: []string{tp("pkg/treatment/reconcile"), tp("internal/goip")}, + }, + { + description: "positive: attribution reads journal snapshots and the flat record; never identity, goip, the agent or the RPC proto", + root: tp("pkg/treatment/attribution"), + forbidden: []string{tp("pkg/treatment/identity"), tp("pkg/treatment/agent"), tp("pkg/treatment/local"), tp("pkg/treatment/controller"), tp("pkg/treatment/experiment"), tp("pkg/treatment/wire"), tp("gen/go/xtcp_treatment"), tp("internal/goip")}, + forbidden3p: []string{"database/sql", "jackc/pgx"}, + expectAny: []string{tp("pkg/treatment/reconcile"), tp("gen/go/xtcp_flat_record")}, }, { description: "positive: the experiment package is pure: no proto, no gRPC, no journal, no identity, no syscalls", @@ -114,9 +131,9 @@ func treatmentBoundaryRules() []boundaryRule { expectAny: []string{tp("pkg/treatment/controller/store")}, }, { - description: "negative: the controller never reaches the host-side reconcile package or goip", + description: "negative: the controller never reaches the host-side reconcile, local or attribution packages, goip or the journal proto", root: tp("pkg/treatment/controller"), - forbidden: []string{tp("pkg/treatment/reconcile"), tp("internal/goip")}, + forbidden: []string{tp("pkg/treatment/reconcile"), tp("pkg/treatment/local"), tp("pkg/treatment/attribution"), tp("internal/goip"), tp("gen/go/xtcp_treatment_journal")}, expectAny: []string{tp("pkg/treatment/experiment")}, }, { diff --git a/pkg/treatment/local/config.go b/pkg/treatment/local/config.go new file mode 100644 index 000000000..88a8a08f0 --- /dev/null +++ b/pkg/treatment/local/config.go @@ -0,0 +1,83 @@ +// Package local is the host-side configuration shared by every process that +// touches the treatment journal (the embedded or standalone agent, the +// watchdog and the treatmentctl commands) and the construction of the +// kernel-backed reconciler from it. It never speaks gRPC, never reads TLS +// material and never resolves DNS: the watchdog must work with the controller +// unreachable and the certificate expired. +package local + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "os" + + "github.com/randomizedcoder/xtcp2/pkg/treatment" +) + +// Config is /etc/xtcp2/controller-agent.json. The controller and credential +// fields are only read by the agent; the watchdog ignores them. +type Config struct { + AgentID string `json:"agent_id"` + Location string `json:"location"` + Controller string `json:"controller"` + ServerName string `json:"server_name"` + Certificate string `json:"certificate"` + PrivateKey string `json:"private_key"` + CA string `json:"ca"` + Directory string `json:"directory"` + EnableApply bool `json:"enable_apply"` + Table uint32 `json:"table"` + RulePriority uint32 `json:"rule_priority"` + Protocol uint8 `json:"protocol"` + ProtectedPrefixes []string `json:"protected_prefixes"` +} + +// DefaultDirectory is where the journal lives unless the config says otherwise. +const DefaultDirectory = "/var/lib/xtcp2/routes" + +// LoadConfig reads and validates the JSON file, applying the private route +// table defaults. Unknown fields and trailing documents are errors so a typo +// can never silently disable a setting. +func LoadConfig(path string) (Config, error) { + var c Config + f, e := os.Open(path) + if e != nil { + return c, e + } + defer f.Close() + d := json.NewDecoder(io.LimitReader(f, 65537)) + d.DisallowUnknownFields() + if e = d.Decode(&c); e != nil { + return c, e + } + var extra any + if d.Decode(&extra) != io.EOF { + return c, errors.New("trailing config data") + } + if !treatment.ValidID(c.AgentID) || !treatment.ValidID(c.Location) || c.Controller == "" { + return c, errors.New("agent_id, location and controller are required") + } + c.applyDefaults() + return c, nil +} + +func (c *Config) applyDefaults() { + if c.Directory == "" { + c.Directory = DefaultDirectory + } + if c.Table == 0 { + c.Table = 10002 + } + if c.RulePriority == 0 { + c.RulePriority = 30000 + } + if c.Protocol == 0 { + c.Protocol = 242 + } +} + +func (c Config) String() string { + return fmt.Sprintf("agent=%s location=%s controller=%s apply=%t", c.AgentID, c.Location, c.Controller, c.EnableApply) +} diff --git a/pkg/treatment/local/helpers_test.go b/pkg/treatment/local/helpers_test.go new file mode 100644 index 000000000..4f2ccf6fa --- /dev/null +++ b/pkg/treatment/local/helpers_test.go @@ -0,0 +1,5 @@ +package local + +import "net/netip" + +func mustPrefix(s string) netip.Prefix { return netip.MustParsePrefix(s) } diff --git a/pkg/treatment/local/host.go b/pkg/treatment/local/host.go new file mode 100644 index 000000000..b68afe955 --- /dev/null +++ b/pkg/treatment/local/host.go @@ -0,0 +1,107 @@ +package local + +import ( + "context" + "errors" + "net/netip" + "os" + "strings" + + "github.com/randomizedcoder/xtcp2/internal/goip" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/reconcile" + "golang.org/x/sys/unix" +) + +// Identity is what the kernel says about the process: the boot it runs in +// and the network namespace it will mutate. +type Identity struct { + BootID string + NetnsInode uint64 +} + +// HostIdentity inspects the running host. The actuator must run in the host +// network namespace, because the routes it installs are host routes. +func HostIdentity() (Identity, error) { + boot, err := os.ReadFile("/proc/sys/kernel/random/boot_id") + if err != nil { + return Identity{}, err + } + var ns, initNS unix.Stat_t + if err = unix.Stat("/proc/self/ns/net", &ns); err != nil { + return Identity{}, err + } + if err = unix.Stat("/proc/1/ns/net", &initNS); err != nil { + return Identity{}, err + } + if ns.Ino != initNS.Ino { + return Identity{}, errors.New("actuator must run in the host network namespace") + } + return Identity{BootID: strings.TrimSpace(string(boot)), NetnsInode: ns.Ino}, nil +} + +// Host is the offline, mutation-capable half of an agent: the reconciler over +// the journal and the goip route backend. It is everything the watchdog and +// the local commands need and nothing the controller connection needs. +type Host struct { + Config Config + Identity Identity + Reconciler *reconcile.Reconciler + backend *routeBackend +} + +// New builds the Host. A nil identity inspects the running host; tests pass +// a fixed one so configuration and lifecycle checks do not depend on +// permission to inspect PID 1's network namespace. +func New(c Config, id *Identity) (*Host, error) { + if !treatment.ValidID(c.AgentID) || !treatment.ValidID(c.Location) { + return nil, errors.New("invalid local agent identity") + } + c.applyDefaults() + if id == nil { + v, err := HostIdentity() + if err != nil { + return nil, err + } + id = &v + } + b := &routeBackend{TreatmentBackend: &goip.TreatmentBackend{Table: c.Table, Priority: c.RulePriority, Protocol: c.Protocol, Enabled: c.EnableApply}} + for _, v := range c.ProtectedPrefixes { + p, err := netip.ParsePrefix(v) + if err != nil { + return nil, err + } + b.Protected = append(b.Protected, p) + } + if c.EnableApply && len(b.Protected) == 0 { + return nil, errors.New("protected_prefixes must cover management, DNS and telemetry networks before enabling application") + } + h := &Host{Config: c, Identity: *id, backend: b} + h.Reconciler = &reconcile.Reconciler{Journal: reconcile.Journal{Dir: c.Directory}, Backend: b, AgentID: c.AgentID, Location: c.Location, BootID: id.BootID} + return h, nil +} + +// Protect adds a prefix the backend must never override, such as the +// controller's own addresses. +func (h *Host) Protect(p netip.Prefix) { h.backend.Protected = append(h.backend.Protected, p) } + +// SimpleHostRouting reports whether the host's rule table is the plain shape +// the treatment routes are designed for. +func (h *Host) SimpleHostRouting(ctx context.Context) bool { return h.backend.Check(ctx) == nil } + +// routeBackend adapts goip's treatment backend to the reconcile.Backend +// contract: it validates the domain treatment and maps it onto goip's own +// route type, so goip never imports the treatment packages. +type routeBackend struct{ *goip.TreatmentBackend } + +func (b *routeBackend) Plan(ctx context.Context, t *treatment.Treatment) ([]byte, error) { + if err := treatment.ValidateTreatment(t); err != nil { + return nil, err + } + s := t.Settings + return b.TreatmentBackend.Plan(ctx, &goip.RouteTreatment{Destination: t.Destination.String(), Settings: &goip.TCPRouteSettings{ + CongestionControl: s.CongestionControl, CongestionLock: s.CongestionLock, RtoMinMs: s.RTOMinMs, + InitialCongestionWindow: s.InitialCongestionWindow, InitialReceiveWindow: s.InitialReceiveWindow, + QuickAck: s.QuickAck, Ecn: s.ECN, + }}) +} diff --git a/pkg/treatment/local/local_test.go b/pkg/treatment/local/local_test.go new file mode 100644 index 000000000..675e06cf6 --- /dev/null +++ b/pkg/treatment/local/local_test.go @@ -0,0 +1,110 @@ +package local + +import ( + "context" + "os" + "path/filepath" + "strings" + "testing" +) + +func TestLoadConfigContracts(t *testing.T) { + for _, tc := range []struct{ name, description, input, wantError string }{ + {"defaults", "positive: minimal config receives safe defaults", `{"agent_id":"host","location":"dc","controller":"localhost:9000"}`, ""}, + {"explicit", "positive: explicit private route settings are preserved", `{"agent_id":"host","location":"dc","controller":"localhost:9000","table":10003,"rule_priority":30001,"protocol":243}`, ""}, + {"unknown", "negative: typos cannot silently disable settings", `{"agent_id":"host","location":"dc","controller":"localhost:9000","enable_aply":true}`, "unknown field"}, + {"trailing", "negative: two JSON documents are rejected", `{} {}`, "trailing"}, + {"missing", "negative: enrollment fields are required", `{}`, "required"}, + {"bad-id", "negative: path-like agent identity is rejected", `{"agent_id":"../host","location":"dc","controller":"localhost:9000"}`, "required"}, + {"malformed", "corner: truncated JSON is rejected", `{`, "EOF"}, + {"oversized", "boundary: oversized config cannot bypass the limit", `{"agent_id":"host","location":"dc","controller":"` + strings.Repeat("x", 65537) + `"}`, "EOF"}, + } { + t.Run(tc.name, func(t *testing.T) { + path := filepath.Join(t.TempDir(), "agent.json") + if err := os.WriteFile(path, []byte(tc.input), 0600); err != nil { + t.Fatal(err) + } + c, err := LoadConfig(path) + if tc.wantError != "" { + if err == nil || !strings.Contains(err.Error(), tc.wantError) { + t.Fatalf("%s: expected %q, got %v", tc.description, tc.wantError, err) + } + return + } + if err != nil { + t.Fatal(err) + } + wantTable, wantPriority, wantProtocol := uint32(10002), uint32(30000), uint8(242) + if tc.name == "explicit" { + wantTable++ + wantPriority++ + wantProtocol++ + } + if c.Table != wantTable || c.RulePriority != wantPriority || c.Protocol != wantProtocol || c.EnableApply || c.Directory != DefaultDirectory { + t.Fatalf("%s: unexpected config %+v", tc.description, c) + } + if !strings.Contains(c.String(), "agent=host") { + t.Fatal("config diagnostic missing identity") + } + }) + } + if _, err := LoadConfig(filepath.Join(t.TempDir(), "missing.json")); err == nil { + t.Fatal("missing file accepted") + } +} + +func TestNewHostContracts(t *testing.T) { + testIdentity := &Identity{BootID: "boot", NetnsInode: 123} + for _, tc := range []struct { + name, description string + change func(*Config) + wantError string + }{ + {"offline", "positive: offline construction needs no TLS, DNS or controller", func(*Config) {}, ""}, + {"identity", "negative: invalid agent identity fails before initialization", func(c *Config) { c.AgentID = "../host" }, "identity"}, + {"location", "negative: invalid location fails before initialization", func(c *Config) { c.Location = "" }, "identity"}, + {"bad-prefix", "negative: malformed protected prefixes fail closed", func(c *Config) { c.ProtectedPrefixes = []string{"bad"} }, "ParsePrefix"}, + {"no-exclusions", "negative: application requires management exclusions", func(c *Config) { c.EnableApply = true }, "protected_prefixes"}, + {"protected", "positive: explicit exclusions allow backend construction without writes", func(c *Config) { c.EnableApply = true; c.ProtectedPrefixes = []string{"192.0.2.0/24"} }, ""}, + {"defaults", "boundary: a config built in code without the loader still gets the table defaults", func(c *Config) { c.Table, c.RulePriority, c.Protocol = 0, 0, 0 }, ""}, + } { + t.Run(tc.name, func(t *testing.T) { + c := Config{AgentID: "host", Location: "dc", Directory: t.TempDir(), Table: 10002, RulePriority: 30000, Protocol: 242} + tc.change(&c) + h, err := New(c, testIdentity) + if tc.wantError != "" { + if err == nil || !strings.Contains(err.Error(), tc.wantError) { + t.Fatalf("%s: got %v want %q", tc.description, err, tc.wantError) + } + return + } + if err != nil { + t.Fatal(err) + } + if h.Identity != *testIdentity || h.Reconciler.BootID != "boot" || h.Reconciler.AgentID != "host" || h.Reconciler.Location != "dc" || h.Reconciler.Journal.Dir != c.Directory { + t.Fatalf("%s: reconciler not wired from config: %+v", tc.description, h.Reconciler) + } + if h.Config.Table != 10002 || h.Config.RulePriority != 30000 || h.Config.Protocol != 242 { + t.Fatalf("%s: defaults not applied: %+v", tc.description, h.Config) + } + before := len(h.backend.Protected) + h.Protect(mustPrefix("198.51.100.1/32")) + if len(h.backend.Protected) != before+1 { + t.Fatal("Protect did not extend the exclusions") + } + }) + } +} + +// Plan validates the domain treatment before goip sees it, so an invalid +// treatment never reaches the kernel planner. +func TestRouteBackendPlanValidates(t *testing.T) { + h, err := New(Config{AgentID: "host", Location: "dc", Directory: t.TempDir()}, &Identity{BootID: "boot", NetnsInode: 1}) + if err != nil { + t.Fatal(err) + } + _, err = h.backend.Plan(context.Background(), nil) + if err == nil || !strings.Contains(err.Error(), "treatment requires") { + t.Fatalf("nil treatment planned: %v", err) + } +} diff --git a/pkg/treatment/model.go b/pkg/treatment/model.go index 6d600c688..556613cd9 100644 --- a/pkg/treatment/model.go +++ b/pkg/treatment/model.go @@ -77,6 +77,26 @@ type Assignment struct { Generation uint64 } +// Equal compares every field, including the predecessor by value. +func (a Assignment) Equal(o Assignment) bool { + if a.ExperimentID != o.ExperimentID || a.Mode != o.Mode || a.Generation != o.Generation || !a.LeaseExpiresAt.Equal(o.LeaseExpiresAt) || !a.Treatment.Equal(o.Treatment) { + return false + } + if a.Predecessor == nil || o.Predecessor == nil { + return a.Predecessor == o.Predecessor + } + return a.Predecessor.Equal(*o.Predecessor) +} + +// Clone copies the predecessor so the two values share nothing. +func (a Assignment) Clone() Assignment { + if a.Predecessor != nil { + p := *a.Predecessor + a.Predecessor = &p + } + return a +} + // DesiredState is everything the controller wants one agent to hold. type DesiredState struct { AgentID string @@ -86,6 +106,35 @@ type DesiredState struct { IssuedAt time.Time } +// Equal compares the header and every assignment in order. +func (d *DesiredState) Equal(o *DesiredState) bool { + if d == nil || o == nil { + return d == o + } + if d.AgentID != o.AgentID || d.Revision != o.Revision || d.Suspended != o.Suspended || !d.IssuedAt.Equal(o.IssuedAt) || len(d.Assignments) != len(o.Assignments) { + return false + } + for i := range d.Assignments { + if !d.Assignments[i].Equal(o.Assignments[i]) { + return false + } + } + return true +} + +// Clone deep-copies the assignments. +func (d *DesiredState) Clone() *DesiredState { + if d == nil { + return nil + } + c := *d + c.Assignments = make([]Assignment, len(d.Assignments)) + for i := range d.Assignments { + c.Assignments[i] = d.Assignments[i].Clone() + } + return &c +} + // Find returns a pointer into d.Assignments for the experiment, or nil. func (d *DesiredState) Find(experimentID string) *Assignment { for i := range d.Assignments { diff --git a/pkg/treatment/reconcile/contracts_test.go b/pkg/treatment/reconcile/contracts_test.go index 44faecb3b..49ddda791 100644 --- a/pkg/treatment/reconcile/contracts_test.go +++ b/pkg/treatment/reconcile/contracts_test.go @@ -7,10 +7,8 @@ import ( "testing" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/timestamppb" ) type faultBackend struct { @@ -20,7 +18,7 @@ type faultBackend struct { func (b *faultBackend) BeginReconcile() {} -func (b *faultBackend) Plan(ctx context.Context, t *pb.Treatment) ([]byte, error) { +func (b *faultBackend) Plan(ctx context.Context, t *treatment.Treatment) ([]byte, error) { if b.fail == "plan" { return nil, errors.New("plan failed") } @@ -51,8 +49,8 @@ func TestReconcileFailureRecovery(t *testing.T) { t.Run(tc.name, func(t *testing.T) { now := time.Unix(1700000000, 0) b := &faultBackend{fakeBackend: &fakeBackend{routes: map[string]bool{}}} - r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot", Now: func() time.Time { return now }} - d := fixtures.DesiredWire(now) + r := newReconciler(t, b, &now) + d := fixtures.Desired(now) if tc.name == "plan" { if _, err := r.Run(context.Background(), d, ModeFull); err != nil { t.Fatal(err) @@ -60,12 +58,12 @@ func TestReconcileFailureRecovery(t *testing.T) { } b.fail = tc.name s, err := r.Run(context.Background(), d, ModeFull) - if err == nil || !strings.Contains(err.Error(), tc.name+" failed") || s.Records[0].Status != pb.ApplyStatus_APPLY_STATUS_FAILED || b.enabled || len(b.routes) != tc.wantRoutes { + if err == nil || !strings.Contains(err.Error(), tc.name+" failed") || s.Records[0].Status != treatment.ApplyFailed || b.enabled || len(b.routes) != tc.wantRoutes { t.Fatalf("%s: err=%v enabled=%t routes=%d state=%v", tc.description, err, b.enabled, len(b.routes), s) } b.fail = "" s, err = r.Run(context.Background(), nil, ModeFull) - if err != nil || !b.enabled || len(b.routes) != 1 || s.Records[0].Status != pb.ApplyStatus_APPLY_STATUS_VERIFIED { + if err != nil || !b.enabled || len(b.routes) != 1 || s.Records[0].Status != treatment.ApplyVerified { t.Fatalf("retry did not recover: %v %v", s, err) } }) @@ -75,25 +73,26 @@ func TestReconcileFailureRecovery(t *testing.T) { func TestReconcileRevisionContracts(t *testing.T) { for _, tc := range []struct { name, description string - change func(*pb.DesiredState) + change func(*treatment.DesiredState) mode Mode wantError string }{ - {"idempotent", "identical state verifies without reinstall", func(*pb.DesiredState) {}, ModeFull, ""}, - {"conflict", "same revision cannot carry different contents", func(d *pb.DesiredState) { d.Suspended = true }, ModeFull, "conflicting"}, - {"wrong-host", "another agent's state is rejected", func(d *pb.DesiredState) { d.AgentId = "other" }, ModeFull, "another agent"}, - {"watchdog", "watchdog cannot accept controller instructions", func(*pb.DesiredState) {}, ModeExpiryOnly, "watchdog"}, + {"idempotent", "identical state verifies without reinstall", func(*treatment.DesiredState) {}, ModeFull, ""}, + {"conflict", "same revision cannot carry different contents", func(d *treatment.DesiredState) { d.Suspended = true }, ModeFull, "conflicting"}, + {"wrong-host", "another agent's state is rejected", func(d *treatment.DesiredState) { d.AgentID = "other" }, ModeFull, "another agent"}, + {"watchdog", "watchdog cannot accept controller instructions", func(*treatment.DesiredState) {}, ModeExpiryOnly, "watchdog"}, + {"invalid", "an invalid desired state is rejected before the journal changes", func(d *treatment.DesiredState) { d.Revision = 0 }, ModeFull, "desired-state header"}, } { t.Run(tc.name, func(t *testing.T) { now := time.Unix(1700000000, 0) b := &fakeBackend{routes: map[string]bool{}} - r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot", Now: func() time.Time { return now }} - d := fixtures.DesiredWire(now) + r := newReconciler(t, b, &now) + d := fixtures.Desired(now) s, err := r.Run(context.Background(), d, ModeFull) if err != nil { t.Fatal(err) } - original := proto.Clone(s.Desired) + original := s.Desired.Clone() tc.change(d) _, err = r.Run(context.Background(), d, tc.mode) if tc.wantError == "" { @@ -106,8 +105,8 @@ func TestReconcileRevisionContracts(t *testing.T) { if b.installs != 1 { t.Fatal("rejected/idempotent state reinstalled route") } - if err = r.Journal.WithLock(func(s *pb.LocalState, _ func() error) error { - if !proto.Equal(s.Desired, original) { + if err = r.Journal.WithLock(func(s *State, _ func() error) error { + if !s.Desired.Equal(original) { t.Fatal("rejected request changed journal") } return nil @@ -122,30 +121,56 @@ func TestReconcileLeaseBoundary(t *testing.T) { for _, tc := range []struct { description string offset time.Duration - want pb.ApplyStatus + want treatment.ApplyStatus routes int }{ - {"one nanosecond before expiry is active", time.Nanosecond, pb.ApplyStatus_APPLY_STATUS_VERIFIED, 1}, - {"exact expiry is withdrawn", 0, pb.ApplyStatus_APPLY_STATUS_EXPIRED, 0}, - {"one nanosecond after expiry is withdrawn", -time.Nanosecond, pb.ApplyStatus_APPLY_STATUS_EXPIRED, 0}, + {"one nanosecond before expiry is active", time.Nanosecond, treatment.ApplyVerified, 1}, + {"exact expiry is withdrawn", 0, treatment.ApplyExpired, 0}, + {"one nanosecond after expiry is withdrawn", -time.Nanosecond, treatment.ApplyExpired, 0}, } { t.Run(tc.description, func(t *testing.T) { now := time.Unix(1700000000, 0) b := &fakeBackend{routes: map[string]bool{}} - r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot", Now: func() time.Time { return now }} - d := fixtures.DesiredWire(now) - d.Assignments[0].LeaseExpiresAt = timestamppb.New(now.Add(tc.offset)) + r := newReconciler(t, b, &now) + d := fixtures.Desired(now) + d.Assignments[0].LeaseExpiresAt = now.Add(tc.offset) s, err := r.Run(context.Background(), d, ModeFull) if err != nil || s.Records[0].Status != tc.want || len(b.routes) != tc.routes { t.Fatalf("got %v %v routes=%d; want %v routes=%d", s, err, len(b.routes), tc.want, tc.routes) } record := s.Records[0] - if !record.ReceivedAt.AsTime().Equal(now) { + if !record.ReceivedAt.Equal(now) { t.Fatal("receipt timestamp missing") } - if tc.routes == 1 && !record.AppliedAt.AsTime().Equal(now) { + if tc.routes == 1 && !record.AppliedAt.Equal(now) { t.Fatal("application timestamp missing") } }) } } + +// Reports strips the kernel-local fields, which is how the check-in path +// guarantees route bytes never cross the wire. +func TestReportsStripLocalFields(t *testing.T) { + now := time.Unix(1700000000, 0) + b := &fakeBackend{routes: map[string]bool{}} + r := newReconciler(t, b, &now) + s, err := r.Run(context.Background(), fixtures.Desired(now), ModeFull) + if err != nil { + t.Fatal(err) + } + if len(s.Records[0].InstalledRoute) == 0 || s.Records[0].InstalledBootID != "boot-1" { + t.Fatal("fixture did not install a route") + } + reports := s.Reports() + if len(reports) != 1 || reports[0].Status != treatment.ApplyVerified || reports[0].Assignment.ExperimentID != "trial-1" { + t.Fatalf("report mismatch: %+v", reports) + } + reports[0].Assignment.Treatment.ID = "mutated" + if s.Records[0].Assignment.Treatment.ID == "mutated" { + t.Fatal("report aliases the snapshot") + } + if (*State)(nil).Reports() != nil || (*State)(nil).Held() || (*State)(nil).Revision() != 0 { + t.Fatal("nil state accessors must be safe") + } +} diff --git a/pkg/treatment/reconcile/journal.go b/pkg/treatment/reconcile/journal.go index 36035af49..2980266fe 100644 --- a/pkg/treatment/reconcile/journal.go +++ b/pkg/treatment/reconcile/journal.go @@ -8,11 +8,8 @@ import ( "os" "path/filepath" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" - "github.com/randomizedcoder/xtcp2/pkg/treatment/wire" "golang.org/x/sys/unix" - "google.golang.org/protobuf/encoding/prototext" ) // Journal serializes the daemon, watchdog and local administrative commands. @@ -20,7 +17,11 @@ import ( // readable projections only, never an input to recovery. type Journal struct{ Dir string } -func (j Journal) WithLock(fn func(*pb.LocalState, func() error) error) error { +// WithLock loads the snapshot under the exclusive journal lock, runs fn with +// a save callback that persists the current state atomically, and refreshes +// the per-assignment projections if anything was saved. A snapshot that +// fails validation never reaches fn. +func (j Journal) WithLock(fn func(*State, func() error) error) error { if err := os.MkdirAll(j.Dir, 0700); err != nil { return err } @@ -33,10 +34,10 @@ func (j Journal) WithLock(fn func(*pb.LocalState, func() error) error) error { return fmt.Errorf("treatment journal busy: %w", err) } defer unix.Flock(fd, unix.LOCK_UN) - s := new(pb.LocalState) - f, err := os.OpenFile(filepath.Join(j.Dir, "state.textproto"), os.O_RDONLY|unix.O_NOFOLLOW, 0) + s := new(State) + f, err := os.OpenFile(filepath.Join(j.Dir, SnapshotFile), os.O_RDONLY|unix.O_NOFOLLOW, 0) if err == nil { - b, rerr := io.ReadAll(io.LimitReader(f, treatment.MaxStateBytes+1)) + b, rerr := readLimited(f) cerr := f.Close() if rerr != nil { return rerr @@ -44,46 +45,22 @@ func (j Journal) WithLock(fn func(*pb.LocalState, func() error) error) error { if cerr != nil { return cerr } - if len(b) > treatment.MaxStateBytes { - return errors.New("journal too large") - } - if err = prototext.Unmarshal(b, s); err != nil { - return fmt.Errorf("invalid treatment journal: %w", err) - } - if s.Desired != nil { - if err = wire.ValidateDesired(s.Desired); err != nil { - return fmt.Errorf("invalid persisted desired state: %w", err) - } - } - if len(s.Records) > treatment.MaxAssignments*2 { - return errors.New("too many journal records") - } - seen := map[string]bool{} - for _, r := range s.Records { - if r.Assignment == nil || !treatment.ValidID(r.Assignment.ExperimentId) { - return errors.New("invalid journal record") - } - if seen[r.Assignment.ExperimentId] { - return errors.New("duplicate journal record") - } - seen[r.Assignment.ExperimentId] = true - if err = wire.ValidateTreatment(r.Assignment.Treatment); err != nil { - return fmt.Errorf("invalid journal treatment: %w", err) - } + if s, err = UnmarshalText(b); err != nil { + return err } } else if !errors.Is(err, os.ErrNotExist) { return err } dirty := false save := func() error { - b, err := prototext.MarshalOptions{Multiline: true, Indent: " "}.Marshal(s) + b, err := MarshalText(s) if err != nil { return err } if len(b) > treatment.MaxStateBytes { return errors.New("journal exceeds size limit") } - if err = atomicFile(j.Dir, "state.textproto", b); err != nil { + if err = atomicFile(j.Dir, SnapshotFile, b); err != nil { return err } dirty = true @@ -91,12 +68,13 @@ func (j Journal) WithLock(fn func(*pb.LocalState, func() error) error) error { } result := fn(s, save) if dirty { - for _, r := range s.Records { - b, err := prototext.MarshalOptions{Multiline: true, Indent: " "}.Marshal(r) + for i := range s.Records { + r := &s.Records[i] + b, err := recordText(r) if err != nil { return errors.Join(result, err) } - name := "assignment-" + r.Assignment.ExperimentId + ".textproto" + name := "assignment-" + r.Assignment.ExperimentID + ".textproto" old, _ := os.ReadFile(filepath.Join(j.Dir, name)) if bytes.Equal(old, b) { continue @@ -109,6 +87,17 @@ func (j Journal) WithLock(fn func(*pb.LocalState, func() error) error) error { return result } +func readLimited(f *os.File) ([]byte, error) { + b, err := io.ReadAll(io.LimitReader(f, treatment.MaxStateBytes+1)) + if err != nil { + return nil, err + } + if len(b) > treatment.MaxStateBytes { + return nil, errors.New("journal too large") + } + return b, nil +} + func atomicFile(dir, name string, b []byte) error { f, err := os.CreateTemp(dir, ".pending-") if err != nil { diff --git a/pkg/treatment/reconcile/journal_test.go b/pkg/treatment/reconcile/journal_test.go index dd4b5c01b..f56c9ef85 100644 --- a/pkg/treatment/reconcile/journal_test.go +++ b/pkg/treatment/reconcile/journal_test.go @@ -8,47 +8,76 @@ import ( "testing" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" + jpb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment_journal" "github.com/randomizedcoder/xtcp2/pkg/treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" "golang.org/x/sys/unix" "google.golang.org/protobuf/encoding/prototext" + "google.golang.org/protobuf/types/known/timestamppb" ) +// legacySnapshot is the pre-journal-package format: prototext of the RPC +// LocalState message. It must fail closed, never half-load. +const legacySnapshot = `desired: { + agent_id: "host-1" + revision: 1 + issued_at: { seconds: 1700000000 } +} +records: { + assignment: { experiment_id: "trial-1" } +} +` + func TestJournalRejectsInvalidSnapshots(t *testing.T) { for _, tc := range []struct { name, description string - change func(*pb.LocalState) + change func(*jpb.Journal) + raw string want string }{ - {"valid", "valid snapshot can be reopened", func(*pb.LocalState) {}, ""}, - {"bad-desired", "invalid desired state is not recovered", func(s *pb.LocalState) { s.Desired.Revision = 0 }, "invalid persisted"}, - {"missing-assignment", "records require an assignment", func(s *pb.LocalState) { s.Records[0].Assignment = nil }, "invalid journal record"}, - {"bad-id", "projection names cannot contain paths", func(s *pb.LocalState) { s.Records[0].Assignment.ExperimentId = "../escape"; s.Desired = nil }, "invalid journal record"}, - {"duplicate", "duplicate records cannot shadow each other", func(s *pb.LocalState) { s.Records = append(s.Records, s.Records[0]) }, "duplicate"}, - {"bad-treatment", "invalid treatment cannot be recovered", func(s *pb.LocalState) { s.Records[0].Assignment.Treatment = nil; s.Desired = nil }, "invalid journal treatment"}, + {"valid", "positive: valid snapshot can be reopened", func(*jpb.Journal) {}, "", ""}, + {"bad-desired", "negative: invalid desired state is not recovered", func(j *jpb.Journal) { j.Desired.Revision = 0 }, "", "invalid persisted"}, + {"missing-assignment", "negative: records require an assignment", func(j *jpb.Journal) { j.Records[0].Assignment = nil }, "", "missing assignment"}, + {"bad-id", "negative: projection names cannot contain paths", func(j *jpb.Journal) { j.Records[0].Assignment.ExperimentId = "../escape"; j.Desired = nil }, "", "invalid journal record"}, + {"duplicate", "negative: duplicate records cannot shadow each other", func(j *jpb.Journal) { j.Records = append(j.Records, j.Records[0]) }, "", "duplicate"}, + {"bad-treatment", "negative: invalid treatment cannot be recovered", func(j *jpb.Journal) { j.Records[0].Assignment.Treatment = nil; j.Desired = nil }, "", "invalid journal treatment"}, + {"unknown-mode", "negative: a mode from a newer writer is refused, not zeroed", func(j *jpb.Journal) { j.Records[0].Assignment.Mode = 99; j.Desired = nil }, "", "unknown assignment mode"}, + {"unknown-status", "negative: an apply status from a newer writer is refused", func(j *jpb.Journal) { j.Records[0].Status = 42 }, "", "unknown apply status"}, + {"bad-timestamp", "negative: an out-of-range timestamp is a parse error", func(j *jpb.Journal) { j.UpdatedAt = ×tamppb.Timestamp{Seconds: 1 << 60} }, "", "updated_at"}, + {"bad-identity", "negative: a path-like agent id in the header is refused", func(j *jpb.Journal) { j.AgentId = "../x" }, "", "invalid journal identity"}, + {"legacy-format", "corner: the pre-journal-package LocalState text fails closed", nil, legacySnapshot, "invalid treatment journal"}, + {"garbage", "corner: non-prototext bytes fail closed", nil, "\x00\x01 not text", "invalid treatment journal"}, } { t.Run(tc.name, func(t *testing.T) { j := Journal{Dir: t.TempDir()} - d := fixtures.DesiredWire(time.Unix(1700000000, 0)) - s := &pb.LocalState{Desired: d, Records: []*pb.ApplicationRecord{{Assignment: d.Assignments[0]}}} - tc.change(s) - raw, err := prototext.Marshal(s) - if err != nil { - t.Fatal(err) + raw := []byte(tc.raw) + if tc.change != nil { + d := fixtures.Desired(time.Unix(1700000000, 0)) + s := &State{AgentID: "host-1", Location: "dc1", BootID: "boot-1", Desired: d, Records: []Record{{ApplicationRecord: treatment.ApplicationRecord{Assignment: d.Assignments[0]}}}} + p := toJournal(s) + tc.change(p) + var err error + if raw, err = prototext.Marshal(p); err != nil { + t.Fatal(err) + } } - if err = os.WriteFile(filepath.Join(j.Dir, "state.textproto"), raw, 0600); err != nil { + if err := os.WriteFile(filepath.Join(j.Dir, SnapshotFile), raw, 0600); err != nil { t.Fatal(err) } called := false - err = j.WithLock(func(*pb.LocalState, func() error) error { called = true; return nil }) + err := j.WithLock(func(*State, func() error) error { called = true; return nil }) if tc.want == "" { if err != nil || !called { t.Fatalf("%s: %v", tc.description, err) } - } else if err == nil || !strings.Contains(err.Error(), tc.want) || called { + return + } + if err == nil || !strings.Contains(err.Error(), tc.want) || called { t.Fatalf("%s: got %v, callback=%t; want %q without callback", tc.description, err, called, tc.want) } + if _, rerr := ReadSnapshot(j.Dir); rerr == nil || !strings.Contains(rerr.Error(), tc.want) { + t.Fatalf("%s: lock-free read disagrees: %v", tc.description, rerr) + } }) } } @@ -57,7 +86,7 @@ func TestJournalConcurrentExclusion(t *testing.T) { j := Journal{Dir: t.TempDir()} entered, release, done := make(chan struct{}), make(chan struct{}), make(chan error, 1) go func() { - done <- j.WithLock(func(s *pb.LocalState, save func() error) error { + done <- j.WithLock(func(s *State, save func() error) error { close(entered) <-release s.SuspensionReason = "first writer" @@ -66,7 +95,7 @@ func TestJournalConcurrentExclusion(t *testing.T) { }() <-entered called := false - err := j.WithLock(func(*pb.LocalState, func() error) error { called = true; return nil }) + err := j.WithLock(func(*State, func() error) error { called = true; return nil }) close(release) if first := <-done; first != nil { t.Fatal(first) @@ -74,7 +103,7 @@ func TestJournalConcurrentExclusion(t *testing.T) { if !errors.Is(err, unix.EWOULDBLOCK) || called { t.Fatalf("competing writer should fail busy before callback: %v callback=%t", err, called) } - if err = j.WithLock(func(s *pb.LocalState, _ func() error) error { + if err = j.WithLock(func(s *State, _ func() error) error { if s.SuspensionReason != "first writer" { t.Fatal("lost first write") } @@ -82,10 +111,20 @@ func TestJournalConcurrentExclusion(t *testing.T) { }); err != nil { t.Fatal("lock not released", err) } + // The lock-free reader sees the committed snapshot while the lock is held. + hold := make(chan struct{}) + go func() { + _ = j.WithLock(func(*State, func() error) error { <-hold; return nil }) + }() + s, err := ReadSnapshot(j.Dir) + close(hold) + if err != nil || s.SuspensionReason != "first writer" { + t.Fatalf("lock-free read under lock: %v %+v", err, s) + } } func TestJournalSymlinkProtection(t *testing.T) { - for _, name := range []string{".lock", "state.textproto"} { + for _, name := range []string{".lock", SnapshotFile} { t.Run(name, func(t *testing.T) { dir := t.TempDir() target := filepath.Join(t.TempDir(), "unrelated") @@ -96,10 +135,15 @@ func TestJournalSymlinkProtection(t *testing.T) { t.Fatal(err) } called := false - err := (Journal{Dir: dir}).WithLock(func(*pb.LocalState, func() error) error { called = true; return nil }) + err := (Journal{Dir: dir}).WithLock(func(*State, func() error) error { called = true; return nil }) if err == nil || called { t.Fatal("followed journal symlink") } + if name == SnapshotFile { + if _, err = ReadSnapshot(dir); err == nil { + t.Fatal("lock-free reader followed symlink") + } + } raw, err := os.ReadFile(target) if err != nil || string(raw) != "sentinel" { t.Fatal("modified symlink target") @@ -110,7 +154,7 @@ func TestJournalSymlinkProtection(t *testing.T) { func TestJournalSizeLimit(t *testing.T) { dir := t.TempDir() - f, err := os.Create(filepath.Join(dir, "state.textproto")) + f, err := os.Create(filepath.Join(dir, SnapshotFile)) if err != nil { t.Fatal(err) } @@ -120,8 +164,36 @@ func TestJournalSizeLimit(t *testing.T) { if err = f.Close(); err != nil { t.Fatal(err) } - err = (Journal{Dir: dir}).WithLock(func(*pb.LocalState, func() error) error { t.Fatal("oversized snapshot reached callback"); return nil }) + err = (Journal{Dir: dir}).WithLock(func(*State, func() error) error { t.Fatal("oversized snapshot reached callback"); return nil }) if err == nil || !strings.Contains(err.Error(), "too large") { t.Fatalf("want size limit, got %v", err) } + if _, err = ReadSnapshot(dir); err == nil || !strings.Contains(err.Error(), "too large") { + t.Fatalf("lock-free reader ignored size limit: %v", err) + } +} + +func TestJournalProjectionsFollowSnapshot(t *testing.T) { + j := Journal{Dir: t.TempDir()} + d := fixtures.Desired(time.Unix(1700000000, 0)) + err := j.WithLock(func(s *State, save func() error) error { + s.AgentID = "host-1" + s.Desired = d + s.Records = []Record{{ApplicationRecord: treatment.ApplicationRecord{Assignment: d.Assignments[0], Status: treatment.ApplyPending}, InstalledRoute: []byte{1, 2, 3}, InstalledBootID: "boot-1"}} + return save() + }) + if err != nil { + t.Fatal(err) + } + raw, err := os.ReadFile(filepath.Join(j.Dir, "assignment-trial-1.textproto")) + if err != nil { + t.Fatal(err) + } + var p jpb.Record + if err = prototext.Unmarshal(raw, &p); err != nil || p.Assignment.ExperimentId != "trial-1" || string(p.InstalledRoute) != "\x01\x02\x03" || p.InstalledBootId != "boot-1" { + t.Fatalf("projection does not mirror the record: %v %+v", err, &p) + } + if _, err = os.Lstat(filepath.Join(j.Dir, ".pending-")); !errors.Is(err, os.ErrNotExist) { + t.Fatal("temp file left behind") + } } diff --git a/pkg/treatment/reconcile/journalpb.go b/pkg/treatment/reconcile/journalpb.go new file mode 100644 index 000000000..b56c9ef6e --- /dev/null +++ b/pkg/treatment/reconcile/journalpb.go @@ -0,0 +1,276 @@ +package reconcile + +import ( + "errors" + "fmt" + "net/netip" + "os" + "path/filepath" + "time" + + jpb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment_journal" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "golang.org/x/sys/unix" + "google.golang.org/protobuf/encoding/prototext" + "google.golang.org/protobuf/types/known/timestamppb" +) + +// The journal file format is xtcp_treatment_journal.v1.Journal in prototext. +// It is converted to and from State here, and every load runs the domain +// validators so a hand-edited or truncated file fails closed before any +// kernel mutation. + +// SnapshotFile is the journal snapshot's name inside Journal.Dir. +const SnapshotFile = "state.textproto" + +var textOptions = prototext.MarshalOptions{Multiline: true, Indent: " "} + +func stamp(t time.Time) *timestamppb.Timestamp { + if t.IsZero() { + return nil + } + return timestamppb.New(t) +} + +func parseStamp(field string, t *timestamppb.Timestamp) (time.Time, error) { + if t == nil { + return time.Time{}, nil + } + if err := t.CheckValid(); err != nil { + return time.Time{}, fmt.Errorf("%s: %w", field, err) + } + return t.AsTime(), nil +} + +func clonePtr[T any](p *T) *T { + if p == nil { + return nil + } + v := *p + return &v +} + +func settingsToJournal(s treatment.TCPSettings) *jpb.TCPSettings { + return &jpb.TCPSettings{CongestionControl: clonePtr(s.CongestionControl), CongestionLock: s.CongestionLock, RtoMinMs: clonePtr(s.RTOMinMs), InitialCongestionWindow: clonePtr(s.InitialCongestionWindow), InitialReceiveWindow: clonePtr(s.InitialReceiveWindow), QuickAck: clonePtr(s.QuickAck), Ecn: clonePtr(s.ECN)} +} + +func settingsFromJournal(p *jpb.TCPSettings) treatment.TCPSettings { + if p == nil { + return treatment.TCPSettings{} + } + return treatment.TCPSettings{CongestionControl: clonePtr(p.CongestionControl), CongestionLock: p.CongestionLock, RTOMinMs: clonePtr(p.RtoMinMs), InitialCongestionWindow: clonePtr(p.InitialCongestionWindow), InitialReceiveWindow: clonePtr(p.InitialReceiveWindow), QuickAck: clonePtr(p.QuickAck), ECN: clonePtr(p.Ecn)} +} + +func treatmentToJournal(t *treatment.Treatment) *jpb.Treatment { + if t == nil { + return nil + } + out := &jpb.Treatment{Id: t.ID, Revision: t.Revision, Settings: settingsToJournal(t.Settings), EcnFabricVerified: t.ECNFabricVerified} + if t.Destination.IsValid() { + out.Destination = t.Destination.String() + } + return out +} + +func treatmentFromJournal(p *jpb.Treatment) treatment.Treatment { + if p == nil { + return treatment.Treatment{} + } + t := treatment.Treatment{ID: p.Id, Revision: p.Revision, Settings: settingsFromJournal(p.Settings), ECNFabricVerified: p.EcnFabricVerified} + if d, err := netip.ParsePrefix(p.Destination); err == nil { + t.Destination = d + } + return t +} + +func assignmentToJournal(a *treatment.Assignment) *jpb.Assignment { + return &jpb.Assignment{ExperimentId: a.ExperimentID, Treatment: treatmentToJournal(&a.Treatment), Mode: jpb.AssignmentMode(a.Mode), LeaseExpiresAt: stamp(a.LeaseExpiresAt), Predecessor: treatmentToJournal(a.Predecessor), Generation: a.Generation} +} + +func assignmentFromJournal(p *jpb.Assignment) (treatment.Assignment, error) { + if p == nil { + return treatment.Assignment{}, errors.New("missing assignment") + } + mode := treatment.AssignmentMode(p.Mode) + if _, ok := jpb.AssignmentMode_name[int32(p.Mode)]; !ok || !mode.Valid() { + return treatment.Assignment{}, fmt.Errorf("unknown assignment mode %d", p.Mode) + } + lease, err := parseStamp("lease_expires_at", p.LeaseExpiresAt) + if err != nil { + return treatment.Assignment{}, err + } + a := treatment.Assignment{ExperimentID: p.ExperimentId, Treatment: treatmentFromJournal(p.Treatment), Mode: mode, LeaseExpiresAt: lease, Generation: p.Generation} + if p.Predecessor != nil { + pred := treatmentFromJournal(p.Predecessor) + a.Predecessor = &pred + } + return a, nil +} + +func desiredToJournal(d *treatment.DesiredState) *jpb.Desired { + if d == nil { + return nil + } + out := &jpb.Desired{Revision: d.Revision, Suspended: d.Suspended, IssuedAt: stamp(d.IssuedAt)} + for i := range d.Assignments { + out.Assignments = append(out.Assignments, assignmentToJournal(&d.Assignments[i])) + } + return out +} + +func desiredFromJournal(agentID string, p *jpb.Desired) (*treatment.DesiredState, error) { + if p == nil { + return nil, nil + } + issued, err := parseStamp("issued_at", p.IssuedAt) + if err != nil { + return nil, err + } + d := &treatment.DesiredState{AgentID: agentID, Revision: p.Revision, Suspended: p.Suspended, IssuedAt: issued} + for _, a := range p.Assignments { + v, err := assignmentFromJournal(a) + if err != nil { + return nil, err + } + d.Assignments = append(d.Assignments, v) + } + return d, nil +} + +func recordToJournal(r *Record) *jpb.Record { + return &jpb.Record{Assignment: assignmentToJournal(&r.Assignment), DesiredRevision: r.DesiredRevision, Status: jpb.ApplyStatus(r.Status), ReceivedAt: stamp(r.ReceivedAt), AttemptedAt: stamp(r.AttemptedAt), AppliedAt: stamp(r.AppliedAt), VerifiedAt: stamp(r.VerifiedAt), RemovedAt: stamp(r.RemovedAt), Error: r.Error, InstalledRoute: append([]byte(nil), r.InstalledRoute...), InstalledBootId: r.InstalledBootID, ExpiredLatched: r.ExpiredLatched} +} + +func recordFromJournal(p *jpb.Record) (Record, error) { + if p == nil { + return Record{}, errors.New("missing record") + } + a, err := assignmentFromJournal(p.Assignment) + if err != nil { + return Record{}, err + } + status := treatment.ApplyStatus(p.Status) + if _, ok := jpb.ApplyStatus_name[int32(p.Status)]; !ok || !status.Valid() { + return Record{}, fmt.Errorf("unknown apply status %d", p.Status) + } + r := Record{ApplicationRecord: treatment.ApplicationRecord{Assignment: a, DesiredRevision: p.DesiredRevision, Status: status, Error: p.Error, ExpiredLatched: p.ExpiredLatched}, InstalledBootID: p.InstalledBootId} + if len(p.InstalledRoute) > 0 { + r.InstalledRoute = append([]byte(nil), p.InstalledRoute...) + } + for _, f := range []struct { + name string + src *timestamppb.Timestamp + dst *time.Time + }{{"received_at", p.ReceivedAt, &r.ReceivedAt}, {"attempted_at", p.AttemptedAt, &r.AttemptedAt}, {"applied_at", p.AppliedAt, &r.AppliedAt}, {"verified_at", p.VerifiedAt, &r.VerifiedAt}, {"removed_at", p.RemovedAt, &r.RemovedAt}} { + if *f.dst, err = parseStamp(f.name, f.src); err != nil { + return Record{}, err + } + } + return r, nil +} + +func toJournal(s *State) *jpb.Journal { + out := &jpb.Journal{AgentId: s.AgentID, Location: s.Location, BootId: s.BootID, Desired: desiredToJournal(s.Desired), LocallySuspended: s.LocallySuspended, SuspensionReason: s.SuspensionReason, UpdatedAt: stamp(s.UpdatedAt)} + for i := range s.Records { + out.Records = append(out.Records, recordToJournal(&s.Records[i])) + } + return out +} + +func fromJournal(p *jpb.Journal) (*State, error) { + s := &State{AgentID: p.AgentId, Location: p.Location, BootID: p.BootId, LocallySuspended: p.LocallySuspended, SuspensionReason: p.SuspensionReason} + var err error + if s.Desired, err = desiredFromJournal(p.AgentId, p.Desired); err != nil { + return nil, err + } + if s.UpdatedAt, err = parseStamp("updated_at", p.UpdatedAt); err != nil { + return nil, err + } + for _, r := range p.Records { + v, err := recordFromJournal(r) + if err != nil { + return nil, err + } + s.Records = append(s.Records, v) + } + return s, nil +} + +// validate is the fail-closed gate every loaded journal passes through. +func validate(s *State) error { + if s.AgentID != "" && !treatment.ValidID(s.AgentID) || s.Location != "" && !treatment.ValidID(s.Location) { + return errors.New("invalid journal identity") + } + if s.Desired != nil { + if err := treatment.ValidateDesired(s.Desired); err != nil { + return fmt.Errorf("invalid persisted desired state: %w", err) + } + } + if len(s.Records) > treatment.MaxAssignments*2 { + return errors.New("too many journal records") + } + seen := map[string]bool{} + for i := range s.Records { + r := &s.Records[i] + if !treatment.ValidID(r.Assignment.ExperimentID) { + return errors.New("invalid journal record") + } + if seen[r.Assignment.ExperimentID] { + return errors.New("duplicate journal record") + } + seen[r.Assignment.ExperimentID] = true + if err := treatment.ValidateTreatment(&r.Assignment.Treatment); err != nil { + return fmt.Errorf("invalid journal treatment: %w", err) + } + } + return nil +} + +// MarshalText renders the snapshot in the journal file format. +func MarshalText(s *State) ([]byte, error) { + if s == nil { + return nil, errors.New("nil state") + } + return textOptions.Marshal(toJournal(s)) +} + +// UnmarshalText parses and validates a journal snapshot. +func UnmarshalText(b []byte) (*State, error) { + if len(b) > treatment.MaxStateBytes { + return nil, errors.New("journal too large") + } + p := new(jpb.Journal) + if err := prototext.Unmarshal(b, p); err != nil { + return nil, fmt.Errorf("invalid treatment journal: %w", err) + } + s, err := fromJournal(p) + if err != nil { + return nil, fmt.Errorf("invalid treatment journal: %w", err) + } + if err = validate(s); err != nil { + return nil, err + } + return s, nil +} + +// recordText renders one record for its human-readable projection file. +func recordText(r *Record) ([]byte, error) { + return textOptions.Marshal(recordToJournal(r)) +} + +// ReadSnapshot loads the snapshot without taking the journal lock. The file +// is replaced atomically, so a reader always sees a complete snapshot; this +// is the path for status display and telemetry attribution, never for +// mutation. A missing snapshot is os.ErrNotExist. +func ReadSnapshot(dir string) (*State, error) { + f, err := os.OpenFile(filepath.Join(dir, SnapshotFile), os.O_RDONLY|unix.O_NOFOLLOW, 0) + if err != nil { + return nil, err + } + defer f.Close() + b, err := readLimited(f) + if err != nil { + return nil, err + } + return UnmarshalText(b) +} diff --git a/pkg/treatment/reconcile/journalpb_test.go b/pkg/treatment/reconcile/journalpb_test.go new file mode 100644 index 000000000..e5b90c46a --- /dev/null +++ b/pkg/treatment/reconcile/journalpb_test.go @@ -0,0 +1,164 @@ +package reconcile + +import ( + "net/netip" + "reflect" + "strconv" + "strings" + "testing" + "time" + + jpb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment_journal" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" + "google.golang.org/protobuf/proto" +) + +func populatedState(now time.Time) *State { + d := fixtures.Desired(now) + pred := d.Assignments[0].Treatment + pred.ID = "old" + pred.Settings = treatment.TCPSettings{CongestionControl: ptr("bbr"), CongestionLock: true, RTOMinMs: ptr[uint32](20), InitialCongestionWindow: ptr[uint32](10), InitialReceiveWindow: ptr[uint32](2), QuickAck: ptr(false), ECN: ptr(true)} + d.Assignments[0].Predecessor = &pred + d.Assignments = append(d.Assignments, treatment.Assignment{ExperimentID: "stable-6", Generation: 2, Mode: treatment.ModeStable, Treatment: treatment.Treatment{ID: "v6", Revision: 3, Destination: netip.MustParsePrefix("2001:db8::1/128"), Settings: treatment.TCPSettings{QuickAck: ptr(true)}}}) + return &State{ + AgentID: "host-1", Location: "dc1", BootID: "boot-1", Desired: d, LocallySuspended: true, SuspensionReason: "incident 7", UpdatedAt: now, + Records: []Record{ + {ApplicationRecord: treatment.ApplicationRecord{Assignment: d.Assignments[0], DesiredRevision: 1, Status: treatment.ApplyVerified, ReceivedAt: now, AttemptedAt: now.Add(time.Second), AppliedAt: now.Add(2 * time.Second), VerifiedAt: now.Add(3 * time.Second)}, InstalledRoute: []byte{0x18, 0, 0, 0, 1, 2, 3}, InstalledBootID: "boot-1"}, + {ApplicationRecord: treatment.ApplicationRecord{Assignment: d.Assignments[1], DesiredRevision: 1, Status: treatment.ApplyPending, ReceivedAt: now}}, + {ApplicationRecord: treatment.ApplicationRecord{Assignment: treatment.Assignment{ExperimentID: "gone", Generation: 3, Mode: treatment.ModeRetired, Treatment: fixtures.Treatment()}, Status: treatment.ApplyRemoved, RemovedAt: now, Error: "x", ExpiredLatched: true}}, + }, + } +} + +// TestJournalRoundTrip converts State -> journal text -> State and requires +// deep equality, and journal -> State -> journal and requires proto equality, +// so neither direction drops, smears or aliases a field. +func TestJournalRoundTrip(t *testing.T) { + now := time.Unix(1700000000, 123456789).UTC() + for _, tc := range []struct { + name, description string + state *State + }{ + {"populated", "positive: header, hold, predecessor, v4 and v6 destinations, installed route bytes and every timestamp survive", populatedState(now)}, + {"unset-optional", "boundary: a stable assignment without lease, predecessor or optional settings stays unset", func() *State { + d := fixtures.Desired(now) + d.Assignments[0].Mode = treatment.ModeStable + d.Assignments[0].LeaseExpiresAt = time.Time{} + return &State{AgentID: "host-1", Desired: d} + }()}, + {"empty", "boundary: a journal with no desired state and no records is the zero State", &State{}}, + {"tombstone-only", "corner: records without desired state (after a controller reset) survive", &State{AgentID: "host-1", Records: []Record{{ApplicationRecord: treatment.ApplicationRecord{Assignment: fixtures.Desired(now).Assignments[0], Status: treatment.ApplyRemoved, RemovedAt: now}}}}}, + } { + t.Run(tc.name, func(t *testing.T) { + b, err := MarshalText(tc.state) + if err != nil { + t.Fatal(err) + } + out, err := UnmarshalText(b) + if err != nil || !reflect.DeepEqual(tc.state, out) { + t.Fatalf("%s: %v\nin %+v\nout %+v", tc.description, err, tc.state, out) + } + again, err := MarshalText(out) + if err != nil || string(again) != string(b) { + t.Fatalf("%s: text differs after a second trip: %v", tc.description, err) + } + if !proto.Equal(toJournal(tc.state), toJournal(out)) { + t.Fatalf("%s: journal messages differ", tc.description) + } + if tc.state.Records != nil { + clone := out.Clone() + clone.Records[0].Assignment.Treatment.ID = "mutated" + if len(clone.Records[0].InstalledRoute) > 0 { + clone.Records[0].InstalledRoute[0] = 0xff + } + if out.Records[0].Assignment.Treatment.ID == "mutated" || len(out.Records[0].InstalledRoute) > 0 && out.Records[0].InstalledRoute[0] == 0xff { + t.Fatal("Clone aliases the original") + } + } + }) + } +} + +func TestJournalEnumsMirrorDomain(t *testing.T) { + _, modes, applies, _ := treatment.EnumCounts() + for _, tc := range []struct { + description string + journal map[int32]string + prefix string + domain int + name func(int32) string + }{ + {"assignment modes", jpb.AssignmentMode_name, "ASSIGNMENT_MODE_", modes, func(i int32) string { return treatment.AssignmentMode(i).String() }}, + {"apply statuses", jpb.ApplyStatus_name, "APPLY_STATUS_", applies, func(i int32) string { return treatment.ApplyStatus(i).String() }}, + } { + if len(tc.journal) != tc.domain { + t.Errorf("%s: journal defines %d values, domain defines %d", tc.description, len(tc.journal), tc.domain) + } + for i, name := range tc.journal { + want := strings.ToLower(strings.TrimPrefix(name, tc.prefix)) + if got := tc.name(i); got != want { + t.Errorf("%s %d: domain name %q, journal %q", tc.description, i, got, name) + } + } + } +} + +func TestUnmarshalTextLimits(t *testing.T) { + now := time.Unix(1700000000, 0).UTC() + for _, tc := range []struct { + name, description string + build func() []byte + expected string + }{ + {"max-records", "boundary: twice MaxAssignments records load", func() []byte { + s := &State{AgentID: "host-1"} + for i := range treatment.MaxAssignments * 2 { + tr := fixtures.Treatment() + tr.Destination = netip.PrefixFrom(netip.AddrFrom4([4]byte{192, 0, byte(i >> 8), byte(i)}), 32) + s.Records = append(s.Records, Record{ApplicationRecord: treatment.ApplicationRecord{Assignment: treatment.Assignment{ExperimentID: "e" + strconv.Itoa(i), Generation: 1, Mode: treatment.ModeStable, Treatment: tr}}}) + } + b, _ := MarshalText(s) + return b + }, ""}, + {"too-many-records", "boundary: one record over the cap is refused", func() []byte { + s := &State{AgentID: "host-1"} + for i := range treatment.MaxAssignments*2 + 1 { + tr := fixtures.Treatment() + tr.Destination = netip.PrefixFrom(netip.AddrFrom4([4]byte{10, byte(i >> 16), byte(i >> 8), byte(i)}), 32) + s.Records = append(s.Records, Record{ApplicationRecord: treatment.ApplicationRecord{Assignment: treatment.Assignment{ExperimentID: "e" + strconv.Itoa(i), Generation: 1, Mode: treatment.ModeStable, Treatment: tr}}}) + } + b, _ := MarshalText(s) + return b + }, "too many journal records"}, + {"oversized", "boundary: a byte over MaxStateBytes is refused before parsing", func() []byte { return make([]byte, treatment.MaxStateBytes+1) }, "too large"}, + {"bad-destination", "negative: an unparsable destination fails treatment validation, not decoding", func() []byte { + p := toJournal(&State{AgentID: "host-1", Records: []Record{{ApplicationRecord: treatment.ApplicationRecord{Assignment: fixtures.Desired(now).Assignments[0]}}}}) + p.Records[0].Assignment.Treatment.Destination = "nonsense" + b, _ := textOptions.Marshal(p) + return b + }, "invalid journal treatment"}, + {"bad-lease", "negative: an invalid lease timestamp names the field", func() []byte { + p := toJournal(&State{AgentID: "host-1", Desired: fixtures.Desired(now)}) + p.Desired.Assignments[0].LeaseExpiresAt.Nanos = -1 + b, _ := textOptions.Marshal(p) + return b + }, "lease_expires_at"}, + } { + t.Run(tc.name, func(t *testing.T) { + _, err := UnmarshalText(tc.build()) + if tc.expected == "" { + if err != nil { + t.Fatalf("%s: %v", tc.description, err) + } + return + } + if err == nil || !strings.Contains(err.Error(), tc.expected) { + t.Fatalf("%s: expected %q, got %v", tc.description, tc.expected, err) + } + }) + } + if _, err := MarshalText(nil); err == nil { + t.Fatal("nil state marshalled") + } +} diff --git a/pkg/treatment/reconcile/reconcile.go b/pkg/treatment/reconcile/reconcile.go index 3f6ce58ec..89223eced 100644 --- a/pkg/treatment/reconcile/reconcile.go +++ b/pkg/treatment/reconcile/reconcile.go @@ -7,16 +7,15 @@ import ( "fmt" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" - "github.com/randomizedcoder/xtcp2/pkg/treatment/wire" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/timestamppb" + "github.com/randomizedcoder/xtcp2/pkg/treatment" ) // Package reconcile owns the host-side half of the treatment contract: the // intent journal and the reconciler that drives a Backend from it. It is // shared by the in-daemon/standalone agent and by the independent watchdog; -// it never speaks gRPC and never imports the controller. +// it never speaks gRPC, never imports the RPC proto and never imports the +// controller. The journal file format is its own proto package +// (xtcp_treatment_journal.v1) converted to and from the domain types. // Mode selects how much a reconciliation pass may do. type Mode uint8 @@ -48,7 +47,7 @@ type Backend interface { // BeginReconcile marks the start of one pass so a backend may drop any // per-pass caches (kernel route snapshots) before Plan/Verify read them. BeginReconcile() - Plan(context.Context, *pb.Treatment) ([]byte, error) + Plan(context.Context, *treatment.Treatment) ([]byte, error) Install(context.Context, []byte) error Verify(context.Context, []byte) error Remove(context.Context, []byte) error @@ -56,12 +55,16 @@ type Backend interface { Disable(context.Context) error } +// Reconciler drives one Backend from one Journal on behalf of one agent. +// AgentID, Location and BootID are written into the journal header on every +// save so the snapshot identifies its writer. type Reconciler struct { - Journal Journal - Backend Backend - AgentID string - BootID string - Now func() time.Time + Journal Journal + Backend Backend + AgentID string + Location string + BootID string + Now func() time.Time } func (r *Reconciler) now() time.Time { @@ -71,150 +74,153 @@ func (r *Reconciler) now() time.Time { return time.Now() } +func (r *Reconciler) header(s *State, now time.Time) { + s.AgentID = r.AgentID + s.Location = r.Location + s.BootID = r.BootID + s.UpdatedAt = now +} + // Run saves intent before touching the kernel and verifies before reporting -// success. See Mode for what ModeExpiryOnly may and may not do. -func (r *Reconciler) Run(ctx context.Context, in *pb.DesiredState, mode Mode) (*pb.LocalState, error) { +// success. See Mode for what ModeExpiryOnly may and may not do. The returned +// snapshot is a copy of the journal after the pass, even when it failed. +func (r *Reconciler) Run(ctx context.Context, in *treatment.DesiredState, mode Mode) (*State, error) { if mode != ModeFull && mode != ModeExpiryOnly { return nil, fmt.Errorf("unknown reconcile mode %d", mode) } expiryOnly := mode == ModeExpiryOnly - var result *pb.LocalState - err := r.Journal.WithLock(func(s *pb.LocalState, save func() error) error { - defer func() { result = proto.Clone(s).(*pb.LocalState) }() + var result *State + err := r.Journal.WithLock(func(s *State, save func() error) error { + defer func() { result = s.Clone() }() r.Backend.BeginReconcile() now := r.now() - stamp := func() *timestamppb.Timestamp { return timestamppb.New(now) } + if s.AgentID != "" && s.AgentID != r.AgentID { + return errors.New("journal belongs to another agent") + } if in != nil { if expiryOnly { return errors.New("watchdog cannot accept desired state") } - if err := wire.ValidateDesired(in); err != nil { + if err := treatment.ValidateDesired(in); err != nil { return err } - if in.AgentId != r.AgentID { + if in.AgentID != r.AgentID { return errors.New("desired state is for another agent") } if s.Desired != nil { if in.Revision < s.Desired.Revision { return errors.New("stale desired revision") } - if in.Revision == s.Desired.Revision && !proto.Equal(in, s.Desired) { + if in.Revision == s.Desired.Revision && !in.Equal(s.Desired) { return errors.New("conflicting desired state at same revision") } } - for _, a := range in.Assignments { - for _, record := range s.Records { - if record.Assignment.ExperimentId == a.ExperimentId && a.Generation < record.Assignment.Generation { - return errors.New("stale assignment generation") - } + for i := range in.Assignments { + a := &in.Assignments[i] + if v := s.Record(a.ExperimentID); v != nil && a.Generation < v.Assignment.Generation { + return errors.New("stale assignment generation") } } - s.Desired = proto.Clone(in).(*pb.DesiredState) - s.UpdatedAt = stamp() + s.Desired = in.Clone() + r.header(s, now) if err := save(); err != nil { return err } } if s.Desired == nil { - result = proto.Clone(s).(*pb.LocalState) return nil } - if s.Desired.AgentId != r.AgentID { - return errors.New("journal belongs to another agent") - } - byID := map[string]*pb.ApplicationRecord{} - for _, v := range s.Records { - byID[v.Assignment.ExperimentId] = v - } active := map[string]bool{} var errs []error // Disable first: an interrupted cleanup must not leave suspended routes // effective. Removal remains per-route and ownership checked. - held := s.LocallySuspended || s.Desired.Suspended + held := s.Held() if held { if err := r.Backend.Disable(ctx); err != nil { errs = append(errs, err) } } - for _, a := range s.Desired.Assignments { + for i := range s.Desired.Assignments { + a := &s.Desired.Assignments[i] if err := ctx.Err(); err != nil { + r.header(s, now) return errors.Join(err, save()) } - active[a.ExperimentId] = true - v := byID[a.ExperimentId] + active[a.ExperimentID] = true + v := s.Record(a.ExperimentID) if v == nil { - v = &pb.ApplicationRecord{Assignment: proto.Clone(a).(*pb.Assignment), ReceivedAt: stamp(), Status: pb.ApplyStatus_APPLY_STATUS_PENDING} - s.Records = append(s.Records, v) - byID[a.ExperimentId] = v + s.Records = append(s.Records, Record{ApplicationRecord: treatment.ApplicationRecord{Assignment: a.Clone(), ReceivedAt: now, Status: treatment.ApplyPending}}) + v = &s.Records[len(s.Records)-1] } if a.Generation < v.Assignment.Generation { return errors.New("stale assignment generation") } if a.Generation > v.Assignment.Generation { v.ExpiredLatched = false - v.ReceivedAt = stamp() + v.ReceivedAt = now } - v.Assignment = proto.Clone(a).(*pb.Assignment) + v.Assignment = a.Clone() v.DesiredRevision = s.Desired.Revision - if a.Mode == pb.AssignmentMode_ASSIGNMENT_MODE_TRIAL && !a.LeaseExpiresAt.AsTime().After(now) { + if a.Mode == treatment.ModeTrial && !a.LeaseExpiresAt.After(now) { v.ExpiredLatched = true } - remove := held || a.Mode == pb.AssignmentMode_ASSIGNMENT_MODE_SUSPENDED || a.Mode == pb.AssignmentMode_ASSIGNMENT_MODE_RETIRED || v.ExpiredLatched + remove := held || a.Mode == treatment.ModeSuspended || a.Mode == treatment.ModeRetired || v.ExpiredLatched if expiryOnly && !remove { continue } var err error - if v.ExpiredLatched && !held && a.Mode == pb.AssignmentMode_ASSIGNMENT_MODE_TRIAL && a.Predecessor != nil { + if v.ExpiredLatched && !held && a.Mode == treatment.ModeTrial && a.Predecessor != nil { err = r.install(ctx, v, a.Predecessor, save, now) if err == nil { - v.Status = pb.ApplyStatus_APPLY_STATUS_EXPIRED + v.Status = treatment.ApplyExpired } } else if remove { - v.AttemptedAt = stamp() + v.AttemptedAt = now if len(v.InstalledRoute) > 0 { if err := save(); err != nil { return err } } - if len(v.InstalledRoute) > 0 && v.InstalledBootId == r.BootID { + if len(v.InstalledRoute) > 0 && v.InstalledBootID == r.BootID { err = r.Backend.Remove(ctx, v.InstalledRoute) } if err == nil { v.InstalledRoute = nil - v.RemovedAt = stamp() - v.Status = pb.ApplyStatus_APPLY_STATUS_REMOVED + v.RemovedAt = now + v.Status = treatment.ApplyRemoved if v.ExpiredLatched { - v.Status = pb.ApplyStatus_APPLY_STATUS_EXPIRED + v.Status = treatment.ApplyExpired } } } else { - err = r.install(ctx, v, a.Treatment, save, now) + err = r.install(ctx, v, &a.Treatment, save, now) } if err != nil { - v.Status = pb.ApplyStatus_APPLY_STATUS_FAILED + v.Status = treatment.ApplyFailed v.Error = err.Error() - errs = append(errs, fmt.Errorf("%s: %w", a.ExperimentId, err)) + errs = append(errs, fmt.Errorf("%s: %w", a.ExperimentID, err)) } else { v.Error = "" } } // Entries absent from the committed snapshot are tombstones, never // candidates for startup application. - for _, v := range s.Records { - if active[v.Assignment.ExperimentId] || len(v.InstalledRoute) == 0 { + for i := range s.Records { + v := &s.Records[i] + if active[v.Assignment.ExperimentID] || len(v.InstalledRoute) == 0 { continue } - if v.InstalledBootId == r.BootID { + if v.InstalledBootID == r.BootID { if err := r.Backend.Remove(ctx, v.InstalledRoute); err != nil { v.Error = err.Error() - v.Status = pb.ApplyStatus_APPLY_STATUS_FAILED + v.Status = treatment.ApplyFailed errs = append(errs, err) continue } } v.InstalledRoute = nil - v.Status = pb.ApplyStatus_APPLY_STATUS_REMOVED - v.RemovedAt = stamp() + v.Status = treatment.ApplyRemoved + v.RemovedAt = now } if len(errs) > 0 { // A failed withdrawal must not leave an expired/unknown override @@ -222,23 +228,23 @@ func (r *Reconciler) Run(ctx context.Context, in *pb.DesiredState, mode Mode) (* if err := r.Backend.Disable(ctx); err != nil { errs = append(errs, err) } - for _, v := range s.Records { - if v.Status == pb.ApplyStatus_APPLY_STATUS_VERIFIED { - v.Status = pb.ApplyStatus_APPLY_STATUS_FAILED + for i := range s.Records { + if v := &s.Records[i]; v.Status == treatment.ApplyVerified { + v.Status = treatment.ApplyFailed v.Error = "owned route lookup disabled after reconciliation failure" } } } else if !held && !expiryOnly { hasRoutes := false - for _, v := range s.Records { - hasRoutes = hasRoutes || len(v.InstalledRoute) > 0 + for i := range s.Records { + hasRoutes = hasRoutes || len(s.Records[i].InstalledRoute) > 0 } if hasRoutes { if err := r.Backend.Enable(ctx); err != nil { errs = append(errs, err, r.Backend.Disable(ctx)) - for _, v := range s.Records { - if v.Status == pb.ApplyStatus_APPLY_STATUS_VERIFIED { - v.Status = pb.ApplyStatus_APPLY_STATUS_FAILED + for i := range s.Records { + if v := &s.Records[i]; v.Status == treatment.ApplyVerified { + v.Status = treatment.ApplyFailed v.Error = err.Error() } } @@ -247,21 +253,20 @@ func (r *Reconciler) Run(ctx context.Context, in *pb.DesiredState, mode Mode) (* errs = append(errs, err) } } - s.UpdatedAt = stamp() + r.header(s, now) if err := save(); err != nil { return err } - result = proto.Clone(s).(*pb.LocalState) return errors.Join(errs...) }) return result, err } -func (r *Reconciler) install(ctx context.Context, v *pb.ApplicationRecord, t *pb.Treatment, save func() error, now time.Time) error { +func (r *Reconciler) install(ctx context.Context, v *Record, t *treatment.Treatment, save func() error, now time.Time) error { b, err := r.Backend.Plan(ctx, t) if err != nil { // A forwarding change invalidates the override; remove it immediately. - if len(v.InstalledRoute) > 0 && v.InstalledBootId == r.BootID { + if len(v.InstalledRoute) > 0 && v.InstalledBootID == r.BootID { if e := r.Backend.Remove(ctx, v.InstalledRoute); e != nil { return errors.Join(err, e) } @@ -269,47 +274,52 @@ func (r *Reconciler) install(ctx context.Context, v *pb.ApplicationRecord, t *pb } return err } - if len(v.InstalledRoute) > 0 && v.InstalledBootId == r.BootID && !bytes.Equal(b, v.InstalledRoute) { + if len(v.InstalledRoute) > 0 && v.InstalledBootID == r.BootID && !bytes.Equal(b, v.InstalledRoute) { if err = r.Backend.Remove(ctx, v.InstalledRoute); err != nil { return err } } - changed := !bytes.Equal(b, v.InstalledRoute) || v.InstalledBootId != r.BootID - if !changed && (v.Status == pb.ApplyStatus_APPLY_STATUS_VERIFIED || v.Status == pb.ApplyStatus_APPLY_STATUS_EXPIRED && v.ExpiredLatched) { + changed := !bytes.Equal(b, v.InstalledRoute) || v.InstalledBootID != r.BootID + if !changed && (v.Status == treatment.ApplyVerified || v.Status == treatment.ApplyExpired && v.ExpiredLatched) { if err = r.Backend.Verify(ctx, b); err == nil { - v.VerifiedAt = timestamppb.New(now) + v.VerifiedAt = now return nil } } - v.AttemptedAt = timestamppb.New(now) + v.AttemptedAt = now v.InstalledRoute = b - v.InstalledBootId = r.BootID - v.Status = pb.ApplyStatus_APPLY_STATUS_PENDING + v.InstalledBootID = r.BootID + v.Status = treatment.ApplyPending if err = save(); err != nil { return err } if err = r.Backend.Install(ctx, b); err != nil { return err } - if changed || v.AppliedAt == nil { - v.AppliedAt = timestamppb.New(now) + if changed || v.AppliedAt.IsZero() { + v.AppliedAt = now } if err = r.Backend.Verify(ctx, b); err != nil { return err } - v.VerifiedAt = timestamppb.New(now) - v.Status = pb.ApplyStatus_APPLY_STATUS_VERIFIED + v.VerifiedAt = now + v.Status = treatment.ApplyVerified return nil } +// SuspendLocal persists an offline hold (or lifts it) that controller +// requests cannot clear. A reason is required for the audit trail. func (r *Reconciler) SuspendLocal(suspended bool, reason string) error { if len(reason) == 0 || len(reason) > 1024 { return errors.New("suspension/resume reason required (maximum 1024 bytes)") } - return r.Journal.WithLock(func(s *pb.LocalState, save func() error) error { + return r.Journal.WithLock(func(s *State, save func() error) error { + if s.AgentID != "" && s.AgentID != r.AgentID { + return errors.New("journal belongs to another agent") + } s.LocallySuspended = suspended s.SuspensionReason = reason - s.UpdatedAt = timestamppb.New(r.now()) + r.header(s, r.now()) return save() }) } diff --git a/pkg/treatment/reconcile/reconcile_test.go b/pkg/treatment/reconcile/reconcile_test.go index ebb6c0c08..7c99f4d9b 100644 --- a/pkg/treatment/reconcile/reconcile_test.go +++ b/pkg/treatment/reconcile/reconcile_test.go @@ -5,15 +5,16 @@ import ( "errors" "os" "path/filepath" + "strings" "testing" "time" - pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/timestamppb" ) +func ptr[T any](v T) *T { return &v } + type fakeBackend struct { routes map[string]bool enabled bool @@ -25,8 +26,8 @@ type fakeBackend struct { func (f *fakeBackend) BeginReconcile() {} -func (f *fakeBackend) Plan(_ context.Context, t *pb.Treatment) ([]byte, error) { - return []byte(t.Destination + ":" + t.Settings.GetCongestionControl()), nil +func (f *fakeBackend) Plan(_ context.Context, t *treatment.Treatment) ([]byte, error) { + return []byte(t.Destination.String() + ":" + t.Settings.Algorithm()), nil } func (f *fakeBackend) Install(_ context.Context, b []byte) error { f.installs++ @@ -49,12 +50,23 @@ func (f *fakeBackend) Remove(_ context.Context, b []byte) error { delete(f.routes, string(b)) return nil } +func (f *fakeBackend) Enable(context.Context) error { f.enabled = true; return nil } +func (f *fakeBackend) Disable(context.Context) error { f.enabled = false; return nil } + +func newReconciler(t *testing.T, b Backend, now *time.Time) *Reconciler { + t.Helper() + r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", Location: "dc1", BootID: "boot-1"} + if now != nil { + r.Now = func() time.Time { return *now } + } + return r +} func TestFailedWithdrawalDisablesOwnedLookup(t *testing.T) { now := time.Now() b := &fakeBackend{routes: map[string]bool{}} - r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot-1", Now: func() time.Time { return now }} - if _, e := r.Run(context.Background(), fixtures.DesiredWire(now), ModeFull); e != nil { + r := newReconciler(t, b, &now) + if _, e := r.Run(context.Background(), fixtures.Desired(now), ModeFull); e != nil { t.Fatal(e) } if !b.enabled { @@ -80,10 +92,10 @@ func TestFailedWithdrawalDisablesOwnedLookup(t *testing.T) { func TestStableRouteSurvivesOfflineReboot(t *testing.T) { now := time.Now() b := &fakeBackend{routes: map[string]bool{}} - r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot-1", Now: func() time.Time { return now }} - d := fixtures.DesiredWire(now) - d.Assignments[0].Mode = pb.AssignmentMode_ASSIGNMENT_MODE_STABLE - d.Assignments[0].LeaseExpiresAt = nil + r := newReconciler(t, b, &now) + d := fixtures.Desired(now) + d.Assignments[0].Mode = treatment.ModeStable + d.Assignments[0].LeaseExpiresAt = time.Time{} if _, e := r.Run(context.Background(), d, ModeFull); e != nil { t.Fatal(e) } @@ -92,26 +104,24 @@ func TestStableRouteSurvivesOfflineReboot(t *testing.T) { b.routes = map[string]bool{} b.enabled = false s, e := r.Run(context.Background(), nil, ModeFull) - if e != nil || len(b.routes) != 1 || !b.enabled || s.Records[0].InstalledBootId != "boot-2" { + if e != nil || len(b.routes) != 1 || !b.enabled || s.Records[0].InstalledBootID != "boot-2" || s.BootID != "boot-2" { t.Fatal("offline reboot did not restore stable treatment", e) } } -func (f *fakeBackend) Enable(context.Context) error { f.enabled = true; return nil } -func (f *fakeBackend) Disable(context.Context) error { f.enabled = false; return nil } func TestJournalIntentPrecedesKernelAndCrashRecovery(t *testing.T) { now := time.Now() - dir := t.TempDir() b := &fakeBackend{routes: map[string]bool{}, failVerify: true} - r := &Reconciler{Journal: Journal{Dir: dir}, Backend: b, AgentID: "host-1", BootID: "boot-1", Now: func() time.Time { return now }} + r := newReconciler(t, b, &now) + dir := r.Journal.Dir b.onInstall = func() { - raw, e := os.ReadFile(filepath.Join(dir, "state.textproto")) + raw, e := os.ReadFile(filepath.Join(dir, SnapshotFile)) if e != nil || len(raw) == 0 { t.Fatal("intent not durable before install", e) } } - s, e := r.Run(context.Background(), fixtures.DesiredWire(now), ModeFull) - if e == nil || s.Records[0].Status != pb.ApplyStatus_APPLY_STATUS_FAILED { + s, e := r.Run(context.Background(), fixtures.Desired(now), ModeFull) + if e == nil || s.Records[0].Status != treatment.ApplyFailed { t.Fatal("must report readback failure", s, e) } // The kernel changed before success was persisted. A fresh reconciler uses @@ -119,7 +129,7 @@ func TestJournalIntentPrecedesKernelAndCrashRecovery(t *testing.T) { b.failVerify = false r2 := *r s, e = r2.Run(context.Background(), nil, ModeFull) - if e != nil || s.Records[0].Status != pb.ApplyStatus_APPLY_STATUS_VERIFIED || len(b.routes) != 1 { + if e != nil || s.Records[0].Status != treatment.ApplyVerified || len(b.routes) != 1 { t.Fatal("restart did not converge", s, e) } } @@ -127,18 +137,18 @@ func TestJournalIntentPrecedesKernelAndCrashRecovery(t *testing.T) { func TestTrialExpiryCannotBeResurrectedByRenewalOrRestart(t *testing.T) { now := time.Now() b := &fakeBackend{routes: map[string]bool{}} - r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot-1", Now: func() time.Time { return now }} - d := fixtures.DesiredWire(now) + r := newReconciler(t, b, &now) + d := fixtures.Desired(now) if _, e := r.Run(context.Background(), d, ModeFull); e != nil { t.Fatal(e) } now = now.Add(61 * time.Minute) s, e := r.Run(context.Background(), nil, ModeExpiryOnly) - if e != nil || len(b.routes) != 0 || s.Records[0].Status != pb.ApplyStatus_APPLY_STATUS_EXPIRED { + if e != nil || len(b.routes) != 0 || s.Records[0].Status != treatment.ApplyExpired { t.Fatal("watchdog expiry", s, e) } d.Revision++ - d.Assignments[0].LeaseExpiresAt = timestamppb.New(now.Add(time.Hour)) + d.Assignments[0].LeaseExpiresAt = now.Add(time.Hour) if _, e = r.Run(context.Background(), d, ModeFull); e != nil { t.Fatal(e) } @@ -158,9 +168,9 @@ func TestTrialExpiryCannotBeResurrectedByRenewalOrRestart(t *testing.T) { func TestPersistentHoldAndTombstones(t *testing.T) { now := time.Now() b := &fakeBackend{routes: map[string]bool{}} - r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot-1"} - d := fixtures.DesiredWire(now) - d.Assignments[0].Mode = pb.AssignmentMode_ASSIGNMENT_MODE_STABLE + r := newReconciler(t, b, nil) + d := fixtures.Desired(now) + d.Assignments[0].Mode = treatment.ModeStable if _, e := r.Run(context.Background(), d, ModeFull); e != nil { t.Fatal(e) } @@ -202,12 +212,12 @@ func TestPersistentHoldAndTombstones(t *testing.T) { func TestCorruptJournalDoesNotInstall(t *testing.T) { dir := t.TempDir() - if e := os.WriteFile(filepath.Join(dir, "state.textproto"), []byte("invalid!!"), 0600); e != nil { + if e := os.WriteFile(filepath.Join(dir, SnapshotFile), []byte("invalid!!"), 0600); e != nil { t.Fatal(e) } b := &fakeBackend{routes: map[string]bool{}} r := &Reconciler{Journal: Journal{Dir: dir}, Backend: b, AgentID: "host-1"} - if _, e := r.Run(context.Background(), fixtures.DesiredWire(time.Now()), ModeFull); e == nil { + if _, e := r.Run(context.Background(), fixtures.Desired(time.Now()), ModeFull); e == nil { t.Fatal("corrupt journal overwritten") } if b.installs != 0 { @@ -218,19 +228,20 @@ func TestCorruptJournalDoesNotInstall(t *testing.T) { func TestPredecessorExpiryAndGenerationRejectionPreserveJournal(t *testing.T) { now := time.Now() b := &fakeBackend{routes: map[string]bool{}} - r := &Reconciler{Journal: Journal{Dir: t.TempDir()}, Backend: b, AgentID: "host-1", BootID: "boot-1", Now: func() time.Time { return now }} - d := fixtures.DesiredWire(now) - a := d.Assignments[0] - a.ExperimentId = "state" + r := newReconciler(t, b, &now) + d := fixtures.Desired(now) + a := &d.Assignments[0] + a.ExperimentID = "state" a.Generation = 2 - a.Predecessor = proto.Clone(a.Treatment).(*pb.Treatment) - a.Predecessor.Settings.CongestionControl = proto.String("bbr") + pred := a.Treatment + pred.Settings.CongestionControl = ptr("bbr") + a.Predecessor = &pred if _, e := r.Run(context.Background(), d, ModeFull); e != nil { t.Fatal(e) } now = now.Add(61 * time.Minute) s, e := r.Run(context.Background(), nil, ModeExpiryOnly) - if e != nil || !b.routes["192.0.2.1/32:bbr"] || s.Records[0].Status != pb.ApplyStatus_APPLY_STATUS_EXPIRED { + if e != nil || !b.routes["192.0.2.1/32:bbr"] || s.Records[0].Status != treatment.ApplyExpired { t.Fatal("predecessor not restored", e) } installs := b.installs @@ -240,13 +251,13 @@ func TestPredecessorExpiryAndGenerationRejectionPreserveJournal(t *testing.T) { if b.installs != installs { t.Fatal("watchdog repeatedly reinstalled predecessor") } - bad := proto.Clone(d).(*pb.DesiredState) + bad := d.Clone() bad.Revision++ bad.Assignments[0].Generation-- if _, e = r.Run(context.Background(), bad, ModeFull); e == nil { t.Fatal("stale generation accepted") } - if e = r.Journal.WithLock(func(s *pb.LocalState, _ func() error) error { + if e = r.Journal.WithLock(func(s *State, _ func() error) error { if s.Desired.Revision != d.Revision { t.Fatal("rejected generation poisoned persisted desired state") } @@ -255,7 +266,7 @@ func TestPredecessorExpiryAndGenerationRejectionPreserveJournal(t *testing.T) { t.Fatal(e) } d.Revision++ - a.Mode = pb.AssignmentMode_ASSIGNMENT_MODE_RETIRED + a.Mode = treatment.ModeRetired if _, e = r.Run(context.Background(), d, ModeFull); e != nil { t.Fatal(e) } @@ -263,3 +274,24 @@ func TestPredecessorExpiryAndGenerationRejectionPreserveJournal(t *testing.T) { t.Fatal("retired assignment restored predecessor") } } + +func TestJournalHeaderIdentifiesWriter(t *testing.T) { + now := time.Unix(1700000000, 0) + b := &fakeBackend{routes: map[string]bool{}} + r := newReconciler(t, b, &now) + s, e := r.Run(context.Background(), fixtures.Desired(now), ModeFull) + if e != nil || s.AgentID != "host-1" || s.Location != "dc1" || s.BootID != "boot-1" || !s.UpdatedAt.Equal(now) { + t.Fatalf("header not written: %+v %v", s, e) + } + other := *r + other.AgentID = "host-2" + if _, e = other.Run(context.Background(), nil, ModeFull); e == nil || !strings.Contains(e.Error(), "another agent") { + t.Fatalf("foreign journal accepted: %v", e) + } + if e = other.SuspendLocal(true, "x"); e == nil || !strings.Contains(e.Error(), "another agent") { + t.Fatalf("foreign journal suspended: %v", e) + } + if b.installs != 1 { + t.Fatal("foreign reconciler touched the kernel") + } +} diff --git a/pkg/treatment/reconcile/state.go b/pkg/treatment/reconcile/state.go new file mode 100644 index 000000000..12f08a813 --- /dev/null +++ b/pkg/treatment/reconcile/state.go @@ -0,0 +1,96 @@ +package reconcile + +import ( + "time" + + "github.com/randomizedcoder/xtcp2/pkg/treatment" +) + +// Record is one assignment as the host knows it: the report the controller +// receives plus the kernel-local facts that never leave the host. Only the +// embedded ApplicationRecord crosses the wire, so the local fields cannot +// leak by construction. +type Record struct { + treatment.ApplicationRecord + // InstalledRoute is the locally planned canonical netlink route body. + // Remove is only ever asked to withdraw exactly these bytes. + InstalledRoute []byte + // InstalledBootID is the boot that installed InstalledRoute. A route + // from another boot is a stale fingerprint, not a current installation. + InstalledBootID string +} + +// Clone copies the route bytes and the assignment's predecessor. +func (r Record) Clone() Record { + r.Assignment = r.Assignment.Clone() + if r.InstalledRoute != nil { + r.InstalledRoute = append([]byte(nil), r.InstalledRoute...) + } + return r +} + +// State is the journal snapshot: who wrote it, what the controller last +// asked for, the local hold and one record per assignment ever received. +// Entries missing from Desired are tombstones that are withdrawn and never +// re-applied. +type State struct { + AgentID string + Location string + // BootID is the boot that last wrote the journal. + BootID string + Desired *treatment.DesiredState + LocallySuspended bool + SuspensionReason string + Records []Record + UpdatedAt time.Time +} + +// Clone deep-copies the snapshot so callers can keep it across passes. +func (s *State) Clone() *State { + if s == nil { + return nil + } + c := *s + c.Desired = s.Desired.Clone() + c.Records = make([]Record, len(s.Records)) + for i := range s.Records { + c.Records[i] = s.Records[i].Clone() + } + return &c +} + +// Record returns a pointer into s.Records for the experiment, or nil. +func (s *State) Record(experimentID string) *Record { + for i := range s.Records { + if s.Records[i].Assignment.ExperimentID == experimentID { + return &s.Records[i] + } + } + return nil +} + +// Reports strips the kernel-local fields for a check-in. +func (s *State) Reports() []treatment.ApplicationRecord { + if s == nil { + return nil + } + out := make([]treatment.ApplicationRecord, len(s.Records)) + for i := range s.Records { + out[i] = s.Records[i].ApplicationRecord + out[i].Assignment = out[i].Assignment.Clone() + } + return out +} + +// Held reports whether a local or controller hold is in force. +func (s *State) Held() bool { + return s != nil && (s.LocallySuspended || s.Desired != nil && s.Desired.Suspended) +} + +// Revision is the desired revision, zero before the first acceptance. +func (s *State) Revision() uint64 { + if s == nil || s.Desired == nil { + return 0 + } + return s.Desired.Revision +} diff --git a/proto/xtcp_treatment_journal/v1/journal.proto b/proto/xtcp_treatment_journal/v1/journal.proto new file mode 100644 index 000000000..8586042cf --- /dev/null +++ b/proto/xtcp_treatment_journal/v1/journal.proto @@ -0,0 +1,118 @@ +// +// xTCP - treatment journal +// +// The host-side intent journal of the route treatment agent: the desired +// state last accepted from the controller, the local hold, and one record per +// assignment with the exact kernel route bytes that were installed. It is a +// FILE FORMAT, read and written only on the host by the agent, the watchdog +// and the local treatmentctl commands, and never an RPC payload. +// +// It is a separate proto package from xtcp_treatment.v1 on purpose: the host +// side (pkg/treatment/reconcile, the watchdog) must be able to read and write +// the journal without linking the gRPC service stubs that share a Go package +// with the RPC messages. The messages below therefore repeat the treatment +// shape rather than importing it; pkg/treatment/reconcile converts them to +// and from the domain types, and the domain validators run on every load. +// +// Values of the two enums mirror xtcp_treatment.v1 one-for-one and the domain +// enums in pkg/treatment; reconcile's tests pin the correspondence. +// +// See docs/tcp-controller.md, "Persistence, restart and withdrawal". + +syntax = "proto3"; + +package xtcp_treatment_journal.v1; + +option go_package = "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment_journal"; + +import "google/protobuf/timestamp.proto"; + +enum AssignmentMode { + ASSIGNMENT_MODE_UNSPECIFIED = 0; + ASSIGNMENT_MODE_TRIAL = 1; + ASSIGNMENT_MODE_STABLE = 2; + ASSIGNMENT_MODE_SUSPENDED = 3; + ASSIGNMENT_MODE_RETIRED = 4; +} + +enum ApplyStatus { + APPLY_STATUS_UNSPECIFIED = 0; + APPLY_STATUS_PENDING = 1; + APPLY_STATUS_VERIFIED = 2; + APPLY_STATUS_REMOVED = 3; + APPLY_STATUS_EXPIRED = 4; + APPLY_STATUS_FAILED = 5; +} + +// TCPSettings mirrors xtcp_treatment.v1.TCPSettings; presence is meaningful. +message TCPSettings { + optional string congestion_control = 1; + bool congestion_lock = 2; + optional uint32 rto_min_ms = 3; + optional uint32 initial_congestion_window = 4; + optional uint32 initial_receive_window = 5; + optional bool quick_ack = 6; + optional bool ecn = 7; +} + +message Treatment { + string id = 1; + uint64 revision = 2; + // Canonical /32 or /128 host prefix. + string destination = 3; + TCPSettings settings = 4; + bool ecn_fabric_verified = 5; +} + +message Assignment { + string experiment_id = 1; + Treatment treatment = 2; + AssignmentMode mode = 3; + google.protobuf.Timestamp lease_expires_at = 4; + Treatment predecessor = 5; + uint64 generation = 6; +} + +// Desired is the controller's last accepted snapshot for this agent. The +// agent identity lives on the Journal header. +message Desired { + uint64 revision = 1; + bool suspended = 2; + google.protobuf.Timestamp issued_at = 3; + repeated Assignment assignments = 4; +} + +// Record is the agent's account of one assignment plus the kernel-local +// facts (installed route bytes, installing boot) that never leave the host. +message Record { + Assignment assignment = 1; + uint64 desired_revision = 2; + ApplyStatus status = 3; + google.protobuf.Timestamp received_at = 4; + google.protobuf.Timestamp attempted_at = 5; + google.protobuf.Timestamp applied_at = 6; + google.protobuf.Timestamp verified_at = 7; + google.protobuf.Timestamp removed_at = 8; + string error = 9; + // Locally planned canonical netlink route body, the only thing Remove will + // ever be asked to withdraw. + bytes installed_route = 10; + string installed_boot_id = 11; + bool expired_latched = 12; +} + +// Journal is the atomic snapshot in /state.textproto. The header +// identifies the writer so a journal can never be reconciled for another +// agent and so a telemetry-only daemon can attribute records from it. +message Journal { + string agent_id = 1; + string location = 2; + // Boot that last wrote the journal; per-record installed_boot_id says + // which boot installed each route. + string boot_id = 3; + Desired desired = 4; + bool locally_suspended = 5; + string suspension_reason = 6; + repeated Record records = 7; + google.protobuf.Timestamp updated_at = 8; +} diff --git a/tools/tcp-controller-integration/main.go b/tools/tcp-controller-integration/main.go index 6cd03c84d..9d3a0f068 100644 --- a/tools/tcp-controller-integration/main.go +++ b/tools/tcp-controller-integration/main.go @@ -22,10 +22,10 @@ import ( pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/controller" "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" + "github.com/randomizedcoder/xtcp2/pkg/treatment/reconcile" "github.com/randomizedcoder/xtcp2/pkg/treatment/wire" "google.golang.org/grpc" "google.golang.org/grpc/credentials" - "google.golang.org/protobuf/encoding/prototext" "google.golang.org/protobuf/types/known/durationpb" ) @@ -366,8 +366,7 @@ func runController(soak bool) { act(canary, "hup") time.Sleep(5 * time.Second) s := status(canary) - state := new(pb.LocalState) - check(prototext.Unmarshal([]byte(s.State), state)) + state := must(reconcile.UnmarshalText([]byte(s.State))) require(state.LocallySuspended && !hasRoute(canary), "remote resume cleared local hold") act(canary, "resume") eventually("local resume", time.Minute, func() bool { return hasRoute(canary) }) From 87321a634fea8f4e83fa7378a130cffd3b1cda77 Mon Sep 17 00:00:00 2001 From: "randomizedcoder dave.seddon.ca@gmail.com" Date: Sun, 25 Oct 2026 19:28:21 -0700 Subject: [PATCH 08/27] feat(treatment): dual-mode agent: journal attribution, treatment build tag, standalone binary Dual-mode agent, part 2. The daemon now gets treatment provenance one of two ways, never both: - -treatmentAttribution (always compiled): a standalone xtcp2-treatment-agent owns the journal; xtcp2 watches the directory (inotify on the atomic rename of state.textproto, 30 s poll fallback, SIGHUP re-reads) and rebuilds the exposure index from the snapshot. A missing or invalid snapshot clears attribution instead of stamping stale state. The daemon keeps its own -location and logs once if the journal disagrees. - -treatmentConfig (build tag `treatment`): the agent runs in-process, as before. Without the tag the flag is refused at startup with both remedies named, the same contract as a slim build asked for an enricher it lacks. Both flags exist in every build, so the flag golden stays flavor-neutral. New - pkg/treatment/attribution.Watcher (journal directory watcher). - cmd/xtcp2-treatment-agent: the agent library as its own process; SIGHUP checks in now, -version prints the build. - cmd/xtcp2/treatments.go (always) and treatments_embedded.go (tagged); checkTreatmentFlags makes the artifact mismatch fatal while runtime controller failures stay non-fatal. Nix and NixOS - mkGoBinary gains `treatment ? null`: the full destination set (fat xtcp2, xtcp2-all) embeds the agent; slim flavors do not. - xtcp2-treatment-agent joins binaryNames; test-focused-xtcp-treatment runs cmd/xtcp2 and the agent binary with the tag. - services.xtcp2.treatments gains mode (embedded | standalone), agentPackage and journalDirectory; standalone adds the xtcp2-treatment-agent service and -treatmentAttribution. - The tcp-controller VM fixture runs standalone with xtcp2-min, so the journal watcher is exercised end to end; the Go fixture takes -agentUnit for restart/stop/HUP of the agent process. Tests - attribution: watcher refresh contracts (journal, missing file, missing directory, corrupt, held, location mismatch warns once), rename following with removal and re-creation, late directory via poll. - cmd/xtcp2: checkTreatmentFlags table, attribution mode start/HUP/stop, tagged registration test. - boundaries: the walk honours build tags (go/build MatchFile), so the untagged daemon is asserted free of the agent, identity, local and the RPC proto, the tagged daemon reaches the agent but never the controller, and the standalone binary never reaches the controller. Docs: agent deployment section and binary table in docs/tcp-controller.md, build-tag mechanics in docs/build-flavors.md, example standalone unit, D2 notes and TODOs in docs/status-tcp-controller.md. Co-Authored-By: Claude Fable 5.1 --- cmd/xtcp2-treatment-agent/main.go | 71 ++++++ cmd/xtcp2/testdata/defineflags-golden.txt | 3 +- cmd/xtcp2/treatments.go | 103 +++++++-- cmd/xtcp2/treatments_embedded.go | 31 +++ cmd/xtcp2/treatments_embedded_test.go | 33 +++ cmd/xtcp2/treatments_test.go | 104 +++++++++ cmd/xtcp2/xtcp2.go | 66 +++--- cmd/xtcp2/xtcp2_flags_helpers_test.go | 53 ++--- docs/build-flavors.md | 9 +- docs/examples/xtcp2-treatment-agent.service | 21 ++ docs/status-tcp-controller.md | 28 ++- docs/tcp-controller.md | 65 +++++- nix/binaries.nix | 4 + nix/default.nix | 4 +- nix/lib/mkGoBinary.nix | 18 +- nix/modules/xtcp2-service.nix | 77 ++++++- nix/tests/focused-quality.nix | 13 ++ nix/tests/tcp-controller/default.nix | 8 +- pkg/treatment/attribution/watch.go | 241 ++++++++++++++++++++ pkg/treatment/attribution/watch_test.go | 213 +++++++++++++++++ pkg/treatment/boundaries_test.go | 47 +++- tools/tcp-controller-integration/main.go | 42 +++- 22 files changed, 1147 insertions(+), 107 deletions(-) create mode 100644 cmd/xtcp2-treatment-agent/main.go create mode 100644 cmd/xtcp2/treatments_embedded.go create mode 100644 cmd/xtcp2/treatments_embedded_test.go create mode 100644 docs/examples/xtcp2-treatment-agent.service create mode 100644 pkg/treatment/attribution/watch.go create mode 100644 pkg/treatment/attribution/watch_test.go diff --git a/cmd/xtcp2-treatment-agent/main.go b/cmd/xtcp2-treatment-agent/main.go new file mode 100644 index 000000000..30660dc2f --- /dev/null +++ b/cmd/xtcp2-treatment-agent/main.go @@ -0,0 +1,71 @@ +// xtcp2-treatment-agent runs the TCP route treatment agent as its own +// process: the mTLS check-in loop, desired-state watch, journal and kernel +// route reconciliation, beside an xtcp2 daemon that reads the journal with +// -treatmentAttribution. It is the same library cmd/xtcp2 embeds when built +// with the treatment tag; the watchdog (xtcp2-treatmentctl -mode=watchdog) +// stays a separate process in both arrangements. +// +// SIGHUP checks in and reconciles immediately; SIGINT/SIGTERM stop the agent +// and leave installed routes for the next start or the watchdog to govern. +package main + +import ( + "context" + "flag" + "log" + "os" + "os/signal" + "syscall" + + "github.com/randomizedcoder/xtcp2/pkg/treatment/agent" +) + +var ( + version = "dev" + commit = "none" + date = "unknown" +) + +func main() { + if err := run(); err != nil { + log.Print(err) + os.Exit(1) + } +} + +func run() error { + config := flag.String("config", "/etc/xtcp2/controller-agent.json", "agent configuration (shared with xtcp2-treatmentctl)") + showVersion := flag.Bool("version", false, "print version and exit") + flag.Parse() + if *showVersion { + log.Printf("xtcp2-treatment-agent %s (%s, %s)", version, commit, date) + return nil + } + ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer cancel() + c, err := agent.LoadConfig(*config) + if err != nil { + return err + } + a, err := agent.New(ctx, c) + if err != nil { + return err + } + defer a.Close() + hup := make(chan os.Signal, 1) + signal.Notify(hup, syscall.SIGHUP) + defer signal.Stop(hup) + go func() { + for { + select { + case <-ctx.Done(): + return + case <-hup: + a.Wake() + } + } + }() + log.Printf("xtcp2-treatment-agent %s: %s journal=%s", version, c, c.Directory) + a.Run(ctx) + return nil +} diff --git a/cmd/xtcp2/testdata/defineflags-golden.txt b/cmd/xtcp2/testdata/defineflags-golden.txt index 06af2e153..36f1c6f95 100644 --- a/cmd/xtcp2/testdata/defineflags-golden.txt +++ b/cmd/xtcp2/testdata/defineflags-golden.txt @@ -117,7 +117,8 @@ s3UploadMaxAttempts 10 s3parquet: max upload attempts (original + retries) befor tag label applied to the protobuf timeout 5s Poll timeout per name space topic xtcp Kafka or NSQ topic -treatmentConfig optional controller agent JSON config; route application requires enable_apply +treatmentAttribution optional journal directory of a standalone xtcp2-treatment-agent (its config's directory, default /var/lib/xtcp2/routes); this daemon only reads the snapshot and stamps treatment provenance onto records. Mutually exclusive with -treatmentConfig +treatmentConfig optional controller agent JSON config, running the treatment agent inside this daemon; needs a build with the treatment tag; route application requires enable_apply. Mutually exclusive with -treatmentAttribution uplinkCount 2 number of uplink slots to populate for LLDP/NIC enrichment (hosts are typically dual-homed = 2; max 2). Falls back to UPLINK_COUNT env. uplinkInterfaces comma-separated explicit uplink interface names (e.g. "eth0,eth1") overriding default-route auto-detection for -enrichLldp/-enrichNic. Falls back to UPLINK_INTERFACES env. v false show version diff --git a/cmd/xtcp2/treatments.go b/cmd/xtcp2/treatments.go index 325e06116..397f2a246 100644 --- a/cmd/xtcp2/treatments.go +++ b/cmd/xtcp2/treatments.go @@ -2,37 +2,106 @@ package main import ( "context" + "errors" + "fmt" "sync" "sync/atomic" - "github.com/randomizedcoder/xtcp2/pkg/treatment/agent" + flat "github.com/randomizedcoder/xtcp2/gen/go/xtcp_flat_record" + "github.com/randomizedcoder/xtcp2/pkg/treatment/attribution" ) -var activeTreatments atomic.Pointer[agent.Agent] +// The treatment feature reaches the daemon in one of two ways, selected by +// flag and never both: +// +// - -treatmentAttribution : always compiled. A standalone +// xtcp2-treatment-agent owns the journal in ; the daemon only reads +// the snapshot and stamps provenance onto records. No gRPC client, no +// TLS material, no kernel writes in this process. +// - -treatmentConfig : compiled only with the `treatment` build tag +// (treatments_embedded.go), which registers embeddedTreatmentAgent. The +// agent runs in-process and feeds the same stamper. +// +// Asking an untagged binary for -treatmentConfig is fatal, like asking a slim +// build for an enricher it lacks: a missing build tag is a property of the +// artifact, identical on every host, and must not degrade silently. -func reconcileTreatments() { - if a := activeTreatments.Load(); a != nil { - a.Wake() +// treatmentFeeder is whichever mode is running. +type treatmentFeeder struct { + stamp func(*flat.XtcpFlatRecord) + wake func() +} + +var activeTreatments atomic.Pointer[treatmentFeeder] + +// embeddedTreatmentAgent is nil unless built with -tags treatment. +var embeddedTreatmentAgent func(ctx context.Context, path string) (*treatmentFeeder, func(), error) + +var errTreatmentNotCompiled = errors.New("-treatmentConfig requested but the treatment agent is not compiled into this binary; rebuild with '-tags treatment' (or use a full `xtcp2` Nix attribute), or run xtcp2-treatment-agent beside this daemon and point it at the journal with -treatmentAttribution") + +// checkTreatmentFlags rejects the combinations that are configuration +// errors regardless of the controller's availability. +func checkTreatmentFlags(configPath, attributionDir string) error { + if configPath != "" && attributionDir != "" { + return errors.New("-treatmentConfig and -treatmentAttribution are mutually exclusive: either embed the agent or read a standalone agent's journal") + } + if configPath != "" && embeddedTreatmentAgent == nil { + return errTreatmentNotCompiled } + return nil } -func startTreatments(parent context.Context, path *string) (func(), error) { - if path == nil || *path == "" { - return func() {}, nil +// treatmentStamper is the hook installed on the collector, nil when no mode +// is active. +func treatmentStamper() func(*flat.XtcpFlatRecord) { + if f := activeTreatments.Load(); f != nil { + return f.stamp } - c, err := agent.LoadConfig(*path) - if err != nil { + return nil +} + +// reconcileTreatments is the SIGHUP action: check in now, or re-read the +// journal now. +func reconcileTreatments() { + if f := activeTreatments.Load(); f != nil { + f.wake() + } +} + +// startTreatments starts the selected mode and returns its stop function. +// Errors from here are runtime conditions (unreadable config, bad journal +// directory, controller unreachable) and must not stop socket telemetry. +func startTreatments(parent context.Context, configPath, attributionDir, location string) (func(), error) { + if err := checkTreatmentFlags(configPath, attributionDir); err != nil { return nil, err } - ctx, cancel := context.WithCancel(parent) - a, err := agent.New(ctx, c) + switch { + case configPath != "": + ctx, cancel := context.WithCancel(parent) + f, stop, err := embeddedTreatmentAgent(ctx, configPath) + if err != nil { + cancel() + return nil, err + } + activeTreatments.Store(f) + return func() { cancel(); stop(); activeTreatments.CompareAndSwap(f, nil) }, nil + case attributionDir != "": + return startAttribution(parent, attributionDir, location) + } + return func() {}, nil +} + +func startAttribution(parent context.Context, dir, location string) (func(), error) { + var stamper attribution.Stamper + w, err := attribution.NewWatcher(attribution.WatchConfig{Dir: dir, Location: location}, &stamper) if err != nil { - cancel() - return nil, err + return nil, fmt.Errorf("treatment attribution: %w", err) } - activeTreatments.Store(a) + ctx, cancel := context.WithCancel(parent) + f := &treatmentFeeder{stamp: stamper.Stamp, wake: w.Wake} + activeTreatments.Store(f) var wg sync.WaitGroup wg.Add(1) - go func() { defer wg.Done(); a.Run(ctx) }() - return func() { cancel(); wg.Wait(); activeTreatments.CompareAndSwap(a, nil); a.Close() }, nil + go func() { defer wg.Done(); w.Run(ctx) }() + return func() { cancel(); wg.Wait(); activeTreatments.CompareAndSwap(f, nil) }, nil } diff --git a/cmd/xtcp2/treatments_embedded.go b/cmd/xtcp2/treatments_embedded.go new file mode 100644 index 000000000..42af9726d --- /dev/null +++ b/cmd/xtcp2/treatments_embedded.go @@ -0,0 +1,31 @@ +//go:build treatment + +package main + +import ( + "context" + "sync" + + "github.com/randomizedcoder/xtcp2/pkg/treatment/agent" +) + +// Built with -tags treatment: the controller agent runs inside the daemon +// and stamps records in-process. This file is the only place cmd/xtcp2 +// imports pkg/treatment/agent, so an untagged build links neither the gRPC +// client nor the TLS identity code. +func init() { embeddedTreatmentAgent = startEmbeddedTreatmentAgent } + +func startEmbeddedTreatmentAgent(ctx context.Context, path string) (*treatmentFeeder, func(), error) { + c, err := agent.LoadConfig(path) + if err != nil { + return nil, nil, err + } + a, err := agent.New(ctx, c) + if err != nil { + return nil, nil, err + } + var wg sync.WaitGroup + wg.Add(1) + go func() { defer wg.Done(); a.Run(ctx) }() + return &treatmentFeeder{stamp: a.Stamp, wake: a.Wake}, func() { wg.Wait(); a.Close() }, nil +} diff --git a/cmd/xtcp2/treatments_embedded_test.go b/cmd/xtcp2/treatments_embedded_test.go new file mode 100644 index 000000000..210c22093 --- /dev/null +++ b/cmd/xtcp2/treatments_embedded_test.go @@ -0,0 +1,33 @@ +//go:build treatment + +package main + +import ( + "context" + "os" + "path/filepath" + "strings" + "testing" +) + +// With the tag the embedded agent is registered and -treatmentConfig is a +// runtime path: an unreadable config is an error, not a fatal build mismatch. +func TestEmbeddedTreatmentAgentRegistered(t *testing.T) { + if embeddedTreatmentAgent == nil { + t.Fatal("treatment tag did not register the embedded agent") + } + if err := checkTreatmentFlags("/etc/xtcp2/controller-agent.json", ""); err != nil { + t.Fatalf("tagged build refused -treatmentConfig: %v", err) + } + path := filepath.Join(t.TempDir(), "agent.json") + if err := os.WriteFile(path, []byte(`{"agent_id":"host","location":"dc","controller":"127.0.0.1:1","certificate":"/nonexistent.crt","private_key":"/nonexistent.key","ca":"/nonexistent-ca.crt"}`), 0600); err != nil { + t.Fatal(err) + } + stop, err := startTreatments(context.Background(), path, "", "dc") + if err == nil || stop != nil || !strings.Contains(err.Error(), "nonexistent") { + t.Fatalf("missing credentials must fail at start: %v", err) + } + if activeTreatments.Load() != nil { + t.Fatal("failed start left a feeder active") + } +} diff --git a/cmd/xtcp2/treatments_test.go b/cmd/xtcp2/treatments_test.go index 08b25560c..b14dd4dc9 100644 --- a/cmd/xtcp2/treatments_test.go +++ b/cmd/xtcp2/treatments_test.go @@ -2,10 +2,18 @@ package main import ( "context" + "errors" + "net/netip" "os" + "strings" "syscall" "testing" "time" + + flat "github.com/randomizedcoder/xtcp2/gen/go/xtcp_flat_record" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/reconcile" + "golang.org/x/sys/unix" ) func TestSIGHUPReconcilesWithoutRebaselineOrShutdown(t *testing.T) { @@ -47,3 +55,99 @@ func TestSIGHUPReconcilesWithoutRebaselineOrShutdown(t *testing.T) { t.Fatal("SIGTERM did not stop") } } + +func TestCheckTreatmentFlags(t *testing.T) { + saved := embeddedTreatmentAgent + t.Cleanup(func() { embeddedTreatmentAgent = saved }) + for _, tc := range []struct { + name, description string + config, dir string + compiled bool + expected string + }{ + {"none", "positive: neither flag is the observation-only daemon", "", "", false, ""}, + {"attribution", "positive: the journal reader is always available", "", "/var/lib/xtcp2/routes", false, ""}, + {"embedded-tagged", "positive: -treatmentConfig works when the agent is compiled in", "/etc/xtcp2/a.json", "", true, ""}, + {"embedded-untagged", "negative: -treatmentConfig without the build tag is a fatal artifact mismatch", "/etc/xtcp2/a.json", "", false, "not compiled into this binary"}, + {"both", "negative: both modes at once is a configuration error even when tagged", "/etc/xtcp2/a.json", "/var/lib/xtcp2/routes", true, "mutually exclusive"}, + {"both-untagged", "corner: mutual exclusion is reported before the missing tag", "/etc/xtcp2/a.json", "/var/lib/xtcp2/routes", false, "mutually exclusive"}, + } { + t.Run(tc.name, func(t *testing.T) { + embeddedTreatmentAgent = nil + if tc.compiled { + embeddedTreatmentAgent = func(context.Context, string) (*treatmentFeeder, func(), error) { return nil, nil, nil } + } + err := checkTreatmentFlags(tc.config, tc.dir) + if tc.expected == "" { + if err != nil { + t.Fatalf("%s: %v", tc.description, err) + } + return + } + if err == nil || !strings.Contains(err.Error(), tc.expected) { + t.Fatalf("%s: expected %q, got %v", tc.description, tc.expected, err) + } + if tc.name == "embedded-untagged" && !errors.Is(err, errTreatmentNotCompiled) { + t.Fatal("missing tag must be the sentinel error so main can make it fatal") + } + }) + } +} + +// Attribution mode installs a stamper fed from the journal directory and +// SIGHUP re-reads it; stopping removes the feeder. +func TestStartTreatmentsAttributionMode(t *testing.T) { + dir := t.TempDir() + now := time.Unix(1700000000, 0) + cubic := "cubic" + d := &treatment.DesiredState{AgentID: "host-1", Revision: 1, IssuedAt: now, Assignments: []treatment.Assignment{{ExperimentID: "trial-1", Generation: 1, Mode: treatment.ModeStable, Treatment: treatment.Treatment{ID: "cubic", Revision: 1, Destination: netip.MustParsePrefix("192.0.2.1/32"), Settings: treatment.TCPSettings{CongestionControl: &cubic}}}}} + write := func(revision uint64) { + d.Revision = revision + err := reconcile.Journal{Dir: dir}.WithLock(func(s *reconcile.State, save func() error) error { + *s = reconcile.State{AgentID: "host-1", Location: "dc1", BootID: "boot-1", Desired: d, Records: []reconcile.Record{{ApplicationRecord: treatment.ApplicationRecord{Assignment: d.Assignments[0], Status: treatment.ApplyVerified, AppliedAt: now}}}} + return save() + }) + if err != nil { + t.Fatal(err) + } + } + write(1) + if treatmentStamper() != nil { + t.Fatal("stamper active before start") + } + stop, err := startTreatments(context.Background(), "", dir, "dc1") + if err != nil { + t.Fatal(err) + } + stamp := treatmentStamper() + if stamp == nil { + t.Fatal("attribution mode installed no stamper") + } + var st unix.Stat_t + if err := unix.Stat("/proc/self/ns/net", &st); err != nil { + t.Skip("cannot resolve own network namespace") + } + r := &flat.XtcpFlatRecord{Location: "dc1", NetnsInode: st.Ino, InetDiagMsgFamily: 2, InetDiagMsgSocketDestination: []byte{192, 0, 2, 1}, InetDiagCong: "cubic"} + deadline := time.Now().Add(5 * time.Second) + for stamp(r); r.TreatmentStateRevision != 1 && time.Now().Before(deadline); stamp(r) { + time.Sleep(10 * time.Millisecond) + } + if r.TreatmentStateRevision != 1 || r.AgentId != "host-1" || r.TreatmentAssignmentId != "trial-1" || !r.TreatmentAlgorithmExposed { + t.Fatalf("journal not attributed: %+v", r) + } + write(2) + reconcileTreatments() + for stamp(r); r.TreatmentStateRevision != 2 && time.Now().Before(deadline); stamp(r) { + time.Sleep(10 * time.Millisecond) + } + if r.TreatmentStateRevision != 2 { + t.Fatal("SIGHUP did not re-read the journal") + } + stop() + if treatmentStamper() != nil { + t.Fatal("stop left the feeder active") + } + if _, err := startTreatments(context.Background(), "", "", "dc1"); err != nil { + t.Fatal("no-mode start must succeed") + } +} diff --git a/cmd/xtcp2/xtcp2.go b/cmd/xtcp2/xtcp2.go index 2162b7edb..ecb7e7101 100644 --- a/cmd/xtcp2/xtcp2.go +++ b/cmd/xtcp2/xtcp2.go @@ -252,31 +252,32 @@ var ( // short and lets the per-section helpers (printFlags, buildConfig, // startProfile) take a single argument instead of 30 positional ones. type mainFlags struct { - treatmentConfig *string - nltimeout *uint64 - pollFrequency *time.Duration - pollTimeout *time.Duration - maxLoops *uint64 - netlinkers *uint - nlmsgSeq *uint - packetSize *uint64 - packetSizeMply *uint - writeFiles *uint - capturePath *string - modulus *uint64 - marshal *string - columns *string - envelopeFlushBytes *uint - envelopeFlushRows *uint - kafkaCompression *string - s3Endpoint *string - s3Bucket *string - s3Prefix *string - s3AccessKey *string - s3SecretKey *string - s3Region *string - s3SkipBucketProbe *bool - s3ParquetFlushBytes *uint + treatmentConfig *string + treatmentAttribution *string + nltimeout *uint64 + pollFrequency *time.Duration + pollTimeout *time.Duration + maxLoops *uint64 + netlinkers *uint + nlmsgSeq *uint + packetSize *uint64 + packetSizeMply *uint + writeFiles *uint + capturePath *string + modulus *uint64 + marshal *string + columns *string + envelopeFlushBytes *uint + envelopeFlushRows *uint + kafkaCompression *string + s3Endpoint *string + s3Bucket *string + s3Prefix *string + s3AccessKey *string + s3SecretKey *string + s3Region *string + s3SkipBucketProbe *bool + s3ParquetFlushBytes *uint pollJitterPct *uint s3FlushInterval *time.Duration @@ -440,7 +441,8 @@ func defaultDest() string { // of all 92 are pinned by cmd/xtcp2/testdata/defineflags-golden.txt. func defineFlags() *mainFlags { f := &mainFlags{} - f.treatmentConfig = flag.String("treatmentConfig", "", "optional controller agent JSON config; route application requires enable_apply") + f.treatmentConfig = flag.String("treatmentConfig", "", "optional controller agent JSON config, running the treatment agent inside this daemon; needs a build with the treatment tag; route application requires enable_apply. Mutually exclusive with -treatmentAttribution") + f.treatmentAttribution = flag.String("treatmentAttribution", "", "optional journal directory of a standalone xtcp2-treatment-agent (its config's directory, default /var/lib/xtcp2/routes); this daemon only reads the snapshot and stamps treatment provenance onto records. Mutually exclusive with -treatmentConfig") f.nltimeout = flag.Uint64("nltimeout", nltimeoutCst, "Netlink socket timeout in milliseconds. Zero(0) for no timeout") f.pollFrequency = flag.Duration("frequency", pollFrequencyCst, "Poll frequency") f.pollTimeout = flag.Duration("timeout", pollTimeoutCst, "Poll timeout per name space") @@ -1131,7 +1133,13 @@ func runMain(ctx context.Context) int { log.Println("config validation succeeded") } - stopTreatments, err := startTreatments(runCtx, f.treatmentConfig) + if err := checkTreatmentFlags(*f.treatmentConfig, *f.treatmentAttribution); err != nil { + // A build without the agent, or both modes at once, is a property of + // the deployment that is identical on every host: fail loudly. + fatalf("%v", err) + return 1 + } + stopTreatments, err := startTreatments(runCtx, *f.treatmentConfig, *f.treatmentAttribution, *f.location) if err != nil { // Controller availability/configuration must not stop socket telemetry. log.Printf("treatment agent unavailable: %v", err) @@ -1156,8 +1164,8 @@ func runMain(ctx context.Context) int { // and run RunWithPoller until ctx cancels the WG. func runDaemonDefault(ctx context.Context, cancel context.CancelFunc, c *xtcp_config.XtcpConfig) { x := xtcp.NewXTCP(ctx, cancel, c) - if a := activeTreatments.Load(); a != nil { - x.SetTreatmentStamper(a.Stamp) + if stamp := treatmentStamper(); stamp != nil { + x.SetTreatmentStamper(stamp) } detach, err := linkMonitorHTTP.attach(x) if err != nil { diff --git a/cmd/xtcp2/xtcp2_flags_helpers_test.go b/cmd/xtcp2/xtcp2_flags_helpers_test.go index 50e51ee40..a46b17e8f 100644 --- a/cmd/xtcp2/xtcp2_flags_helpers_test.go +++ b/cmd/xtcp2/xtcp2_flags_helpers_test.go @@ -89,7 +89,7 @@ const ( // mainFlags field must be a registered flag and every registered flag must // have a field. A flag added without a field, or a field that stops being // registered, is the drift this file exists to notice. - mainFlagsFieldCountCst = 125 // optional treatmentConfig adds the controller agent + mainFlagsFieldCountCst = 126 // treatmentConfig (embedded agent) and treatmentAttribution (journal reader) printFlagsGoldenCst = "testdata/printflags-golden.txt" defineFlagsGoldenCst = "testdata/defineflags-golden.txt" @@ -155,31 +155,32 @@ func populatedMainFlags() *mainFlags { ms := func(v int) *time.Duration { d := time.Duration(v) * time.Millisecond; return &d } return &mainFlags{ - treatmentConfig: s("/etc/xtcp2/test-treatment-config.json"), - nltimeout: u64(101), - pollFrequency: ms(102), - pollTimeout: ms(103), - maxLoops: u64(104), - netlinkers: u(105), - nlmsgSeq: u(106), - packetSize: u64(107), - packetSizeMply: u(108), - writeFiles: u(109), - capturePath: s("capturePath-v"), - modulus: u64(110), - marshal: s("marshal-v"), - columns: s("columns-v"), - envelopeFlushBytes: u(111), - envelopeFlushRows: u(112), - kafkaCompression: s("kafkaCompression-v"), - s3Endpoint: s("s3Endpoint-v"), - s3Bucket: s("s3Bucket-v"), - s3Prefix: s("s3Prefix-v"), - s3AccessKey: s(fakeS3AccessSentinelCst), - s3SecretKey: s(fakeS3SigningSentinelCst), - s3Region: s("s3Region-v"), - s3SkipBucketProbe: b(true), - s3ParquetFlushBytes: u(113), + treatmentConfig: s("/etc/xtcp2/test-treatment-config.json"), + treatmentAttribution: s("/var/lib/xtcp2/test-treatment-routes"), + nltimeout: u64(101), + pollFrequency: ms(102), + pollTimeout: ms(103), + maxLoops: u64(104), + netlinkers: u(105), + nlmsgSeq: u(106), + packetSize: u64(107), + packetSizeMply: u(108), + writeFiles: u(109), + capturePath: s("capturePath-v"), + modulus: u64(110), + marshal: s("marshal-v"), + columns: s("columns-v"), + envelopeFlushBytes: u(111), + envelopeFlushRows: u(112), + kafkaCompression: s("kafkaCompression-v"), + s3Endpoint: s("s3Endpoint-v"), + s3Bucket: s("s3Bucket-v"), + s3Prefix: s("s3Prefix-v"), + s3AccessKey: s(fakeS3AccessSentinelCst), + s3SecretKey: s(fakeS3SigningSentinelCst), + s3Region: s("s3Region-v"), + s3SkipBucketProbe: b(true), + s3ParquetFlushBytes: u(113), pollJitterPct: u(114), s3FlushInterval: ms(115), diff --git a/docs/build-flavors.md b/docs/build-flavors.md index eb1864b72..832582e04 100644 --- a/docs/build-flavors.md +++ b/docs/build-flavors.md @@ -250,9 +250,13 @@ The absent ones are published as `0` deliberately: a binary built without the ta | `enrich.go`, `enrich_core.go` | (none) | always | | `enrich_asn.go` | `//go:build enrich_asn` | only with `-tags enrich_asn` | | `enrich_locality.go` | `//go:build enrich_locality` | only with `-tags enrich_locality` | +| `cmd/xtcp2/treatments.go` (journal reader, `-treatmentAttribution`) | (none) | always | +| `cmd/xtcp2/treatments_embedded.go` (embedded agent, `-treatmentConfig`) | `//go:build treatment` | only with `-tags treatment` | Each tagged file calls `RegisterDestination(scheme, factory)` / `RegisterEnricher(name, factory)` from its `init()`. With the tag off the file isn't compiled, so the registry simply lacks that entry — no `!tag` stub file is needed. Both registries panic on a duplicate registration, which can only mean two files claimed the same tag. +The treatment agent follows the same shape one level up, in `cmd/xtcp2`: the untagged file registers nothing, so `-treatmentConfig` is refused with a message naming the tag and the standalone alternative, while `-treatmentAttribution` (read a standalone `xtcp2-treatment-agent`'s journal, stamp provenance) is always compiled. `mkGoBinary`'s `treatment` knob defaults to "on for the full destination set, off for the slim flavors", so `xtcp2` / `xtcp2-all` embed the agent and `xtcp2-min` & co. pair with the standalone binary. `pkg/treatment/boundaries_test.go` walks `cmd/xtcp2` both ways and asserts the untagged closure never reaches the agent, the TLS identity code or the RPC proto. See [tcp-controller.md](tcp-controller.md#agent-deployment). + The rule that makes this work is that **no tagged package's type may appear on the `XTCP` struct**: `dest` is a `Destination` interface, and the two enrichers are `asnLookuper` / `localityEnricher` interfaces declared in untagged code. A single `*ipasn.Index`-typed field would re-link parquet-go into every flavor and silently undo the whole scheme. The CLI distinguishes "unknown" from "known but not compiled in", for both: @@ -278,6 +282,7 @@ The destination case is *unavailable* — the daemon has nowhere to send records ```sh go test -tags 'enrich_asn enrich_locality' ./pkg/xtcp/ ./cmd/xtcp2/ go test -tags 'dest_s3parquet enrich_asn enrich_locality' ./pkg/xtcp/ +go test -tags treatment ./cmd/xtcp2/ ./cmd/xtcp2-treatment-agent/ # nix build .#test-focused-xtcp-treatment ``` `nix flake check` does this for you via `nix/tests/go-test-flavors.nix`, which has one target per flavor plus an `all` target. The registry and hot-path tests (`enrich_core_test.go`, `enrich_seam_test.go`) are deliberately **untagged** and derive their expectations from `EnricherCompiledIn`, so they assert correct behaviour in every flavor — including the builds that lack the code. @@ -285,9 +290,9 @@ go test -tags 'dest_s3parquet enrich_asn enrich_locality' ./pkg/xtcp/ ## Building outside Nix ```bash -# Full xtcp2 (every destination + every enricher): +# Full xtcp2 (every destination + every enricher + the embedded treatment agent): CGO_ENABLED=0 go build \ - -tags "netgo,osusergo,dest_kafka,dest_nats,dest_nsq,dest_valkey,dest_s3parquet,enrich_asn,enrich_locality" \ + -tags "netgo,osusergo,dest_kafka,dest_nats,dest_nsq,dest_valkey,dest_s3parquet,enrich_asn,enrich_locality,treatment" \ -ldflags "-s -w" -trimpath -o xtcp2 ./cmd/xtcp2 # Unix-domain-socket flavor (stdlib only, no enrichers): diff --git a/docs/examples/xtcp2-treatment-agent.service b/docs/examples/xtcp2-treatment-agent.service new file mode 100644 index 000000000..bf6983142 --- /dev/null +++ b/docs/examples/xtcp2-treatment-agent.service @@ -0,0 +1,21 @@ +[Unit] +Description=xtcp2 TCP route treatment agent (standalone) +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=root +ExecStart=/usr/local/bin/xtcp2-treatment-agent -config=/etc/xtcp2/controller-agent.json +# Check in and reconcile now; the embedded agent does the same on SIGHUP. +ExecReload=/bin/kill -HUP $MAINPID +Restart=always +RestartSec=2s +StateDirectory=xtcp2 +StateDirectoryMode=0750 +AmbientCapabilities=CAP_NET_ADMIN CAP_SYS_PTRACE +CapabilityBoundingSet=CAP_NET_ADMIN CAP_SYS_PTRACE +NoNewPrivileges=true + +[Install] +WantedBy=multi-user.target diff --git a/docs/status-tcp-controller.md b/docs/status-tcp-controller.md index 49023bd1e..c0b22b06a 100644 --- a/docs/status-tcp-controller.md +++ b/docs/status-tcp-controller.md @@ -12,9 +12,9 @@ review being addressed. Items are listed in execution order. | 8 | proto moved to `xtcp_treatment/v1` with `buf.validate` rules | done | 460164b1 | | 3 | explicit state machine; `Outcome` enum | done | 85b2cf59 | | 4 | repository ports, relational schema, pgx, domain types, `wire` | done | eae1d87d | -| D1 | dual-mode agent, part 1: journal-specific proto, proto-free `reconcile`, `local`, `attribution`, watchdog off the agent | done | this commit | -| D2 | dual-mode agent, part 2: `-treatmentAttribution` journal watcher, `treatment` build tag, `cmd/xtcp2-treatment-agent`, Nix and module wiring | next | | -| 5 | evidence port (`CandidateSource`), ClickHouse as one adapter | pending | | +| D1 | dual-mode agent, part 1: journal-specific proto, proto-free `reconcile`, `local`, `attribution`, watchdog off the agent | done | 6398338a | +| D2 | dual-mode agent, part 2: `-treatmentAttribution` journal watcher, `treatment` build tag, `cmd/xtcp2-treatment-agent`, Nix and module wiring | done | this commit | +| 5 | evidence port (`CandidateSource`), ClickHouse as one adapter | next | | | 6 | auth as interceptor with per-RPC role option | pending | | | 7 | typed `xtcpnl.RuleWrite`; backend to `internal/goip/actuate` | pending | | | 9 | identity/location startup check | pending | | @@ -56,6 +56,24 @@ process. `XTCP_CONTROLLER_TEST_DSN`, against PostgreSQL; both stores pass the shared conformance suite in `store/storetest`. +## D2 notes + +- `cmd/xtcp2/treatments.go` is always built and owns `-treatmentAttribution` + (an `attribution.Watcher` over the journal directory) and the fatal checks; + `treatments_embedded.go` (`//go:build treatment`) registers the embedded + agent for `-treatmentConfig`. Both flags exist in every build so the flag + golden is flavor-neutral; the untagged daemon refuses `-treatmentConfig`. +- `mkGoBinary` gained `treatment ? null`: fat builds (full destination set) + embed the agent, slim flavors do not. `xtcp2-treatment-agent` joined + `binaryNames`; `test-focused-xtcp-treatment` runs the tagged daemon tests. +- The NixOS module gained `treatments.mode` (`embedded` | `standalone`), + `agentPackage` and `journalDirectory`; standalone adds the + `xtcp2-treatment-agent` service. The VM fixture runs standalone with + `xtcp2-min`, which exercises the journal watcher end to end. +- The boundary walk now honours build tags (`go/build.Context.MatchFile`), so + it asserts the untagged daemon never reaches the agent, identity or the + RPC proto, and the tagged one does reach the agent but never the controller. + ## TODO - The watchdog and `pkg/treatment/local` still link gRPC, through @@ -72,3 +90,7 @@ process. expression-index timings; refresh from `TestPostgresExperimentQueryIndexes` on the Nix fixture. - Coverage table in `docs/tcp-controller-testing.md` predates the split; regenerate. +- `nix/checks/cli-help-smoke.nix` keeps its own `cmdNames` list and does not + include the treatment binaries (`xtcp2-controller`, `xtcp2-controllerctl`, + `xtcp2-treatment-agent`, `xtcp2-treatmentctl`); add them when that list is + next touched. diff --git a/docs/tcp-controller.md b/docs/tcp-controller.md index 8974985ba..e9c9fa496 100644 --- a/docs/tcp-controller.md +++ b/docs/tcp-controller.md @@ -2,8 +2,12 @@ xtcp2 can now opt into a separate controller that proposes TCP route treatments, rolls them out after human review, and withdraws failed trials. Ordinary xtcp2 -collection and the goip CLI retain their existing behavior. Route application -requires both `-treatmentConfig` and `enable_apply: true`. +collection and the goip CLI retain their existing behavior. The agent runs +either inside xtcp2 (`-treatmentConfig`, a build with the `treatment` tag) or +as the standalone `xtcp2-treatment-agent` beside an xtcp2 that only reads its +journal (`-treatmentAttribution`); see [Agent deployment](#agent-deployment). +Route application requires `enable_apply: true` in the agent configuration in +either arrangement. ## Components and authority @@ -60,7 +64,8 @@ The binaries are: | --- | --- | | `xtcp2-controller` | PostgreSQL state, discovery, analysis, review gates, leases, gRPC, status and metrics | | `xtcp2-controllerctl` | Authenticated `propose`, `analyze`, `review`, `control`, and `inspect` requests | -| `xtcp2` | Optional outbound controller connection, reconciliation and telemetry attribution | +| `xtcp2` | Telemetry attribution from the journal (always); the embedded agent with `-treatmentConfig` when built with the `treatment` tag | +| `xtcp2-treatment-agent` | The same agent as a standalone process: controller connection, journal and kernel reconciliation; pairs with `-treatmentAttribution` | | `xtcp2-treatmentctl` | Local status, suspension, resume, reconciliation, independent watchdog | ClickHouse is an evidence source; PostgreSQL is the authority for desired state. @@ -362,16 +367,51 @@ to turn over before interpreting a temporary baseline test. Disconnected agents cannot receive an immediate remote hold; use the local command or wait for trial expiry. Stable routes intentionally persist during controller outages. -Before disabling `enable_apply`, removing `-treatmentConfig`, or uninstalling -the watchdog, suspend the treatments and verify withdrawal. Disabling the +Before disabling `enable_apply`, removing `-treatmentConfig`, stopping a +standalone agent for good, or uninstalling the watchdog, suspend the treatments +and verify withdrawal. Disabling the actuator prevents further writes; it does not itself delete existing kernel routes. Keep the journal through cleanup so ownership can be checked. +## Agent deployment + +The agent is a library (`pkg/treatment/agent`) with two front ends. Pick one per +host; xtcp2 refuses both flags together. + +| Arrangement | xtcp2 flag | Who owns the journal and routes | When | +| --- | --- | --- | --- | +| Embedded | `-treatmentConfig=/etc/xtcp2/controller-agent.json` | xtcp2 itself | the fat `xtcp2` build (Nix: every attribute with the full destination set carries the `treatment` tag); one process to run and one journal writer | +| Standalone | `-treatmentAttribution=/var/lib/xtcp2/routes` | `xtcp2-treatment-agent -config=/etc/xtcp2/controller-agent.json` | slim xtcp2 flavors (`xtcp2-min`, `xtcp2-kafka`, ...), which do not embed the agent, or wherever the controller connection and kernel writes should restart independently of collection | + +In both arrangements the daemon stamps every record from the same exposure +index (`pkg/treatment/attribution`): agent, boot, collection nonce, desired +revision and, for sockets whose destination has a verified route in the host +namespace, the experiment, generation, application time and whether the +observed congestion algorithm matches. Embedded, the index is rebuilt after +every reconcile pass. Standalone, xtcp2 watches the journal directory +(inotify on the atomic rename of `state.textproto`, a 30 s poll as fallback, +SIGHUP to re-read now) and rebuilds it from the snapshot; a missing or invalid +snapshot clears attribution rather than stamping stale state. The embedded +agent writes its certificate-bound location onto records; the standalone +arrangement keeps the daemon's own `-location` and logs once if the journal +disagrees. + +A build without the `treatment` tag rejects `-treatmentConfig` at startup with +a message naming both remedies, the same way a slim build rejects an enricher +it lacks: a missing build tag is a property of the artifact and must not +degrade silently. `go test ./cmd/xtcp2` covers the untagged daemon; +`nix build .#test-focused-xtcp-treatment` (or +`go test -tags treatment ./cmd/xtcp2 ./cmd/xtcp2-treatment-agent`) covers the +embedded path. The watchdog, `xtcp2-treatmentctl -mode=watchdog`, is a +separate process in both arrangements and builds on `pkg/treatment/local` and +`pkg/treatment/reconcile` only. + ## Deployment -1. Build the controller, client, treatmentctl and your normal xtcp2 build flavor. - Nix exposes `.#xtcp2-controller`, `.#xtcp2-controllerctl` and - `.#xtcp2-treatmentctl`; all three also build with ordinary `go build`. +1. Build the controller, client, treatmentctl, the standalone agent if you + use it, and your normal xtcp2 build flavor. Nix exposes + `.#xtcp2-controller`, `.#xtcp2-controllerctl`, `.#xtcp2-treatment-agent` + and `.#xtcp2-treatmentctl`; all of them also build with ordinary `go build`. 2. Provision PostgreSQL with backups and a restricted database identity. The controller's `-migrate` applies the additive schema; use it during controlled deployment, then remove it from the long-running command. Back up audit and @@ -416,7 +456,14 @@ replace its documentation addresses and protected prefixes before use. The hypotheses, not recommended network-wide tuning defaults. On NixOS, set `services.xtcp2.treatments.enable = true`, its `configFile`, and -`watchdogPackage`. The module creates an independent watchdog service. A runtime +`watchdogPackage`. `treatments.mode` selects the arrangement: `embedded` (the +default; `package` must be a build with the `treatment` tag) passes +`-treatmentConfig`, `standalone` runs `agentPackage`'s +`xtcp2-treatment-agent` as its own service and passes +`-treatmentAttribution=` to xtcp2. The module creates an +independent watchdog service in both modes; the example +[standalone agent unit](examples/xtcp2-treatment-agent.service) is the +non-NixOS equivalent. A runtime JSON file and private keys should remain outside the Nix store. The existing `systemctl reload xtcp2` sends SIGUSR1 for link-monitor baseline relearning; use `kill -HUP ` for route reconciliation. Other systems can use the diff --git a/nix/binaries.nix b/nix/binaries.nix index 89f74d618..8d1d755f0 100644 --- a/nix/binaries.nix +++ b/nix/binaries.nix @@ -92,6 +92,10 @@ let "xtcp2" "xtcp2-controller" "xtcp2-controllerctl" + # The standalone treatment agent pairs with slim xtcp2 flavors, which do + # not embed it (mkGoBinary's `treatment` knob); the watchdog is separate + # in both arrangements. See docs/tcp-controller.md, "Agent deployment". + "xtcp2-treatment-agent" "xtcp2-treatmentctl" "xtcp2client" "xtcp2ctl" diff --git a/nix/default.nix b/nix/default.nix index 5ce4e3dd4..2d7bd0026 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -360,7 +360,8 @@ let "$FLAKE_REF#test-focused-asn-locality" \ "$FLAKE_REF#test-focused-goip" \ "$FLAKE_REF#test-focused-gordma" \ - "$FLAKE_REF#test-focused-xtcp-enrich" + "$FLAKE_REF#test-focused-xtcp-enrich" \ + "$FLAKE_REF#test-focused-xtcp-treatment" echo "==> focused quality checks passed" ''; @@ -1110,6 +1111,7 @@ in test-focused-goip = tests.focused.focused-goip; test-focused-gordma = tests.focused.focused-gordma; test-focused-xtcp-enrich = tests.focused.focused-xtcp-enrich; + test-focused-xtcp-treatment = tests.focused.focused-xtcp-treatment; test-microvm-lifecycle-x86_64 = tests.microvm-lifecycle.x86_64.fullTest; test-microvm-lifecycle-x86_64-ipmeta-bootstrap = tests.microvm-lifecycle-ipmeta-bootstrap.x86_64.fullTest; diff --git a/nix/lib/mkGoBinary.nix b/nix/lib/mkGoBinary.nix index 2e9e79091..c242e6743 100644 --- a/nix/lib/mkGoBinary.nix +++ b/nix/lib/mkGoBinary.nix @@ -14,6 +14,10 @@ # destinationFlavors); null = all, [] = stdlib only # enrichments — enrichment features to compile in (see versions.nix # enrichmentFlavors); null = all, [] = none +# treatment — compile the TCP route treatment agent into cmd/xtcp2 +# (`-tags treatment`, see docs/tcp-controller.md); null = +# only for the full destination set, i.e. the fat builds. +# Slim flavors read a standalone agent's journal instead. # commit, date, # version — injected into main.{commit,date,version} via -ldflags -X # extraLdflags — additional -ldflags entries appended after the variant's @@ -54,6 +58,13 @@ in # A list like `[ "asn" ]` → just those. See versions.nix enrichmentFlavors # for why these two are compile-time gated when the other enrichers are not. enrichments ? null, + # Whether cmd/xtcp2 embeds the treatment agent (build tag `treatment`). + # `null` → follow the destination axis: the fat builds (destinations == + # null) embed it, the slim per-destination flavors do not and pair with + # the standalone xtcp2-treatment-agent + -treatmentAttribution. A build + # without the tag refuses -treatmentConfig at startup, like a slim build + # refuses an enricher it lacks. + treatment ? null, vendorHash ? versions.goVendorHash, commit ? "nix", date ? "1970-01-01-00:00", @@ -101,6 +112,11 @@ let enrichmentTags = map (f: "enrich_${f}") effectiveEnrichments; + # The treatment agent rides the destination axis unless told otherwise: + # embedded in the fat builds, left to the standalone binary in slim ones. + embedTreatment = if treatment == null then destinations == null else treatment; + treatmentTags = lib.optional embedTreatment "treatment"; + # Unlike destSuffix there is no "-min" equivalent: the empty enrichment set # is the plain name, so `xtcp2-min` / `xtcp2-s3parquet` keep the names they # have always had while gaining `-asn` / `-locality` / `-asn-locality` @@ -128,7 +144,7 @@ buildGoModule { CGO_ENABLED = if versions.cgoEnabled then "1" else "0"; }; - tags = versions.buildTags ++ destinationTags ++ enrichmentTags; + tags = versions.buildTags ++ destinationTags ++ enrichmentTags ++ treatmentTags; ldflags = variantCfg.extraLdflags diff --git a/nix/modules/xtcp2-service.nix b/nix/modules/xtcp2-service.nix index b8ca3ffe4..605b4c9b2 100644 --- a/nix/modules/xtcp2-service.nix +++ b/nix/modules/xtcp2-service.nix @@ -72,11 +72,40 @@ in treatments = { enable = lib.mkEnableOption "reviewed TCP route treatments and the independent expiry watchdog"; + mode = lib.mkOption { + type = lib.types.enum [ + "embedded" + "standalone" + ]; + default = "embedded"; + description = '' + How the treatment agent runs. `embedded` passes `-treatmentConfig` + to xtcp2, which must be a build with the `treatment` tag (the fat + `xtcp2` attribute; slim flavors refuse the flag at startup). + `standalone` runs `xtcp2-treatment-agent` as its own service from + `agentPackage` and passes `-treatmentAttribution=` + to xtcp2, which then only reads the journal to stamp provenance. The + watchdog service is independent in both modes. + ''; + }; configFile = lib.mkOption { type = lib.types.str; default = "/etc/xtcp2/controller-agent.json"; description = "Runtime agent JSON file; keep private keys outside the Nix store."; }; + journalDirectory = lib.mkOption { + type = lib.types.str; + default = "/var/lib/xtcp2/routes"; + description = '' + The agent's journal directory (`directory` in configFile). In + standalone mode xtcp2 reads `state.textproto` from here. + ''; + }; + agentPackage = lib.mkOption { + type = lib.types.nullOr lib.types.package; + default = null; + description = "Package providing bin/xtcp2-treatment-agent, required in standalone mode."; + }; watchdogPackage = lib.mkOption { type = lib.types.package; description = "Package providing bin/xtcp2-treatmentctl, required when treatments are enabled."; @@ -183,6 +212,13 @@ in }; config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = !(cfg.treatments.enable && cfg.treatments.mode == "standalone") || cfg.treatments.agentPackage != null; + message = "services.xtcp2.treatments.mode = \"standalone\" needs services.xtcp2.treatments.agentPackage"; + } + ]; + systemd.services.xtcp2 = { description = "xtcp2 — TCP socket introspection via netlink"; after = [ "network-online.target" ]; @@ -203,7 +239,12 @@ in # parse a JSON config, flip this back to `-config ${cfg.configFile}`. ExecStart = xtcp2Exec.mkExecStart cfg.package ( linkMonitorArgs - ++ lib.optionals cfg.treatments.enable [ "-treatmentConfig=${cfg.treatments.configFile}" ] + ++ lib.optionals (cfg.treatments.enable && cfg.treatments.mode == "embedded") [ + "-treatmentConfig=${cfg.treatments.configFile}" + ] + ++ lib.optionals (cfg.treatments.enable && cfg.treatments.mode == "standalone") [ + "-treatmentAttribution=${cfg.treatments.journalDirectory}" + ] ++ cfg.extraArgs ); # `systemctl reload xtcp2` asks the link monitor to relearn the @@ -238,6 +279,40 @@ in }; }; + # Standalone agent: owns the journal and the kernel routes; xtcp2 only + # reads the journal. Restarting one does not restart the other, and the + # journal replay on agent start restores stable routes without a + # controller round trip. + systemd.services.xtcp2-treatment-agent = lib.mkIf (cfg.treatments.enable && cfg.treatments.mode == "standalone") { + description = "xtcp2 route treatment agent"; + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + serviceConfig = { + Type = "simple"; + User = cfg.user; + StateDirectory = "xtcp2"; + StateDirectoryMode = "0750"; + ExecStart = xtcp2Exec.escapeSystemdExecArgs [ + "${cfg.treatments.agentPackage}/bin/xtcp2-treatment-agent" + "-config=${cfg.treatments.configFile}" + ]; + # `systemctl reload` checks in and reconciles now, as SIGHUP does for + # the embedded agent. + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + Restart = "always"; + RestartSec = "2s"; + AmbientCapabilities = [ + "CAP_NET_ADMIN" + "CAP_SYS_PTRACE" + ]; + CapabilityBoundingSet = [ + "CAP_NET_ADMIN" + "CAP_SYS_PTRACE" + ]; + }; + }; + # Deliberately independent of xtcp2.service: daemon crashes must not leave # trial routes installed past their local lease deadline. systemd.services.xtcp2-treatment-watchdog = lib.mkIf cfg.treatments.enable { diff --git a/nix/tests/focused-quality.nix b/nix/tests/focused-quality.nix index be590d5eb..c3c590ff8 100644 --- a/nix/tests/focused-quality.nix +++ b/nix/tests/focused-quality.nix @@ -84,4 +84,17 @@ in "./cmd/xtcp2" ]; }; + + # cmd/xtcp2 with the embedded treatment agent (`-tags treatment`): the + # default `go test ./cmd/xtcp2` compiles treatments_embedded.go out, so + # without this leg a regression in the tagged file is invisible until the + # fat Nix build. pkg/treatment itself is covered by test-tcp-controller-unit. + focused-xtcp-treatment = mkFocusedTest "xtcp-treatment" { + tags = "treatment"; + ldflags = "-checklinkname=0"; + paths = [ + "./cmd/xtcp2" + "./cmd/xtcp2-treatment-agent" + ]; + }; } diff --git a/nix/tests/tcp-controller/default.nix b/nix/tests/tcp-controller/default.nix index c0a8d4e36..0a3f613c6 100644 --- a/nix/tests/tcp-controller/default.nix +++ b/nix/tests/tcp-controller/default.nix @@ -137,11 +137,16 @@ let }; services.xtcp2 = { enable = true; + # The slim daemon does not embed the agent, so this fixture runs the + # standalone arrangement: xtcp2-treatment-agent owns the journal and + # the routes, xtcp2 reads the journal to stamp provenance. package = binaries.xtcp2-min; configFile = "/etc/xtcp2/unused.json"; linkMonitor.enable = false; treatments = { enable = true; + mode = "standalone"; + agentPackage = binaries.xtcp2-treatment-agent; watchdogPackage = binaries.xtcp2-treatmentctl; }; extraArgs = [ @@ -154,13 +159,14 @@ let }; # The Go fixture installs runtime credentials before starting the daemon. systemd.services.xtcp2.wantedBy = lib.mkForce [ ]; + systemd.services.xtcp2-treatment-agent.wantedBy = lib.mkForce [ ]; systemd.services.xtcp2-treatment-watchdog.wantedBy = lib.mkForce [ ]; systemd.services.controller-test-agent = { wantedBy = [ "multi-user.target" ]; after = [ "network-online.target" ]; wants = [ "network-online.target" ]; path = fixturePath; - serviceConfig.ExecStart = "${harness}/bin/tcp-controller-integration -role=agent -listen=192.168.1.${host}:9009"; + serviceConfig.ExecStart = "${harness}/bin/tcp-controller-integration -role=agent -listen=192.168.1.${host}:9009 -agentUnit=xtcp2-treatment-agent"; }; systemd.services.load-near.serviceConfig = { EnvironmentFile = "/var/lib/controller-test/load.env"; diff --git a/pkg/treatment/attribution/watch.go b/pkg/treatment/attribution/watch.go new file mode 100644 index 000000000..94f2671cb --- /dev/null +++ b/pkg/treatment/attribution/watch.go @@ -0,0 +1,241 @@ +package attribution + +import ( + "crypto/rand" + "encoding/hex" + "errors" + "fmt" + "log" + "os" + "path/filepath" + "sync" + "time" + "unsafe" + + "github.com/randomizedcoder/xtcp2/pkg/treatment/reconcile" + "golang.org/x/sys/unix" +) + +// WatchConfig tells a Watcher where the standalone agent writes its journal. +type WatchConfig struct { + // Dir is the journal directory (local.Config.Directory of the agent). + Dir string + // Location is this daemon's own telemetry label. The journal's location + // is never written onto records; a difference is logged once. + Location string + // Poll bounds staleness when inotify is unavailable, the directory does + // not exist yet or an event was missed. Zero means 30s. + Poll time.Duration + // NetnsInode is the namespace the agent's routes live in. Zero means this + // process's own namespace, which is right whenever daemon and agent run + // in the host namespace, as the agent requires. + NetnsInode uint64 + // Log receives diagnostics; nil means log.Printf. + Log func(format string, args ...any) +} + +// Watcher feeds a Stamper from a journal directory written by another +// process. It reads the snapshot without the journal lock (the file is +// replaced atomically), on an inotify event for the snapshot's name, on +// Wake, and on the poll interval as a fallback. A missing or invalid +// snapshot publishes a nil index: with no trustworthy journal there is no +// agent to speak for, and the daemon degrades to an observation-only +// producer rather than attributing from stale state. +type Watcher struct { + cfg WatchConfig + stamper *Stamper + collectionID string + wake chan struct{} + mu sync.Mutex + lastModTime time.Time + lastSize int64 + lastErr string + warnedFor string +} + +// NewWatcher validates the configuration and resolves the namespace. It +// does not read the journal; Run or Refresh does. +func NewWatcher(cfg WatchConfig, s *Stamper) (*Watcher, error) { + if cfg.Dir == "" || s == nil { + return nil, errors.New("attribution: journal directory and stamper are required") + } + if cfg.Poll <= 0 { + cfg.Poll = 30 * time.Second + } + if cfg.Log == nil { + cfg.Log = log.Printf + } + if cfg.NetnsInode == 0 { + var st unix.Stat_t + if err := unix.Stat("/proc/self/ns/net", &st); err != nil { + return nil, fmt.Errorf("attribution: network namespace: %w", err) + } + cfg.NetnsInode = st.Ino + } + var nonce [16]byte + if _, err := rand.Read(nonce[:]); err != nil { + return nil, err + } + return &Watcher{cfg: cfg, stamper: s, collectionID: hex.EncodeToString(nonce[:]), wake: make(chan struct{}, 1)}, nil +} + +// Wake asks Run to re-read the snapshot now; concurrent wakes coalesce. +func (w *Watcher) Wake() { + select { + case w.wake <- struct{}{}: + default: + } +} + +// Refresh re-reads the snapshot and publishes the resulting index. force +// re-reads even when size and modification time are unchanged. The error +// is the load failure, if any; it is also logged once per distinct message. +func (w *Watcher) Refresh(force bool) error { + w.mu.Lock() + defer w.mu.Unlock() + path := filepath.Join(w.cfg.Dir, reconcile.SnapshotFile) + info, err := os.Lstat(path) + if err == nil && !force && info.ModTime().Equal(w.lastModTime) && info.Size() == w.lastSize && w.stamper.Current() != nil { + return nil + } + if err != nil { + w.lastModTime, w.lastSize = time.Time{}, 0 + w.stamper.Publish(nil) + if errors.Is(err, os.ErrNotExist) { + w.note("") + return nil + } + w.note(err.Error()) + return err + } + s, err := reconcile.ReadSnapshot(w.cfg.Dir) + if err != nil { + w.lastModTime, w.lastSize = time.Time{}, 0 + w.stamper.Publish(nil) + w.note(err.Error()) + return err + } + w.lastModTime, w.lastSize = info.ModTime(), info.Size() + w.note("") + if s.Location != "" && w.cfg.Location != "" && s.Location != w.cfg.Location && w.warnedFor != s.Location { + w.warnedFor = s.Location + w.cfg.Log("treatment attribution: journal location %q differs from this daemon's -location %q; records keep %q", s.Location, w.cfg.Location, w.cfg.Location) + } + w.stamper.Publish(Build(s, Identity{AgentID: s.AgentID, Location: s.Location, BootID: s.BootID, CollectionID: w.collectionID, NetnsInode: w.cfg.NetnsInode}, false)) + return nil +} + +// note logs a load failure once per distinct message and a recovery once. +func (w *Watcher) note(msg string) { + if msg == w.lastErr { + return + } + if msg == "" { + w.cfg.Log("treatment attribution: journal %s readable again", w.cfg.Dir) + } else { + w.cfg.Log("treatment attribution: %s; records are not attributed until the journal is valid", msg) + } + w.lastErr = msg +} + +// Run watches until ctx is done. It never returns an error: attribution is +// best effort and a broken watch degrades to polling. +func (w *Watcher) Run(ctx interface { + Done() <-chan struct{} + Err() error +}) { + _ = w.Refresh(true) + fd, err := unix.InotifyInit1(unix.IN_CLOEXEC | unix.IN_NONBLOCK) + if err != nil { + w.cfg.Log("treatment attribution: inotify unavailable (%v); polling every %s", err, w.cfg.Poll) + fd = -1 + } else { + defer unix.Close(fd) + } + watching := false + addWatch := func() { + if fd < 0 || watching { + return + } + _, err := unix.InotifyAddWatch(fd, w.cfg.Dir, unix.IN_MOVED_TO|unix.IN_CLOSE_WRITE|unix.IN_CREATE|unix.IN_DELETE|unix.IN_DELETE_SELF|unix.IN_MOVE_SELF|unix.IN_ONLYDIR) + watching = err == nil + } + addWatch() + buf := make([]byte, 64<<10) + timer := time.NewTimer(w.cfg.Poll) + defer timer.Stop() + events := make(chan bool, 1) // true: the snapshot changed; false: the directory itself went away + if fd >= 0 { + go w.readEvents(ctx, fd, buf, events) + } + for { + select { + case <-ctx.Done(): + return + case <-w.wake: + _ = w.Refresh(true) + case changed := <-events: + if !changed { + watching = false + } + _ = w.Refresh(true) + case <-timer.C: + addWatch() + _ = w.Refresh(false) + } + if !timer.Stop() { + select { + case <-timer.C: + default: + } + } + timer.Reset(w.cfg.Poll) + } +} + +// readEvents turns inotify events for the snapshot into notifications. +func (w *Watcher) readEvents(ctx interface{ Err() error }, fd int, buf []byte, events chan<- bool) { + for ctx.Err() == nil { + p := []unix.PollFd{{Fd: int32(fd), Events: unix.POLLIN}} + if _, err := unix.Poll(p, 1000); err != nil && err != unix.EINTR { + return + } + if p[0].Revents == 0 { + continue + } + n, err := unix.Read(fd, buf) + if err == unix.EAGAIN || err == unix.EINTR { + continue + } + if err != nil { + return + } + changed, gone := false, false + for off := 0; off+unix.SizeofInotifyEvent <= n; { + ev := (*unix.InotifyEvent)(unsafe.Pointer(&buf[off])) + nameLen := int(ev.Len) + name := "" + if nameLen > 0 && off+unix.SizeofInotifyEvent+nameLen <= n { + name = unix.ByteSliceToString(buf[off+unix.SizeofInotifyEvent : off+unix.SizeofInotifyEvent+nameLen]) + } + switch { + case ev.Mask&(unix.IN_DELETE_SELF|unix.IN_MOVE_SELF|unix.IN_IGNORED) != 0: + gone = true + case ev.Mask&unix.IN_Q_OVERFLOW != 0 || name == reconcile.SnapshotFile: + changed = true + } + off += unix.SizeofInotifyEvent + nameLen + } + if gone { + select { + case events <- false: + default: + } + } else if changed { + select { + case events <- true: + default: + } + } + } +} diff --git a/pkg/treatment/attribution/watch_test.go b/pkg/treatment/attribution/watch_test.go new file mode 100644 index 000000000..69d8cb713 --- /dev/null +++ b/pkg/treatment/attribution/watch_test.go @@ -0,0 +1,213 @@ +package attribution + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" + "sync" + "testing" + "time" + + flat "github.com/randomizedcoder/xtcp2/gen/go/xtcp_flat_record" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" + "github.com/randomizedcoder/xtcp2/pkg/treatment/reconcile" +) + +// writeJournal commits a snapshot the way the agent does: under the lock, +// by atomic rename. +func writeJournal(t *testing.T, dir string, s *reconcile.State) { + t.Helper() + err := reconcile.Journal{Dir: dir}.WithLock(func(cur *reconcile.State, save func() error) error { + *cur = *s + return save() + }) + if err != nil { + t.Fatal(err) + } +} + +func verifiedState(revision uint64) *reconcile.State { + now := time.Unix(1700000000, 0) + d := fixtures.Desired(now) + d.Revision = revision + d.Assignments[0].Mode = treatment.ModeStable + d.Assignments[0].LeaseExpiresAt = time.Time{} + return &reconcile.State{AgentID: "host-1", Location: "dc1", BootID: "boot-1", Desired: d, Records: []reconcile.Record{{ApplicationRecord: treatment.ApplicationRecord{Assignment: d.Assignments[0], Status: treatment.ApplyVerified, AppliedAt: now, DesiredRevision: revision}, InstalledRoute: []byte{1}, InstalledBootID: "boot-1"}}} +} + +func record() *flat.XtcpFlatRecord { + return &flat.XtcpFlatRecord{Location: "dc1", NetnsInode: 7, InetDiagMsgFamily: 2, InetDiagMsgSocketDestination: []byte{192, 0, 2, 1}, InetDiagCong: "cubic"} +} + +type logSink struct { + mu sync.Mutex + lines []string +} + +func (l *logSink) printf(format string, args ...any) { + l.mu.Lock() + defer l.mu.Unlock() + l.lines = append(l.lines, fmt.Sprintf(format, args...)) +} + +func (l *logSink) count(sub string) int { + l.mu.Lock() + defer l.mu.Unlock() + n := 0 + for _, v := range l.lines { + if strings.Contains(v, sub) { + n++ + } + } + return n +} + +func eventually(t *testing.T, what string, fn func() bool) { + t.Helper() + deadline := time.Now().Add(5 * time.Second) + for time.Now().Before(deadline) { + if fn() { + return + } + time.Sleep(10 * time.Millisecond) + } + t.Fatalf("%s: condition not met within 5s", what) +} + +func TestWatcherRefreshContracts(t *testing.T) { + for _, tc := range []struct { + name, description string + prepare func(t *testing.T, dir string) + wantErr string + wantAgent string + wantAssignment string + wantLogged string + }{ + {"journal", "positive: a committed journal attributes records with the journal's agent and the daemon's location", func(t *testing.T, dir string) { writeJournal(t, dir, verifiedState(3)) }, "", "host-1", "trial-1", ""}, + {"missing-file", "negative: no snapshot yet means identity is not invented", func(*testing.T, string) {}, "", "", "", ""}, + {"missing-dir", "negative: a directory that does not exist yet is not an error", func(t *testing.T, dir string) { os.RemoveAll(dir) }, "", "", "", ""}, + {"corrupt", "negative: an invalid snapshot clears attribution and is logged", func(t *testing.T, dir string) { + if err := os.WriteFile(filepath.Join(dir, reconcile.SnapshotFile), []byte("nonsense {"), 0600); err != nil { + t.Fatal(err) + } + }, "invalid treatment journal", "", "", "not attributed"}, + {"held", "boundary: a journal under a hold stamps identity but no treatment", func(t *testing.T, dir string) { + s := verifiedState(4) + s.LocallySuspended = true + writeJournal(t, dir, s) + }, "", "host-1", "", ""}, + {"location-mismatch", "corner: a journal from another location keeps the daemon's label and warns once", func(t *testing.T, dir string) { + s := verifiedState(5) + s.Location = "elsewhere" + writeJournal(t, dir, s) + }, "", "host-1", "trial-1", "differs from this daemon"}, + } { + t.Run(tc.name, func(t *testing.T) { + dir := t.TempDir() + tc.prepare(t, dir) + var sink logSink + var st Stamper + w, err := NewWatcher(WatchConfig{Dir: dir, Location: "dc1", NetnsInode: 7, Log: sink.printf}, &st) + if err != nil { + t.Fatal(err) + } + err = w.Refresh(true) + if tc.wantErr == "" && err != nil || tc.wantErr != "" && (err == nil || !strings.Contains(err.Error(), tc.wantErr)) { + t.Fatalf("%s: refresh error %v, want %q", tc.description, err, tc.wantErr) + } + r := record() + st.Stamp(r) + if r.AgentId != tc.wantAgent || r.TreatmentAssignmentId != tc.wantAssignment || r.Location != "dc1" { + t.Fatalf("%s: agent=%q assignment=%q location=%q", tc.description, r.AgentId, r.TreatmentAssignmentId, r.Location) + } + if tc.wantAgent != "" && (r.HostBootId != "boot-1" || len(r.CollectionId) != 32) { + t.Fatalf("%s: boot/collection not stamped: %+v", tc.description, r) + } + // A second refresh logs nothing new: errors and warnings are once per cause. + _ = w.Refresh(true) + if tc.wantLogged != "" && sink.count(tc.wantLogged) != 1 { + t.Fatalf("%s: expected one log line containing %q, got %v", tc.description, tc.wantLogged, sink.lines) + } + if tc.wantLogged == "" && sink.count("not attributed")+sink.count("differs") != 0 { + t.Fatalf("%s: unexpected log lines %v", tc.description, sink.lines) + } + }) + } + if _, err := NewWatcher(WatchConfig{}, nil); err == nil { + t.Fatal("empty configuration accepted") + } +} + +func TestWatcherFollowsRenames(t *testing.T) { + dir := t.TempDir() + writeJournal(t, dir, verifiedState(1)) + var st Stamper + w, err := NewWatcher(WatchConfig{Dir: dir, NetnsInode: 7, Poll: time.Hour, Log: func(string, ...any) {}}, &st) + if err != nil { + t.Fatal(err) + } + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + done := make(chan struct{}) + go func() { defer close(done); w.Run(ctx) }() + revision := func() uint64 { + r := record() + st.Stamp(r) + return r.TreatmentStateRevision + } + eventually(t, "initial load", func() bool { return revision() == 1 }) + // Atomic rename of a new snapshot: the inotify path, with the hour-long + // poll ruling out the fallback. + writeJournal(t, dir, verifiedState(2)) + eventually(t, "rename picked up", func() bool { return revision() == 2 }) + // Removal clears attribution. + if err := os.Remove(filepath.Join(dir, reconcile.SnapshotFile)); err != nil { + t.Fatal(err) + } + eventually(t, "removal picked up", func() bool { return st.Current() == nil }) + // Re-creation restores it. + writeJournal(t, dir, verifiedState(3)) + eventually(t, "re-creation picked up", func() bool { return revision() == 3 }) + // A change that leaves size and mtime identical is still visible on Wake. + w.Wake() + eventually(t, "wake refreshes", func() bool { return revision() == 3 }) + cancel() + <-done +} + +func TestWatcherPollsWhenDirectoryAppearsLater(t *testing.T) { + parent := t.TempDir() + dir := filepath.Join(parent, "routes") + var st Stamper + w, err := NewWatcher(WatchConfig{Dir: dir, NetnsInode: 7, Poll: 20 * time.Millisecond, Log: func(string, ...any) {}}, &st) + if err != nil { + t.Fatal(err) + } + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + done := make(chan struct{}) + go func() { defer close(done); w.Run(ctx) }() + time.Sleep(50 * time.Millisecond) + if st.Current() != nil { + t.Fatal("index published before any journal existed") + } + writeJournal(t, dir, verifiedState(9)) + eventually(t, "late directory", func() bool { + r := record() + st.Stamp(r) + return r.TreatmentStateRevision == 9 && r.AgentId == "host-1" + }) + // The directory now exists, so the watch is added on the next poll and + // a rename is seen even if polling were slow. + writeJournal(t, dir, verifiedState(10)) + eventually(t, "post-attach rename", func() bool { + r := record() + st.Stamp(r) + return r.TreatmentStateRevision == 10 + }) + cancel() + <-done +} diff --git a/pkg/treatment/boundaries_test.go b/pkg/treatment/boundaries_test.go index 34b209e56..6207c9c69 100644 --- a/pkg/treatment/boundaries_test.go +++ b/pkg/treatment/boundaries_test.go @@ -1,6 +1,7 @@ package treatment import ( + "go/build" "go/parser" "go/token" "os" @@ -40,6 +41,7 @@ const ( type boundaryRule struct { description string root string + tags []string // build tags in effect for the walk; files excluded by //go:build are skipped forbidden []string // first-party package paths forbidden3p []string // substrings of third-party import paths expectAny []string // first-party packages that must be present, so the walk is not vacuous @@ -49,11 +51,26 @@ func treatmentBoundaryRules() []boundaryRule { tp := func(p string) string { return modulePath + "/" + p } return []boundaryRule{ { - description: "positive: the daemon links neither the controller, a SQL driver nor ClickHouse", + description: "positive: the untagged daemon reads journals only: no agent, no TLS identity, no RPC proto, no controller, no SQL driver, no ClickHouse", root: tp("cmd/xtcp2"), + forbidden: []string{tp("pkg/treatment/agent"), tp("pkg/treatment/identity"), tp("pkg/treatment/local"), tp("pkg/treatment/wire"), tp("gen/go/xtcp_treatment"), tp("pkg/treatment/controller"), tp("pkg/treatment/experiment")}, + forbidden3p: []string{"database/sql", "jackc/pgx", "clickhouse"}, + expectAny: []string{tp("pkg/xtcp"), tp("pkg/treatment/attribution")}, + }, + { + description: "positive: the daemon built with the treatment tag embeds the agent but still never the controller, a SQL driver or ClickHouse", + root: tp("cmd/xtcp2"), + tags: []string{"treatment"}, forbidden: []string{tp("pkg/treatment/controller"), tp("pkg/treatment/experiment")}, forbidden3p: []string{"database/sql", "jackc/pgx", "clickhouse"}, - expectAny: []string{tp("pkg/xtcp")}, + expectAny: []string{tp("pkg/xtcp"), tp("pkg/treatment/agent"), tp("pkg/treatment/identity")}, + }, + { + description: "positive: the standalone agent binary stands on the agent library, never on the controller or a database", + root: tp("cmd/xtcp2-treatment-agent"), + forbidden: []string{tp("pkg/treatment/controller"), tp("pkg/treatment/experiment"), tp("pkg/xtcp")}, + forbidden3p: []string{"database/sql", "jackc/pgx", "clickhouse"}, + expectAny: []string{tp("pkg/treatment/agent"), tp("pkg/treatment/local"), tp("pkg/treatment/attribution")}, }, { description: "positive: the agent library stands on identity and the contract, never on the controller", @@ -149,10 +166,10 @@ func treatmentBoundaryRules() []boundaryRule { func TestTreatmentImportBoundaries(t *testing.T) { for _, r := range treatmentBoundaryRules() { t.Run(r.description, func(t *testing.T) { - closure, thirdParty := firstPartyClosure(t, r.root) + closure, thirdParty := firstPartyClosure(t, r.root, r.tags) for _, p := range r.forbidden { if closure[p] { - t.Errorf("%s reaches %s; the chain is: %s", r.root, p, importChain(t, r.root, p)) + t.Errorf("%s reaches %s; the chain is: %s", r.root, p, importChain(t, r.root, p, r.tags)) } } for imp, from := range thirdParty { @@ -173,19 +190,31 @@ func TestTreatmentImportBoundaries(t *testing.T) { type anImport struct{ path, file string } -func packageImports(t *testing.T, pkgPath string) []anImport { +// packageImports lists the non-test imports of one package under the given +// build tags, honouring //go:build lines and _GOOS/_GOARCH suffixes the way +// the compiler would for the host platform, so a file such as +// cmd/xtcp2/treatments_embedded.go (//go:build treatment) only counts when +// the rule asks for that tag. +func packageImports(t *testing.T, pkgPath string, tags []string) []anImport { t.Helper() dir := filepath.Join(repoRoot, strings.TrimPrefix(pkgPath, modulePath+"/")) entries, err := os.ReadDir(dir) if err != nil { t.Fatalf("read package dir %s (for import %q): %v", dir, pkgPath, err) } + ctx := build.Default + ctx.BuildTags = append([]string(nil), tags...) var out []anImport fset := token.NewFileSet() for _, e := range entries { if e.IsDir() || !strings.HasSuffix(e.Name(), ".go") || strings.HasSuffix(e.Name(), "_test.go") { continue } + if ok, merr := ctx.MatchFile(dir, e.Name()); merr != nil { + t.Fatalf("match %s: %v", filepath.Join(dir, e.Name()), merr) + } else if !ok { + continue + } path := filepath.Join(dir, e.Name()) f, perr := parser.ParseFile(fset, path, nil, parser.ImportsOnly|parser.SkipObjectResolution) if perr != nil { @@ -208,7 +237,7 @@ func isFirstParty(imp string) bool { // firstPartyClosure follows non-test first-party imports from root and returns // the packages reached plus third-party edges mapped to one importer. -func firstPartyClosure(t *testing.T, root string) (map[string]bool, map[string]string) { +func firstPartyClosure(t *testing.T, root string, tags []string) (map[string]bool, map[string]string) { t.Helper() closure := map[string]bool{} thirdParty := map[string]string{} @@ -218,7 +247,7 @@ func firstPartyClosure(t *testing.T, root string) (map[string]bool, map[string]s return } closure[p] = true - for _, imp := range packageImports(t, p) { + for _, imp := range packageImports(t, p, tags) { switch { case isFirstParty(imp.path): walk(imp.path) @@ -240,7 +269,7 @@ func firstPartyClosure(t *testing.T, root string) (map[string]bool, map[string]s // importChain finds one path root -> ... -> target so a failure says where the // edge came from instead of only that it exists. -func importChain(t *testing.T, root, target string) string { +func importChain(t *testing.T, root, target string, tags []string) string { t.Helper() parent := map[string]string{root: ""} queue := []string{root} @@ -255,7 +284,7 @@ func importChain(t *testing.T, root, target string) string { sort.SliceStable(chain, func(i, j int) bool { return i > j }) return strings.Join(chain, " -> ") } - for _, imp := range packageImports(t, p) { + for _, imp := range packageImports(t, p, tags) { if isFirstParty(imp.path) { if _, seen := parent[imp.path]; !seen { parent[imp.path] = p diff --git a/tools/tcp-controller-integration/main.go b/tools/tcp-controller-integration/main.go index 9d3a0f068..677765840 100644 --- a/tools/tcp-controller-integration/main.go +++ b/tools/tcp-controller-integration/main.go @@ -30,6 +30,29 @@ import ( ) const pkiDir = "/var/lib/controller-test/pki" + +// agentUnit is the systemd unit running the treatment agent when xtcp2 does +// not embed it (-agentUnit=xtcp2-treatment-agent). Empty means the embedded +// arrangement, where the agent lives and dies with the xtcp2 unit. +var agentUnit string + +// agentProcess is the unit whose restart, stop and SIGHUP exercise the agent. +func agentProcess() string { + if agentUnit != "" { + return agentUnit + } + return "xtcp2" +} + +// treatmentUnits lists every unit that must be up for treatments to work. +func treatmentUnits() []string { + units := []string{"xtcp2", "xtcp2-treatment-watchdog"} + if agentUnit != "" { + units = append(units, agentUnit) + } + return units +} + const statePath = "/var/lib/xtcp2/routes/state.textproto" const evidenceDir = "/var/lib/controller-test/evidence" @@ -133,17 +156,17 @@ func serveAgent(address string) { command("tc", "qdisc", "add", "dev", "eth0", "parent", "1:3", "handle", "30:", "netem", "delay", "5ms") command("tc", "filter", "add", "dev", "eth0", "parent", "1:", "protocol", "ip", "prio", "1", "u32", "match", "ip", "dst", "198.18.0.2/32", "flowid", "1:2") command("tc", "filter", "add", "dev", "eth0", "parent", "1:", "protocol", "ip", "prio", "2", "u32", "match", "ip", "dst", "198.18.0.0/24", "flowid", "1:3") - systemctl("start", "load-near", "load-far", "xtcp2", "xtcp2-treatment-watchdog") + systemctl(append([]string{"start", "load-near", "load-far"}, treatmentUnits()...)...) case "status": result = localSnapshot() case "restart-agent": - systemctl("restart", "xtcp2") + systemctl("restart", agentProcess()) agentReady() case "stop-agent": - systemctl("stop", "xtcp2") + systemctl("stop", agentProcess()) systemctl("is-active", "xtcp2-treatment-watchdog") case "start-agent": - systemctl("start", "xtcp2") + systemctl("start", agentProcess()) agentReady() case "restart-load": systemctl("restart", "load-near") @@ -153,7 +176,7 @@ func serveAgent(address string) { systemctl("restart", "load-near") case "hup": agentReady() - systemctl("kill", "-s", "HUP", "xtcp2") + systemctl("kill", "-s", "HUP", agentProcess()) case "suspend", "resume": command("xtcp2-treatmentctl", "-mode="+a.Name, "-reason=integration-local-"+a.Name) case "delay": @@ -165,10 +188,14 @@ func serveAgent(address string) { require(a.Seconds > 0, "invalid clock") command("date", "-s", fmt.Sprintf("@%d", a.Seconds)) case "healthy": - result = systemctl("is-active", "xtcp2", "load-near", "load-far", "xtcp2-treatment-watchdog") + result = systemctl(append([]string{"is-active", "load-near", "load-far"}, treatmentUnits()...)...) case "evidence": s := localSnapshot() - s.Journal = command("journalctl", "-b", "--no-pager", "-n", "500", "-u", "xtcp2", "-u", "xtcp2-treatment-watchdog", "-u", "controller-test-agent") + args := []string{"-b", "--no-pager", "-n", "500", "-u", "controller-test-agent"} + for _, u := range treatmentUnits() { + args = append(args, "-u", u) + } + s.Journal = command("journalctl", args...) s.Rules = command("ip", "-j", "rule") s.Qdisc = command("tc", "-s", "qdisc", "show") result = s @@ -433,6 +460,7 @@ func main() { role := flag.String("role", "", "controller or agent") listen := flag.String("listen", "", "isolated guest fixture address") soak := flag.Bool("soak", false, "run repeated RTT/discovery/rollback cycles from the shared soak-config.json") + flag.StringVar(&agentUnit, "agentUnit", "", "agent role: systemd unit of a standalone xtcp2-treatment-agent; empty when xtcp2 embeds the agent") flag.Parse() if _, err := os.Stat("/etc/xtcp-controller-integration"); err != nil { log.Fatal("requires disposable integration VM marker") From 20f3a35a3df1395ac30bacb391e257bff2017ed6 Mon Sep 17 00:00:00 2001 From: "randomizedcoder dave.seddon.ca@gmail.com" Date: Sun, 25 Oct 2026 19:36:43 -0700 Subject: [PATCH 09/27] fix(treatment): attribution adopts the journal location when the daemon has none The standalone VM run failed its baseline because the slim daemon shipped location="" and the ClickHouse analysis filters by location. The watcher now takes the journal's location when -location/LOCATION is unset and keeps the daemon's own label when set. Co-Authored-By: Claude Fable 5.1 --- docs/status-tcp-controller.md | 8 +++++++- docs/tcp-controller.md | 7 ++++--- pkg/treatment/attribution/watch.go | 13 ++++++++++--- pkg/treatment/attribution/watch_test.go | 23 +++++++++++++++-------- 4 files changed, 36 insertions(+), 15 deletions(-) diff --git a/docs/status-tcp-controller.md b/docs/status-tcp-controller.md index c0b22b06a..f4bb3cd85 100644 --- a/docs/status-tcp-controller.md +++ b/docs/status-tcp-controller.md @@ -13,7 +13,7 @@ review being addressed. Items are listed in execution order. | 3 | explicit state machine; `Outcome` enum | done | 85b2cf59 | | 4 | repository ports, relational schema, pgx, domain types, `wire` | done | eae1d87d | | D1 | dual-mode agent, part 1: journal-specific proto, proto-free `reconcile`, `local`, `attribution`, watchdog off the agent | done | 6398338a | -| D2 | dual-mode agent, part 2: `-treatmentAttribution` journal watcher, `treatment` build tag, `cmd/xtcp2-treatment-agent`, Nix and module wiring | done | this commit | +| D2 | dual-mode agent, part 2: `-treatmentAttribution` journal watcher, `treatment` build tag, `cmd/xtcp2-treatment-agent`, Nix and module wiring | done | 87321a63, fix this commit | | 5 | evidence port (`CandidateSource`), ClickHouse as one adapter | next | | | 6 | auth as interceptor with per-RPC role option | pending | | | 7 | typed `xtcpnl.RuleWrite`; backend to `internal/goip/actuate` | pending | | @@ -73,6 +73,12 @@ process. - The boundary walk now honours build tags (`go/build.Context.MatchFile`), so it asserts the untagged daemon never reaches the agent, identity or the RPC proto, and the tagged one does reach the agent but never the controller. +- The first VM run of 87321a63 failed at the fresh six-host baseline with + `invalid evidence population`: in standalone mode the slim daemon had no + `-location`, so every record shipped `location=""` and the ClickHouse + analysis (which filters by location) saw no hosts. The watcher now adopts + the journal's location when the daemon has none and still keeps its own + when one is set (logged once either way). ## TODO diff --git a/docs/tcp-controller.md b/docs/tcp-controller.md index e9c9fa496..7e8cedef3 100644 --- a/docs/tcp-controller.md +++ b/docs/tcp-controller.md @@ -392,9 +392,10 @@ every reconcile pass. Standalone, xtcp2 watches the journal directory (inotify on the atomic rename of `state.textproto`, a 30 s poll as fallback, SIGHUP to re-read now) and rebuilds it from the snapshot; a missing or invalid snapshot clears attribution rather than stamping stale state. The embedded -agent writes its certificate-bound location onto records; the standalone -arrangement keeps the daemon's own `-location` and logs once if the journal -disagrees. +agent writes its certificate-bound location onto records. The standalone +arrangement keeps the daemon's own `-location` when one is set and logs once +if the journal disagrees; a daemon started without `-location`/`LOCATION` +adopts the journal's location, since evidence queries filter by it. A build without the `treatment` tag rejects `-treatmentConfig` at startup with a message naming both remedies, the same way a slim build rejects an enricher diff --git a/pkg/treatment/attribution/watch.go b/pkg/treatment/attribution/watch.go index 94f2671cb..446db2bfd 100644 --- a/pkg/treatment/attribution/watch.go +++ b/pkg/treatment/attribution/watch.go @@ -20,8 +20,10 @@ import ( type WatchConfig struct { // Dir is the journal directory (local.Config.Directory of the agent). Dir string - // Location is this daemon's own telemetry label. The journal's location - // is never written onto records; a difference is logged once. + // Location is this daemon's own telemetry label. When it is set the + // journal's location is never written onto records and a difference is + // logged once. When it is empty (no -location/LOCATION) the journal's + // location is adopted so evidence queries can find the host. Location string // Poll bounds staleness when inotify is unavailable, the directory does // not exist yet or an event was missed. Zero means 30s. @@ -121,7 +123,12 @@ func (w *Watcher) Refresh(force bool) error { w.warnedFor = s.Location w.cfg.Log("treatment attribution: journal location %q differs from this daemon's -location %q; records keep %q", s.Location, w.cfg.Location, w.cfg.Location) } - w.stamper.Publish(Build(s, Identity{AgentID: s.AgentID, Location: s.Location, BootID: s.BootID, CollectionID: w.collectionID, NetnsInode: w.cfg.NetnsInode}, false)) + adoptLocation := w.cfg.Location == "" + if adoptLocation && s.Location != "" && w.warnedFor != s.Location { + w.warnedFor = s.Location + w.cfg.Log("treatment attribution: this daemon has no -location; records take the journal's %q", s.Location) + } + w.stamper.Publish(Build(s, Identity{AgentID: s.AgentID, Location: s.Location, BootID: s.BootID, CollectionID: w.collectionID, NetnsInode: w.cfg.NetnsInode}, adoptLocation)) return nil } diff --git a/pkg/treatment/attribution/watch_test.go b/pkg/treatment/attribution/watch_test.go index 69d8cb713..59577973f 100644 --- a/pkg/treatment/attribution/watch_test.go +++ b/pkg/treatment/attribution/watch_test.go @@ -85,32 +85,39 @@ func TestWatcherRefreshContracts(t *testing.T) { wantAgent string wantAssignment string wantLogged string + daemonLocation string + wantLocation string }{ - {"journal", "positive: a committed journal attributes records with the journal's agent and the daemon's location", func(t *testing.T, dir string) { writeJournal(t, dir, verifiedState(3)) }, "", "host-1", "trial-1", ""}, - {"missing-file", "negative: no snapshot yet means identity is not invented", func(*testing.T, string) {}, "", "", "", ""}, - {"missing-dir", "negative: a directory that does not exist yet is not an error", func(t *testing.T, dir string) { os.RemoveAll(dir) }, "", "", "", ""}, + {"journal", "positive: a committed journal attributes records with the journal's agent and the daemon's location", func(t *testing.T, dir string) { writeJournal(t, dir, verifiedState(3)) }, "", "host-1", "trial-1", "", "dc1", "dc1"}, + {"missing-file", "negative: no snapshot yet means identity is not invented", func(*testing.T, string) {}, "", "", "", "", "dc1", "dc1"}, + {"missing-dir", "negative: a directory that does not exist yet is not an error", func(t *testing.T, dir string) { os.RemoveAll(dir) }, "", "", "", "", "dc1", "dc1"}, {"corrupt", "negative: an invalid snapshot clears attribution and is logged", func(t *testing.T, dir string) { if err := os.WriteFile(filepath.Join(dir, reconcile.SnapshotFile), []byte("nonsense {"), 0600); err != nil { t.Fatal(err) } - }, "invalid treatment journal", "", "", "not attributed"}, + }, "invalid treatment journal", "", "", "not attributed", "dc1", "dc1"}, {"held", "boundary: a journal under a hold stamps identity but no treatment", func(t *testing.T, dir string) { s := verifiedState(4) s.LocallySuspended = true writeJournal(t, dir, s) - }, "", "host-1", "", ""}, + }, "", "host-1", "", "", "dc1", "dc1"}, {"location-mismatch", "corner: a journal from another location keeps the daemon's label and warns once", func(t *testing.T, dir string) { s := verifiedState(5) s.Location = "elsewhere" writeJournal(t, dir, s) - }, "", "host-1", "trial-1", "differs from this daemon"}, + }, "", "host-1", "trial-1", "differs from this daemon", "dc1", "dc1"}, + {"adopt-location", "corner: a daemon without its own -location takes the journal's so evidence queries can find it", func(t *testing.T, dir string) { + s := verifiedState(6) + s.Location = "journal-dc" + writeJournal(t, dir, s) + }, "", "host-1", "trial-1", "records take the journal's", "", "journal-dc"}, } { t.Run(tc.name, func(t *testing.T) { dir := t.TempDir() tc.prepare(t, dir) var sink logSink var st Stamper - w, err := NewWatcher(WatchConfig{Dir: dir, Location: "dc1", NetnsInode: 7, Log: sink.printf}, &st) + w, err := NewWatcher(WatchConfig{Dir: dir, Location: tc.daemonLocation, NetnsInode: 7, Log: sink.printf}, &st) if err != nil { t.Fatal(err) } @@ -120,7 +127,7 @@ func TestWatcherRefreshContracts(t *testing.T) { } r := record() st.Stamp(r) - if r.AgentId != tc.wantAgent || r.TreatmentAssignmentId != tc.wantAssignment || r.Location != "dc1" { + if r.AgentId != tc.wantAgent || r.TreatmentAssignmentId != tc.wantAssignment || r.Location != tc.wantLocation { t.Fatalf("%s: agent=%q assignment=%q location=%q", tc.description, r.AgentId, r.TreatmentAssignmentId, r.Location) } if tc.wantAgent != "" && (r.HostBootId != "boot-1" || len(r.CollectionId) != 32) { From caba9bc4baebc92c6523214ccc26c8dea5108888 Mon Sep 17 00:00:00 2001 From: "randomizedcoder dave.seddon.ca@gmail.com" Date: Sun, 25 Oct 2026 19:43:07 -0700 Subject: [PATCH 10/27] refactor(treatment): evidence port with ClickHouse as one adapter Add pkg/treatment/controller/evidence: Source (measure a Window), CandidateSource (scan a location), Warehouse (both) and the controller's window policy (WindowFor, SettleDelay, MaxWindow). Maintain and Analyze take an evidence.Source; Discover takes an evidence.Warehouse and keeps only the template ASN/prefix scoping, destination cooldown and proposal logic. Move the ClickHouse client to evidence/clickhouse with analysis.sql and the former discovery SQL constant as embedded files. Candidates fails on an undecodable destination instead of skipping it. The controller root no longer contains SQL, an HTTP client or the ClickHouse name; the boundary test forbids the adapter from the controller and keeps the port a leaf. Handler tests use in-memory fakes (fixedEvidence, fakeWarehouse); the adapter keeps the transport and evidence contract tables, gains a candidate table, and keeps TestClickHouseAnalysisCounterDeltas for the Nix harness. Document artifact_sha256 as the digest of the unarchived response. Co-Authored-By: Claude Fable 5.1 --- cmd/xtcp2-controller/main.go | 18 +- docs/status-tcp-controller.md | 19 +- docs/tcp-controller.md | 17 +- gen/dart/xtcp_treatment/v1/treatment.pb.dart | 7 +- gen/go/xtcp_treatment/treatment.pb.go | 7 +- pkg/treatment/boundaries_test.go | 23 +- pkg/treatment/controller/analyze.go | 5 +- pkg/treatment/controller/clickhouse.go | 157 --------- pkg/treatment/controller/clickhouse_test.go | 72 ----- pkg/treatment/controller/contracts_test.go | 143 --------- pkg/treatment/controller/discovery.go | 123 +++----- .../controller/discovery_contracts_test.go | 43 +-- pkg/treatment/controller/discovery_test.go | 107 ++++--- .../{ => evidence/clickhouse}/analysis.sql | 0 .../evidence/clickhouse/clickhouse.go | 222 +++++++++++++ .../evidence/clickhouse/clickhouse_test.go | 297 ++++++++++++++++++ .../evidence/clickhouse/discovery.sql | 25 ++ pkg/treatment/controller/evidence/evidence.go | 120 +++++++ .../controller/evidence/evidence_test.go | 119 +++++++ pkg/treatment/controller/helpers_test.go | 52 ++- pkg/treatment/controller/maintenance.go | 14 +- pkg/treatment/controller/server.go | 9 +- proto/xtcp_treatment/v1/treatment.proto | 7 +- 23 files changed, 1043 insertions(+), 563 deletions(-) delete mode 100644 pkg/treatment/controller/clickhouse.go delete mode 100644 pkg/treatment/controller/clickhouse_test.go delete mode 100644 pkg/treatment/controller/contracts_test.go rename pkg/treatment/controller/{ => evidence/clickhouse}/analysis.sql (100%) create mode 100644 pkg/treatment/controller/evidence/clickhouse/clickhouse.go create mode 100644 pkg/treatment/controller/evidence/clickhouse/clickhouse_test.go create mode 100644 pkg/treatment/controller/evidence/clickhouse/discovery.sql create mode 100644 pkg/treatment/controller/evidence/evidence.go create mode 100644 pkg/treatment/controller/evidence/evidence_test.go diff --git a/cmd/xtcp2-controller/main.go b/cmd/xtcp2-controller/main.go index 08651b290..18b31aab8 100644 --- a/cmd/xtcp2-controller/main.go +++ b/cmd/xtcp2-controller/main.go @@ -19,6 +19,8 @@ import ( pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/controller" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/evidence" + chwarehouse "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/evidence/clickhouse" "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store/postgres" "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" "google.golang.org/grpc" @@ -76,11 +78,13 @@ func run() error { } } s := &controller.Server{Store: db, Hub: controller.NewNotificationHub(), DiscoveryWindow: *discoveryWindow} - var analysis *controller.ClickHouse - var source controller.EvidenceSource + // The warehouse is one adapter behind two ports: evidence.Source for + // analysis and lease renewal, evidence.Warehouse for discovery. + var warehouse *chwarehouse.Client + var source evidence.Source if *clickhouse != "" { - analysis = &controller.ClickHouse{URL: *clickhouse, Table: *table, Username: os.Getenv("XTCP_CLICKHOUSE_USER"), Password: os.Getenv("XTCP_CLICKHOUSE_PASSWORD")} - source = analysis + warehouse = &chwarehouse.Client{URL: *clickhouse, Table: *table, Username: os.Getenv("XTCP_CLICKHOUSE_USER"), Password: os.Getenv("XTCP_CLICKHOUSE_PASSWORD")} + source = warehouse } var templates []controller.DiscoveryTemplate s.EvidenceSource = source @@ -92,7 +96,7 @@ func run() error { if e = json.Unmarshal(b, &templates); e != nil { return e } - if analysis == nil { + if warehouse == nil { return fmt.Errorf("discovery requires ClickHouse") } } @@ -116,8 +120,8 @@ func run() error { if e := s.Maintain(ctx, source); e != nil { log.Printf("controller maintenance: %v", e) } - if analysis != nil && !time.Now().Before(nextDiscovery) { - if e := s.Discover(ctx, analysis, templates); e != nil { + if warehouse != nil && !time.Now().Before(nextDiscovery) { + if e := s.Discover(ctx, warehouse, templates); e != nil { log.Printf("candidate discovery: %v", e) } // Keep the cadence independent of query duration; otherwise a diff --git a/docs/status-tcp-controller.md b/docs/status-tcp-controller.md index f4bb3cd85..454f59b8a 100644 --- a/docs/status-tcp-controller.md +++ b/docs/status-tcp-controller.md @@ -14,8 +14,8 @@ review being addressed. Items are listed in execution order. | 4 | repository ports, relational schema, pgx, domain types, `wire` | done | eae1d87d | | D1 | dual-mode agent, part 1: journal-specific proto, proto-free `reconcile`, `local`, `attribution`, watchdog off the agent | done | 6398338a | | D2 | dual-mode agent, part 2: `-treatmentAttribution` journal watcher, `treatment` build tag, `cmd/xtcp2-treatment-agent`, Nix and module wiring | done | 87321a63, fix this commit | -| 5 | evidence port (`CandidateSource`), ClickHouse as one adapter | next | | -| 6 | auth as interceptor with per-RPC role option | pending | | +| 5 | evidence port (`Source`, `CandidateSource`, `Warehouse`), ClickHouse as one adapter | done | this commit | +| 6 | auth as interceptor with per-RPC role option | next | | | 7 | typed `xtcpnl.RuleWrite`; backend to `internal/goip/actuate` | pending | | | 9 | identity/location startup check | pending | | | 10 | blocking journal lock | pending | | @@ -80,6 +80,21 @@ process. the journal's location when the daemon has none and still keeps its own when one is set (logged once either way). +## Item 5 notes + +- `pkg/treatment/controller/evidence` holds the ports and the controller's + window policy (`WindowFor`, `SettleDelay`, `MaxWindow`); the controller + root no longer contains SQL text, an HTTP client or the word ClickHouse. +- `evidence/clickhouse` is the adapter: `analysis.sql` moved there unchanged, + the discovery query became `discovery.sql`, both embedded. `Candidates` + returns an error (not a skipped row) when a destination does not decode, + because that means the table is not the schema the adapter was written for. +- Handler tests use `fixedEvidence` and `fakeWarehouse`; the adapter keeps + the transport, evidence and (new) candidate contract tables plus the + real-SQL `TestClickHouseAnalysisCounterDeltas` the Nix harness greps for. +- `artifact_sha256` is documented as the digest of the unarchived response. + Archiving responses to the S3 destination remains a possible follow-up. + ## TODO - The watchdog and `pkg/treatment/local` still link gRPC, through diff --git a/docs/tcp-controller.md b/docs/tcp-controller.md index 7e8cedef3..c04ce7fe1 100644 --- a/docs/tcp-controller.md +++ b/docs/tcp-controller.md @@ -41,8 +41,10 @@ a typed [protobuf contract](../proto/xtcp_treatment/v1/) (`xtcp_treatment.v1`: ` | `pkg/treatment/attribution` | `xtcp2`, agent | the exposure index built from a journal snapshot and the hot-path telemetry stamp; the same code serves the embedded agent and a telemetry-only daemon | | `pkg/treatment/experiment` | controller | evaluation statistics, cohort allocation, phase advancement, state machine; pure functions over domain types | | `pkg/treatment/agent` | `xtcp2`, agent binary | check-in loop, watch stream, capabilities, over `local` and `attribution`; a library with two front ends | -| `pkg/treatment/controller` | `xtcp2-controller` | gRPC handlers, maintenance and discovery over the store ports; ClickHouse evidence | +| `pkg/treatment/controller` | `xtcp2-controller` | gRPC handlers, maintenance and discovery over the store and evidence ports | | `pkg/treatment/controller/store` | controller | repository interfaces (`Agents`, `Experiments`, `Evidence`, `Holds`, `Audit`, `Metrics`) and `Store.Tx` | +| `pkg/treatment/controller/evidence` | controller | warehouse ports: `Source` (measure a `Window`), `CandidateSource` (scan a location), `Warehouse` (both); `WindowFor` is the controller's window policy | +| `pkg/treatment/controller/evidence/clickhouse` | `xtcp2-controller` | the one warehouse adapter: `analysis.sql` and `discovery.sql` over the ClickHouse HTTP interface, parameterized, 8 MiB response cap | | `pkg/treatment/controller/store/postgres` | `xtcp2-controller` | the only package with SQL: relational schema, pgx through `database/sql`, advisory-lock transactions | | `pkg/treatment/controller/store/memory` | handler tests | map-backed store that passes the same conformance suite (`store/storetest`) | @@ -207,7 +209,7 @@ means every treated host has verified the assignment and the window is open. An assignment is identified by its `experiment_id`; one experiment never issues two concurrent assignments to the same host. -[analysis.sql](../pkg/treatment/controller/analysis.sql) computes whole-window +[analysis.sql](../pkg/treatment/controller/evidence/clickhouse/analysis.sql) computes whole-window counter deltas per boot, host, namespace and socket cookie, deduplicates poll timestamps, then aggregates per host. It rejects negative endpoint deltas and collection gaps over thirty seconds. Require normal collection at ten seconds @@ -215,6 +217,17 @@ or faster. This is not a complete exporter-health detector: lost short flows, resets followed by recovery within the window, and traffic missing altogether still require external collector-health monitoring. +The controller never names ClickHouse. `Maintain` and `Analyze` take an +`evidence.Source` and ask for `evidence.WindowFor(experiment, now)`; candidate +discovery takes an `evidence.Warehouse`, asks for `Candidates` within the +template's RTT and retransmission bounds, applies the template's ASN and prefix +allow-lists, then takes a baseline `Evidence` for each candidate before +proposing. `Evidence.artifact_sha256` is the digest of the raw response bytes +the summary was decoded from; the response is not archived, so reproducing a +measurement means re-running `query_version` over the same window. Handler +tests use in-memory fakes of both ports; the adapter has its own contract +tests plus a real-SQL test under `clickhouse local`. + The host is the experimental unit. Evaluation compares each host's log goodput change with contemporaneous controls and uses a conservative Welch interval budgeted over planned phases and minute-level looks. Tiny cohorts are safety diff --git a/gen/dart/xtcp_treatment/v1/treatment.pb.dart b/gen/dart/xtcp_treatment/v1/treatment.pb.dart index 39626689d..c9eafbe51 100644 --- a/gen/dart/xtcp_treatment/v1/treatment.pb.dart +++ b/gen/dart/xtcp_treatment/v1/treatment.pb.dart @@ -1217,7 +1217,12 @@ class Evidence extends $pb.GeneratedMessage { @$pb.TagNumber(7) $pb.PbList get hosts => $_getList(6); - /// SHA-256 of the complete analysis response this summary was derived from. + /// SHA-256 of the raw warehouse response bytes, as received, that this + /// summary was decoded from. The response itself is not archived: the + /// digest lets two controllers (or a later audit) confirm they decoded the + /// same bytes, and reproducing it means re-running query_version over + /// [window_start, window_end) against the same table. Archiving responses + /// to object storage is a possible follow-up, not a current guarantee. @$pb.TagNumber(8) $core.String get artifactSha256 => $_getSZ(7); @$pb.TagNumber(8) diff --git a/gen/go/xtcp_treatment/treatment.pb.go b/gen/go/xtcp_treatment/treatment.pb.go index df51e7597..b1ebb79d7 100644 --- a/gen/go/xtcp_treatment/treatment.pb.go +++ b/gen/go/xtcp_treatment/treatment.pb.go @@ -1120,7 +1120,12 @@ type Evidence struct { QueryVersion string `protobuf:"bytes,5,opt,name=query_version,json=queryVersion,proto3" json:"query_version,omitempty"` EnrichmentVersion string `protobuf:"bytes,6,opt,name=enrichment_version,json=enrichmentVersion,proto3" json:"enrichment_version,omitempty"` Hosts []*HostEvidence `protobuf:"bytes,7,rep,name=hosts,proto3" json:"hosts,omitempty"` - // SHA-256 of the complete analysis response this summary was derived from. + // SHA-256 of the raw warehouse response bytes, as received, that this + // summary was decoded from. The response itself is not archived: the + // digest lets two controllers (or a later audit) confirm they decoded the + // same bytes, and reproducing it means re-running query_version over + // [window_start, window_end) against the same table. Archiving responses + // to object storage is a possible follow-up, not a current guarantee. ArtifactSha256 string `protobuf:"bytes,8,opt,name=artifact_sha256,json=artifactSha256,proto3" json:"artifact_sha256,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache diff --git a/pkg/treatment/boundaries_test.go b/pkg/treatment/boundaries_test.go index 6207c9c69..42bef2fd4 100644 --- a/pkg/treatment/boundaries_test.go +++ b/pkg/treatment/boundaries_test.go @@ -25,8 +25,9 @@ import ( // pkg/treatment/attribution ExposureIndex + telemetry stamping from a journal snapshot [daemon, agent] // pkg/treatment/experiment statistics + state machine, pure [controller] // pkg/treatment/agent check-in loop + watch over local + attribution [daemon (treatment tag), agent binary] -// pkg/treatment/controller gRPC handlers over store ports [controller binary only] +// pkg/treatment/controller gRPC handlers over store and evidence ports [controller binary only] // pkg/treatment/controller/store ports; postgres/ and memory/ implement them [controller] +// pkg/treatment/controller/evidence warehouse ports; clickhouse/ implements them [controller] // // The daemon (cmd/xtcp2) must never reach the controller, a SQL driver or the // ClickHouse client: that was the regression that prompted this file. @@ -159,6 +160,26 @@ func treatmentBoundaryRules() []boundaryRule { forbidden: []string{tp("pkg/treatment/agent"), tp("internal/goip")}, expectAny: []string{tp("pkg/treatment/identity")}, }, + { + description: "negative: the controller depends on the evidence port, never on the ClickHouse adapter (main wires the adapter)", + root: tp("pkg/treatment/controller"), + forbidden: []string{tp("pkg/treatment/controller/evidence/clickhouse")}, + expectAny: []string{tp("pkg/treatment/controller/evidence")}, + }, + { + description: "boundary: the evidence port is a leaf over the contract: no controller, no store, no proto, no HTTP client", + root: tp("pkg/treatment/controller/evidence"), + forbidden: []string{tp("pkg/treatment/controller"), tp("pkg/treatment/controller/store"), tp("pkg/treatment/wire"), tp("gen/go/xtcp_treatment"), tp("pkg/treatment/experiment")}, + forbidden3p: []string{"net/http", "database/sql"}, + expectAny: []string{tp("pkg/treatment")}, + }, + { + description: "corner: the ClickHouse adapter implements the port and knows nothing of the controller, store, wire types or proto", + root: tp("pkg/treatment/controller/evidence/clickhouse"), + forbidden: []string{tp("pkg/treatment/controller"), tp("pkg/treatment/controller/store"), tp("pkg/treatment/wire"), tp("gen/go/xtcp_treatment"), tp("pkg/treatment/experiment")}, + forbidden3p: []string{"database/sql", "google.golang.org/grpc"}, + expectAny: []string{tp("pkg/treatment/controller/evidence")}, + }, } } diff --git a/pkg/treatment/controller/analyze.go b/pkg/treatment/controller/analyze.go index a198cf4d3..821b8d9a6 100644 --- a/pkg/treatment/controller/analyze.go +++ b/pkg/treatment/controller/analyze.go @@ -6,6 +6,7 @@ import ( pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/evidence" "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store" "github.com/randomizedcoder/xtcp2/pkg/treatment/experiment" "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" @@ -26,7 +27,7 @@ func (s *Server) Analyze(ctx context.Context, r *pb.AnalyzeRequest) (*pb.Analyze return nil, status.Error(codes.InvalidArgument, "invalid experiment ID") } if s.EvidenceSource == nil { - return nil, status.Error(codes.Unavailable, "ClickHouse analysis is disabled") + return nil, status.Error(codes.Unavailable, "evidence analysis is disabled: no warehouse configured") } var n *treatment.Experiment e = s.Store.Tx(ctx, func(rp store.Repos) error { @@ -50,7 +51,7 @@ func (s *Server) Analyze(ctx context.Context, r *pb.AnalyzeRequest) (*pb.Analyze if n.Status == treatment.StatusComplete { input = s.driftInput(n) } - ev, e := s.EvidenceSource.Analyze(ctx, input) + ev, e := s.EvidenceSource.Evidence(ctx, evidence.WindowFor(input, s.now())) if e != nil { return nil, failure(e) } diff --git a/pkg/treatment/controller/clickhouse.go b/pkg/treatment/controller/clickhouse.go deleted file mode 100644 index 85f528630..000000000 --- a/pkg/treatment/controller/clickhouse.go +++ /dev/null @@ -1,157 +0,0 @@ -package controller - -import ( - "bytes" - "context" - "crypto/sha256" - _ "embed" - "encoding/hex" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "net/netip" - "net/url" - "regexp" - "strconv" - "strings" - "time" - - "github.com/randomizedcoder/xtcp2/pkg/treatment" -) - -//go:embed analysis.sql -var analysisSQL string -var tableName = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*\.[a-zA-Z_][a-zA-Z0-9_]*$`) - -type ClickHouse struct { - URL, Username, Password, Table string - Client *http.Client - Now func() time.Time -} - -func (c *ClickHouse) now() time.Time { - if c.Now != nil { - return c.Now() - } - return time.Now() -} - -func (c *ClickHouse) query(ctx context.Context, query string, params map[string]string) ([]byte, error) { - if !tableName.MatchString(c.Table) { - return nil, errors.New("ClickHouse table must be database.table") - } - u, e := url.Parse(c.URL) - if e != nil { - return nil, e - } - if u.Scheme != "http" && u.Scheme != "https" { - return nil, errors.New("ClickHouse URL must be HTTP(S)") - } - v := u.Query() - for k, val := range params { - v.Set("param_"+k, val) - } - v.Set("max_execution_time", "30") - v.Set("max_result_rows", "10000") - v.Set("result_overflow_mode", "throw") - v.Set("output_format_json_quote_64bit_integers", "0") - u.RawQuery = v.Encode() - body := strings.ReplaceAll(query, "{{TABLE}}", c.Table) - r, e := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), strings.NewReader(body)) - if e != nil { - return nil, e - } - r.SetBasicAuth(c.Username, c.Password) - client := c.Client - if client == nil { - client = &http.Client{Timeout: 35 * time.Second} - } - response, e := client.Do(r) - if e != nil { - return nil, e - } - defer response.Body.Close() - b, e := io.ReadAll(io.LimitReader(response.Body, 8<<20)) - if e != nil { - return nil, e - } - if len(b) >= 8<<20 { - return nil, errors.New("ClickHouse result exceeds 8 MiB") - } - if response.StatusCode != http.StatusOK { - return nil, fmt.Errorf("ClickHouse status %d: %.1024s", response.StatusCode, b) - } - return b, nil -} - -func (c *ClickHouse) Analyze(ctx context.Context, e *treatment.Experiment) (*treatment.Evidence, error) { - p := e.CurrentPhase() - if p == nil { - end := c.now().Add(-20 * time.Second) - return c.Evidence(ctx, e.Location, e.Treatment.Destination, e.ID, e.CohortOrder, end.Add(-time.Hour), end) - } - start := p.IssuedAt - if !p.ObservationStartedAt.IsZero() { - start = p.ObservationStartedAt - } - return c.Evidence(ctx, e.Location, e.Treatment.Destination, e.ID, e.CohortOrder, start, c.now().Add(-20*time.Second)) -} - -// hostRow is one JSONEachRow line of analysis.sql. -type hostRow struct { - AgentID string `json:"agent_id"` - Sockets uint64 `json:"sockets"` - BytesAcked uint64 `json:"bytes_acked"` - GoodputBps float64 `json:"goodput_bps"` - RetransmissionRatio float64 `json:"retransmission_ratio"` - RttP95Ms float64 `json:"rtt_p95_ms"` - ExposedSockets uint64 `json:"exposed_sockets"` - CollectionHealthy bool `json:"collection_healthy"` -} - -func (c *ClickHouse) Evidence(ctx context.Context, location string, destination netip.Prefix, assignment string, agents []string, start, end time.Time) (*treatment.Evidence, error) { - if !end.After(start) || end.Sub(start) > 7*24*time.Hour || len(agents) == 0 || len(agents) > treatment.MaxAgents { - return nil, errors.New("invalid analysis window/population") - } - quoted := make([]string, len(agents)) - for i, id := range agents { - if !treatment.ValidID(id) { - return nil, errors.New("invalid agent") - } - quoted[i] = "'" + id + "'" - } - if !destination.IsValid() { - return nil, errors.New("invalid destination") - } - addr := destination.Addr().AsSlice() - if destination.Addr().Is4() { - addr = append(addr, make([]byte, 12)...) - } - params := map[string]string{"start": strconv.FormatInt(start.UnixNano(), 10), "end": strconv.FormatInt(end.UnixNano(), 10), "location": location, "assignment": assignment, "agents": "[" + strings.Join(quoted, ",") + "]", "destination_hex": strings.ToUpper(hex.EncodeToString(addr))} - b, e := c.query(ctx, analysisSQL, params) - if e != nil { - return nil, e - } - digest := sha256.Sum256(b) - sha := hex.EncodeToString(digest[:]) - analyzed := c.now() - v := &treatment.Evidence{ID: "ev-" + sha[:24] + "-" + strconv.FormatInt(analyzed.UnixNano(), 10), AnalyzedAt: analyzed, WindowStart: start, WindowEnd: end, QueryVersion: "tcp-host-deltas-v1", EnrichmentVersion: "xtcp-schema-2", ArtifactSHA256: sha} - dec := json.NewDecoder(bytes.NewReader(b)) - for { - var h hostRow - e := dec.Decode(&h) - if e == io.EOF { - break - } - if e != nil { - return nil, e - } - v.Hosts = append(v.Hosts, treatment.HostEvidence(h)) - } - if e = treatment.ValidateEvidence(v); e != nil { - return nil, e - } - return v, nil -} diff --git a/pkg/treatment/controller/clickhouse_test.go b/pkg/treatment/controller/clickhouse_test.go deleted file mode 100644 index 78ef105a3..000000000 --- a/pkg/treatment/controller/clickhouse_test.go +++ /dev/null @@ -1,72 +0,0 @@ -package controller - -import ( - "encoding/json" - "os" - "os/exec" - "path/filepath" - "strings" - "testing" -) - -// Execute the production SQL against an isolated ClickHouse local database. -// No live service or production telemetry is accessed. -func TestClickHouseAnalysisCounterDeltas(t *testing.T) { - binary := os.Getenv("XTCP_CLICKHOUSE_TEST_BINARY") - if binary == "" { - t.Skip("set XTCP_CLICKHOUSE_TEST_BINARY to run real SQL") - } - setup := `CREATE TABLE default.sockets ( - agent_id String,host_boot_id String,netns_inode UInt64,inet_diag_msg_socket_cookie UInt64, - timestamp_ns DateTime64(9),tcp_info_bytes_acked UInt64,tcp_info_bytes_sent UInt64, - tcp_info_bytes_retrans UInt64,tcp_info_rtt UInt32,tcp_info_min_rtt UInt32, - treatment_algorithm_exposed Bool,treatment_assignment_id String,location String, - inet_diag_msg_state UInt8,inet_diag_msg_socket_destination FixedString(16), - inet_diag_msg_family UInt8,enrich_socket_dest_asn UInt64) ENGINE=Memory; - INSERT INTO default.sockets SELECT 'host-1','boot-1',1,1, - fromUnixTimestamp64Nano(1700000000000000000+toInt64(number)*10000000000), - 1000000+number*1000,1000000+number*1000,number*10,1000,500,true,'trial','dc1',1, - unhex('C0000201000000000000000000000000'),2,64512 FROM numbers(6); - INSERT INTO default.sockets SELECT * FROM default.sockets; - ` - query := strings.ReplaceAll(analysisSQL, "{{TABLE}}", "default.sockets") - run := func(sql string) []byte { - t.Helper() - dir := t.TempDir() - file := filepath.Join(dir, "query.sql") - if e := os.WriteFile(file, []byte(sql), 0600); e != nil { - t.Fatal(e) - } - cmd := exec.Command(binary, "local", "--path", filepath.Join(dir, "data"), "--queries-file", file, "--multiquery", - "--param_start=1700000000000000000", "--param_end=1700000060000000000", "--param_location=dc1", - "--param_agents=['host-1']", "--param_destination_hex=C0000201000000000000000000000000", "--param_assignment=trial", - "--param_minimum_rtt=0", "--param_maximum_rtt=10", "--param_minimum_retransmission=0", - "--output_format_json_quote_64bit_integers=0") - out, e := cmd.CombinedOutput() - if e != nil { - t.Fatalf("ClickHouse SQL: %v\n%s", e, out) - } - return out - } - var h hostRow - if e := json.Unmarshal(run(setup+query), &h); e != nil { - t.Fatal(e) - } - if h.Sockets != 1 || h.BytesAcked != 5000 || h.ExposedSockets != 1 || !h.CollectionHealthy || h.RttP95Ms != 1 || h.RetransmissionRatio != .01 { - t.Fatalf("bad window deltas/deduplication: %+v", h) - } - reset := `INSERT INTO default.sockets SELECT 'host-1','boot-1',1,1, - fromUnixTimestamp64Nano(1700000055000000000),1,1,0,1000,500,true,'trial','dc1',1, - unhex('C0000201000000000000000000000000'),2,64512;` - if e := json.Unmarshal(run(setup+reset+query), &h); e != nil { - t.Fatal(e) - } - if h.CollectionHealthy { - t.Fatal("counter reset accepted as healthy") - } - // Syntax/type-check candidate discovery too; a one-host fixture cannot meet - // its six-host eligibility threshold. - if out := run(setup + strings.ReplaceAll(discoverySQL, "{{TABLE}}", "default.sockets")); len(out) != 0 { - t.Fatalf("unexpected discovery: %s", out) - } -} diff --git a/pkg/treatment/controller/contracts_test.go b/pkg/treatment/controller/contracts_test.go deleted file mode 100644 index 100342c54..000000000 --- a/pkg/treatment/controller/contracts_test.go +++ /dev/null @@ -1,143 +0,0 @@ -package controller - -import ( - "context" - "errors" - "io" - "net/http" - "net/netip" - "strings" - "testing" - "time" -) - -type trackedBody struct { - io.Reader - closed bool -} - -func (b *trackedBody) Close() error { b.closed = true; return nil } - -type brokenReader struct{} - -func (brokenReader) Read([]byte) (int, error) { return 0, errors.New("broken response") } - -func TestClickHouseTransportContracts(t *testing.T) { - for _, tc := range []struct { - name, description, table, url, body, wantError string - status int - transportError, readError bool - }{ - {name: "valid", description: "query is parameterized and authenticated", status: 200, body: "ok"}, - {name: "table", description: "table names cannot inject SQL", table: "db.rows;DROP TABLE rows", wantError: "database.table"}, - {name: "scheme", description: "only HTTP(S) transports are accepted", url: "file:///tmp/rows", wantError: "HTTP(S)"}, - {name: "http-error", description: "server errors are surfaced", status: 500, body: "query failed", wantError: "status 500"}, - {name: "network", description: "transport errors are surfaced", transportError: true, wantError: "offline"}, - {name: "read", description: "partial response errors fail analysis", status: 200, readError: true, wantError: "broken response"}, - {name: "size-limit", description: "oversized responses fail closed", status: 200, body: strings.Repeat("x", 8<<20), wantError: "exceeds"}, - } { - t.Run(tc.name, func(t *testing.T) { - table := tc.table - if table == "" { - table = "db.rows" - } - endpoint := tc.url - if endpoint == "" { - endpoint = "https://fixture.invalid/" - } - body := &trackedBody{Reader: strings.NewReader(tc.body)} - if tc.readError { - body.Reader = brokenReader{} - } - called := false - c := &ClickHouse{URL: endpoint, Table: table, Username: "user", Password: "test-secret", Client: &http.Client{Transport: discoveryTransport(func(r *http.Request) (*http.Response, error) { - called = true - if tc.transportError { - return nil, errors.New("offline") - } - u, p, ok := r.BasicAuth() - if !ok || u != "user" || p != "test-secret" { - t.Fatal("missing authentication") - } - query, err := io.ReadAll(r.Body) - if err != nil { - t.Fatal(err) - } - if string(query) != "SELECT * FROM db.rows" || r.Method != "POST" || r.URL.Query().Get("param_location") != "dc'quote" || r.URL.Query().Get("result_overflow_mode") != "throw" { - t.Fatal("query contract changed") - } - return &http.Response{StatusCode: tc.status, Body: body, Header: make(http.Header)}, nil - })}} - got, err := c.query(context.Background(), "SELECT * FROM {{TABLE}}", map[string]string{"location": "dc'quote"}) - if tc.wantError == "" { - if err != nil || string(got) != tc.body { - t.Fatalf("%s: %q %v", tc.description, got, err) - } - } else if err == nil || !strings.Contains(err.Error(), tc.wantError) { - t.Fatalf("%s: want %q got %v", tc.description, tc.wantError, err) - } - if called && !tc.transportError && !body.closed { - t.Fatal("response body leaked") - } - if (tc.table != "" || tc.url != "") && called { - t.Fatal("invalid configuration reached transport") - } - }) - } -} - -func TestClickHouseEvidenceContracts(t *testing.T) { - const host = `{"agent_id":"host","sockets":30,"bytes_acked":1048576,"goodput_bps":1000,"rtt_p95_ms":5,"collection_healthy":true}` - now := time.Unix(1700000000, 0) - for _, tc := range []struct { - name, description, body string - destination netip.Prefix - agents []string - window time.Duration - invalidDestination bool - wantError string - }{ - {name: "valid", description: "complete response records provenance", body: host}, - {name: "empty", description: "empty response cannot renew a lease", wantError: "population"}, - {name: "malformed", description: "malformed JSON is rejected", body: "{", wantError: "EOF"}, - {name: "duplicate", description: "duplicate hosts cannot inflate sample size", body: host + "\n" + host, wantError: "duplicate"}, - {name: "bad-window", description: "empty analysis interval is rejected", window: -time.Second, wantError: "window"}, - {name: "long-window", description: "analysis is limited to seven days", window: 7*24*time.Hour + time.Second, wantError: "window"}, - {name: "bad-agent", description: "agent IDs cannot inject query parameters", agents: []string{"host'"}, wantError: "invalid agent"}, - {name: "bad-destination", description: "an invalid destination fails before the query", destination: netip.Prefix{}, invalidDestination: true, wantError: "invalid destination"}, - } { - t.Run(tc.name, func(t *testing.T) { - window := tc.window - if window == 0 { - window = time.Minute - } - agents := tc.agents - if agents == nil { - agents = []string{"host"} - } - dest := tc.destination - if !tc.invalidDestination { - dest = netip.MustParsePrefix("192.0.2.1/32") - } - c := &ClickHouse{URL: "http://fixture.invalid", Table: "db.rows", Now: func() time.Time { return now }, Client: &http.Client{Transport: discoveryTransport(func(r *http.Request) (*http.Response, error) { - if r.URL.Query().Get("param_destination_hex") != "C0000201000000000000000000000000" { - t.Fatal("wrong destination encoding") - } - return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(tc.body)), Header: make(http.Header)}, nil - })}} - ev, err := c.Evidence(context.Background(), "dc", dest, "assignment", agents, now.Add(-window), now) - if tc.wantError != "" { - if err == nil || !strings.Contains(err.Error(), tc.wantError) { - t.Fatalf("%s: want %q got %v", tc.description, tc.wantError, err) - } - return - } - if err != nil { - t.Fatal(err) - } - if len(ev.Hosts) != 1 || len(ev.ArtifactSHA256) != 64 || ev.QueryVersion != "tcp-host-deltas-v1" || !ev.AnalyzedAt.Equal(now) { - t.Fatalf("%s: bad evidence %v", tc.description, ev) - } - }) - } -} diff --git a/pkg/treatment/controller/discovery.go b/pkg/treatment/controller/discovery.go index e93651742..ce69da23e 100644 --- a/pkg/treatment/controller/discovery.go +++ b/pkg/treatment/controller/discovery.go @@ -1,20 +1,17 @@ package controller import ( - "bytes" "context" "crypto/sha256" "encoding/hex" - "encoding/json" "errors" "fmt" - "io" "net/netip" - "strconv" "time" pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/evidence" "github.com/randomizedcoder/xtcp2/pkg/treatment/wire" ) @@ -44,6 +41,30 @@ func (t DiscoveryTemplate) policy() treatment.Policy { return p } +// inScope applies the template's ASN and prefix allow-lists; an empty list +// allows everything. +func (t DiscoveryTemplate) inScope(c evidence.Candidate, prefixes []netip.Prefix) bool { + if len(t.ASNs) > 0 { + match := false + for _, a := range t.ASNs { + match = match || a == c.ASN + } + if !match { + return false + } + } + if len(prefixes) > 0 { + match := false + for _, p := range prefixes { + match = match || p.Contains(c.Destination.Addr()) + } + if !match { + return false + } + } + return true +} + func (t DiscoveryTemplate) settings() treatment.TCPSettings { cc := t.CongestionControl return treatment.TCPSettings{ @@ -55,36 +76,21 @@ func (t DiscoveryTemplate) settings() treatment.TCPSettings { } } -const discoverySQL = ` -WITH sockets AS ( - SELECT agent_id,host_boot_id,netns_inode,inet_diag_msg_socket_cookie, - any(hex(inet_diag_msg_socket_destination)) AS destination_hex, - any(inet_diag_msg_family) AS family, - argMax(enrich_socket_dest_asn,timestamp_ns) AS asn, - min(tcp_info_min_rtt)/1000 AS min_rtt_ms, - max(tcp_info_bytes_sent)-min(tcp_info_bytes_sent) AS sent, - max(tcp_info_bytes_retrans)-min(tcp_info_bytes_retrans) AS retrans - FROM {{TABLE}} - WHERE timestamp_ns>=fromUnixTimestamp64Nano({start:Int64}) AND timestamp_ns=3 -) -SELECT destination_hex,family,any(asn) AS asn,groupUniqArray(10000)(agent_id) AS agents, - quantileExact(0.5)(min_rtt_ms) AS rtt_ms,sum(retrans)/greatest(1,sum(sent)) AS retransmission_ratio -FROM sockets GROUP BY destination_hex,family -HAVING length(agents)>=6 AND rtt_ms>={minimum_rtt:Float64} AND rtt_ms<={maximum_rtt:Float64} - AND retransmission_ratio>={minimum_retransmission:Float64} -ORDER BY sum(sent) DESC LIMIT 10 FORMAT JSONEachRow` - -func (s *Server) Discover(ctx context.Context, c *ClickHouse, templates []DiscoveryTemplate) error { +// Discover scans each template's location for candidate destinations, takes +// a baseline for each and proposes a hypothesis. Proposals stay in +// StatusProposed until an operator reviews them. Templates are validated +// before the warehouse is touched; a busy location is skipped without a scan. +func (s *Server) Discover(ctx context.Context, w evidence.Warehouse, templates []DiscoveryTemplate) error { window := s.DiscoveryWindow if window == 0 { window = time.Hour } - if window < time.Minute || window > 7*24*time.Hour { + if window < time.Minute || window > evidence.MaxWindow { return errors.New("discovery window must be between one minute and seven days") } + if w == nil { + return errors.New("candidate discovery requires a warehouse") + } var failures []error for _, t := range templates { if !treatment.ValidID(t.ID) || !treatment.ValidID(t.Location) || (t.CongestionControl != "cubic" && t.CongestionControl != "bbr") || t.MaximumRTTMs <= t.MinimumRTTMs || t.MinimumRTTMs < 0 || t.MinimumRetransmissionRatio < 0 || t.MinimumRetransmissionRatio > 1 { @@ -114,65 +120,20 @@ func (s *Server) Discover(ctx context.Context, c *ClickHouse, templates []Discov if busy { continue } - end := s.now().Add(-20 * time.Second) + end := s.now().Add(-evidence.SettleDelay) start := end.Add(-window) - params := map[string]string{"location": t.Location, "start": strconv.FormatInt(start.UnixNano(), 10), "end": strconv.FormatInt(end.UnixNano(), 10), "minimum_rtt": fmt.Sprint(t.MinimumRTTMs), "maximum_rtt": fmt.Sprint(t.MaximumRTTMs), "minimum_retransmission": fmt.Sprint(t.MinimumRetransmissionRatio)} - data, e := c.query(ctx, discoverySQL, params) + candidates, e := w.Candidates(ctx, evidence.CandidateQuery{Location: t.Location, Start: start, End: end, MinimumRTTMs: t.MinimumRTTMs, MaximumRTTMs: t.MaximumRTTMs, MinimumRetransmissionRatio: t.MinimumRetransmissionRatio}) if e != nil { failures = append(failures, e) continue } - dec := json.NewDecoder(bytes.NewReader(data)) - for { - var row struct { - DestinationHex string `json:"destination_hex"` - Family uint32 `json:"family"` - ASN uint64 `json:"asn"` - Agents []string `json:"agents"` - RTT float64 `json:"rtt_ms"` - Retransmission float64 `json:"retransmission_ratio"` - } - if e = dec.Decode(&row); e == io.EOF { - break - } - if e != nil { - failures = append(failures, e) - break - } - if len(t.ASNs) > 0 { - match := false - for _, a := range t.ASNs { - match = match || a == row.ASN - } - if !match { - continue - } - } - raw, e := hex.DecodeString(row.DestinationHex) - if e != nil { - failures = append(failures, e) - continue - } - if row.Family == 2 && len(raw) >= 4 { - raw = raw[:4] - } - ip, ok := netip.AddrFromSlice(raw) - if !ok { + for _, c := range candidates { + if !t.inScope(c, prefixes) { continue } - if len(prefixes) > 0 { - match := false - for _, p := range prefixes { - match = match || p.Contains(ip) - } - if !match { - continue - } - } - destination := netip.PrefixFrom(ip, ip.BitLen()) // Completed or held treatments and recent rejected trials suppress // repeat proposals. Cooldown is at least a day after terminal state. - exists, e := s.Store.Repos().Experiments.DestinationBlocked(ctx, t.Location, destination, s.now().Add(-24*time.Hour)) + exists, e := s.Store.Repos().Experiments.DestinationBlocked(ctx, t.Location, c.Destination, s.now().Add(-24*time.Hour)) if e != nil { failures = append(failures, e) continue @@ -180,14 +141,14 @@ func (s *Server) Discover(ctx context.Context, c *ClickHouse, templates []Discov if exists { continue } - seed := sha256.Sum256([]byte(t.ID + "/" + destination.String() + "/" + s.now().UTC().Format("2006-01-02"))) + seed := sha256.Sum256([]byte(t.ID + "/" + c.Destination.String() + "/" + s.now().UTC().Format("2006-01-02"))) id := "hyp-" + hex.EncodeToString(seed[:12]) - baseline, e := c.Evidence(ctx, t.Location, destination, id, row.Agents, start, end) + baseline, e := w.Evidence(ctx, evidence.Window{Location: t.Location, Destination: c.Destination, ExperimentID: id, Agents: c.Agents, Start: start, End: end}) if e != nil { failures = append(failures, e) continue } - n := &treatment.Experiment{ID: id, Location: t.Location, Hypothesis: fmt.Sprintf("Template %s: test %s for %s (ASN %d), observed RTT %.3f ms and retransmission ratio %.6f; this is a hypothesis, not an efficacy claim", t.ID, t.CongestionControl, destination, row.ASN, row.RTT, row.Retransmission), Treatment: treatment.Treatment{ID: t.ID, Revision: 1, Destination: destination, Settings: t.settings()}, Policy: t.policy(), Baseline: baseline, CohortOrder: row.Agents} + n := &treatment.Experiment{ID: id, Location: t.Location, Hypothesis: fmt.Sprintf("Template %s: test %s for %s (ASN %d), observed RTT %.3f ms and retransmission ratio %.6f; this is a hypothesis, not an efficacy claim", t.ID, t.CongestionControl, c.Destination, c.ASN, c.RTTMs, c.RetransmissionRatio), Treatment: treatment.Treatment{ID: t.ID, Revision: 1, Destination: c.Destination, Settings: t.settings()}, Policy: t.policy(), Baseline: baseline, CohortOrder: c.Agents} if _, e = s.propose(ctx, &pb.ProposeRequest{Experiment: wire.Experiment(n), RequestId: id}, "candidate-discovery", true); e != nil { if !errors.Is(e, errExperimentBudget) && !errors.Is(e, errDiscoverySuppressed) { failures = append(failures, e) diff --git a/pkg/treatment/controller/discovery_contracts_test.go b/pkg/treatment/controller/discovery_contracts_test.go index b34ef27a1..1b911edc0 100644 --- a/pkg/treatment/controller/discovery_contracts_test.go +++ b/pkg/treatment/controller/discovery_contracts_test.go @@ -2,18 +2,16 @@ package controller import ( "context" - "encoding/json" "errors" "fmt" - "io" - "net/http" - "strings" + "net/netip" "sync" "testing" "time" pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/evidence" "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store" "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store/postgres" "google.golang.org/protobuf/proto" @@ -37,33 +35,12 @@ func TestDiscoveryScopeAndCooldown(t *testing.T) { now := time.Now().UTC().Truncate(time.Microsecond) s := testServer(st, &now) var agents []string - var evidence strings.Builder for i := range 8 { id := fmt.Sprintf("host-%d", i) enroll(t, s, id) agents = append(agents, id) - raw, err := json.Marshal(hostRow{AgentID: id, Sockets: 100, BytesAcked: 1 << 25, GoodputBps: 1e8, RttP95Ms: 5, CollectionHealthy: true}) - if err != nil { - t.Fatal(err) - } - evidence.Write(raw) - evidence.WriteByte('\n') - } - row, err := json.Marshal(map[string]any{"destination_hex": "C0000201000000000000000000000000", "family": 2, "asn": 64512, "agents": agents, "rtt_ms": 5, "retransmission_ratio": 0}) - if err != nil { - t.Fatal(err) } - c := &ClickHouse{URL: "http://fixture.invalid", Table: "db.rows", Now: s.Now, Client: &http.Client{Transport: discoveryTransport(func(r *http.Request) (*http.Response, error) { - q, err := io.ReadAll(r.Body) - if err != nil { - return nil, err - } - body := evidence.String() - if string(q) == strings.ReplaceAll(discoverySQL, "{{TABLE}}", "db.rows") { - body = string(row) - } - return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(body)), Header: make(http.Header)}, nil - })}} + c := &fakeWarehouse{now: s.Now, candidates: []evidence.Candidate{{Destination: netip.MustParsePrefix("192.0.2.1/32"), ASN: 64512, Agents: agents, RTTMs: 5}}} templates := []DiscoveryTemplate{{ID: "nearby", Location: "dc1", CongestionControl: "cubic", MaximumRTTMs: 15, ASNs: tc.asns, Prefixes: tc.prefixes}} for range 2 { if err := s.Discover(context.Background(), c, templates); err != nil { @@ -115,14 +92,10 @@ func TestDiscoverySkipsBusyLocation(t *testing.T) { now := time.Now().UTC() seedExperiment(t, st, "active", "dc1", "192.0.2.9/32", treatment.StatusRunning, now) s := testServer(st, &now) - called := false - ch := &ClickHouse{URL: "http://fixture.invalid", Table: "db.rows", Client: &http.Client{Transport: discoveryTransport(func(*http.Request) (*http.Response, error) { - called = true - return nil, errors.New("warehouse must not be queried") - })}} + ch := &fakeWarehouse{candidateErr: errors.New("warehouse must not be queried")} templates := []DiscoveryTemplate{{ID: "nearby", Location: "dc1", CongestionControl: "cubic", MaximumRTTMs: 15}} - if err := s.Discover(context.Background(), ch, templates); err != nil || called { - t.Fatalf("busy location queried warehouse: called=%t err=%v", called, err) + if err := s.Discover(context.Background(), ch, templates); err != nil || len(ch.queries) > 0 { + t.Fatalf("busy location queried warehouse: queries=%d err=%v", len(ch.queries), err) } pg, ok := st.(*postgres.Store) if !ok { @@ -131,8 +104,8 @@ func TestDiscoverySkipsBusyLocation(t *testing.T) { if err := pg.Close(); err != nil { t.Fatal(err) } - if err := s.Discover(context.Background(), ch, templates); err == nil || called { - t.Fatalf("database failure must stop discovery: called=%t err=%v", called, err) + if err := s.Discover(context.Background(), ch, templates); err == nil || len(ch.queries) > 0 { + t.Fatalf("database failure must stop discovery: queries=%d err=%v", len(ch.queries), err) } }) } diff --git a/pkg/treatment/controller/discovery_test.go b/pkg/treatment/controller/discovery_test.go index 902d982af..081372355 100644 --- a/pkg/treatment/controller/discovery_test.go +++ b/pkg/treatment/controller/discovery_test.go @@ -3,16 +3,15 @@ package controller import ( "context" "encoding/json" - "io" + "errors" "math" - "net/http" "net/netip" - "strconv" "strings" "testing" "time" "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/evidence" "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store/memory" "google.golang.org/protobuf/proto" ) @@ -20,32 +19,66 @@ import ( func TestDiscoveryWindow(t *testing.T) { now := time.Unix(1800000000, 0) for _, tc := range []struct { - name string - window, want time.Duration - invalid bool + name, description string + window, want time.Duration + invalid bool }{ - {"production-default", 0, time.Hour, false}, - {"short-window", time.Minute, time.Minute, false}, - {"maximum", 7 * 24 * time.Hour, 7 * 24 * time.Hour, false}, - {"negative", -time.Second, 0, true}, - {"too-short", 59 * time.Second, 0, true}, - {"too-long", 7*24*time.Hour + time.Second, 0, true}, + {"production-default", "positive: an unset window scans the last hour", 0, time.Hour, false}, + {"short-window", "boundary: one minute is the shortest scan", time.Minute, time.Minute, false}, + {"maximum", "boundary: seven days is the longest scan", 7 * 24 * time.Hour, 7 * 24 * time.Hour, false}, + {"negative", "negative: a negative window never reaches the warehouse", -time.Second, 0, true}, + {"too-short", "negative: under a minute is refused", 59 * time.Second, 0, true}, + {"too-long", "negative: over seven days is refused", 7*24*time.Hour + time.Second, 0, true}, } { t.Run(tc.name, func(t *testing.T) { - called := false - ch := &ClickHouse{URL: "http://fixture.invalid", Table: "xtcp.rows", Client: &http.Client{Transport: discoveryTransport(func(r *http.Request) (*http.Response, error) { - called = true - params := r.URL.Query() - wantEnd := now.Add(-20 * time.Second) - if params.Get("param_start") != strconv.FormatInt(wantEnd.Add(-tc.want).UnixNano(), 10) || params.Get("param_end") != strconv.FormatInt(wantEnd.UnixNano(), 10) { - t.Fatalf("wrong discovery bounds: %s", r.URL.RawQuery) - } - return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader("")), Header: make(http.Header)}, nil - })}} + w := &fakeWarehouse{now: func() time.Time { return now }} s := &Server{Store: memory.New(), Now: func() time.Time { return now }, DiscoveryWindow: tc.window} - err := s.Discover(context.Background(), ch, []DiscoveryTemplate{{ID: "near", Location: "test-dc", CongestionControl: "cubic", MaximumRTTMs: 15}}) - if (err != nil) != tc.invalid || called == tc.invalid { - t.Fatalf("err=%v query-called=%t invalid=%t", err, called, tc.invalid) + err := s.Discover(context.Background(), w, []DiscoveryTemplate{{ID: "near", Location: "test-dc", CongestionControl: "cubic", MaximumRTTMs: 15}}) + if (err != nil) != tc.invalid || (len(w.queries) > 0) == tc.invalid { + t.Fatalf("%s: err=%v queries=%d invalid=%t", tc.description, err, len(w.queries), tc.invalid) + } + if tc.invalid { + return + } + wantEnd := now.Add(-evidence.SettleDelay) + q := w.queries[0] + if !q.End.Equal(wantEnd) || !q.Start.Equal(wantEnd.Add(-tc.want)) || q.Location != "test-dc" || q.MaximumRTTMs != 15 { + t.Fatalf("%s: wrong discovery bounds: %+v", tc.description, q) + } + }) + } +} + +func TestDiscoveryWarehouseFailures(t *testing.T) { + now := time.Unix(1800000000, 0) + candidate := evidence.Candidate{Destination: netip.MustParsePrefix("192.0.2.1/32"), ASN: 64512, Agents: []string{"a", "b", "c", "d", "e", "f"}, RTTMs: 5} + for _, tc := range []struct { + name, description string + warehouse *fakeWarehouse + nilWarehouse bool + wantErr string + wantWindows int + }{ + {"candidates-fail", "negative: a failed scan is reported and takes no baseline", &fakeWarehouse{candidateErr: errors.New("warehouse offline")}, false, "warehouse offline", 0}, + {"baseline-fails", "negative: a failed baseline is reported and proposes nothing", &fakeWarehouse{candidates: []evidence.Candidate{candidate}, evidenceErr: errors.New("baseline timeout")}, false, "baseline timeout", 1}, + {"no-warehouse", "corner: discovery without a warehouse is a configuration error", nil, true, "requires a warehouse", 0}, + {"no-candidates", "boundary: an empty scan is not an error", &fakeWarehouse{}, false, "", 0}, + } { + t.Run(tc.name, func(t *testing.T) { + s := &Server{Store: memory.New(), Now: func() time.Time { return now }} + var w evidence.Warehouse + if !tc.nilWarehouse { + w = tc.warehouse + } + err := s.Discover(context.Background(), w, []DiscoveryTemplate{{ID: "near", Location: "test-dc", CongestionControl: "cubic", MaximumRTTMs: 15}}) + if tc.wantErr == "" && err != nil || tc.wantErr != "" && (err == nil || !strings.Contains(err.Error(), tc.wantErr)) { + t.Fatalf("%s: err=%v want %q", tc.description, err, tc.wantErr) + } + if tc.warehouse != nil && len(tc.warehouse.windows) != tc.wantWindows { + t.Fatalf("%s: baseline windows=%d want %d", tc.description, len(tc.warehouse.windows), tc.wantWindows) + } + if n := len(experimentsOf(t, s.Store)); n != 0 { + t.Fatalf("%s: proposals=%d", tc.description, n) } }) } @@ -67,14 +100,10 @@ func TestDiscoveryRTTPolicy(t *testing.T) { } { t.Run(tc.name, func(t *testing.T) { template := DiscoveryTemplate{ID: "near", Location: "test-dc", CongestionControl: "cubic", MaximumRTTMs: 15, MaximumRTTIncrease: tc.limit} - called := false - ch := &ClickHouse{URL: "http://fixture.invalid", Table: "xtcp.rows", Client: &http.Client{Transport: discoveryTransport(func(r *http.Request) (*http.Response, error) { - called = true - return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader("")), Header: make(http.Header)}, nil - })}} - err := (&Server{Store: memory.New()}).Discover(context.Background(), ch, []DiscoveryTemplate{template}) - if (err != nil) != tc.invalid || called == tc.invalid { - t.Fatalf("err=%v queried=%t", err, called) + w := &fakeWarehouse{} + err := (&Server{Store: memory.New()}).Discover(context.Background(), w, []DiscoveryTemplate{template}) + if (err != nil) != tc.invalid || (len(w.queries) > 0) == tc.invalid { + t.Fatalf("err=%v queried=%d", err, len(w.queries)) } if tc.invalid { return @@ -107,14 +136,10 @@ func TestDiscoveryRouteSettings(t *testing.T) { if err := json.Unmarshal([]byte(`{"id":"near","location":"test-dc","congestion_control":"cubic","maximum_rtt_ms":15`+tc.fields+`}`), &template); err != nil { t.Fatal(err) } - called := false - ch := &ClickHouse{URL: "http://fixture.invalid", Table: "xtcp.rows", Client: &http.Client{Transport: discoveryTransport(func(r *http.Request) (*http.Response, error) { - called = true - return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader("")), Header: make(http.Header)}, nil - })}} - err := (&Server{Store: memory.New()}).Discover(context.Background(), ch, []DiscoveryTemplate{template}) - if (err != nil) != tc.invalid || called == tc.invalid { - t.Fatalf("err=%v query-called=%t", err, called) + w := &fakeWarehouse{} + err := (&Server{Store: memory.New()}).Discover(context.Background(), w, []DiscoveryTemplate{template}) + if (err != nil) != tc.invalid || (len(w.queries) > 0) == tc.invalid { + t.Fatalf("err=%v queried=%d", err, len(w.queries)) } if tc.invalid { return diff --git a/pkg/treatment/controller/analysis.sql b/pkg/treatment/controller/evidence/clickhouse/analysis.sql similarity index 100% rename from pkg/treatment/controller/analysis.sql rename to pkg/treatment/controller/evidence/clickhouse/analysis.sql diff --git a/pkg/treatment/controller/evidence/clickhouse/clickhouse.go b/pkg/treatment/controller/evidence/clickhouse/clickhouse.go new file mode 100644 index 000000000..48fd2c490 --- /dev/null +++ b/pkg/treatment/controller/evidence/clickhouse/clickhouse.go @@ -0,0 +1,222 @@ +// Package clickhouse is the warehouse adapter: it implements evidence.Source +// and evidence.CandidateSource over the ClickHouse HTTP interface with two +// embedded, parameterized queries. It is the only package that knows the +// telemetry table's columns; the controller sees evidence.Window and +// evidence.Candidate. +package clickhouse + +import ( + "bytes" + "context" + "crypto/sha256" + _ "embed" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/netip" + "net/url" + "regexp" + "strconv" + "strings" + "time" + + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/evidence" +) + +//go:embed analysis.sql +var analysisSQL string + +//go:embed discovery.sql +var discoverySQL string + +var tableName = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*\.[a-zA-Z_][a-zA-Z0-9_]*$`) + +// QueryVersion names the analysis query so evidence records which SQL +// produced them; bump it when analysis.sql changes meaning. +const QueryVersion = "tcp-host-deltas-v1" + +// EnrichmentVersion names the telemetry schema the queries assume. +const EnrichmentVersion = "xtcp-schema-2" + +// Client talks to one ClickHouse HTTP(S) endpoint and one table. +type Client struct { + URL, Username, Password, Table string + HTTP *http.Client + Now func() time.Time +} + +var _ evidence.Warehouse = (*Client)(nil) + +func (c *Client) now() time.Time { + if c.Now != nil { + return c.Now() + } + return time.Now() +} + +// query POSTs one parameterized statement and returns the raw response. +// Parameters travel as ClickHouse `param_*` query parameters, never by +// string interpolation; the table name is validated before substitution. +func (c *Client) query(ctx context.Context, query string, params map[string]string) ([]byte, error) { + if !tableName.MatchString(c.Table) { + return nil, errors.New("ClickHouse table must be database.table") + } + u, e := url.Parse(c.URL) + if e != nil { + return nil, e + } + if u.Scheme != "http" && u.Scheme != "https" { + return nil, errors.New("ClickHouse URL must be HTTP(S)") + } + v := u.Query() + for k, val := range params { + v.Set("param_"+k, val) + } + v.Set("max_execution_time", "30") + v.Set("max_result_rows", "10000") + v.Set("result_overflow_mode", "throw") + v.Set("output_format_json_quote_64bit_integers", "0") + u.RawQuery = v.Encode() + body := strings.ReplaceAll(query, "{{TABLE}}", c.Table) + r, e := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), strings.NewReader(body)) + if e != nil { + return nil, e + } + r.SetBasicAuth(c.Username, c.Password) + client := c.HTTP + if client == nil { + client = &http.Client{Timeout: 35 * time.Second} + } + response, e := client.Do(r) + if e != nil { + return nil, e + } + defer response.Body.Close() + b, e := io.ReadAll(io.LimitReader(response.Body, 8<<20)) + if e != nil { + return nil, e + } + if len(b) >= 8<<20 { + return nil, errors.New("ClickHouse result exceeds 8 MiB") + } + if response.StatusCode != http.StatusOK { + return nil, fmt.Errorf("ClickHouse status %d: %.1024s", response.StatusCode, b) + } + return b, nil +} + +// hostRow is one JSONEachRow line of analysis.sql. +type hostRow struct { + AgentID string `json:"agent_id"` + Sockets uint64 `json:"sockets"` + BytesAcked uint64 `json:"bytes_acked"` + GoodputBps float64 `json:"goodput_bps"` + RetransmissionRatio float64 `json:"retransmission_ratio"` + RttP95Ms float64 `json:"rtt_p95_ms"` + ExposedSockets uint64 `json:"exposed_sockets"` + CollectionHealthy bool `json:"collection_healthy"` +} + +// destinationHex renders an address the way the telemetry stores it: a +// 16-byte FixedString, IPv4 left-aligned and zero padded. +func destinationHex(a netip.Addr) string { + raw := a.AsSlice() + if a.Is4() { + raw = append(raw, make([]byte, 12)...) + } + return strings.ToUpper(hex.EncodeToString(raw)) +} + +// Evidence runs analysis.sql for the window. ArtifactSHA256 is the digest of +// the raw response bytes as received; the response itself is not archived, +// so reproducing it means re-running QueryVersion over the same window. +func (c *Client) Evidence(ctx context.Context, w evidence.Window) (*treatment.Evidence, error) { + if e := w.Validate(); e != nil { + return nil, e + } + quoted := make([]string, len(w.Agents)) + for i, id := range w.Agents { + quoted[i] = "'" + id + "'" + } + params := map[string]string{"start": strconv.FormatInt(w.Start.UnixNano(), 10), "end": strconv.FormatInt(w.End.UnixNano(), 10), "location": w.Location, "assignment": w.ExperimentID, "agents": "[" + strings.Join(quoted, ",") + "]", "destination_hex": destinationHex(w.Destination.Addr())} + b, e := c.query(ctx, analysisSQL, params) + if e != nil { + return nil, e + } + digest := sha256.Sum256(b) + sha := hex.EncodeToString(digest[:]) + analyzed := c.now() + v := &treatment.Evidence{ID: "ev-" + sha[:24] + "-" + strconv.FormatInt(analyzed.UnixNano(), 10), AnalyzedAt: analyzed, WindowStart: w.Start, WindowEnd: w.End, QueryVersion: QueryVersion, EnrichmentVersion: EnrichmentVersion, ArtifactSHA256: sha} + dec := json.NewDecoder(bytes.NewReader(b)) + for { + var h hostRow + e := dec.Decode(&h) + if e == io.EOF { + break + } + if e != nil { + return nil, e + } + v.Hosts = append(v.Hosts, treatment.HostEvidence(h)) + } + if e = treatment.ValidateEvidence(v); e != nil { + return nil, e + } + return v, nil +} + +// candidateRow is one JSONEachRow line of discovery.sql. +type candidateRow struct { + DestinationHex string `json:"destination_hex"` + Family uint32 `json:"family"` + ASN uint64 `json:"asn"` + Agents []string `json:"agents"` + RTT float64 `json:"rtt_ms"` + Retransmission float64 `json:"retransmission_ratio"` +} + +// Candidates runs discovery.sql. A row whose destination does not decode is +// an error, not a skipped row: it means the table is not the schema this +// adapter was written for. +func (c *Client) Candidates(ctx context.Context, q evidence.CandidateQuery) ([]evidence.Candidate, error) { + if e := q.Validate(); e != nil { + return nil, e + } + params := map[string]string{"location": q.Location, "start": strconv.FormatInt(q.Start.UnixNano(), 10), "end": strconv.FormatInt(q.End.UnixNano(), 10), "minimum_rtt": fmt.Sprint(q.MinimumRTTMs), "maximum_rtt": fmt.Sprint(q.MaximumRTTMs), "minimum_retransmission": fmt.Sprint(q.MinimumRetransmissionRatio)} + data, e := c.query(ctx, discoverySQL, params) + if e != nil { + return nil, e + } + var out []evidence.Candidate + dec := json.NewDecoder(bytes.NewReader(data)) + for { + var row candidateRow + if e = dec.Decode(&row); e == io.EOF { + return out, nil + } + if e != nil { + return nil, e + } + raw, e := hex.DecodeString(row.DestinationHex) + if e != nil { + return nil, fmt.Errorf("candidate destination %q: %w", row.DestinationHex, e) + } + if row.Family == 2 && len(raw) >= 4 { + raw = raw[:4] + } + ip, ok := netip.AddrFromSlice(raw) + if !ok { + return nil, fmt.Errorf("candidate destination %q: not an address for family %d", row.DestinationHex, row.Family) + } + for _, id := range row.Agents { + if !treatment.ValidID(id) { + return nil, fmt.Errorf("candidate %s: invalid agent id %q", ip, id) + } + } + out = append(out, evidence.Candidate{Destination: netip.PrefixFrom(ip, ip.BitLen()), ASN: row.ASN, Agents: row.Agents, RTTMs: row.RTT, RetransmissionRatio: row.Retransmission}) + } +} diff --git a/pkg/treatment/controller/evidence/clickhouse/clickhouse_test.go b/pkg/treatment/controller/evidence/clickhouse/clickhouse_test.go new file mode 100644 index 000000000..382716bf1 --- /dev/null +++ b/pkg/treatment/controller/evidence/clickhouse/clickhouse_test.go @@ -0,0 +1,297 @@ +package clickhouse + +import ( + "context" + "encoding/json" + "errors" + "io" + "net/http" + "net/netip" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/evidence" +) + +type roundTripper func(*http.Request) (*http.Response, error) + +func (f roundTripper) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) } + +type trackedBody struct { + io.Reader + closed bool +} + +func (b *trackedBody) Close() error { b.closed = true; return nil } + +type brokenReader struct{} + +func (brokenReader) Read([]byte) (int, error) { return 0, errors.New("broken response") } + +// fixture answers every query with body and records the request it saw. +func fixture(now time.Time, body string, saw func(*http.Request)) *Client { + return &Client{URL: "http://fixture.invalid", Table: "db.rows", Now: func() time.Time { return now }, HTTP: &http.Client{Transport: roundTripper(func(r *http.Request) (*http.Response, error) { + if saw != nil { + saw(r) + } + return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(body)), Header: make(http.Header)}, nil + })}} +} + +func TestClickHouseTransportContracts(t *testing.T) { + for _, tc := range []struct { + name, description, table, url, body, wantError string + status int + transportError, readError bool + }{ + {name: "valid", description: "positive: query is parameterized and authenticated", status: 200, body: "ok"}, + {name: "table", description: "negative: table names cannot inject SQL", table: "db.rows;DROP TABLE rows", wantError: "database.table"}, + {name: "scheme", description: "negative: only HTTP(S) transports are accepted", url: "file:///tmp/rows", wantError: "HTTP(S)"}, + {name: "http-error", description: "negative: server errors are surfaced", status: 500, body: "query failed", wantError: "status 500"}, + {name: "network", description: "negative: transport errors are surfaced", transportError: true, wantError: "offline"}, + {name: "read", description: "corner: partial response errors fail analysis", status: 200, readError: true, wantError: "broken response"}, + {name: "size-limit", description: "boundary: oversized responses fail closed", status: 200, body: strings.Repeat("x", 8<<20), wantError: "exceeds"}, + } { + t.Run(tc.name, func(t *testing.T) { + table := tc.table + if table == "" { + table = "db.rows" + } + endpoint := tc.url + if endpoint == "" { + endpoint = "https://fixture.invalid/" + } + body := &trackedBody{Reader: strings.NewReader(tc.body)} + if tc.readError { + body.Reader = brokenReader{} + } + called := false + c := &Client{URL: endpoint, Table: table, Username: "user", Password: "test-secret", HTTP: &http.Client{Transport: roundTripper(func(r *http.Request) (*http.Response, error) { + called = true + if tc.transportError { + return nil, errors.New("offline") + } + u, p, ok := r.BasicAuth() + if !ok || u != "user" || p != "test-secret" { + t.Fatal("missing authentication") + } + query, err := io.ReadAll(r.Body) + if err != nil { + t.Fatal(err) + } + if string(query) != "SELECT * FROM db.rows" || r.Method != "POST" || r.URL.Query().Get("param_location") != "dc'quote" || r.URL.Query().Get("result_overflow_mode") != "throw" { + t.Fatal("query contract changed") + } + return &http.Response{StatusCode: tc.status, Body: body, Header: make(http.Header)}, nil + })}} + got, err := c.query(context.Background(), "SELECT * FROM {{TABLE}}", map[string]string{"location": "dc'quote"}) + if tc.wantError == "" { + if err != nil || string(got) != tc.body { + t.Fatalf("%s: %q %v", tc.description, got, err) + } + } else if err == nil || !strings.Contains(err.Error(), tc.wantError) { + t.Fatalf("%s: want %q got %v", tc.description, tc.wantError, err) + } + if called && !tc.transportError && !body.closed { + t.Fatal("response body leaked") + } + if (tc.table != "" || tc.url != "") && called { + t.Fatal("invalid configuration reached transport") + } + }) + } +} + +func TestClickHouseEvidenceContracts(t *testing.T) { + const host = `{"agent_id":"host","sockets":30,"bytes_acked":1048576,"goodput_bps":1000,"rtt_p95_ms":5,"collection_healthy":true}` + now := time.Unix(1700000000, 0) + for _, tc := range []struct { + name, description, body string + destination netip.Prefix + agents []string + window time.Duration + invalidDestination bool + // beforeQuery says the error must come from Window.Validate, so the + // warehouse is never contacted. + beforeQuery bool + wantError string + }{ + {name: "valid", description: "positive: complete response records provenance", body: host}, + {name: "empty", description: "negative: empty response cannot renew a lease", wantError: "population"}, + {name: "malformed", description: "negative: malformed JSON is rejected", body: "{", wantError: "EOF"}, + {name: "duplicate", description: "negative: duplicate hosts cannot inflate sample size", body: host + "\n" + host, wantError: "duplicate"}, + {name: "bad-window", description: "boundary: empty analysis interval is rejected", window: -time.Second, beforeQuery: true, wantError: "window"}, + {name: "long-window", description: "boundary: analysis is limited to seven days", window: evidence.MaxWindow + time.Second, beforeQuery: true, wantError: "window"}, + {name: "bad-agent", description: "negative: agent IDs cannot inject query parameters", agents: []string{"host'"}, beforeQuery: true, wantError: "invalid agent"}, + {name: "bad-destination", description: "corner: an invalid destination fails before the query", destination: netip.Prefix{}, invalidDestination: true, beforeQuery: true, wantError: "invalid destination"}, + } { + t.Run(tc.name, func(t *testing.T) { + window := tc.window + if window == 0 { + window = time.Minute + } + agents := tc.agents + if agents == nil { + agents = []string{"host"} + } + dest := tc.destination + if !tc.invalidDestination { + dest = netip.MustParsePrefix("192.0.2.1/32") + } + called := false + c := fixture(now, tc.body, func(r *http.Request) { + called = true + q := r.URL.Query() + if q.Get("param_destination_hex") != "C0000201000000000000000000000000" || q.Get("param_assignment") != "assignment" || q.Get("param_agents") != "['host']" { + t.Fatalf("wrong query parameters: %s", r.URL.RawQuery) + } + }) + ev, err := c.Evidence(context.Background(), evidence.Window{Location: "dc", Destination: dest, ExperimentID: "assignment", Agents: agents, Start: now.Add(-window), End: now}) + if tc.wantError != "" { + if err == nil || !strings.Contains(err.Error(), tc.wantError) { + t.Fatalf("%s: want %q got %v", tc.description, tc.wantError, err) + } + if tc.beforeQuery && called { + t.Fatalf("%s: invalid window reached the warehouse", tc.description) + } + return + } + if err != nil { + t.Fatal(err) + } + if len(ev.Hosts) != 1 || len(ev.ArtifactSHA256) != 64 || ev.QueryVersion != QueryVersion || ev.EnrichmentVersion != EnrichmentVersion || !ev.AnalyzedAt.Equal(now) || !ev.WindowEnd.Equal(now) { + t.Fatalf("%s: bad evidence %+v", tc.description, ev) + } + }) + } +} + +func TestClickHouseCandidateContracts(t *testing.T) { + now := time.Unix(1700000000, 0) + agents := `["h1","h2","h3","h4","h5","h6"]` + v4 := `{"destination_hex":"C0000201000000000000000000000000","family":2,"asn":64512,"agents":` + agents + `,"rtt_ms":5.5,"retransmission_ratio":0.01}` + v6 := `{"destination_hex":"20010DB8000000000000000000000001","family":10,"asn":64513,"agents":` + agents + `,"rtt_ms":80,"retransmission_ratio":0}` + for _, tc := range []struct { + name, description, body string + query evidence.CandidateQuery + want []string + wantASN uint64 + wantError string + }{ + {name: "v4", description: "positive: an IPv4 row decodes to a /32 with its population", body: v4, want: []string{"192.0.2.1/32"}, wantASN: 64512}, + {name: "v6", description: "positive: an IPv6 row decodes to a /128", body: v6, want: []string{"2001:db8::1/128"}, wantASN: 64513}, + {name: "ordered", description: "positive: rows keep the warehouse's best-first order", body: v6 + "\n" + v4, want: []string{"2001:db8::1/128", "192.0.2.1/32"}, wantASN: 64513}, + {name: "empty", description: "boundary: no rows is no candidates, not an error", body: ""}, + {name: "malformed", description: "negative: malformed JSON is an error", body: "{", wantError: "EOF"}, + {name: "bad-hex", description: "negative: an undecodable destination is a schema error, not a skipped row", body: `{"destination_hex":"ZZ","family":2,"agents":` + agents + `}`, wantError: "candidate destination"}, + {name: "short", description: "corner: too few bytes for the family is a schema error", body: `{"destination_hex":"C000","family":10,"agents":` + agents + `}`, wantError: "not an address"}, + {name: "bad-agent", description: "negative: an agent id that cannot be a parameter poisons the row", body: `{"destination_hex":"C0000201000000000000000000000000","family":2,"agents":["ok","bad'"]}`, wantError: "invalid agent id"}, + {name: "bad-bounds", description: "negative: inverted RTT bounds never reach the warehouse", body: v4, query: evidence.CandidateQuery{Location: "dc", Start: now.Add(-time.Hour), End: now, MinimumRTTMs: 10, MaximumRTTMs: 5}, wantError: "discovery bounds"}, + {name: "bad-window", description: "boundary: a window over seven days never reaches the warehouse", body: v4, query: evidence.CandidateQuery{Location: "dc", Start: now.Add(-evidence.MaxWindow - time.Second), End: now, MaximumRTTMs: 15}, wantError: "discovery window"}, + } { + t.Run(tc.name, func(t *testing.T) { + q := tc.query + if q.Location == "" { + q = evidence.CandidateQuery{Location: "dc", Start: now.Add(-time.Hour), End: now, MaximumRTTMs: 15} + } + called := false + c := fixture(now, tc.body, func(r *http.Request) { + called = true + p := r.URL.Query() + if p.Get("param_location") != "dc" || p.Get("param_maximum_rtt") != "15" || p.Get("param_minimum_rtt") != "0" { + t.Fatalf("wrong query parameters: %s", r.URL.RawQuery) + } + }) + got, err := c.Candidates(context.Background(), q) + if tc.wantError != "" { + if err == nil || !strings.Contains(err.Error(), tc.wantError) { + t.Fatalf("%s: want %q got %v", tc.description, tc.wantError, err) + } + if tc.query.Location != "" && called { + t.Fatalf("%s: invalid query reached the warehouse", tc.description) + } + return + } + if err != nil { + t.Fatalf("%s: %v", tc.description, err) + } + if len(got) != len(tc.want) { + t.Fatalf("%s: got %d candidates, want %d", tc.description, len(got), len(tc.want)) + } + for i, want := range tc.want { + if got[i].Destination.String() != want || len(got[i].Agents) != 6 { + t.Fatalf("%s: candidate %d = %+v, want %s", tc.description, i, got[i], want) + } + } + if len(got) > 0 && (got[0].ASN != tc.wantASN || got[0].RTTMs == 0) { + t.Fatalf("%s: traffic shape lost: %+v", tc.description, got[0]) + } + }) + } +} + +// Execute the production SQL against an isolated ClickHouse local database. +// No live service or production telemetry is accessed. +func TestClickHouseAnalysisCounterDeltas(t *testing.T) { + binary := os.Getenv("XTCP_CLICKHOUSE_TEST_BINARY") + if binary == "" { + t.Skip("set XTCP_CLICKHOUSE_TEST_BINARY to run real SQL") + } + setup := `CREATE TABLE default.sockets ( + agent_id String,host_boot_id String,netns_inode UInt64,inet_diag_msg_socket_cookie UInt64, + timestamp_ns DateTime64(9),tcp_info_bytes_acked UInt64,tcp_info_bytes_sent UInt64, + tcp_info_bytes_retrans UInt64,tcp_info_rtt UInt32,tcp_info_min_rtt UInt32, + treatment_algorithm_exposed Bool,treatment_assignment_id String,location String, + inet_diag_msg_state UInt8,inet_diag_msg_socket_destination FixedString(16), + inet_diag_msg_family UInt8,enrich_socket_dest_asn UInt64) ENGINE=Memory; + INSERT INTO default.sockets SELECT 'host-1','boot-1',1,1, + fromUnixTimestamp64Nano(1700000000000000000+toInt64(number)*10000000000), + 1000000+number*1000,1000000+number*1000,number*10,1000,500,true,'trial','dc1',1, + unhex('C0000201000000000000000000000000'),2,64512 FROM numbers(6); + INSERT INTO default.sockets SELECT * FROM default.sockets; + ` + query := strings.ReplaceAll(analysisSQL, "{{TABLE}}", "default.sockets") + run := func(sql string) []byte { + t.Helper() + dir := t.TempDir() + file := filepath.Join(dir, "query.sql") + if e := os.WriteFile(file, []byte(sql), 0600); e != nil { + t.Fatal(e) + } + cmd := exec.Command(binary, "local", "--path", filepath.Join(dir, "data"), "--queries-file", file, "--multiquery", + "--param_start=1700000000000000000", "--param_end=1700000060000000000", "--param_location=dc1", + "--param_agents=['host-1']", "--param_destination_hex=C0000201000000000000000000000000", "--param_assignment=trial", + "--param_minimum_rtt=0", "--param_maximum_rtt=10", "--param_minimum_retransmission=0", + "--output_format_json_quote_64bit_integers=0") + out, e := cmd.CombinedOutput() + if e != nil { + t.Fatalf("ClickHouse SQL: %v\n%s", e, out) + } + return out + } + var h hostRow + if e := json.Unmarshal(run(setup+query), &h); e != nil { + t.Fatal(e) + } + if h.Sockets != 1 || h.BytesAcked != 5000 || h.ExposedSockets != 1 || !h.CollectionHealthy || h.RttP95Ms != 1 || h.RetransmissionRatio != .01 { + t.Fatalf("bad window deltas/deduplication: %+v", h) + } + reset := `INSERT INTO default.sockets SELECT 'host-1','boot-1',1,1, + fromUnixTimestamp64Nano(1700000055000000000),1,1,0,1000,500,true,'trial','dc1',1, + unhex('C0000201000000000000000000000000'),2,64512;` + if e := json.Unmarshal(run(setup+reset+query), &h); e != nil { + t.Fatal(e) + } + if h.CollectionHealthy { + t.Fatal("counter reset accepted as healthy") + } + // Syntax/type-check candidate discovery too; a one-host fixture cannot meet + // its six-host eligibility threshold. + if out := run(setup + strings.ReplaceAll(discoverySQL, "{{TABLE}}", "default.sockets")); len(out) != 0 { + t.Fatalf("unexpected discovery: %s", out) + } +} diff --git a/pkg/treatment/controller/evidence/clickhouse/discovery.sql b/pkg/treatment/controller/evidence/clickhouse/discovery.sql new file mode 100644 index 000000000..9436f0025 --- /dev/null +++ b/pkg/treatment/controller/evidence/clickhouse/discovery.sql @@ -0,0 +1,25 @@ +-- Candidate discovery: destinations in one location that at least six hosts +-- talked to during the window, with the median of per-socket minimum RTT and +-- the pooled retransmission ratio, best traffic first. A row is a hypothesis +-- input; the controller still filters by template scope and the operator +-- still reviews every proposal. +WITH sockets AS ( + SELECT agent_id,host_boot_id,netns_inode,inet_diag_msg_socket_cookie, + any(hex(inet_diag_msg_socket_destination)) AS destination_hex, + any(inet_diag_msg_family) AS family, + argMax(enrich_socket_dest_asn,timestamp_ns) AS asn, + min(tcp_info_min_rtt)/1000 AS min_rtt_ms, + max(tcp_info_bytes_sent)-min(tcp_info_bytes_sent) AS sent, + max(tcp_info_bytes_retrans)-min(tcp_info_bytes_retrans) AS retrans + FROM {{TABLE}} + WHERE timestamp_ns>=fromUnixTimestamp64Nano({start:Int64}) AND timestamp_ns=3 +) +SELECT destination_hex,family,any(asn) AS asn,groupUniqArray(10000)(agent_id) AS agents, + quantileExact(0.5)(min_rtt_ms) AS rtt_ms,sum(retrans)/greatest(1,sum(sent)) AS retransmission_ratio +FROM sockets GROUP BY destination_hex,family +HAVING length(agents)>=6 AND rtt_ms>={minimum_rtt:Float64} AND rtt_ms<={maximum_rtt:Float64} + AND retransmission_ratio>={minimum_retransmission:Float64} +ORDER BY sum(sent) DESC LIMIT 10 FORMAT JSONEachRow diff --git a/pkg/treatment/controller/evidence/evidence.go b/pkg/treatment/controller/evidence/evidence.go new file mode 100644 index 000000000..0367136d5 --- /dev/null +++ b/pkg/treatment/controller/evidence/evidence.go @@ -0,0 +1,120 @@ +// Package evidence defines the controller's ports to the telemetry warehouse: +// a Source that measures a frozen population over a window and a +// CandidateSource that proposes destinations worth a hypothesis. The +// controller depends only on these interfaces; package clickhouse is the one +// adapter today, and handler tests use in-memory fakes. +package evidence + +import ( + "context" + "errors" + "net/netip" + "time" + + "github.com/randomizedcoder/xtcp2/pkg/treatment" +) + +// Window is what a Source measures: one destination, one frozen population, +// one half-open interval [Start, End). ExperimentID is the assignment the +// exposure columns are matched against. +type Window struct { + Location string + Destination netip.Prefix + ExperimentID string + Agents []string + Start, End time.Time +} + +// Validate applies the bounds every adapter must enforce before querying. +func (w Window) Validate() error { + if !w.End.After(w.Start) || w.End.Sub(w.Start) > MaxWindow || len(w.Agents) == 0 || len(w.Agents) > treatment.MaxAgents { + return errors.New("invalid analysis window/population") + } + for _, id := range w.Agents { + if !treatment.ValidID(id) { + return errors.New("invalid agent") + } + } + if !w.Destination.IsValid() { + return errors.New("invalid destination") + } + if !treatment.ValidID(w.Location) || !treatment.ValidID(w.ExperimentID) { + return errors.New("invalid location or experiment") + } + return nil +} + +// MaxWindow bounds one analysis or discovery query. +const MaxWindow = 7 * 24 * time.Hour + +// SettleDelay is how far behind the clock a window ends, so the newest +// telemetry has landed in the warehouse before it is measured. +const SettleDelay = 20 * time.Second + +// Source produces immutable per-host evidence for a window. Failure never +// authorizes lease renewal or promotion; the controller treats an error as +// no evidence. +type Source interface { + Evidence(context.Context, Window) (*treatment.Evidence, error) +} + +// WindowFor is the controller's policy for which interval an experiment is +// measured over: before any phase, the hour before now; during a phase, from +// the observation start (or issue, if observation has not begun) until now. +// Both end SettleDelay behind now. +func WindowFor(e *treatment.Experiment, now time.Time) Window { + end := now.Add(-SettleDelay) + w := Window{Location: e.Location, Destination: e.Treatment.Destination, ExperimentID: e.ID, Agents: e.CohortOrder, Start: end.Add(-time.Hour), End: end} + if p := e.CurrentPhase(); p != nil { + w.Start = p.IssuedAt + if !p.ObservationStartedAt.IsZero() { + w.Start = p.ObservationStartedAt + } + } + return w +} + +// CandidateQuery bounds one discovery scan of a location. +type CandidateQuery struct { + Location string + Start, End time.Time + // Median minimum RTT band and retransmission floor a destination must + // fall in to be a candidate; the template that authorized the scan sets + // them. + MinimumRTTMs, MaximumRTTMs float64 + MinimumRetransmissionRatio float64 +} + +// Validate applies the bounds every adapter must enforce. +func (q CandidateQuery) Validate() error { + if !treatment.ValidID(q.Location) || !q.End.After(q.Start) || q.End.Sub(q.Start) > MaxWindow { + return errors.New("invalid discovery window") + } + if q.MinimumRTTMs < 0 || q.MaximumRTTMs <= q.MinimumRTTMs || q.MinimumRetransmissionRatio < 0 || q.MinimumRetransmissionRatio > 1 { + return errors.New("invalid discovery bounds") + } + return nil +} + +// Candidate is one destination a scan surfaced: the hosts that talked to it +// and the traffic shape that made it interesting. It is a hypothesis input, +// not a measurement of benefit. +type Candidate struct { + Destination netip.Prefix + ASN uint64 + Agents []string + RTTMs float64 + RetransmissionRatio float64 +} + +// CandidateSource lists destinations worth proposing, best first. +type CandidateSource interface { + Candidates(context.Context, CandidateQuery) ([]Candidate, error) +} + +// Warehouse is what candidate discovery needs: candidates and the baseline +// evidence for each. +type Warehouse interface { + Source + CandidateSource +} diff --git a/pkg/treatment/controller/evidence/evidence_test.go b/pkg/treatment/controller/evidence/evidence_test.go new file mode 100644 index 000000000..9ba1d7d48 --- /dev/null +++ b/pkg/treatment/controller/evidence/evidence_test.go @@ -0,0 +1,119 @@ +package evidence + +import ( + "net/netip" + "strings" + "testing" + "time" + + "github.com/randomizedcoder/xtcp2/pkg/treatment" +) + +func TestWindowFor(t *testing.T) { + now := time.Unix(1700000000, 0).UTC() + base := func() *treatment.Experiment { + return &treatment.Experiment{ID: "exp", Location: "dc1", Treatment: treatment.Treatment{Destination: netip.MustParsePrefix("192.0.2.1/32")}, CohortOrder: []string{"a", "b"}} + } + for _, tc := range []struct { + name, description string + experiment func() *treatment.Experiment + wantStart time.Time + }{ + {"no-phase", "positive: before review the baseline window is the hour before the settled end", base, now.Add(-SettleDelay - time.Hour)}, + {"phase-issued", "positive: a phase without observation measures from issue", func() *treatment.Experiment { + e := base() + e.Phases = []treatment.Phase{{IssuedAt: now.Add(-40 * time.Minute)}} + return e + }, now.Add(-40 * time.Minute)}, + {"phase-observing", "positive: once observation started the window begins there", func() *treatment.Experiment { + e := base() + e.Phases = []treatment.Phase{{IssuedAt: now.Add(-40 * time.Minute), ObservationStartedAt: now.Add(-35 * time.Minute)}} + return e + }, now.Add(-35 * time.Minute)}, + {"latest-phase", "boundary: only the current phase counts", func() *treatment.Experiment { + e := base() + e.Phases = []treatment.Phase{{IssuedAt: now.Add(-3 * time.Hour), ObservationStartedAt: now.Add(-3 * time.Hour)}, {IssuedAt: now.Add(-10 * time.Minute)}} + return e + }, now.Add(-10 * time.Minute)}, + } { + t.Run(tc.name, func(t *testing.T) { + e := tc.experiment() + w := WindowFor(e, now) + if !w.Start.Equal(tc.wantStart) || !w.End.Equal(now.Add(-SettleDelay)) { + t.Fatalf("%s: window %v..%v, want start %v", tc.description, w.Start, w.End, tc.wantStart) + } + if w.Location != "dc1" || w.ExperimentID != "exp" || w.Destination != e.Treatment.Destination || len(w.Agents) != 2 { + t.Fatalf("%s: identity not carried: %+v", tc.description, w) + } + if err := w.Validate(); err != nil { + t.Fatalf("%s: policy window must validate: %v", tc.description, err) + } + }) + } +} + +func TestWindowValidate(t *testing.T) { + now := time.Unix(1700000000, 0) + valid := func() Window { + return Window{Location: "dc1", Destination: netip.MustParsePrefix("192.0.2.1/32"), ExperimentID: "exp", Agents: []string{"a"}, Start: now.Add(-time.Minute), End: now} + } + many := make([]string, treatment.MaxAgents) + for i := range many { + many[i] = "h" + strings.Repeat("x", i%3) + string(rune('a'+i%26)) + string(rune('a'+(i/26)%26)) + string(rune('a'+(i/676)%26)) + } + for _, tc := range []struct { + name, description string + change func(*Window) + expected string + }{ + {"valid", "positive: a bounded window with one agent passes", func(*Window) {}, ""}, + {"max-agents", "boundary: MaxAgents hosts are accepted", func(w *Window) { w.Agents = many }, ""}, + {"too-many-agents", "boundary: one host over MaxAgents is refused", func(w *Window) { w.Agents = append(append([]string(nil), many...), "extra") }, "population"}, + {"max-window", "boundary: exactly seven days is accepted", func(w *Window) { w.Start = w.End.Add(-MaxWindow) }, ""}, + {"long-window", "boundary: a second over seven days is refused", func(w *Window) { w.Start = w.End.Add(-MaxWindow - time.Second) }, "window"}, + {"empty-window", "negative: end must follow start", func(w *Window) { w.Start = w.End }, "window"}, + {"no-agents", "negative: an empty population cannot be measured", func(w *Window) { w.Agents = nil }, "population"}, + {"bad-agent", "negative: an agent ID that could inject a parameter is refused", func(w *Window) { w.Agents = []string{"host'"} }, "invalid agent"}, + {"bad-destination", "negative: the zero prefix is refused", func(w *Window) { w.Destination = netip.Prefix{} }, "invalid destination"}, + {"bad-location", "corner: the location is an identifier too", func(w *Window) { w.Location = "dc 1" }, "location"}, + } { + t.Run(tc.name, func(t *testing.T) { + w := valid() + tc.change(&w) + err := w.Validate() + if tc.expected == "" && err != nil || tc.expected != "" && (err == nil || !strings.Contains(err.Error(), tc.expected)) { + t.Fatalf("%s: got %v, want %q", tc.description, err, tc.expected) + } + }) + } +} + +func TestCandidateQueryValidate(t *testing.T) { + now := time.Unix(1700000000, 0) + valid := func() CandidateQuery { + return CandidateQuery{Location: "dc1", Start: now.Add(-time.Hour), End: now, MinimumRTTMs: 0, MaximumRTTMs: 15, MinimumRetransmissionRatio: 0} + } + for _, tc := range []struct { + name, description string + change func(*CandidateQuery) + expected string + }{ + {"valid", "positive: a template's bounds pass", func(*CandidateQuery) {}, ""}, + {"max-window", "boundary: seven days is the longest scan", func(q *CandidateQuery) { q.Start = q.End.Add(-MaxWindow) }, ""}, + {"long-window", "boundary: over seven days is refused", func(q *CandidateQuery) { q.Start = q.End.Add(-MaxWindow - time.Nanosecond) }, "window"}, + {"rtt-order", "negative: the RTT band must be non-empty", func(q *CandidateQuery) { q.MaximumRTTMs = q.MinimumRTTMs }, "bounds"}, + {"negative-rtt", "negative: a negative RTT floor is refused", func(q *CandidateQuery) { q.MinimumRTTMs = -1 }, "bounds"}, + {"retrans-over-one", "negative: a retransmission ratio above one is meaningless", func(q *CandidateQuery) { q.MinimumRetransmissionRatio = 1.01 }, "bounds"}, + {"retrans-one", "boundary: a ratio of exactly one is allowed", func(q *CandidateQuery) { q.MinimumRetransmissionRatio = 1 }, ""}, + {"bad-location", "corner: the location is an identifier", func(q *CandidateQuery) { q.Location = "" }, "window"}, + } { + t.Run(tc.name, func(t *testing.T) { + q := valid() + tc.change(&q) + err := q.Validate() + if tc.expected == "" && err != nil || tc.expected != "" && (err == nil || !strings.Contains(err.Error(), tc.expected)) { + t.Fatalf("%s: got %v, want %q", tc.description, err, tc.expected) + } + }) + } +} diff --git a/pkg/treatment/controller/helpers_test.go b/pkg/treatment/controller/helpers_test.go index 0de77d37e..a014bc4e7 100644 --- a/pkg/treatment/controller/helpers_test.go +++ b/pkg/treatment/controller/helpers_test.go @@ -9,12 +9,14 @@ import ( "net/http/httptest" "net/netip" "net/url" + "strconv" "strings" "testing" "time" pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/evidence" "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store" "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store/memory" "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store/pgtest" @@ -118,12 +120,56 @@ func seedExperiment(t *testing.T, s store.Store, id, location, destination strin } } +// fixedEvidence is an evidence.Source that answers every window with one +// prepared measurement. type fixedEvidence struct{ value *treatment.Evidence } -func (f fixedEvidence) Analyze(context.Context, *treatment.Experiment) (*treatment.Evidence, error) { +func (f fixedEvidence) Evidence(context.Context, evidence.Window) (*treatment.Evidence, error) { return f.value, nil } +// fakeWarehouse is an in-memory evidence.Warehouse for discovery tests. It +// records the queries it saw so tests can assert on bounds, and it builds +// baseline evidence for whatever population a window names. +type fakeWarehouse struct { + candidates []evidence.Candidate + candidateErr error + evidenceErr error + queries []evidence.CandidateQuery + windows []evidence.Window + now func() time.Time +} + +func (f *fakeWarehouse) Candidates(_ context.Context, q evidence.CandidateQuery) ([]evidence.Candidate, error) { + f.queries = append(f.queries, q) + if f.candidateErr != nil { + return nil, f.candidateErr + } + if err := q.Validate(); err != nil { + return nil, err + } + return f.candidates, nil +} + +func (f *fakeWarehouse) Evidence(_ context.Context, w evidence.Window) (*treatment.Evidence, error) { + f.windows = append(f.windows, w) + if f.evidenceErr != nil { + return nil, f.evidenceErr + } + if err := w.Validate(); err != nil { + return nil, err + } + now := time.Now() + if f.now != nil { + now = f.now() + } + v := &treatment.Evidence{ID: "ev-" + strconv.FormatInt(now.UnixNano(), 10), AnalyzedAt: now, WindowStart: w.Start, WindowEnd: w.End, QueryVersion: "fake-v1", ArtifactSHA256: strings.Repeat("ab", 32)} + for _, id := range w.Agents { + v.Hosts = append(v.Hosts, treatment.HostEvidence{AgentID: id, Sockets: 100, BytesAcked: 1 << 25, GoodputBps: 1e8, RttP95Ms: 5, CollectionHealthy: true}) + } + return v, nil +} + func parseEvidence(t *testing.T, p *pb.Evidence) *treatment.Evidence { t.Helper() v, e := wire.ParseEvidence(p) @@ -149,10 +195,6 @@ func acknowledgeFleet(t *testing.T, s *Server, n *pb.Experiment) { } } -type discoveryTransport func(*http.Request) (*http.Response, error) - -func (f discoveryTransport) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) } - func operatorHTTP(method, path, identity string) *http.Request { r := httptest.NewRequest(method, path, nil) if identity != "" { diff --git a/pkg/treatment/controller/maintenance.go b/pkg/treatment/controller/maintenance.go index 95f921bc8..c9220117d 100644 --- a/pkg/treatment/controller/maintenance.go +++ b/pkg/treatment/controller/maintenance.go @@ -7,17 +7,12 @@ import ( "time" "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/evidence" "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store" "github.com/randomizedcoder/xtcp2/pkg/treatment/experiment" "github.com/randomizedcoder/xtcp2/pkg/treatment/wire" ) -// EvidenceSource must produce immutable per-host evidence for a frozen -// population and treatment. Failure never authorizes lease renewal/promotion. -type EvidenceSource interface { - Analyze(context.Context, *treatment.Experiment) (*treatment.Evidence, error) -} - // driftInput rewinds the current phase an hour so a completed treatment is // re-analyzed over a rolling window rather than since its review. func (s *Server) driftInput(n *treatment.Experiment) *treatment.Experiment { @@ -30,8 +25,9 @@ func (s *Server) driftInput(n *treatment.Experiment) *treatment.Experiment { // Maintain refreshes exposure, stores evidence, renews healthy leases, and // withdraws harmful/expired trials. It NEVER promotes a phase. A fresh human -// review remains necessary for each increase in exposure. -func (s *Server) Maintain(ctx context.Context, source EvidenceSource) error { +// review remains necessary for each increase in exposure. A nil source (no +// warehouse configured) still expires and withdraws; it never renews. +func (s *Server) Maintain(ctx context.Context, source evidence.Source) error { ids, e := s.Store.Repos().Experiments.IDs(ctx, treatment.StatusRunning, treatment.StatusPaused, treatment.StatusComplete) if e != nil { return e @@ -70,7 +66,7 @@ func (s *Server) Maintain(ctx context.Context, source EvidenceSource) error { if snapshot.Status == treatment.StatusComplete { input = s.driftInput(snapshot) } - ev, e = source.Analyze(ctx, input) + ev, e = source.Evidence(ctx, evidence.WindowFor(input, s.now())) if e != nil { failures = append(failures, fmt.Errorf("%s analysis: %w", id, e)) } diff --git a/pkg/treatment/controller/server.go b/pkg/treatment/controller/server.go index 21e48ee04..dbbd151a8 100644 --- a/pkg/treatment/controller/server.go +++ b/pkg/treatment/controller/server.go @@ -16,6 +16,7 @@ import ( pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/evidence" "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store" "github.com/randomizedcoder/xtcp2/pkg/treatment/experiment" "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" @@ -34,9 +35,11 @@ const agentWindow = 2 * time.Minute type Server struct { pb.UnimplementedAgentControlServiceServer pb.UnimplementedOperatorServiceServer - Store store.Store - Hub *NotificationHub - EvidenceSource EvidenceSource + Store store.Store + Hub *NotificationHub + // EvidenceSource measures experiments; nil disables Analyze and lease + // renewal. Candidate discovery takes its warehouse per call. + EvidenceSource evidence.Source Now func() time.Time // Zero preserves the production one-hour candidate discovery window. DiscoveryWindow time.Duration diff --git a/proto/xtcp_treatment/v1/treatment.proto b/proto/xtcp_treatment/v1/treatment.proto index a41d71cbe..6bba8e2a2 100644 --- a/proto/xtcp_treatment/v1/treatment.proto +++ b/proto/xtcp_treatment/v1/treatment.proto @@ -256,7 +256,12 @@ message Evidence { string query_version = 5 [(buf.validate.field).string = {min_len: 1, max_len: 256}]; string enrichment_version = 6 [(buf.validate.field).string.max_len = 256]; repeated HostEvidence hosts = 7 [(buf.validate.field).repeated = {min_items: 1, max_items: 10000}]; - // SHA-256 of the complete analysis response this summary was derived from. + // SHA-256 of the raw warehouse response bytes, as received, that this + // summary was decoded from. The response itself is not archived: the + // digest lets two controllers (or a later audit) confirm they decoded the + // same bytes, and reproducing it means re-running query_version over + // [window_start, window_end) against the same table. Archiving responses + // to object storage is a possible follow-up, not a current guarantee. string artifact_sha256 = 8 [(buf.validate.field).string.pattern = "^[0-9a-f]{64}$"]; } From 01278820649fb13f78c0457892894e70b9da3f3f Mon Sep 17 00:00:00 2001 From: "randomizedcoder dave.seddon.ca@gmail.com" Date: Sun, 25 Oct 2026 19:49:18 -0700 Subject: [PATCH 11/27] fix(treatment): port the integration tool to the ClickHouse adapter; wait for the lock holder in the journal test The item 5 Nix harness failed because tools/tcp-controller-integration still named controller.ClickHouse. TestJournalConcurrentExclusion could also race its temp-dir cleanup against the goroutine still holding the lock; it now waits for that goroutine before returning. Co-Authored-By: Claude Fable 5.1 --- pkg/treatment/reconcile/journal_test.go | 8 ++++++-- tools/tcp-controller-integration/main.go | 7 ++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/pkg/treatment/reconcile/journal_test.go b/pkg/treatment/reconcile/journal_test.go index f56c9ef85..bd7461cd1 100644 --- a/pkg/treatment/reconcile/journal_test.go +++ b/pkg/treatment/reconcile/journal_test.go @@ -112,12 +112,16 @@ func TestJournalConcurrentExclusion(t *testing.T) { t.Fatal("lock not released", err) } // The lock-free reader sees the committed snapshot while the lock is held. - hold := make(chan struct{}) + hold, held, released := make(chan struct{}), make(chan struct{}), make(chan struct{}) go func() { - _ = j.WithLock(func(*State, func() error) error { <-hold; return nil }) + defer close(released) + _ = j.WithLock(func(*State, func() error) error { close(held); <-hold; return nil }) }() + <-held s, err := ReadSnapshot(j.Dir) close(hold) + // Wait for the holder to finish before the temp dir is removed. + <-released if err != nil || s.SuspensionReason != "first writer" { t.Fatalf("lock-free read under lock: %v %+v", err, s) } diff --git a/tools/tcp-controller-integration/main.go b/tools/tcp-controller-integration/main.go index 677765840..9edb2a68f 100644 --- a/tools/tcp-controller-integration/main.go +++ b/tools/tcp-controller-integration/main.go @@ -20,7 +20,8 @@ import ( "time" pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" - "github.com/randomizedcoder/xtcp2/pkg/treatment/controller" + evport "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/evidence" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/evidence/clickhouse" "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" "github.com/randomizedcoder/xtcp2/pkg/treatment/reconcile" "github.com/randomizedcoder/xtcp2/pkg/treatment/wire" @@ -276,13 +277,13 @@ func review(op pb.OperatorServiceClient, e *pb.Experiment, id string, evidence * return must(op.Review(ctx, &pb.ReviewRequest{ExperimentId: e.Id, ExpectedRevision: e.Revision, Approve: true, Reason: "Integration: reviewed measured evidence and destination scope", RequestId: id, Evidence: evidence})).Experiment } func propose(op pb.OperatorServiceClient, id string) *pb.Experiment { - ch := controller.ClickHouse{URL: "http://127.0.0.1:8123", Table: "xtcp.xtcp_flat_records_v2"} + ch := clickhouse.Client{URL: "http://127.0.0.1:8123", Table: "xtcp.xtcp_flat_records_v2"} var baseline *pb.Evidence eventually("fresh six-host baseline", 3*time.Minute, func() bool { end := time.Now().Add(-20 * time.Second) ctx, cancel := rpcContext() defer cancel() - baseline = wire.Evidence(must(ch.Evidence(ctx, "test-dc", netip.MustParsePrefix("198.18.0.1/32"), id, hosts, end.Add(-60*time.Second), end))) + baseline = wire.Evidence(must(ch.Evidence(ctx, evport.Window{Location: "test-dc", Destination: netip.MustParsePrefix("198.18.0.1/32"), ExperimentID: id, Agents: hosts, Start: end.Add(-60 * time.Second), End: end}))) require(len(baseline.Hosts) == 6, "baseline needs six hosts: %v", baseline.Hosts) for _, h := range baseline.Hosts { require(h.CollectionHealthy && h.Sockets >= 8 && h.BytesAcked >= 4096, "baseline not ready: %v", h) From d7fefee4e6d3be69276b00e262c5d5ffb00c3e48 Mon Sep 17 00:00:00 2001 From: "randomizedcoder dave.seddon.ca@gmail.com" Date: Sun, 25 Oct 2026 19:49:18 -0700 Subject: [PATCH 12/27] refactor(treatment): authorization as interceptors over the proto role option identity.Policy maps full method names to the role a caller must present; its unary and stream interceptors resolve the peer certificate once, store the identity in the context, refuse the wrong role, refuse any method without a declaration (fail closed) and, for agent RPCs, refuse a request whose agent_id or location is not the certificate's. identity.Agent and identity.Operator prefer the resolved identity and fall back to the peer, so handlers and their direct-call tests are unchanged. controller.RolePolicy derives the table from the (xtcp_treatment.v1.role) option on both service descriptors; controller.GRPCOptions bundles the interceptors with the message bounds and main passes it to grpc.NewServer. TestEveryMethodAgainstEveryRole tries each served method with an agent certificate, an operator certificate and none; TestRolePolicy fails if a served method has no declaration. The mTLS bufconn test now runs through the interceptors. Co-Authored-By: Claude Fable 5.1 --- cmd/xtcp2-controller/main.go | 3 +- docs/status-tcp-controller.md | 29 ++++- docs/tcp-controller.md | 15 ++- pkg/treatment/controller/auth.go | 58 +++++++++ pkg/treatment/controller/auth_test.go | 107 ++++++++++++++++ pkg/treatment/identity/identity.go | 16 ++- pkg/treatment/identity/interceptor.go | 107 ++++++++++++++++ pkg/treatment/identity/interceptor_test.go | 141 +++++++++++++++++++++ pkg/treatment/identity/mtls_test.go | 26 +++- 9 files changed, 482 insertions(+), 20 deletions(-) create mode 100644 pkg/treatment/controller/auth.go create mode 100644 pkg/treatment/controller/auth_test.go create mode 100644 pkg/treatment/identity/interceptor.go create mode 100644 pkg/treatment/identity/interceptor_test.go diff --git a/cmd/xtcp2-controller/main.go b/cmd/xtcp2-controller/main.go index 18b31aab8..a2b9ed828 100644 --- a/cmd/xtcp2-controller/main.go +++ b/cmd/xtcp2-controller/main.go @@ -17,7 +17,6 @@ import ( "time" pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" - "github.com/randomizedcoder/xtcp2/pkg/treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/controller" "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/evidence" chwarehouse "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/evidence/clickhouse" @@ -105,7 +104,7 @@ func run() error { return e } defer listener.Close() - grpcServer := grpc.NewServer(grpc.Creds(credentials.NewTLS(tlsConfig)), grpc.MaxRecvMsgSize(treatment.MaxStateBytes), grpc.MaxSendMsgSize(treatment.MaxStateBytes), grpc.MaxConcurrentStreams(8)) + grpcServer := grpc.NewServer(append(controller.GRPCOptions(), grpc.Creds(credentials.NewTLS(tlsConfig)))...) pb.RegisterAgentControlServiceServer(grpcServer, s) pb.RegisterOperatorServiceServer(grpcServer, s) var wg sync.WaitGroup diff --git a/docs/status-tcp-controller.md b/docs/status-tcp-controller.md index 454f59b8a..bfa97acd1 100644 --- a/docs/status-tcp-controller.md +++ b/docs/status-tcp-controller.md @@ -13,10 +13,10 @@ review being addressed. Items are listed in execution order. | 3 | explicit state machine; `Outcome` enum | done | 85b2cf59 | | 4 | repository ports, relational schema, pgx, domain types, `wire` | done | eae1d87d | | D1 | dual-mode agent, part 1: journal-specific proto, proto-free `reconcile`, `local`, `attribution`, watchdog off the agent | done | 6398338a | -| D2 | dual-mode agent, part 2: `-treatmentAttribution` journal watcher, `treatment` build tag, `cmd/xtcp2-treatment-agent`, Nix and module wiring | done | 87321a63, fix this commit | -| 5 | evidence port (`Source`, `CandidateSource`, `Warehouse`), ClickHouse as one adapter | done | this commit | -| 6 | auth as interceptor with per-RPC role option | next | | -| 7 | typed `xtcpnl.RuleWrite`; backend to `internal/goip/actuate` | pending | | +| D2 | dual-mode agent, part 2: `-treatmentAttribution` journal watcher, `treatment` build tag, `cmd/xtcp2-treatment-agent`, Nix and module wiring | done | 87321a63, fix 20f3a35a | +| 5 | evidence port (`Source`, `CandidateSource`, `Warehouse`), ClickHouse as one adapter | done | caba9bc4 | +| 6 | auth as interceptor with per-RPC role option | done | this commit | +| 7 | typed `xtcpnl.RuleWrite`; backend to `internal/goip/actuate` | next | | | 9 | identity/location startup check | pending | | | 10 | blocking journal lock | pending | | | 11 | scale: notification watermark, heartbeat split, leader lease | pending | | @@ -78,7 +78,8 @@ process. `-location`, so every record shipped `location=""` and the ClickHouse analysis (which filters by location) saw no hosts. The watcher now adopts the journal's location when the daemon has none and still keeps its own - when one is set (logged once either way). + when one is set (logged once either way). The rerun pinned to 20f3a35a + passed (seven sentinels). ## Item 5 notes @@ -95,6 +96,24 @@ process. - `artifact_sha256` is documented as the digest of the unarchived response. Archiving responses to the S3 destination remains a possible follow-up. +## Item 6 notes + +- `identity.Policy` (method -> role) with `UnaryInterceptor`/`StreamInterceptor` + and `Authorize`; identity resolved once per call and stored in the context + (`WithIdentity`/`FromContext`). `identity.Agent`/`Operator` prefer that + identity and fall back to the peer, so handler-level tests and direct calls + are unchanged. Agent unary requests are additionally checked generically: + `GetAgentId()` must be the caller and a non-empty `GetLocation()` must be + the certificate's. +- `controller.RolePolicy()` derives the table from the proto option over + both service descriptors; `controller.GRPCOptions()` is what main passes + to `grpc.NewServer` (interceptors + message bounds). A method without a + role option is simply absent from the policy and refused for everyone. +- Tests: identity interceptor tables (unary, stream, resolve precedence), + the mTLS bufconn test now runs through the interceptors and checks an + undeclared method fails closed, and `TestEveryMethodAgainstEveryRole` + tries every served method with each of three callers. + ## TODO - The watchdog and `pkg/treatment/local` still link gRPC, through diff --git a/docs/tcp-controller.md b/docs/tcp-controller.md index c04ce7fe1..73df29b62 100644 --- a/docs/tcp-controller.md +++ b/docs/tcp-controller.md @@ -35,7 +35,7 @@ a typed [protobuf contract](../proto/xtcp_treatment/v1/) (`xtcp_treatment.v1`: ` | --- | --- | --- | | `pkg/treatment` | everyone | the domain model (`Treatment`, `Experiment`, `Evidence`, `DesiredState`, enums), limits and validators; proto-free | | `pkg/treatment/wire` | gRPC boundary | domain ↔ generated proto conversion; `Parse*` fails closed on unknown enums and invalid timestamps | -| `pkg/treatment/identity` | agent, controller, tools | SPIFFE URI identity, mTLS configuration, gRPC peer resolution | +| `pkg/treatment/identity` | agent, controller, tools | SPIFFE URI identity, mTLS configuration, gRPC peer resolution, `Policy` interceptors that resolve the caller once per call | | `pkg/treatment/reconcile` | agent, watchdog | intent journal (`State`, `Record`, the `xtcp_treatment_journal.v1` file format), reconciler, `Backend` interface, `Mode` (full or expiry-only); domain types only, no RPC proto, no gRPC | | `pkg/treatment/local` | agent, watchdog, `treatmentctl` | the shared `controller-agent.json` config and the goip-backed reconciler built from it; no TLS, no controller | | `pkg/treatment/attribution` | `xtcp2`, agent | the exposure index built from a journal snapshot and the hot-path telemetry stamp; the same code serves the embedded agent and a telemetry-only daemon | @@ -443,6 +443,19 @@ separate process in both arrangements and builds on `pkg/treatment/local` and certificates cannot invoke operator RPCs or fetch another agent's state. Use protected key files; restart processes to load rotated certificates. There is no online certificate revocation service in this release. + + Authorization is declared, not hand-rolled. Every RPC in `agent.proto` and + `operator.proto` carries an `(xtcp_treatment.v1.role)` option; the + controller builds an `identity.Policy` from those options at startup and + installs unary and stream interceptors that resolve the peer certificate + once, refuse the wrong role, refuse any method without a declaration + (fail closed), and for agent RPCs refuse a request whose `agent_id` or + `location` is not the certificate's. Handlers read the resolved identity + from the context; the same `identity.Agent`/`identity.Operator` checks + remain in the handlers so a direct call behaves identically. A test in + `pkg/treatment/controller` tries every served method with an agent + certificate, an operator certificate and none, and fails if a method has + no declaration. 5. Configure explicit protected prefixes for management, DNS, telemetry, controller dependencies and any infrastructure that must not be experimented on. The agent also protects controller addresses resolved at startup. DNS diff --git a/pkg/treatment/controller/auth.go b/pkg/treatment/controller/auth.go new file mode 100644 index 000000000..c27b69837 --- /dev/null +++ b/pkg/treatment/controller/auth.go @@ -0,0 +1,58 @@ +package controller + +import ( + "fmt" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" + "google.golang.org/grpc" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" +) + +// serviceFiles are the proto files whose services the controller serves. +var serviceFiles = []protoreflect.FileDescriptor{pb.File_xtcp_treatment_v1_agent_proto, pb.File_xtcp_treatment_v1_operator_proto} + +// RolePolicy derives the per-RPC authorization table from the +// (xtcp_treatment.v1.role) option on every method of the served services. +// A method declared without a role (or ROLE_UNSPECIFIED) is left out of the +// policy, and the interceptor then refuses it for everyone; TestRolePolicy +// fails the build's test run if that ever happens, so the declaration is +// the single place a new RPC's audience is decided. +func RolePolicy() identity.Policy { + p := identity.Policy{} + for _, f := range serviceFiles { + services := f.Services() + for i := 0; i < services.Len(); i++ { + sd := services.Get(i) + methods := sd.Methods() + for j := 0; j < methods.Len(); j++ { + m := methods.Get(j) + name := fmt.Sprintf("/%s/%s", sd.FullName(), m.Name()) + switch proto.GetExtension(m.Options(), pb.E_Role).(pb.Role) { + case pb.Role_ROLE_AGENT: + p[name] = identity.RoleAgent + case pb.Role_ROLE_OPERATOR: + p[name] = identity.RoleOperator + } + } + } + } + return p +} + +// GRPCOptions are the server options every controller listener uses: the +// authorization interceptors over RolePolicy and the message bounds. The +// caller adds transport credentials; without mTLS the interceptors refuse +// every call as unauthenticated. +func GRPCOptions() []grpc.ServerOption { + p := RolePolicy() + return []grpc.ServerOption{ + grpc.ChainUnaryInterceptor(p.UnaryInterceptor()), + grpc.ChainStreamInterceptor(p.StreamInterceptor()), + grpc.MaxRecvMsgSize(treatment.MaxStateBytes), + grpc.MaxSendMsgSize(treatment.MaxStateBytes), + grpc.MaxConcurrentStreams(8), + } +} diff --git a/pkg/treatment/controller/auth_test.go b/pkg/treatment/controller/auth_test.go new file mode 100644 index 000000000..206b411d2 --- /dev/null +++ b/pkg/treatment/controller/auth_test.go @@ -0,0 +1,107 @@ +package controller + +import ( + "context" + "strings" + "testing" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// servedMethods lists every RPC the controller registers, from the generated +// service descriptors, so a new RPC is covered the moment it exists. +func servedMethods() map[string]bool { + out := map[string]bool{} + for _, sd := range []grpc.ServiceDesc{pb.AgentControlService_ServiceDesc, pb.OperatorService_ServiceDesc} { + for _, m := range sd.Methods { + out["/"+sd.ServiceName+"/"+m.MethodName] = false + } + for _, s := range sd.Streams { + out["/"+sd.ServiceName+"/"+s.StreamName] = true + } + } + return out +} + +// go test ./pkg/treatment/controller -run TestRolePolicy +func TestRolePolicy(t *testing.T) { + policy := RolePolicy() + served := servedMethods() + if len(served) == 0 { + t.Fatal("no served methods found") + } + for method := range served { + role, ok := policy[method] + if !ok { + t.Errorf("%s has no (xtcp_treatment.v1.role) declaration; the interceptor refuses it for everyone", method) + continue + } + want := identity.RoleOperator + if strings.HasPrefix(method, "/xtcp_treatment.v1.AgentControlService/") { + want = identity.RoleAgent + } + if role != want { + t.Errorf("%s declares %s, expected %s: agents must never reach operator RPCs and vice versa", method, role, want) + } + } + for method := range policy { + if _, ok := served[method]; !ok { + t.Errorf("policy names %s, which no served service has", method) + } + } +} + +// TestEveryMethodAgainstEveryRole is the exhaustive matrix: each served +// method is attempted with an agent certificate, an operator certificate +// and no certificate, and only the declared role may reach the handler. +func TestEveryMethodAgainstEveryRole(t *testing.T) { + policy := RolePolicy() + callers := []struct { + description string + ctx context.Context + role identity.Role + }{ + {"agent certificate", testIdentity("agent/dc1/host"), identity.RoleAgent}, + {"operator certificate", testIdentity("operator/alice"), identity.RoleOperator}, + {"no certificate", context.Background(), ""}, + } + for method, stream := range servedMethods() { + for _, c := range callers { + t.Run(method+"/"+c.description, func(t *testing.T) { + expected := codes.PermissionDenied + if c.role == "" { + expected = codes.Unauthenticated + } else if policy[method] == c.role { + expected = codes.OK + } + reached := false + var err error + if stream { + err = policy.StreamInterceptor()(nil, &desiredTestStream{ctx: c.ctx}, &grpc.StreamServerInfo{FullMethod: method}, func(any, grpc.ServerStream) error { reached = true; return nil }) + } else { + // A request with no agent fields so only the role decides. + _, err = policy.UnaryInterceptor()(c.ctx, struct{}{}, &grpc.UnaryServerInfo{FullMethod: method}, func(context.Context, any) (any, error) { reached = true; return nil, nil }) + } + if status.Code(err) != expected || reached != (expected == codes.OK) { + t.Fatalf("%s with %s: err=%v reached=%t expected %v", method, c.description, err, reached, expected) + } + }) + } + } +} + +func TestGRPCOptionsCarryInterceptors(t *testing.T) { + // The options must at least construct a server; the interceptor behaviour + // itself is covered above and by the identity package's mTLS test. + s := grpc.NewServer(GRPCOptions()...) + defer s.Stop() + pb.RegisterAgentControlServiceServer(s, &Server{}) + pb.RegisterOperatorServiceServer(s, &Server{}) + if len(s.GetServiceInfo()) != 2 { + t.Fatalf("services registered: %d", len(s.GetServiceInfo())) + } +} diff --git a/pkg/treatment/identity/identity.go b/pkg/treatment/identity/identity.go index a0f45e6be..dfa00b221 100644 --- a/pkg/treatment/identity/identity.go +++ b/pkg/treatment/identity/identity.go @@ -61,9 +61,9 @@ func FromCertificate(cert *x509.Certificate) (Identity, error) { return found[0], nil } -// FromPeer resolves the verified client certificate of the gRPC peer. It is -// the only function here that reads the context; errors already carry gRPC -// status codes so handlers and interceptors can return them unchanged. +// FromPeer resolves the verified client certificate of the gRPC peer. Errors +// already carry gRPC status codes so handlers and interceptors can return +// them unchanged. func FromPeer(ctx context.Context) (Identity, error) { p, ok := peer.FromContext(ctx) if !ok { @@ -80,10 +80,11 @@ func FromPeer(ctx context.Context) (Identity, error) { return id, nil } -// Agent requires the peer to be the agent named by want. Agents may only +// Agent requires the caller to be the agent named by want. Agents may only // speak for themselves; the controller passes the request's agent_id here. +// It uses the identity an interceptor already resolved when there is one. func Agent(ctx context.Context, want string) (Identity, error) { - id, e := FromPeer(ctx) + id, e := resolve(ctx) if e != nil { return id, e } @@ -94,9 +95,10 @@ func Agent(ctx context.Context, want string) (Identity, error) { } // Operator requires an operator certificate and returns the operator ID, -// which callers record as the audit actor. +// which callers record as the audit actor. It uses the identity an +// interceptor already resolved when there is one. func Operator(ctx context.Context) (string, error) { - id, e := FromPeer(ctx) + id, e := resolve(ctx) if e != nil { return "", e } diff --git a/pkg/treatment/identity/interceptor.go b/pkg/treatment/identity/interceptor.go new file mode 100644 index 000000000..0b46da5ba --- /dev/null +++ b/pkg/treatment/identity/interceptor.go @@ -0,0 +1,107 @@ +package identity + +import ( + "context" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// Policy maps full gRPC method names ("/pkg.Service/Method") to the Role a +// caller must present. The controller derives it from the proto's per-RPC +// role option; this package only enforces it. A method absent from the +// policy is refused for everyone, so an RPC added without a declaration +// fails closed rather than open. +type Policy map[string]Role + +type ctxKey struct{} + +// WithIdentity stores a resolved identity so handlers do not parse the +// certificate again. +func WithIdentity(ctx context.Context, id Identity) context.Context { + return context.WithValue(ctx, ctxKey{}, id) +} + +// FromContext returns the identity an interceptor resolved, if any. +func FromContext(ctx context.Context) (Identity, bool) { + id, ok := ctx.Value(ctxKey{}).(Identity) + return id, ok +} + +// resolve prefers the interceptor's identity and falls back to the peer, so +// handlers behave the same whether or not the interceptor ran (direct calls +// in tests, or a server wired without it). +func resolve(ctx context.Context) (Identity, error) { + if id, ok := FromContext(ctx); ok { + return id, nil + } + return FromPeer(ctx) +} + +// Authorize resolves the peer once and checks it against the policy for +// method. It returns a context carrying the identity for the handler. +func (p Policy) Authorize(ctx context.Context, method string) (context.Context, error) { + required, ok := p[method] + if !ok || required == "" { + return nil, status.Errorf(codes.PermissionDenied, "%s has no role declaration", method) + } + id, e := FromPeer(ctx) + if e != nil { + return nil, e + } + if id.Role != required { + return nil, status.Errorf(codes.PermissionDenied, "%s certificate required", required) + } + return WithIdentity(ctx, id), nil +} + +// agentRequest is the part of an agent RPC's request the interceptor can +// check generically: an agent speaks only for itself and only from its +// enrolled location. Requests without these fields are left to the handler. +func agentRequest(id Identity, req any) error { + if r, ok := req.(interface{ GetAgentId() string }); ok && r.GetAgentId() != id.ID { + return status.Error(codes.PermissionDenied, "agent identity mismatch") + } + if r, ok := req.(interface{ GetLocation() string }); ok && r.GetLocation() != "" && r.GetLocation() != id.Location { + return status.Error(codes.PermissionDenied, "agent location differs from certificate") + } + return nil +} + +// UnaryInterceptor enforces the policy for unary RPCs and hands the handler +// a context carrying the identity. +func (p Policy) UnaryInterceptor() grpc.UnaryServerInterceptor { + return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { + ctx, e := p.Authorize(ctx, info.FullMethod) + if e != nil { + return nil, e + } + if id, _ := FromContext(ctx); id.Role == RoleAgent { + if e = agentRequest(id, req); e != nil { + return nil, e + } + } + return handler(ctx, req) + } +} + +type identifiedStream struct { + grpc.ServerStream + ctx context.Context +} + +func (s identifiedStream) Context() context.Context { return s.ctx } + +// StreamInterceptor enforces the policy for streaming RPCs. The first +// message is not visible here, so per-request agent checks stay in the +// handler; the identity itself is resolved once and carried on the stream. +func (p Policy) StreamInterceptor() grpc.StreamServerInterceptor { + return func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + ctx, e := p.Authorize(ss.Context(), info.FullMethod) + if e != nil { + return e + } + return handler(srv, identifiedStream{ServerStream: ss, ctx: ctx}) + } +} diff --git a/pkg/treatment/identity/interceptor_test.go b/pkg/treatment/identity/interceptor_test.go new file mode 100644 index 000000000..c516c0cb7 --- /dev/null +++ b/pkg/treatment/identity/interceptor_test.go @@ -0,0 +1,141 @@ +package identity + +import ( + "context" + "crypto/tls" + "crypto/x509" + "errors" + "net/url" + "testing" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/peer" + "google.golang.org/grpc/status" +) + +// peerContext fakes a verified mTLS peer whose leaf carries the given SAN +// path; an empty path is a plaintext peer. +func peerContext(path string) context.Context { + if path == "" { + return context.Background() + } + u, _ := url.Parse("spiffe://xtcp/" + path) + cert := &x509.Certificate{URIs: []*url.URL{u}} + return peer.NewContext(context.Background(), &peer.Peer{AuthInfo: credentials.TLSInfo{State: tls.ConnectionState{PeerCertificates: []*x509.Certificate{cert}, VerifiedChains: [][]*x509.Certificate{{cert}}}}}) +} + +// agentRequestFixture mimics a request with the generic agent fields. +type agentRequestFixture struct{ agentID, location string } + +func (r agentRequestFixture) GetAgentId() string { return r.agentID } +func (r agentRequestFixture) GetLocation() string { return r.location } + +const ( + checkIn = "/xtcp_treatment.v1.AgentControlService/CheckIn" + propose = "/xtcp_treatment.v1.OperatorService/Propose" + watch = "/xtcp_treatment.v1.AgentControlService/WatchDesiredState" +) + +var testPolicy = Policy{checkIn: RoleAgent, watch: RoleAgent, propose: RoleOperator} + +func TestPolicyUnaryInterceptor(t *testing.T) { + for _, tc := range []struct { + name, description, peer, method string + request any + expected codes.Code + wantIdentity Identity + }{ + {"agent-calls-checkin", "positive: an agent certificate may call an agent RPC for itself", "agent/dc1/host-1", checkIn, agentRequestFixture{"host-1", "dc1"}, codes.OK, Identity{Role: RoleAgent, Location: "dc1", ID: "host-1"}}, + {"operator-calls-propose", "positive: an operator certificate may call an operator RPC", "operator/alice", propose, struct{}{}, codes.OK, Identity{Role: RoleOperator, ID: "alice"}}, + {"agent-calls-propose", "negative: an agent certificate cannot call an operator RPC", "agent/dc1/host-1", propose, struct{}{}, codes.PermissionDenied, Identity{}}, + {"operator-calls-checkin", "negative: an operator certificate cannot call an agent RPC", "operator/alice", checkIn, agentRequestFixture{"host-1", "dc1"}, codes.PermissionDenied, Identity{}}, + {"no-peer-cert", "negative: a plaintext peer is unauthenticated before any role check", "", propose, struct{}{}, codes.Unauthenticated, Identity{}}, + {"bad-spiffe-path", "negative: a certificate whose SAN is not an xtcp identity is refused", "agent/only-one-segment", checkIn, agentRequestFixture{"only-one-segment", ""}, codes.PermissionDenied, Identity{}}, + {"unclassified-method", "boundary: a method missing from the policy is refused for everyone (fail closed)", "operator/alice", "/xtcp_treatment.v1.OperatorService/Undeclared", struct{}{}, codes.PermissionDenied, Identity{}}, + {"agent-id-mismatch", "corner: an agent naming another host is refused before the handler", "agent/dc1/host-1", checkIn, agentRequestFixture{"host-2", "dc1"}, codes.PermissionDenied, Identity{}}, + {"location-mismatch", "corner: an agent reporting from another location is refused before the handler", "agent/dc1/host-1", checkIn, agentRequestFixture{"host-1", "dc2"}, codes.PermissionDenied, Identity{}}, + {"location-unset", "boundary: a request without a location leaves the location check to enrollment", "agent/dc1/host-1", checkIn, agentRequestFixture{"host-1", ""}, codes.OK, Identity{Role: RoleAgent, Location: "dc1", ID: "host-1"}}, + } { + t.Run(tc.name, func(t *testing.T) { + var seen Identity + called := false + handler := func(ctx context.Context, _ any) (any, error) { + called = true + seen, _ = FromContext(ctx) + return nil, nil + } + _, err := testPolicy.UnaryInterceptor()(peerContext(tc.peer), tc.request, &grpc.UnaryServerInfo{FullMethod: tc.method}, handler) + if status.Code(err) != tc.expected { + t.Fatalf("%s: got %v, expected %v", tc.description, err, tc.expected) + } + if called != (tc.expected == codes.OK) { + t.Fatalf("%s: handler called=%t", tc.description, called) + } + if seen != tc.wantIdentity { + t.Fatalf("%s: handler saw %+v, want %+v", tc.description, seen, tc.wantIdentity) + } + }) + } +} + +type fakeStream struct { + grpc.ServerStream + ctx context.Context +} + +func (s fakeStream) Context() context.Context { return s.ctx } + +func TestPolicyStreamInterceptor(t *testing.T) { + for _, tc := range []struct { + name, description, peer, method string + expected codes.Code + }{ + {"agent-watch", "positive: an agent may open an agent stream and the handler sees its identity once", "agent/dc1/host-1", watch, codes.OK}, + {"operator-watch", "negative: an operator cannot open an agent stream", "operator/alice", watch, codes.PermissionDenied}, + {"plaintext", "negative: a plaintext stream is unauthenticated", "", watch, codes.Unauthenticated}, + {"unclassified", "boundary: an undeclared stream method is refused", "agent/dc1/host-1", "/xtcp_treatment.v1.AgentControlService/Undeclared", codes.PermissionDenied}, + } { + t.Run(tc.name, func(t *testing.T) { + called := false + handler := func(_ any, ss grpc.ServerStream) error { + called = true + id, ok := FromContext(ss.Context()) + if !ok || id.ID != "host-1" { + return errors.New("stream lost identity") + } + // The handler's own check must not need to re-read the peer. + if _, err := Agent(ss.Context(), "host-1"); err != nil { + return err + } + return nil + } + err := testPolicy.StreamInterceptor()(nil, fakeStream{ctx: peerContext(tc.peer)}, &grpc.StreamServerInfo{FullMethod: tc.method}, handler) + if status.Code(err) != tc.expected || called != (tc.expected == codes.OK) { + t.Fatalf("%s: err=%v called=%t expected %v", tc.description, err, called, tc.expected) + } + }) + } +} + +func TestResolvePrefersInterceptorIdentity(t *testing.T) { + for _, tc := range []struct { + name, description string + ctx context.Context + want string + expected codes.Code + }{ + {"context", "positive: a resolved identity is reused without re-reading the peer", WithIdentity(context.Background(), Identity{Role: RoleOperator, ID: "alice"}), "alice", codes.OK}, + {"peer", "positive: without an interceptor the peer certificate is read", peerContext("operator/bob"), "bob", codes.OK}, + {"context-wins", "corner: the interceptor's identity takes precedence over a conflicting peer", WithIdentity(peerContext("agent/dc1/host-1"), Identity{Role: RoleOperator, ID: "carol"}), "carol", codes.OK}, + {"none", "negative: neither source yields an identity", context.Background(), "", codes.Unauthenticated}, + } { + t.Run(tc.name, func(t *testing.T) { + got, err := Operator(tc.ctx) + if status.Code(err) != tc.expected || got != tc.want { + t.Fatalf("%s: got %q %v", tc.description, got, err) + } + }) + } +} diff --git a/pkg/treatment/identity/mtls_test.go b/pkg/treatment/identity/mtls_test.go index e4c8c5688..fe23587d5 100644 --- a/pkg/treatment/identity/mtls_test.go +++ b/pkg/treatment/identity/mtls_test.go @@ -25,18 +25,31 @@ import ( "google.golang.org/grpc/test/bufconn" ) +// authProbe trusts the interceptor: it only reports what identity reached +// the handler, so a wrong answer here is the interceptor's fault. type authProbe struct { pb.UnimplementedAgentControlServiceServer pb.UnimplementedOperatorServiceServer } func (authProbe) CheckIn(ctx context.Context, r *pb.CheckInRequest) (*pb.CheckInResponse, error) { - _, e := Agent(ctx, r.AgentId) - return &pb.CheckInResponse{}, e + id, ok := FromContext(ctx) + if !ok || id.Role != RoleAgent || id.ID != r.AgentId { + return nil, status.Error(codes.Internal, "interceptor did not resolve the agent") + } + return &pb.CheckInResponse{}, nil } func (authProbe) Inspect(ctx context.Context, _ *pb.InspectRequest) (*pb.InspectResponse, error) { - _, e := Operator(ctx) - return &pb.InspectResponse{}, e + id, ok := FromContext(ctx) + if !ok || id.Role != RoleOperator { + return nil, status.Error(codes.Internal, "interceptor did not resolve the operator") + } + return &pb.InspectResponse{}, nil +} + +var probePolicy = Policy{ + "/xtcp_treatment.v1.AgentControlService/CheckIn": RoleAgent, + "/xtcp_treatment.v1.OperatorService/Inspect": RoleOperator, } func TestMTLSHandshakesAndRoleBoundaries(t *testing.T) { @@ -95,7 +108,7 @@ func TestMTLSHandshakesAndRoleBoundaries(t *testing.T) { return config } listener := bufconn.Listen(1 << 20) - server := grpc.NewServer(grpc.Creds(credentials.NewTLS(issue("server", "", true)))) + server := grpc.NewServer(grpc.Creds(credentials.NewTLS(issue("server", "", true))), grpc.ChainUnaryInterceptor(probePolicy.UnaryInterceptor()), grpc.ChainStreamInterceptor(probePolicy.StreamInterceptor())) pb.RegisterAgentControlServiceServer(server, authProbe{}) pb.RegisterOperatorServiceServer(server, authProbe{}) go server.Serve(listener) @@ -126,6 +139,9 @@ func TestMTLSHandshakesAndRoleBoundaries(t *testing.T) { if _, e = pb.NewOperatorServiceClient(operator).Inspect(ctx, &pb.InspectRequest{}); e != nil { t.Fatal("authenticated operator", e) } + if _, e = pb.NewOperatorServiceClient(operator).Propose(ctx, &pb.ProposeRequest{}); status.Code(e) != codes.PermissionDenied { + t.Fatal("undeclared method must fail closed", e) + } for name, config := range map[string]*tls.Config{"missing_client_certificate": agentTLS.Clone(), "wrong_server_name": agentTLS.Clone(), "untrusted_server": agentTLS.Clone()} { t.Run(name, func(t *testing.T) { switch name { From 3a2843b25eeb53d24aeb4faa138bfcf9594cfe63 Mon Sep 17 00:00:00 2001 From: "randomizedcoder dave.seddon.ca@gmail.com" Date: Sun, 25 Oct 2026 20:07:34 -0700 Subject: [PATCH 13/27] refactor(goip): typed xtcpnl.RuleWrite; treatment actuator moves to internal/goip/actuate Add xtcpnl.RuleWrite, RouteWrite's counterpart for RTM_NEWRULE/RTM_DELRULE: the fib_rule_hdr from fields, a u32 Table that goes in the header byte up to 255 and in FRA_TABLE above it, and the FRA_* selectors as an ordered list. The treatment backend's rule() now builds from those fields instead of spelling `ip rule` keywords and running them through parseRuleWriteArgs; TestRuleWriteTypedMatchesArgv in internal/goip keeps the two encoders byte for byte equal for the treatment rule, its v6 and delete forms, table 255, /32, /128 and `not`. Move the backend out of the goip root into internal/goip/actuate (actuate.Backend, RouteTreatment, TCPRouteSettings; actuate.go, rules.go, routes.go) with its tests and benchmark. rule() refuses out-of-range table, priority and protocol before encoding. NetlinkSource.ApplyRouteControl stays in the goip root (route_control.go) because it needs the socket's unexported descriptor; it remains the only live-write entry point. The goip hermetic-import test pins the new package in the closure, pkg/treatment/local imports the actuator, the boundary rule expects it, and the Nix harness runs ./internal/goip/actuate. Docs: netlink README's "no live kernel writes" callout and the top-level README sentence now describe the one opted-in actuator path; the controller doc gains the actuate package row and the typed-rule note; status doc item 7. Co-Authored-By: Claude Fable 5.1 --- README.md | 2 +- docs/netlink/README.md | 19 +- docs/status-tcp-controller.md | 28 +- docs/tcp-controller.md | 10 +- internal/goip/actuate/actuate.go | 120 ++++ .../backend_test.go} | 14 +- .../contracts_test.go} | 40 +- .../live_test.go} | 8 +- internal/goip/actuate/routes.go | 310 ++++++++++ internal/goip/actuate/rule_test.go | 128 ++++ internal/goip/actuate/rules.go | 187 ++++++ internal/goip/imports_test.go | 2 + internal/goip/route_control.go | 38 ++ internal/goip/rule_write_typed_test.go | 97 +++ internal/goip/treatment_linux.go | 570 ------------------ nix/tests/tcp-controller/quality.nix | 4 +- pkg/treatment/boundaries_test.go | 4 +- pkg/treatment/local/host.go | 8 +- pkg/xtcpnl/xtcpnl_rule_write.go | 129 ++++ pkg/xtcpnl/xtcpnl_rule_write_test.go | 175 ++++++ 20 files changed, 1273 insertions(+), 620 deletions(-) create mode 100644 internal/goip/actuate/actuate.go rename internal/goip/{treatment_linux_test.go => actuate/backend_test.go} (77%) rename internal/goip/{treatment_contracts_test.go => actuate/contracts_test.go} (68%) rename internal/goip/{treatment_live_test.go => actuate/live_test.go} (89%) create mode 100644 internal/goip/actuate/routes.go create mode 100644 internal/goip/actuate/rule_test.go create mode 100644 internal/goip/actuate/rules.go create mode 100644 internal/goip/route_control.go create mode 100644 internal/goip/rule_write_typed_test.go delete mode 100644 internal/goip/treatment_linux.go create mode 100644 pkg/xtcpnl/xtcpnl_rule_write.go create mode 100644 pkg/xtcpnl/xtcpnl_rule_write_test.go diff --git a/README.md b/README.md index a5d2b33b1..d702e008d 100644 --- a/README.md +++ b/README.md @@ -216,7 +216,7 @@ The main operator and integration tools under `cmd/` are: ## Future direction: from observation to action -The longer-term direction is for xtcp2 to take network actions as well as observe. goip already constructs requests for a growing set of write operations, but **live kernel writes remain incomplete and disabled**: supported write commands serialize requests to `GOIP_WRITE_OUT` for validation and refuse to apply them. The [write-path and parity documentation](docs/netlink/README.md) tracks that work; the [network-control gRPC design](docs/design-network-control-grpc.md) describes proposed APIs, not an available service. +The longer-term direction is for xtcp2 to take network actions as well as observe. goip constructs requests for a growing set of write operations, and the CLI's write commands serialize them to `GOIP_WRITE_OUT` for validation and refuse to apply them. The one live-write path is the opt-in [TCP route treatment controller](docs/tcp-controller.md): its actuator installs host routes in a private table through goip, with ownership and readback checks, and is disabled unless an operator enables it. The [write-path and parity documentation](docs/netlink/README.md) tracks the rest of that work; the [network-control gRPC design](docs/design-network-control-grpc.md) describes proposed APIs, not an available service. Future network control and true BGP-origin/next-hop ASN enrichment are separate developments. Neither is required to use today's collection, inspection, or operational alerting features. diff --git a/docs/netlink/README.md b/docs/netlink/README.md index d935a2e37..ede61f8dc 100644 --- a/docs/netlink/README.md +++ b/docs/netlink/README.md @@ -55,8 +55,12 @@ nix run .#microvm-x86_64-goip-parity # the live tier; needs /dev/kvm, not r ## Two constraints worth knowing up front -> **No live kernel writes today.** Inspection sends read requests; implemented -> write verbs serialize dry-run requests for validation rather than applying them. +> **One live kernel write path, opted in.** Inspection sends read requests; +> the CLI's write verbs serialize dry-run requests for validation rather than +> applying them. The single exception is the treatment route actuator in +> [`internal/goip/actuate`](../../internal/goip/actuate), which installs +> host routes in a private table behind an explicit `enable_apply` setting +> ([tcp-controller.md](../tcp-controller.md#supported-routes-and-tcp-semantics)). The original expansion plan's read-only constraint was narrowed as dry-run write support landed. The first paths were `ip route` and `ip link` — see @@ -72,7 +76,13 @@ since its G3-T03, `RTM_NEWNEXTHOP` and `RTM_DELNEXTHOP`, and, since its G3-T04, `RTM_NEWADDRLABEL` and `RTM_DELADDRLABEL`, and, since its G3-T05, `RTM_SETNEIGHTBL` and `RTM_SETSTATS`, and, since its G3-T06, `RTM_NEWLINKPROP` and `RTM_DELLINKPROP`. Requests built there are written to a file for -comparison and never sent; no automated path applies a write to a live kernel. +comparison and never sent by the CLI. The one path that does send is +`goip.NetlinkSource.ApplyRouteControl`, reached only by the treatment +actuator: it admits acknowledged `RTM_NEWROUTE`/`RTM_DELROUTE`/`RTM_NEWRULE`/ +`RTM_DELRULE` requests built from `xtcpnl.RouteWrite` and `xtcpnl.RuleWrite`, +never from argv, and the actuator checks ownership and reads back after +every mutation. + Both dry runs have since landed on that gate: `goip link add|set|change|replace|delete` and `goip route add|change|replace|prepend|append|test|delete` build `ip`'s request @@ -86,7 +96,8 @@ now do too, and refuse with Supported families have expanded beyond those initial paths; the [coverage tracker](coverage-status.md) records current supported operations. Coverage of parsing and dry-run requests does not establish live mutation -support. Future action-taking is described in the +support; the actuator's two route forms and one rule are the whole of it. +Broader action-taking is described in the [network-control gRPC design](../design-network-control-grpc.md), which is a proposal rather than an available service. Comparisons with libraries that configure the kernel must preserve that distinction. diff --git a/docs/status-tcp-controller.md b/docs/status-tcp-controller.md index bfa97acd1..46957d450 100644 --- a/docs/status-tcp-controller.md +++ b/docs/status-tcp-controller.md @@ -15,9 +15,9 @@ review being addressed. Items are listed in execution order. | D1 | dual-mode agent, part 1: journal-specific proto, proto-free `reconcile`, `local`, `attribution`, watchdog off the agent | done | 6398338a | | D2 | dual-mode agent, part 2: `-treatmentAttribution` journal watcher, `treatment` build tag, `cmd/xtcp2-treatment-agent`, Nix and module wiring | done | 87321a63, fix 20f3a35a | | 5 | evidence port (`Source`, `CandidateSource`, `Warehouse`), ClickHouse as one adapter | done | caba9bc4 | -| 6 | auth as interceptor with per-RPC role option | done | this commit | -| 7 | typed `xtcpnl.RuleWrite`; backend to `internal/goip/actuate` | next | | -| 9 | identity/location startup check | pending | | +| 6 | auth as interceptor with per-RPC role option | done | d7fefee4 | +| 7 | typed `xtcpnl.RuleWrite`; backend to `internal/goip/actuate` | done | this commit | +| 9 | identity/location startup check | next | | | 10 | blocking journal lock | pending | | | 11 | scale: notification watermark, heartbeat split, leader lease | pending | | | 12 | docs: threat model, naming, SIGHUP, philosophy sentence | pending | | @@ -114,6 +114,28 @@ process. undeclared method fails closed, and `TestEveryMethodAgainstEveryRole` tries every served method with each of three callers. +## Item 7 notes + +- `xtcpnl.RuleWrite` (+ `RuleAttr*`, `EncodeFibRuleHdr`, + `BuildRuleWriteRequest`) is `RouteWrite`'s counterpart for `RTM_NEWRULE`/ + `RTM_DELRULE`. `Table` is a u32: 1..255 goes in the header byte, above + that the header is `RT_TABLE_UNSPEC` and `FRA_TABLE` is written first, + which is also what the argv `table` keyword does at its position. +- `internal/goip/actuate` holds the backend (`actuate.Backend`, + `RouteTreatment`, `TCPRouteSettings`), split into `actuate.go`, `rules.go` + and `routes.go`; `rule()` builds from fields and refuses out-of-range + table/priority/protocol before encoding. `NetlinkSource.ApplyRouteControl` + stays in `internal/goip/route_control.go` because it needs the socket's + unexported descriptor; it is the only live-write entry point. +- Tests: `TestBuildRuleWriteRequest` (hand-written hex) and + `TestRuleWriteReadsBack` in `pkg/xtcpnl`; `TestRuleWriteTypedMatchesArgv` + in `internal/goip` compares typed and `parseRuleWriteArgs` bytes for the + treatment rule, v6, delete, table 255, /32, /128 and `not`; + `TestTreatmentRuleRequest` in `actuate` reads the owned rule back and + covers the refused ranges. The goip hermetic-import test pins the new + package in goip's closure; the Nix harness and docs point at + `./internal/goip/actuate`. + ## TODO - The watchdog and `pkg/treatment/local` still link gRPC, through diff --git a/docs/tcp-controller.md b/docs/tcp-controller.md index 73df29b62..60e84d2d2 100644 --- a/docs/tcp-controller.md +++ b/docs/tcp-controller.md @@ -21,7 +21,7 @@ flowchart LR C -->|revision notification| D D -->|check-in and observed state| C D --> J[Atomic protobuf-text journal] - J --> R[goip route backend] + J --> R[goip actuator, internal/goip/actuate] R --> L[Owned Linux routes and TCP lookup rules] W[Independent local lease watchdog] --> J ``` @@ -38,6 +38,7 @@ a typed [protobuf contract](../proto/xtcp_treatment/v1/) (`xtcp_treatment.v1`: ` | `pkg/treatment/identity` | agent, controller, tools | SPIFFE URI identity, mTLS configuration, gRPC peer resolution, `Policy` interceptors that resolve the caller once per call | | `pkg/treatment/reconcile` | agent, watchdog | intent journal (`State`, `Record`, the `xtcp_treatment_journal.v1` file format), reconciler, `Backend` interface, `Mode` (full or expiry-only); domain types only, no RPC proto, no gRPC | | `pkg/treatment/local` | agent, watchdog, `treatmentctl` | the shared `controller-agent.json` config and the goip-backed reconciler built from it; no TLS, no controller | +| `internal/goip/actuate` | `local` | the route actuator: owned table, rule priority and protocol, baseline copy with TCP metrics, ownership and readback checks; requests come from `xtcpnl`'s typed `RouteWrite` and `RuleWrite` builders and reach the kernel only through `goip.NetlinkSource.ApplyRouteControl`, the one live-write path in goip | | `pkg/treatment/attribution` | `xtcp2`, agent | the exposure index built from a journal snapshot and the hot-path telemetry stamp; the same code serves the embedded agent and a telemetry-only daemon | | `pkg/treatment/experiment` | controller | evaluation statistics, cohort allocation, phase advancement, state machine; pure functions over domain types | | `pkg/treatment/agent` | `xtcp2`, agent binary | check-in loop, watch stream, capabilities, over `local` and `attribution`; a library with two front ends | @@ -92,7 +93,10 @@ physical host's PID 1. The default reserved resources are table **10002**, rule priority **30000**, and route/rule protocol **242**. The actuator copies the baseline gateway, interface, preferred source and supported metrics into a destination override. TCP rules -with `iif lo` select locally originated traffic. Unmatched traffic falls through +with `iif lo` select locally originated traffic. The rule is built from fields +with `xtcpnl.RuleWrite`, not from `ip rule` keywords; `TestRuleWriteTypedMatchesArgv` +in `internal/goip` keeps it byte for byte what `ip rule add table 10002 priority +30000 ipproto tcp iif lo protocol 242` sends. Unmatched traffic falls through to the normal tables. Main-table routes are never rewritten. These reserved resources must have one owner; every mutation checks ownership and readback, and refuses to overwrite or delete a changed/foreign route. Link, address, route @@ -574,7 +578,7 @@ XTCP_CONTROLLER_TEST_DSN='host=... dbname=... user=... sslmode=...' \ go test ./pkg/treatment/controller/... XTCP_CLICKHOUSE_TEST_BINARY=/path/to/clickhouse \ go test ./pkg/treatment/controller -run ClickHouse -XTCP_TREATMENT_LIVE_TEST=1 go test ./internal/goip -run '^TestTreatmentLiveNamespace$' +XTCP_TREATMENT_LIVE_TEST=1 go test ./internal/goip/actuate -run '^TestTreatmentLiveNamespace$' ``` Every controller handler test runs twice, as `/memory` and diff --git a/internal/goip/actuate/actuate.go b/internal/goip/actuate/actuate.go new file mode 100644 index 000000000..a63d08c1b --- /dev/null +++ b/internal/goip/actuate/actuate.go @@ -0,0 +1,120 @@ +// Package actuate is the treatment route actuator: the one consumer of goip +// that applies a request to a live kernel. It owns a private routing table, +// a private rule priority and a private route protocol, installs host routes +// that copy the baseline path with TCP metrics changed, and refuses anything +// it does not own (docs/tcp-controller.md, "Supported routes and TCP +// semantics"). +// +// Requests are built with xtcpnl's typed RouteWrite and RuleWrite builders, +// never from argv: the CLI's keyword loop in internal/goip is for parity with +// `ip`, and a library that writes to a kernel should not depend on how an +// operator would have spelled the command. The parity between the two is +// internal/goip's TestRuleWriteTypedMatchesArgv. +// +// The import boundary is goip's: xtcpnl, x/sys/unix and the standard library. +// RPC and journal types stay in pkg/treatment; pkg/treatment/local adapts +// them to RouteTreatment. +package actuate + +import ( + "context" + "errors" + "net/netip" + "time" + + "github.com/randomizedcoder/xtcp2/internal/goip" + "github.com/randomizedcoder/xtcp2/internal/goip/service" + "golang.org/x/sys/unix" +) + +// RouteTreatment is the narrow typed input to the live route backend. Keeping +// RPC and journal types outside goip preserves its independent import +// boundary; pkg/treatment/local maps the domain treatment onto it. +type RouteTreatment struct { + Destination string + Settings *TCPRouteSettings +} + +type TCPRouteSettings struct { + CongestionControl *string + CongestionLock bool + RtoMinMs, InitialCongestionWindow, InitialReceiveWindow *uint32 + QuickAck, Ecn *bool +} + +func (s *TCPRouteSettings) GetCongestionControl() string { + if s == nil || s.CongestionControl == nil { + return "" + } + return *s.CongestionControl +} + +// Backend is the explicitly enabled, narrow live-write consumer of goip. +// The CLI and parity write paths stay generation-only; this is the one place +// a request reaches a kernel, through goip.NetlinkSource.ApplyRouteControl. +// Calls are serialized by the treatment journal; every operation owns its own +// bounded netlink socket and cannot interleave with telemetry collection. +type Backend struct { + Table uint32 + Priority uint32 + Protocol uint8 + Enabled bool + Protected []netip.Prefix + routeCache map[uint64][][]byte + ruleCache map[uint8]error +} + +// Cache one consistent read pass. Mutations invalidate their table before +// readback; the next reconciliation refreshes all forwarding/rule state. +func (b *Backend) BeginReconcile() { + b.routeCache = map[uint64][][]byte{} + b.ruleCache = map[uint8]error{} +} + +func (b *Backend) valid() error { + if !b.Enabled { + return errors.New("route actuator disabled") + } + return b.reserved() +} + +// reserved checks the private routing resources alone, so a rule can be +// built (and compared against the kernel's) before the opt-in is examined. +// Table 0 is "no table" on the wire and 1..255 collide with the well-known +// tables; priority 0 is the local rule and 32766+ the main and default +// rules; protocols below 192 are IANA-assigned. +func (b *Backend) reserved() error { + if b.Table < 256 || b.Priority == 0 || b.Priority >= 32766 || b.Protocol < 192 { + return errors.New("reserve a private table >=256, priority 1..32765 and protocol 192..255") + } + return nil +} + +func withSource(ctx context.Context, fn func(*goip.NetlinkSource, *service.Service) error) error { + if err := ctx.Err(); err != nil { + return err + } + s, err := goip.OpenNetlinkTimeout(2 * time.Second) + if err != nil { + return err + } + defer s.Close() + seq := uint32(0) + svc := service.New(s, func() uint32 { seq++; return seq }) + return fn(s, svc) +} + +func (b *Backend) Check(ctx context.Context) error { + if err := b.valid(); err != nil { + return err + } + b.BeginReconcile() + return withSource(ctx, func(_ *goip.NetlinkSource, svc *service.Service) error { + for _, f := range []uint8{unix.AF_INET, unix.AF_INET6} { + if err := b.checkRules(svc, f); err != nil { + return err + } + } + return nil + }) +} diff --git a/internal/goip/treatment_linux_test.go b/internal/goip/actuate/backend_test.go similarity index 77% rename from internal/goip/treatment_linux_test.go rename to internal/goip/actuate/backend_test.go index cda9b52a4..236eb2867 100644 --- a/internal/goip/treatment_linux_test.go +++ b/internal/goip/actuate/backend_test.go @@ -1,4 +1,4 @@ -package goip +package actuate import ( "context" @@ -9,13 +9,13 @@ import ( "golang.org/x/sys/unix" ) -func routeTestPointer[T any](v T) *T { return &v } +func ptr[T any](v T) *T { return &v } func TestTreatmentRoutePreservesPathAndMetrics(t *testing.T) { - b := &TreatmentBackend{Table: 10002, Priority: 30000, Protocol: 242, Enabled: true} + b := &Backend{Table: 10002, Priority: 30000, Protocol: 242, Enabled: true} r := xtcpnl.RouteInfo{Family: unix.AF_INET, Table: unix.RT_TABLE_MAIN, Type: unix.RTN_UNICAST, Oif: 4, Gateway: []byte{192, 0, 2, 254}, PrefSrc: []byte{192, 0, 2, 10}, Metrics: &xtcpnl.RouteMetrics{Present: 1 << unix.RTAX_MTU}} r.Metrics.Values[unix.RTAX_MTU] = 1400 - treatment := &RouteTreatment{Destination: "198.51.100.10/32", Settings: &TCPRouteSettings{CongestionControl: routeTestPointer("cubic"), RtoMinMs: routeTestPointer(uint32(5)), QuickAck: routeTestPointer(true), InitialCongestionWindow: routeTestPointer(uint32(100)), InitialReceiveWindow: routeTestPointer(uint32(100))}} + treatment := &RouteTreatment{Destination: "198.51.100.10/32", Settings: &TCPRouteSettings{CongestionControl: ptr("cubic"), RtoMinMs: ptr(uint32(5)), QuickAck: ptr(true), InitialCongestionWindow: ptr(uint32(100)), InitialReceiveWindow: ptr(uint32(100))}} raw, e := b.planRoute(treatment, r) if e != nil { t.Fatal(e) @@ -41,7 +41,7 @@ func TestTreatmentRoutePreservesPathAndMetrics(t *testing.T) { } } func TestTreatmentRuleOwnershipIncludesSelectors(t *testing.T) { - b := &TreatmentBackend{Table: 10002, Priority: 30000, Protocol: 242, Enabled: true} + b := &Backend{Table: 10002, Priority: 30000, Protocol: 242, Enabled: true} for _, family := range []uint8{unix.AF_INET, unix.AF_INET6} { raw, e := b.rule(family, false) if e != nil { @@ -63,7 +63,7 @@ func TestTreatmentRuleOwnershipIncludesSelectors(t *testing.T) { } } func TestTreatmentActuatorRequiresOptIn(t *testing.T) { - b := &TreatmentBackend{Table: 10002, Priority: 30000, Protocol: 242} + b := &Backend{Table: 10002, Priority: 30000, Protocol: 242} if e := b.Disable(context.Background()); e == nil { t.Fatal("disabled backend opened mutation path") } @@ -72,7 +72,7 @@ func TestTreatmentLiveReadOnly(t *testing.T) { if os.Getenv("XTCP_TREATMENT_NETLINK_READ_TEST") != "1" { t.Skip("optional read-only host topology check") } - b := &TreatmentBackend{Table: 10002, Priority: 30000, Protocol: 242, Enabled: true} + b := &Backend{Table: 10002, Priority: 30000, Protocol: 242, Enabled: true} if e := b.Check(context.Background()); e != nil { t.Fatal(e) } diff --git a/internal/goip/treatment_contracts_test.go b/internal/goip/actuate/contracts_test.go similarity index 68% rename from internal/goip/treatment_contracts_test.go rename to internal/goip/actuate/contracts_test.go index f0e1fbfc8..3cb019081 100644 --- a/internal/goip/treatment_contracts_test.go +++ b/internal/goip/actuate/contracts_test.go @@ -1,4 +1,4 @@ -package goip +package actuate import ( "testing" @@ -7,25 +7,25 @@ import ( "golang.org/x/sys/unix" ) -func TestTreatmentBackendBounds(t *testing.T) { +func TestBackendBounds(t *testing.T) { for _, tc := range []struct { name, description string - change func(*TreatmentBackend) + change func(*Backend) wantError bool }{ - {"valid", "private routing resources are accepted", func(*TreatmentBackend) {}, false}, - {"disabled", "mutation is opt-in", func(b *TreatmentBackend) { b.Enabled = false }, true}, - {"table-low", "main and compatibility tables are excluded", func(b *TreatmentBackend) { b.Table = 255 }, true}, - {"table-min", "first private table is accepted", func(b *TreatmentBackend) { b.Table = 256 }, false}, - {"priority-zero", "local lookup priority is reserved", func(b *TreatmentBackend) { b.Priority = 0 }, true}, - {"priority-min", "first nonlocal priority is valid", func(b *TreatmentBackend) { b.Priority = 1 }, false}, - {"priority-max", "last priority before main is valid", func(b *TreatmentBackend) { b.Priority = 32765 }, false}, - {"priority-main", "main table priority is reserved", func(b *TreatmentBackend) { b.Priority = 32766 }, true}, - {"protocol-low", "well known protocol IDs are excluded", func(b *TreatmentBackend) { b.Protocol = 191 }, true}, - {"protocol-min", "first private protocol is valid", func(b *TreatmentBackend) { b.Protocol = 192 }, false}, + {"valid", "private routing resources are accepted", func(*Backend) {}, false}, + {"disabled", "mutation is opt-in", func(b *Backend) { b.Enabled = false }, true}, + {"table-low", "main and compatibility tables are excluded", func(b *Backend) { b.Table = 255 }, true}, + {"table-min", "first private table is accepted", func(b *Backend) { b.Table = 256 }, false}, + {"priority-zero", "local lookup priority is reserved", func(b *Backend) { b.Priority = 0 }, true}, + {"priority-min", "first nonlocal priority is valid", func(b *Backend) { b.Priority = 1 }, false}, + {"priority-max", "last priority before main is valid", func(b *Backend) { b.Priority = 32765 }, false}, + {"priority-main", "main table priority is reserved", func(b *Backend) { b.Priority = 32766 }, true}, + {"protocol-low", "well known protocol IDs are excluded", func(b *Backend) { b.Protocol = 191 }, true}, + {"protocol-min", "first private protocol is valid", func(b *Backend) { b.Protocol = 192 }, false}, } { t.Run(tc.name, func(t *testing.T) { - b := &TreatmentBackend{Enabled: true, Table: 10002, Priority: 30000, Protocol: 242} + b := &Backend{Enabled: true, Table: 10002, Priority: 30000, Protocol: 242} tc.change(b) err := b.valid() if (err != nil) != tc.wantError { @@ -54,10 +54,10 @@ func TestTreatmentRejectsUnsupportedBaseline(t *testing.T) { {"unknown-metrics", "unknown metrics cannot be discarded", func(r *xtcpnl.RouteInfo) { r.Metrics = &xtcpnl.RouteMetrics{Unknown: 1} }, true}, } { t.Run(tc.name, func(t *testing.T) { - b := &TreatmentBackend{Enabled: true, Table: 10002, Priority: 30000, Protocol: 242} + b := &Backend{Enabled: true, Table: 10002, Priority: 30000, Protocol: 242} r := xtcpnl.RouteInfo{Family: unix.AF_INET, Type: unix.RTN_UNICAST, Oif: 4} tc.change(&r) - _, err := b.planRoute(&RouteTreatment{Destination: "192.0.2.1/32", Settings: &TCPRouteSettings{QuickAck: routeTestPointer(true)}}, r) + _, err := b.planRoute(&RouteTreatment{Destination: "192.0.2.1/32", Settings: &TCPRouteSettings{QuickAck: ptr(true)}}, r) if (err != nil) != tc.wantError { t.Fatalf("%s: err=%v wantError=%t", tc.description, err, tc.wantError) } @@ -72,13 +72,13 @@ func TestTreatmentMetricBooleanOverrides(t *testing.T) { name = "enable" } t.Run(name, func(t *testing.T) { - b := &TreatmentBackend{Enabled: true, Table: 10002, Priority: 30000, Protocol: 242} + b := &Backend{Enabled: true, Table: 10002, Priority: 30000, Protocol: 242} m := &xtcpnl.RouteMetrics{Present: 1< best.DstLen || r.DstLen == best.DstLen && r.Priority < best.Priority { + best = &r + bestRaw = v + ambiguous = false + } else if r.DstLen == best.DstLen && r.Priority == best.Priority { + ambiguous = true + } + } + if best == nil || ambiguous { + return errors.New("no unique baseline route") + } + unsupported := false + if e = xtcpnl.WalkRTAttrs(bestRaw[12:], func(typ uint16, _ []byte) { + switch typ { + case unix.RTA_DST, unix.RTA_GATEWAY, unix.RTA_OIF, unix.RTA_PREFSRC, unix.RTA_PRIORITY, unix.RTA_PREF, unix.RTA_CACHEINFO, unix.RTA_TABLE, unix.RTA_METRICS: + default: + unsupported = true + } + }); e != nil { + return e + } + if unsupported { + return errors.New("baseline route has unsupported attributes") + } + out, e = b.planRoute(t, *best) + return e + }) + return out, err +} + +// planRoute deliberately rejects route forms whose forwarding semantics this +// actuator cannot preserve. It never invents a gateway or alters the main table. +func (b *Backend) planRoute(t *RouteTreatment, r xtcpnl.RouteInfo) ([]byte, error) { + if r.Type != unix.RTN_UNICAST || r.Oif == 0 || r.HasMultipath || r.HasVia || r.NhID != 0 || r.SrcLen != 0 || r.Tos != 0 || r.Flags != 0 || r.HasExpires || r.Metrics != nil && r.Metrics.Unknown != 0 { + return nil, errors.New("unsupported baseline route") + } + ip := netip.MustParsePrefix(t.Destination).Addr() + w := xtcpnl.RouteWrite{MsgType: unix.RTM_NEWROUTE, Flags: unix.NLM_F_ACK | unix.NLM_F_CREATE | unix.NLM_F_EXCL, Family: r.Family, DstLen: uint8(ip.BitLen()), Protocol: b.Protocol, Scope: r.Scope, Type: unix.RTN_UNICAST} + w.Attrs = []xtcpnl.RouteAttr{xtcpnl.RouteAttrBytes(unix.RTA_DST, ip.AsSlice()), xtcpnl.RouteAttrU32(unix.RTA_TABLE, b.Table), xtcpnl.RouteAttrU32(unix.RTA_OIF, r.Oif)} + if len(r.Gateway) > 0 { + w.Attrs = append(w.Attrs, xtcpnl.RouteAttrBytes(unix.RTA_GATEWAY, r.Gateway)) + } + if len(r.PrefSrc) > 0 { + w.Attrs = append(w.Attrs, xtcpnl.RouteAttrBytes(unix.RTA_PREFSRC, r.PrefSrc)) + } + if r.HasPriority { + w.Attrs = append(w.Attrs, xtcpnl.RouteAttrU32(unix.RTA_PRIORITY, r.Priority)) + } + if r.HasPref { + w.Attrs = append(w.Attrs, xtcpnl.RouteAttrBytes(unix.RTA_PREF, []byte{r.Pref})) + } + m := xtcpnl.RouteMetrics{} + if r.Metrics != nil { + m = *r.Metrics + } + set := func(k uint16, v uint32) { m.Present |= 1 << k; m.Values[k] = v } + s := t.Settings + if s.CongestionControl != nil { + m.CcAlgo = *s.CongestionControl + m.Present |= 1 << unix.RTAX_CC_ALGO + lock := m.Values[unix.RTAX_LOCK] &^ (1 << unix.RTAX_CC_ALGO) + if s.CongestionLock { + lock |= 1 << unix.RTAX_CC_ALGO + } + set(unix.RTAX_LOCK, lock) + } + if s.RtoMinMs != nil { + set(unix.RTAX_RTO_MIN, *s.RtoMinMs) + set(unix.RTAX_LOCK, m.Values[unix.RTAX_LOCK]|1< 1 { + return errors.New("missing/duplicate baseline routing rules") + } + if b.ruleCache != nil { + b.ruleCache[family] = nil + } + return nil +} + +func (b *Backend) rules(ctx context.Context, enable bool) error { + if err := b.valid(); err != nil { + return err + } + return withSource(ctx, func(s *goip.NetlinkSource, svc *service.Service) error { + for _, f := range []uint8{unix.AF_INET, unix.AF_INET6} { + if enable { + if err := b.checkRules(svc, f); err != nil { + return err + } + } + rs, err := svc.Rules(f) + if err != nil { + return err + } + owned := 0 + for _, r := range rs { + if b.ownRule(xtcpnl.RuleInfo(r)) { + owned++ + } else if enable && (r.Priority == b.Priority || r.Table == b.Table) { + return errors.New("reserved routing rule collision") + } + } + if enable { + routes, e := b.routeBodies(svc, f, b.Table) + if e != nil { + return e + } + for _, raw := range routes { + r, e := xtcpnl.ParseNewRoute(raw) + if e != nil { + return e + } + if r.Protocol != b.Protocol { + return errors.New("foreign route in reserved treatment table") + } + } + } + if enable && owned == 0 || !enable && owned > 0 { + msg, err := b.rule(f, !enable) + if err != nil { + return err + } + for range max(1, owned) { + if err = s.ApplyRouteControl(msg); err != nil { + return err + } + } + } + rs, err = svc.Rules(f) + if err != nil { + return err + } + owned = 0 + for _, r := range rs { + if b.ownRule(xtcpnl.RuleInfo(r)) { + owned++ + } + } + if enable && owned != 1 || !enable && owned != 0 { + return errors.New("rule readback mismatch") + } + } + return nil + }) +} + +func (b *Backend) Enable(ctx context.Context) error { return b.rules(ctx, true) } + +func (b *Backend) Disable(ctx context.Context) error { return b.rules(ctx, false) } diff --git a/internal/goip/imports_test.go b/internal/goip/imports_test.go index ca5bcea40..98b5dfdc2 100644 --- a/internal/goip/imports_test.go +++ b/internal/goip/imports_test.go @@ -22,6 +22,7 @@ const ( var goipPackages = []string{ modulePath + "/cmd/goip", modulePath + "/internal/goip", + modulePath + "/internal/goip/actuate", modulePath + "/internal/goip/model", modulePath + "/internal/goip/render", modulePath + "/internal/goip/req", @@ -160,6 +161,7 @@ func TestGoipImportSetIsHermetic(t *testing.T) { modulePath + "/internal/goip/req": "the pure request builders", modulePath + "/internal/goip/service": "shared request, decode, filter and ordering application layer", modulePath + "/internal/goip/goiphttp": "the read-only /ip HTTP paths over goip.Show", + modulePath + "/internal/goip/actuate": "the treatment route actuator, the one live-write consumer, on xtcpnl's typed builders", modulePath + "/pkg/xtcpnl": "the subject under test", modulePath + "/pkg/nlparity": "the tolerant walker, for ReplaySource", modulePath + "/gen/go/xtcp_flat_record": "reached through pkg/xtcpnl's inet_diag decoders, which " + diff --git a/internal/goip/route_control.go b/internal/goip/route_control.go new file mode 100644 index 000000000..cc017f1c0 --- /dev/null +++ b/internal/goip/route_control.go @@ -0,0 +1,38 @@ +package goip + +import ( + "encoding/binary" + "errors" + + "github.com/randomizedcoder/xtcp2/pkg/xtcpnl" + "golang.org/x/sys/unix" +) + +// This file is the one path from goip to a live kernel. Every other write +// verb serializes to GOIP_WRITE_OUT (docs/netlink/link-write-path.md, +// decision 5); the treatment actuator in internal/goip/actuate reaches the +// socket only through ApplyRouteControl, which stays here because it needs +// the socket's own descriptor and address. + +// ApplyRouteControl is deliberately separate from the read and CLI byte-output +// paths. Only acknowledged route/rule mutations are admitted here. +func (s *NetlinkSource) ApplyRouteControl(request []byte) error { + if len(request) < 28 { + return errors.New("short route-control request") + } + typ := binary.LittleEndian.Uint16(request[4:6]) + if typ != unix.RTM_NEWROUTE && typ != unix.RTM_DELROUTE && typ != unix.RTM_NEWRULE && typ != unix.RTM_DELRULE { + return errors.New("not a route-control mutation") + } + if binary.LittleEndian.Uint16(request[6:8])&unix.NLM_F_ACK == 0 { + return errors.New("mutation requires acknowledgement") + } + got, _, err := xtcpnl.TalkRtnetlink(s.fd, request, s.sa) + if err != nil { + return err + } + if got != 0 { + return errors.New("unexpected non-ACK mutation response") + } + return nil +} diff --git a/internal/goip/rule_write_typed_test.go b/internal/goip/rule_write_typed_test.go new file mode 100644 index 000000000..18ba0ef41 --- /dev/null +++ b/internal/goip/rule_write_typed_test.go @@ -0,0 +1,97 @@ +package goip + +import ( + "bytes" + "strings" + "testing" + + "github.com/randomizedcoder/xtcp2/pkg/xtcpnl" + "golang.org/x/sys/unix" +) + +// TestRuleWriteTypedMatchesArgv is the claim xtcpnl_rule_write.go makes and +// cannot test alone: a RuleWrite and iprule_modify's argv loop produce the +// same bytes for the same rule. Both encoders are visible here. The argv is +// spelled with `table` first because the typed builder writes FRA_TABLE +// first; the kernel reads the attributes as a set, so internal/goip/actuate +// and `ip rule` agree on the request even though the CLI would let an +// operator type the keywords in another order. +// +// go test ./internal/goip -run TestRuleWriteTypedMatchesArgv +func TestRuleWriteTypedMatchesArgv(t *testing.T) { + const addAck = uint16(unix.NLM_F_CREATE | unix.NLM_F_EXCL | unix.NLM_F_ACK) + treatmentAttrs := []xtcpnl.RuleAttr{ + xtcpnl.RuleAttrU32(unix.FRA_PRIORITY, 30000), xtcpnl.RuleAttrU8(unix.FRA_IP_PROTO, unix.IPPROTO_TCP), + xtcpnl.RuleAttrName(unix.FRA_IIFNAME, "lo"), xtcpnl.RuleAttrU8(unix.FRA_PROTOCOL, 242), + } + tests := []struct { + description string + family uint8 + typed xtcpnl.RuleWrite + argv string + }{ + { + description: "positive: the IPv4 treatment rule add is byte for byte `ip rule add table 10002 priority 30000 ipproto tcp iif lo protocol 242`", + family: unix.AF_INET, + typed: xtcpnl.RuleWrite{MsgType: unix.RTM_NEWRULE, Flags: addAck, Family: unix.AF_INET, Action: unix.FR_ACT_TO_TBL, Table: 10002, Attrs: treatmentAttrs}, + argv: "table 10002 priority 30000 ipproto tcp iif lo protocol 242", + }, + { + description: "positive: the IPv6 treatment rule is the same request under `ip -6`", + family: unix.AF_INET6, + typed: xtcpnl.RuleWrite{MsgType: unix.RTM_NEWRULE, Flags: addAck, Family: unix.AF_INET6, Action: unix.FR_ACT_TO_TBL, Table: 10002, Attrs: treatmentAttrs}, + argv: "table 10002 priority 30000 ipproto tcp iif lo protocol 242", + }, + { + description: "positive: the delete keeps every selector and drops the action and the create flags", + family: unix.AF_INET, + typed: xtcpnl.RuleWrite{MsgType: unix.RTM_DELRULE, Flags: unix.NLM_F_ACK, Family: unix.AF_INET, Action: unix.FR_ACT_UNSPEC, Table: 10002, Attrs: treatmentAttrs}, + argv: "table 10002 priority 30000 ipproto tcp iif lo protocol 242", + }, + { + description: "boundary: table 255 stays in the header byte on both paths", + family: unix.AF_INET, + typed: xtcpnl.RuleWrite{MsgType: unix.RTM_NEWRULE, Flags: addAck, Family: unix.AF_INET, Action: unix.FR_ACT_TO_TBL, Table: 255, Attrs: treatmentAttrs[:1]}, + argv: "table 255 priority 30000", + }, + { + description: "boundary: a /32 destination is FRA_DST with the full length, as `to 192.0.2.1/32`", + family: unix.AF_INET, + typed: xtcpnl.RuleWrite{MsgType: unix.RTM_NEWRULE, Flags: addAck, Family: unix.AF_INET, DstLen: 32, Action: unix.FR_ACT_TO_TBL, Table: 10002, + Attrs: []xtcpnl.RuleAttr{xtcpnl.RuleAttrBytes(unix.FRA_DST, []byte{192, 0, 2, 1})}}, + argv: "table 10002 to 192.0.2.1/32", + }, + { + description: "boundary: a /128 destination is sixteen bytes of FRA_DST, as `to 2001:db8::1/128`", + family: unix.AF_INET6, + typed: xtcpnl.RuleWrite{MsgType: unix.RTM_NEWRULE, Flags: addAck, Family: unix.AF_INET6, DstLen: 128, Action: unix.FR_ACT_TO_TBL, Table: 10002, + Attrs: []xtcpnl.RuleAttr{xtcpnl.RuleAttrBytes(unix.FRA_DST, []byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})}}, + argv: "table 10002 to 2001:db8::1/128", + }, + { + description: "corner: `not` is the header flag on both paths", + family: unix.AF_INET, + typed: xtcpnl.RuleWrite{MsgType: unix.RTM_NEWRULE, Flags: addAck, Family: unix.AF_INET, Action: unix.FR_ACT_TO_TBL, FrhFlags: unix.FIB_RULE_INVERT, Table: 10002, Attrs: treatmentAttrs[:1]}, + argv: "not table 10002 priority 30000", + }, + } + for _, tc := range tests { + t.Run(tc.description, func(t *testing.T) { + want, err := parseRuleWriteArgs(tc.family, tc.typed.MsgType, strings.Fields(tc.argv)) + if err != nil { + t.Fatalf("%s: argv: %v", tc.description, err) + } + expected, err := xtcpnl.BuildWriteRequest(tc.typed.MsgType, tc.typed.Flags, 1, want.frh(), want.attrs.Bytes()) + if err != nil { + t.Fatalf("%s: argv build: %v", tc.description, err) + } + got, err := xtcpnl.BuildRuleWriteRequest(tc.typed, 1) + if err != nil { + t.Fatalf("%s: typed: %v", tc.description, err) + } + if !bytes.Equal(got, expected) { + t.Fatalf("%s:\n typed %x\n argv %x", tc.description, got, expected) + } + }) + } +} diff --git a/internal/goip/treatment_linux.go b/internal/goip/treatment_linux.go deleted file mode 100644 index 119de34bc..000000000 --- a/internal/goip/treatment_linux.go +++ /dev/null @@ -1,570 +0,0 @@ -package goip - -import ( - "bytes" - "context" - "encoding/binary" - "errors" - "fmt" - "net/netip" - "os" - "reflect" - "slices" - "strings" - "time" - - "github.com/randomizedcoder/xtcp2/internal/goip/service" - "github.com/randomizedcoder/xtcp2/pkg/xtcpnl" - "golang.org/x/sys/unix" -) - -// RouteTreatment is the narrow typed input to the live route backend. Keeping -// RPC and journal types outside goip preserves its independent import boundary. -type RouteTreatment struct { - Destination string - Settings *TCPRouteSettings -} -type TCPRouteSettings struct { - CongestionControl *string - CongestionLock bool - RtoMinMs, InitialCongestionWindow, InitialReceiveWindow *uint32 - QuickAck, Ecn *bool -} - -func (s *TCPRouteSettings) GetCongestionControl() string { - if s == nil || s.CongestionControl == nil { - return "" - } - return *s.CongestionControl -} - -// TreatmentBackend is an explicitly enabled, narrow live-write consumer of -// goip. The existing CLI/parity write paths remain generation-only. -// Calls are serialized by the treatment journal; every operation owns its own -// bounded netlink socket and cannot interleave with telemetry collection. -type TreatmentBackend struct { - Table uint32 - Priority uint32 - Protocol uint8 - Enabled bool - Protected []netip.Prefix - routeCache map[uint64][][]byte - ruleCache map[uint8]error -} - -// Cache one consistent read pass. Mutations invalidate their table before -// readback; the next reconciliation refreshes all forwarding/rule state. -func (b *TreatmentBackend) BeginReconcile() { - b.routeCache = map[uint64][][]byte{} - b.ruleCache = map[uint8]error{} -} -func routeCacheKey(f uint8, table uint32) uint64 { return uint64(f)<<32 | uint64(table) } -func (b *TreatmentBackend) routeBodies(svc *service.Service, f uint8, table uint32) ([][]byte, error) { - k := routeCacheKey(f, table) - if v, ok := b.routeCache[k]; ok { - return v, nil - } - v, e := svc.RouteBodies(f, 0, table, 0) - // A private FIB table does not exist until its first route is added. - if table == b.Table && errors.Is(e, unix.ENOENT) { - v = nil - e = nil - } - if e == nil && b.routeCache != nil { - b.routeCache[k] = v - } - return v, e -} - -func (b *TreatmentBackend) valid() error { - if !b.Enabled { - return errors.New("route actuator disabled") - } - if b.Table < 256 || b.Priority == 0 || b.Priority >= 32766 || b.Protocol < 192 { - return errors.New("reserve a private table >=256, priority 1..32765 and protocol 192..255") - } - return nil -} - -func withTreatmentSource(ctx context.Context, fn func(*NetlinkSource, *service.Service) error) error { - if err := ctx.Err(); err != nil { - return err - } - s, err := OpenNetlinkTimeout(2 * time.Second) - if err != nil { - return err - } - defer s.Close() - seq := uint32(0) - svc := service.New(s, func() uint32 { seq++; return seq }) - return fn(s, svc) -} - -func (b *TreatmentBackend) rule(family uint8, del bool) ([]byte, error) { - args := []string{"priority", fmt.Sprint(b.Priority), "table", fmt.Sprint(b.Table), "ipproto", "tcp", "iif", "lo", "protocol", fmt.Sprint(b.Protocol)} - typ := uint16(unix.RTM_NEWRULE) - flags := uint16(unix.NLM_F_ACK | unix.NLM_F_CREATE | unix.NLM_F_EXCL) - if del { - typ = unix.RTM_DELRULE - flags = unix.NLM_F_ACK - } - w, err := parseRuleWriteArgs(family, typ, args) - if err != nil { - return nil, err - } - return xtcpnl.BuildWriteRequest(typ, flags, 1, w.frh(), w.attrs.Bytes()) -} - -func (b *TreatmentBackend) ownRule(r xtcpnl.RuleInfo) bool { - raw, err := b.rule(r.Family, false) - if err != nil { - return false - } - want, err := xtcpnl.ParseRule(raw[16:]) - clearDefaultSuppressors(&r) - clearDefaultSuppressors(&want) - // FRA_TABLE is authoritative; Linux emits RT_TABLE_COMPAT (252) in - // the legacy byte for a table ID that cannot fit there. - r.RawTable = 0 - want.RawTable = 0 - return err == nil && reflect.DeepEqual(r, want) -} - -func clearDefaultSuppressors(r *xtcpnl.RuleInfo) { - if r.HasSuppressPrefixlen && r.SuppressPrefixlen == ^uint32(0) { - r.HasSuppressPrefixlen = false - r.SuppressPrefixlen = 0 - } - if r.HasSuppressIfgroup && r.SuppressIfgroup == ^uint32(0) { - r.HasSuppressIfgroup = false - r.SuppressIfgroup = 0 - } -} - -func normalRule(r xtcpnl.RuleInfo) bool { - if !(r.Priority == 0 && r.Table == unix.RT_TABLE_LOCAL || r.Priority == 32766 && r.Table == unix.RT_TABLE_MAIN || r.Priority == 32767 && r.Table == unix.RT_TABLE_DEFAULT) { - return false - } - if r.Action != unix.FR_ACT_TO_TBL { - return false - } - clearDefaultSuppressors(&r) - // Kernel-generated default rules may carry FRA_PROTOCOL=kernel. - r.Family = 0 - r.Table = 0 - r.RawTable = 0 - r.Priority = 0 - r.HasPriority = false - r.Action = 0 - r.Protocol = 0 - r.HasProtocol = false - return reflect.DeepEqual(r, xtcpnl.RuleInfo{}) -} - -func (b *TreatmentBackend) checkRules(svc *service.Service, family uint8) error { - if e, ok := b.ruleCache[family]; ok { - return e - } - rs, err := svc.Rules(family) - if err != nil { - return err - } - local, main := false, false - owned := 0 - for _, v := range rs { - r := xtcpnl.RuleInfo(v) - if b.ownRule(r) { - owned++ - continue - } - if !normalRule(r) { - return errors.New("unsupported policy routing or reserved rule collision") - } - local = local || r.Table == unix.RT_TABLE_LOCAL - main = main || r.Table == unix.RT_TABLE_MAIN - } - if !local || !main || owned > 1 { - return errors.New("missing/duplicate baseline routing rules") - } - if b.ruleCache != nil { - b.ruleCache[family] = nil - } - return nil -} - -func (b *TreatmentBackend) Check(ctx context.Context) error { - if err := b.valid(); err != nil { - return err - } - b.BeginReconcile() - return withTreatmentSource(ctx, func(_ *NetlinkSource, svc *service.Service) error { - for _, f := range []uint8{unix.AF_INET, unix.AF_INET6} { - if err := b.checkRules(svc, f); err != nil { - return err - } - } - return nil - }) -} - -func (b *TreatmentBackend) Plan(ctx context.Context, t *RouteTreatment) (out []byte, err error) { - if err = b.valid(); err != nil { - return nil, err - } - if t == nil || t.Settings == nil { - return nil, errors.New("missing route settings") - } - p, err := netip.ParsePrefix(t.Destination) - if err != nil || p != p.Masked() || p.Bits() != p.Addr().BitLen() || p.Addr().Is4In6() || !p.Addr().IsGlobalUnicast() || p.Addr().IsLoopback() { - return nil, errors.New("expected canonical unicast host prefix") - } - ip := p.Addr() - for _, x := range b.Protected { - if x.Contains(ip) { - return nil, errors.New("protected destination") - } - } - if c := t.Settings.GetCongestionControl(); c != "" { - available, e := os.ReadFile("/proc/sys/net/ipv4/tcp_available_congestion_control") - if e != nil { - return nil, e - } - if !slices.Contains(strings.Fields(string(available)), c) { - return nil, fmt.Errorf("congestion algorithm %q not loaded", c) - } - } - family := uint8(unix.AF_INET) - if ip.Is6() { - family = unix.AF_INET6 - } - err = withTreatmentSource(ctx, func(_ *NetlinkSource, svc *service.Service) error { - if e := b.checkRules(svc, family); e != nil { - return e - } - locals, e := b.routeBodies(svc, family, unix.RT_TABLE_LOCAL) - if e != nil { - return e - } - for _, raw := range locals { - r, e := xtcpnl.ParseNewRoute(raw) - if e != nil { - return e - } - if a, ok := netip.AddrFromSlice(r.Dst); ok && netip.PrefixFrom(a, int(r.DstLen)).Contains(ip) { - return errors.New("destination is local/broadcast") - } - } - routes, e := b.routeBodies(svc, family, unix.RT_TABLE_MAIN) - if e != nil { - return e - } - var best *xtcpnl.RouteInfo - var bestRaw []byte - ambiguous := false - for _, v := range routes { - r, e := xtcpnl.ParseNewRoute(v) - if e != nil { - return e - } - a, ok := netip.AddrFromSlice(r.Dst) - if r.DstLen == 0 { - if family == unix.AF_INET { - a = netip.IPv4Unspecified() - } else { - a = netip.IPv6Unspecified() - } - ok = true - } - if !ok || !netip.PrefixFrom(a, int(r.DstLen)).Contains(ip) { - continue - } - if best == nil || r.DstLen > best.DstLen || r.DstLen == best.DstLen && r.Priority < best.Priority { - best = &r - bestRaw = v - ambiguous = false - } else if r.DstLen == best.DstLen && r.Priority == best.Priority { - ambiguous = true - } - } - if best == nil || ambiguous { - return errors.New("no unique baseline route") - } - unsupported := false - if e = xtcpnl.WalkRTAttrs(bestRaw[12:], func(typ uint16, _ []byte) { - switch typ { - case unix.RTA_DST, unix.RTA_GATEWAY, unix.RTA_OIF, unix.RTA_PREFSRC, unix.RTA_PRIORITY, unix.RTA_PREF, unix.RTA_CACHEINFO, unix.RTA_TABLE, unix.RTA_METRICS: - default: - unsupported = true - } - }); e != nil { - return e - } - if unsupported { - return errors.New("baseline route has unsupported attributes") - } - out, e = b.planRoute(t, *best) - return e - }) - return out, err -} - -// planRoute deliberately rejects route forms whose forwarding semantics this -// actuator cannot preserve. It never invents a gateway or alters the main table. -func (b *TreatmentBackend) planRoute(t *RouteTreatment, r xtcpnl.RouteInfo) ([]byte, error) { - if r.Type != unix.RTN_UNICAST || r.Oif == 0 || r.HasMultipath || r.HasVia || r.NhID != 0 || r.SrcLen != 0 || r.Tos != 0 || r.Flags != 0 || r.HasExpires || r.Metrics != nil && r.Metrics.Unknown != 0 { - return nil, errors.New("unsupported baseline route") - } - ip := netip.MustParsePrefix(t.Destination).Addr() - w := xtcpnl.RouteWrite{MsgType: unix.RTM_NEWROUTE, Flags: unix.NLM_F_ACK | unix.NLM_F_CREATE | unix.NLM_F_EXCL, Family: r.Family, DstLen: uint8(ip.BitLen()), Protocol: b.Protocol, Scope: r.Scope, Type: unix.RTN_UNICAST} - w.Attrs = []xtcpnl.RouteAttr{xtcpnl.RouteAttrBytes(unix.RTA_DST, ip.AsSlice()), xtcpnl.RouteAttrU32(unix.RTA_TABLE, b.Table), xtcpnl.RouteAttrU32(unix.RTA_OIF, r.Oif)} - if len(r.Gateway) > 0 { - w.Attrs = append(w.Attrs, xtcpnl.RouteAttrBytes(unix.RTA_GATEWAY, r.Gateway)) - } - if len(r.PrefSrc) > 0 { - w.Attrs = append(w.Attrs, xtcpnl.RouteAttrBytes(unix.RTA_PREFSRC, r.PrefSrc)) - } - if r.HasPriority { - w.Attrs = append(w.Attrs, xtcpnl.RouteAttrU32(unix.RTA_PRIORITY, r.Priority)) - } - if r.HasPref { - w.Attrs = append(w.Attrs, xtcpnl.RouteAttrBytes(unix.RTA_PREF, []byte{r.Pref})) - } - m := xtcpnl.RouteMetrics{} - if r.Metrics != nil { - m = *r.Metrics - } - set := func(k uint16, v uint32) { m.Present |= 1 << k; m.Values[k] = v } - s := t.Settings - if s.CongestionControl != nil { - m.CcAlgo = *s.CongestionControl - m.Present |= 1 << unix.RTAX_CC_ALGO - lock := m.Values[unix.RTAX_LOCK] &^ (1 << unix.RTAX_CC_ALGO) - if s.CongestionLock { - lock |= 1 << unix.RTAX_CC_ALGO - } - set(unix.RTAX_LOCK, lock) - } - if s.RtoMinMs != nil { - set(unix.RTAX_RTO_MIN, *s.RtoMinMs) - set(unix.RTAX_LOCK, m.Values[unix.RTAX_LOCK]|1< 0 { - msg, err := b.rule(f, !enable) - if err != nil { - return err - } - for range max(1, owned) { - if err = s.ApplyRouteControl(msg); err != nil { - return err - } - } - } - rs, err = svc.Rules(f) - if err != nil { - return err - } - owned = 0 - for _, r := range rs { - if b.ownRule(xtcpnl.RuleInfo(r)) { - owned++ - } - } - if enable && owned != 1 || !enable && owned != 0 { - return errors.New("rule readback mismatch") - } - } - return nil - }) -} -func (b *TreatmentBackend) Enable(ctx context.Context) error { return b.rules(ctx, true) } -func (b *TreatmentBackend) Disable(ctx context.Context) error { return b.rules(ctx, false) } diff --git a/nix/tests/tcp-controller/quality.nix b/nix/tests/tcp-controller/quality.nix index 022de9115..9a968fbef 100644 --- a/nix/tests/tcp-controller/quality.nix +++ b/nix/tests/tcp-controller/quality.nix @@ -34,7 +34,7 @@ let grep -q -- '--- PASS: TestPostgresConcurrentReviewFencesReplicas' "$out/tests.log" grep -q -- '--- PASS: TestClickHouseAnalysisCounterDeltas' "$out/tests.log" go test -ldflags=-checklinkname=0 ${if race then "-race -count=3" else "-count=1"} \ - -timeout 5m -v -run '^TestTreatment' ./internal/goip > "$out/routes.log" 2>&1 \ + -timeout 5m -v -run '^TestTreatment' ./internal/goip/actuate > "$out/routes.log" 2>&1 \ || { cat "$out/routes.log"; exit 1; } ${if race then "" else ''go tool cover -func "$out/coverage.out" > "$out/coverage.txt"''} ''; @@ -52,6 +52,6 @@ in go test -ldflags=-checklinkname=0 -run '^$' -bench . -benchmem -benchtime=1s -count=5 \ ./pkg/treatment/... > "$out/benchmarks.txt" 2>&1 || { cat "$out/benchmarks.txt"; exit 1; } go test -ldflags=-checklinkname=0 -run '^$' -bench '^BenchmarkTreatment' -benchmem -benchtime=1s -count=5 \ - ./internal/goip >> "$out/benchmarks.txt" 2>&1 || { cat "$out/benchmarks.txt"; exit 1; } + ./internal/goip/actuate >> "$out/benchmarks.txt" 2>&1 || { cat "$out/benchmarks.txt"; exit 1; } ''; } diff --git a/pkg/treatment/boundaries_test.go b/pkg/treatment/boundaries_test.go index 42bef2fd4..f4abbdce4 100644 --- a/pkg/treatment/boundaries_test.go +++ b/pkg/treatment/boundaries_test.go @@ -115,11 +115,11 @@ func treatmentBoundaryRules() []boundaryRule { expectAny: []string{tp("pkg/treatment"), tp("gen/go/xtcp_treatment_journal")}, }, { - description: "positive: the local package builds the kernel-backed reconciler without TLS or the RPC proto", + description: "positive: the local package builds the kernel-backed reconciler on the goip actuator, without TLS or the RPC proto", root: tp("pkg/treatment/local"), forbidden: []string{tp("pkg/treatment/identity"), tp("pkg/treatment/agent"), tp("pkg/treatment/attribution"), tp("pkg/treatment/controller"), tp("pkg/treatment/experiment"), tp("pkg/treatment/wire"), tp("gen/go/xtcp_treatment")}, forbidden3p: []string{"database/sql", "google.golang.org/grpc/credentials"}, - expectAny: []string{tp("pkg/treatment/reconcile"), tp("internal/goip")}, + expectAny: []string{tp("pkg/treatment/reconcile"), tp("internal/goip/actuate"), tp("internal/goip")}, }, { description: "positive: attribution reads journal snapshots and the flat record; never identity, goip, the agent or the RPC proto", diff --git a/pkg/treatment/local/host.go b/pkg/treatment/local/host.go index b68afe955..d1dcd7832 100644 --- a/pkg/treatment/local/host.go +++ b/pkg/treatment/local/host.go @@ -7,7 +7,7 @@ import ( "os" "strings" - "github.com/randomizedcoder/xtcp2/internal/goip" + "github.com/randomizedcoder/xtcp2/internal/goip/actuate" "github.com/randomizedcoder/xtcp2/pkg/treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/reconcile" "golang.org/x/sys/unix" @@ -65,7 +65,7 @@ func New(c Config, id *Identity) (*Host, error) { } id = &v } - b := &routeBackend{TreatmentBackend: &goip.TreatmentBackend{Table: c.Table, Priority: c.RulePriority, Protocol: c.Protocol, Enabled: c.EnableApply}} + b := &routeBackend{Backend: &actuate.Backend{Table: c.Table, Priority: c.RulePriority, Protocol: c.Protocol, Enabled: c.EnableApply}} for _, v := range c.ProtectedPrefixes { p, err := netip.ParsePrefix(v) if err != nil { @@ -92,14 +92,14 @@ func (h *Host) SimpleHostRouting(ctx context.Context) bool { return h.backend.Ch // routeBackend adapts goip's treatment backend to the reconcile.Backend // contract: it validates the domain treatment and maps it onto goip's own // route type, so goip never imports the treatment packages. -type routeBackend struct{ *goip.TreatmentBackend } +type routeBackend struct{ *actuate.Backend } func (b *routeBackend) Plan(ctx context.Context, t *treatment.Treatment) ([]byte, error) { if err := treatment.ValidateTreatment(t); err != nil { return nil, err } s := t.Settings - return b.TreatmentBackend.Plan(ctx, &goip.RouteTreatment{Destination: t.Destination.String(), Settings: &goip.TCPRouteSettings{ + return b.Backend.Plan(ctx, &actuate.RouteTreatment{Destination: t.Destination.String(), Settings: &actuate.TCPRouteSettings{ CongestionControl: s.CongestionControl, CongestionLock: s.CongestionLock, RtoMinMs: s.RTOMinMs, InitialCongestionWindow: s.InitialCongestionWindow, InitialReceiveWindow: s.InitialReceiveWindow, QuickAck: s.QuickAck, Ecn: s.ECN, diff --git a/pkg/xtcpnl/xtcpnl_rule_write.go b/pkg/xtcpnl/xtcpnl_rule_write.go new file mode 100644 index 000000000..3007198b5 --- /dev/null +++ b/pkg/xtcpnl/xtcpnl_rule_write.go @@ -0,0 +1,129 @@ +package xtcpnl + +import ( + "errors" + + "golang.org/x/sys/unix" +) + +// This file encodes the request `ip rule add/delete` sends: the fib_rule_hdr +// and the FRA_* attributes. It is RouteWrite's counterpart for the routing +// policy database, so a library caller builds a rule from fields rather than +// from argv (internal/goip/actuate is the first; the CLI path in +// internal/goip/rule_write.go keeps parsing argv in iprule_modify's order). +// +// It encodes; it does not parse argv. iprule_modify (ip/iprule.c) writes each +// attribute as its keyword is read, so the order is argv order and that is +// internal/goip's business. A RuleWrite carries the attributes as an ordered +// list and this file writes them as given, after the one attribute the header +// forces: fib_rule_hdr.table is eight bits, so a table id past 255 travels as +// RT_TABLE_UNSPEC in the header and the real id in FRA_TABLE, which is +// written first. The argv `table N` keyword does the same at its own +// position, so `ip rule add table N …` and a RuleWrite with Table N agree +// byte for byte. +// +// Nothing here sends. BuildRuleWriteRequest goes through BuildWriteRequest, +// and goip writes the result to a file (link-write-path.md, decision 5) or, +// for the one opted-in actuator, to an acknowledged socket. + +// ruleWriteBufCst is iprule_modify's char buf[1024], the room after the +// nlmsghdr and fib_rule_hdr that addattr_l is given as maxlen. +const ruleWriteBufCst = 1024 + +// ErrRuleWriteType is a RuleWrite whose MsgType is not RTM_NEWRULE or +// RTM_DELRULE. BuildWriteRequest would admit route and link types too; a +// rule write must not carry one. +var ErrRuleWriteType = errors.New("xtcpnl: a rule write is RTM_NEWRULE or RTM_DELRULE") + +// RuleAttr is one FRA_* attribute of a rule write, payload already encoded. +type RuleAttr struct { + Type uint16 + Payload []byte +} + +// RuleAttrU8 is addattr8: FRA_IP_PROTO, FRA_PROTOCOL, FRA_L3MDEV. +func RuleAttrU8(atype uint16, v uint8) RuleAttr { + return RuleAttr{Type: atype, Payload: []byte{v}} +} + +// RuleAttrU32 is addattr32: FRA_PRIORITY, FRA_FWMARK, FRA_FWMASK, FRA_GOTO, +// FRA_SUPPRESS_PREFIXLEN. FRA_TABLE is RuleWrite.Table's business. +func RuleAttrU32(atype uint16, v uint32) RuleAttr { + return RuleAttr{Type: atype, Payload: RouteAttrU32(atype, v).Payload} +} + +// RuleAttrName is addattr_l of a NUL-terminated interface name: FRA_IIFNAME +// and FRA_OIFNAME. iprule_modify sends strlen+1, so the NUL is counted in +// rta_len and the padding after it is not. +func RuleAttrName(atype uint16, name string) RuleAttr { + return RuleAttr{Type: atype, Payload: append([]byte(name), 0)} +} + +// RuleAttrBytes is addattr_l of raw bytes: FRA_DST and FRA_SRC (the address +// bytes), FRA_UID_RANGE and the port ranges (their structs). +func RuleAttrBytes(atype uint16, b []byte) RuleAttr { + return RuleAttr{Type: atype, Payload: b} +} + +// RuleWrite is one rule write as iprule_modify assembles it. +type RuleWrite struct { + // MsgType is RTM_NEWRULE or RTM_DELRULE. + MsgType uint16 + // Flags are the NLM_F_* bits besides NLM_F_REQUEST: NLM_F_CREATE| + // NLM_F_EXCL for add, nothing for delete, and NLM_F_ACK, which + // rtnl_talk adds. + Flags uint16 + + // The struct fib_rule_hdr, field for field, except Table. + Family uint8 + DstLen uint8 + SrcLen uint8 + Tos uint8 + // Action is FR_ACT_TO_TBL for an add and FR_ACT_UNSPEC for a delete + // (iprule_modify sets it from the verb before the loop). + Action uint8 + // FrhFlags is fib_rule_hdr.flags: FIB_RULE_INVERT for `not`. + FrhFlags uint32 + + // Table is the routing table id. Zero sends none (a delete that does + // not select on table); 1..255 is the header byte; above that the + // header carries RT_TABLE_UNSPEC and FRA_TABLE carries the id. + Table uint32 + + // Attrs are the selectors in wire order, after FRA_TABLE when there + // is one. + Attrs []RuleAttr +} + +// EncodeFibRuleHdr lays out a struct fib_rule_hdr: family, dst_len, +// src_len, tos, table, two reserved bytes, action, flags. +func EncodeFibRuleHdr(w *RuleWrite) []byte { + table := uint8(unix.RT_TABLE_UNSPEC) + if w.Table < 256 { + table = uint8(w.Table) + } + b := make([]byte, 0, FibRuleHdrSizeCst) + b = append(b, w.Family, w.DstLen, w.SrcLen, w.Tos, table, 0, 0, w.Action) + return append(b, byte(w.FrhFlags), byte(w.FrhFlags>>8), byte(w.FrhFlags>>16), byte(w.FrhFlags>>24)) +} + +// BuildRuleWriteRequest lays out w: the fib_rule_hdr, FRA_TABLE when the id +// does not fit the header, then w.Attrs in order. +func BuildRuleWriteRequest(w RuleWrite, seq uint32) ([]byte, error) { + if w.MsgType != uint16(unix.RTM_NEWRULE) && w.MsgType != uint16(unix.RTM_DELRULE) { + return nil, ErrRuleWriteType + } + var raw [ruleWriteBufCst]byte + ab := NewAttrBuilder(raw[:]) + if w.Table >= 256 { + if err := ab.PutU32(uint16(unix.FRA_TABLE), w.Table); err != nil { + return nil, err + } + } + for _, a := range w.Attrs { + if err := ab.PutBytes(a.Type, a.Payload); err != nil { + return nil, err + } + } + return BuildWriteRequest(w.MsgType, w.Flags, seq, EncodeFibRuleHdr(&w), ab.Bytes()) +} diff --git a/pkg/xtcpnl/xtcpnl_rule_write_test.go b/pkg/xtcpnl/xtcpnl_rule_write_test.go new file mode 100644 index 000000000..7ea66a12f --- /dev/null +++ b/pkg/xtcpnl/xtcpnl_rule_write_test.go @@ -0,0 +1,175 @@ +package xtcpnl + +import ( + "bytes" + "errors" + "reflect" + "testing" + + "golang.org/x/sys/unix" +) + +// This file tests xtcpnl_rule_write.go, the typed counterpart of the argv +// path in internal/goip/rule_write.go. The hex is written out by hand from +// the layout rules of iprule_modify and lib/libnetlink.c, not produced by +// any encoder; ParseRule is the independent reader for the rest. The +// typed-equals-argv claim is internal/goip's test +// (TestRuleWriteTypedMatchesArgv), where both encoders are visible. + +// TestBuildRuleWriteRequest is the fib_rule_hdr, FRA_TABLE's placement and +// the plain attributes. +// +// go test ./pkg/xtcpnl/ -run TestBuildRuleWriteRequest +func TestBuildRuleWriteRequest(t *testing.T) { + const addAck = uint16(unix.NLM_F_CREATE | unix.NLM_F_EXCL | unix.NLM_F_ACK) + v6dst := []byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} + treatment := func(msgType, flags uint16, family, action uint8) RuleWrite { + return RuleWrite{ + MsgType: msgType, Flags: flags, Family: family, Action: action, Table: 10002, + Attrs: []RuleAttr{ + RuleAttrU32(unix.FRA_PRIORITY, 30000), RuleAttrU8(unix.FRA_IP_PROTO, unix.IPPROTO_TCP), + RuleAttrName(unix.FRA_IIFNAME, "lo"), RuleAttrU8(unix.FRA_PROTOCOL, 242), + }, + } + } + // Bytes 16: of the add below, so the delete and family rows can share it. + treatmentBody := `02000000 00000001 00000000 + 0800 0f00 12270000 + 0800 0600 30750000 + 0500 1600 06000000 + 0700 0300 6c6f0000 + 0500 1500 f2000000` + + tests := []struct { + description string + w RuleWrite + expected []byte + wantErr error + }{ + { + description: "positive: the treatment rule, `add table 10002 priority 30000 ipproto tcp iif lo protocol 242`, is FRA_TABLE first and 68 bytes", + w: treatment(unix.RTM_NEWRULE, addAck, unix.AF_INET, unix.FR_ACT_TO_TBL), + expected: hexBytes(t, `44000000 2000 0506 01000000 00000000 `+treatmentBody), + }, + { + description: "positive: the same rule's delete is RTM_DELRULE, NLM_F_ACK alone and FR_ACT_UNSPEC, selectors kept", + w: treatment(unix.RTM_DELRULE, unix.NLM_F_ACK, unix.AF_INET, unix.FR_ACT_UNSPEC), + expected: hexBytes(t, `44000000 2100 0500 01000000 00000000 + 02000000 00000000 00000000 + 0800 0f00 12270000 + 0800 0600 30750000 + 0500 1600 06000000 + 0700 0300 6c6f0000 + 0500 1500 f2000000`), + }, + { + description: "positive: `add to 2001:db8::1/128 table 10002` is AF_INET6, dst_len 128 and a 16-byte FRA_DST after FRA_TABLE", + w: RuleWrite{ + MsgType: unix.RTM_NEWRULE, Flags: addAck, Family: unix.AF_INET6, DstLen: 128, Action: unix.FR_ACT_TO_TBL, Table: 10002, + Attrs: []RuleAttr{RuleAttrBytes(unix.FRA_DST, v6dst)}, + }, + expected: hexBytes(t, `38000000 2000 0506 01000000 00000000 + 0a800000 00000001 00000000 + 0800 0f00 12270000 + 1400 0100 20010db8 00000000 00000000 00000001`), + }, + { + description: "boundary: table 255 is the last id the header byte holds; no FRA_TABLE", + w: RuleWrite{MsgType: unix.RTM_NEWRULE, Flags: addAck, Family: unix.AF_INET, Action: unix.FR_ACT_TO_TBL, Table: 255}, + expected: hexBytes(t, `1c000000 2000 0506 01000000 00000000 02000000 ff000001 00000000`), + }, + { + description: "boundary: table 256 is the first id that moves to FRA_TABLE, the header byte RT_TABLE_UNSPEC", + w: RuleWrite{MsgType: unix.RTM_NEWRULE, Flags: addAck, Family: unix.AF_INET, Action: unix.FR_ACT_TO_TBL, Table: 256}, + expected: hexBytes(t, `24000000 2000 0506 01000000 00000000 02000000 00000001 00000000 0800 0f00 00010000`), + }, + { + description: "boundary: table 0 sends neither byte nor attribute, as `ip rule del pref 7` does", + w: RuleWrite{MsgType: unix.RTM_DELRULE, Flags: unix.NLM_F_ACK, Family: unix.AF_INET, Attrs: []RuleAttr{RuleAttrU32(unix.FRA_PRIORITY, 7)}}, + expected: hexBytes(t, `24000000 2100 0500 01000000 00000000 02000000 00000000 00000000 0800 0600 07000000`), + }, + { + description: "corner: `not` is FIB_RULE_INVERT in fib_rule_hdr.flags, little-endian", + w: RuleWrite{MsgType: unix.RTM_NEWRULE, Flags: addAck, Family: unix.AF_INET, Action: unix.FR_ACT_TO_TBL, Table: 254, FrhFlags: unix.FIB_RULE_INVERT}, + expected: hexBytes(t, `1c000000 2000 0506 01000000 00000000 02000000 fe000001 02000000`), + }, + { + description: "negative: RTM_NEWROUTE is admitted by BuildWriteRequest but is not a rule write", + w: RuleWrite{MsgType: unix.RTM_NEWROUTE, Flags: addAck, Family: unix.AF_INET, Table: 10002}, + wantErr: ErrRuleWriteType, + }, + { + description: "negative: an attribute past iprule_modify's 1024-byte buffer is refused rather than truncated", + w: RuleWrite{MsgType: unix.RTM_NEWRULE, Flags: addAck, Family: unix.AF_INET, Action: unix.FR_ACT_TO_TBL, Table: 254, + Attrs: []RuleAttr{RuleAttrBytes(unix.FRA_DST, make([]byte, ruleWriteBufCst))}}, + wantErr: ErrAttrNoSpace, + }, + } + for _, tc := range tests { + t.Run(tc.description, func(t *testing.T) { + got, err := BuildRuleWriteRequest(tc.w, 1) + if tc.wantErr != nil { + if !errors.Is(err, tc.wantErr) { + t.Fatalf("%s: err=%v, want %v", tc.description, err, tc.wantErr) + } + return + } + if err != nil { + t.Fatalf("%s: %v", tc.description, err) + } + if !bytes.Equal(got, tc.expected) { + t.Fatalf("%s:\n got %x\nwant %x", tc.description, got, tc.expected) + } + }) + } +} + +// TestRuleWriteReadsBack closes the loop through ParseRule: what the builder +// writes, the decoder reports, including the table id above 255 that the +// header byte cannot hold. +// +// go test ./pkg/xtcpnl/ -run TestRuleWriteReadsBack +func TestRuleWriteReadsBack(t *testing.T) { + const addAck = uint16(unix.NLM_F_CREATE | unix.NLM_F_EXCL | unix.NLM_F_ACK) + tests := []struct { + description string + w RuleWrite + expected RuleInfo + }{ + { + description: "positive: the treatment rule reads back as priority 30000, table 10002, tcp from lo, protocol 242", + w: RuleWrite{MsgType: unix.RTM_NEWRULE, Flags: addAck, Family: unix.AF_INET, Action: unix.FR_ACT_TO_TBL, Table: 10002, + Attrs: []RuleAttr{RuleAttrU32(unix.FRA_PRIORITY, 30000), RuleAttrU8(unix.FRA_IP_PROTO, unix.IPPROTO_TCP), RuleAttrName(unix.FRA_IIFNAME, "lo"), RuleAttrU8(unix.FRA_PROTOCOL, 242)}}, + expected: RuleInfo{Family: unix.AF_INET, Action: unix.FR_ACT_TO_TBL, RawTable: unix.RT_TABLE_UNSPEC, Table: 10002, Priority: 30000, HasPriority: true, + IifName: "lo", HasIifName: true, IPProto: unix.IPPROTO_TCP, HasIPProto: true, Protocol: 242, HasProtocol: true}, + }, + { + description: "boundary: a header-byte table reads back from the byte, FRA_TABLE absent", + w: RuleWrite{MsgType: unix.RTM_NEWRULE, Flags: addAck, Family: unix.AF_INET6, Action: unix.FR_ACT_TO_TBL, Table: 200}, + expected: RuleInfo{Family: unix.AF_INET6, Action: unix.FR_ACT_TO_TBL, RawTable: 200, Table: 200}, + }, + { + description: "corner: a /128 destination keeps its full length and all sixteen bytes", + w: RuleWrite{MsgType: unix.RTM_DELRULE, Flags: unix.NLM_F_ACK, Family: unix.AF_INET6, DstLen: 128, + Attrs: []RuleAttr{RuleAttrBytes(unix.FRA_DST, bytes.Repeat([]byte{0xab}, 16))}}, + expected: RuleInfo{Family: unix.AF_INET6, DstLen: 128, Dst: bytes.Repeat([]byte{0xab}, 16)}, + }, + } + for _, tc := range tests { + t.Run(tc.description, func(t *testing.T) { + msg, err := BuildRuleWriteRequest(tc.w, 1) + if err != nil { + t.Fatalf("%s: %v", tc.description, err) + } + got, err := ParseRule(msg[NlMsgHdrSizeCst:]) + if err != nil { + t.Fatalf("%s: %v", tc.description, err) + } + if !ruleInfoEqual(got, tc.expected) { + t.Fatalf("%s:\n got %+v\nwant %+v", tc.description, got, tc.expected) + } + }) + } +} + +func ruleInfoEqual(a, b RuleInfo) bool { return reflect.DeepEqual(a, b) } From 9372e0bfcaba65b4235453ecf940e766ac032549 Mon Sep 17 00:00:00 2001 From: "randomizedcoder dave.seddon.ca@gmail.com" Date: Sun, 25 Oct 2026 20:12:07 -0700 Subject: [PATCH 14/27] fix(treatment): one location rule for the daemon and its agent The agent's location is certificate-bound and evidence filters by it, so the daemon's own -location/LOCATION must agree with it or be absent. Embedded, the daemon now checks right after loading the agent config and before reading any credential: an empty daemon location adopts the agent's (logged once); a set one that differs refuses to start the agent with a message naming both labels, and socket telemetry continues without it. The record stamp no longer silently rewrites a configured label, because the only remaining cases are equal or adopted. Standalone attribution already kept the daemon's label and adopted the journal's when there was none; unchanged. main passes c.Location (after envOverrideLabeling) to startTreatments rather than the raw flag, so LOCATION from the environment counts in both modes. agent.New names both sides when config and certificate disagree. Tests: TestCheckTreatmentLocation drives the rule through envOverrideLabeling (flag, env, equal, differs, env over flag, case); the tagged TestEmbeddedTreatmentAgentLocation shows unset and equal reach the credential error while differs is refused before it. Co-Authored-By: Claude Fable 5.1 --- cmd/xtcp2/treatments.go | 24 +++++++++++-- cmd/xtcp2/treatments_embedded.go | 12 ++++++- cmd/xtcp2/treatments_embedded_test.go | 30 ++++++++++++++++ cmd/xtcp2/treatments_test.go | 49 ++++++++++++++++++++++++++- cmd/xtcp2/xtcp2.go | 3 +- docs/status-tcp-controller.md | 24 +++++++++++-- docs/tcp-controller.md | 16 ++++++--- pkg/treatment/agent/agent.go | 4 +-- 8 files changed, 147 insertions(+), 15 deletions(-) diff --git a/cmd/xtcp2/treatments.go b/cmd/xtcp2/treatments.go index 397f2a246..0618020cd 100644 --- a/cmd/xtcp2/treatments.go +++ b/cmd/xtcp2/treatments.go @@ -25,6 +25,14 @@ import ( // Asking an untagged binary for -treatmentConfig is fatal, like asking a slim // build for an enricher it lacks: a missing build tag is a property of the // artifact, identical on every host, and must not degrade silently. +// +// Location has one rule in both modes. The agent's location is bound to its +// certificate (spiffe://xtcp/agent//) and evidence queries +// filter by it. When this daemon has no -location/LOCATION of its own, its +// records take the agent's, logged once. When it has one and the agent's +// differs, the embedded agent refuses to start, naming both, and the +// attribution watcher keeps the daemon's label and logs the disagreement +// once (pkg/treatment/attribution). Nothing rewrites a label silently. // treatmentFeeder is whichever mode is running. type treatmentFeeder struct { @@ -34,8 +42,9 @@ type treatmentFeeder struct { var activeTreatments atomic.Pointer[treatmentFeeder] -// embeddedTreatmentAgent is nil unless built with -tags treatment. -var embeddedTreatmentAgent func(ctx context.Context, path string) (*treatmentFeeder, func(), error) +// embeddedTreatmentAgent is nil unless built with -tags treatment. location +// is the daemon's own, after -location and LOCATION, empty when it has none. +var embeddedTreatmentAgent func(ctx context.Context, path, location string) (*treatmentFeeder, func(), error) var errTreatmentNotCompiled = errors.New("-treatmentConfig requested but the treatment agent is not compiled into this binary; rebuild with '-tags treatment' (or use a full `xtcp2` Nix attribute), or run xtcp2-treatment-agent beside this daemon and point it at the journal with -treatmentAttribution") @@ -51,6 +60,15 @@ func checkTreatmentFlags(configPath, attributionDir string) error { return nil } +// checkTreatmentLocation applies the location rule to the embedded agent: +// an empty daemon location adopts the agent's; a set one must equal it. +func checkTreatmentLocation(daemon, agent string) error { + if daemon != "" && daemon != agent { + return fmt.Errorf("this daemon's -location/LOCATION is %q but the treatment agent's certificate-bound location is %q; set them equal or drop -location so records take the agent's", daemon, agent) + } + return nil +} + // treatmentStamper is the hook installed on the collector, nil when no mode // is active. func treatmentStamper() func(*flat.XtcpFlatRecord) { @@ -78,7 +96,7 @@ func startTreatments(parent context.Context, configPath, attributionDir, locatio switch { case configPath != "": ctx, cancel := context.WithCancel(parent) - f, stop, err := embeddedTreatmentAgent(ctx, configPath) + f, stop, err := embeddedTreatmentAgent(ctx, configPath, location) if err != nil { cancel() return nil, err diff --git a/cmd/xtcp2/treatments_embedded.go b/cmd/xtcp2/treatments_embedded.go index 42af9726d..8e9b31901 100644 --- a/cmd/xtcp2/treatments_embedded.go +++ b/cmd/xtcp2/treatments_embedded.go @@ -4,6 +4,7 @@ package main import ( "context" + "log" "sync" "github.com/randomizedcoder/xtcp2/pkg/treatment/agent" @@ -15,11 +16,20 @@ import ( // client nor the TLS identity code. func init() { embeddedTreatmentAgent = startEmbeddedTreatmentAgent } -func startEmbeddedTreatmentAgent(ctx context.Context, path string) (*treatmentFeeder, func(), error) { +func startEmbeddedTreatmentAgent(ctx context.Context, path, location string) (*treatmentFeeder, func(), error) { c, err := agent.LoadConfig(path) if err != nil { return nil, nil, err } + // Before any credential is read: a label disagreement is a deployment + // error, not a runtime one, and the message should be the first thing + // in the log. + if err = checkTreatmentLocation(location, c.Location); err != nil { + return nil, nil, err + } + if location == "" { + log.Printf("treatment agent: this daemon has no -location/LOCATION; records take the agent's %q", c.Location) + } a, err := agent.New(ctx, c) if err != nil { return nil, nil, err diff --git a/cmd/xtcp2/treatments_embedded_test.go b/cmd/xtcp2/treatments_embedded_test.go index 210c22093..55c70ab73 100644 --- a/cmd/xtcp2/treatments_embedded_test.go +++ b/cmd/xtcp2/treatments_embedded_test.go @@ -31,3 +31,33 @@ func TestEmbeddedTreatmentAgentRegistered(t *testing.T) { t.Fatal("failed start left a feeder active") } } + +// The embedded agent's location rule runs before any credential is read. +func TestEmbeddedTreatmentAgentLocation(t *testing.T) { + path := filepath.Join(t.TempDir(), "agent.json") + if err := os.WriteFile(path, []byte(`{"agent_id":"host","location":"dc1","controller":"127.0.0.1:1","certificate":"/nonexistent.crt","private_key":"/nonexistent.key","ca":"/nonexistent-ca.crt"}`), 0600); err != nil { + t.Fatal(err) + } + for _, tc := range []struct { + name, description string + daemon string + expected string + }{ + {"unset", "positive: a daemon without a location adopts the agent's and proceeds to credentials", "", "nonexistent"}, + {"equal", "positive: an equal location proceeds to credentials", "dc1", "nonexistent"}, + {"differs", "negative: a different location is refused before any credential is read", "dc2", `"dc2"`}, + } { + t.Run(tc.name, func(t *testing.T) { + stop, err := startTreatments(context.Background(), path, "", tc.daemon) + if err == nil || stop != nil || !strings.Contains(err.Error(), tc.expected) { + t.Fatalf("%s: err=%v, want %q", tc.description, err, tc.expected) + } + if tc.name == "differs" && strings.Contains(err.Error(), "nonexistent") { + t.Fatalf("%s: credentials were read before the location check", tc.description) + } + if activeTreatments.Load() != nil { + t.Fatal("failed start left a feeder active") + } + }) + } +} diff --git a/cmd/xtcp2/treatments_test.go b/cmd/xtcp2/treatments_test.go index b14dd4dc9..8008c58b3 100644 --- a/cmd/xtcp2/treatments_test.go +++ b/cmd/xtcp2/treatments_test.go @@ -10,6 +10,7 @@ import ( "testing" "time" + "github.com/randomizedcoder/xtcp2/gen/go/xtcp_config" flat "github.com/randomizedcoder/xtcp2/gen/go/xtcp_flat_record" "github.com/randomizedcoder/xtcp2/pkg/treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/reconcile" @@ -75,7 +76,7 @@ func TestCheckTreatmentFlags(t *testing.T) { t.Run(tc.name, func(t *testing.T) { embeddedTreatmentAgent = nil if tc.compiled { - embeddedTreatmentAgent = func(context.Context, string) (*treatmentFeeder, func(), error) { return nil, nil, nil } + embeddedTreatmentAgent = func(context.Context, string, string) (*treatmentFeeder, func(), error) { return nil, nil, nil } } err := checkTreatmentFlags(tc.config, tc.dir) if tc.expected == "" { @@ -94,6 +95,52 @@ func TestCheckTreatmentFlags(t *testing.T) { } } +// The location rule for the embedded agent, with the daemon's label resolved +// the way main resolves it: -location first, LOCATION from the environment +// over it. The standalone counterpart (keep the daemon's label, adopt when +// there is none) is pkg/treatment/attribution's TestWatcherRefresh rows. +func TestCheckTreatmentLocation(t *testing.T) { + for _, tc := range []struct { + name, description string + flag, env string + agent string + expected []string // substrings of the error; empty means start + }{ + {"flag-unset", "positive: no -location and no LOCATION adopts the agent's certificate-bound location", "", "", "dc1", nil}, + {"flag-equal", "positive: -location equal to the agent's starts", "dc1", "", "dc1", nil}, + {"env-equal", "positive: LOCATION equal to the agent's starts", "", "dc1", "dc1", nil}, + {"flag-differs", "negative: -location differing from the agent's refuses, naming both", "dc2", "", "dc1", []string{`"dc2"`, `"dc1"`}}, + {"env-differs", "negative: LOCATION differing from the agent's refuses the same way; the flag alone is not what counts", "", "dc2", "dc1", []string{`"dc2"`, `"dc1"`}}, + {"env-over-flag", "corner: LOCATION overrides -location, as it does for every record, so the override is what is compared", "dc1", "dc2", "dc1", []string{`"dc2"`}}, + {"case", "boundary: labels are compared exactly; DC1 is not dc1", "DC1", "", "dc1", []string{`"DC1"`, `"dc1"`}}, + } { + t.Run(tc.name, func(t *testing.T) { + if tc.env != "" { + t.Setenv("LOCATION", tc.env) + } else { + os.Unsetenv("LOCATION") + } + c := &xtcp_config.XtcpConfig{Location: tc.flag} + envOverrideLabeling(c, 0) + err := checkTreatmentLocation(c.Location, tc.agent) + if len(tc.expected) == 0 { + if err != nil { + t.Fatalf("%s: %v", tc.description, err) + } + return + } + if err == nil { + t.Fatalf("%s: expected an error", tc.description) + } + for _, want := range tc.expected { + if !strings.Contains(err.Error(), want) { + t.Fatalf("%s: error %q does not name %s", tc.description, err, want) + } + } + }) + } +} + // Attribution mode installs a stamper fed from the journal directory and // SIGHUP re-reads it; stopping removes the feeder. func TestStartTreatmentsAttributionMode(t *testing.T) { diff --git a/cmd/xtcp2/xtcp2.go b/cmd/xtcp2/xtcp2.go index ecb7e7101..683d886da 100644 --- a/cmd/xtcp2/xtcp2.go +++ b/cmd/xtcp2/xtcp2.go @@ -1139,7 +1139,8 @@ func runMain(ctx context.Context) int { fatalf("%v", err) return 1 } - stopTreatments, err := startTreatments(runCtx, *f.treatmentConfig, *f.treatmentAttribution, *f.location) + // c.Location, not the flag: LOCATION from the environment counts too. + stopTreatments, err := startTreatments(runCtx, *f.treatmentConfig, *f.treatmentAttribution, c.Location) if err != nil { // Controller availability/configuration must not stop socket telemetry. log.Printf("treatment agent unavailable: %v", err) diff --git a/docs/status-tcp-controller.md b/docs/status-tcp-controller.md index 46957d450..e549bac5f 100644 --- a/docs/status-tcp-controller.md +++ b/docs/status-tcp-controller.md @@ -17,8 +17,8 @@ review being addressed. Items are listed in execution order. | 5 | evidence port (`Source`, `CandidateSource`, `Warehouse`), ClickHouse as one adapter | done | caba9bc4 | | 6 | auth as interceptor with per-RPC role option | done | d7fefee4 | | 7 | typed `xtcpnl.RuleWrite`; backend to `internal/goip/actuate` | done | this commit | -| 9 | identity/location startup check | next | | -| 10 | blocking journal lock | pending | | +| 9 | identity/location startup check | done | this commit | +| 10 | blocking journal lock | next | | | 11 | scale: notification watermark, heartbeat split, leader lease | pending | | | 12 | docs: threat model, naming, SIGHUP, philosophy sentence | pending | | @@ -136,6 +136,26 @@ process. package in goip's closure; the Nix harness and docs point at `./internal/goip/actuate`. +## Item 9 notes + +- One rule for both modes, in `cmd/xtcp2/treatments.go`: an empty daemon + location adopts the agent's (logged once); a set one must equal the + agent's certificate-bound location. Embedded, `checkTreatmentLocation` + runs right after `agent.LoadConfig` and before any credential is read, + so a mismatch is the first line in the log and the agent does not start + (telemetry continues). Standalone, the attribution watcher already kept + the daemon's label and adopted the journal's when there was none + (20f3a35a); unchanged. +- main now passes `c.Location` (after `envOverrideLabeling`) to + `startTreatments`, so `LOCATION` from the environment counts in both + modes; it used to pass the raw flag. +- `agent.New` names both sides when config and certificate disagree. +- Tests: `TestCheckTreatmentLocation` (flag/env/equal/differs/override/case, + through `envOverrideLabeling`), tagged + `TestEmbeddedTreatmentAgentLocation` (unset and equal reach the + credential error, differs is refused before it), and the existing + attribution watcher rows for the standalone side. + ## TODO - The watchdog and `pkg/treatment/local` still link gRPC, through diff --git a/docs/tcp-controller.md b/docs/tcp-controller.md index 60e84d2d2..775ba9ffc 100644 --- a/docs/tcp-controller.md +++ b/docs/tcp-controller.md @@ -408,11 +408,17 @@ observed congestion algorithm matches. Embedded, the index is rebuilt after every reconcile pass. Standalone, xtcp2 watches the journal directory (inotify on the atomic rename of `state.textproto`, a 30 s poll as fallback, SIGHUP to re-read now) and rebuilds it from the snapshot; a missing or invalid -snapshot clears attribution rather than stamping stale state. The embedded -agent writes its certificate-bound location onto records. The standalone -arrangement keeps the daemon's own `-location` when one is set and logs once -if the journal disagrees; a daemon started without `-location`/`LOCATION` -adopts the journal's location, since evidence queries filter by it. +snapshot clears attribution rather than stamping stale state. + +Location follows one rule in both arrangements, because the agent's location +is certificate-bound and evidence queries filter by it. A daemon started +without `-location`/`LOCATION` adopts the agent's location for its records and +logs that once. A daemon with its own label keeps it: embedded, an agent whose +location differs refuses to start with a message naming both labels (socket +telemetry continues without the agent); standalone, the watcher keeps the +daemon's label and logs the disagreement once. No label is rewritten silently. +`agent.New` likewise refuses a config whose `location`/`agent_id` differ from +the certificate's URI SAN, naming both. A build without the `treatment` tag rejects `-treatmentConfig` at startup with a message naming both remedies, the same way a slim build rejects an enricher diff --git a/pkg/treatment/agent/agent.go b/pkg/treatment/agent/agent.go index 9b81afc4b..a8e8f14f2 100644 --- a/pkg/treatment/agent/agent.go +++ b/pkg/treatment/agent/agent.go @@ -10,7 +10,7 @@ import ( "crypto/rand" "crypto/x509" "encoding/hex" - "errors" + "fmt" "log" "math/big" "net" @@ -86,7 +86,7 @@ func New(ctx context.Context, c Config) (*Agent, error) { return nil, e } if id.Role != identity.RoleAgent || id.ID != c.AgentID || id.Location != c.Location { - return nil, errors.New("agent config does not match certificate identity") + return nil, fmt.Errorf("agent config names %s/%s but the certificate's URI SAN is %s/%s/%s; location and agent_id are certificate-bound", c.Location, c.AgentID, id.Role, id.Location, id.ID) } a, e := newLocal(c, nil) if e != nil { From 3478d9b34772fa003f550de7558116039618ec87 Mon Sep 17 00:00:00 2001 From: "randomizedcoder dave.seddon.ca@gmail.com" Date: Sun, 25 Oct 2026 20:16:10 -0700 Subject: [PATCH 15/27] fix(treatment): the journal lock waits, bounded by the caller's context Journal.WithLock took the flock with LOCK_NB, so the daemon's reconcile pass, the watchdog's expiry pass and a local suspend/resume failed "journal busy" whenever they met, and the loser skipped its pass; the docs said the lock serialised them. It now waits: non-blocking attempts with a 1 to 50 ms doubling delay (a blocking flock cannot be interrupted by a context), returning ErrJournalBusy wrapping ctx.Err() at the deadline before anything is read or written. Under the lock it sweeps .pending-* temporaries, which only a writer that died mid-write can leave. Reconciler.Run passes its ctx, SuspendLocal takes one, and treatmentctl suspend|resume bounds it at 30 s. status was already lock-free and stays so. Tests: TestJournalLockWaits (uncontended, contended-waits, ctx-deadline, zero-budget), TestJournalLockFreeReadUnderLock, and TestJournalLockHolderDies, which kills a child test process holding the lock with a half-written temp file and checks the lock is free, the snapshot intact and the temp swept. Co-Authored-By: Claude Fable 5.1 --- cmd/xtcp2-treatmentctl/main.go | 6 +- cmd/xtcp2/treatments_test.go | 2 +- docs/status-tcp-controller.md | 20 ++- docs/tcp-controller.md | 12 +- pkg/treatment/attribution/watch_test.go | 2 +- pkg/treatment/reconcile/contracts_test.go | 2 +- pkg/treatment/reconcile/journal.go | 75 +++++++- pkg/treatment/reconcile/journal_test.go | 209 ++++++++++++++++++---- pkg/treatment/reconcile/reconcile.go | 9 +- pkg/treatment/reconcile/reconcile_test.go | 8 +- 10 files changed, 289 insertions(+), 56 deletions(-) diff --git a/cmd/xtcp2-treatmentctl/main.go b/cmd/xtcp2-treatmentctl/main.go index d9b97041b..5dad3e3ae 100644 --- a/cmd/xtcp2-treatmentctl/main.go +++ b/cmd/xtcp2-treatmentctl/main.go @@ -51,7 +51,11 @@ func run() error { _, e = os.Stdout.Write(b) return e case "suspend", "resume": - if e = h.Reconciler.SuspendLocal(*mode == "suspend", *reason); e != nil { + // Waits for a reconcile pass in progress; a watchdog pass is bounded + // at 25 s, so a longer wait means the journal is wedged. + hold, cancelHold := context.WithTimeout(ctx, 30*time.Second) + defer cancelHold() + if e = h.Reconciler.SuspendLocal(hold, *mode == "suspend", *reason); e != nil { return e } case "reconcile": diff --git a/cmd/xtcp2/treatments_test.go b/cmd/xtcp2/treatments_test.go index 8008c58b3..d2c2a52d8 100644 --- a/cmd/xtcp2/treatments_test.go +++ b/cmd/xtcp2/treatments_test.go @@ -150,7 +150,7 @@ func TestStartTreatmentsAttributionMode(t *testing.T) { d := &treatment.DesiredState{AgentID: "host-1", Revision: 1, IssuedAt: now, Assignments: []treatment.Assignment{{ExperimentID: "trial-1", Generation: 1, Mode: treatment.ModeStable, Treatment: treatment.Treatment{ID: "cubic", Revision: 1, Destination: netip.MustParsePrefix("192.0.2.1/32"), Settings: treatment.TCPSettings{CongestionControl: &cubic}}}}} write := func(revision uint64) { d.Revision = revision - err := reconcile.Journal{Dir: dir}.WithLock(func(s *reconcile.State, save func() error) error { + err := reconcile.Journal{Dir: dir}.WithLock(context.Background(), func(s *reconcile.State, save func() error) error { *s = reconcile.State{AgentID: "host-1", Location: "dc1", BootID: "boot-1", Desired: d, Records: []reconcile.Record{{ApplicationRecord: treatment.ApplicationRecord{Assignment: d.Assignments[0], Status: treatment.ApplyVerified, AppliedAt: now}}}} return save() }) diff --git a/docs/status-tcp-controller.md b/docs/status-tcp-controller.md index e549bac5f..ff522a4a3 100644 --- a/docs/status-tcp-controller.md +++ b/docs/status-tcp-controller.md @@ -18,8 +18,8 @@ review being addressed. Items are listed in execution order. | 6 | auth as interceptor with per-RPC role option | done | d7fefee4 | | 7 | typed `xtcpnl.RuleWrite`; backend to `internal/goip/actuate` | done | this commit | | 9 | identity/location startup check | done | this commit | -| 10 | blocking journal lock | next | | -| 11 | scale: notification watermark, heartbeat split, leader lease | pending | | +| 10 | blocking journal lock | done | this commit | +| 11 | scale: notification watermark, heartbeat split, leader lease | next | | | 12 | docs: threat model, naming, SIGHUP, philosophy sentence | pending | | Dual-mode agent (library + `cmd/xtcp2-treatment-agent` + `treatment` build tag @@ -156,6 +156,22 @@ process. credential error, differs is refused before it), and the existing attribution watcher rows for the standalone side. +## Item 10 notes + +- `Journal.WithLock(ctx, fn)` waits for the flock (non-blocking attempts with + a 1 to 50 ms doubling delay, since a blocking flock cannot be interrupted by + a context) and returns `ErrJournalBusy` wrapping `ctx.Err()` at the + deadline, before reading the snapshot. Under the lock it sweeps + `.pending-*` files, which only a writer that died mid-write can leave. + `Reconciler.Run` passes its ctx; `SuspendLocal` gained a ctx and + `treatmentctl suspend|resume` bounds it at 30 s. `status` was already + lock-free and stays so; no `LOCK_NB` entry point is needed. +- Tests: `TestJournalLockWaits` (uncontended, contended-waits, ctx-deadline, + zero-budget), `TestJournalLockFreeReadUnderLock`, and + `TestJournalLockHolderDies`, which kills a child test process holding the + lock with a half-written temp file and checks the lock is free, the + snapshot intact and the temp file swept. + ## TODO - The watchdog and `pkg/treatment/local` still link gRPC, through diff --git a/docs/tcp-controller.md b/docs/tcp-controller.md index 775ba9ffc..5e63d31d4 100644 --- a/docs/tcp-controller.md +++ b/docs/tcp-controller.md @@ -346,9 +346,15 @@ any other shape (including the pre-rework `LocalState` text) fails closed. `assignment-.textproto` files are readable projections containing receipt, attempt, application, verification and removal times. Projection files are never replayed independently. A private directory, file lock, temp-file rename and file -and directory fsync serialize the daemon, watchdog and local commands. Intent is -persisted before kernel mutation; kernel readback precedes success reporting. -Do not hand-edit projections to change desired state. +and directory fsync serialize the daemon, watchdog and local commands. The lock +waits rather than fails: each pass is bounded by its caller's context (15 s for +the agent's check-in, 25 s for a watchdog pass, 30 s for `treatmentctl +suspend`/`resume`), and at the deadline the caller gets "treatment journal busy" +without having read or written anything. A holder that dies releases the lock, +and the next holder sweeps the half-written temporary file it left. +`treatmentctl status` reads the snapshot without the lock and never waits. +Intent is persisted before kernel mutation; kernel readback precedes success +reporting. Do not hand-edit projections to change desired state. Startup, periodic work and SIGHUP reconcile the snapshot. Boot IDs prevent stale kernel fingerprints from being treated as current installations after reboot. diff --git a/pkg/treatment/attribution/watch_test.go b/pkg/treatment/attribution/watch_test.go index 59577973f..524b327cc 100644 --- a/pkg/treatment/attribution/watch_test.go +++ b/pkg/treatment/attribution/watch_test.go @@ -20,7 +20,7 @@ import ( // by atomic rename. func writeJournal(t *testing.T, dir string, s *reconcile.State) { t.Helper() - err := reconcile.Journal{Dir: dir}.WithLock(func(cur *reconcile.State, save func() error) error { + err := reconcile.Journal{Dir: dir}.WithLock(context.Background(), func(cur *reconcile.State, save func() error) error { *cur = *s return save() }) diff --git a/pkg/treatment/reconcile/contracts_test.go b/pkg/treatment/reconcile/contracts_test.go index 49ddda791..ffe39b464 100644 --- a/pkg/treatment/reconcile/contracts_test.go +++ b/pkg/treatment/reconcile/contracts_test.go @@ -105,7 +105,7 @@ func TestReconcileRevisionContracts(t *testing.T) { if b.installs != 1 { t.Fatal("rejected/idempotent state reinstalled route") } - if err = r.Journal.WithLock(func(s *State, _ func() error) error { + if err = r.Journal.WithLock(context.Background(), func(s *State, _ func() error) error { if !s.Desired.Equal(original) { t.Fatal("rejected request changed journal") } diff --git a/pkg/treatment/reconcile/journal.go b/pkg/treatment/reconcile/journal.go index 2980266fe..15128b4a9 100644 --- a/pkg/treatment/reconcile/journal.go +++ b/pkg/treatment/reconcile/journal.go @@ -2,11 +2,14 @@ package reconcile import ( "bytes" + "context" "errors" "fmt" "io" "os" "path/filepath" + "strings" + "time" "github.com/randomizedcoder/xtcp2/pkg/treatment" "golang.org/x/sys/unix" @@ -17,11 +20,32 @@ import ( // readable projections only, never an input to recovery. type Journal struct{ Dir string } +// ErrJournalBusy is returned, wrapping the context's error, when the lock +// was still held by another process or goroutine at the context's deadline. +var ErrJournalBusy = errors.New("treatment journal busy") + +// lockPollMaxCst bounds the retry interval while waiting for the lock. The +// kernel's blocking flock cannot be interrupted by a context, so the wait is +// a non-blocking attempt repeated with a doubling delay from 1 ms to this. +const lockPollMaxCst = 50 * time.Millisecond + +// pendingPrefix names atomicFile's temporary files. One is only ever created +// under the lock, so any found by a new lock holder belongs to a writer that +// died mid-write and is swept before the snapshot is read. +const pendingPrefix = ".pending-" + // WithLock loads the snapshot under the exclusive journal lock, runs fn with // a save callback that persists the current state atomically, and refreshes // the per-assignment projections if anything was saved. A snapshot that // fails validation never reaches fn. -func (j Journal) WithLock(fn func(*State, func() error) error) error { +// +// The lock waits: the daemon's reconcile pass, the watchdog's expiry pass and +// a local suspend/resume each take it in turn rather than one skipping its +// pass because another held it. The wait is bounded by ctx; at its deadline +// WithLock returns ErrJournalBusy wrapping ctx.Err() without having read or +// written anything. `treatmentctl status` reads the snapshot without the lock +// and never waits. +func (j Journal) WithLock(ctx context.Context, fn func(*State, func() error) error) error { if err := os.MkdirAll(j.Dir, 0700); err != nil { return err } @@ -30,10 +54,13 @@ func (j Journal) WithLock(fn func(*State, func() error) error) error { return err } defer unix.Close(fd) - if err = unix.Flock(fd, unix.LOCK_EX|unix.LOCK_NB); err != nil { - return fmt.Errorf("treatment journal busy: %w", err) + if err = lock(ctx, fd); err != nil { + return err } defer unix.Flock(fd, unix.LOCK_UN) + if err = j.sweepPending(); err != nil { + return err + } s := new(State) f, err := os.OpenFile(filepath.Join(j.Dir, SnapshotFile), os.O_RDONLY|unix.O_NOFOLLOW, 0) if err == nil { @@ -87,6 +114,46 @@ func (j Journal) WithLock(fn func(*State, func() error) error) error { return result } +// lock takes the exclusive flock, waiting until ctx is done. +func lock(ctx context.Context, fd int) error { + delay := time.Millisecond + for { + err := unix.Flock(fd, unix.LOCK_EX|unix.LOCK_NB) + if err == nil { + return nil + } + if !errors.Is(err, unix.EWOULDBLOCK) { + return err + } + timer := time.NewTimer(delay) + select { + case <-ctx.Done(): + timer.Stop() + return fmt.Errorf("%w: %w", ErrJournalBusy, ctx.Err()) + case <-timer.C: + } + delay = min(2*delay, lockPollMaxCst) + } +} + +// sweepPending removes temporary files left by a writer that died between +// CreateTemp and Rename. The caller holds the lock, so none is in use. +func (j Journal) sweepPending() error { + entries, err := os.ReadDir(j.Dir) + if err != nil { + return err + } + for _, e := range entries { + if !strings.HasPrefix(e.Name(), pendingPrefix) { + continue + } + if err = os.Remove(filepath.Join(j.Dir, e.Name())); err != nil && !errors.Is(err, os.ErrNotExist) { + return err + } + } + return nil +} + func readLimited(f *os.File) ([]byte, error) { b, err := io.ReadAll(io.LimitReader(f, treatment.MaxStateBytes+1)) if err != nil { @@ -99,7 +166,7 @@ func readLimited(f *os.File) ([]byte, error) { } func atomicFile(dir, name string, b []byte) error { - f, err := os.CreateTemp(dir, ".pending-") + f, err := os.CreateTemp(dir, pendingPrefix) if err != nil { return err } diff --git a/pkg/treatment/reconcile/journal_test.go b/pkg/treatment/reconcile/journal_test.go index bd7461cd1..ec3a0a455 100644 --- a/pkg/treatment/reconcile/journal_test.go +++ b/pkg/treatment/reconcile/journal_test.go @@ -1,8 +1,12 @@ package reconcile import ( + "context" "errors" + "fmt" + "io" "os" + "os/exec" "path/filepath" "strings" "testing" @@ -11,7 +15,6 @@ import ( jpb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment_journal" "github.com/randomizedcoder/xtcp2/pkg/treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" - "golang.org/x/sys/unix" "google.golang.org/protobuf/encoding/prototext" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -65,7 +68,7 @@ func TestJournalRejectsInvalidSnapshots(t *testing.T) { t.Fatal(err) } called := false - err := j.WithLock(func(*State, func() error) error { called = true; return nil }) + err := j.WithLock(context.Background(), func(*State, func() error) error { called = true; return nil }) if tc.want == "" { if err != nil || !called { t.Fatalf("%s: %v", tc.description, err) @@ -82,51 +85,187 @@ func TestJournalRejectsInvalidSnapshots(t *testing.T) { } } -func TestJournalConcurrentExclusion(t *testing.T) { - j := Journal{Dir: t.TempDir()} - entered, release, done := make(chan struct{}), make(chan struct{}), make(chan error, 1) - go func() { - done <- j.WithLock(func(s *State, save func() error) error { - close(entered) - <-release - s.SuspensionReason = "first writer" - return save() +// TestJournalLockWaits is the lock's contract: a free lock is taken at once, +// a held one is waited for, and the wait ends at the context's deadline with +// ErrJournalBusy and nothing read or written. +func TestJournalLockWaits(t *testing.T) { + for _, tc := range []struct { + name, description string + hold time.Duration // how long a competing holder keeps the lock; 0 is none + timeout time.Duration + wantBusy bool + }{ + {"uncontended", "positive: a free lock runs the callback immediately", 0, time.Second, false}, + {"contended-waits", "positive: a lock held 100 ms is waited for within a 1 s budget, then the callback runs", 100 * time.Millisecond, 5 * time.Second, false}, + {"ctx-deadline", "negative: a lock held past a 150 ms budget returns ErrJournalBusy wrapping the deadline, callback never called", 400 * time.Millisecond, 150 * time.Millisecond, true}, + {"zero-budget", "boundary: an already expired context fails busy without touching the journal when the lock is held", 400 * time.Millisecond, 0, true}, + } { + t.Run(tc.name, func(t *testing.T) { + j := Journal{Dir: t.TempDir()} + if err := j.WithLock(context.Background(), func(s *State, save func() error) error { + s.SuspensionReason = "first writer" + return save() + }); err != nil { + t.Fatal(err) + } + held, released := make(chan struct{}), make(chan struct{}) + if tc.hold > 0 { + go func() { + defer close(released) + _ = j.WithLock(context.Background(), func(*State, func() error) error { + close(held) + time.Sleep(tc.hold) + return nil + }) + }() + <-held + } else { + close(released) + } + ctx, cancel := context.WithTimeout(context.Background(), tc.timeout) + defer cancel() + called := false + start := time.Now() + err := j.WithLock(ctx, func(s *State, save func() error) error { + called = true + s.SuspensionReason = "second writer" + return save() + }) + waited := time.Since(start) + <-released + if tc.wantBusy { + if !errors.Is(err, ErrJournalBusy) || !errors.Is(err, context.DeadlineExceeded) || called { + t.Fatalf("%s: err=%v called=%t", tc.description, err, called) + } + } else if err != nil || !called { + t.Fatalf("%s: err=%v called=%t", tc.description, err, called) + } + if tc.hold > 0 && !tc.wantBusy && waited < tc.hold/2 { + t.Fatalf("%s: ran after %v without waiting for the holder", tc.description, waited) + } + want := "second writer" + if tc.wantBusy { + want = "first writer" + } + s, err := ReadSnapshot(j.Dir) + if err != nil || s.SuspensionReason != want { + t.Fatalf("%s: snapshot %+v err=%v, want reason %q", tc.description, s, err, want) + } }) - }() - <-entered - called := false - err := j.WithLock(func(*State, func() error) error { called = true; return nil }) - close(release) - if first := <-done; first != nil { - t.Fatal(first) - } - if !errors.Is(err, unix.EWOULDBLOCK) || called { - t.Fatalf("competing writer should fail busy before callback: %v callback=%t", err, called) - } - if err = j.WithLock(func(s *State, _ func() error) error { - if s.SuspensionReason != "first writer" { - t.Fatal("lost first write") - } - return nil + } +} + +// The lock-free reader sees the committed snapshot while the lock is held: +// `treatmentctl status` never waits. +func TestJournalLockFreeReadUnderLock(t *testing.T) { + j := Journal{Dir: t.TempDir()} + if err := j.WithLock(context.Background(), func(s *State, save func() error) error { + s.SuspensionReason = "committed" + return save() }); err != nil { - t.Fatal("lock not released", err) + t.Fatal(err) } - // The lock-free reader sees the committed snapshot while the lock is held. hold, held, released := make(chan struct{}), make(chan struct{}), make(chan struct{}) go func() { defer close(released) - _ = j.WithLock(func(*State, func() error) error { close(held); <-hold; return nil }) + _ = j.WithLock(context.Background(), func(s *State, save func() error) error { + close(held) + <-hold + return nil + }) }() <-held s, err := ReadSnapshot(j.Dir) close(hold) - // Wait for the holder to finish before the temp dir is removed. <-released - if err != nil || s.SuspensionReason != "first writer" { + if err != nil || s.SuspensionReason != "committed" { t.Fatalf("lock-free read under lock: %v %+v", err, s) } } +// TestJournalLockHolderDies kills a process holding the lock mid-write: the +// kernel drops its flock, the next holder acquires at once and sweeps the +// temporary file the dead writer left, and the committed snapshot is intact. +func TestJournalLockHolderDies(t *testing.T) { + dir := t.TempDir() + j := Journal{Dir: dir} + if err := j.WithLock(context.Background(), func(s *State, save func() error) error { + s.SuspensionReason = "committed" + return save() + }); err != nil { + t.Fatal(err) + } + cmd := exec.Command(os.Args[0], "-test.run=^TestJournalLockHolderHelper$") + cmd.Env = append(os.Environ(), "XTCP_JOURNAL_HOLD_DIR="+dir) + out, err := cmd.StdoutPipe() + if err != nil { + t.Fatal(err) + } + if err = cmd.Start(); err != nil { + t.Fatal(err) + } + line := make([]byte, 5) + if _, err = io.ReadFull(out, line); err != nil || string(line) != "held\n" { + _ = cmd.Process.Kill() + t.Fatalf("holder did not report the lock: %q %v", line, err) + } + // While the child holds the lock, a short wait fails busy. + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) + err = j.WithLock(ctx, func(*State, func() error) error { t.Fatal("entered under a live holder"); return nil }) + cancel() + if !errors.Is(err, ErrJournalBusy) { + t.Fatalf("live holder: %v", err) + } + if err = cmd.Process.Kill(); err != nil { + t.Fatal(err) + } + _ = cmd.Wait() + ctx, cancel = context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + if err = j.WithLock(ctx, func(s *State, save func() error) error { + if s.SuspensionReason != "committed" { + t.Fatalf("dead writer corrupted the snapshot: %+v", s) + } + return nil + }); err != nil { + t.Fatalf("lock not released by the dead holder: %v", err) + } + entries, err := os.ReadDir(dir) + if err != nil { + t.Fatal(err) + } + for _, e := range entries { + if strings.HasPrefix(e.Name(), pendingPrefix) { + t.Fatalf("dead writer's temporary file survived: %s", e.Name()) + } + } +} + +// TestJournalLockHolderHelper is the child of TestJournalLockHolderDies. It +// takes the lock, leaves a half-written temporary file as a dying writer +// would, reports, and blocks until killed. +func TestJournalLockHolderHelper(t *testing.T) { + dir := os.Getenv("XTCP_JOURNAL_HOLD_DIR") + if dir == "" { + t.Skip("helper for TestJournalLockHolderDies") + } + err := Journal{Dir: dir}.WithLock(context.Background(), func(*State, func() error) error { + f, err := os.CreateTemp(dir, pendingPrefix) + if err != nil { + return err + } + if _, err = f.WriteString("partial"); err != nil { + return err + } + if err = f.Close(); err != nil { + return err + } + fmt.Println("held") + select {} + }) + t.Fatal(err) +} + func TestJournalSymlinkProtection(t *testing.T) { for _, name := range []string{".lock", SnapshotFile} { t.Run(name, func(t *testing.T) { @@ -139,7 +278,7 @@ func TestJournalSymlinkProtection(t *testing.T) { t.Fatal(err) } called := false - err := (Journal{Dir: dir}).WithLock(func(*State, func() error) error { called = true; return nil }) + err := (Journal{Dir: dir}).WithLock(context.Background(), func(*State, func() error) error { called = true; return nil }) if err == nil || called { t.Fatal("followed journal symlink") } @@ -168,7 +307,7 @@ func TestJournalSizeLimit(t *testing.T) { if err = f.Close(); err != nil { t.Fatal(err) } - err = (Journal{Dir: dir}).WithLock(func(*State, func() error) error { t.Fatal("oversized snapshot reached callback"); return nil }) + err = (Journal{Dir: dir}).WithLock(context.Background(), func(*State, func() error) error { t.Fatal("oversized snapshot reached callback"); return nil }) if err == nil || !strings.Contains(err.Error(), "too large") { t.Fatalf("want size limit, got %v", err) } @@ -180,7 +319,7 @@ func TestJournalSizeLimit(t *testing.T) { func TestJournalProjectionsFollowSnapshot(t *testing.T) { j := Journal{Dir: t.TempDir()} d := fixtures.Desired(time.Unix(1700000000, 0)) - err := j.WithLock(func(s *State, save func() error) error { + err := j.WithLock(context.Background(), func(s *State, save func() error) error { s.AgentID = "host-1" s.Desired = d s.Records = []Record{{ApplicationRecord: treatment.ApplicationRecord{Assignment: d.Assignments[0], Status: treatment.ApplyPending}, InstalledRoute: []byte{1, 2, 3}, InstalledBootID: "boot-1"}} diff --git a/pkg/treatment/reconcile/reconcile.go b/pkg/treatment/reconcile/reconcile.go index 89223eced..ab85bfb5e 100644 --- a/pkg/treatment/reconcile/reconcile.go +++ b/pkg/treatment/reconcile/reconcile.go @@ -90,7 +90,7 @@ func (r *Reconciler) Run(ctx context.Context, in *treatment.DesiredState, mode M } expiryOnly := mode == ModeExpiryOnly var result *State - err := r.Journal.WithLock(func(s *State, save func() error) error { + err := r.Journal.WithLock(ctx, func(s *State, save func() error) error { defer func() { result = s.Clone() }() r.Backend.BeginReconcile() now := r.now() @@ -308,12 +308,13 @@ func (r *Reconciler) install(ctx context.Context, v *Record, t *treatment.Treatm } // SuspendLocal persists an offline hold (or lifts it) that controller -// requests cannot clear. A reason is required for the audit trail. -func (r *Reconciler) SuspendLocal(suspended bool, reason string) error { +// requests cannot clear. A reason is required for the audit trail. ctx +// bounds the wait for the journal lock. +func (r *Reconciler) SuspendLocal(ctx context.Context, suspended bool, reason string) error { if len(reason) == 0 || len(reason) > 1024 { return errors.New("suspension/resume reason required (maximum 1024 bytes)") } - return r.Journal.WithLock(func(s *State, save func() error) error { + return r.Journal.WithLock(ctx, func(s *State, save func() error) error { if s.AgentID != "" && s.AgentID != r.AgentID { return errors.New("journal belongs to another agent") } diff --git a/pkg/treatment/reconcile/reconcile_test.go b/pkg/treatment/reconcile/reconcile_test.go index 7c99f4d9b..5e145f6a5 100644 --- a/pkg/treatment/reconcile/reconcile_test.go +++ b/pkg/treatment/reconcile/reconcile_test.go @@ -174,7 +174,7 @@ func TestPersistentHoldAndTombstones(t *testing.T) { if _, e := r.Run(context.Background(), d, ModeFull); e != nil { t.Fatal(e) } - if e := r.SuspendLocal(true, "incident test"); e != nil { + if e := r.SuspendLocal(context.Background(), true, "incident test"); e != nil { t.Fatal(e) } if _, e := r.Run(context.Background(), nil, ModeFull); e != nil { @@ -190,7 +190,7 @@ func TestPersistentHoldAndTombstones(t *testing.T) { if len(b.routes) != 0 { t.Fatal("controller update cleared local hold") } - if e := r.SuspendLocal(false, "test complete"); e != nil { + if e := r.SuspendLocal(context.Background(), false, "test complete"); e != nil { t.Fatal(e) } d.Revision++ @@ -257,7 +257,7 @@ func TestPredecessorExpiryAndGenerationRejectionPreserveJournal(t *testing.T) { if _, e = r.Run(context.Background(), bad, ModeFull); e == nil { t.Fatal("stale generation accepted") } - if e = r.Journal.WithLock(func(s *State, _ func() error) error { + if e = r.Journal.WithLock(context.Background(), func(s *State, _ func() error) error { if s.Desired.Revision != d.Revision { t.Fatal("rejected generation poisoned persisted desired state") } @@ -288,7 +288,7 @@ func TestJournalHeaderIdentifiesWriter(t *testing.T) { if _, e = other.Run(context.Background(), nil, ModeFull); e == nil || !strings.Contains(e.Error(), "another agent") { t.Fatalf("foreign journal accepted: %v", e) } - if e = other.SuspendLocal(true, "x"); e == nil || !strings.Contains(e.Error(), "another agent") { + if e = other.SuspendLocal(context.Background(), true, "x"); e == nil || !strings.Contains(e.Error(), "another agent") { t.Fatalf("foreign journal suspended: %v", e) } if b.installs != 1 { From 91ccde4a713ae07358459dabc19400201ad48814 Mon Sep 17 00:00:00 2001 From: "randomizedcoder dave.seddon.ca@gmail.com" Date: Sun, 25 Oct 2026 20:46:12 -0700 Subject: [PATCH 16/27] perf(treatment): incremental notification scan, heartbeat split, housekeeping lease Agents.DesiredRevisions (a full scan every two seconds) becomes Agents.DesiredSince(since), reading rows stamped at or after a watermark through the desired_updated_at index. SaveDesired and Enroll stamp with clock_timestamp(): now() is transaction start, which precedes the advisory lock, so it could sort a later commit before an earlier one. The scanner carries the newest store stamp less a one-second margin; the hub drops repeats. A step larger than the margin is covered by the agent's next check-in. Observe writes one agents row per heartbeat and rewrites the algorithm and record rows only when a SHA-256 digest of their insert form changes, kept in the new report_digest column. The previous digest comes back from the same UPDATE through a FOR UPDATE subquery. Store.Lease(name, fn) elects which replica runs periodic work: Postgres takes pg_try_advisory_lock on a dedicated connection and discards the connection if the unlock fails; memory is a try-lock map. Server.Housekeep runs Maintain and, when due, Discover under the housekeeping lease and the controller main loop logs leader transitions. Tests: storetest rows for DesiredSince (all, changed, equal stamp, none, store clock) and Lease (free, held elsewhere, released on error, independent names, cancelled); TestPostgresObserveRewritesOnlyChangedReports compares row xmin values; TestNotifyWatermark walks a scanner through change, no change, equal stamp, scan failure, future and behind-watermark steps; TestHousekeepLease covers the controller side. Co-Authored-By: Claude Fable 5.1 --- cmd/xtcp2-controller/main.go | 26 +++- docs/status-tcp-controller.md | 45 +++++- docs/tcp-controller.md | 28 +++- pkg/treatment/controller/housekeep_test.go | 86 +++++++++++ pkg/treatment/controller/maintenance.go | 25 +++ pkg/treatment/controller/notifications.go | 45 +++++- .../controller/notifications_test.go | 135 ++++++++++++++++ .../controller/store/memory/memory.go | 42 ++++- .../controller/store/postgres/agents.go | 70 +++++++-- .../store/postgres/heartbeat_test.go | 114 ++++++++++++++ .../controller/store/postgres/postgres.go | 42 +++++ .../controller/store/postgres/schema.sql | 3 + pkg/treatment/controller/store/store.go | 24 ++- .../controller/store/storetest/storetest.go | 146 +++++++++++++++++- 14 files changed, 780 insertions(+), 51 deletions(-) create mode 100644 pkg/treatment/controller/housekeep_test.go create mode 100644 pkg/treatment/controller/store/postgres/heartbeat_test.go diff --git a/cmd/xtcp2-controller/main.go b/cmd/xtcp2-controller/main.go index a2b9ed828..2f24e7409 100644 --- a/cmd/xtcp2-controller/main.go +++ b/cmd/xtcp2-controller/main.go @@ -115,20 +115,34 @@ func run() error { tick := time.NewTicker(time.Minute) defer tick.Stop() nextDiscovery := time.Now() + leader := false for { - if e := s.Maintain(ctx, source); e != nil { - log.Printf("controller maintenance: %v", e) - } + // Discovery is due on this replica's own cadence whether or not + // it holds the lease, so a follower that becomes leader does not + // run a backlog of passes. + var due evidence.Warehouse if warehouse != nil && !time.Now().Before(nextDiscovery) { - if e := s.Discover(ctx, warehouse, templates); e != nil { - log.Printf("candidate discovery: %v", e) - } + due = warehouse // Keep the cadence independent of query duration; otherwise a // one-minute interval can accidentally skip every other tick. for !nextDiscovery.After(time.Now()) { nextDiscovery = nextDiscovery.Add(*discoveryInterval) } } + held, e := s.Housekeep(ctx, source, due, templates) + if e != nil { + log.Printf("controller housekeeping: %v", e) + } + // A failed Lease call reports held=false with an error; that is + // not a follower, so the leader state is kept until it answers. + if (e == nil || held) && held != leader { + leader = held + if held { + log.Print("controller housekeeping: this replica holds the lease") + } else { + log.Print("controller housekeeping: another replica holds the lease; skipping passes") + } + } select { case <-ctx.Done(): return diff --git a/docs/status-tcp-controller.md b/docs/status-tcp-controller.md index ff522a4a3..18ac926b4 100644 --- a/docs/status-tcp-controller.md +++ b/docs/status-tcp-controller.md @@ -18,9 +18,9 @@ review being addressed. Items are listed in execution order. | 6 | auth as interceptor with per-RPC role option | done | d7fefee4 | | 7 | typed `xtcpnl.RuleWrite`; backend to `internal/goip/actuate` | done | this commit | | 9 | identity/location startup check | done | this commit | -| 10 | blocking journal lock | done | this commit | -| 11 | scale: notification watermark, heartbeat split, leader lease | next | | -| 12 | docs: threat model, naming, SIGHUP, philosophy sentence | pending | | +| 10 | blocking journal lock | done | 3478d9b3 | +| 11 | scale: notification watermark, heartbeat split, leader lease | done | this commit | +| 12 | docs: threat model, naming, SIGHUP, philosophy sentence | next | | Dual-mode agent (library + `cmd/xtcp2-treatment-agent` + `treatment` build tag in `cmd/xtcp2`, with an `attribution` journal reader for the untagged daemon) @@ -172,6 +172,43 @@ process. lock with a half-written temp file and checks the lock is free, the snapshot intact and the temp file swept. +## Item 11 notes + +- Notification scan: `Agents.DesiredRevisions` (full scan) became + `Agents.DesiredSince(ctx, since) []DesiredRevision{AgentID, Revision, + UpdatedAt}` with `>=` on `desired_updated_at`, ordered by stamp then ID. + `SaveDesired` and `Enroll` stamp with `clock_timestamp()` rather than + `now()`: `now()` is transaction start, which precedes the advisory lock, so + a stamp could sort before a row that committed earlier. With every stamping + writer under the mutation lock, stamps commit in order and the watermark + (newest stamp less a one-second margin, store clock only) cannot skip a + row; the margin covers a store clock step under a second and the hub drops + the repeats it causes. A step larger than the margin leaves that revision + to the agent's next check-in, which always returns desired state on a + revision mismatch. `TestNotifyWatermark` walks a scanner through first + scan, change, no change, equal stamp, scan failure, future stamp and + behind-watermark steps. +- Heartbeat split: `report_digest` column on `xtcp_treatment_agents` holds a + SHA-256 over the algorithm list and every record in insert-column form. + `Observe` does one `UPDATE ... FROM (... FOR UPDATE) RETURNING + o.report_digest` and rewrites the child rows only when the digest changed. + `TestPostgresObserveRewritesOnlyChangedReports` compares row `xmin` values + across identical, heartbeat-only, status, timestamp, order, algorithm and + dropped-record reports. +- Leader lease: `Store.Lease(ctx, name, fn) (held bool, err error)`. + Postgres takes `pg_try_advisory_lock(fnv64a(name))` on a dedicated + `db.Conn`, unlocks after `fn` with a bounded context, and discards the + connection if the unlock fails so a pooled connection never carries the + lock. Memory is a try-lock map outside the Tx snapshot. + `Server.Housekeep` runs `Maintain` and, when due, `Discover` under the + `housekeeping` lease; the controller main loop logs leader transitions + and advances its discovery cadence whether or not it held the lease. + Conformance rows: free, held-elsewhere, released-on-error, + independent-names, cancelled-context; `TestHousekeepLease` covers the + controller side. +- The schema is pre-release, so `report_digest` is added to the `CREATE + TABLE` rather than through an `ALTER TABLE ... ADD COLUMN IF NOT EXISTS`. + ## TODO - The watchdog and `pkg/treatment/local` still link gRPC, through @@ -182,8 +219,6 @@ process. - `Experiments.List` loads each experiment with its own round trips (cohort, phases, evidence). Fine for `inspect` limits; batch before any fleet-wide listing path. -- `Agents.Observe` rewrites every record row per heartbeat. Item 11 splits the - heartbeat update from record writes. - `docs/benchmarks/tcp-controller/postgres-indexes.txt` still shows the JSONB expression-index timings; refresh from `TestPostgresExperimentQueryIndexes` on the Nix fixture. diff --git a/docs/tcp-controller.md b/docs/tcp-controller.md index 5e63d31d4..d9ebfc744 100644 --- a/docs/tcp-controller.md +++ b/docs/tcp-controller.md @@ -80,6 +80,20 @@ Agents consume complete revisioned snapshots, so missed notifications are safe. A shared two-second notification scan coalesces updates; jittered check-ins run approximately every thirty seconds. No message broker is required. +The scan is incremental. Every desired-state write stamps the agent row with +the database clock (`clock_timestamp()`, taken after the advisory lock, so +stamps commit in lock order), and each scan reads only rows stamped at or +after the previous scan's newest stamp less a one-second margin, through the +`desired_updated_at` index. The first scan after a restart reads every row so +late subscribers learn the current revision; the hub drops repeated revisions. +A check-in writes one agent row: the record and algorithm rows are rewritten +only when a digest of the report's content changes, so a steady fleet costs +one single-row update per heartbeat. Maintenance and candidate discovery run +under a session-level advisory lease (`pg_try_advisory_lock` on a dedicated +connection) that one replica holds for the pass; the others log that they are +skipping. The lease is about cost, not correctness: every write is still fenced +by the transaction-level mutation lock. + ## Supported routes and TCP semantics The first actuator supports IPv4 `/32` and IPv6 `/128` destinations in the host @@ -291,9 +305,11 @@ Two B-tree indexes on plain columns support these paths: The budget query uses `EXISTS` rather than counting every match. Evidence host rows live in `xtcp_treatment_evidence_hosts`, keyed by evidence ID, so an experiment row stays small no matter how many hosts were measured. Agent -primary-key and location indexes already cover their current lookups; no extra -heartbeat indexes were added. An all-agent notification scan still needs to -read all agents, so an additional index alone would not eliminate that work. +primary-key and location indexes cover their lookups; the notification scan +reads through `xtcp_treatment_agents_desired_updated` with a watermark, so it +touches the rows that changed since the last scan rather than the fleet. +`TestPostgresObserveRewritesOnlyChangedReports` checks, through row `xmin` +values, that an unchanged report leaves the record and algorithm rows alone. `TestPostgresExperimentQueryIndexes` (in `store/postgres`) loads 100,000 retired experiments and five active experiments, runs `ANALYZE`, and compares @@ -329,9 +345,9 @@ its invalid index before retrying: `IF NOT EXISTS` does not repair an invalid index. The startup migration uses ordinary index creation, not concurrent DDL. Further scaling work should measure per-host SQL round trips during review and -lease renewal, large experiment-document rewrites, evidence/audit retention, and -the notification scan. Those need batching or data-layout changes, not blanket -indexes on every field. Discovery currently considers the ten busiest candidate +lease renewal, large experiment-document rewrites, and evidence/audit +retention. Those need batching or data-layout changes, not blanket indexes on +every field. Discovery currently considers the ten busiest candidate destinations per template before the PostgreSQL destination filter; pagination or earlier exclusion would be needed to avoid already-treated destinations occupying all ten slots. diff --git a/pkg/treatment/controller/housekeep_test.go b/pkg/treatment/controller/housekeep_test.go new file mode 100644 index 000000000..6339f2beb --- /dev/null +++ b/pkg/treatment/controller/housekeep_test.go @@ -0,0 +1,86 @@ +package controller + +import ( + "context" + "errors" + "strings" + "testing" + "time" + + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/evidence" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store/memory" +) + +// TestHousekeepLease checks that maintenance and discovery run on exactly +// one replica per tick: the one holding the lease. +func TestHousekeepLease(t *testing.T) { + now := time.Unix(1800000000, 0) + templates := []DiscoveryTemplate{{ID: "near", Location: "test-dc", CongestionControl: "cubic", MaximumRTTMs: 15}} + for _, tc := range []struct { + name, description string + warehouse *fakeWarehouse + heldElsewhere bool + cancelled bool + wantHeld bool + wantErr string + wantQueries int + }{ + {"free", "positive: with the lease free this replica runs maintenance and discovery", &fakeWarehouse{}, false, false, true, "", 1}, + {"held-elsewhere", "corner: while another replica holds the lease this one runs nothing and reports it", &fakeWarehouse{}, true, false, false, "", 0}, + {"no-warehouse", "boundary: without a warehouse the pass is maintenance only", nil, false, false, true, "", 0}, + {"discovery-fails", "negative: a failing pass still held the lease and names the failing stage", &fakeWarehouse{candidateErr: errors.New("warehouse offline")}, false, false, true, "candidate discovery: warehouse offline", 1}, + {"cancelled", "negative: a cancelled context takes no lease and runs nothing", &fakeWarehouse{}, false, true, false, "context canceled", 0}, + } { + t.Run(tc.name, func(t *testing.T) { + st := memory.New() + s := &Server{Store: st, Now: func() time.Time { return now }} + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + if tc.cancelled { + cancel() + } + release := make(chan struct{}) + if tc.heldElsewhere { + entered := make(chan struct{}) + go func() { + _, _ = st.Lease(context.Background(), housekeepingLeaseCst, func() error { close(entered); <-release; return nil }) + }() + <-entered + } + var w evidence.Warehouse + if tc.warehouse != nil { + w = tc.warehouse + } + held, err := s.Housekeep(ctx, nil, w, templates) + if tc.heldElsewhere { + close(release) + } + if held != tc.wantHeld { + t.Fatalf("%s: held=%t want %t", tc.description, held, tc.wantHeld) + } + if tc.wantErr == "" && err != nil || tc.wantErr != "" && (err == nil || !strings.Contains(err.Error(), tc.wantErr)) { + t.Fatalf("%s: err=%v want %q", tc.description, err, tc.wantErr) + } + if tc.warehouse != nil && len(tc.warehouse.queries) != tc.wantQueries { + t.Fatalf("%s: discovery queries=%d want %d", tc.description, len(tc.warehouse.queries), tc.wantQueries) + } + if tc.heldElsewhere { + // Once the holder returns, this replica takes over. + deadline := time.Now().Add(2 * time.Second) + for { + held, err = s.Housekeep(ctx, nil, w, templates) + if err != nil { + t.Fatal(err) + } + if held || time.Now().After(deadline) { + break + } + time.Sleep(5 * time.Millisecond) + } + if !held || len(tc.warehouse.queries) != 1 { + t.Fatalf("%s: expected the released lease to be taken, held=%t queries=%d", tc.description, held, len(tc.warehouse.queries)) + } + } + }) + } +} diff --git a/pkg/treatment/controller/maintenance.go b/pkg/treatment/controller/maintenance.go index c9220117d..ecd682921 100644 --- a/pkg/treatment/controller/maintenance.go +++ b/pkg/treatment/controller/maintenance.go @@ -23,6 +23,31 @@ func (s *Server) driftInput(n *treatment.Experiment) *treatment.Experiment { return input } +// housekeepingLeaseCst names the lease one replica holds while it runs +// maintenance and discovery; the others skip the tick. +const housekeepingLeaseCst = "housekeeping" + +// Housekeep runs one maintenance pass, and a discovery pass when warehouse +// is non-nil, under the housekeeping lease. It reports whether this replica +// held the lease; false means another replica did the work for this tick. +// Correctness never depended on a single runner (the mutation lock fences +// every write); the lease keeps the evidence queries from running once per +// replica. +func (s *Server) Housekeep(ctx context.Context, source evidence.Source, warehouse evidence.Warehouse, templates []DiscoveryTemplate) (bool, error) { + return s.Store.Lease(ctx, housekeepingLeaseCst, func() error { + var errs []error + if e := s.Maintain(ctx, source); e != nil { + errs = append(errs, fmt.Errorf("maintenance: %w", e)) + } + if warehouse != nil { + if e := s.Discover(ctx, warehouse, templates); e != nil { + errs = append(errs, fmt.Errorf("candidate discovery: %w", e)) + } + } + return errors.Join(errs...) + }) +} + // Maintain refreshes exposure, stores evidence, renews healthy leases, and // withdraws harmful/expired trials. It NEVER promotes a phase. A fresh human // review remains necessary for each increase in exposure. A nil source (no diff --git a/pkg/treatment/controller/notifications.go b/pkg/treatment/controller/notifications.go index d9f622bdb..6515831bf 100644 --- a/pkg/treatment/controller/notifications.go +++ b/pkg/treatment/controller/notifications.go @@ -53,15 +53,28 @@ func (h *NotificationHub) publish(id string, revision uint64) { ch <- revision } } + +// notificationIntervalCst is the scan period; check-ins deliver desired +// state anyway, so a missed scan costs latency, not correctness. +const notificationIntervalCst = 2 * time.Second + +// notificationLagCst is taken off the watermark between scans. Stamps and +// commits are ordered by the mutation lock, so the margin only covers a +// store clock stepping backwards; the hub drops the repeats it causes. +const notificationLagCst = time.Second + +// RunNotifications publishes desired-state changes to subscribed agents. +// The first scan reads every agent so that late subscribers learn the +// current revision; each later scan reads only rows stamped at or after the +// previous scan's newest stamp less the lag, so its cost follows the change +// rate rather than the fleet size. Watermarks are store stamps, never this +// process's clock. func (s *Server) RunNotifications(ctx context.Context) { - t := time.NewTicker(2 * time.Second) + t := time.NewTicker(notificationIntervalCst) defer t.Stop() + var since time.Time for { - if revisions, e := s.Store.Repos().Agents.DesiredRevisions(ctx); e == nil { - for id, rev := range revisions { - s.Hub.publish(id, rev) - } - } + since = s.notify(ctx, since) select { case <-ctx.Done(): return @@ -69,3 +82,23 @@ func (s *Server) RunNotifications(ctx context.Context) { } } } + +// notify publishes every change stamped at or after since and returns the +// next watermark. A failed scan keeps the watermark so nothing is skipped. +func (s *Server) notify(ctx context.Context, since time.Time) time.Time { + changes, e := s.Store.Repos().Agents.DesiredSince(ctx, since) + if e != nil { + return since + } + newest := since + for _, c := range changes { + s.Hub.publish(c.AgentID, c.Revision) + if c.UpdatedAt.After(newest) { + newest = c.UpdatedAt + } + } + if next := newest.Add(-notificationLagCst); next.After(since) { + return next + } + return since +} diff --git a/pkg/treatment/controller/notifications_test.go b/pkg/treatment/controller/notifications_test.go index b91380925..6748e9359 100644 --- a/pkg/treatment/controller/notifications_test.go +++ b/pkg/treatment/controller/notifications_test.go @@ -1,9 +1,17 @@ package controller import ( + "context" + "errors" "fmt" + "maps" "sync" "testing" + "time" + + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store/memory" ) func TestNotificationLifecycle(t *testing.T) { @@ -104,3 +112,130 @@ func BenchmarkNotificationFleet(b *testing.B) { }) } } + +// scanRecorder wraps a store to record what each notification scan asked +// for and how many rows came back, and to inject a scan failure. +type scanRecorder struct { + store.Store + since []time.Time + rows []int + err error +} + +func (r *scanRecorder) Repos() store.Repos { + rp := r.Store.Repos() + rp.Agents = &recordingAgents{Agents: rp.Agents, r: r} + return rp +} + +type recordingAgents struct { + store.Agents + r *scanRecorder +} + +func (a *recordingAgents) DesiredSince(ctx context.Context, since time.Time) ([]store.DesiredRevision, error) { + if a.r.err != nil { + return nil, a.r.err + } + out, err := a.Agents.DesiredSince(ctx, since) + a.r.since = append(a.r.since, since) + a.r.rows = append(a.r.rows, len(out)) + return out, err +} + +// TestNotifyWatermark walks one scanner through a sequence of store writes +// and checks what each scan reads, what subscribers receive and where the +// watermark lands. Steps build on each other, like the scans they model. +func TestNotifyWatermark(t *testing.T) { + ctx := context.Background() + base := time.Unix(1800000000, 0).UTC() + mem := memory.New() + clock := base + mem.Now = func() time.Time { return clock } + rec := &scanRecorder{Store: mem} + s := &Server{Store: rec, Hub: NewNotificationHub()} + agents := mem.Repos().Agents + // host-2 enrolled ten seconds before host-1, so after the first scan only + // host-1's stamp sits inside the watermark's one-second lag. + for _, e := range []struct { + id string + ago time.Duration + }{{"host-2", 10 * time.Second}, {"host-1", 0}} { + clock = base.Add(-e.ago) + if err := agents.Enroll(ctx, e.id, "dc1", &treatment.DesiredState{AgentID: e.id, Revision: 1, IssuedAt: base}); err != nil { + t.Fatal(err) + } + } + subs := map[string]chan uint64{} + for _, id := range []string{"host-1", "host-2"} { + ch, cancel := s.Hub.subscribe(id) + defer cancel() + subs[id] = ch + } + delivered := func() map[string]uint64 { + got := map[string]uint64{} + for id, ch := range subs { + select { + case v := <-ch: + got[id] = v + default: + } + } + return got + } + since := time.Time{} + for _, step := range []struct { + name, description string + clock time.Duration + write func(a store.Agents) error + scanErr error + wantRows int + wantDelivered map[string]uint64 + wantWatermark time.Duration // offset from base + }{ + {"first-scan", "positive: the first scan reads every agent, publishes the current revisions and sets the watermark one lag behind the newest stamp", 0, nil, nil, 2, map[string]uint64{"host-1": 1, "host-2": 1}, -time.Second}, + {"changed-agent", "positive: a desired state saved after the watermark is read and delivered alone", 10 * time.Second, func(a store.Agents) error { + return a.SaveDesired(ctx, &treatment.DesiredState{AgentID: "host-1", Revision: 2, IssuedAt: base}) + }, nil, 1, map[string]uint64{"host-1": 2}, 9 * time.Second}, + {"no-change", "positive: with nothing saved the scan re-reads only the newest row (inside the lag), delivers nothing and keeps the watermark", 10 * time.Second, nil, nil, 1, map[string]uint64{}, 9 * time.Second}, + {"equal-timestamp", "boundary: a row stamped exactly at the watermark is read, alongside the row inside the lag, and only the new revision is delivered", 9 * time.Second, func(a store.Agents) error { + return a.SaveDesired(ctx, &treatment.DesiredState{AgentID: "host-2", Revision: 2, IssuedAt: base}) + }, nil, 2, map[string]uint64{"host-2": 2}, 9 * time.Second}, + {"equal-timestamp-repeat", "boundary: the next scan reads the same two rows again and delivers nothing twice", 9 * time.Second, nil, nil, 2, map[string]uint64{}, 9 * time.Second}, + {"scan-error", "negative: a failed scan delivers nothing and leaves the watermark where it was", 20 * time.Second, func(a store.Agents) error { + return a.SaveDesired(ctx, &treatment.DesiredState{AgentID: "host-1", Revision: 3, IssuedAt: base}) + }, errors.New("database unavailable"), 0, map[string]uint64{}, 9 * time.Second}, + {"after-error", "positive: the scan after a failure picks the change up from the kept watermark", 20 * time.Second, nil, nil, 2, map[string]uint64{"host-1": 3}, 19 * time.Second}, + {"future-stamp", "corner: a stamp far in the future is read and the watermark follows it", time.Hour, func(a store.Agents) error { + return a.SaveDesired(ctx, &treatment.DesiredState{AgentID: "host-2", Revision: 3, IssuedAt: base}) + }, nil, 2, map[string]uint64{"host-2": 3}, time.Hour - time.Second}, + {"behind-watermark", "corner: a stamp more than the lag behind the watermark is not scanned (the agent's next check-in carries it); the future row is re-read and dropped", 30 * time.Second, func(a store.Agents) error { + return a.SaveDesired(ctx, &treatment.DesiredState{AgentID: "host-1", Revision: 4, IssuedAt: base}) + }, nil, 1, map[string]uint64{}, time.Hour - time.Second}, + } { + t.Run(step.name, func(t *testing.T) { + clock = base.Add(step.clock) + if step.write != nil { + if err := step.write(agents); err != nil { + t.Fatal(err) + } + } + rec.err = step.scanErr + rec.since, rec.rows = nil, nil + since = s.notify(ctx, since) + rows := -1 + if len(rec.rows) == 1 { + rows = rec.rows[0] + } + if step.scanErr == nil && rows != step.wantRows { + t.Fatalf("%s: scan read %d rows, want %d", step.description, rows, step.wantRows) + } + if got := delivered(); !maps.Equal(got, step.wantDelivered) { + t.Fatalf("%s: delivered %v, want %v", step.description, got, step.wantDelivered) + } + if want := base.Add(step.wantWatermark); !since.Equal(want) { + t.Fatalf("%s: watermark %v, want %v", step.description, since, want) + } + }) + } +} diff --git a/pkg/treatment/controller/store/memory/memory.go b/pkg/treatment/controller/store/memory/memory.go index 395e192e7..470cc58fa 100644 --- a/pkg/treatment/controller/store/memory/memory.go +++ b/pkg/treatment/controller/store/memory/memory.go @@ -86,14 +86,15 @@ func cloneAgent(a *agentRow) *agentRow { // Store satisfies store.Store. type Store struct { - mu sync.Mutex - st *state + mu sync.Mutex + st *state + leases map[string]bool // Now stamps updated_at columns; tests may pin it. Now func() time.Time } func New() *Store { - return &Store{st: &state{agents: map[string]*agentRow{}, experiments: map[string]*experimentRow{}, evidence: map[string]evidenceRow{}, holds: map[string]string{}}} + return &Store{st: &state{agents: map[string]*agentRow{}, experiments: map[string]*experimentRow{}, evidence: map[string]evidenceRow{}, holds: map[string]string{}}, leases: map[string]bool{}} } func (s *Store) now() time.Time { @@ -129,6 +130,27 @@ func (s *Store) Tx(ctx context.Context, fn func(store.Repos) error) error { return nil } +// Lease is a try-lock per name. Leases are not state: a rolled-back Tx +// does not release one. +func (s *Store) Lease(ctx context.Context, name string, fn func() error) (bool, error) { + if err := ctx.Err(); err != nil { + return false, err + } + s.mu.Lock() + if s.leases[name] { + s.mu.Unlock() + return false, nil + } + s.leases[name] = true + s.mu.Unlock() + defer func() { + s.mu.Lock() + delete(s.leases, name) + s.mu.Unlock() + }() + return true, fn() +} + type repo struct { s *Store lock bool @@ -220,12 +242,20 @@ func (r agentsRepo) List(_ context.Context, location string) ([]store.AgentRef, return out, nil } -func (r agentsRepo) DesiredRevisions(context.Context) (map[string]uint64, error) { +func (r agentsRepo) DesiredSince(_ context.Context, since time.Time) ([]store.DesiredRevision, error) { defer r.enter()() - out := make(map[string]uint64, len(r.s.st.agents)) + var out []store.DesiredRevision for id, a := range r.s.st.agents { - out[id] = a.desired.Revision + if !a.updatedAt.Before(since) { + out = append(out, store.DesiredRevision{AgentID: id, Revision: a.desired.Revision, UpdatedAt: a.updatedAt}) + } } + sort.Slice(out, func(i, j int) bool { + if !out[i].UpdatedAt.Equal(out[j].UpdatedAt) { + return out[i].UpdatedAt.Before(out[j].UpdatedAt) + } + return out[i].AgentID < out[j].AgentID + }) return out, nil } diff --git a/pkg/treatment/controller/store/postgres/agents.go b/pkg/treatment/controller/store/postgres/agents.go index 6c4f10f76..92522198b 100644 --- a/pkg/treatment/controller/store/postgres/agents.go +++ b/pkg/treatment/controller/store/postgres/agents.go @@ -2,8 +2,11 @@ package postgres import ( "context" + "crypto/sha256" "database/sql" + "encoding/hex" "errors" + "fmt" "time" "github.com/randomizedcoder/xtcp2/pkg/treatment" @@ -14,8 +17,8 @@ type agents struct{ *runner } func (a *agents) Enroll(ctx context.Context, id, location string, initial *treatment.DesiredState) error { return a.atomic(ctx, func(q querier) error { - res, err := q.ExecContext(ctx, `INSERT INTO xtcp_treatment_agents(id,location,desired_revision,desired_suspended,desired_issued_at) - VALUES($1,$2,$3,$4,$5) ON CONFLICT(id) DO NOTHING`, id, location, int64(initial.Revision), initial.Suspended, initial.IssuedAt) + res, err := q.ExecContext(ctx, `INSERT INTO xtcp_treatment_agents(id,location,desired_revision,desired_suspended,desired_issued_at,desired_updated_at) + VALUES($1,$2,$3,$4,$5,clock_timestamp()) ON CONFLICT(id) DO NOTHING`, id, location, int64(initial.Revision), initial.Suspended, initial.IssuedAt) if err != nil { return err } @@ -77,19 +80,31 @@ func (a *agents) Get(ctx context.Context, id string) (*store.Agent, error) { return out, nil } +// Observe always stamps the heartbeat columns on the agents row. The +// algorithm and record rows are rewritten only when their digest differs +// from the one stored with the previous report, so a steady fleet costs one +// single-row update per check-in. func (a *agents) Observe(ctx context.Context, report *treatment.AgentReport, seen time.Time) error { + digest := reportDigest(report) return a.atomic(ctx, func(q querier) error { c := report.Capabilities - res, err := q.ExecContext(ctx, `UPDATE xtcp_treatment_agents SET last_seen=$3,boot_id=$4,kernel_release=$5,simple_host_routing=$6,actuator_enabled=$7,locally_suspended=$8,reported_revision=$9 - WHERE id=$1 AND location=$2`, report.AgentID, report.Location, seen, c.BootID, c.KernelRelease, c.SimpleHostRouting, c.ActuatorEnabled, report.LocallySuspended, int64(report.DesiredRevision)) + var previous string + // The subquery's report_digest is the value before this update. + err := q.QueryRowContext(ctx, `UPDATE xtcp_treatment_agents AS a + SET last_seen=$3,boot_id=$4,kernel_release=$5,simple_host_routing=$6,actuator_enabled=$7, + locally_suspended=$8,reported_revision=$9,report_digest=$10 + FROM (SELECT id,report_digest FROM xtcp_treatment_agents WHERE id=$1 AND location=$2 FOR UPDATE) AS o + WHERE a.id=o.id RETURNING o.report_digest`, + report.AgentID, report.Location, seen, c.BootID, c.KernelRelease, c.SimpleHostRouting, c.ActuatorEnabled, + report.LocallySuspended, int64(report.DesiredRevision), digest).Scan(&previous) + if errors.Is(err, sql.ErrNoRows) { + return store.ErrNotEnrolled + } if err != nil { return err } - if n, err := res.RowsAffected(); err != nil || n == 0 { - if err != nil { - return err - } - return store.ErrNotEnrolled + if previous == digest { + return nil } if _, err = q.ExecContext(ctx, "DELETE FROM xtcp_treatment_agent_algorithms WHERE agent_id=$1", report.AgentID); err != nil { return err @@ -112,6 +127,27 @@ func (a *agents) Observe(ctx context.Context, report *treatment.AgentReport, see }) } +// reportDigest covers exactly what Observe writes outside the agents row: +// the algorithm list and every record in the column form it would be +// inserted with. Reports with equal digests produce identical rows. +func reportDigest(report *treatment.AgentReport) string { + h := sha256.New() + for _, name := range report.Capabilities.CongestionAlgorithms { + fmt.Fprintf(h, "%s\x00", name) + } + h.Write([]byte{0xff}) + for i := range report.Records { + for _, v := range recordArgs(report.AgentID, i, &report.Records[i]) { + if t, ok := v.(time.Time); ok { + v = t.UnixNano() + } + fmt.Fprintf(h, "%v\x00", v) + } + h.Write([]byte{0xfe}) + } + return hex.EncodeToString(h.Sum(nil)) +} + func (a *agents) Desired(ctx context.Context, id string) (*treatment.DesiredState, error) { q := a.q() d := &treatment.DesiredState{AgentID: id} @@ -149,7 +185,7 @@ func (a *agents) Desired(ctx context.Context, id string) (*treatment.DesiredStat func (a *agents) SaveDesired(ctx context.Context, d *treatment.DesiredState) error { return a.atomic(ctx, func(q querier) error { - res, err := q.ExecContext(ctx, "UPDATE xtcp_treatment_agents SET desired_revision=$2,desired_suspended=$3,desired_issued_at=$4,desired_updated_at=now() WHERE id=$1", d.AgentID, int64(d.Revision), d.Suspended, d.IssuedAt) + res, err := q.ExecContext(ctx, "UPDATE xtcp_treatment_agents SET desired_revision=$2,desired_suspended=$3,desired_issued_at=$4,desired_updated_at=clock_timestamp() WHERE id=$1", d.AgentID, int64(d.Revision), d.Suspended, d.IssuedAt) if err != nil { return err } @@ -191,20 +227,22 @@ func (a *agents) List(ctx context.Context, location string) ([]store.AgentRef, e return out, errors.Join(rows.Err(), rows.Close()) } -func (a *agents) DesiredRevisions(ctx context.Context) (map[string]uint64, error) { - rows, err := a.q().QueryContext(ctx, "SELECT id,desired_revision FROM xtcp_treatment_agents") +func (a *agents) DesiredSince(ctx context.Context, since time.Time) ([]store.DesiredRevision, error) { + rows, err := a.q().QueryContext(ctx, `SELECT id,desired_revision,desired_updated_at FROM xtcp_treatment_agents + WHERE desired_updated_at>=$1 ORDER BY desired_updated_at,id`, since) if err != nil { return nil, err } - out := map[string]uint64{} + var out []store.DesiredRevision for rows.Next() { - var id string + var r store.DesiredRevision var rev int64 - if err = rows.Scan(&id, &rev); err != nil { + if err = rows.Scan(&r.AgentID, &rev, &r.UpdatedAt); err != nil { rows.Close() return nil, err } - out[id] = uint64(rev) + r.Revision = uint64(rev) + out = append(out, r) } return out, errors.Join(rows.Err(), rows.Close()) } diff --git a/pkg/treatment/controller/store/postgres/heartbeat_test.go b/pkg/treatment/controller/store/postgres/heartbeat_test.go new file mode 100644 index 000000000..71ced21bc --- /dev/null +++ b/pkg/treatment/controller/store/postgres/heartbeat_test.go @@ -0,0 +1,114 @@ +package postgres_test + +import ( + "context" + "slices" + "testing" + "time" + + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store/pgtest" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store/postgres" + "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" +) + +// rowVersions returns the xmin of each child row for the agent, in position +// order. PostgreSQL assigns a new xmin whenever a row is written, so equal +// slices across two Observe calls prove the second call did not rewrite. +func rowVersions(t *testing.T, s *postgres.Store, table, agent string) []string { + t.Helper() + rows, err := s.DB().Query("SELECT xmin::text FROM "+table+" WHERE agent_id=$1 ORDER BY position", agent) + if err != nil { + t.Fatal(err) + } + defer rows.Close() + var out []string + for rows.Next() { + var v string + if err := rows.Scan(&v); err != nil { + t.Fatal(err) + } + out = append(out, v) + } + if err := rows.Err(); err != nil { + t.Fatal(err) + } + return out +} + +func sampleReport() *treatment.AgentReport { + now := time.Unix(1700000000, 0).UTC() + d := fixtures.Desired(now) + return &treatment.AgentReport{ + AgentID: "host-1", Location: "dc1", + Capabilities: treatment.Capabilities{BootID: "boot-1", KernelRelease: "6.12.0", CongestionAlgorithms: []string{"cubic", "bbr"}, ActuatorEnabled: true}, + DesiredRevision: 3, + Records: []treatment.ApplicationRecord{ + {Assignment: d.Assignments[0], DesiredRevision: 3, Status: treatment.ApplyVerified, ReceivedAt: now, AppliedAt: now.Add(time.Second), VerifiedAt: now.Add(2 * time.Second)}, + {Assignment: treatment.Assignment{ExperimentID: "old", Generation: 2, Mode: treatment.ModeRetired, Treatment: fixtures.Treatment()}, DesiredRevision: 2, Status: treatment.ApplyFailed, Error: "ENETUNREACH"}, + }, + } +} + +// TestPostgresObserveRewritesOnlyChangedReports checks the heartbeat split: +// every check-in stamps the agents row, but the algorithm and record rows +// are written only when the report content behind them changed. +func TestPostgresObserveRewritesOnlyChangedReports(t *testing.T) { + ctx := context.Background() + for _, tc := range []struct { + name, description string + mutate func(r *treatment.AgentReport) + expectRewrite bool + }{ + {"identical", "positive: an identical report leaves every record and algorithm row untouched", func(*treatment.AgentReport) {}, false}, + {"heartbeat-only", "positive: boot ID, kernel, local suspension and reported revision live on the agents row and do not rewrite records", func(r *treatment.AgentReport) { + r.Capabilities.BootID, r.Capabilities.KernelRelease, r.LocallySuspended, r.DesiredRevision = "boot-2", "6.13.0", true, 4 + }, false}, + {"record-status", "negative: a record status change rewrites the record rows", func(r *treatment.AgentReport) { r.Records[1].Status = treatment.ApplyRemoved }, true}, + {"record-timestamp", "boundary: a one-nanosecond timestamp change is a change", func(r *treatment.AgentReport) { r.Records[0].VerifiedAt = r.Records[0].VerifiedAt.Add(time.Nanosecond) }, true}, + {"record-order", "corner: the same records in a different order are a different report", func(r *treatment.AgentReport) { + r.Records[0], r.Records[1] = r.Records[1], r.Records[0] + }, true}, + {"algorithm-list", "negative: a changed algorithm list rewrites the algorithm rows", func(r *treatment.AgentReport) { + r.Capabilities.CongestionAlgorithms = []string{"bbr", "cubic"} + }, true}, + {"records-dropped", "boundary: a report with no records removes the rows", func(r *treatment.AgentReport) { r.Records = nil }, true}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Log(tc.description) + s := pgtest.Open(t) + a := s.Repos().Agents + first := sampleReport() + if err := a.Enroll(ctx, "host-1", "dc1", &treatment.DesiredState{AgentID: "host-1", Revision: 1, IssuedAt: time.Unix(1700000000, 0)}); err != nil { + t.Fatal(err) + } + seen := time.Unix(1700000100, 0).UTC() + if err := a.Observe(ctx, first, seen); err != nil { + t.Fatal(err) + } + records, algorithms := rowVersions(t, s, "xtcp_treatment_records", "host-1"), rowVersions(t, s, "xtcp_treatment_agent_algorithms", "host-1") + if len(records) != 2 || len(algorithms) != 2 { + t.Fatalf("expected two records and two algorithms after the first report, got %d and %d", len(records), len(algorithms)) + } + second := sampleReport() + tc.mutate(second) + if err := a.Observe(ctx, second, seen.Add(30*time.Second)); err != nil { + t.Fatal(err) + } + got, err := a.Get(ctx, "host-1") + if err != nil { + t.Fatal(err) + } + if !got.LastSeen.Equal(seen.Add(30*time.Second)) || got.Report.Capabilities.BootID != second.Capabilities.BootID || got.Report.DesiredRevision != second.DesiredRevision { + t.Fatalf("expected the heartbeat columns from the second report, got %+v", got) + } + if len(got.Report.Records) != len(second.Records) { + t.Fatalf("expected %d records read back, got %d", len(second.Records), len(got.Report.Records)) + } + rewritten := !slices.Equal(records, rowVersions(t, s, "xtcp_treatment_records", "host-1")) || !slices.Equal(algorithms, rowVersions(t, s, "xtcp_treatment_agent_algorithms", "host-1")) + if rewritten != tc.expectRewrite { + t.Fatalf("expected rewrite=%t, got %t", tc.expectRewrite, rewritten) + } + }) + } +} diff --git a/pkg/treatment/controller/store/postgres/postgres.go b/pkg/treatment/controller/store/postgres/postgres.go index 750cf111e..75dab3d24 100644 --- a/pkg/treatment/controller/store/postgres/postgres.go +++ b/pkg/treatment/controller/store/postgres/postgres.go @@ -6,9 +6,11 @@ package postgres import ( "context" "database/sql" + "database/sql/driver" _ "embed" "errors" "fmt" + "hash/fnv" "strings" "time" @@ -78,6 +80,46 @@ func (s *Store) Tx(ctx context.Context, fn func(store.Repos) error) error { return tx.Commit() } +// Lease takes a session-level advisory lock on a dedicated connection, so +// it outlives any transaction and ends when fn returns or the connection +// drops. Names hash into the same key space as mutationLock; the lock a +// name maps to is stable across replicas because the hash is. +func (s *Store) Lease(ctx context.Context, name string, fn func() error) (bool, error) { + conn, err := s.db.Conn(ctx) + if err != nil { + return false, err + } + defer conn.Close() + key := leaseKey(name) + var held bool + if err = conn.QueryRowContext(ctx, "SELECT pg_try_advisory_lock($1)", key).Scan(&held); err != nil { + return false, err + } + if !held { + return false, nil + } + defer func() { + unlockCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), leaseUnlockTimeoutCst) + defer cancel() + if _, e := conn.ExecContext(unlockCtx, "SELECT pg_advisory_unlock($1)", key); e != nil { + // A pooled connection must never carry the lock into its next + // use; discarding it releases the session lock server-side. + _ = conn.Raw(func(any) error { return driver.ErrBadConn }) + } + }() + return true, fn() +} + +// leaseUnlockTimeoutCst bounds the unlock that runs after the caller's +// context may already be cancelled. +const leaseUnlockTimeoutCst = 5 * time.Second + +func leaseKey(name string) int64 { + h := fnv.New64a() + h.Write([]byte(name)) + return int64(h.Sum64()) +} + func repos(r *runner) store.Repos { return store.Repos{Agents: &agents{r}, Experiments: &experiments{r}, Evidence: &evidence{r}, Holds: &holds{r}, Audit: &audit{r}, Metrics: &metrics{r}} } diff --git a/pkg/treatment/controller/store/postgres/schema.sql b/pkg/treatment/controller/store/postgres/schema.sql index f5622f98e..2c6e9f925 100644 --- a/pkg/treatment/controller/store/postgres/schema.sql +++ b/pkg/treatment/controller/store/postgres/schema.sql @@ -14,6 +14,9 @@ CREATE TABLE IF NOT EXISTS xtcp_treatment_agents ( actuator_enabled boolean NOT NULL DEFAULT false, locally_suspended boolean NOT NULL DEFAULT false, reported_revision bigint NOT NULL DEFAULT 0, + -- Digest of the algorithm and record rows last written for this agent; + -- a heartbeat with an equal digest updates this row only. + report_digest text NOT NULL DEFAULT '', desired_revision bigint NOT NULL, desired_suspended boolean NOT NULL DEFAULT false, desired_issued_at timestamptz NOT NULL, diff --git a/pkg/treatment/controller/store/store.go b/pkg/treatment/controller/store/store.go index 63a4f310b..050426e4e 100644 --- a/pkg/treatment/controller/store/store.go +++ b/pkg/treatment/controller/store/store.go @@ -71,8 +71,20 @@ type Agents interface { // List returns agents in the location, or every agent when location is // empty, ordered by ID. List(ctx context.Context, location string) ([]AgentRef, error) - // DesiredRevisions returns every agent's current desired revision. - DesiredRevisions(ctx context.Context) (map[string]uint64, error) + // DesiredSince returns the desired revision of every agent whose + // desired state was stamped at or after since, ordered by stamp then + // ID. A zero since returns every agent. Stamps come from the store's + // own clock, so callers carry the newest stamp returned forward as the + // next since rather than comparing it with their clock. + DesiredSince(ctx context.Context, since time.Time) ([]DesiredRevision, error) +} + +// DesiredRevision is one agent's desired revision and the store's stamp of +// when it last changed. +type DesiredRevision struct { + AgentID string + Revision uint64 + UpdatedAt time.Time } // Experiments stores experiments with their cohort, phases and evidence @@ -159,10 +171,16 @@ type Repos struct { // Store is the root. Repos performs each call in its own implicit // transaction. Tx runs fn inside one transaction that holds the controller's // mutation lock, so every control-plane change is serialized across replicas; -// agent heartbeats never take it. +// agent heartbeats never take it. Lease elects which replica does periodic +// work without making any replica special. type Store interface { Repos() Repos Tx(ctx context.Context, fn func(Repos) error) error + // Lease runs fn while holding the named lease, which at most one caller + // across every replica holds at a time. When another caller holds it, + // Lease returns false without calling fn. The lease is released when fn + // returns, whatever it returns. + Lease(ctx context.Context, name string, fn func() error) (bool, error) Ping(ctx context.Context) error Close() error } diff --git a/pkg/treatment/controller/store/storetest/storetest.go b/pkg/treatment/controller/store/storetest/storetest.go index a513df912..ba95716cf 100644 --- a/pkg/treatment/controller/store/storetest/storetest.go +++ b/pkg/treatment/controller/store/storetest/storetest.go @@ -38,6 +38,7 @@ func Run(t *testing.T, open Opener) { t.Run("Holds", func(t *testing.T) { holds(t, open) }) t.Run("Audit", func(t *testing.T) { audit(t, open) }) t.Run("Tx", func(t *testing.T) { tx(t, open) }) + t.Run("Lease", func(t *testing.T) { lease(t, open) }) t.Run("Metrics", func(t *testing.T) { metrics(t, open) }) } @@ -128,10 +129,67 @@ func agents(t *testing.T, open Opener) { got, err := a.Desired(ctx, "host-1") must(t, err) sameDesired(t, "expected the saved desired state", got, d) - revs, err := a.DesiredRevisions(ctx) + revs, err := a.DesiredSince(ctx, time.Time{}) must(t, err) - if len(revs) != 1 || revs["host-1"] != 7 { - t.Fatalf("expected revision 7 for host-1, got %v", revs) + if len(revs) != 1 || revs[0].AgentID != "host-1" || revs[0].Revision != 7 || revs[0].UpdatedAt.IsZero() { + t.Fatalf("expected a stamped revision 7 for host-1, got %+v", revs) + } + }}, + {"desired-since-all", "positive: a zero watermark returns every agent, enrolled or saved, ordered by stamp then ID", func(t *testing.T, a store.Agents) { + must(t, a.Enroll(ctx, "host-2", "dc1", &treatment.DesiredState{AgentID: "host-2", Revision: 1, IssuedAt: now})) + settle() + must(t, a.Enroll(ctx, "host-1", "dc1", initial)) + settle() + must(t, a.SaveDesired(ctx, &treatment.DesiredState{AgentID: "host-2", Revision: 2, IssuedAt: now})) + got, err := a.DesiredSince(ctx, time.Time{}) + must(t, err) + if len(got) != 2 || got[0].AgentID != "host-1" || got[0].Revision != 1 || got[1].AgentID != "host-2" || got[1].Revision != 2 || !got[0].UpdatedAt.Before(got[1].UpdatedAt) { + t.Fatalf("expected host-1 rev 1 then host-2 rev 2 in stamp order, got %+v", got) + } + }}, + {"desired-since-changed", "positive: a watermark after the older stamp returns only the agent saved since", func(t *testing.T, a store.Agents) { + must(t, a.Enroll(ctx, "host-1", "dc1", initial)) + must(t, a.Enroll(ctx, "host-2", "dc1", initial)) + all, err := a.DesiredSince(ctx, time.Time{}) + must(t, err) + newest := all[len(all)-1].UpdatedAt + settle() + must(t, a.SaveDesired(ctx, &treatment.DesiredState{AgentID: "host-1", Revision: 5, IssuedAt: now})) + got, err := a.DesiredSince(ctx, newest.Add(time.Microsecond)) + must(t, err) + if len(got) != 1 || got[0].AgentID != "host-1" || got[0].Revision != 5 || !got[0].UpdatedAt.After(newest) { + t.Fatalf("expected only host-1 at revision 5, got %+v", got) + } + }}, + {"desired-since-equal", "boundary: a row stamped exactly at the watermark is included", func(t *testing.T, a store.Agents) { + must(t, a.Enroll(ctx, "host-1", "dc1", initial)) + settle() + must(t, a.Enroll(ctx, "host-2", "dc1", initial)) + all, err := a.DesiredSince(ctx, time.Time{}) + must(t, err) + got, err := a.DesiredSince(ctx, all[1].UpdatedAt) + must(t, err) + if len(got) != 1 || got[0].AgentID != all[1].AgentID || !got[0].UpdatedAt.Equal(all[1].UpdatedAt) { + t.Fatalf("expected exactly the row stamped at the watermark, got %+v", got) + } + }}, + {"desired-since-none", "boundary: a watermark past the newest stamp returns nothing without error", func(t *testing.T, a store.Agents) { + must(t, a.Enroll(ctx, "host-1", "dc1", initial)) + all, err := a.DesiredSince(ctx, time.Time{}) + must(t, err) + got, err := a.DesiredSince(ctx, all[0].UpdatedAt.Add(time.Microsecond)) + must(t, err) + if len(got) != 0 { + t.Fatalf("expected no rows, got %+v", got) + } + }}, + {"desired-since-stamp-is-store-clock", "corner: the stamp is the store's clock, not the desired state's issued_at", func(t *testing.T, a store.Agents) { + must(t, a.Enroll(ctx, "host-1", "dc1", initial)) + must(t, a.SaveDesired(ctx, &treatment.DesiredState{AgentID: "host-1", Revision: 2, IssuedAt: now.Add(-24 * time.Hour)})) + got, err := a.DesiredSince(ctx, now) + must(t, err) + if len(got) != 1 || got[0].UpdatedAt.Before(now) { + t.Fatalf("expected a current stamp despite an old issued_at, got %+v", got) } }}, {"enroll-idempotent", "corner: a second enrollment never changes location or desired state", func(t *testing.T, a store.Agents) { @@ -722,6 +780,88 @@ func tx(t *testing.T, open Opener) { }) } +// settle separates two stamps by more than a timestamptz microsecond so the +// watermark tests order them deterministically on either store. +func settle() { time.Sleep(2 * time.Millisecond) } + +func lease(t *testing.T, open Opener) { + ctx := context.Background() + t.Run("free", func(t *testing.T) { + t.Log("positive: a free lease runs fn and reports held") + s := open(t) + ran := false + held, err := s.Lease(ctx, "housekeeping", func() error { ran = true; return nil }) + must(t, err) + if !held || !ran { + t.Fatalf("expected held and run, got held=%t ran=%t", held, ran) + } + }) + t.Run("held-elsewhere", func(t *testing.T) { + t.Log("corner: while one caller holds the lease a second caller is told so and fn never runs") + s := open(t) + entered, release := make(chan struct{}), make(chan struct{}) + var wg sync.WaitGroup + wg.Go(func() { + held, err := s.Lease(ctx, "housekeeping", func() error { close(entered); <-release; return nil }) + if err != nil || !held { + t.Errorf("first holder: held=%t err=%v", held, err) + } + }) + <-entered + held, err := s.Lease(ctx, "housekeeping", func() error { t.Error("second caller ran under a held lease"); return nil }) + must(t, err) + if held { + t.Fatal("expected the second caller to be refused") + } + close(release) + wg.Wait() + held, err = s.Lease(ctx, "housekeeping", func() error { return nil }) + must(t, err) + if !held { + t.Fatal("expected the lease to be free once the holder returned") + } + }) + t.Run("released-on-error", func(t *testing.T) { + t.Log("negative: fn's error is returned with held=true and the lease is released") + s := open(t) + boom := errors.New("boom") + held, err := s.Lease(ctx, "housekeeping", func() error { return boom }) + if !held || !errors.Is(err, boom) { + t.Fatalf("expected held with the callback error, got held=%t err=%v", held, err) + } + held, err = s.Lease(ctx, "housekeeping", func() error { return nil }) + must(t, err) + if !held { + t.Fatal("expected the lease to be free after a failed pass") + } + }) + t.Run("independent-names", func(t *testing.T) { + t.Log("boundary: different names are different leases") + s := open(t) + held, err := s.Lease(ctx, "a", func() error { + inner, err := s.Lease(ctx, "b", func() error { return nil }) + if err != nil || !inner { + return fmt.Errorf("expected lease b while holding a, got held=%t err=%v", inner, err) + } + return nil + }) + must(t, err) + if !held { + t.Fatal("expected lease a") + } + }) + t.Run("cancelled-context", func(t *testing.T) { + t.Log("negative: a cancelled context fails before fn runs") + s := open(t) + cancelled, cancel := context.WithCancel(ctx) + cancel() + held, err := s.Lease(cancelled, "housekeeping", func() error { t.Error("fn ran with a cancelled context"); return nil }) + if err == nil || held { + t.Fatalf("expected an error and held=false, got held=%t err=%v", held, err) + } + }) +} + func metrics(t *testing.T, open Opener) { ctx := context.Background() t.Run("empty", func(t *testing.T) { From b6df3de9bfd0f61a7cd79886b098e1b232b96d9a Mon Sep 17 00:00:00 2001 From: "randomizedcoder dave.seddon.ca@gmail.com" Date: Sun, 25 Oct 2026 20:49:27 -0700 Subject: [PATCH 17/27] docs(treatment): trust boundaries, names and surfaces, SIGHUP semantics The design document gains a threat-model section: the four principals and what their credentials allow, defence in depth on the route itself, and the evidence boundary, where records carry a host-stamped agent ID and location over the unauthenticated telemetry pipeline. It states what bounds that today (location and cohort filters, sample minimums, freshness, human review, immutable evidence, audit) and what is not done (binding records to the boot ID and collection nonce reported at check-in), which the status document tracks. A names section fixes the vocabulary: treatment for the feature, controller for the server role, route for the kernel object; why xtcp2-treatmentctl is not folded into xtcp2ctl; the three configuration surfaces and their audiences. SIGHUP is documented as "reconcile now" only, with the controller-free treatmentctl pass beside it. The docs hub now says xtcp2 observes by default and names the controller as the one opt-in, reviewed component that changes host state. Co-Authored-By: Claude Fable 5.1 --- docs/README.md | 4 +- docs/status-tcp-controller.md | 32 +++++++++++-- docs/tcp-controller.md | 90 ++++++++++++++++++++++++++++++++++- 3 files changed, 118 insertions(+), 8 deletions(-) diff --git a/docs/README.md b/docs/README.md index 90f310645..025cb42bc 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@ # xtcp2 documentation -This is the documentation hub for **xtcp2**, a Linux network observability suite for TCP socket diagnostics, network-state inspection, Ethernet/RDMA port health, and enriched telemetry. If you just want the feature summary or to build and run it, start with the [top-level README](../README.md). This hub explains *how it works* and links to detailed feature documentation. +This is the documentation hub for **xtcp2**, a Linux network observability suite for TCP socket diagnostics, network-state inspection, Ethernet/RDMA port health, and enriched telemetry. xtcp2 observes by default and changes nothing on the host; the one exception is the opt-in, human-reviewed [TCP route treatment controller](tcp-controller.md), which an operator has to deploy and enable and whose actuator only ever touches routes it owns. If you just want the feature summary or to build and run it, start with the [top-level README](../README.md). This hub explains *how it works* and links to detailed feature documentation. ## Table of contents @@ -52,7 +52,7 @@ Combines `/proc//ns/net` discovery with bind-mounted namespaces under `/run ### [Ethernet/RDMA statistics and link health](../cmd/go-link-monitor/README.md) -`go-link-monitor` collects netdev, ethtool driver/PHY, RDMA, and host protocol statistics. It tracks a persisted up-link baseline, speed/duplex/width, flaps, errors, and collector health, exposing a sparse problem summary and detailed Prometheus category/interface paths. It is also [embedded in xtcp2 and enabled by default](design-linkmonitor-in-xtcp2.md). Optional read-only [goip HTTP paths](../cmd/go-link-monitor/IP-ARGV-PATHS-DESIGN.md) support remote inspection. Alerting systems consume the signals; the monitor does not perform remediation. +`go-link-monitor` collects netdev, ethtool driver/PHY, RDMA, and host protocol statistics. It tracks a persisted up-link baseline, speed/duplex/width, flaps, errors, and collector health, exposing a sparse problem summary and detailed Prometheus category/interface paths. It is also [embedded in xtcp2 and enabled by default](design-linkmonitor-in-xtcp2.md). Optional read-only [goip HTTP paths](../cmd/go-link-monitor/IP-ARGV-PATHS-DESIGN.md) support remote inspection. Alerting systems consume the signals; the monitor does not perform remediation. The only xtcp2 component that changes host state is the opt-in [treatment controller](tcp-controller.md), and it does so only after operator review. ### [Metadata enrichment](design-metadata-enrichment.md) diff --git a/docs/status-tcp-controller.md b/docs/status-tcp-controller.md index 18ac926b4..104551779 100644 --- a/docs/status-tcp-controller.md +++ b/docs/status-tcp-controller.md @@ -16,11 +16,11 @@ review being addressed. Items are listed in execution order. | D2 | dual-mode agent, part 2: `-treatmentAttribution` journal watcher, `treatment` build tag, `cmd/xtcp2-treatment-agent`, Nix and module wiring | done | 87321a63, fix 20f3a35a | | 5 | evidence port (`Source`, `CandidateSource`, `Warehouse`), ClickHouse as one adapter | done | caba9bc4 | | 6 | auth as interceptor with per-RPC role option | done | d7fefee4 | -| 7 | typed `xtcpnl.RuleWrite`; backend to `internal/goip/actuate` | done | this commit | -| 9 | identity/location startup check | done | this commit | +| 7 | typed `xtcpnl.RuleWrite`; backend to `internal/goip/actuate` | done | 3a2843b2 | +| 9 | identity/location startup check | done | 9372e0bf | | 10 | blocking journal lock | done | 3478d9b3 | -| 11 | scale: notification watermark, heartbeat split, leader lease | done | this commit | -| 12 | docs: threat model, naming, SIGHUP, philosophy sentence | next | | +| 11 | scale: notification watermark, heartbeat split, leader lease | done | 91ccde4a | +| 12 | docs: threat model, naming, SIGHUP, philosophy sentence | done | this commit | Dual-mode agent (library + `cmd/xtcp2-treatment-agent` + `treatment` build tag in `cmd/xtcp2`, with an `attribution` journal reader for the untagged daemon) @@ -209,8 +209,32 @@ process. - The schema is pre-release, so `report_digest` is added to the `CREATE TABLE` rather than through an `ALTER TABLE ... ADD COLUMN IF NOT EXISTS`. +## Item 12 notes + +- `docs/tcp-controller.md` gained "Trust boundaries and threat model" + (principal table, defence in depth on the route, the evidence boundary: + host-stamped agent ID and location over an unauthenticated telemetry + pipeline, what bounds it today, what is not done) and "Names and surfaces" + (`treatment` for the feature, `controller` for the server role, `route` + for the kernel object; why `xtcp2-treatmentctl` is not part of `xtcp2ctl`; + the three configuration surfaces). The naming decision is to keep the + current binary names; no renames. +- SIGHUP: documented as "reconcile now" only, in both daemon modes and the + standalone agent; the daemon has no admin RPC yet, so the signal stays + until the network-control admin API exists. `xtcp2-treatmentctl + -mode=reconcile` is the controller-free full pass. +- `docs/README.md`: the hub intro and the link-monitor paragraph now say that + xtcp2 observes by default and that the treatment controller is the one + opt-in, operator-reviewed component that changes host state. + ## TODO +- Evidence binding (threat model): the warehouse query filters by location + and cohort agent IDs but does not check the boot ID or collection nonce a + record carries against the values the agent reported at check-in. Pass the + cohort's `(agent_id, boot_id)` pairs into `analysis.sql` and drop rows that + do not match; the nonce needs a check-in field first. + - The watchdog and `pkg/treatment/local` still link gRPC, through `pkg/xtcpnl` -> `gen/go/xtcp_flat_record`, whose Go package carries that proto's gRPC and gateway stubs. Generating the flat record's service stubs diff --git a/docs/tcp-controller.md b/docs/tcp-controller.md index d9ebfc744..db767965e 100644 --- a/docs/tcp-controller.md +++ b/docs/tcp-controller.md @@ -94,6 +94,78 @@ connection) that one replica holds for the pass; the others log that they are skipping. The lease is about cost, not correctness: every write is still fenced by the transaction-level mutation lock. +## Trust boundaries and threat model + +The control plane has four kinds of principal. What each can do is decided by +the certificate it presents or the host it runs on, never by a request field. + +| Principal | Credential | Can | Cannot | +| --- | --- | --- | --- | +| Operator | mTLS certificate with `spiffe://xtcp/operator/` | propose, analyze, review, control, inspect; every action is audited under that ID | act as an agent; reuse a request ID with a different body | +| Agent | mTLS certificate with `spiffe://xtcp/agent//` | check in, fetch and watch its own desired state | speak for another agent, call an operator RPC, move to another location, or receive a route no operator reviewed | +| Controller replica | PostgreSQL and ClickHouse credentials | everything above on the operators' behalf | nothing is withheld; replicas are peers and fully trusted | +| Host root | the journal directory and the netlink socket | run the agent, the watchdog and `xtcp2-treatmentctl`; suspend locally | change another host; the actuator only touches its own table, rule priority and route protocol | + +The route itself is defended in depth: the controller cannot install anything +without an operator review, the actuator refuses routes it does not own on +readback, trials carry leases the watchdog enforces without the controller, and +a local suspension or a withdrawn desired state removes routes without any +network dependency. + +Evidence is the weakest boundary and the one to understand. Every telemetry +record carries the agent ID, location, boot ID, collection nonce and exposure +fields that the host itself stamped, and reaches ClickHouse through the +existing telemetry pipeline (Kafka or NSQ, then ClickHouse), which carries no +per-host authentication. The controller therefore trusts the warehouse to tell +it which host produced a record. A compromised or misconfigured host can emit +records that name another enrolled agent in its location and shift that host's +measured goodput, retransmission ratio or RTT. It cannot make the controller +issue a route (that needs an operator), but it can push a running trial into an +automatic rollback by faking harm, or mask real harm on a host it impersonates. + +What limits that today: evidence queries filter by the experiment's location +and the frozen cohort's agent IDs, so records naming hosts outside the cohort +are never read; per-host sample minimums and the two-minute freshness window +bound how little data can swing a verdict; every expansion and completion is a +human review of typed evidence, and completed experiments only ever request +review on drift; evidence rows are immutable once stored, keyed by ID and +digest, and every decision is in the audit log with the operator's identity. +What is not done: binding a telemetry record to the boot ID and collection +nonce the same agent reported at check-in, which would let the warehouse +query reject records a host did not plausibly produce. That is the next +hardening step for this boundary and is tracked in the status document. + +Inside the controller, the advisory mutation lock and the housekeeping lease +order work and bound cost; neither is an authorization mechanism. Request IDs +make an operator's own retries idempotent; they are no defence against a +principal holding an operator certificate, who is an operator. + +## Names and surfaces + +One feature name, `treatment`, is used for everything the host side and the +contract share: the Go packages under `pkg/treatment`, the proto packages +`xtcp_treatment.v1` and `xtcp_treatment_journal.v1`, the journal, the +`treatment` build tag, the daemon flags `-treatmentConfig` and +`-treatmentAttribution`, and the NixOS options under +`services.xtcp2.treatments`. `controller` names the server role, so the server +binary is `xtcp2-controller` and its operator client is `xtcp2-controllerctl`. +`route` is reserved for the kernel object the actuator manages and appears in +`internal/goip/actuate` and in prose, not in a binary or package name. + +`xtcp2-treatmentctl` stays separate from the daemon's `xtcp2ctl` on purpose: +it works on the journal directory as root, has no network path and must keep +working, as the watchdog, when the daemon and the controller are both down. +Folding it into a daemon-side CLI would make the independent withdrawal path +depend on the process it is meant to outlive. + +There are three configuration surfaces, one per audience. Operators configure +`xtcp2-controller` with flags, a DSN file and an optional discovery-template +JSON file, all read at start. Hosts share one `controller-agent.json` between +the agent, the watchdog and `xtcp2-treatmentctl`, so all three agree on the +journal directory and route table. Operator requests are the RPC messages +themselves in protobuf text, which `xtcp2-controllerctl -file` sends without +a schema of its own. + ## Supported routes and TCP semantics The first actuator supports IPv4 `/32` and IPv6 `/128` destinations in the host @@ -442,6 +514,19 @@ daemon's label and logs the disagreement once. No label is rewritten silently. `agent.New` likewise refuses a config whose `location`/`agent_id` differ from the certificate's URI SAN, naming both. +SIGHUP means "reconcile now" on the host side, and nothing else. In `xtcp2` +it wakes the embedded agent (check in, then reconcile) or the journal watcher +(re-read the snapshot); it never rebaselines link state, which is SIGUSR1, and +never reloads configuration or restarts collection. The standalone agent +handles SIGHUP the same way. The daemon has no admin RPC surface today, so a +signal is the only in-process nudge; the +[network-control gRPC design](design-network-control-grpc.md) describes a +future admin API, and when it lands "reconcile now" becomes a method there with +SIGHUP kept as its signal equivalent. `xtcp2-treatmentctl -mode=reconcile` +is different: it runs one full reconcile pass from the journal through the +actuator in its own process, without the controller, and prints the resulting +snapshot, which is the tool for a host whose agent is stopped. + A build without the `treatment` tag rejects `-treatmentConfig` at startup with a message naming both remedies, the same way a slim build rejects an enricher it lacks: a missing build tag is a property of the artifact and must not @@ -596,8 +681,9 @@ Nix unit/race/benchmark targets with isolated PostgreSQL and ClickHouse fixtures Tests cover durable intent/crash recovery, stale generations, predecessor expiry, local/broad holds, concurrent controller reviews, full human-reviewed rollout, -early safety withdrawal, actual algorithm attribution, and 10k-host cohort and -notification behavior. Optional tests exercise real PostgreSQL, ClickHouse SQL, +early safety withdrawal, actual algorithm attribution, 10k-host cohort and +notification behavior, the incremental notification watermark, the heartbeat +split and the housekeeping lease. Optional tests exercise real PostgreSQL, ClickHouse SQL, and IPv4/IPv6 netlink mutation in a disposable namespace: ```sh From c3981ff182cdd41025e3a6d3d0c6577296abc3b0 Mon Sep 17 00:00:00 2001 From: "randomizedcoder dave.seddon.ca@gmail.com" Date: Sun, 25 Oct 2026 21:52:33 -0700 Subject: [PATCH 18/27] lint(treatment): clear the comprehensive tier The lint baseline gate failed on 107 findings in the new controller, agent and actuator code. Every one is fixed structurally; no nolint directives, threshold changes or new exclusions. - Complexity: Reconciler.Run becomes acceptDesired plus a per-pass type (assignment, withdraw, tombstones, settle); Maintain becomes maintainOne over observe, fetchEvidence, driftPass and trialPass; Control splits into controlExperiment and controlHolds; Evaluate into windowOutcome, cohortSamples and compare; Backend.Plan into destination, rejectLocal, bestMainRoute and supportedAttrs; the controller main into flag parsing, store/warehouse setup and a housekeep loop; the experiments get into loadCohort/loadPhases. - Errors: deferred closes, unlocks, rollbacks and Lease's unlock now report their failures (Rollback after Commit and a deliberately discarded connection are expected and ignored); scan-error paths join rows.Close; the journal's atomic write removes its temporary file only on failure. - Contexts: the integration fixture threads the request context; the controller listens through net.ListenConfig; pgtest uses ExecContext and the test's context. - Attribution polls sooner (5s) while the journal is unreadable. - The ClickHouse SQL test finds clickhouse on PATH (the Nix check puts it there) instead of an environment-supplied path. - US spelling, exhaustive switches, shared constants, prealloc. Co-Authored-By: Claude Opus 5.5 --- cmd/xtcp2-controller/main.go | 266 ++++++++----- cmd/xtcp2-controllerctl/main.go | 6 +- cmd/xtcp2-treatment-agent/main.go | 8 +- cmd/xtcp2-treatmentctl/main.go | 4 +- docs/tcp-controller-testing.md | 5 +- docs/tcp-controller.md | 4 +- internal/goip/actuate/actuate.go | 15 +- internal/goip/actuate/live_test.go | 6 +- internal/goip/actuate/routes.go | 189 +++++---- internal/goip/actuate/rule_test.go | 4 +- internal/goip/actuate/rules.go | 18 +- nix/tests/tcp-controller/quality.nix | 2 +- pkg/treatment/agent/agent.go | 4 +- pkg/treatment/agent/watch_linux.go | 6 +- pkg/treatment/attribution/watch.go | 32 +- pkg/treatment/attribution/watch_test.go | 6 +- pkg/treatment/boundaries_test.go | 2 +- pkg/treatment/controller/auth.go | 9 +- pkg/treatment/controller/auth_test.go | 2 +- pkg/treatment/controller/discovery.go | 8 +- .../controller/discovery_contracts_test.go | 9 +- .../evidence/clickhouse/clickhouse_test.go | 6 +- pkg/treatment/controller/housekeep_test.go | 6 +- pkg/treatment/controller/maintenance.go | 252 ++++++------ .../controller/notifications_test.go | 2 +- pkg/treatment/controller/server.go | 175 +++++---- pkg/treatment/controller/server_test.go | 4 +- pkg/treatment/controller/status.go | 10 +- .../controller/store/memory/memory.go | 4 +- .../controller/store/pgtest/pgtest.go | 29 +- .../controller/store/postgres/agents.go | 23 +- .../controller/store/postgres/columns.go | 8 +- .../controller/store/postgres/evidence.go | 5 +- .../controller/store/postgres/experiments.go | 74 ++-- .../store/postgres/heartbeat_test.go | 3 +- .../controller/store/postgres/postgres.go | 52 ++- .../controller/store/storetest/storetest.go | 280 ++++++++------ pkg/treatment/enums.go | 11 +- pkg/treatment/experiment/experiment.go | 131 ++++--- pkg/treatment/identity/identity_test.go | 4 +- pkg/treatment/identity/mtls_test.go | 13 +- pkg/treatment/reconcile/journal.go | 46 ++- pkg/treatment/reconcile/journal_test.go | 3 +- pkg/treatment/reconcile/journalpb_test.go | 2 +- pkg/treatment/reconcile/reconcile.go | 366 ++++++++++-------- pkg/treatment/validate.go | 2 +- pkg/treatment/validate_test.go | 18 +- pkg/treatment/wire/rules_test.go | 2 +- tools/tcp-controller-integration/main.go | 134 ++++--- 49 files changed, 1378 insertions(+), 892 deletions(-) diff --git a/cmd/xtcp2-controller/main.go b/cmd/xtcp2-controller/main.go index 2f24e7409..27ce825da 100644 --- a/cmd/xtcp2-controller/main.go +++ b/cmd/xtcp2-controller/main.go @@ -4,6 +4,7 @@ import ( "context" "crypto/tls" "encoding/json" + "errors" "flag" "fmt" "log" @@ -32,146 +33,165 @@ func main() { os.Exit(1) } } -func run() error { - listen := flag.String("listen", ":9443", "mTLS gRPC listener") - statusListen := flag.String("statusListen", "127.0.0.1:9444", "mTLS status/metrics HTTP listener") - cert := flag.String("cert", "", "server certificate") - key := flag.String("key", "", "server private key") - ca := flag.String("ca", "", "client CA") - dsnFile := flag.String("databaseDSNFile", "", "file containing PostgreSQL DSN (or XTCP_CONTROLLER_DATABASE_DSN)") - migrate := flag.Bool("migrate", false, "apply additive PostgreSQL schema before serving") - clickhouse := flag.String("clickhouse", "", "ClickHouse HTTP(S) endpoint; absent disables analysis and lease renewal") - table := flag.String("clickhouseTable", "xtcp.xtcp_flat_records_v2", "source telemetry table") - templateFile := flag.String("discoveryTemplates", "", "optional JSON array of candidate discovery templates") - discoveryInterval := flag.Duration("discoveryInterval", 30*time.Minute, "candidate discovery interval (at least one minute; checked on maintenance ticks)") - discoveryWindow := flag.Duration("discoveryWindow", time.Hour, "candidate discovery lookback (one minute to seven days)") + +// options are the controller's command-line settings. +type options struct { + listen, statusListen string + cert, key, ca string + dsnFile string + migrate bool + clickhouse, table string + templateFile string + discoveryInterval, discoveryWindow time.Duration +} + +func parseFlags() (*options, error) { + o := new(options) + flag.StringVar(&o.listen, "listen", ":9443", "mTLS gRPC listener") + flag.StringVar(&o.statusListen, "statusListen", "127.0.0.1:9444", "mTLS status/metrics HTTP listener") + flag.StringVar(&o.cert, "cert", "", "server certificate") + flag.StringVar(&o.key, "key", "", "server private key") + flag.StringVar(&o.ca, "ca", "", "client CA") + flag.StringVar(&o.dsnFile, "databaseDSNFile", "", "file containing PostgreSQL DSN (or XTCP_CONTROLLER_DATABASE_DSN)") + flag.BoolVar(&o.migrate, "migrate", false, "apply additive PostgreSQL schema before serving") + flag.StringVar(&o.clickhouse, "clickhouse", "", "ClickHouse HTTP(S) endpoint; absent disables analysis and lease renewal") + flag.StringVar(&o.table, "clickhouseTable", "xtcp.xtcp_flat_records_v2", "source telemetry table") + flag.StringVar(&o.templateFile, "discoveryTemplates", "", "optional JSON array of candidate discovery templates") + flag.DurationVar(&o.discoveryInterval, "discoveryInterval", 30*time.Minute, "candidate discovery interval (at least one minute; checked on maintenance ticks)") + flag.DurationVar(&o.discoveryWindow, "discoveryWindow", time.Hour, "candidate discovery lookback (one minute to seven days)") flag.Parse() - if *discoveryInterval < time.Minute || *discoveryInterval > 24*time.Hour || *discoveryWindow < time.Minute || *discoveryWindow > 7*24*time.Hour { - return fmt.Errorf("discovery interval must be 1m..24h and window 1m..168h") + if o.discoveryInterval < time.Minute || o.discoveryInterval > 24*time.Hour || o.discoveryWindow < time.Minute || o.discoveryWindow > 7*24*time.Hour { + return nil, fmt.Errorf("discovery interval must be 1m..24h and window 1m..168h") } - ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT) - defer cancel() + return o, nil +} + +// dsn reads the PostgreSQL DSN from the file flag or the environment. +func (o *options) dsn() (string, error) { dsn := os.Getenv("XTCP_CONTROLLER_DATABASE_DSN") - if *dsnFile != "" { - b, e := os.ReadFile(*dsnFile) + if o.dsnFile != "" { + b, e := os.ReadFile(o.dsnFile) if e != nil { - return e + return "", e } dsn = strings.TrimSpace(string(b)) } if dsn == "" { - return fmt.Errorf("PostgreSQL DSN required") + return "", fmt.Errorf("PostgreSQL DSN required") } - tlsConfig, e := identity.TLSConfig(*cert, *key, *ca, "", true) - if e != nil { - return e + return dsn, nil +} + +// templates loads the discovery templates, which need a warehouse. +func (o *options) templates(warehouse *chwarehouse.Client) ([]controller.DiscoveryTemplate, error) { + if o.templateFile == "" { + return nil, nil } - db, e := postgres.Open(ctx, dsn) + b, e := os.ReadFile(o.templateFile) if e != nil { - return e + return nil, e } - defer db.Close() - if *migrate { - if e = db.Migrate(ctx); e != nil { - return e - } + var templates []controller.DiscoveryTemplate + if e = json.Unmarshal(b, &templates); e != nil { + return nil, e } - s := &controller.Server{Store: db, Hub: controller.NewNotificationHub(), DiscoveryWindow: *discoveryWindow} - // The warehouse is one adapter behind two ports: evidence.Source for - // analysis and lease renewal, evidence.Warehouse for discovery. - var warehouse *chwarehouse.Client - var source evidence.Source - if *clickhouse != "" { - warehouse = &chwarehouse.Client{URL: *clickhouse, Table: *table, Username: os.Getenv("XTCP_CLICKHOUSE_USER"), Password: os.Getenv("XTCP_CLICKHOUSE_PASSWORD")} - source = warehouse + if warehouse == nil { + return nil, fmt.Errorf("discovery requires ClickHouse") } - var templates []controller.DiscoveryTemplate - s.EvidenceSource = source - if *templateFile != "" { - b, e := os.ReadFile(*templateFile) - if e != nil { - return e - } - if e = json.Unmarshal(b, &templates); e != nil { - return e + return templates, nil +} + +// openStore connects to PostgreSQL and applies the schema when asked. +func (o *options) openStore(ctx context.Context) (*postgres.Store, error) { + dsn, err := o.dsn() + if err != nil { + return nil, err + } + db, err := postgres.Open(ctx, dsn) + if err != nil { + return nil, err + } + if o.migrate { + if err = db.Migrate(ctx); err != nil { + return nil, errors.Join(err, db.Close()) } - if warehouse == nil { - return fmt.Errorf("discovery requires ClickHouse") + } + return db, nil +} + +// warehouse is one adapter behind two ports: evidence.Source for analysis +// and lease renewal, evidence.Warehouse for discovery. Both are nil without +// -clickhouse. +func (o *options) warehouse() (*chwarehouse.Client, evidence.Source) { + if o.clickhouse == "" { + return nil, nil + } + w := &chwarehouse.Client{URL: o.clickhouse, Table: o.table, Username: os.Getenv("XTCP_CLICKHOUSE_USER"), Password: os.Getenv("XTCP_CLICKHOUSE_PASSWORD")} + return w, w +} + +func run() (err error) { + o, err := parseFlags() + if err != nil { + return err + } + ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT) + defer cancel() + tlsConfig, e := identity.TLSConfig(o.cert, o.key, o.ca, "", true) + if e != nil { + return e + } + db, e := o.openStore(ctx) + if e != nil { + return e + } + defer func() { + if cerr := db.Close(); cerr != nil { + log.Printf("database close: %v", cerr) } + }() + warehouse, source := o.warehouse() + s := &controller.Server{Store: db, Hub: controller.NewNotificationHub(), DiscoveryWindow: o.discoveryWindow, EvidenceSource: source} + templates, e := o.templates(warehouse) + if e != nil { + return e } - listener, e := net.Listen("tcp", *listen) + // grpcServer.Serve owns the listener and closes it when it stops. + listener, e := (&net.ListenConfig{}).Listen(ctx, "tcp", o.listen) if e != nil { return e } - defer listener.Close() grpcServer := grpc.NewServer(append(controller.GRPCOptions(), grpc.Creds(credentials.NewTLS(tlsConfig)))...) pb.RegisterAgentControlServiceServer(grpcServer, s) pb.RegisterOperatorServiceServer(grpcServer, s) var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done(); s.RunNotifications(ctx) }() - go func() { - defer wg.Done() - tick := time.NewTicker(time.Minute) - defer tick.Stop() - nextDiscovery := time.Now() - leader := false - for { - // Discovery is due on this replica's own cadence whether or not - // it holds the lease, so a follower that becomes leader does not - // run a backlog of passes. - var due evidence.Warehouse - if warehouse != nil && !time.Now().Before(nextDiscovery) { - due = warehouse - // Keep the cadence independent of query duration; otherwise a - // one-minute interval can accidentally skip every other tick. - for !nextDiscovery.After(time.Now()) { - nextDiscovery = nextDiscovery.Add(*discoveryInterval) - } - } - held, e := s.Housekeep(ctx, source, due, templates) - if e != nil { - log.Printf("controller housekeeping: %v", e) - } - // A failed Lease call reports held=false with an error; that is - // not a follower, so the leader state is kept until it answers. - if (e == nil || held) && held != leader { - leader = held - if held { - log.Print("controller housekeeping: this replica holds the lease") - } else { - log.Print("controller housekeeping: another replica holds the lease; skipping passes") - } - } - select { - case <-ctx.Done(): - return - case <-tick.C: - } - } - }() - statusServer := &http.Server{Addr: *statusListen, Handler: s.StatusHandler(), TLSConfig: tlsConfig.Clone(), ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 15 * time.Second, WriteTimeout: 15 * time.Second, IdleTimeout: time.Minute} - errors := make(chan error, 2) - go func() { errors <- grpcServer.Serve(listener) }() - if *statusListen != "" { + go func() { defer wg.Done(); housekeep(ctx, s, source, warehouse, templates, o.discoveryInterval) }() + statusServer := &http.Server{Addr: o.statusListen, Handler: s.StatusHandler(), TLSConfig: tlsConfig.Clone(), ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 15 * time.Second, WriteTimeout: 15 * time.Second, IdleTimeout: time.Minute} + failed := make(chan error, 2) + go func() { failed <- grpcServer.Serve(listener) }() + if o.statusListen != "" { go func() { - l, e := tls.Listen("tcp", *statusListen, tlsConfig.Clone()) + l, e := (&net.ListenConfig{}).Listen(ctx, "tcp", o.statusListen) if e != nil { - errors <- e + failed <- e return } - errors <- statusServer.Serve(l) + failed <- statusServer.Serve(tls.NewListener(l, statusServer.TLSConfig)) }() } - log.Printf("xtcp2 controller listening on %s; human review required", *listen) + log.Printf("xtcp2 controller listening on %s; human review required", o.listen) select { case <-ctx.Done(): - case e = <-errors: + case e = <-failed: cancel() } shutdown, done := context.WithTimeout(context.Background(), 5*time.Second) defer done() - _ = statusServer.Shutdown(shutdown) + if serr := statusServer.Shutdown(shutdown); serr != nil { + log.Printf("status listener shutdown: %v", serr) + } stopped := make(chan struct{}) go func() { grpcServer.GracefulStop(); close(stopped) }() select { @@ -185,3 +205,45 @@ func run() error { } return e } + +// housekeep runs maintenance every minute, and discovery on its own +// cadence, under the housekeeping lease until ctx is done. +func housekeep(ctx context.Context, s *controller.Server, source evidence.Source, warehouse *chwarehouse.Client, templates []controller.DiscoveryTemplate, interval time.Duration) { + tick := time.NewTicker(time.Minute) + defer tick.Stop() + nextDiscovery := time.Now() + leader := false + for { + // Discovery is due on this replica's own cadence whether or not + // it holds the lease, so a follower that becomes leader does not + // run a backlog of passes. + var due evidence.Warehouse + if warehouse != nil && !time.Now().Before(nextDiscovery) { + due = warehouse + // Keep the cadence independent of query duration; otherwise a + // one-minute interval can accidentally skip every other tick. + for !nextDiscovery.After(time.Now()) { + nextDiscovery = nextDiscovery.Add(interval) + } + } + held, e := s.Housekeep(ctx, source, due, templates) + if e != nil { + log.Printf("controller housekeeping: %v", e) + } + // A failed Lease call reports held=false with an error; that is + // not a follower, so the leader state is kept until it answers. + if (e == nil || held) && held != leader { + leader = held + if held { + log.Print("controller housekeeping: this replica holds the lease") + } else { + log.Print("controller housekeeping: another replica holds the lease; skipping passes") + } + } + select { + case <-ctx.Done(): + return + case <-tick.C: + } + } +} diff --git a/cmd/xtcp2-controllerctl/main.go b/cmd/xtcp2-controllerctl/main.go index 2bbbb3b41..5a2615442 100644 --- a/cmd/xtcp2-controllerctl/main.go +++ b/cmd/xtcp2-controllerctl/main.go @@ -40,7 +40,11 @@ func run() error { if e != nil { return e } - defer conn.Close() + defer func() { + if cerr := conn.Close(); cerr != nil { + log.Printf("close: %v", cerr) + } + }() ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() client := pb.NewOperatorServiceClient(conn) diff --git a/cmd/xtcp2-treatment-agent/main.go b/cmd/xtcp2-treatment-agent/main.go index 30660dc2f..d28debbb0 100644 --- a/cmd/xtcp2-treatment-agent/main.go +++ b/cmd/xtcp2-treatment-agent/main.go @@ -33,7 +33,7 @@ func main() { } } -func run() error { +func run() (err error) { config := flag.String("config", "/etc/xtcp2/controller-agent.json", "agent configuration (shared with xtcp2-treatmentctl)") showVersion := flag.Bool("version", false, "print version and exit") flag.Parse() @@ -51,7 +51,11 @@ func run() error { if err != nil { return err } - defer a.Close() + defer func() { + if cerr := a.Close(); cerr != nil && err == nil { + err = cerr + } + }() hup := make(chan os.Signal, 1) signal.Notify(hup, syscall.SIGHUP) defer signal.Stop(hup) diff --git a/cmd/xtcp2-treatmentctl/main.go b/cmd/xtcp2-treatmentctl/main.go index 5dad3e3ae..845b6bb9b 100644 --- a/cmd/xtcp2-treatmentctl/main.go +++ b/cmd/xtcp2-treatmentctl/main.go @@ -81,7 +81,9 @@ func run() error { s, e := h.Reconciler.Run(ctx, nil, reconcile.ModeFull) if s != nil { if b, err := reconcile.MarshalText(s); err == nil { - os.Stdout.Write(b) + if _, werr := os.Stdout.Write(b); werr != nil && e == nil { + e = werr + } } } return e diff --git a/docs/tcp-controller-testing.md b/docs/tcp-controller-testing.md index 739def973..5cccdc4a2 100644 --- a/docs/tcp-controller-testing.md +++ b/docs/tcp-controller-testing.md @@ -33,8 +33,9 @@ CGO_ENABLED=1 go test -race -ldflags=-checklinkname=0 -count=10 \ go test -ldflags=-checklinkname=0 -run '^$' -bench . -benchmem ./pkg/treatment/... ``` -Without `XTCP_CONTROLLER_TEST_DSN` and `XTCP_CLICKHOUSE_TEST_BINARY`, direct Go -commands skip the database tests. The Nix targets supply both automatically. +Without `XTCP_CONTROLLER_TEST_DSN`, direct Go commands skip the PostgreSQL +tests; without a `clickhouse` binary on `PATH` they skip the ClickHouse SQL +test. The Nix targets supply both automatically. ## Coverage and test contracts diff --git a/docs/tcp-controller.md b/docs/tcp-controller.md index db767965e..ece1735cc 100644 --- a/docs/tcp-controller.md +++ b/docs/tcp-controller.md @@ -690,8 +690,8 @@ and IPv4/IPv6 netlink mutation in a disposable namespace: go test ./pkg/treatment/... ./internal/goip/... XTCP_CONTROLLER_TEST_DSN='host=... dbname=... user=... sslmode=...' \ go test ./pkg/treatment/controller/... -XTCP_CLICKHOUSE_TEST_BINARY=/path/to/clickhouse \ - go test ./pkg/treatment/controller -run ClickHouse +PATH=/path/to/clickhouse/bin:$PATH \ + go test ./pkg/treatment/controller/evidence/clickhouse -run ClickHouse XTCP_TREATMENT_LIVE_TEST=1 go test ./internal/goip/actuate -run '^TestTreatmentLiveNamespace$' ``` diff --git a/internal/goip/actuate/actuate.go b/internal/goip/actuate/actuate.go index a63d08c1b..be18d3b86 100644 --- a/internal/goip/actuate/actuate.go +++ b/internal/goip/actuate/actuate.go @@ -64,8 +64,9 @@ type Backend struct { ruleCache map[uint8]error } -// Cache one consistent read pass. Mutations invalidate their table before -// readback; the next reconciliation refreshes all forwarding/rule state. +// BeginReconcile caches one consistent read pass. Mutations invalidate their +// table before readback; the next reconciliation refreshes all forwarding and +// rule state. func (b *Backend) BeginReconcile() { b.routeCache = map[uint64][][]byte{} b.ruleCache = map[uint8]error{} @@ -90,15 +91,19 @@ func (b *Backend) reserved() error { return nil } -func withSource(ctx context.Context, fn func(*goip.NetlinkSource, *service.Service) error) error { - if err := ctx.Err(); err != nil { +func withSource(ctx context.Context, fn func(*goip.NetlinkSource, *service.Service) error) (err error) { + if err = ctx.Err(); err != nil { return err } s, err := goip.OpenNetlinkTimeout(2 * time.Second) if err != nil { return err } - defer s.Close() + defer func() { + if cerr := s.Close(); cerr != nil && err == nil { + err = cerr + } + }() seq := uint32(0) svc := service.New(s, func() uint32 { seq++; return seq }) return fn(s, svc) diff --git a/internal/goip/actuate/live_test.go b/internal/goip/actuate/live_test.go index 5b557d3d6..0c17d7a9e 100644 --- a/internal/goip/actuate/live_test.go +++ b/internal/goip/actuate/live_test.go @@ -21,7 +21,11 @@ func TestTreatmentLiveNamespace(t *testing.T) { t.Fatal(e) } defer original.Close() - cmd := exec.Command("unshare", "--user", "--map-root-user", "--net", os.Args[0], "-test.run=^TestTreatmentLiveNamespace$", "-test.v") + self, e := os.Executable() + if e != nil { + t.Fatal(e) + } + cmd := exec.Command("unshare", "--user", "--map-root-user", "--net", self, "-test.run=^TestTreatmentLiveNamespace$", "-test.v") cmd.ExtraFiles = []*os.File{original} cmd.Env = append(os.Environ(), "XTCP_TREATMENT_CHILD_NS=1") out, e := cmd.CombinedOutput() diff --git a/internal/goip/actuate/routes.go b/internal/goip/actuate/routes.go index 45ea7732b..0f59552cc 100644 --- a/internal/goip/actuate/routes.go +++ b/internal/goip/actuate/routes.go @@ -36,6 +36,8 @@ func (b *Backend) routeBodies(svc *service.Service, f uint8, table uint32) ([][] return v, e } +// Plan resolves the treatment's destination against the kernel's baseline +// routing and returns the route body that Install, Verify and Remove act on. func (b *Backend) Plan(ctx context.Context, t *RouteTreatment) (out []byte, err error) { if err = b.valid(); err != nil { return nil, err @@ -43,98 +45,140 @@ func (b *Backend) Plan(ctx context.Context, t *RouteTreatment) (out []byte, err if t == nil || t.Settings == nil { return nil, errors.New("missing route settings") } + ip, family, err := b.destination(t) + if err != nil { + return nil, err + } + err = withSource(ctx, func(_ *goip.NetlinkSource, svc *service.Service) error { + if e := b.checkRules(svc, family); e != nil { + return e + } + if e := b.rejectLocal(svc, family, ip); e != nil { + return e + } + routes, e := b.routeBodies(svc, family, unix.RT_TABLE_MAIN) + if e != nil { + return e + } + best, bestRaw, e := bestMainRoute(routes, family, ip) + if e != nil { + return e + } + if e = supportedAttrs(bestRaw); e != nil { + return e + } + out, e = b.planRoute(t, *best) + return e + }) + return out, err +} + +// destination validates the treatment's destination and congestion control +// and returns the address with its family. +func (b *Backend) destination(t *RouteTreatment) (netip.Addr, uint8, error) { p, err := netip.ParsePrefix(t.Destination) if err != nil || p != p.Masked() || p.Bits() != p.Addr().BitLen() || p.Addr().Is4In6() || !p.Addr().IsGlobalUnicast() || p.Addr().IsLoopback() { - return nil, errors.New("expected canonical unicast host prefix") + return netip.Addr{}, 0, errors.New("expected canonical unicast host prefix") } ip := p.Addr() for _, x := range b.Protected { if x.Contains(ip) { - return nil, errors.New("protected destination") + return netip.Addr{}, 0, errors.New("protected destination") } } if c := t.Settings.GetCongestionControl(); c != "" { available, e := os.ReadFile("/proc/sys/net/ipv4/tcp_available_congestion_control") if e != nil { - return nil, e + return netip.Addr{}, 0, e } if !slices.Contains(strings.Fields(string(available)), c) { - return nil, fmt.Errorf("congestion algorithm %q not loaded", c) + return netip.Addr{}, 0, fmt.Errorf("congestion algorithm %q not loaded", c) } } family := uint8(unix.AF_INET) if ip.Is6() { family = unix.AF_INET6 } - err = withSource(ctx, func(_ *goip.NetlinkSource, svc *service.Service) error { - if e := b.checkRules(svc, family); e != nil { - return e - } - locals, e := b.routeBodies(svc, family, unix.RT_TABLE_LOCAL) + return ip, family, nil +} + +// rejectLocal fails when the local table claims the destination. +func (b *Backend) rejectLocal(svc *service.Service, family uint8, ip netip.Addr) error { + locals, e := b.routeBodies(svc, family, unix.RT_TABLE_LOCAL) + if e != nil { + return e + } + for _, raw := range locals { + r, e := xtcpnl.ParseNewRoute(raw) if e != nil { return e } - for _, raw := range locals { - r, e := xtcpnl.ParseNewRoute(raw) - if e != nil { - return e - } - if a, ok := netip.AddrFromSlice(r.Dst); ok && netip.PrefixFrom(a, int(r.DstLen)).Contains(ip) { - return errors.New("destination is local/broadcast") - } + if a, ok := netip.AddrFromSlice(r.Dst); ok && netip.PrefixFrom(a, int(r.DstLen)).Contains(ip) { + return errors.New("destination is local/broadcast") } - routes, e := b.routeBodies(svc, family, unix.RT_TABLE_MAIN) + } + return nil +} + +// bestMainRoute is the longest-prefix, lowest-metric main-table route that +// covers ip. Two routes tied on both are ambiguous and rejected. +func bestMainRoute(routes [][]byte, family uint8, ip netip.Addr) (*xtcpnl.RouteInfo, []byte, error) { + var best *xtcpnl.RouteInfo + var bestRaw []byte + ambiguous := false + for _, v := range routes { + r, e := xtcpnl.ParseNewRoute(v) if e != nil { - return e + return nil, nil, e } - var best *xtcpnl.RouteInfo - var bestRaw []byte - ambiguous := false - for _, v := range routes { - r, e := xtcpnl.ParseNewRoute(v) - if e != nil { - return e - } - a, ok := netip.AddrFromSlice(r.Dst) - if r.DstLen == 0 { - if family == unix.AF_INET { - a = netip.IPv4Unspecified() - } else { - a = netip.IPv6Unspecified() - } - ok = true - } - if !ok || !netip.PrefixFrom(a, int(r.DstLen)).Contains(ip) { - continue - } - if best == nil || r.DstLen > best.DstLen || r.DstLen == best.DstLen && r.Priority < best.Priority { - best = &r - bestRaw = v - ambiguous = false - } else if r.DstLen == best.DstLen && r.Priority == best.Priority { - ambiguous = true - } + if !routeCovers(&r, family, ip) { + continue } - if best == nil || ambiguous { - return errors.New("no unique baseline route") + switch { + case best == nil || r.DstLen > best.DstLen || r.DstLen == best.DstLen && r.Priority < best.Priority: + best = &r + bestRaw = v + ambiguous = false + case r.DstLen == best.DstLen && r.Priority == best.Priority: + ambiguous = true } - unsupported := false - if e = xtcpnl.WalkRTAttrs(bestRaw[12:], func(typ uint16, _ []byte) { - switch typ { - case unix.RTA_DST, unix.RTA_GATEWAY, unix.RTA_OIF, unix.RTA_PREFSRC, unix.RTA_PRIORITY, unix.RTA_PREF, unix.RTA_CACHEINFO, unix.RTA_TABLE, unix.RTA_METRICS: - default: - unsupported = true - } - }); e != nil { - return e + } + if best == nil || ambiguous { + return nil, nil, errors.New("no unique baseline route") + } + return best, bestRaw, nil +} + +// routeCovers reports whether the route's destination prefix contains ip. A +// zero-length destination is the family's default route. +func routeCovers(r *xtcpnl.RouteInfo, family uint8, ip netip.Addr) bool { + a, ok := netip.AddrFromSlice(r.Dst) + if r.DstLen == 0 { + a, ok = netip.IPv4Unspecified(), true + if family == unix.AF_INET6 { + a = netip.IPv6Unspecified() } - if unsupported { - return errors.New("baseline route has unsupported attributes") + } + return ok && netip.PrefixFrom(a, int(r.DstLen)).Contains(ip) +} + +// supportedAttrs fails when the baseline route carries attributes planRoute +// cannot reproduce. +func supportedAttrs(raw []byte) error { + unsupported := false + if e := xtcpnl.WalkRTAttrs(raw[12:], func(typ uint16, _ []byte) { + switch typ { + case unix.RTA_DST, unix.RTA_GATEWAY, unix.RTA_OIF, unix.RTA_PREFSRC, unix.RTA_PRIORITY, unix.RTA_PREF, unix.RTA_CACHEINFO, unix.RTA_TABLE, unix.RTA_METRICS: + default: + unsupported = true } - out, e = b.planRoute(t, *best) + }); e != nil { return e - }) - return out, err + } + if unsupported { + return errors.New("baseline route has unsupported attributes") + } + return nil } // planRoute deliberately rejects route forms whose forwarding semantics this @@ -252,6 +296,13 @@ func (b *Backend) current(svc *service.Service, body []byte) (bool, bool, error) return found, match, nil } +// routeOp operations. +const ( + opInstallCst = "install" + opVerifyCst = "verify" + opRemoveCst = "remove" +) + func (b *Backend) routeOp(ctx context.Context, body []byte, op string) error { if err := b.valid(); err != nil { return err @@ -264,17 +315,17 @@ func (b *Backend) routeOp(ctx context.Context, body []byte, op string) error { if found && !match { return errors.New("owned route changed externally; refusing to overwrite/delete") } - if op == "verify" { + if op == opVerifyCst { if !found { return errors.New("route missing after application") } return nil } - if op == "install" && found || op == "remove" && !found { + if op == opInstallCst && found || op == opRemoveCst && !found { return nil } typ, flags := uint16(unix.RTM_NEWROUTE), uint16(unix.NLM_F_ACK|unix.NLM_F_CREATE|unix.NLM_F_EXCL) - if op == "remove" { + if op == opRemoveCst { typ = unix.RTM_DELROUTE flags = unix.NLM_F_ACK } @@ -290,7 +341,7 @@ func (b *Backend) routeOp(ctx context.Context, body []byte, op string) error { if e != nil { return e } - if op == "install" && (!found || !match) || op == "remove" && found { + if op == opInstallCst && (!found || !match) || op == opRemoveCst && found { return errors.New("route readback disagrees with mutation") } return nil @@ -298,13 +349,13 @@ func (b *Backend) routeOp(ctx context.Context, body []byte, op string) error { } func (b *Backend) Install(ctx context.Context, body []byte) error { - return b.routeOp(ctx, body, "install") + return b.routeOp(ctx, body, opInstallCst) } func (b *Backend) Verify(ctx context.Context, body []byte) error { - return b.routeOp(ctx, body, "verify") + return b.routeOp(ctx, body, opVerifyCst) } func (b *Backend) Remove(ctx context.Context, body []byte) error { - return b.routeOp(ctx, body, "remove") + return b.routeOp(ctx, body, opRemoveCst) } diff --git a/internal/goip/actuate/rule_test.go b/internal/goip/actuate/rule_test.go index 640fb8a7f..72c7e8340 100644 --- a/internal/goip/actuate/rule_test.go +++ b/internal/goip/actuate/rule_test.go @@ -59,7 +59,7 @@ func TestTreatmentRuleRequest(t *testing.T) { IifName: "lo", HasIifName: true, IPProto: unix.IPPROTO_TCP, HasIPProto: true, Protocol: 192, HasProtocol: true}, }, { - description: "corner: a disabled backend still describes its rule, so readback can recognise leftovers before Enable", + description: "corner: a disabled backend still describes its rule, so readback can recognize leftovers before Enable", backend: Backend{Table: 10002, Priority: 30000, Protocol: 242}, family: unix.AF_INET, wantType: unix.RTM_NEWRULE, wantFlags: addAck, expected: owned(unix.AF_INET, unix.FR_ACT_TO_TBL, 10002), @@ -121,7 +121,7 @@ func TestTreatmentRuleRequest(t *testing.T) { // Ownership is decided against what the kernel reports, which is // always the installed (add) form; a delete carries no action. if !tc.del && !tc.backend.ownRule(got) { - t.Fatalf("%s: the backend does not recognise its own rule", tc.description) + t.Fatalf("%s: the backend does not recognize its own rule", tc.description) } }) } diff --git a/internal/goip/actuate/rules.go b/internal/goip/actuate/rules.go index d84aa90e7..22928f425 100644 --- a/internal/goip/actuate/rules.go +++ b/internal/goip/actuate/rules.go @@ -65,7 +65,10 @@ func clearDefaultSuppressors(r *xtcpnl.RuleInfo) { } func normalRule(r xtcpnl.RuleInfo) bool { - if !(r.Priority == 0 && r.Table == unix.RT_TABLE_LOCAL || r.Priority == 32766 && r.Table == unix.RT_TABLE_MAIN || r.Priority == 32767 && r.Table == unix.RT_TABLE_DEFAULT) { + baseline := r.Priority == 0 && r.Table == unix.RT_TABLE_LOCAL || + r.Priority == 32766 && r.Table == unix.RT_TABLE_MAIN || + r.Priority == 32767 && r.Table == unix.RT_TABLE_DEFAULT + if !baseline { return false } if r.Action != unix.FR_ACT_TO_TBL { @@ -94,8 +97,8 @@ func (b *Backend) checkRules(svc *service.Service, family uint8) error { } local, main := false, false owned := 0 - for _, v := range rs { - r := xtcpnl.RuleInfo(v) + for i := range rs { + r := xtcpnl.RuleInfo(rs[i]) if b.ownRule(r) { owned++ continue @@ -131,8 +134,9 @@ func (b *Backend) rules(ctx context.Context, enable bool) error { return err } owned := 0 - for _, r := range rs { - if b.ownRule(xtcpnl.RuleInfo(r)) { + for i := range rs { + r := xtcpnl.RuleInfo(rs[i]) + if b.ownRule(r) { owned++ } else if enable && (r.Priority == b.Priority || r.Table == b.Table) { return errors.New("reserved routing rule collision") @@ -169,8 +173,8 @@ func (b *Backend) rules(ctx context.Context, enable bool) error { return err } owned = 0 - for _, r := range rs { - if b.ownRule(xtcpnl.RuleInfo(r)) { + for i := range rs { + if b.ownRule(xtcpnl.RuleInfo(rs[i])) { owned++ } } diff --git a/nix/tests/tcp-controller/quality.nix b/nix/tests/tcp-controller/quality.nix index 9a968fbef..111ec95bb 100644 --- a/nix/tests/tcp-controller/quality.nix +++ b/nix/tests/tcp-controller/quality.nix @@ -10,6 +10,7 @@ let versions.go pkgs.gcc pkgs.postgresql + pkgs.clickhouse # clickhouse_test.go finds it on PATH ]; } '' @@ -24,7 +25,6 @@ let pg_ctl -D "$TMPDIR/pg-data" -l "$out/postgres.log" \ -o "-k $TMPDIR/pg-socket -h ''' -p 55439" -w start export XTCP_CONTROLLER_TEST_DSN="host=$TMPDIR/pg-socket port=55439 dbname=postgres sslmode=disable" - export XTCP_CLICKHOUSE_TEST_BINARY=${pkgs.clickhouse}/bin/clickhouse go test -ldflags=-checklinkname=0 ${ if race then "-race -count=3" else "-count=1 -coverprofile=$out/coverage.out" } \ diff --git a/pkg/treatment/agent/agent.go b/pkg/treatment/agent/agent.go index a8e8f14f2..a8972f772 100644 --- a/pkg/treatment/agent/agent.go +++ b/pkg/treatment/agent/agent.go @@ -192,9 +192,9 @@ func (a *Agent) Run(ctx context.Context) { go func() { defer workers.Done(); a.watch(ctx) }() } } - jitter, _ := rand.Int(rand.Reader, big.NewInt(6000)) + // Without entropy the check-in simply runs unjittered. delay := 27 * time.Second - if jitter != nil { + if jitter, jerr := rand.Int(rand.Reader, big.NewInt(6000)); jerr == nil { delay += time.Duration(jitter.Int64()) * time.Millisecond } timer := time.NewTimer(delay) diff --git a/pkg/treatment/agent/watch_linux.go b/pkg/treatment/agent/watch_linux.go index 4affe3405..534e6e80d 100644 --- a/pkg/treatment/agent/watch_linux.go +++ b/pkg/treatment/agent/watch_linux.go @@ -13,7 +13,11 @@ func (a *Agent) watchRouting(ctx context.Context) { log.Printf("treatment route watch unavailable: %v; periodic reconciliation remains enabled", e) return } - defer unix.Close(fd) + defer func() { + if cerr := unix.Close(fd); cerr != nil { + log.Printf("treatment route watch close: %v", cerr) + } + }() groups := uint32(unix.RTMGRP_LINK | unix.RTMGRP_IPV4_IFADDR | unix.RTMGRP_IPV6_IFADDR | unix.RTMGRP_IPV4_ROUTE | unix.RTMGRP_IPV6_ROUTE | unix.RTMGRP_IPV4_RULE) // RTNLGRP_IPV6_RULE is a group number; sockaddr_nl expects its bitmask. groups |= 1 << (unix.RTNLGRP_IPV6_RULE - 1) diff --git a/pkg/treatment/attribution/watch.go b/pkg/treatment/attribution/watch.go index 446db2bfd..d9c1590b1 100644 --- a/pkg/treatment/attribution/watch.go +++ b/pkg/treatment/attribution/watch.go @@ -132,6 +132,20 @@ func (w *Watcher) Refresh(force bool) error { return nil } +// refreshRetryCst bounds the poll interval while the journal is unreadable. +const refreshRetryCst = 5 * time.Second + +// pollAfter is the wait before the next unprompted refresh: the configured +// period, or sooner while the last refresh failed so a journal that becomes +// readable again is noticed without waiting a full period. Refresh has +// already logged the failure once. +func (w *Watcher) pollAfter(lastErr error) time.Duration { + if lastErr != nil { + return min(w.cfg.Poll, refreshRetryCst) + } + return w.cfg.Poll +} + // note logs a load failure once per distinct message and a recovery once. func (w *Watcher) note(msg string) { if msg == w.lastErr { @@ -151,13 +165,17 @@ func (w *Watcher) Run(ctx interface { Done() <-chan struct{} Err() error }) { - _ = w.Refresh(true) + refreshed := w.Refresh(true) fd, err := unix.InotifyInit1(unix.IN_CLOEXEC | unix.IN_NONBLOCK) if err != nil { w.cfg.Log("treatment attribution: inotify unavailable (%v); polling every %s", err, w.cfg.Poll) fd = -1 } else { - defer unix.Close(fd) + defer func() { + if cerr := unix.Close(fd); cerr != nil { + w.cfg.Log("treatment attribution: inotify close: %v", cerr) + } + }() } watching := false addWatch := func() { @@ -169,7 +187,7 @@ func (w *Watcher) Run(ctx interface { } addWatch() buf := make([]byte, 64<<10) - timer := time.NewTimer(w.cfg.Poll) + timer := time.NewTimer(w.pollAfter(refreshed)) defer timer.Stop() events := make(chan bool, 1) // true: the snapshot changed; false: the directory itself went away if fd >= 0 { @@ -180,15 +198,15 @@ func (w *Watcher) Run(ctx interface { case <-ctx.Done(): return case <-w.wake: - _ = w.Refresh(true) + refreshed = w.Refresh(true) case changed := <-events: if !changed { watching = false } - _ = w.Refresh(true) + refreshed = w.Refresh(true) case <-timer.C: addWatch() - _ = w.Refresh(false) + refreshed = w.Refresh(false) } if !timer.Stop() { select { @@ -196,7 +214,7 @@ func (w *Watcher) Run(ctx interface { default: } } - timer.Reset(w.cfg.Poll) + timer.Reset(w.pollAfter(refreshed)) } } diff --git a/pkg/treatment/attribution/watch_test.go b/pkg/treatment/attribution/watch_test.go index 524b327cc..39625bd0d 100644 --- a/pkg/treatment/attribution/watch_test.go +++ b/pkg/treatment/attribution/watch_test.go @@ -90,7 +90,11 @@ func TestWatcherRefreshContracts(t *testing.T) { }{ {"journal", "positive: a committed journal attributes records with the journal's agent and the daemon's location", func(t *testing.T, dir string) { writeJournal(t, dir, verifiedState(3)) }, "", "host-1", "trial-1", "", "dc1", "dc1"}, {"missing-file", "negative: no snapshot yet means identity is not invented", func(*testing.T, string) {}, "", "", "", "", "dc1", "dc1"}, - {"missing-dir", "negative: a directory that does not exist yet is not an error", func(t *testing.T, dir string) { os.RemoveAll(dir) }, "", "", "", "", "dc1", "dc1"}, + {"missing-dir", "negative: a directory that does not exist yet is not an error", func(t *testing.T, dir string) { + if err := os.RemoveAll(dir); err != nil { + t.Fatal(err) + } + }, "", "", "", "", "dc1", "dc1"}, {"corrupt", "negative: an invalid snapshot clears attribution and is logged", func(t *testing.T, dir string) { if err := os.WriteFile(filepath.Join(dir, reconcile.SnapshotFile), []byte("nonsense {"), 0600); err != nil { t.Fatal(err) diff --git a/pkg/treatment/boundaries_test.go b/pkg/treatment/boundaries_test.go index f4abbdce4..27ce940c8 100644 --- a/pkg/treatment/boundaries_test.go +++ b/pkg/treatment/boundaries_test.go @@ -212,7 +212,7 @@ func TestTreatmentImportBoundaries(t *testing.T) { type anImport struct{ path, file string } // packageImports lists the non-test imports of one package under the given -// build tags, honouring //go:build lines and _GOOS/_GOARCH suffixes the way +// build tags, honoring //go:build lines and _GOOS/_GOARCH suffixes the way // the compiler would for the host platform, so a file such as // cmd/xtcp2/treatments_embedded.go (//go:build treatment) only counts when // the rule asks for that tag. diff --git a/pkg/treatment/controller/auth.go b/pkg/treatment/controller/auth.go index c27b69837..add8d17b0 100644 --- a/pkg/treatment/controller/auth.go +++ b/pkg/treatment/controller/auth.go @@ -30,11 +30,18 @@ func RolePolicy() identity.Policy { for j := 0; j < methods.Len(); j++ { m := methods.Get(j) name := fmt.Sprintf("/%s/%s", sd.FullName(), m.Name()) - switch proto.GetExtension(m.Options(), pb.E_Role).(pb.Role) { + role, ok := proto.GetExtension(m.Options(), pb.E_Role).(pb.Role) + if !ok { + continue + } + switch role { case pb.Role_ROLE_AGENT: p[name] = identity.RoleAgent case pb.Role_ROLE_OPERATOR: p[name] = identity.RoleOperator + case pb.Role_ROLE_UNSPECIFIED: + // Left out of the policy, so the interceptors deny the method + // and the coverage test names it. } } } diff --git a/pkg/treatment/controller/auth_test.go b/pkg/treatment/controller/auth_test.go index 206b411d2..052cdb5a5 100644 --- a/pkg/treatment/controller/auth_test.go +++ b/pkg/treatment/controller/auth_test.go @@ -95,7 +95,7 @@ func TestEveryMethodAgainstEveryRole(t *testing.T) { } func TestGRPCOptionsCarryInterceptors(t *testing.T) { - // The options must at least construct a server; the interceptor behaviour + // The options must at least construct a server; the interceptor behavior // itself is covered above and by the identity package's mTLS test. s := grpc.NewServer(GRPCOptions()...) defer s.Stop() diff --git a/pkg/treatment/controller/discovery.go b/pkg/treatment/controller/discovery.go index ce69da23e..4254f448e 100644 --- a/pkg/treatment/controller/discovery.go +++ b/pkg/treatment/controller/discovery.go @@ -15,8 +15,9 @@ import ( "github.com/randomizedcoder/xtcp2/pkg/treatment/wire" ) -// Templates only authorize candidate discovery. Every generated hypothesis and -// every increase in exposure still requires an authenticated operator review. +// DiscoveryTemplate authorizes candidate discovery for one location. Templates +// only authorize discovery: every generated hypothesis and every increase in +// exposure still requires an authenticated operator review. type DiscoveryTemplate struct { ID string `json:"id"` Location string `json:"location"` @@ -92,7 +93,8 @@ func (s *Server) Discover(ctx context.Context, w evidence.Warehouse, templates [ return errors.New("candidate discovery requires a warehouse") } var failures []error - for _, t := range templates { + for i := range templates { + t := &templates[i] if !treatment.ValidID(t.ID) || !treatment.ValidID(t.Location) || (t.CongestionControl != "cubic" && t.CongestionControl != "bbr") || t.MaximumRTTMs <= t.MinimumRTTMs || t.MinimumRTTMs < 0 || t.MinimumRetransmissionRatio < 0 || t.MinimumRetransmissionRatio > 1 { return errors.New("invalid discovery template; initial discovery supports CUBIC/BBR only") } diff --git a/pkg/treatment/controller/discovery_contracts_test.go b/pkg/treatment/controller/discovery_contracts_test.go index 1b911edc0..6dae3e1cc 100644 --- a/pkg/treatment/controller/discovery_contracts_test.go +++ b/pkg/treatment/controller/discovery_contracts_test.go @@ -34,7 +34,7 @@ func TestDiscoveryScopeAndCooldown(t *testing.T) { eachStore(t, func(t *testing.T, st store.Store) { now := time.Now().UTC().Truncate(time.Microsecond) s := testServer(st, &now) - var agents []string + agents := make([]string, 0, 8) for i := range 8 { id := fmt.Sprintf("host-%d", i) enroll(t, s, id) @@ -151,11 +151,12 @@ func TestConcurrentDiscoveryProposals(t *testing.T) { close(results) successes, suppressed := 0, 0 for err := range results { - if err == nil { + switch { + case err == nil: successes++ - } else if errors.Is(err, errDiscoverySuppressed) { + case errors.Is(err, errDiscoverySuppressed): suppressed++ - } else { + default: t.Fatal(err) } } diff --git a/pkg/treatment/controller/evidence/clickhouse/clickhouse_test.go b/pkg/treatment/controller/evidence/clickhouse/clickhouse_test.go index 382716bf1..67e39b243 100644 --- a/pkg/treatment/controller/evidence/clickhouse/clickhouse_test.go +++ b/pkg/treatment/controller/evidence/clickhouse/clickhouse_test.go @@ -237,9 +237,9 @@ func TestClickHouseCandidateContracts(t *testing.T) { // Execute the production SQL against an isolated ClickHouse local database. // No live service or production telemetry is accessed. func TestClickHouseAnalysisCounterDeltas(t *testing.T) { - binary := os.Getenv("XTCP_CLICKHOUSE_TEST_BINARY") - if binary == "" { - t.Skip("set XTCP_CLICKHOUSE_TEST_BINARY to run real SQL") + binary, lerr := exec.LookPath("clickhouse") + if lerr != nil { + t.Skip("clickhouse is not on PATH; the Nix quality check supplies it") } setup := `CREATE TABLE default.sockets ( agent_id String,host_boot_id String,netns_inode UInt64,inet_diag_msg_socket_cookie UInt64, diff --git a/pkg/treatment/controller/housekeep_test.go b/pkg/treatment/controller/housekeep_test.go index 6339f2beb..3b428f06c 100644 --- a/pkg/treatment/controller/housekeep_test.go +++ b/pkg/treatment/controller/housekeep_test.go @@ -20,7 +20,7 @@ func TestHousekeepLease(t *testing.T) { name, description string warehouse *fakeWarehouse heldElsewhere bool - cancelled bool + canceled bool wantHeld bool wantErr string wantQueries int @@ -29,14 +29,14 @@ func TestHousekeepLease(t *testing.T) { {"held-elsewhere", "corner: while another replica holds the lease this one runs nothing and reports it", &fakeWarehouse{}, true, false, false, "", 0}, {"no-warehouse", "boundary: without a warehouse the pass is maintenance only", nil, false, false, true, "", 0}, {"discovery-fails", "negative: a failing pass still held the lease and names the failing stage", &fakeWarehouse{candidateErr: errors.New("warehouse offline")}, false, false, true, "candidate discovery: warehouse offline", 1}, - {"cancelled", "negative: a cancelled context takes no lease and runs nothing", &fakeWarehouse{}, false, true, false, "context canceled", 0}, + {"canceled", "negative: a canceled context takes no lease and runs nothing", &fakeWarehouse{}, false, true, false, "context canceled", 0}, } { t.Run(tc.name, func(t *testing.T) { st := memory.New() s := &Server{Store: st, Now: func() time.Time { return now }} ctx, cancel := context.WithCancel(context.Background()) defer cancel() - if tc.cancelled { + if tc.canceled { cancel() } release := make(chan struct{}) diff --git a/pkg/treatment/controller/maintenance.go b/pkg/treatment/controller/maintenance.go index ecd682921..792a1ad29 100644 --- a/pkg/treatment/controller/maintenance.go +++ b/pkg/treatment/controller/maintenance.go @@ -59,127 +59,161 @@ func (s *Server) Maintain(ctx context.Context, source evidence.Source) error { } var failures []error for _, id := range ids { - var snapshot *treatment.Experiment - e = s.Store.Tx(ctx, func(rp store.Repos) error { - n, e := rp.Experiments.Get(ctx, id) - if e != nil { - return e - } - if n.Status != treatment.StatusRunning && n.Status != treatment.StatusPaused && n.Status != treatment.StatusComplete { + if e = s.maintainOne(ctx, source, id); e != nil { + failures = append(failures, e) + } + } + return errors.Join(failures...) +} + +// maintainOne is one experiment's pass: start observation, query evidence +// outside any transaction, then apply the result under the mutation lock if +// nobody changed the experiment meanwhile. +func (s *Server) maintainOne(ctx context.Context, source evidence.Source, id string) error { + snapshot, e := s.observe(ctx, id) + if e != nil || snapshot == nil || len(snapshot.Phases) == 0 { + return e + } + var errs []error + ev, e := s.fetchEvidence(ctx, source, snapshot) + if e != nil { + errs = append(errs, fmt.Errorf("%s analysis: %w", id, e)) + } + e = s.Store.Tx(ctx, func(rp store.Repos) error { + n, e := rp.Experiments.Get(ctx, id) + if e != nil { + return e + } + if n.Revision != snapshot.Revision { + return nil + } + if n.Status == treatment.StatusComplete { + return s.driftPass(ctx, rp, n, ev) + } + return s.trialPass(ctx, rp, n, ev) + }) + return errors.Join(append(errs, e)...) +} + +// observe starts observation for a running or paused experiment and returns +// the snapshot the evidence window is computed from, or nil when the +// experiment needs no pass this tick. +func (s *Server) observe(ctx context.Context, id string) (*treatment.Experiment, error) { + var snapshot *treatment.Experiment + e := s.Store.Tx(ctx, func(rp store.Repos) error { + n, e := rp.Experiments.Get(ctx, id) + if e != nil { + return e + } + if n.Status != treatment.StatusRunning && n.Status != treatment.StatusPaused && n.Status != treatment.StatusComplete { + return nil + } + if n.Status == treatment.StatusComplete { + if n.LatestEvidence != nil && s.now().Sub(n.LatestEvidence.AnalyzedAt) < time.Hour { return nil } - if n.Status == treatment.StatusComplete { - if n.LatestEvidence != nil && s.now().Sub(n.LatestEvidence.AnalyzedAt) < time.Hour { - return nil - } - } else if e = s.startObservation(ctx, rp, n); e != nil { + } else if e = s.startObservation(ctx, rp, n); e != nil { + return e + } + snapshot = n + return s.save(ctx, rp, n) + }) + return snapshot, e +} + +// fetchEvidence queries the warehouse for the experiment's current window. +// Without a warehouse, or while the experiment is paused, there is none. +func (s *Server) fetchEvidence(ctx context.Context, source evidence.Source, snapshot *treatment.Experiment) (*treatment.Evidence, error) { + if source == nil || snapshot.Status == treatment.StatusPaused { + return nil, nil + } + input := snapshot + if snapshot.Status == treatment.StatusComplete { + input = s.driftInput(snapshot) + } + return source.Evidence(ctx, evidence.WindowFor(input, s.now())) +} + +// driftPass records drift evidence for a completed treatment and flags a +// regression for review; it never withdraws on its own. +func (s *Server) driftPass(ctx context.Context, rp store.Repos, n *treatment.Experiment, ev *treatment.Evidence) error { + if ev != nil { + if e := saveEvidence(ctx, rp, n.ID, ev); e != nil { + return e + } + healthy, harmful := experiment.HealthyEvidence(n, ev, s.now()) + n.LatestEvidence = ev + n.LatestOutcome = treatment.OutcomeStableObserved + if !healthy { + n.LatestOutcome = treatment.OutcomeStableEvidenceIncomplete + } + if harmful { + n.LatestOutcome = treatment.OutcomeStableRegressionReviewRequired + if e := audit(ctx, rp, "drift-monitor", "", "stable_regression", wire.Evidence(ev)); e != nil { return e } - snapshot = n - return s.save(ctx, rp, n) - }) + } + } + return s.save(ctx, rp, n) +} + +// trialPass evaluates the current phase of a running or paused experiment: +// it withdraws an expired or harmful trial and renews the leases of a healthy +// one. Only Review writes the phase's reviewer and outcome. +func (s *Server) trialPass(ctx context.Context, rp store.Repos, n *treatment.Experiment, ev *treatment.Evidence) error { + phase := n.CurrentPhase() + now := s.now() + outcome := treatment.OutcomeUnavailable + switch { + case now.Sub(phase.IssuedAt) >= n.Policy.MaximumPhase: + outcome = treatment.OutcomeExpired + case ev != nil: + if e := saveEvidence(ctx, rp, n.ID, ev); e != nil { + return e + } + v, e := experiment.Evaluate(n, ev, now) if e != nil { - failures = append(failures, e) - continue + return e } - if snapshot == nil || len(snapshot.Phases) == 0 { - continue + outcome = v.Outcome + // Keep analysis visible to reviewers without marking a phase as + // approved/evaluated. + n.LatestEvidence = ev + if _, harmful := experiment.HealthyEvidence(n, ev, now); harmful { + outcome = treatment.OutcomeHarmful } - var ev *treatment.Evidence - if source != nil && snapshot.Status != treatment.StatusPaused { - input := snapshot - if snapshot.Status == treatment.StatusComplete { - input = s.driftInput(snapshot) - } - ev, e = source.Evidence(ctx, evidence.WindowFor(input, s.now())) - if e != nil { - failures = append(failures, fmt.Errorf("%s analysis: %w", id, e)) - } + } + n.LatestOutcome = outcome + switch { + case outcome.Withdraws(): + event := experiment.EventExpire + if outcome == treatment.OutcomeHarmful { + event = experiment.EventHarm + } + var e error + if n.Status, e = experiment.Transition(n.Status, event); e != nil { + return e + } + phase.Outcome = outcome + phase.EvaluatedAt = now + n.Revision++ + if e = s.assign(ctx, rp, n, false); e != nil { + return e + } + if e = audit(ctx, rp, "safety-controller", "", "automatic_withdrawal", wire.Experiment(n)); e != nil { + return e } - e = s.Store.Tx(ctx, func(rp store.Repos) error { - n, e := rp.Experiments.Get(ctx, id) - if e != nil { + case ev != nil && n.Status == treatment.StatusRunning: + // Before the 30-minute decision window completes, fresh health + // evidence can renew the existing phase but cannot expand it. + healthy, _ := experiment.HealthyEvidence(n, ev, now) + if healthy && outcome.RenewsLease() { + if e := s.renew(ctx, rp, n, now); e != nil { return e } - if n.Revision != snapshot.Revision { - return nil - } - phase := n.CurrentPhase() - now := s.now() - outcome := treatment.OutcomeUnavailable - if n.Status == treatment.StatusComplete { - if ev != nil { - if e = saveEvidence(ctx, rp, id, ev); e != nil { - return e - } - healthy, harmful := experiment.HealthyEvidence(n, ev, now) - n.LatestEvidence = ev - n.LatestOutcome = treatment.OutcomeStableObserved - if !healthy { - n.LatestOutcome = treatment.OutcomeStableEvidenceIncomplete - } - if harmful { - n.LatestOutcome = treatment.OutcomeStableRegressionReviewRequired - if e = audit(ctx, rp, "drift-monitor", "", "stable_regression", wire.Evidence(ev)); e != nil { - return e - } - } - } - return s.save(ctx, rp, n) - } - if now.Sub(phase.IssuedAt) >= n.Policy.MaximumPhase { - outcome = treatment.OutcomeExpired - } else if ev != nil { - if e = saveEvidence(ctx, rp, id, ev); e != nil { - return e - } - v, e := experiment.Evaluate(n, ev, now) - if e != nil { - return e - } - outcome = v.Outcome - // Keep analysis visible to reviewers without marking a phase as - // approved/evaluated. Only Review writes reviewer and outcome. - n.LatestEvidence = ev - if _, harmful := experiment.HealthyEvidence(n, ev, now); harmful { - outcome = treatment.OutcomeHarmful - } - } - n.LatestOutcome = outcome - if outcome.Withdraws() { - event := experiment.EventExpire - if outcome == treatment.OutcomeHarmful { - event = experiment.EventHarm - } - if n.Status, e = experiment.Transition(n.Status, event); e != nil { - return e - } - phase.Outcome = outcome - phase.EvaluatedAt = now - n.Revision++ - if e = s.assign(ctx, rp, n, false); e != nil { - return e - } - if e = audit(ctx, rp, "safety-controller", "", "automatic_withdrawal", wire.Experiment(n)); e != nil { - return e - } - } else if ev != nil && n.Status == treatment.StatusRunning { - // Before the 30-minute decision window completes, fresh health - // evidence can renew the existing phase but cannot expand it. - healthy, _ := experiment.HealthyEvidence(n, ev, now) - if healthy && outcome.RenewsLease() { - if e = s.renew(ctx, rp, n, now); e != nil { - return e - } - } - } - return s.save(ctx, rp, n) - }) - if e != nil { - failures = append(failures, e) } } - return errors.Join(failures...) + return s.save(ctx, rp, n) } func (s *Server) renew(ctx context.Context, rp store.Repos, n *treatment.Experiment, now time.Time) error { diff --git a/pkg/treatment/controller/notifications_test.go b/pkg/treatment/controller/notifications_test.go index 6748e9359..f2baca9e2 100644 --- a/pkg/treatment/controller/notifications_test.go +++ b/pkg/treatment/controller/notifications_test.go @@ -58,7 +58,7 @@ func TestNotificationLifecycle(t *testing.T) { h.publish("host", 4) select { case <-ch: - t.Fatal("cancelled subscription received an update") + t.Fatal("canceled subscription received an update") default: } }) diff --git a/pkg/treatment/controller/server.go b/pkg/treatment/controller/server.go index dbbd151a8..8bcf826c1 100644 --- a/pkg/treatment/controller/server.go +++ b/pkg/treatment/controller/server.go @@ -68,7 +68,7 @@ func failure(e error) error { return status.Error(codes.NotFound, "not found") } if errors.Is(e, context.Canceled) { - return status.Error(codes.Canceled, "cancelled") + return status.Error(codes.Canceled, "canceled") } return status.Error(codes.FailedPrecondition, e.Error()) } @@ -599,82 +599,16 @@ func (s *Server) Control(ctx context.Context, r *pb.ControlRequest) (out *pb.Con if yes, e := replay(ctx, rp, r.RequestId, "control", r, out); yes || e != nil { return e } + var e error if r.ExperimentId != "" { - n, e := rp.Experiments.Get(ctx, r.ExperimentId) - if e != nil { - return e - } - if n.Status.Terminal() { - return errors.New("terminal experiment cannot be reactivated") - } - event, e := controlEvent(n, r.Action) - if e != nil { - return e - } - if n.Status, e = experiment.Transition(n.Status, event); e != nil { - return e - } - if event == experiment.EventResume { - p := n.CurrentPhase() - n.Phases = append(n.Phases, treatment.Phase{TargetHosts: p.TargetHosts, IssuedAt: s.now(), Reviewer: actor, Reason: r.Reason}) - } - n.Revision++ - if e = s.assign(ctx, rp, n, r.Action == pb.ControlAction_CONTROL_ACTION_RESUME); e != nil { - return e - } - if e = s.save(ctx, rp, n); e != nil { - return e - } - out.AffectedAgents = uint32(len(n.CohortOrder)) + e = s.controlExperiment(ctx, rp, r, actor, out) } else { - if r.Action != pb.ControlAction_CONTROL_ACTION_SUSPEND && r.Action != pb.ControlAction_CONTROL_ACTION_RESUME { - return errors.New("non-experiment controls support suspend/resume only") - } - scope := "fleet" - if r.AgentId != "" { - scope = "agent:" + r.AgentId - } - if r.Location != "" { - scope = "location:" + r.Location - } - var e error - if r.Action == pb.ControlAction_CONTROL_ACTION_SUSPEND { - e = rp.Holds.Set(ctx, scope, r.Reason) - } else { - e = rp.Holds.Clear(ctx, scope) - } - if e != nil { - return e - } - var hosts []store.AgentRef - if r.AgentId != "" { - a, e := rp.Agents.Get(ctx, r.AgentId) - if e != nil && !errors.Is(e, store.ErrNotFound) { - return e - } - if e == nil { - hosts = append(hosts, store.AgentRef{ID: a.ID, Location: a.Location}) - } - } else if hosts, e = rp.Agents.List(ctx, r.Location); e != nil { - return e - } - for _, h := range hosts { - d, e := rp.Agents.Desired(ctx, h.ID) - if e != nil { - return e - } - if d.Suspended, e = rp.Holds.Held(ctx, holdScopes(h.Location, h.ID)...); e != nil { - return e - } - d.Revision++ - d.IssuedAt = s.now() - if e = rp.Agents.SaveDesired(ctx, d); e != nil { - return e - } - } - out.AffectedAgents = uint32(len(hosts)) + e = s.controlHolds(ctx, rp, r, out) + } + if e != nil { + return e } - if e := audit(ctx, rp, actor, r.RequestId, "control", r); e != nil { + if e = audit(ctx, rp, actor, r.RequestId, "control", r); e != nil { return e } return audit(ctx, rp, actor, r.RequestId+".result", "result", out) @@ -682,6 +616,98 @@ func (s *Server) Control(ctx context.Context, r *pb.ControlRequest) (out *pb.Con return out, failure(err) } +// controlExperiment applies an operator action to one experiment through +// the state machine and reissues its assignments. +func (s *Server) controlExperiment(ctx context.Context, rp store.Repos, r *pb.ControlRequest, actor string, out *pb.ControlResponse) error { + n, e := rp.Experiments.Get(ctx, r.ExperimentId) + if e != nil { + return e + } + if n.Status.Terminal() { + return errors.New("terminal experiment cannot be reactivated") + } + event, e := controlEvent(n, r.Action) + if e != nil { + return e + } + if n.Status, e = experiment.Transition(n.Status, event); e != nil { + return e + } + if event == experiment.EventResume { + p := n.CurrentPhase() + n.Phases = append(n.Phases, treatment.Phase{TargetHosts: p.TargetHosts, IssuedAt: s.now(), Reviewer: actor, Reason: r.Reason}) + } + n.Revision++ + if e = s.assign(ctx, rp, n, r.Action == pb.ControlAction_CONTROL_ACTION_RESUME); e != nil { + return e + } + if e = s.save(ctx, rp, n); e != nil { + return e + } + out.AffectedAgents = uint32(len(n.CohortOrder)) + return nil +} + +// controlHolds sets or clears a fleet, location or agent hold and reissues +// the desired state of every agent the scope covers. +func (s *Server) controlHolds(ctx context.Context, rp store.Repos, r *pb.ControlRequest, out *pb.ControlResponse) error { + if r.Action != pb.ControlAction_CONTROL_ACTION_SUSPEND && r.Action != pb.ControlAction_CONTROL_ACTION_RESUME { + return errors.New("non-experiment controls support suspend/resume only") + } + scope := "fleet" + if r.AgentId != "" { + scope = "agent:" + r.AgentId + } + if r.Location != "" { + scope = "location:" + r.Location + } + var e error + if r.Action == pb.ControlAction_CONTROL_ACTION_SUSPEND { + e = rp.Holds.Set(ctx, scope, r.Reason) + } else { + e = rp.Holds.Clear(ctx, scope) + } + if e != nil { + return e + } + hosts, e := s.holdTargets(ctx, rp, r) + if e != nil { + return e + } + for _, h := range hosts { + d, e := rp.Agents.Desired(ctx, h.ID) + if e != nil { + return e + } + if d.Suspended, e = rp.Holds.Held(ctx, holdScopes(h.Location, h.ID)...); e != nil { + return e + } + d.Revision++ + d.IssuedAt = s.now() + if e = rp.Agents.SaveDesired(ctx, d); e != nil { + return e + } + } + out.AffectedAgents = uint32(len(hosts)) + return nil +} + +// holdTargets lists the agents a hold control covers. An unknown agent is +// not an error: the hold still applies when it enrolls. +func (s *Server) holdTargets(ctx context.Context, rp store.Repos, r *pb.ControlRequest) ([]store.AgentRef, error) { + if r.AgentId == "" { + return rp.Agents.List(ctx, r.Location) + } + a, e := rp.Agents.Get(ctx, r.AgentId) + if errors.Is(e, store.ErrNotFound) { + return nil, nil + } + if e != nil { + return nil, e + } + return []store.AgentRef{{ID: a.ID, Location: a.Location}}, nil +} + // controlEvent maps an operator action onto a state-machine event. Resume // needs the experiment: a held completed treatment resumes as stable, a held // trial resumes with a fresh phase, and a held proposal cannot resume at all. @@ -704,6 +730,7 @@ func controlEvent(n *treatment.Experiment, action pb.ControlAction) (experiment. return experiment.EventResumeStable, nil } return experiment.EventResume, nil + case pb.ControlAction_CONTROL_ACTION_UNSPECIFIED: } return 0, fmt.Errorf("unknown control action %s", action) } diff --git a/pkg/treatment/controller/server_test.go b/pkg/treatment/controller/server_test.go index 30e2bd161..fce277a51 100644 --- a/pkg/treatment/controller/server_test.go +++ b/pkg/treatment/controller/server_test.go @@ -126,8 +126,8 @@ func TestCertificateIdentityIsolation(t *testing.T) { func TestNotificationFanoutTenThousandAgents(t *testing.T) { h := NewNotificationHub() - var channels []chan uint64 - var cleanup []func() + channels := make([]chan uint64, 0, 10000) + cleanup := make([]func(), 0, 10000) for i := range 10000 { ch, done := h.subscribe(fmt.Sprint(i)) channels = append(channels, ch) diff --git a/pkg/treatment/controller/status.go b/pkg/treatment/controller/status.go index 7f1aa2a9f..1ba19dbbf 100644 --- a/pkg/treatment/controller/status.go +++ b/pkg/treatment/controller/status.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "log" "net/http" "sort" "time" @@ -48,8 +49,15 @@ func (s *Server) StatusHandler() http.Handler { return } if r.URL.Path == "/status" { + b, merr := json.Marshal(values) + if merr != nil { + http.Error(w, "status encoding failed", http.StatusInternalServerError) + return + } w.Header().Set("Content-Type", "application/json") - _ = json.NewEncoder(w).Encode(values) + if _, werr := w.Write(append(b, '\n')); werr != nil { + log.Printf("controller status: write: %v", werr) + } return } keys := make([]string, 0, len(values)) diff --git a/pkg/treatment/controller/store/memory/memory.go b/pkg/treatment/controller/store/memory/memory.go index 470cc58fa..a74f71012 100644 --- a/pkg/treatment/controller/store/memory/memory.go +++ b/pkg/treatment/controller/store/memory/memory.go @@ -1,5 +1,5 @@ // Package memory implements the controller's store ports with maps. It is -// for handler tests: it honours every sentinel error and rolls a failed Tx +// for handler tests: it honors every sentinel error and rolls a failed Tx // back, but it has no durability and no concurrency beyond one mutex. package memory @@ -472,6 +472,7 @@ func (r metricsRepo) Snapshot(_ context.Context, now time.Time) (map[string]floa m["routes_failed"]++ case treatment.ApplyExpired: m["routes_expired"]++ + default: } } } @@ -483,6 +484,7 @@ func (r metricsRepo) Snapshot(_ context.Context, now time.Time) (map[string]floa m["experiments_running"]++ case treatment.StatusComplete: m["experiments_complete"]++ + default: } } return m, nil diff --git a/pkg/treatment/controller/store/pgtest/pgtest.go b/pkg/treatment/controller/store/pgtest/pgtest.go index 38efe73ca..85f15bc00 100644 --- a/pkg/treatment/controller/store/pgtest/pgtest.go +++ b/pkg/treatment/controller/store/pgtest/pgtest.go @@ -7,6 +7,7 @@ package pgtest import ( "context" "database/sql" + "errors" "fmt" "net/url" "os" @@ -37,25 +38,33 @@ func Open(t testing.TB) *postgres.Store { if err != nil { t.Fatal(err) } + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) + defer cancel() schema := fmt.Sprintf("xtcp_test_%d_%d", time.Now().UnixNano(), sequence.Add(1)) - if _, err = admin.Exec("CREATE SCHEMA " + schema); err != nil { - admin.Close() - t.Fatal(err) + if _, err = admin.ExecContext(ctx, "CREATE SCHEMA "+schema); err != nil { + t.Fatal(errors.Join(err, admin.Close())) } scoped, err := withSearchPath(dsn, schema) if err != nil { - t.Fatal(err) + t.Fatal(errors.Join(err, admin.Close())) } - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() s, err := postgres.Open(ctx, scoped) if err != nil { - t.Fatal(err) + t.Fatal(errors.Join(err, admin.Close())) } t.Cleanup(func() { - s.Close() - _, _ = admin.Exec("DROP SCHEMA " + schema + " CASCADE") - admin.Close() + // The test's context is already canceled when cleanups run. + cctx, ccancel := context.WithTimeout(context.WithoutCancel(ctx), 30*time.Second) + defer ccancel() + if cerr := s.Close(); cerr != nil { + t.Error(cerr) + } + if _, derr := admin.ExecContext(cctx, "DROP SCHEMA "+schema+" CASCADE"); derr != nil { + t.Errorf("drop schema %s: %v", schema, derr) + } + if cerr := admin.Close(); cerr != nil { + t.Error(cerr) + } }) if err = s.Migrate(ctx); err != nil { t.Fatal(err) diff --git a/pkg/treatment/controller/store/postgres/agents.go b/pkg/treatment/controller/store/postgres/agents.go index 92522198b..81d098fe8 100644 --- a/pkg/treatment/controller/store/postgres/agents.go +++ b/pkg/treatment/controller/store/postgres/agents.go @@ -49,8 +49,7 @@ func (a *agents) Get(ctx context.Context, id string) (*store.Agent, error) { for rows.Next() { var name string if err = rows.Scan(&name); err != nil { - rows.Close() - return nil, err + return nil, errors.Join(err, rows.Close()) } out.Report.Capabilities.CongestionAlgorithms = append(out.Report.Capabilities.CongestionAlgorithms, name) } @@ -64,13 +63,11 @@ func (a *agents) Get(ctx context.Context, id string) (*store.Agent, error) { for rows.Next() { var r recordRow if err = rows.Scan(r.targets()...); err != nil { - rows.Close() - return nil, err + return nil, errors.Join(err, rows.Close()) } rec, err := r.value() if err != nil { - rows.Close() - return nil, err + return nil, errors.Join(err, rows.Close()) } out.Report.Records = append(out.Report.Records, rec) } @@ -113,7 +110,7 @@ func (a *agents) Observe(ctx context.Context, report *treatment.AgentReport, see for i, name := range c.CongestionAlgorithms { algos[i] = []any{report.AgentID, i, name} } - if err = batchInsert(ctx, q, "xtcp_treatment_agent_algorithms", []string{"agent_id", "position", "name"}, algos); err != nil { + if err = batchInsert(ctx, q, "xtcp_treatment_agent_algorithms", []string{agentIDColumnCst, positionColumnCst, "name"}, algos); err != nil { return err } if _, err = q.ExecContext(ctx, "DELETE FROM xtcp_treatment_records WHERE agent_id=$1", report.AgentID); err != nil { @@ -167,13 +164,11 @@ func (a *agents) Desired(ctx context.Context, id string) (*treatment.DesiredStat for rows.Next() { var r assignmentRow if err = rows.Scan(r.targets()...); err != nil { - rows.Close() - return nil, err + return nil, errors.Join(err, rows.Close()) } v, err := r.value() if err != nil { - rows.Close() - return nil, err + return nil, errors.Join(err, rows.Close()) } d.Assignments = append(d.Assignments, v) } @@ -219,8 +214,7 @@ func (a *agents) List(ctx context.Context, location string) ([]store.AgentRef, e for rows.Next() { var r store.AgentRef if err = rows.Scan(&r.ID, &r.Location); err != nil { - rows.Close() - return nil, err + return nil, errors.Join(err, rows.Close()) } out = append(out, r) } @@ -238,8 +232,7 @@ func (a *agents) DesiredSince(ctx context.Context, since time.Time) ([]store.Des var r store.DesiredRevision var rev int64 if err = rows.Scan(&r.AgentID, &rev, &r.UpdatedAt); err != nil { - rows.Close() - return nil, err + return nil, errors.Join(err, rows.Close()) } r.Revision = uint64(rev) out = append(out, r) diff --git a/pkg/treatment/controller/store/postgres/columns.go b/pkg/treatment/controller/store/postgres/columns.go index 0aeff8406..4b940c542 100644 --- a/pkg/treatment/controller/store/postgres/columns.go +++ b/pkg/treatment/controller/store/postgres/columns.go @@ -82,7 +82,13 @@ func optBool(v sql.NullBool) *bool { // assignmentColumns are shared by assignments and the assignment half of // records: identity, mode, lease, treatment and predecessor. -var assignmentColumns = append(append([]string{"agent_id", "experiment_id", "position", "mode", "generation", "lease_expires_at"}, prefixed("treatment_", treatmentColumns)...), prefixed("predecessor_", treatmentColumns)...) +// agentIDColumnCst is the agent foreign-key column every child table shares. +const agentIDColumnCst = "agent_id" + +// positionColumnCst orders the rows of every child list. +const positionColumnCst = "position" + +var assignmentColumns = append(append([]string{agentIDColumnCst, "experiment_id", positionColumnCst, "mode", "generation", "lease_expires_at"}, prefixed("treatment_", treatmentColumns)...), prefixed("predecessor_", treatmentColumns)...) func assignmentArgs(agentID string, position int, a *treatment.Assignment) []any { args := []any{agentID, a.ExperimentID, position, a.Mode.String(), int64(a.Generation), nullTime(a.LeaseExpiresAt)} diff --git a/pkg/treatment/controller/store/postgres/evidence.go b/pkg/treatment/controller/store/postgres/evidence.go index 865c08b39..96b8ac65c 100644 --- a/pkg/treatment/controller/store/postgres/evidence.go +++ b/pkg/treatment/controller/store/postgres/evidence.go @@ -15,7 +15,7 @@ import ( type evidence struct{ *runner } -var hostColumns = []string{"evidence_id", "position", "agent_id", "sockets", "bytes_acked", "goodput_bps", "retransmission_ratio", "rtt_p95_ms", "exposed_sockets", "collection_healthy"} +var hostColumns = []string{"evidence_id", positionColumnCst, agentIDColumnCst, "sockets", "bytes_acked", "goodput_bps", "retransmission_ratio", "rtt_p95_ms", "exposed_sockets", "collection_healthy"} // digest is a content hash over the domain value so Put can detect reuse of // an evidence ID for different content without reloading every host row. @@ -50,8 +50,7 @@ func getEvidence(ctx context.Context, q querier, id string) (*treatment.Evidence var h treatment.HostEvidence var sockets, bytes, exposed int64 if err = rows.Scan(&h.AgentID, &sockets, &bytes, &h.GoodputBps, &h.RetransmissionRatio, &h.RttP95Ms, &exposed, &h.CollectionHealthy); err != nil { - rows.Close() - return nil, err + return nil, errors.Join(err, rows.Close()) } h.Sockets, h.BytesAcked, h.ExposedSockets = uint64(sockets), uint64(bytes), uint64(exposed) e.Hosts = append(e.Hosts, h) diff --git a/pkg/treatment/controller/store/postgres/experiments.go b/pkg/treatment/controller/store/postgres/experiments.go index f62c04dfc..5ae158fd6 100644 --- a/pkg/treatment/controller/store/postgres/experiments.go +++ b/pkg/treatment/controller/store/postgres/experiments.go @@ -5,6 +5,7 @@ import ( "database/sql" "errors" "net/netip" + "slices" "strings" "time" @@ -64,9 +65,8 @@ func (x *experiments) get(ctx context.Context, q querier, id string) (*treatment var status, outcome string var proposed sql.NullTime var updated time.Time - targets := []any{&e.Location, &e.Hypothesis} - targets = append(targets, tr.targets()...) - targets = append(targets, &minObs, &maxPhase, &maxAge, &minSockets, &minBytes, &minTreated, &e.Policy.MinimumImprovement, &e.Policy.MaximumRetransmissionIncrease, &e.Policy.MaximumRTTIncrease, &baseline, &status, &revision, &proposed, &latest, &outcome, &updated) + targets := slices.Concat([]any{&e.Location, &e.Hypothesis}, tr.targets(), + []any{&minObs, &maxPhase, &maxAge, &minSockets, &minBytes, &minTreated, &e.Policy.MinimumImprovement, &e.Policy.MaximumRetransmissionIncrease, &e.Policy.MaximumRTTIncrease, &baseline, &status, &revision, &proposed, &latest, &outcome, &updated}) err := q.QueryRowContext(ctx, "SELECT "+selectList(experimentColumns[1:])+" FROM xtcp_treatment_experiments WHERE id=$1"+suffix, id).Scan(targets...) if err != nil { return nil, notFound(err) @@ -106,57 +106,71 @@ func (x *experiments) get(ctx context.Context, q querier, id string) (*treatment if e.LatestEvidence, err = load(latest); err != nil { return nil, err } + if e.CohortOrder, err = loadCohort(ctx, q, id); err != nil { + return nil, err + } + if e.Phases, err = loadPhases(ctx, q, id, load); err != nil { + return nil, err + } + return e, nil +} + +// loadCohort reads the experiment's cohort in allocation order. +func loadCohort(ctx context.Context, q querier, id string) ([]string, error) { rows, err := q.QueryContext(ctx, "SELECT agent_id FROM xtcp_treatment_cohort WHERE experiment_id=$1 ORDER BY position", id) if err != nil { return nil, err } + var cohort []string for rows.Next() { var a string if err = rows.Scan(&a); err != nil { - rows.Close() - return nil, err + return nil, errors.Join(err, rows.Close()) } - e.CohortOrder = append(e.CohortOrder, a) + cohort = append(cohort, a) } if err = errors.Join(rows.Err(), rows.Close()); err != nil { return nil, err } - rows, err = q.QueryContext(ctx, "SELECT target_hosts,issued_at,observation_started_at,evaluated_at,evidence_id,outcome,reviewer,reason FROM xtcp_treatment_phases WHERE experiment_id=$1 ORDER BY position", id) + return cohort, nil +} + +// loadPhases reads the experiment's phases in order and resolves each +// phase's evidence through load. Evidence is loaded after the rows are +// closed so a transaction never has two result sets open. +func loadPhases(ctx context.Context, q querier, id string, load func(sql.NullString) (*treatment.Evidence, error)) ([]treatment.Phase, error) { + rows, err := q.QueryContext(ctx, "SELECT target_hosts,issued_at,observation_started_at,evaluated_at,evidence_id,outcome,reviewer,reason FROM xtcp_treatment_phases WHERE experiment_id=$1 ORDER BY position", id) if err != nil { return nil, err } - type phaseRow struct { - p treatment.Phase - ev sql.NullString - } - var phases []phaseRow + var phases []treatment.Phase + var evidenceIDs []sql.NullString for rows.Next() { - var r phaseRow + var p treatment.Phase + var ev sql.NullString var target int64 var issued, started, evaluated sql.NullTime var out string - if err = rows.Scan(&target, &issued, &started, &evaluated, &r.ev, &out, &r.p.Reviewer, &r.p.Reason); err != nil { - rows.Close() - return nil, err + if err = rows.Scan(&target, &issued, &started, &evaluated, &ev, &out, &p.Reviewer, &p.Reason); err != nil { + return nil, errors.Join(err, rows.Close()) } - r.p.TargetHosts = uint32(target) - r.p.IssuedAt, r.p.ObservationStartedAt, r.p.EvaluatedAt = fromNullTime(issued), fromNullTime(started), fromNullTime(evaluated) - if r.p.Outcome, err = treatment.ParseOutcome(out); err != nil { - rows.Close() - return nil, err + p.TargetHosts = uint32(target) + p.IssuedAt, p.ObservationStartedAt, p.EvaluatedAt = fromNullTime(issued), fromNullTime(started), fromNullTime(evaluated) + if p.Outcome, err = treatment.ParseOutcome(out); err != nil { + return nil, errors.Join(err, rows.Close()) } - phases = append(phases, r) + phases = append(phases, p) + evidenceIDs = append(evidenceIDs, ev) } if err = errors.Join(rows.Err(), rows.Close()); err != nil { return nil, err } - for _, r := range phases { - if r.p.Evidence, err = load(r.ev); err != nil { + for i := range phases { + if phases[i].Evidence, err = load(evidenceIDs[i]); err != nil { return nil, err } - e.Phases = append(e.Phases, r.p) } - return e, nil + return phases, nil } // Save stores the evidence the experiment carries, upserts the row, writes @@ -189,7 +203,7 @@ func (x *experiments) Save(ctx context.Context, e *treatment.Experiment) error { for i, a := range e.CohortOrder { rows[i] = []any{e.ID, i, a} } - if err := batchInsert(ctx, q, "xtcp_treatment_cohort", []string{"experiment_id", "position", "agent_id"}, rows); err != nil { + if err := batchInsert(ctx, q, "xtcp_treatment_cohort", []string{"experiment_id", positionColumnCst, agentIDColumnCst}, rows); err != nil { return err } } @@ -216,8 +230,7 @@ func (x *experiments) List(ctx context.Context, id string, limit int) ([]*treatm for rows.Next() { var v string if err = rows.Scan(&v); err != nil { - rows.Close() - return nil, err + return nil, errors.Join(err, rows.Close()) } ids = append(ids, v) } @@ -256,8 +269,7 @@ func (x *experiments) IDs(ctx context.Context, statuses ...treatment.Status) ([] for rows.Next() { var id string if err = rows.Scan(&id); err != nil { - rows.Close() - return nil, err + return nil, errors.Join(err, rows.Close()) } out = append(out, id) } diff --git a/pkg/treatment/controller/store/postgres/heartbeat_test.go b/pkg/treatment/controller/store/postgres/heartbeat_test.go index 71ced21bc..bab7d57c9 100644 --- a/pkg/treatment/controller/store/postgres/heartbeat_test.go +++ b/pkg/treatment/controller/store/postgres/heartbeat_test.go @@ -1,7 +1,6 @@ package postgres_test import ( - "context" "slices" "testing" "time" @@ -54,7 +53,6 @@ func sampleReport() *treatment.AgentReport { // every check-in stamps the agents row, but the algorithm and record rows // are written only when the report content behind them changed. func TestPostgresObserveRewritesOnlyChangedReports(t *testing.T) { - ctx := context.Background() for _, tc := range []struct { name, description string mutate func(r *treatment.AgentReport) @@ -77,6 +75,7 @@ func TestPostgresObserveRewritesOnlyChangedReports(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Log(tc.description) s := pgtest.Open(t) + ctx := t.Context() a := s.Repos().Agents first := sampleReport() if err := a.Enroll(ctx, "host-1", "dc1", &treatment.DesiredState{AgentID: "host-1", Revision: 1, IssuedAt: time.Unix(1700000000, 0)}); err != nil { diff --git a/pkg/treatment/controller/store/postgres/postgres.go b/pkg/treatment/controller/store/postgres/postgres.go index 75dab3d24..28567ea53 100644 --- a/pkg/treatment/controller/store/postgres/postgres.go +++ b/pkg/treatment/controller/store/postgres/postgres.go @@ -10,7 +10,6 @@ import ( _ "embed" "errors" "fmt" - "hash/fnv" "strings" "time" @@ -40,8 +39,7 @@ func Open(ctx context.Context, dsn string) (*Store, error) { db.SetMaxIdleConns(8) db.SetConnMaxLifetime(30 * time.Minute) if err = db.PingContext(ctx); err != nil { - db.Close() - return nil, err + return nil, errors.Join(err, db.Close()) } return &Store{db: db}, nil } @@ -65,12 +63,12 @@ func (s *Store) Repos() store.Repos { return repos(&runner{db: s.db}) } // Tx runs fn in one transaction holding the mutation lock. Agent heartbeats // go through Repos and never take it. -func (s *Store) Tx(ctx context.Context, fn func(store.Repos) error) error { +func (s *Store) Tx(ctx context.Context, fn func(store.Repos) error) (err error) { tx, err := s.db.BeginTx(ctx, nil) if err != nil { return err } - defer tx.Rollback() + defer rollback(tx, &err) if _, err = tx.ExecContext(ctx, "SELECT pg_advisory_xact_lock($1)", mutationLock); err != nil { return err } @@ -84,14 +82,18 @@ func (s *Store) Tx(ctx context.Context, fn func(store.Repos) error) error { // it outlives any transaction and ends when fn returns or the connection // drops. Names hash into the same key space as mutationLock; the lock a // name maps to is stable across replicas because the hash is. -func (s *Store) Lease(ctx context.Context, name string, fn func() error) (bool, error) { +func (s *Store) Lease(ctx context.Context, name string, fn func() error) (held bool, err error) { conn, err := s.db.Conn(ctx) if err != nil { return false, err } - defer conn.Close() + defer func() { + // A connection discarded after a failed unlock reports ErrConnDone. + if cerr := conn.Close(); cerr != nil && !errors.Is(cerr, sql.ErrConnDone) && err == nil { + err = cerr + } + }() key := leaseKey(name) - var held bool if err = conn.QueryRowContext(ctx, "SELECT pg_try_advisory_lock($1)", key).Scan(&held); err != nil { return false, err } @@ -103,21 +105,39 @@ func (s *Store) Lease(ctx context.Context, name string, fn func() error) (bool, defer cancel() if _, e := conn.ExecContext(unlockCtx, "SELECT pg_advisory_unlock($1)", key); e != nil { // A pooled connection must never carry the lock into its next - // use; discarding it releases the session lock server-side. - _ = conn.Raw(func(any) error { return driver.ErrBadConn }) + // use; discarding it releases the session lock server-side, so + // the expected ErrBadConn is the success case here. + err = errors.Join(err, fmt.Errorf("lease %s unlock: %w", name, e)) + if derr := conn.Raw(func(any) error { return driver.ErrBadConn }); !errors.Is(derr, driver.ErrBadConn) { + err = errors.Join(err, derr) + } } }() return true, fn() } // leaseUnlockTimeoutCst bounds the unlock that runs after the caller's -// context may already be cancelled. +// context may already be canceled. const leaseUnlockTimeoutCst = 5 * time.Second +// leaseKey is the 64-bit FNV-1a hash of name, so every replica maps a name +// to the same advisory lock. func leaseKey(name string) int64 { - h := fnv.New64a() - h.Write([]byte(name)) - return int64(h.Sum64()) + h := uint64(14695981039346656037) + for i := 0; i < len(name); i++ { + h ^= uint64(name[i]) + h *= 1099511628211 + } + return int64(h) +} + +// rollback ends a transaction that did not commit. After Commit it reports +// ErrTxDone, which is expected; any other failure is kept when the +// transaction's own result was success. +func rollback(tx *sql.Tx, err *error) { + if rerr := tx.Rollback(); rerr != nil && !errors.Is(rerr, sql.ErrTxDone) && *err == nil { + *err = rerr + } } func repos(r *runner) store.Repos { @@ -151,7 +171,7 @@ func (r *runner) locked() bool { return r.tx != nil } // atomic runs fn in the open transaction, or in a short private one when the // repository is bound to the pool, so multi-statement writes stay consistent // without the mutation lock. -func (r *runner) atomic(ctx context.Context, fn func(querier) error) error { +func (r *runner) atomic(ctx context.Context, fn func(querier) error) (err error) { if r.tx != nil { return fn(r.tx) } @@ -159,7 +179,7 @@ func (r *runner) atomic(ctx context.Context, fn func(querier) error) error { if err != nil { return err } - defer tx.Rollback() + defer rollback(tx, &err) if err = fn(tx); err != nil { return err } diff --git a/pkg/treatment/controller/store/storetest/storetest.go b/pkg/treatment/controller/store/storetest/storetest.go index ba95716cf..2a69b92ca 100644 --- a/pkg/treatment/controller/store/storetest/storetest.go +++ b/pkg/treatment/controller/store/storetest/storetest.go @@ -21,6 +21,18 @@ import ( "google.golang.org/protobuf/proto" ) +// Fixture values shared by many rows. +const ( + locationCst = "dc1" + host1Cst = "host-1" + host2Cst = "host-2" + destinationCst = "192.0.2.1/32" + agentScopeCst = "agent:h" + missingCst = "missing" + fleetScopeCst = "fleet" + locationScopeCst = "location:" + locationCst +) + // Opener returns a fresh, empty store for one test. type Opener func(t *testing.T) store.Store @@ -88,29 +100,42 @@ func sameEvidence(t *testing.T, description string, got, want *treatment.Evidenc } } +// agentCase is one Agents contract row. +type agentCase struct { + name, description string + run func(t *testing.T, a store.Agents) +} + func agents(t *testing.T, open Opener) { ctx := context.Background() - initial := &treatment.DesiredState{AgentID: "host-1", Revision: 1, Suspended: true, IssuedAt: now} - for _, tc := range []struct { - name, description string - run func(t *testing.T, a store.Agents) - }{ + initial := &treatment.DesiredState{AgentID: host1Cst, Revision: 1, Suspended: true, IssuedAt: now} + for _, tc := range slices.Concat(agentEnrollCases(ctx, initial), agentDesiredSinceCases(ctx, initial), agentLookupCases(ctx, initial)) { + t.Run(tc.name, func(t *testing.T) { + t.Log(tc.description) + tc.run(t, open(t).Repos().Agents) + }) + } +} + +// agentEnrollCases covers enrollment, reports and the desired-state round trip. +func agentEnrollCases(ctx context.Context, initial *treatment.DesiredState) []agentCase { + return []agentCase{ {"enroll-roundtrip", "positive: enrollment stores location and the initial desired state", func(t *testing.T, a store.Agents) { - must(t, a.Enroll(ctx, "host-1", "dc1", initial)) - got, err := a.Get(ctx, "host-1") + must(t, a.Enroll(ctx, host1Cst, locationCst, initial)) + got, err := a.Get(ctx, host1Cst) must(t, err) - if got.ID != "host-1" || got.Location != "dc1" || got.EnrolledAt.IsZero() || !got.LastSeen.IsZero() || got.Report.AgentID != "host-1" || got.Report.Location != "dc1" { + if got.ID != host1Cst || got.Location != locationCst || got.EnrolledAt.IsZero() || !got.LastSeen.IsZero() || got.Report.AgentID != host1Cst || got.Report.Location != locationCst { t.Fatalf("expected an enrolled agent without a report, got %+v", got) } - d, err := a.Desired(ctx, "host-1") + d, err := a.Desired(ctx, host1Cst) must(t, err) sameDesired(t, "expected the initial desired state", d, initial) }}, {"observe-roundtrip", "positive: a report is stored with every capability and record column", func(t *testing.T, a store.Agents) { - must(t, a.Enroll(ctx, "host-1", "dc1", initial)) - r := report("host-1", "dc1") + must(t, a.Enroll(ctx, host1Cst, locationCst, initial)) + r := report(host1Cst, locationCst) must(t, a.Observe(ctx, r, now.Add(time.Minute))) - got, err := a.Get(ctx, "host-1") + got, err := a.Get(ctx, host1Cst) must(t, err) if !got.LastSeen.Equal(now.Add(time.Minute)) || !got.Seen(now.Add(2*time.Minute), 2*time.Minute) || got.Seen(now.Add(4*time.Minute), 2*time.Minute) { t.Fatalf("expected last_seen %s, got %s", now.Add(time.Minute), got.LastSeen) @@ -118,7 +143,7 @@ func agents(t *testing.T, open Opener) { sameReport(t, "expected the observed report", &got.Report, r) }}, {"desired-roundtrip", "positive: every assignment column, predecessor and lease survives", func(t *testing.T, a store.Agents) { - must(t, a.Enroll(ctx, "host-1", "dc1", initial)) + must(t, a.Enroll(ctx, host1Cst, locationCst, initial)) d := fixtures.Desired(now) d.Revision = 7 pred := fixtures.Treatment() @@ -126,45 +151,51 @@ func agents(t *testing.T, open Opener) { pred.Settings = treatment.TCPSettings{CongestionControl: ptr("bbr"), CongestionLock: true, RTOMinMs: ptr(uint32(5)), InitialCongestionWindow: ptr(uint32(100)), InitialReceiveWindow: ptr(uint32(0)), QuickAck: ptr(false), ECN: ptr(true)} d.Assignments = append(d.Assignments, treatment.Assignment{ExperimentID: "stable-1", Generation: 4, Mode: treatment.ModeStable, Treatment: treatment.Treatment{ID: "v6", Revision: 3, Destination: netip.MustParsePrefix("2001:db8::1/128"), Settings: pred.Settings, ECNFabricVerified: true}, Predecessor: &pred}) must(t, a.SaveDesired(ctx, d)) - got, err := a.Desired(ctx, "host-1") + got, err := a.Desired(ctx, host1Cst) must(t, err) sameDesired(t, "expected the saved desired state", got, d) revs, err := a.DesiredSince(ctx, time.Time{}) must(t, err) - if len(revs) != 1 || revs[0].AgentID != "host-1" || revs[0].Revision != 7 || revs[0].UpdatedAt.IsZero() { + if len(revs) != 1 || revs[0].AgentID != host1Cst || revs[0].Revision != 7 || revs[0].UpdatedAt.IsZero() { t.Fatalf("expected a stamped revision 7 for host-1, got %+v", revs) } }}, + } +} + +// agentDesiredSinceCases covers the notification watermark query. +func agentDesiredSinceCases(ctx context.Context, initial *treatment.DesiredState) []agentCase { + return []agentCase{ {"desired-since-all", "positive: a zero watermark returns every agent, enrolled or saved, ordered by stamp then ID", func(t *testing.T, a store.Agents) { - must(t, a.Enroll(ctx, "host-2", "dc1", &treatment.DesiredState{AgentID: "host-2", Revision: 1, IssuedAt: now})) + must(t, a.Enroll(ctx, host2Cst, locationCst, &treatment.DesiredState{AgentID: host2Cst, Revision: 1, IssuedAt: now})) settle() - must(t, a.Enroll(ctx, "host-1", "dc1", initial)) + must(t, a.Enroll(ctx, host1Cst, locationCst, initial)) settle() - must(t, a.SaveDesired(ctx, &treatment.DesiredState{AgentID: "host-2", Revision: 2, IssuedAt: now})) + must(t, a.SaveDesired(ctx, &treatment.DesiredState{AgentID: host2Cst, Revision: 2, IssuedAt: now})) got, err := a.DesiredSince(ctx, time.Time{}) must(t, err) - if len(got) != 2 || got[0].AgentID != "host-1" || got[0].Revision != 1 || got[1].AgentID != "host-2" || got[1].Revision != 2 || !got[0].UpdatedAt.Before(got[1].UpdatedAt) { + if len(got) != 2 || got[0].AgentID != host1Cst || got[0].Revision != 1 || got[1].AgentID != host2Cst || got[1].Revision != 2 || !got[0].UpdatedAt.Before(got[1].UpdatedAt) { t.Fatalf("expected host-1 rev 1 then host-2 rev 2 in stamp order, got %+v", got) } }}, {"desired-since-changed", "positive: a watermark after the older stamp returns only the agent saved since", func(t *testing.T, a store.Agents) { - must(t, a.Enroll(ctx, "host-1", "dc1", initial)) - must(t, a.Enroll(ctx, "host-2", "dc1", initial)) + must(t, a.Enroll(ctx, host1Cst, locationCst, initial)) + must(t, a.Enroll(ctx, host2Cst, locationCst, initial)) all, err := a.DesiredSince(ctx, time.Time{}) must(t, err) newest := all[len(all)-1].UpdatedAt settle() - must(t, a.SaveDesired(ctx, &treatment.DesiredState{AgentID: "host-1", Revision: 5, IssuedAt: now})) + must(t, a.SaveDesired(ctx, &treatment.DesiredState{AgentID: host1Cst, Revision: 5, IssuedAt: now})) got, err := a.DesiredSince(ctx, newest.Add(time.Microsecond)) must(t, err) - if len(got) != 1 || got[0].AgentID != "host-1" || got[0].Revision != 5 || !got[0].UpdatedAt.After(newest) { + if len(got) != 1 || got[0].AgentID != host1Cst || got[0].Revision != 5 || !got[0].UpdatedAt.After(newest) { t.Fatalf("expected only host-1 at revision 5, got %+v", got) } }}, {"desired-since-equal", "boundary: a row stamped exactly at the watermark is included", func(t *testing.T, a store.Agents) { - must(t, a.Enroll(ctx, "host-1", "dc1", initial)) + must(t, a.Enroll(ctx, host1Cst, locationCst, initial)) settle() - must(t, a.Enroll(ctx, "host-2", "dc1", initial)) + must(t, a.Enroll(ctx, host2Cst, locationCst, initial)) all, err := a.DesiredSince(ctx, time.Time{}) must(t, err) got, err := a.DesiredSince(ctx, all[1].UpdatedAt) @@ -174,7 +205,7 @@ func agents(t *testing.T, open Opener) { } }}, {"desired-since-none", "boundary: a watermark past the newest stamp returns nothing without error", func(t *testing.T, a store.Agents) { - must(t, a.Enroll(ctx, "host-1", "dc1", initial)) + must(t, a.Enroll(ctx, host1Cst, locationCst, initial)) all, err := a.DesiredSince(ctx, time.Time{}) must(t, err) got, err := a.DesiredSince(ctx, all[0].UpdatedAt.Add(time.Microsecond)) @@ -184,31 +215,37 @@ func agents(t *testing.T, open Opener) { } }}, {"desired-since-stamp-is-store-clock", "corner: the stamp is the store's clock, not the desired state's issued_at", func(t *testing.T, a store.Agents) { - must(t, a.Enroll(ctx, "host-1", "dc1", initial)) - must(t, a.SaveDesired(ctx, &treatment.DesiredState{AgentID: "host-1", Revision: 2, IssuedAt: now.Add(-24 * time.Hour)})) + must(t, a.Enroll(ctx, host1Cst, locationCst, initial)) + must(t, a.SaveDesired(ctx, &treatment.DesiredState{AgentID: host1Cst, Revision: 2, IssuedAt: now.Add(-24 * time.Hour)})) got, err := a.DesiredSince(ctx, now) must(t, err) if len(got) != 1 || got[0].UpdatedAt.Before(now) { t.Fatalf("expected a current stamp despite an old issued_at, got %+v", got) } }}, + } +} + +// agentLookupCases covers idempotence, identity fences, bounds and listing. +func agentLookupCases(ctx context.Context, initial *treatment.DesiredState) []agentCase { + return []agentCase{ {"enroll-idempotent", "corner: a second enrollment never changes location or desired state", func(t *testing.T, a store.Agents) { - must(t, a.Enroll(ctx, "host-1", "dc1", initial)) - must(t, a.Enroll(ctx, "host-1", "dc2", &treatment.DesiredState{AgentID: "host-1", Revision: 9, IssuedAt: now})) - got, err := a.Get(ctx, "host-1") + must(t, a.Enroll(ctx, host1Cst, locationCst, initial)) + must(t, a.Enroll(ctx, host1Cst, "dc2", &treatment.DesiredState{AgentID: host1Cst, Revision: 9, IssuedAt: now})) + got, err := a.Get(ctx, host1Cst) must(t, err) - d, err := a.Desired(ctx, "host-1") + d, err := a.Desired(ctx, host1Cst) must(t, err) - if got.Location != "dc1" || d.Revision != 1 || !d.Suspended { + if got.Location != locationCst || d.Revision != 1 || !d.Suspended { t.Fatalf("expected the first enrollment to win, got %s revision %d", got.Location, d.Revision) } }}, {"observe-location-change", "negative: a host cannot move location by reporting a new one", func(t *testing.T, a store.Agents) { - must(t, a.Enroll(ctx, "host-1", "dc1", initial)) - if err := a.Observe(ctx, report("host-1", "dc2"), now); !errors.Is(err, store.ErrNotEnrolled) { + must(t, a.Enroll(ctx, host1Cst, locationCst, initial)) + if err := a.Observe(ctx, report(host1Cst, "dc2"), now); !errors.Is(err, store.ErrNotEnrolled) { t.Fatalf("expected ErrNotEnrolled, got %v", err) } - got, err := a.Get(ctx, "host-1") + got, err := a.Get(ctx, host1Cst) must(t, err) if !got.LastSeen.IsZero() || len(got.Report.Records) != 0 { t.Fatal("expected the rejected report to leave no trace") @@ -224,26 +261,26 @@ func agents(t *testing.T, open Opener) { if err := a.SaveDesired(ctx, &treatment.DesiredState{AgentID: "ghost", Revision: 1, IssuedAt: now}); !errors.Is(err, store.ErrNotEnrolled) { t.Fatalf("expected ErrNotEnrolled from SaveDesired, got %v", err) } - if err := a.Observe(ctx, report("ghost", "dc1"), now); !errors.Is(err, store.ErrNotEnrolled) { + if err := a.Observe(ctx, report("ghost", locationCst), now); !errors.Is(err, store.ErrNotEnrolled) { t.Fatalf("expected ErrNotEnrolled from Observe, got %v", err) } }}, {"empty-desired", "boundary: a desired state with no assignments replaces a populated one", func(t *testing.T, a store.Agents) { - must(t, a.Enroll(ctx, "host-1", "dc1", fixtures.Desired(now))) - empty := &treatment.DesiredState{AgentID: "host-1", Revision: 2, IssuedAt: now.Add(time.Minute)} + must(t, a.Enroll(ctx, host1Cst, locationCst, fixtures.Desired(now))) + empty := &treatment.DesiredState{AgentID: host1Cst, Revision: 2, IssuedAt: now.Add(time.Minute)} must(t, a.SaveDesired(ctx, empty)) - got, err := a.Desired(ctx, "host-1") + got, err := a.Desired(ctx, host1Cst) must(t, err) sameDesired(t, "expected no assignments", got, empty) }}, {"max-assignments", "boundary: MaxAssignments assignments are stored in order", func(t *testing.T, a store.Agents) { - must(t, a.Enroll(ctx, "host-1", "dc1", initial)) - d := &treatment.DesiredState{AgentID: "host-1", Revision: 2, IssuedAt: now} + must(t, a.Enroll(ctx, host1Cst, locationCst, initial)) + d := &treatment.DesiredState{AgentID: host1Cst, Revision: 2, IssuedAt: now} for i := range treatment.MaxAssignments { d.Assignments = append(d.Assignments, treatment.Assignment{ExperimentID: fmt.Sprintf("exp-%05d", i), Generation: uint64(i), Mode: treatment.ModeTrial, LeaseExpiresAt: now.Add(time.Hour), Treatment: fixtures.Treatment()}) } must(t, a.SaveDesired(ctx, d)) - got, err := a.Desired(ctx, "host-1") + got, err := a.Desired(ctx, host1Cst) must(t, err) if len(got.Assignments) != treatment.MaxAssignments { t.Fatalf("expected %d assignments, got %d", treatment.MaxAssignments, len(got.Assignments)) @@ -255,25 +292,20 @@ func agents(t *testing.T, open Opener) { } }}, {"list-by-location", "positive: List filters by location and orders by ID; empty location lists everyone", func(t *testing.T, a store.Agents) { - for _, h := range []struct{ id, loc string }{{"c", "dc2"}, {"a", "dc1"}, {"b", "dc1"}} { + for _, h := range []struct{ id, loc string }{{"c", "dc2"}, {"a", locationCst}, {"b", locationCst}} { must(t, a.Enroll(ctx, h.id, h.loc, &treatment.DesiredState{AgentID: h.id, Revision: 1, IssuedAt: now})) } - dc1, err := a.List(ctx, "dc1") + dc1, err := a.List(ctx, locationCst) must(t, err) all, err := a.List(ctx, "") must(t, err) none, err := a.List(ctx, "dc3") must(t, err) - want := []store.AgentRef{{ID: "a", Location: "dc1"}, {ID: "b", Location: "dc1"}} + want := []store.AgentRef{{ID: "a", Location: locationCst}, {ID: "b", Location: locationCst}} if !slices.Equal(dc1, want) || len(all) != 3 || all[2].ID != "c" || len(none) != 0 { t.Fatalf("expected %v / 3 / 0, got %v / %d / %d", want, dc1, len(all), len(none)) } }}, - } { - t.Run(tc.name, func(t *testing.T) { - t.Log(tc.description) - tc.run(t, open(t).Repos().Agents) - }) } } @@ -292,12 +324,25 @@ func populated() *treatment.Experiment { return e } +// repoCase is one Experiments contract row. +type repoCase struct { + name, description string + run func(t *testing.T, rp store.Repos) +} + func experiments(t *testing.T, open Opener) { ctx := context.Background() - for _, tc := range []struct { - name, description string - run func(t *testing.T, rp store.Repos) - }{ + for _, tc := range slices.Concat(experimentRoundTripCases(ctx), experimentQueryCases(ctx)) { + t.Run(tc.name, func(t *testing.T) { + t.Log(tc.description) + tc.run(t, open(t).Repos()) + }) + } +} + +// experimentRoundTripCases covers what Save writes and Get reads back. +func experimentRoundTripCases(ctx context.Context) []repoCase { + return []repoCase{ {"insert-relational", "positive: cohort, phases, baseline and evidence references reconstruct the experiment exactly", func(t *testing.T, rp store.Repos) { e := populated() must(t, rp.Experiments.Save(ctx, e)) @@ -340,14 +385,14 @@ func experiments(t *testing.T, open Opener) { } }}, {"no-evidence", "boundary: an experiment without baseline, latest evidence or phases round-trips", func(t *testing.T, rp store.Repos) { - e := &treatment.Experiment{ID: "bare", Location: "dc1", Hypothesis: "h", Treatment: fixtures.Treatment(), Policy: treatment.DefaultPolicy(), Status: treatment.StatusProposed, Revision: 1, ProposedAt: now, UpdatedAt: now} + e := &treatment.Experiment{ID: "bare", Location: locationCst, Hypothesis: "h", Treatment: fixtures.Treatment(), Policy: treatment.DefaultPolicy(), Status: treatment.StatusProposed, Revision: 1, ProposedAt: now, UpdatedAt: now} must(t, rp.Experiments.Save(ctx, e)) got, err := rp.Experiments.Get(ctx, "bare") must(t, err) sameExperiment(t, "expected the bare experiment", got, e) }}, {"max-cohort", "boundary: a cohort of MaxAgents positions is stored in order", func(t *testing.T, rp store.Repos) { - e := &treatment.Experiment{ID: "big", Location: "dc1", Hypothesis: "h", Treatment: fixtures.Treatment(), Policy: treatment.DefaultPolicy(), Status: treatment.StatusProposed, Revision: 1, ProposedAt: now, UpdatedAt: now} + e := &treatment.Experiment{ID: "big", Location: locationCst, Hypothesis: "h", Treatment: fixtures.Treatment(), Policy: treatment.DefaultPolicy(), Status: treatment.StatusProposed, Revision: 1, ProposedAt: now, UpdatedAt: now} for i := range treatment.MaxAgents { e.CohortOrder = append(e.CohortOrder, fmt.Sprintf("host-%05d", i)) } @@ -358,7 +403,13 @@ func experiments(t *testing.T, open Opener) { t.Fatalf("expected %d ordered cohort members, got %d", len(e.CohortOrder), len(got.CohortOrder)) } }}, - {"missing", "negative: an unknown experiment is ErrNotFound and does not exist", func(t *testing.T, rp store.Repos) { + } +} + +// experimentQueryCases covers lookups, conflicts, listing and status filters. +func experimentQueryCases(ctx context.Context) []repoCase { + return []repoCase{ + {missingCst, "negative: an unknown experiment is ErrNotFound and does not exist", func(t *testing.T, rp store.Repos) { if _, err := rp.Experiments.Get(ctx, "ghost"); !errors.Is(err, store.ErrNotFound) { t.Fatalf("expected ErrNotFound, got %v", err) } @@ -387,9 +438,9 @@ func experiments(t *testing.T, open Opener) { t.Fatal("expected the conflicting save to leave no row") } }}, - {"list-and-ids", "positive: List orders by ID and honours the limit; IDs filters by status", func(t *testing.T, rp store.Repos) { + {"list-and-ids", "positive: List orders by ID and honors the limit; IDs filters by status", func(t *testing.T, rp store.Repos) { for i, s := range []treatment.Status{treatment.StatusRunning, treatment.StatusProposed, treatment.StatusRetired, treatment.StatusRunning} { - e := &treatment.Experiment{ID: fmt.Sprintf("e%d", 3-i), Location: "dc1", Hypothesis: "h", Treatment: fixtures.Treatment(), Policy: treatment.DefaultPolicy(), Status: s, Revision: 1, ProposedAt: now, UpdatedAt: now} + e := &treatment.Experiment{ID: fmt.Sprintf("e%d", 3-i), Location: locationCst, Hypothesis: "h", Treatment: fixtures.Treatment(), Policy: treatment.DefaultPolicy(), Status: s, Revision: 1, ProposedAt: now, UpdatedAt: now} must(t, rp.Experiments.Save(ctx, e)) } list, err := rp.Experiments.List(ctx, "", 3) @@ -414,11 +465,6 @@ func experiments(t *testing.T, open Opener) { t.Fatalf("expected [e0 e3] and nothing for no statuses, got %v %v", running, none) } }}, - } { - t.Run(tc.name, func(t *testing.T) { - t.Log(tc.description) - tc.run(t, open(t).Repos()) - }) } } @@ -426,7 +472,7 @@ func experiments(t *testing.T, open Opener) { // destination or consume the location's single-experiment budget. func eligibility(t *testing.T, open Opener) { ctx := context.Background() - dst := netip.MustParsePrefix("192.0.2.1/32") + dst := netip.MustParsePrefix(destinationCst) for _, tc := range []struct { name, description string status treatment.Status @@ -435,27 +481,27 @@ func eligibility(t *testing.T, open Opener) { expectBlocked bool expectActive bool }{ - {"proposed", "positive: pending review reserves the destination", treatment.StatusProposed, 48 * time.Hour, "dc1", "192.0.2.1/32", true, true}, - {"running", "positive: an active trial is not rediscovered", treatment.StatusRunning, 48 * time.Hour, "dc1", "192.0.2.1/32", true, true}, - {"paused", "positive: paused trials remain reserved", treatment.StatusPaused, 48 * time.Hour, "dc1", "192.0.2.1/32", true, true}, - {"suspended", "positive: temporary holds are not bypassed", treatment.StatusSuspended, 48 * time.Hour, "dc1", "192.0.2.1/32", true, true}, - {"stable", "positive: completed stable treatments stay reserved and free the budget", treatment.StatusComplete, 48 * time.Hour, "dc1", "192.0.2.1/32", true, false}, - {"recent-rollback", "negative: a recent withdrawal is in cooldown", treatment.StatusRolledBack, 23 * time.Hour, "dc1", "192.0.2.1/32", true, false}, - {"recent-retirement", "negative: a recent retirement is in cooldown", treatment.StatusRetired, 23 * time.Hour, "dc1", "192.0.2.1/32", true, false}, - {"cooldown-boundary", "boundary: cooldown ends at exactly 24 hours", treatment.StatusRolledBack, 24 * time.Hour, "dc1", "192.0.2.1/32", false, false}, - {"old-retirement", "boundary: an old retirement does not reserve the destination", treatment.StatusRetired, 25 * time.Hour, "dc1", "192.0.2.1/32", false, false}, - {"other-location", "corner: reservation is location specific", treatment.StatusRunning, time.Hour, "dc2", "192.0.2.1/32", false, false}, - {"other-destination", "corner: another destination consumes the budget only", treatment.StatusRunning, time.Hour, "dc1", "192.0.2.2/32", false, true}, - {"covering-prefix", "corner: a /24 experiment does not reserve a /32 inside it", treatment.StatusRunning, time.Hour, "dc1", "192.0.2.0/24", false, true}, + {"proposed", "positive: pending review reserves the destination", treatment.StatusProposed, 48 * time.Hour, locationCst, destinationCst, true, true}, + {"running", "positive: an active trial is not rediscovered", treatment.StatusRunning, 48 * time.Hour, locationCst, destinationCst, true, true}, + {"paused", "positive: paused trials remain reserved", treatment.StatusPaused, 48 * time.Hour, locationCst, destinationCst, true, true}, + {"suspended", "positive: temporary holds are not bypassed", treatment.StatusSuspended, 48 * time.Hour, locationCst, destinationCst, true, true}, + {"stable", "positive: completed stable treatments stay reserved and free the budget", treatment.StatusComplete, 48 * time.Hour, locationCst, destinationCst, true, false}, + {"recent-rollback", "negative: a recent withdrawal is in cooldown", treatment.StatusRolledBack, 23 * time.Hour, locationCst, destinationCst, true, false}, + {"recent-retirement", "negative: a recent retirement is in cooldown", treatment.StatusRetired, 23 * time.Hour, locationCst, destinationCst, true, false}, + {"cooldown-boundary", "boundary: cooldown ends at exactly 24 hours", treatment.StatusRolledBack, 24 * time.Hour, locationCst, destinationCst, false, false}, + {"old-retirement", "boundary: an old retirement does not reserve the destination", treatment.StatusRetired, 25 * time.Hour, locationCst, destinationCst, false, false}, + {"other-location", "corner: reservation is location specific", treatment.StatusRunning, time.Hour, "dc2", destinationCst, false, false}, + {"other-destination", "corner: another destination consumes the budget only", treatment.StatusRunning, time.Hour, locationCst, "192.0.2.2/32", false, true}, + {"covering-prefix", "corner: a /24 experiment does not reserve a /32 inside it", treatment.StatusRunning, time.Hour, locationCst, "192.0.2.0/24", false, true}, } { t.Run(tc.name, func(t *testing.T) { rp := open(t).Repos() tr := fixtures.Treatment() tr.Destination = netip.MustParsePrefix(tc.destination) must(t, rp.Experiments.Save(ctx, &treatment.Experiment{ID: "prior", Location: tc.location, Hypothesis: "h", Treatment: tr, Policy: treatment.DefaultPolicy(), Status: tc.status, Revision: 1, ProposedAt: now.Add(-tc.age), UpdatedAt: now.Add(-tc.age)})) - blocked, err := rp.Experiments.DestinationBlocked(ctx, "dc1", dst, now.Add(-24*time.Hour)) + blocked, err := rp.Experiments.DestinationBlocked(ctx, locationCst, dst, now.Add(-24*time.Hour)) must(t, err) - active, err := rp.Experiments.HasActive(ctx, "dc1") + active, err := rp.Experiments.HasActive(ctx, locationCst) must(t, err) if blocked != tc.expectBlocked || active != tc.expectActive { t.Fatalf("%s: blocked=%t active=%t, expected %t/%t", tc.description, blocked, active, tc.expectBlocked, tc.expectActive) @@ -467,10 +513,10 @@ func eligibility(t *testing.T, open Opener) { rp := open(t).Repos() tr := fixtures.Treatment() tr.Destination = netip.MustParsePrefix("2001:db8::1/128") - must(t, rp.Experiments.Save(ctx, &treatment.Experiment{ID: "v6", Location: "dc1", Hypothesis: "h", Treatment: tr, Policy: treatment.DefaultPolicy(), Status: treatment.StatusRunning, Revision: 1, ProposedAt: now, UpdatedAt: now})) - hit, err := rp.Experiments.DestinationBlocked(ctx, "dc1", tr.Destination, now) + must(t, rp.Experiments.Save(ctx, &treatment.Experiment{ID: "v6", Location: locationCst, Hypothesis: "h", Treatment: tr, Policy: treatment.DefaultPolicy(), Status: treatment.StatusRunning, Revision: 1, ProposedAt: now, UpdatedAt: now})) + hit, err := rp.Experiments.DestinationBlocked(ctx, locationCst, tr.Destination, now) must(t, err) - miss, err := rp.Experiments.DestinationBlocked(ctx, "dc1", netip.MustParsePrefix("2001:db8::2/128"), now) + miss, err := rp.Experiments.DestinationBlocked(ctx, locationCst, netip.MustParsePrefix("2001:db8::2/128"), now) must(t, err) if !hit || miss { t.Fatalf("expected exact /128 match only, got hit=%t miss=%t", hit, miss) @@ -481,7 +527,7 @@ func eligibility(t *testing.T, open Opener) { func evidence(t *testing.T, open Opener) { ctx := context.Background() base := func() *treatment.Evidence { - return &treatment.Evidence{ID: "ev-1", AnalyzedAt: now, WindowStart: now.Add(-time.Hour), WindowEnd: now, QueryVersion: "v1", EnrichmentVersion: "e1", ArtifactSHA256: strings.Repeat("b", 64), Hosts: []treatment.HostEvidence{{AgentID: "host-1", Sockets: 10, BytesAcked: 1 << 20, GoodputBps: 1e6, RetransmissionRatio: .001, RttP95Ms: 2.5, ExposedSockets: 9, CollectionHealthy: true}}} + return &treatment.Evidence{ID: "ev-1", AnalyzedAt: now, WindowStart: now.Add(-time.Hour), WindowEnd: now, QueryVersion: "v1", EnrichmentVersion: "e1", ArtifactSHA256: strings.Repeat("b", 64), Hosts: []treatment.HostEvidence{{AgentID: host1Cst, Sockets: 10, BytesAcked: 1 << 20, GoodputBps: 1e6, RetransmissionRatio: .001, RttP95Ms: 2.5, ExposedSockets: 9, CollectionHealthy: true}}} } for _, tc := range []struct { name, description string @@ -525,7 +571,7 @@ func evidence(t *testing.T, open Opener) { must(t, v.Put(ctx, "exp", got)) must(t, v.Put(ctx, "exp", e)) }}, - {"missing", "negative: unknown evidence is ErrNotFound", func(t *testing.T, v store.Evidence) { + {missingCst, "negative: unknown evidence is ErrNotFound", func(t *testing.T, v store.Evidence) { if _, err := v.Get(ctx, "ghost"); !errors.Is(err, store.ErrNotFound) { t.Fatalf("expected ErrNotFound, got %v", err) } @@ -573,13 +619,13 @@ func holds(t *testing.T, open Opener) { set, clear, ask []string expected bool }{ - {"fleet", "positive: a fleet hold answers any scope list containing it", []string{"fleet"}, nil, []string{"fleet", "location:dc1", "agent:h"}, true}, - {"narrower", "positive: an agent hold is seen when asked for that agent", []string{"agent:h"}, nil, []string{"fleet", "location:dc1", "agent:h"}, true}, - {"cleared", "negative: a cleared hold no longer answers", []string{"fleet"}, []string{"fleet"}, []string{"fleet"}, false}, - {"other-scope", "negative: holds on other scopes do not answer", []string{"location:dc2"}, nil, []string{"fleet", "location:dc1"}, false}, - {"no-scopes", "boundary: asking for no scopes is never held", []string{"fleet"}, nil, nil, false}, - {"clear-missing", "corner: clearing an absent scope is not an error and leaves others set", []string{"fleet"}, []string{"agent:ghost"}, []string{"fleet"}, true}, - {"reset-reason", "corner: setting a held scope again keeps it held", []string{"fleet", "fleet"}, nil, []string{"fleet"}, true}, + {fleetScopeCst, "positive: a fleet hold answers any scope list containing it", []string{fleetScopeCst}, nil, []string{fleetScopeCst, locationScopeCst, agentScopeCst}, true}, + {"narrower", "positive: an agent hold is seen when asked for that agent", []string{agentScopeCst}, nil, []string{fleetScopeCst, locationScopeCst, agentScopeCst}, true}, + {"cleared", "negative: a cleared hold no longer answers", []string{fleetScopeCst}, []string{fleetScopeCst}, []string{fleetScopeCst}, false}, + {"other-scope", "negative: holds on other scopes do not answer", []string{"location:dc2"}, nil, []string{fleetScopeCst, locationScopeCst}, false}, + {"no-scopes", "boundary: asking for no scopes is never held", []string{fleetScopeCst}, nil, nil, false}, + {"clear-missing", "corner: clearing an absent scope is not an error and leaves others set", []string{fleetScopeCst}, []string{"agent:ghost"}, []string{fleetScopeCst}, true}, + {"reset-reason", "corner: setting a held scope again keeps it held", []string{fleetScopeCst, fleetScopeCst}, nil, []string{fleetScopeCst}, true}, } { t.Run(tc.name, func(t *testing.T) { t.Log(tc.description) @@ -636,7 +682,7 @@ func audit(t *testing.T, open Opener) { t.Fatalf("expected ErrNotFound for the empty request ID, got %v", err) } }}, - {"missing", "negative: an unknown request ID is ErrNotFound", func(t *testing.T, a store.Audit) { + {missingCst, "negative: an unknown request ID is ErrNotFound", func(t *testing.T, a store.Audit) { if _, err := a.Lookup(ctx, "ghost"); !errors.Is(err, store.ErrNotFound) { t.Fatalf("expected ErrNotFound, got %v", err) } @@ -668,14 +714,14 @@ func tx(t *testing.T, open Opener) { t.Log("positive: writes inside a successful Tx are visible afterwards") s := open(t) must(t, s.Tx(ctx, func(rp store.Repos) error { - if err := rp.Holds.Set(ctx, "fleet", "incident"); err != nil { + if err := rp.Holds.Set(ctx, fleetScopeCst, "incident"); err != nil { return err } - return rp.Agents.Enroll(ctx, "host-1", "dc1", fixtures.Desired(now)) + return rp.Agents.Enroll(ctx, host1Cst, locationCst, fixtures.Desired(now)) })) - held, err := s.Repos().Holds.Held(ctx, "fleet") + held, err := s.Repos().Holds.Held(ctx, fleetScopeCst) must(t, err) - if _, err := s.Repos().Agents.Get(ctx, "host-1"); err != nil || !held { + if _, err := s.Repos().Agents.Get(ctx, host1Cst); err != nil || !held { t.Fatalf("expected committed hold and agent, got held=%t err=%v", held, err) } }) @@ -684,7 +730,7 @@ func tx(t *testing.T, open Opener) { s := open(t) boom := errors.New("boom") err := s.Tx(ctx, func(rp store.Repos) error { - if err := rp.Holds.Set(ctx, "fleet", "incident"); err != nil { + if err := rp.Holds.Set(ctx, fleetScopeCst, "incident"); err != nil { return err } if err := rp.Audit.Append(ctx, "x", "req-1", "k", []byte(`{}`)); err != nil { @@ -695,7 +741,7 @@ func tx(t *testing.T, open Opener) { if !errors.Is(err, boom) { t.Fatalf("expected the callback error, got %v", err) } - held, err := s.Repos().Holds.Held(ctx, "fleet") + held, err := s.Repos().Holds.Held(ctx, fleetScopeCst) must(t, err) n, err := s.Repos().Audit.Count(ctx) must(t, err) @@ -720,21 +766,21 @@ func tx(t *testing.T, open Opener) { return nil })) }) - t.Run("cancelled-context", func(t *testing.T) { - t.Log("boundary: a cancelled context fails the Tx and persists nothing") + t.Run("canceled-context", func(t *testing.T) { + t.Log("boundary: a canceled context fails the Tx and persists nothing") s := open(t) - cancelled, cancel := context.WithCancel(ctx) + canceled, cancel := context.WithCancel(ctx) cancel() - if err := s.Tx(cancelled, func(rp store.Repos) error { return rp.Holds.Set(ctx, "fleet", "x") }); err == nil { - t.Fatal("expected an error from a cancelled context") + if err := s.Tx(canceled, func(rp store.Repos) error { return rp.Holds.Set(ctx, fleetScopeCst, "x") }); err == nil { + t.Fatal("expected an error from a canceled context") } - held, err := s.Repos().Holds.Held(ctx, "fleet") + held, err := s.Repos().Holds.Held(ctx, fleetScopeCst) must(t, err) if held { - t.Fatal("expected no hold after a cancelled transaction") + t.Fatal("expected no hold after a canceled transaction") } }) - t.Run("serialises", func(t *testing.T) { + t.Run("serializes", func(t *testing.T) { t.Log("corner: two concurrent Tx calls run one after the other, both commit, sequences stay monotonic") s := open(t) entered := make(chan struct{}) @@ -850,12 +896,12 @@ func lease(t *testing.T, open Opener) { t.Fatal("expected lease a") } }) - t.Run("cancelled-context", func(t *testing.T) { - t.Log("negative: a cancelled context fails before fn runs") + t.Run("canceled-context", func(t *testing.T) { + t.Log("negative: a canceled context fails before fn runs") s := open(t) - cancelled, cancel := context.WithCancel(ctx) + canceled, cancel := context.WithCancel(ctx) cancel() - held, err := s.Lease(cancelled, "housekeeping", func() error { t.Error("fn ran with a cancelled context"); return nil }) + held, err := s.Lease(canceled, "housekeeping", func() error { t.Error("fn ran with a canceled context"); return nil }) if err == nil || held { t.Fatalf("expected an error and held=false, got held=%t err=%v", held, err) } @@ -879,13 +925,13 @@ func metrics(t *testing.T, open Opener) { t.Log("positive: gauges reflect agents, records, experiments and events") s := open(t) rp := s.Repos() - must(t, rp.Agents.Enroll(ctx, "host-1", "dc1", fixtures.Desired(now))) - must(t, rp.Agents.Observe(ctx, report("host-1", "dc1"), now.Add(-time.Minute))) - must(t, rp.Agents.Enroll(ctx, "host-2", "dc1", &treatment.DesiredState{AgentID: "host-2", Revision: 1, Suspended: true, IssuedAt: now})) - must(t, rp.Agents.Enroll(ctx, "host-3", "dc1", &treatment.DesiredState{AgentID: "host-3", Revision: 1, IssuedAt: now})) - must(t, rp.Agents.Observe(ctx, &treatment.AgentReport{AgentID: "host-3", Location: "dc1", Capabilities: treatment.Capabilities{BootID: "b"}, Records: []treatment.ApplicationRecord{{Assignment: fixtures.Desired(now).Assignments[0], Status: treatment.ApplyExpired, ExpiredLatched: true}}}, now.Add(-10*time.Minute))) + must(t, rp.Agents.Enroll(ctx, host1Cst, locationCst, fixtures.Desired(now))) + must(t, rp.Agents.Observe(ctx, report(host1Cst, locationCst), now.Add(-time.Minute))) + must(t, rp.Agents.Enroll(ctx, host2Cst, locationCst, &treatment.DesiredState{AgentID: host2Cst, Revision: 1, Suspended: true, IssuedAt: now})) + must(t, rp.Agents.Enroll(ctx, "host-3", locationCst, &treatment.DesiredState{AgentID: "host-3", Revision: 1, IssuedAt: now})) + must(t, rp.Agents.Observe(ctx, &treatment.AgentReport{AgentID: "host-3", Location: locationCst, Capabilities: treatment.Capabilities{BootID: "b"}, Records: []treatment.ApplicationRecord{{Assignment: fixtures.Desired(now).Assignments[0], Status: treatment.ApplyExpired, ExpiredLatched: true}}}, now.Add(-10*time.Minute))) for id, st := range map[string]treatment.Status{"p": treatment.StatusProposed, "r": treatment.StatusRunning, "c": treatment.StatusComplete, "x": treatment.StatusRetired} { - must(t, rp.Experiments.Save(ctx, &treatment.Experiment{ID: id, Location: "dc1", Hypothesis: "h", Treatment: fixtures.Treatment(), Policy: treatment.DefaultPolicy(), Status: st, Revision: 1, ProposedAt: now, UpdatedAt: now})) + must(t, rp.Experiments.Save(ctx, &treatment.Experiment{ID: id, Location: locationCst, Hypothesis: "h", Treatment: fixtures.Treatment(), Policy: treatment.DefaultPolicy(), Status: st, Revision: 1, ProposedAt: now, UpdatedAt: now})) } must(t, rp.Audit.Append(ctx, "x", "", "k", []byte(`{}`))) got, err := rp.Metrics.Snapshot(ctx, now) diff --git a/pkg/treatment/enums.go b/pkg/treatment/enums.go index 7c97d3ce2..d757dd73e 100644 --- a/pkg/treatment/enums.go +++ b/pkg/treatment/enums.go @@ -7,6 +7,9 @@ import "fmt" // convert with a range-checked cast; wire's mirror tests pin that // correspondence. Names are the snake_case forms used in SQL, logs and docs. +// unspecifiedNameCst names the zero value of every enumeration. +const unspecifiedNameCst = "unspecified" + // Status is an experiment's lifecycle state. Legal changes are the state // machine in package experiment. type Status uint8 @@ -23,7 +26,7 @@ const ( statusCount ) -var statusNames = [statusCount]string{"unspecified", "proposed", "running", "paused", "suspended", "complete", "retired", "rolled_back"} +var statusNames = [statusCount]string{unspecifiedNameCst, "proposed", "running", "paused", "suspended", "complete", "retired", "rolled_back"} func (s Status) String() string { return enumName(uint8(s), statusNames[:], "status") } @@ -69,7 +72,7 @@ const ( modeCount ) -var modeNames = [modeCount]string{"unspecified", "trial", "stable", "suspended", "retired"} +var modeNames = [modeCount]string{unspecifiedNameCst, "trial", "stable", "suspended", "retired"} func (m AssignmentMode) String() string { return enumName(uint8(m), modeNames[:], "mode") } @@ -98,7 +101,7 @@ const ( applyCount ) -var applyNames = [applyCount]string{"unspecified", "pending", "verified", "removed", "expired", "failed"} +var applyNames = [applyCount]string{unspecifiedNameCst, "pending", "verified", "removed", "expired", "failed"} func (a ApplyStatus) String() string { return enumName(uint8(a), applyNames[:], "apply status") } @@ -140,7 +143,7 @@ const ( ) var outcomeNames = [outcomeCount]string{ - "unspecified", + unspecifiedNameCst, "baseline", "awaiting_application", "awaiting_exposure", diff --git a/pkg/treatment/experiment/experiment.go b/pkg/treatment/experiment/experiment.go index a1b397e92..9ddd84ce5 100644 --- a/pkg/treatment/experiment/experiment.go +++ b/pkg/treatment/experiment/experiment.go @@ -31,84 +31,119 @@ func Evaluate(e *treatment.Experiment, ev *treatment.Evidence, now time.Time) (E return Evaluation{}, errors.New("experiment has no phase") } p := e.Policy - if phase.ObservationStartedAt.IsZero() { - return Evaluation{Outcome: treatment.OutcomeAwaitingExposure}, nil - } - if now.Sub(phase.IssuedAt) >= p.MaximumPhase { - return Evaluation{Outcome: treatment.OutcomeExpired}, nil - } - if ev.AnalyzedAt.After(now.Add(30*time.Second)) || now.Sub(ev.WindowEnd) > p.MaximumEvidenceAge { - return Evaluation{Outcome: treatment.OutcomeStale}, nil - } - if ev.WindowStart.Before(phase.ObservationStartedAt) || ev.WindowEnd.Sub(ev.WindowStart) < p.MinimumObservation { - return Evaluation{Outcome: treatment.OutcomeInsufficientDuration}, nil + if o, ok := windowOutcome(&p, phase, ev, now); ok { + return Evaluation{Outcome: o}, nil } if e.Baseline == nil { return Evaluation{}, errors.New("experiment has no baseline") } - base, current := hostIndex(e.Baseline.Hosts), hostIndex(ev.Hosts) target := int(phase.TargetHosts) if target < 1 || target > len(e.CohortOrder) { return Evaluation{}, errors.New("invalid phase cohort size") } - treated, control := make([]float64, 0, target), make([]float64, 0, len(e.CohortOrder)-target) - tr, cr := make([]float64, 0, target), make([]float64, 0, len(e.CohortOrder)-target) - tlat, clat := make([]float64, 0, target), make([]float64, 0, len(e.CohortOrder)-target) + treated, control, o := cohortSamples(e, ev, target) + if o != treatment.OutcomeUnspecified { + return Evaluation{Outcome: o}, nil + } + // Guardrails protect controls as well as treated traffic. A shared-link + // regression is harmful even if treated hosts outperform their peers. + if treated.breaches(&p) || control.breaches(&p) { + return Evaluation{Outcome: treatment.OutcomeHarmful}, nil + } + return compare(&p, treated.goodput, control.goodput, len(e.CohortOrder)), nil +} + +// windowOutcome reports the outcome when the phase or the evidence window +// cannot support an evaluation yet: no exposure, expiry, stale evidence or +// too short a window. +func windowOutcome(p *treatment.Policy, phase *treatment.Phase, ev *treatment.Evidence, now time.Time) (treatment.Outcome, bool) { + switch { + case phase.ObservationStartedAt.IsZero(): + return treatment.OutcomeAwaitingExposure, true + case now.Sub(phase.IssuedAt) >= p.MaximumPhase: + return treatment.OutcomeExpired, true + case ev.AnalyzedAt.After(now.Add(30*time.Second)) || now.Sub(ev.WindowEnd) > p.MaximumEvidenceAge: + return treatment.OutcomeStale, true + case ev.WindowStart.Before(phase.ObservationStartedAt) || ev.WindowEnd.Sub(ev.WindowStart) < p.MinimumObservation: + return treatment.OutcomeInsufficientDuration, true + } + return treatment.OutcomeUnspecified, false +} + +// samples are one arm's per-host changes against the baseline: log goodput +// ratio, retransmission ratio difference and log RTT p95 ratio. +type samples struct{ goodput, retrans, latency []float64 } + +func newSamples(n int) samples { + return samples{make([]float64, 0, n), make([]float64, 0, n), make([]float64, 0, n)} +} + +func (s *samples) add(h, b *treatment.HostEvidence) { + s.goodput = append(s.goodput, math.Log(h.GoodputBps/b.GoodputBps)) + s.retrans = append(s.retrans, h.RetransmissionRatio-b.RetransmissionRatio) + s.latency = append(s.latency, math.Log1p((max(h.RttP95Ms, .001)-b.RttP95Ms)/b.RttP95Ms)) +} + +// breaches reports whether the arm's mean retransmission or RTT change +// exceeds the policy's guardrail. +func (s *samples) breaches(p *treatment.Policy) bool { + return mean(s.retrans) > p.MaximumRetransmissionIncrease || mean(s.latency) > math.Log1p(p.MaximumRTTIncrease) +} + +// cohortSamples splits the cohort into the treated first target hosts and +// the controls. A host without usable data, or a treated host not yet +// exposed, stops the evaluation with that outcome. +func cohortSamples(e *treatment.Experiment, ev *treatment.Evidence, target int) (treated, control samples, o treatment.Outcome) { + p := &e.Policy + base, current := hostIndex(e.Baseline.Hosts), hostIndex(ev.Hosts) + treated, control = newSamples(target), newSamples(len(e.CohortOrder)-target) for i, id := range e.CohortOrder { h, b := current[id], base[id] if h == nil || b == nil || !h.CollectionHealthy || !b.CollectionHealthy || h.Sockets < p.MinimumSocketsPerHost || h.BytesAcked < p.MinimumBytesPerHost || h.GoodputBps <= 0 || b.GoodputBps <= 0 || b.RttP95Ms <= 0 { - return Evaluation{Outcome: treatment.OutcomeInsufficientData}, nil + return treated, control, treatment.OutcomeInsufficientData } - if i < target && h.ExposedSockets < p.MinimumSocketsPerHost { - return Evaluation{Outcome: treatment.OutcomeAwaitingExposure}, nil + if i >= target { + control.add(h, b) + continue } - x := math.Log(h.GoodputBps / b.GoodputBps) - if i < target { - treated = append(treated, x) - tr = append(tr, h.RetransmissionRatio-b.RetransmissionRatio) - tlat = append(tlat, math.Log1p((max(h.RttP95Ms, .001)-b.RttP95Ms)/b.RttP95Ms)) - } else { - control = append(control, x) - cr = append(cr, h.RetransmissionRatio-b.RetransmissionRatio) - clat = append(clat, math.Log1p((max(h.RttP95Ms, .001)-b.RttP95Ms)/b.RttP95Ms)) + if h.ExposedSockets < p.MinimumSocketsPerHost { + return treated, control, treatment.OutcomeAwaitingExposure } + treated.add(h, b) } - // Guardrails protect controls as well as treated traffic. A shared-link - // regression is harmful even if treated hosts outperform their peers. - if mean(tr) > p.MaximumRetransmissionIncrease || mean(cr) > p.MaximumRetransmissionIncrease || mean(tlat) > math.Log1p(p.MaximumRTTIncrease) || mean(clat) > math.Log1p(p.MaximumRTTIncrease) { - return Evaluation{Outcome: treatment.OutcomeHarmful}, nil - } + return treated, control, treatment.OutcomeUnspecified +} + +// compare turns the treated and control goodput changes into an outcome. +// cohort is the full cohort size, used to budget the number of looks. +func compare(p *treatment.Policy, treated, control []float64, cohort int) Evaluation { effect := mean(treated) - mean(control) - if len(control) == 0 { + harmful := effect < -math.Log1p(p.MinimumImprovement) + switch { + case len(control) == 0 && harmful, len(treated) < int(p.MinimumTreatedHosts) && harmful: + return Evaluation{Outcome: treatment.OutcomeHarmful} + case len(control) == 0: // Final 100% phase is a safety check; causal benefit was established // before the last control cohort received the treatment. - if effect < -math.Log1p(p.MinimumImprovement) { - return Evaluation{Outcome: treatment.OutcomeHarmful}, nil - } - return Evaluation{Outcome: treatment.OutcomeFinalSafety, Improvement: math.Expm1(effect)}, nil - } - if len(treated) < int(p.MinimumTreatedHosts) { - if effect < -math.Log1p(p.MinimumImprovement) { - return Evaluation{Outcome: treatment.OutcomeHarmful}, nil - } - return Evaluation{Outcome: treatment.OutcomeSafetyOnly, Improvement: math.Expm1(effect)}, nil - } - if len(control) < 2 { - return Evaluation{Outcome: treatment.OutcomeInsufficientControls}, nil + return Evaluation{Outcome: treatment.OutcomeFinalSafety, Improvement: math.Expm1(effect)} + case len(treated) < int(p.MinimumTreatedHosts): + return Evaluation{Outcome: treatment.OutcomeSafetyOnly, Improvement: math.Expm1(effect)} + case len(control) < 2: + return Evaluation{Outcome: treatment.OutcomeInsufficientControls} } a, b := variance(treated)/float64(len(treated)), variance(control)/float64(len(control)) se := math.Sqrt(a + b) lower := effect if se > 0 { df := (a + b) * (a + b) / (a*a/float64(len(treated)-1) + b*b/float64(len(control)-1)) - looks := (math.Ceil(math.Log2(float64(len(e.CohortOrder)))) + 2) * math.Ceil(p.MaximumPhase.Minutes()) + looks := (math.Ceil(math.Log2(float64(cohort))) + 2) * math.Ceil(p.MaximumPhase.Minutes()) lower -= studentQuantile(1-.05/looks, df) * se } out := Evaluation{Outcome: treatment.OutcomeInconclusive, Improvement: math.Expm1(effect), LowerBound: math.Expm1(lower)} if lower > math.Log1p(p.MinimumImprovement) { out.Outcome = treatment.OutcomeBeneficial } - return out, nil + return out } func hostIndex(hosts []treatment.HostEvidence) map[string]*treatment.HostEvidence { diff --git a/pkg/treatment/identity/identity_test.go b/pkg/treatment/identity/identity_test.go index b85c6229a..514d66eb4 100644 --- a/pkg/treatment/identity/identity_test.go +++ b/pkg/treatment/identity/identity_test.go @@ -101,8 +101,8 @@ func TestFromCertificate(t *testing.T) { }, { // url.Parse lowercases the scheme, and x509 parses SANs with it, - // so this is accepted. Recorded so the behaviour is deliberate. - description: "corner: upper-case scheme is normalised by the URL parser and accepted", + // so this is accepted. Recorded so the behavior is deliberate. + description: "corner: upper-case scheme is normalized by the URL parser and accepted", uris: []string{"SPIFFE://xtcp/agent/dc1/host-1"}, expected: Identity{Role: RoleAgent, Location: "dc1", ID: "host-1"}, }, diff --git a/pkg/treatment/identity/mtls_test.go b/pkg/treatment/identity/mtls_test.go index fe23587d5..dd9b5ad02 100644 --- a/pkg/treatment/identity/mtls_test.go +++ b/pkg/treatment/identity/mtls_test.go @@ -112,14 +112,23 @@ func TestMTLSHandshakesAndRoleBoundaries(t *testing.T) { pb.RegisterAgentControlServiceServer(server, authProbe{}) pb.RegisterOperatorServiceServer(server, authProbe{}) go server.Serve(listener) - t.Cleanup(func() { server.Stop(); listener.Close() }) + t.Cleanup(func() { + server.Stop() + if e := listener.Close(); e != nil { + t.Error(e) + } + }) connect := func(config *tls.Config) *grpc.ClientConn { t.Helper() conn, e := grpc.NewClient("passthrough:///controller.test", grpc.WithTransportCredentials(credentials.NewTLS(config)), grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { return listener.Dial() })) if e != nil { t.Fatal(e) } - t.Cleanup(func() { conn.Close() }) + t.Cleanup(func() { + if e := conn.Close(); e != nil { + t.Error(e) + } + }) return conn } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) diff --git a/pkg/treatment/reconcile/journal.go b/pkg/treatment/reconcile/journal.go index 15128b4a9..a0deed928 100644 --- a/pkg/treatment/reconcile/journal.go +++ b/pkg/treatment/reconcile/journal.go @@ -45,19 +45,27 @@ const pendingPrefix = ".pending-" // WithLock returns ErrJournalBusy wrapping ctx.Err() without having read or // written anything. `treatmentctl status` reads the snapshot without the lock // and never waits. -func (j Journal) WithLock(ctx context.Context, fn func(*State, func() error) error) error { - if err := os.MkdirAll(j.Dir, 0700); err != nil { +func (j Journal) WithLock(ctx context.Context, fn func(*State, func() error) error) (err error) { + if err = os.MkdirAll(j.Dir, 0700); err != nil { return err } fd, err := unix.Open(filepath.Join(j.Dir, ".lock"), unix.O_CREAT|unix.O_RDWR|unix.O_CLOEXEC|unix.O_NOFOLLOW, 0600) if err != nil { return err } - defer unix.Close(fd) + defer func() { + if cerr := unix.Close(fd); cerr != nil && err == nil { + err = cerr + } + }() if err = lock(ctx, fd); err != nil { return err } - defer unix.Flock(fd, unix.LOCK_UN) + defer func() { + if uerr := unix.Flock(fd, unix.LOCK_UN); uerr != nil && err == nil { + err = uerr + } + }() if err = j.sweepPending(); err != nil { return err } @@ -102,8 +110,8 @@ func (j Journal) WithLock(ctx context.Context, fn func(*State, func() error) err return errors.Join(result, err) } name := "assignment-" + r.Assignment.ExperimentID + ".textproto" - old, _ := os.ReadFile(filepath.Join(j.Dir, name)) - if bytes.Equal(old, b) { + old, rerr := os.ReadFile(filepath.Join(j.Dir, name)) + if rerr == nil && bytes.Equal(old, b) { continue } if err = atomicFile(j.Dir, name, b); err != nil { @@ -165,20 +173,28 @@ func readLimited(f *os.File) ([]byte, error) { return b, nil } -func atomicFile(dir, name string, b []byte) error { +// atomicFile writes b to a temporary file in dir, syncs it, renames it over +// name and syncs the directory. On failure the temporary file is removed; a +// crash before that point leaves one for sweepPending. +func atomicFile(dir, name string, b []byte) (err error) { f, err := os.CreateTemp(dir, pendingPrefix) if err != nil { return err } tmp := f.Name() - defer os.Remove(tmp) + defer func() { + if err == nil { + return + } + if rerr := os.Remove(tmp); rerr != nil && !errors.Is(rerr, os.ErrNotExist) { + err = errors.Join(err, rerr) + } + }() if _, err = f.Write(b); err != nil { - f.Close() - return err + return errors.Join(err, f.Close()) } if err = f.Sync(); err != nil { - f.Close() - return err + return errors.Join(err, f.Close()) } if err = f.Close(); err != nil { return err @@ -190,6 +206,8 @@ func atomicFile(dir, name string, b []byte) error { if err != nil { return err } - defer d.Close() - return d.Sync() + if err = d.Sync(); err != nil { + return errors.Join(err, d.Close()) + } + return d.Close() } diff --git a/pkg/treatment/reconcile/journal_test.go b/pkg/treatment/reconcile/journal_test.go index ec3a0a455..5a473a896 100644 --- a/pkg/treatment/reconcile/journal_test.go +++ b/pkg/treatment/reconcile/journal_test.go @@ -195,7 +195,8 @@ func TestJournalLockHolderDies(t *testing.T) { }); err != nil { t.Fatal(err) } - cmd := exec.Command(os.Args[0], "-test.run=^TestJournalLockHolderHelper$") + // /proc/self/exe re-executes this test binary. + cmd := exec.Command("/proc/self/exe", "-test.run=^TestJournalLockHolderHelper$") cmd.Env = append(os.Environ(), "XTCP_JOURNAL_HOLD_DIR="+dir) out, err := cmd.StdoutPipe() if err != nil { diff --git a/pkg/treatment/reconcile/journalpb_test.go b/pkg/treatment/reconcile/journalpb_test.go index e5b90c46a..044898f0f 100644 --- a/pkg/treatment/reconcile/journalpb_test.go +++ b/pkg/treatment/reconcile/journalpb_test.go @@ -159,6 +159,6 @@ func TestUnmarshalTextLimits(t *testing.T) { }) } if _, err := MarshalText(nil); err == nil { - t.Fatal("nil state marshalled") + t.Fatal("nil state marshaled") } } diff --git a/pkg/treatment/reconcile/reconcile.go b/pkg/treatment/reconcile/reconcile.go index ab85bfb5e..33124000a 100644 --- a/pkg/treatment/reconcile/reconcile.go +++ b/pkg/treatment/reconcile/reconcile.go @@ -1,3 +1,9 @@ +// Package reconcile owns the host-side half of the treatment contract: the +// intent journal and the reconciler that drives a Backend from it. It is +// shared by the in-daemon/standalone agent and by the independent watchdog; +// it never speaks gRPC, never imports the RPC proto and never imports the +// controller. The journal file format is its own proto package +// (xtcp_treatment_journal.v1) converted to and from the domain types. package reconcile import ( @@ -10,13 +16,6 @@ import ( "github.com/randomizedcoder/xtcp2/pkg/treatment" ) -// Package reconcile owns the host-side half of the treatment contract: the -// intent journal and the reconciler that drives a Backend from it. It is -// shared by the in-daemon/standalone agent and by the independent watchdog; -// it never speaks gRPC, never imports the RPC proto and never imports the -// controller. The journal file format is its own proto package -// (xtcp_treatment_journal.v1) converted to and from the domain types. - // Mode selects how much a reconciliation pass may do. type Mode uint8 @@ -88,7 +87,6 @@ func (r *Reconciler) Run(ctx context.Context, in *treatment.DesiredState, mode M if mode != ModeFull && mode != ModeExpiryOnly { return nil, fmt.Errorf("unknown reconcile mode %d", mode) } - expiryOnly := mode == ModeExpiryOnly var result *State err := r.Journal.WithLock(ctx, func(s *State, save func() error) error { defer func() { result = s.Clone() }() @@ -98,168 +96,224 @@ func (r *Reconciler) Run(ctx context.Context, in *treatment.DesiredState, mode M return errors.New("journal belongs to another agent") } if in != nil { - if expiryOnly { - return errors.New("watchdog cannot accept desired state") - } - if err := treatment.ValidateDesired(in); err != nil { - return err - } - if in.AgentID != r.AgentID { - return errors.New("desired state is for another agent") - } - if s.Desired != nil { - if in.Revision < s.Desired.Revision { - return errors.New("stale desired revision") - } - if in.Revision == s.Desired.Revision && !in.Equal(s.Desired) { - return errors.New("conflicting desired state at same revision") - } - } - for i := range in.Assignments { - a := &in.Assignments[i] - if v := s.Record(a.ExperimentID); v != nil && a.Generation < v.Assignment.Generation { - return errors.New("stale assignment generation") - } - } - s.Desired = in.Clone() - r.header(s, now) - if err := save(); err != nil { + if err := r.acceptDesired(s, in, mode, now, save); err != nil { return err } } if s.Desired == nil { return nil } - active := map[string]bool{} - var errs []error - // Disable first: an interrupted cleanup must not leave suspended routes - // effective. Removal remains per-route and ownership checked. - held := s.Held() - if held { - if err := r.Backend.Disable(ctx); err != nil { - errs = append(errs, err) - } + p := &pass{r: r, s: s, mode: mode, now: now, save: save, held: s.Held()} + return p.run(ctx) + }) + return result, err +} + +// acceptDesired validates in against the journal and persists it as the new +// desired state before anything touches the kernel. +func (r *Reconciler) acceptDesired(s *State, in *treatment.DesiredState, mode Mode, now time.Time, save func() error) error { + if mode == ModeExpiryOnly { + return errors.New("watchdog cannot accept desired state") + } + if err := treatment.ValidateDesired(in); err != nil { + return err + } + if in.AgentID != r.AgentID { + return errors.New("desired state is for another agent") + } + if s.Desired != nil { + if in.Revision < s.Desired.Revision { + return errors.New("stale desired revision") } - for i := range s.Desired.Assignments { - a := &s.Desired.Assignments[i] - if err := ctx.Err(); err != nil { - r.header(s, now) - return errors.Join(err, save()) - } - active[a.ExperimentID] = true - v := s.Record(a.ExperimentID) - if v == nil { - s.Records = append(s.Records, Record{ApplicationRecord: treatment.ApplicationRecord{Assignment: a.Clone(), ReceivedAt: now, Status: treatment.ApplyPending}}) - v = &s.Records[len(s.Records)-1] - } - if a.Generation < v.Assignment.Generation { - return errors.New("stale assignment generation") - } - if a.Generation > v.Assignment.Generation { - v.ExpiredLatched = false - v.ReceivedAt = now - } - v.Assignment = a.Clone() - v.DesiredRevision = s.Desired.Revision - if a.Mode == treatment.ModeTrial && !a.LeaseExpiresAt.After(now) { - v.ExpiredLatched = true - } - remove := held || a.Mode == treatment.ModeSuspended || a.Mode == treatment.ModeRetired || v.ExpiredLatched - if expiryOnly && !remove { - continue - } - var err error - if v.ExpiredLatched && !held && a.Mode == treatment.ModeTrial && a.Predecessor != nil { - err = r.install(ctx, v, a.Predecessor, save, now) - if err == nil { - v.Status = treatment.ApplyExpired - } - } else if remove { - v.AttemptedAt = now - if len(v.InstalledRoute) > 0 { - if err := save(); err != nil { - return err - } - } - if len(v.InstalledRoute) > 0 && v.InstalledBootID == r.BootID { - err = r.Backend.Remove(ctx, v.InstalledRoute) - } - if err == nil { - v.InstalledRoute = nil - v.RemovedAt = now - v.Status = treatment.ApplyRemoved - if v.ExpiredLatched { - v.Status = treatment.ApplyExpired - } - } - } else { - err = r.install(ctx, v, &a.Treatment, save, now) - } - if err != nil { - v.Status = treatment.ApplyFailed - v.Error = err.Error() - errs = append(errs, fmt.Errorf("%s: %w", a.ExperimentID, err)) - } else { - v.Error = "" + if in.Revision == s.Desired.Revision && !in.Equal(s.Desired) { + return errors.New("conflicting desired state at same revision") + } + } + for i := range in.Assignments { + a := &in.Assignments[i] + if v := s.Record(a.ExperimentID); v != nil && a.Generation < v.Assignment.Generation { + return errors.New("stale assignment generation") + } + } + s.Desired = in.Clone() + r.header(s, now) + return save() +} + +// pass is one reconciliation of the journal's desired state against the +// kernel. Failures of individual assignments are recorded on their records +// and collected in errs; only journal and ordering faults abort the pass. +type pass struct { + r *Reconciler + s *State + mode Mode + now time.Time + save func() error + held bool + errs []error +} + +func (p *pass) run(ctx context.Context) error { + s := p.s + // Disable first: an interrupted cleanup must not leave suspended routes + // effective. Removal remains per-route and ownership checked. + if p.held { + if err := p.r.Backend.Disable(ctx); err != nil { + p.errs = append(p.errs, err) + } + } + active := map[string]bool{} + for i := range s.Desired.Assignments { + a := &s.Desired.Assignments[i] + if err := ctx.Err(); err != nil { + p.r.header(s, p.now) + return errors.Join(err, p.save()) + } + active[a.ExperimentID] = true + if err := p.assignment(ctx, a); err != nil { + return err + } + } + p.tombstones(ctx, active) + p.settle(ctx) + p.r.header(s, p.now) + if err := p.save(); err != nil { + return err + } + return errors.Join(p.errs...) +} + +// assignment brings one assignment's record and route in line with it. +func (p *pass) assignment(ctx context.Context, a *treatment.Assignment) error { + s, now := p.s, p.now + v := s.Record(a.ExperimentID) + if v == nil { + s.Records = append(s.Records, Record{ApplicationRecord: treatment.ApplicationRecord{Assignment: a.Clone(), ReceivedAt: now, Status: treatment.ApplyPending}}) + v = &s.Records[len(s.Records)-1] + } + if a.Generation < v.Assignment.Generation { + return errors.New("stale assignment generation") + } + if a.Generation > v.Assignment.Generation { + v.ExpiredLatched = false + v.ReceivedAt = now + } + v.Assignment = a.Clone() + v.DesiredRevision = s.Desired.Revision + if a.Mode == treatment.ModeTrial && !a.LeaseExpiresAt.After(now) { + v.ExpiredLatched = true + } + remove := p.held || a.Mode == treatment.ModeSuspended || a.Mode == treatment.ModeRetired || v.ExpiredLatched + if p.mode == ModeExpiryOnly && !remove { + return nil + } + var err error + switch { + case v.ExpiredLatched && !p.held && a.Mode == treatment.ModeTrial && a.Predecessor != nil: + if err = p.r.install(ctx, v, a.Predecessor, p.save, now); err == nil { + v.Status = treatment.ApplyExpired + } + case remove: + v.AttemptedAt = now + if len(v.InstalledRoute) > 0 { + if serr := p.save(); serr != nil { + return serr } } - // Entries absent from the committed snapshot are tombstones, never - // candidates for startup application. - for i := range s.Records { - v := &s.Records[i] - if active[v.Assignment.ExperimentID] || len(v.InstalledRoute) == 0 { + err = p.withdraw(ctx, v) + default: + err = p.r.install(ctx, v, &a.Treatment, p.save, now) + } + if err != nil { + v.Status = treatment.ApplyFailed + v.Error = err.Error() + p.errs = append(p.errs, fmt.Errorf("%s: %w", a.ExperimentID, err)) + return nil + } + v.Error = "" + return nil +} + +// withdraw removes the record's route when this boot installed it and marks +// the record removed, or expired when its lease ran out. +func (p *pass) withdraw(ctx context.Context, v *Record) error { + if len(v.InstalledRoute) > 0 && v.InstalledBootID == p.r.BootID { + if err := p.r.Backend.Remove(ctx, v.InstalledRoute); err != nil { + return err + } + } + v.InstalledRoute = nil + v.RemovedAt = p.now + v.Status = treatment.ApplyRemoved + if v.ExpiredLatched { + v.Status = treatment.ApplyExpired + } + return nil +} + +// tombstones withdraws routes whose assignments are absent from the desired +// state. Such entries are never candidates for startup application. +func (p *pass) tombstones(ctx context.Context, active map[string]bool) { + for i := range p.s.Records { + v := &p.s.Records[i] + if active[v.Assignment.ExperimentID] || len(v.InstalledRoute) == 0 { + continue + } + if v.InstalledBootID == p.r.BootID { + if err := p.r.Backend.Remove(ctx, v.InstalledRoute); err != nil { + v.Error = err.Error() + v.Status = treatment.ApplyFailed + p.errs = append(p.errs, err) continue } - if v.InstalledBootID == r.BootID { - if err := r.Backend.Remove(ctx, v.InstalledRoute); err != nil { - v.Error = err.Error() - v.Status = treatment.ApplyFailed - errs = append(errs, err) - continue - } - } - v.InstalledRoute = nil - v.Status = treatment.ApplyRemoved - v.RemovedAt = now } - if len(errs) > 0 { - // A failed withdrawal must not leave an expired/unknown override - // selected. Isolate the whole owned table until a clean pass. - if err := r.Backend.Disable(ctx); err != nil { - errs = append(errs, err) - } - for i := range s.Records { - if v := &s.Records[i]; v.Status == treatment.ApplyVerified { - v.Status = treatment.ApplyFailed - v.Error = "owned route lookup disabled after reconciliation failure" - } - } - } else if !held && !expiryOnly { - hasRoutes := false - for i := range s.Records { - hasRoutes = hasRoutes || len(s.Records[i].InstalledRoute) > 0 - } - if hasRoutes { - if err := r.Backend.Enable(ctx); err != nil { - errs = append(errs, err, r.Backend.Disable(ctx)) - for i := range s.Records { - if v := &s.Records[i]; v.Status == treatment.ApplyVerified { - v.Status = treatment.ApplyFailed - v.Error = err.Error() - } - } - } - } else if err := r.Backend.Disable(ctx); err != nil { - errs = append(errs, err) - } + v.InstalledRoute = nil + v.Status = treatment.ApplyRemoved + v.RemovedAt = p.now + } +} + +// settle leaves the owned lookup rules consistent with the pass: disabled +// after any failure, enabled only when a clean full pass left routes +// installed, and disabled when nothing is installed. +func (p *pass) settle(ctx context.Context) { + if len(p.errs) > 0 { + // A failed withdrawal must not leave an expired/unknown override + // selected. Isolate the whole owned table until a clean pass. + if err := p.r.Backend.Disable(ctx); err != nil { + p.errs = append(p.errs, err) } - r.header(s, now) - if err := save(); err != nil { - return err + p.failVerified("owned route lookup disabled after reconciliation failure") + return + } + if p.held || p.mode == ModeExpiryOnly { + return + } + hasRoutes := false + for i := range p.s.Records { + hasRoutes = hasRoutes || len(p.s.Records[i].InstalledRoute) > 0 + } + if !hasRoutes { + if err := p.r.Backend.Disable(ctx); err != nil { + p.errs = append(p.errs, err) } - return errors.Join(errs...) - }) - return result, err + return + } + if err := p.r.Backend.Enable(ctx); err != nil { + p.errs = append(p.errs, err, p.r.Backend.Disable(ctx)) + p.failVerified(err.Error()) + } +} + +// failVerified demotes every verified record after the owned rules were +// disabled, so the report never claims a route the kernel cannot select. +func (p *pass) failVerified(reason string) { + for i := range p.s.Records { + if v := &p.s.Records[i]; v.Status == treatment.ApplyVerified { + v.Status = treatment.ApplyFailed + v.Error = reason + } + } } func (r *Reconciler) install(ctx context.Context, v *Record, t *treatment.Treatment, save func() error, now time.Time) error { diff --git a/pkg/treatment/validate.go b/pkg/treatment/validate.go index 7e3f9f2c9..3179208f8 100644 --- a/pkg/treatment/validate.go +++ b/pkg/treatment/validate.go @@ -133,7 +133,7 @@ func ValidateEvidence(e *Evidence) error { return errors.New("evidence provenance required") } for _, c := range e.ArtifactSHA256 { - if !(c >= '0' && c <= '9' || c >= 'a' && c <= 'f') { + if (c < '0' || c > '9') && (c < 'a' || c > 'f') { return errors.New("invalid evidence sha256") } } diff --git a/pkg/treatment/validate_test.go b/pkg/treatment/validate_test.go index edee03ff7..6b1ea70ee 100644 --- a/pkg/treatment/validate_test.go +++ b/pkg/treatment/validate_test.go @@ -63,13 +63,14 @@ func TestValidIDBoundaries(t *testing.T) { } func TestTreatmentValidation(t *testing.T) { - cases := []validationCase[*treatment.Treatment]{ + fixed := []validationCase[*treatment.Treatment]{ {"valid", "the supported nearby profile is accepted", func(**treatment.Treatment) {}, ""}, {"nil", "missing treatments are rejected", func(p **treatment.Treatment) { *p = nil }, "requires"}, {"revision", "zero revision is invalid", func(p **treatment.Treatment) { (*p).Revision = 0 }, "requires"}, {"empty-settings", "a treatment with no parameter set is rejected", func(p **treatment.Treatment) { (*p).Settings = treatment.TCPSettings{} }, "empty"}, {"zero-destination", "the zero prefix is not a destination", func(p **treatment.Treatment) { (*p).Destination = netip.Prefix{} }, "canonical unicast"}, } + cases := append(make([]validationCase[*treatment.Treatment], 0, len(fixed)+10), fixed...) for _, tc := range []struct { destination string valid bool @@ -92,7 +93,7 @@ func TestTCPSettingsValidation(t *testing.T) { s treatment.TCPSettings verified bool } - cases := []validationCase[input]{ + fixed := []validationCase[input]{ {"cubic", "ordinary CUBIC is accepted", func(*input) {}, ""}, {"empty", "at least one explicit metric is required", func(p *input) { p.s = treatment.TCPSettings{} }, "empty"}, {"explicit-false", "false quickack is a real setting", func(p *input) { p.s = treatment.TCPSettings{QuickAck: ptr(false)} }, ""}, @@ -113,6 +114,7 @@ func TestTCPSettingsValidation(t *testing.T) { p.verified = true }, ""}, } + cases := append(make([]validationCase[input], 0, len(fixed)+3*4), fixed...) for _, metric := range []string{"rto", "cwnd", "rwnd"} { maximum := uint32(1024) errorText := "initial window" @@ -141,13 +143,14 @@ func TestTCPSettingsValidation(t *testing.T) { } func TestPolicyValidation(t *testing.T) { - cases := []validationCase[treatment.Policy]{ + fixed := []validationCase[treatment.Policy]{ {"default", "production defaults are valid", func(*treatment.Policy) {}, ""}, {"zero", "the zero policy is rejected", func(p *treatment.Policy) { *p = treatment.Policy{} }, "duration"}, {"observation-after-phase", "observation cannot exceed phase", func(p *treatment.Policy) { p.MaximumPhase = time.Minute }, "sample bounds"}, {"zero-sockets", "sample count must be positive", func(p *treatment.Policy) { p.MinimumSocketsPerHost = 0 }, "sample bounds"}, {"zero-bytes", "byte count must be positive", func(p *treatment.Policy) { p.MinimumBytesPerHost = 0 }, "sample bounds"}, } + cases := append(make([]validationCase[treatment.Policy], 0, len(fixed)+4+3*4+3*7), fixed...) for _, n := range []uint32{1, 2, treatment.MaxAgents, treatment.MaxAgents + 1} { want := "" if n < 2 || n > treatment.MaxAgents { @@ -199,7 +202,7 @@ func TestPolicyValidation(t *testing.T) { func TestEvidenceValidation(t *testing.T) { now := time.Unix(1700000000, 0) - cases := []validationCase[*treatment.Evidence]{ + fixed := []validationCase[*treatment.Evidence]{ {"valid", "complete finite evidence is accepted", func(**treatment.Evidence) {}, ""}, {"nil", "evidence is required", func(p **treatment.Evidence) { *p = nil }, "interval"}, {"empty-window", "a zero length window is invalid", func(p **treatment.Evidence) { (*p).WindowStart = (*p).WindowEnd }, "interval"}, @@ -215,6 +218,7 @@ func TestEvidenceValidation(t *testing.T) { {"duplicate-host", "hosts are independent units", func(p **treatment.Evidence) { (*p).Hosts[1] = (*p).Hosts[0] }, "host"}, {"overexposure", "exposed sockets cannot exceed total sockets", func(p **treatment.Evidence) { (*p).Hosts[0].ExposedSockets = 101 }, "host"}, } + cases := append(make([]validationCase[*treatment.Evidence], 0, len(fixed)+3*5+2), fixed...) for _, field := range []string{"goodput", "loss", "rtt"} { for _, v := range []float64{0, -1, math.NaN(), math.Inf(1), math.Inf(-1)} { want := "" @@ -251,7 +255,7 @@ func TestEvidenceValidation(t *testing.T) { func TestDesiredValidation(t *testing.T) { now := time.Unix(1700000000, 0) - cases := []validationCase[*treatment.DesiredState]{ + fixed := []validationCase[*treatment.DesiredState]{ {"valid", "leased trials are accepted", func(**treatment.DesiredState) {}, ""}, {"nil", "desired header is required", func(p **treatment.DesiredState) { *p = nil }, "header"}, {"empty", "empty assignment set withdraws treatments", func(p **treatment.DesiredState) { (*p).Assignments = nil }, ""}, @@ -293,6 +297,7 @@ func TestDesiredValidation(t *testing.T) { a.Predecessor = &pred }, "differs"}, } + cases := append(make([]validationCase[*treatment.DesiredState], 0, len(fixed)+2), fixed...) for _, n := range []int{treatment.MaxAssignments, treatment.MaxAssignments + 1} { want := "" if n > treatment.MaxAssignments { @@ -335,7 +340,7 @@ func TestReportValidation(t *testing.T) { d := fixtures.Desired(now) return &treatment.AgentReport{AgentID: "host-1", Location: "dc1", Capabilities: treatment.Capabilities{BootID: "boot-1", KernelRelease: "6.12", CongestionAlgorithms: []string{"cubic", "bbr"}, SimpleHostRouting: true, ActuatorEnabled: true}, DesiredRevision: 1, Records: []treatment.ApplicationRecord{{Assignment: d.Assignments[0], DesiredRevision: 1, Status: treatment.ApplyVerified, AppliedAt: now}}} } - cases := []validationCase[*treatment.AgentReport]{ + fixed := []validationCase[*treatment.AgentReport]{ {"valid", "a verified report is accepted", func(**treatment.AgentReport) {}, ""}, {"nil", "a report is required", func(p **treatment.AgentReport) { *p = nil }, "agent report"}, {"no-records", "a fresh host reports nothing", func(p **treatment.AgentReport) { (*p).Records = nil }, ""}, @@ -350,6 +355,7 @@ func TestReportValidation(t *testing.T) { {"unspecified-status", "an unacted assignment may be reported", func(p **treatment.AgentReport) { (*p).Records[0].Status = treatment.ApplyUnspecified }, ""}, {"bad-treatment", "records carry valid treatments", func(p **treatment.AgentReport) { (*p).Records[0].Assignment.Treatment.Revision = 0 }, "requires"}, } + cases := append(make([]validationCase[*treatment.AgentReport], 0, len(fixed)+2), fixed...) for _, n := range []int{64, 65} { want := "" if n > 64 { diff --git a/pkg/treatment/wire/rules_test.go b/pkg/treatment/wire/rules_test.go index a7cf69359..8fc5eb81e 100644 --- a/pkg/treatment/wire/rules_test.go +++ b/pkg/treatment/wire/rules_test.go @@ -75,7 +75,7 @@ func TestProtoRulesMirrorValidators(t *testing.T) { {"negative: evidence window inverted", experiment(func(e *pb.Experiment) { e.Baseline.WindowStart, e.Baseline.WindowEnd = e.Baseline.WindowEnd, e.Baseline.WindowStart }), "evidence.interval"}, - {"negative: evidence analysed before its window closed", experiment(func(e *pb.Experiment) { + {"negative: evidence analyzed before its window closed", experiment(func(e *pb.Experiment) { e.Baseline.AnalyzedAt = timestamppb.New(e.Baseline.WindowEnd.AsTime().Add(-time.Second)) }), "evidence.interval"}, {"negative: artifact digest of the wrong length", experiment(func(e *pb.Experiment) { e.Baseline.ArtifactSha256 = "abc" }), "string.pattern"}, diff --git a/tools/tcp-controller-integration/main.go b/tools/tcp-controller-integration/main.go index 9edb2a68f..65e1369d4 100644 --- a/tools/tcp-controller-integration/main.go +++ b/tools/tcp-controller-integration/main.go @@ -87,8 +87,10 @@ func require(ok bool, format string, args ...any) { panic(fmt.Sprintf(format, args...)) } } -func command(name string, args ...string) string { - ctx, cancel := context.WithTimeout(context.Background(), 25*time.Second) + +// command runs a fixture command bounded by ctx and 25 seconds. +func command(ctx context.Context, name string, args ...string) string { + ctx, cancel := context.WithTimeout(ctx, 25*time.Second) defer cancel() out, err := exec.CommandContext(ctx, name, args...).CombinedOutput() if err != nil { @@ -101,18 +103,33 @@ func write(path string, data []byte) { check(os.MkdirAll(filepath.Dir(path), 0700)) check(os.WriteFile(path, data, 0600)) } -func systemctl(args ...string) string { return command("systemctl", args...) } -func agentReady() { + +// host runs a command on the controller side, which has no caller context: +// each fixture step is bounded by command's own timeout. +func host(name string, args ...string) string { + return command(context.Background(), name, args...) +} +func systemctl(ctx context.Context, args ...string) string { return command(ctx, "systemctl", args...) } +func agentReady(ctx context.Context) { probe := &http.Client{Timeout: time.Second} eventually("local daemon ready", 15*time.Second, func() bool { - response := must(probe.Get("http://127.0.0.1:9088/readyz")) - defer response.Body.Close() + request := must(http.NewRequestWithContext(ctx, http.MethodGet, "http://127.0.0.1:9088/readyz", http.NoBody)) + response := must(probe.Do(request)) + closeBody(response) return response.StatusCode == http.StatusOK }) } -func routeTable() string { + +// closeBody discards a response body; a failed close only loses the +// connection for reuse. +func closeBody(response *http.Response) { + if err := response.Body.Close(); err != nil { + log.Printf("response body close: %v", err) + } +} +func routeTable(ctx context.Context) string { // Before the first assignment the private table does not exist. - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() out, err := exec.CommandContext(ctx, "ip", "-j", "route", "show", "table", "10002").CombinedOutput() if err != nil && strings.Contains(string(out), "FIB table does not exist") { @@ -121,16 +138,20 @@ func routeTable() string { check(err) return string(out) } -func localSnapshot() snapshot { - s := snapshot{Routes: routeTable(), Main: command("ip", "-j", "route", "show", "table", "main"), DefaultCC: strings.TrimSpace(command("sysctl", "-n", "net.ipv4.tcp_congestion_control"))} +func localSnapshot(ctx context.Context) snapshot { + s := snapshot{Routes: routeTable(ctx), Main: command(ctx, "ip", "-j", "route", "show", "table", "main"), DefaultCC: strings.TrimSpace(command(ctx, "sysctl", "-n", "net.ipv4.tcp_congestion_control"))} b, err := os.ReadFile(statePath) if err == nil { s.State = string(b) } else if !os.IsNotExist(err) { panic(err) } - b, _ = os.ReadFile("/var/lib/xtcp2/routes/assignment-netem-canary.textproto") - s.Projection = string(b) + b, err = os.ReadFile("/var/lib/xtcp2/routes/assignment-netem-canary.textproto") + if err == nil { + s.Projection = string(b) + } else if !os.IsNotExist(err) { + panic(err) + } return s } func serveAgent(address string) { @@ -141,6 +162,7 @@ func serveAgent(address string) { http.Error(w, fmt.Sprint(p), 500) } }() + ctx := r.Context() var a action check(json.NewDecoder(http.MaxBytesReader(w, r.Body, 64<<10)).Decode(&a)) var result any = "ok" @@ -152,53 +174,55 @@ func serveAgent(address string) { write(pkiDir+"/agent.crt", a.Certificate) write(pkiDir+"/agent.key", a.Key) write("/var/lib/controller-test/load.env", []byte("TCP_TEST_DESTINATION=198.18.0.1\n")) - command("tc", "qdisc", "replace", "dev", "eth0", "root", "handle", "1:", "prio", "bands", "3", "priomap", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0") - command("tc", "qdisc", "add", "dev", "eth0", "parent", "1:2", "handle", "20:", "netem", "delay", "80ms") - command("tc", "qdisc", "add", "dev", "eth0", "parent", "1:3", "handle", "30:", "netem", "delay", "5ms") - command("tc", "filter", "add", "dev", "eth0", "parent", "1:", "protocol", "ip", "prio", "1", "u32", "match", "ip", "dst", "198.18.0.2/32", "flowid", "1:2") - command("tc", "filter", "add", "dev", "eth0", "parent", "1:", "protocol", "ip", "prio", "2", "u32", "match", "ip", "dst", "198.18.0.0/24", "flowid", "1:3") - systemctl(append([]string{"start", "load-near", "load-far"}, treatmentUnits()...)...) + command(ctx, "tc", "qdisc", "replace", "dev", "eth0", "root", "handle", "1:", "prio", "bands", "3", "priomap", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0") + command(ctx, "tc", "qdisc", "add", "dev", "eth0", "parent", "1:2", "handle", "20:", "netem", "delay", "80ms") + command(ctx, "tc", "qdisc", "add", "dev", "eth0", "parent", "1:3", "handle", "30:", "netem", "delay", "5ms") + command(ctx, "tc", "filter", "add", "dev", "eth0", "parent", "1:", "protocol", "ip", "prio", "1", "u32", "match", "ip", "dst", "198.18.0.2/32", "flowid", "1:2") + command(ctx, "tc", "filter", "add", "dev", "eth0", "parent", "1:", "protocol", "ip", "prio", "2", "u32", "match", "ip", "dst", "198.18.0.0/24", "flowid", "1:3") + systemctl(ctx, append([]string{"start", "load-near", "load-far"}, treatmentUnits()...)...) case "status": - result = localSnapshot() + result = localSnapshot(ctx) case "restart-agent": - systemctl("restart", agentProcess()) - agentReady() + systemctl(ctx, "restart", agentProcess()) + agentReady(ctx) case "stop-agent": - systemctl("stop", agentProcess()) - systemctl("is-active", "xtcp2-treatment-watchdog") + systemctl(ctx, "stop", agentProcess()) + systemctl(ctx, "is-active", "xtcp2-treatment-watchdog") case "start-agent": - systemctl("start", agentProcess()) - agentReady() + systemctl(ctx, "start", agentProcess()) + agentReady(ctx) case "restart-load": - systemctl("restart", "load-near") + systemctl(ctx, "restart", "load-near") case "destination": require(validSoakDestination(a.Destination), "invalid fixture destination") write("/var/lib/controller-test/load.env", []byte("TCP_TEST_DESTINATION="+a.Destination+"\n")) - systemctl("restart", "load-near") + systemctl(ctx, "restart", "load-near") case "hup": - agentReady() - systemctl("kill", "-s", "HUP", agentProcess()) + agentReady(ctx) + systemctl(ctx, "kill", "-s", "HUP", agentProcess()) case "suspend", "resume": - command("xtcp2-treatmentctl", "-mode="+a.Name, "-reason=integration-local-"+a.Name) + command(ctx, "xtcp2-treatmentctl", "-mode="+a.Name, "-reason=integration-local-"+a.Name) case "delay": require(a.Seconds == 5 || a.Seconds == 180, "invalid fixture delay") - command("tc", "qdisc", "change", "dev", "eth0", "parent", "1:3", "handle", "30:", "netem", "delay", fmt.Sprintf("%dms", a.Seconds)) + command(ctx, "tc", "qdisc", "change", "dev", "eth0", "parent", "1:3", "handle", "30:", "netem", "delay", fmt.Sprintf("%dms", a.Seconds)) case "expire": - command("date", "-s", fmt.Sprintf("@%d", time.Now().Unix()+3700)) + command(ctx, "date", "-s", fmt.Sprintf("@%d", time.Now().Unix()+3700)) case "restore-clock": require(a.Seconds > 0, "invalid clock") - command("date", "-s", fmt.Sprintf("@%d", a.Seconds)) + command(ctx, "date", "-s", fmt.Sprintf("@%d", a.Seconds)) case "healthy": - result = systemctl(append([]string{"is-active", "load-near", "load-far"}, treatmentUnits()...)...) + result = systemctl(ctx, append([]string{"is-active", "load-near", "load-far"}, treatmentUnits()...)...) case "evidence": - s := localSnapshot() - args := []string{"-b", "--no-pager", "-n", "500", "-u", "controller-test-agent"} - for _, u := range treatmentUnits() { + s := localSnapshot(ctx) + units := treatmentUnits() + args := make([]string, 0, 6+2*len(units)) + args = append(args, "-b", "--no-pager", "-n", "500", "-u", "controller-test-agent") + for _, u := range units { args = append(args, "-u", u) } - s.Journal = command("journalctl", args...) - s.Rules = command("ip", "-j", "rule") - s.Qdisc = command("tc", "-s", "qdisc", "show") + s.Journal = command(ctx, "journalctl", args...) + s.Rules = command(ctx, "ip", "-j", "rule") + s.Qdisc = command(ctx, "tc", "-s", "qdisc", "show") result = s default: panic("unknown fixture action") @@ -218,8 +242,10 @@ func remote(host string, a action, result any) { } require(index != 0, "unknown agent %q", host) body := must(json.Marshal(a)) - response := must(client.Post(fmt.Sprintf("http://192.168.1.%d:9009/action", index), "application/json", bytes.NewReader(body))) - defer response.Body.Close() + request := must(http.NewRequestWithContext(context.Background(), http.MethodPost, fmt.Sprintf("http://192.168.1.%d:9009/action", index), bytes.NewReader(body))) + request.Header.Set("Content-Type", "application/json") + response := must(client.Do(request)) + defer closeBody(response) data := must(io.ReadAll(io.LimitReader(response.Body, 2<<20))) require(response.StatusCode == 200, "%s %s: %s", host, a.Name, data) if result != nil { @@ -258,7 +284,7 @@ func eventually(label string, timeout time.Duration, fn func() bool) { } panic(fmt.Sprintf("%s: timed out after %s (last error: %v)", label, timeout, last)) } -func sql(query string) string { return command("clickhouse-client", "--query", query) } +func sql(query string) string { return host("clickhouse-client", "--query", query) } func count(query string) int { return must(strconv.Atoi(strings.TrimSpace(sql(query)))) } func pass(stage string) { fmt.Println("TCP_CONTROLLER_" + stage + "_PASS") } func rpcContext() (context.Context, context.CancelFunc) { @@ -323,8 +349,8 @@ func evidence() { return must(json.MarshalIndent(s, "", " ")) }) } - attempt("controller.log", func() []byte { return []byte(command("journalctl", "-b", "--no-pager", "-n", "1500")) }) - attempt("controller.sql", func() []byte { return []byte(command("pg_dump", "-h", "/run/postgresql", "-U", "root", "xtcp")) }) + attempt("controller.log", func() []byte { return []byte(host("journalctl", "-b", "--no-pager", "-n", "1500")) }) + attempt("controller.sql", func() []byte { return []byte(host("pg_dump", "-h", "/run/postgresql", "-U", "root", "xtcp")) }) attempt("telemetry.jsonl", func() []byte { return []byte(sql("SELECT * FROM xtcp.xtcp_flat_records_v2 ORDER BY timestamp_ns DESC LIMIT 2000 FORMAT JSONEachRow")) }) @@ -333,20 +359,26 @@ func runController(soak bool) { defer evidence() eventually("ClickHouse ready", 3*time.Minute, func() bool { return strings.TrimSpace(sql("SELECT 1")) == "1" }) sql("CREATE DATABASE xtcp") - cmd := exec.Command("clickhouse-client", "--multiquery") + schemaCtx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + cmd := exec.CommandContext(schemaCtx, "clickhouse-client", "--multiquery") cmd.Stdin = bytes.NewReader(read("/etc/xtcp-test/schema.sql")) out, err := cmd.CombinedOutput() require(err == nil, "schema: %v: %s", err, out) sql("CREATE TABLE xtcp.ingest ENGINE=Null AS SELECT toInt64(0) AS timestamp_ns, * EXCEPT(timestamp_ns) FROM xtcp.xtcp_flat_records_v2 WHERE 0") sql("CREATE MATERIALIZED VIEW xtcp.ingest_mv TO xtcp.xtcp_flat_records_v2 AS SELECT fromUnixTimestamp64Nano(timestamp_ns) AS timestamp_ns, * EXCEPT(timestamp_ns) FROM xtcp.ingest") - systemctl("start", "xtcp-controller") + host("systemctl", "start", "xtcp-controller") for _, h := range hosts { eventually(h+" fixture ready", 3*time.Minute, func() bool { act(h, "ready"); return true }) remote(h, action{Name: "setup", CA: read(pkiDir + "/ca.crt"), Certificate: read(pkiDir + "/" + h + ".crt"), Key: read(pkiDir + "/" + h + ".key")}, nil) } tls := must(identity.TLSConfig(pkiDir+"/operator.crt", pkiDir+"/operator.key", pkiDir+"/ca.crt", "controller", false)) conn := must(grpc.NewClient("127.0.0.1:9443", grpc.WithTransportCredentials(credentials.NewTLS(tls)))) - defer conn.Close() + defer func() { + if cerr := conn.Close(); cerr != nil { + log.Printf("operator connection close: %v", cerr) + } + }() op := pb.NewOperatorServiceClient(conn) if soak { runSoak(op) @@ -384,7 +416,7 @@ func runController(soak bool) { act(canary, "restart-agent") eventually("journal replay", time.Minute, func() bool { s := status(canary); return hasRoute(canary) && s.State != "" && s.Projection != "" }) assertNearbyRoute(canary, "198.18.0.1") - systemctl("restart", "xtcp-controller") + host("systemctl", "restart", "xtcp-controller") eventually("controller state survived restart", time.Minute, func() bool { return inspect(op, e.Id).Revision == e.Revision }) pass("PERSIST") act(canary, "suspend") @@ -434,7 +466,7 @@ func runController(soak bool) { e = propose(op, "watchdog-outage") victim := e.CohortOrder[0] eventually("outage trial installed", time.Minute, func() bool { return hasRoute(victim) }) - systemctl("stop", "xtcp-controller") + host("systemctl", "stop", "xtcp-controller") act(victim, "stop-agent") act(victim, "expire") eventually("independent watchdog expires unchanged one-hour lease", 90*time.Second, func() bool { @@ -444,7 +476,7 @@ func runController(soak bool) { time.Sleep(5 * time.Second) require(!hasRoute(victim), "restart resurrected expired trial") remote(victim, action{Name: "restore-clock", Seconds: time.Now().Unix()}, nil) - systemctl("start", "xtcp-controller") + host("systemctl", "start", "xtcp-controller") act(victim, "hup") time.Sleep(5 * time.Second) require(!hasRoute(victim), "reconnect resurrected expired generation") From 4bcb653330a5690d99f22e4947f75931ec6b889f Mon Sep 17 00:00:00 2001 From: "randomizedcoder dave.seddon.ca@gmail.com" Date: Sun, 25 Oct 2026 22:07:53 -0700 Subject: [PATCH 19/27] docs(treatment): record the lint commit in the rework status Co-Authored-By: Claude Opus 5.5 --- docs/status-tcp-controller.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/status-tcp-controller.md b/docs/status-tcp-controller.md index 104551779..e5d19a475 100644 --- a/docs/status-tcp-controller.md +++ b/docs/status-tcp-controller.md @@ -20,7 +20,8 @@ review being addressed. Items are listed in execution order. | 9 | identity/location startup check | done | 9372e0bf | | 10 | blocking journal lock | done | 3478d9b3 | | 11 | scale: notification watermark, heartbeat split, leader lease | done | 91ccde4a | -| 12 | docs: threat model, naming, SIGHUP, philosophy sentence | done | this commit | +| 12 | docs: threat model, naming, SIGHUP, philosophy sentence | done | b6df3de9 | +| L | lint: comprehensive tier clean on the new code; `lint-baseline` gate passes | done | c3981ff1 | Dual-mode agent (library + `cmd/xtcp2-treatment-agent` + `treatment` build tag in `cmd/xtcp2`, with an `attribution` journal reader for the untagged daemon) @@ -227,6 +228,17 @@ process. xtcp2 observes by default and that the treatment controller is the one opt-in, operator-reviewed component that changes host state. +## Lint notes + +- The comprehensive tier went from 107 findings to zero on the treatment + packages, the four binaries, `internal/goip/actuate` and the integration + tool, with no `//nolint`, no exclusions and no threshold changes. Long + functions (`reconcile.Run`, `Maintain`, `Control`, `Evaluate`, `Plan`, the + controller `main`) were split into named steps with unchanged behavior; + close, rollback and unlock errors are now joined into the returned error. +- The ClickHouse SQL test finds `clickhouse` on `PATH` (the unit check adds + it to `nativeBuildInputs`) instead of `XTCP_CLICKHOUSE_TEST_BINARY`. + ## TODO - Evidence binding (threat model): the warehouse query filters by location From 75093584e1dc652f1eb554aa8cb2d481967c0ef0 Mon Sep 17 00:00:00 2001 From: "randomizedcoder dave.seddon.ca@gmail.com" Date: Mon, 26 Oct 2026 07:27:03 -0700 Subject: [PATCH 20/27] perf(treatment): load Experiments.List in a fixed number of statements List and Get share one loader that reads the experiment rows, cohorts and phases with id = ANY($1), then every referenced evidence document and its host rows in two more statements. A 1000-experiment inspect went from about 1 + N x (3 + 2E) round trips to at most six. pgtest.OpenCounting traces statements through the pgx driver so the bound is pinned by TestPostgresListStatements without a hook in the store. Co-Authored-By: Claude Opus 5.5 --- .../controller/store/pgtest/pgtest.go | 45 +++- .../controller/store/postgres/evidence.go | 53 +++- .../controller/store/postgres/experiments.go | 241 ++++++++++++------ .../store/postgres/postgres_test.go | 46 ++++ .../controller/store/storetest/storetest.go | 92 ++++++- 5 files changed, 382 insertions(+), 95 deletions(-) diff --git a/pkg/treatment/controller/store/pgtest/pgtest.go b/pkg/treatment/controller/store/pgtest/pgtest.go index 85f15bc00..93eb54103 100644 --- a/pkg/treatment/controller/store/pgtest/pgtest.go +++ b/pkg/treatment/controller/store/pgtest/pgtest.go @@ -16,6 +16,8 @@ import ( "testing" "time" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/stdlib" "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store/postgres" ) @@ -29,6 +31,47 @@ func Available() bool { return os.Getenv(EnvDSN) != "" } // Open returns a migrated store on a private schema, or skips the test. func Open(t testing.TB) *postgres.Store { + t.Helper() + return open(t, postgres.Open) +} + +// Counter counts the statements a store sends to PostgreSQL. +type Counter struct{ n atomic.Int64 } + +// Statements returns the number of statements sent so far. +func (c *Counter) Statements() int64 { return c.n.Load() } + +// Reset sets the count back to zero. +func (c *Counter) Reset() { c.n.Store(0) } + +func (c *Counter) TraceQueryStart(ctx context.Context, _ *pgx.Conn, _ pgx.TraceQueryStartData) context.Context { + c.n.Add(1) + return ctx +} + +func (*Counter) TraceQueryEnd(context.Context, *pgx.Conn, pgx.TraceQueryEndData) {} + +// OpenCounting is Open with every statement counted, so tests can pin how +// many round trips a repository method makes. +func OpenCounting(t testing.TB) (*postgres.Store, *Counter) { + t.Helper() + c := new(Counter) + s := open(t, func(ctx context.Context, dsn string) (*postgres.Store, error) { + cfg, err := pgx.ParseConfig(dsn) + if err != nil { + return nil, err + } + cfg.Tracer = c + db := stdlib.OpenDB(*cfg) + if err = db.PingContext(ctx); err != nil { + return nil, errors.Join(err, db.Close()) + } + return postgres.New(db), nil + }) + return s, c +} + +func open(t testing.TB, connect func(context.Context, string) (*postgres.Store, error)) *postgres.Store { t.Helper() dsn := os.Getenv(EnvDSN) if dsn == "" { @@ -48,7 +91,7 @@ func Open(t testing.TB) *postgres.Store { if err != nil { t.Fatal(errors.Join(err, admin.Close())) } - s, err := postgres.Open(ctx, scoped) + s, err := connect(ctx, scoped) if err != nil { t.Fatal(errors.Join(err, admin.Close())) } diff --git a/pkg/treatment/controller/store/postgres/evidence.go b/pkg/treatment/controller/store/postgres/evidence.go index 96b8ac65c..53109a72a 100644 --- a/pkg/treatment/controller/store/postgres/evidence.go +++ b/pkg/treatment/controller/store/postgres/evidence.go @@ -37,28 +37,61 @@ func (v *evidence) Get(ctx context.Context, id string) (*treatment.Evidence, err } func getEvidence(ctx context.Context, q querier, id string) (*treatment.Evidence, error) { - e := &treatment.Evidence{ID: id} - err := q.QueryRowContext(ctx, "SELECT analyzed_at,window_start,window_end,query_version,enrichment_version,artifact_sha256 FROM xtcp_treatment_evidence WHERE id=$1", id).Scan(&e.AnalyzedAt, &e.WindowStart, &e.WindowEnd, &e.QueryVersion, &e.EnrichmentVersion, &e.ArtifactSHA256) + loaded, err := getEvidenceMany(ctx, q, []string{id}) if err != nil { - return nil, notFound(err) + return nil, err + } + e, ok := loaded[id] + if !ok { + return nil, store.ErrNotFound + } + return e, nil +} + +// getEvidenceMany reads the named evidence documents and their host rows in +// two statements. IDs that do not exist are absent from the result. +func getEvidenceMany(ctx context.Context, q querier, ids []string) (map[string]*treatment.Evidence, error) { + out := make(map[string]*treatment.Evidence, len(ids)) + if len(ids) == 0 { + return out, nil } - rows, err := q.QueryContext(ctx, "SELECT agent_id,sockets,bytes_acked,goodput_bps,retransmission_ratio,rtt_p95_ms,exposed_sockets,collection_healthy FROM xtcp_treatment_evidence_hosts WHERE evidence_id=$1 ORDER BY position", id) + rows, err := q.QueryContext(ctx, "SELECT id,analyzed_at,window_start,window_end,query_version,enrichment_version,artifact_sha256 FROM xtcp_treatment_evidence WHERE id = ANY($1)", ids) if err != nil { return nil, err } for rows.Next() { - var h treatment.HostEvidence - var sockets, bytes, exposed int64 - if err = rows.Scan(&h.AgentID, &sockets, &bytes, &h.GoodputBps, &h.RetransmissionRatio, &h.RttP95Ms, &exposed, &h.CollectionHealthy); err != nil { + e := new(treatment.Evidence) + if err = rows.Scan(&e.ID, &e.AnalyzedAt, &e.WindowStart, &e.WindowEnd, &e.QueryVersion, &e.EnrichmentVersion, &e.ArtifactSHA256); err != nil { return nil, errors.Join(err, rows.Close()) } - h.Sockets, h.BytesAcked, h.ExposedSockets = uint64(sockets), uint64(bytes), uint64(exposed) - e.Hosts = append(e.Hosts, h) + out[e.ID] = e } if err = errors.Join(rows.Err(), rows.Close()); err != nil { return nil, err } - return e, nil + return out, loadEvidenceHosts(ctx, q, ids, out) +} + +// loadEvidenceHosts appends each evidence document's host rows in position +// order. +func loadEvidenceHosts(ctx context.Context, q querier, ids []string, into map[string]*treatment.Evidence) error { + rows, err := q.QueryContext(ctx, "SELECT evidence_id,agent_id,sockets,bytes_acked,goodput_bps,retransmission_ratio,rtt_p95_ms,exposed_sockets,collection_healthy FROM xtcp_treatment_evidence_hosts WHERE evidence_id = ANY($1) ORDER BY evidence_id,position", ids) + if err != nil { + return err + } + for rows.Next() { + var id string + var h treatment.HostEvidence + var sockets, bytes, exposed int64 + if err = rows.Scan(&id, &h.AgentID, &sockets, &bytes, &h.GoodputBps, &h.RetransmissionRatio, &h.RttP95Ms, &exposed, &h.CollectionHealthy); err != nil { + return errors.Join(err, rows.Close()) + } + h.Sockets, h.BytesAcked, h.ExposedSockets = uint64(sockets), uint64(bytes), uint64(exposed) + if e, ok := into[id]; ok { + e.Hosts = append(e.Hosts, h) + } + } + return errors.Join(rows.Err(), rows.Close()) } func (v *evidence) Put(ctx context.Context, experimentID string, e *treatment.Evidence) error { diff --git a/pkg/treatment/controller/store/postgres/experiments.go b/pkg/treatment/controller/store/postgres/experiments.go index 5ae158fd6..6167f25d4 100644 --- a/pkg/treatment/controller/store/postgres/experiments.go +++ b/pkg/treatment/controller/store/postgres/experiments.go @@ -4,12 +4,14 @@ import ( "context" "database/sql" "errors" + "fmt" "net/netip" "slices" "strings" "time" "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store" ) type experiments struct{ *runner } @@ -50,127 +52,206 @@ func (x *experiments) Exists(ctx context.Context, id string) (bool, error) { } func (x *experiments) Get(ctx context.Context, id string) (*treatment.Experiment, error) { - return x.get(ctx, x.q(), id) + es, err := loadMany(ctx, x.q(), x.locked(), []string{id}) + if err != nil { + return nil, err + } + if len(es) == 0 { + return nil, store.ErrNotFound + } + return es[0], nil +} + +// evidenceRefs are the evidence IDs an experiment row and its phases point +// at, resolved after every row is read. +type evidenceRefs struct { + baseline, latest sql.NullString + phases []sql.NullString } -func (x *experiments) get(ctx context.Context, q querier, id string) (*treatment.Experiment, error) { +// loadMany reads the experiments named by ids, in ids order, skipping IDs +// that do not exist. It runs a fixed number of statements whatever len(ids) +// is: the experiment rows, the cohorts, the phases, then every referenced +// evidence document at once. Inside Store.Tx the experiment rows are locked. +func loadMany(ctx context.Context, q querier, locked bool, ids []string) ([]*treatment.Experiment, error) { + if len(ids) == 0 { + return nil, nil + } + byID, refs, err := loadExperimentRows(ctx, q, locked, ids) + if err != nil || len(byID) == 0 { + return nil, err + } + if err = loadCohorts(ctx, q, ids, byID); err != nil { + return nil, err + } + if err = loadPhases(ctx, q, ids, byID, refs); err != nil { + return nil, err + } + if err = attachEvidence(ctx, q, byID, refs); err != nil { + return nil, err + } + out := make([]*treatment.Experiment, 0, len(byID)) + for _, id := range ids { + if e, ok := byID[id]; ok { + out = append(out, e) + } + } + return out, nil +} + +// loadExperimentRows reads the experiment rows and the evidence IDs they +// reference. +func loadExperimentRows(ctx context.Context, q querier, locked bool, ids []string) (map[string]*treatment.Experiment, map[string]*evidenceRefs, error) { suffix := "" - if x.locked() { + if locked { suffix = " FOR UPDATE" } - e := &treatment.Experiment{ID: id} + rows, err := q.QueryContext(ctx, "SELECT "+selectList(experimentColumns)+" FROM xtcp_treatment_experiments WHERE id = ANY($1)"+suffix, ids) + if err != nil { + return nil, nil, err + } + byID := make(map[string]*treatment.Experiment, len(ids)) + refs := make(map[string]*evidenceRefs, len(ids)) + for rows.Next() { + e, r, err := scanExperiment(rows) + if err != nil { + return nil, nil, errors.Join(err, rows.Close()) + } + byID[e.ID], refs[e.ID] = e, r + } + if err = errors.Join(rows.Err(), rows.Close()); err != nil { + return nil, nil, err + } + return byID, refs, nil +} + +// scanExperiment reads one row of experimentColumns. +func scanExperiment(rows *sql.Rows) (*treatment.Experiment, *evidenceRefs, error) { + e := new(treatment.Experiment) + r := new(evidenceRefs) var tr treatmentRow var minObs, maxPhase, maxAge, minSockets, minBytes, minTreated, revision int64 - var baseline, latest sql.NullString var status, outcome string var proposed sql.NullTime var updated time.Time - targets := slices.Concat([]any{&e.Location, &e.Hypothesis}, tr.targets(), - []any{&minObs, &maxPhase, &maxAge, &minSockets, &minBytes, &minTreated, &e.Policy.MinimumImprovement, &e.Policy.MaximumRetransmissionIncrease, &e.Policy.MaximumRTTIncrease, &baseline, &status, &revision, &proposed, &latest, &outcome, &updated}) - err := q.QueryRowContext(ctx, "SELECT "+selectList(experimentColumns[1:])+" FROM xtcp_treatment_experiments WHERE id=$1"+suffix, id).Scan(targets...) - if err != nil { - return nil, notFound(err) + targets := slices.Concat([]any{&e.ID, &e.Location, &e.Hypothesis}, tr.targets(), + []any{&minObs, &maxPhase, &maxAge, &minSockets, &minBytes, &minTreated, &e.Policy.MinimumImprovement, &e.Policy.MaximumRetransmissionIncrease, &e.Policy.MaximumRTTIncrease, &r.baseline, &status, &revision, &proposed, &r.latest, &outcome, &updated}) + if err := rows.Scan(targets...); err != nil { + return nil, nil, err } t, err := tr.value() if err != nil { - return nil, err + return nil, nil, err } e.Treatment = *t e.Policy.MinimumObservation, e.Policy.MaximumPhase, e.Policy.MaximumEvidenceAge = time.Duration(minObs), time.Duration(maxPhase), time.Duration(maxAge) e.Policy.MinimumSocketsPerHost, e.Policy.MinimumBytesPerHost, e.Policy.MinimumTreatedHosts = uint64(minSockets), uint64(minBytes), uint32(minTreated) if e.Status, err = treatment.ParseStatus(status); err != nil { - return nil, err + return nil, nil, err } if e.LatestOutcome, err = treatment.ParseOutcome(outcome); err != nil { - return nil, err + return nil, nil, err } e.Revision, e.ProposedAt, e.UpdatedAt = uint64(revision), fromNullTime(proposed), updated - loaded := map[string]*treatment.Evidence{} - load := func(id sql.NullString) (*treatment.Evidence, error) { - if !id.Valid { - return nil, nil - } - if v, ok := loaded[id.String]; ok { - return v.Clone(), nil - } - v, err := getEvidence(ctx, q, id.String) - if err != nil { - return nil, err - } - loaded[id.String] = v - return v.Clone(), nil - } - if e.Baseline, err = load(baseline); err != nil { - return nil, err - } - if e.LatestEvidence, err = load(latest); err != nil { - return nil, err - } - if e.CohortOrder, err = loadCohort(ctx, q, id); err != nil { - return nil, err - } - if e.Phases, err = loadPhases(ctx, q, id, load); err != nil { - return nil, err - } - return e, nil + return e, r, nil } -// loadCohort reads the experiment's cohort in allocation order. -func loadCohort(ctx context.Context, q querier, id string) ([]string, error) { - rows, err := q.QueryContext(ctx, "SELECT agent_id FROM xtcp_treatment_cohort WHERE experiment_id=$1 ORDER BY position", id) +// loadCohorts reads every experiment's cohort in allocation order. +func loadCohorts(ctx context.Context, q querier, ids []string, byID map[string]*treatment.Experiment) error { + rows, err := q.QueryContext(ctx, "SELECT experiment_id,agent_id FROM xtcp_treatment_cohort WHERE experiment_id = ANY($1) ORDER BY experiment_id,position", ids) if err != nil { - return nil, err + return err } - var cohort []string for rows.Next() { - var a string - if err = rows.Scan(&a); err != nil { - return nil, errors.Join(err, rows.Close()) + var id, a string + if err = rows.Scan(&id, &a); err != nil { + return errors.Join(err, rows.Close()) + } + if e, ok := byID[id]; ok { + e.CohortOrder = append(e.CohortOrder, a) } - cohort = append(cohort, a) - } - if err = errors.Join(rows.Err(), rows.Close()); err != nil { - return nil, err } - return cohort, nil + return errors.Join(rows.Err(), rows.Close()) } -// loadPhases reads the experiment's phases in order and resolves each -// phase's evidence through load. Evidence is loaded after the rows are -// closed so a transaction never has two result sets open. -func loadPhases(ctx context.Context, q querier, id string, load func(sql.NullString) (*treatment.Evidence, error)) ([]treatment.Phase, error) { - rows, err := q.QueryContext(ctx, "SELECT target_hosts,issued_at,observation_started_at,evaluated_at,evidence_id,outcome,reviewer,reason FROM xtcp_treatment_phases WHERE experiment_id=$1 ORDER BY position", id) +// loadPhases reads every experiment's phases in order and records each +// phase's evidence ID for attachEvidence. +func loadPhases(ctx context.Context, q querier, ids []string, byID map[string]*treatment.Experiment, refs map[string]*evidenceRefs) error { + rows, err := q.QueryContext(ctx, "SELECT experiment_id,target_hosts,issued_at,observation_started_at,evaluated_at,evidence_id,outcome,reviewer,reason FROM xtcp_treatment_phases WHERE experiment_id = ANY($1) ORDER BY experiment_id,position", ids) if err != nil { - return nil, err + return err } - var phases []treatment.Phase - var evidenceIDs []sql.NullString for rows.Next() { + var id string var p treatment.Phase var ev sql.NullString var target int64 var issued, started, evaluated sql.NullTime var out string - if err = rows.Scan(&target, &issued, &started, &evaluated, &ev, &out, &p.Reviewer, &p.Reason); err != nil { - return nil, errors.Join(err, rows.Close()) + if err = rows.Scan(&id, &target, &issued, &started, &evaluated, &ev, &out, &p.Reviewer, &p.Reason); err != nil { + return errors.Join(err, rows.Close()) } p.TargetHosts = uint32(target) p.IssuedAt, p.ObservationStartedAt, p.EvaluatedAt = fromNullTime(issued), fromNullTime(started), fromNullTime(evaluated) if p.Outcome, err = treatment.ParseOutcome(out); err != nil { - return nil, errors.Join(err, rows.Close()) + return errors.Join(err, rows.Close()) + } + if e, ok := byID[id]; ok { + e.Phases = append(e.Phases, p) + refs[id].phases = append(refs[id].phases, ev) } - phases = append(phases, p) - evidenceIDs = append(evidenceIDs, ev) } - if err = errors.Join(rows.Err(), rows.Close()); err != nil { - return nil, err + return errors.Join(rows.Err(), rows.Close()) +} + +// attachEvidence loads every referenced evidence document once and gives +// each reference its own copy, so callers can mutate one experiment's +// evidence without touching another's. +func attachEvidence(ctx context.Context, q querier, byID map[string]*treatment.Experiment, refs map[string]*evidenceRefs) error { + seen := map[string]bool{} + var wanted []string + want := func(id sql.NullString) { + if id.Valid && !seen[id.String] { + seen[id.String] = true + wanted = append(wanted, id.String) + } } - for i := range phases { - if phases[i].Evidence, err = load(evidenceIDs[i]); err != nil { - return nil, err + for _, r := range refs { + want(r.baseline) + want(r.latest) + for _, p := range r.phases { + want(p) } } - return phases, nil + loaded, err := getEvidenceMany(ctx, q, wanted) + if err != nil { + return err + } + resolve := func(id sql.NullString) (*treatment.Evidence, error) { + if !id.Valid { + return nil, nil + } + v, ok := loaded[id.String] + if !ok { + return nil, fmt.Errorf("evidence %s: %w", id.String, store.ErrNotFound) + } + return v.Clone(), nil + } + for id, e := range byID { + r := refs[id] + if e.Baseline, err = resolve(r.baseline); err != nil { + return err + } + if e.LatestEvidence, err = resolve(r.latest); err != nil { + return err + } + for i := range e.Phases { + if e.Phases[i].Evidence, err = resolve(r.phases[i]); err != nil { + return err + } + } + } + return nil } // Save stores the evidence the experiment carries, upserts the row, writes @@ -220,6 +301,8 @@ func (x *experiments) Save(ctx context.Context, e *treatment.Experiment) error { }) } +// List returns the experiment named id, or up to limit experiments ordered +// by ID when id is empty, in a fixed number of statements. func (x *experiments) List(ctx context.Context, id string, limit int) ([]*treatment.Experiment, error) { q := x.q() rows, err := q.QueryContext(ctx, "SELECT id FROM xtcp_treatment_experiments WHERE ($1='' OR id=$1) ORDER BY id LIMIT $2", id, limit) @@ -237,15 +320,7 @@ func (x *experiments) List(ctx context.Context, id string, limit int) ([]*treatm if err = errors.Join(rows.Err(), rows.Close()); err != nil { return nil, err } - out := make([]*treatment.Experiment, 0, len(ids)) - for _, v := range ids { - e, err := x.get(ctx, q, v) - if err != nil { - return nil, err - } - out = append(out, e) - } - return out, nil + return loadMany(ctx, q, x.locked(), ids) } func statusList(statuses []treatment.Status) (string, []any) { @@ -287,7 +362,7 @@ func activeStatuses() []treatment.Status { } // hasActiveSQL and destinationBlockedSQL are the two hot discovery lookups; -// TestExperimentQueryIndexes pins the indexes that serve them. +// TestPostgresExperimentQueryIndexes pins the indexes that serve them. var hasActiveSQL = func() string { names := make([]string, 0, 4) for _, s := range activeStatuses() { diff --git a/pkg/treatment/controller/store/postgres/postgres_test.go b/pkg/treatment/controller/store/postgres/postgres_test.go index 776e7290f..0d1e8429a 100644 --- a/pkg/treatment/controller/store/postgres/postgres_test.go +++ b/pkg/treatment/controller/store/postgres/postgres_test.go @@ -18,6 +18,52 @@ func TestPostgresConformance(t *testing.T) { storetest.Run(t, func(t *testing.T) store.Store { return pgtest.Open(t) }) } +// TestPostgresListStatements pins List and Get to a fixed number of +// statements, whatever the number of experiments and evidence documents. +func TestPostgresListStatements(t *testing.T) { + ctx := context.Background() + s, counter := pgtest.OpenCounting(t) + rp := s.Repos() + for i := range 200 { + if err := rp.Experiments.Save(ctx, storetest.Numbered(i)); err != nil { + t.Fatal(err) + } + } + tests := []struct { + description string + run func() error + expected int64 + }{ + {"positive: List of 200 experiments with cohorts, phases and evidence", func() error { + _, err := rp.Experiments.List(ctx, "", 1000) + return err + }, 6}, + {"boundary: List of one experiment", func() error { + _, err := rp.Experiments.List(ctx, "exp-007", 1000) + return err + }, 6}, + {"positive: Get reads one experiment through the same loader", func() error { + _, err := rp.Experiments.Get(ctx, "exp-007") + return err + }, 5}, + {"negative: List of an unknown ID stops after the ID lookup", func() error { + _, err := rp.Experiments.List(ctx, "ghost", 1000) + return err + }, 1}, + } + for _, tc := range tests { + t.Run(tc.description, func(t *testing.T) { + counter.Reset() + if err := tc.run(); err != nil { + t.Fatal(err) + } + if got := counter.Statements(); got > tc.expected { + t.Fatalf("expected at most %d statements, got %d", tc.expected, got) + } + }) + } +} + func TestPostgresMigrateIdempotent(t *testing.T) { s := pgtest.Open(t) ctx := context.Background() diff --git a/pkg/treatment/controller/store/storetest/storetest.go b/pkg/treatment/controller/store/storetest/storetest.go index 2a69b92ca..ae21bc8ee 100644 --- a/pkg/treatment/controller/store/storetest/storetest.go +++ b/pkg/treatment/controller/store/storetest/storetest.go @@ -332,7 +332,7 @@ type repoCase struct { func experiments(t *testing.T, open Opener) { ctx := context.Background() - for _, tc := range slices.Concat(experimentRoundTripCases(ctx), experimentQueryCases(ctx)) { + for _, tc := range slices.Concat(experimentRoundTripCases(ctx), experimentQueryCases(ctx), experimentListCases(ctx)) { t.Run(tc.name, func(t *testing.T) { t.Log(tc.description) tc.run(t, open(t).Repos()) @@ -406,6 +406,74 @@ func experimentRoundTripCases(ctx context.Context) []repoCase { } } +// Numbered returns populated() renamed to exp-, with evidence IDs of its +// own so many copies can share one store. +func Numbered(i int) *treatment.Experiment { + e := populated() + e.ID = fmt.Sprintf("exp-%03d", i) + e.Baseline.ID = e.ID + "-baseline" + after := e.LatestEvidence.Clone() + after.ID = e.ID + "-after" + e.LatestEvidence, e.Phases[0].Evidence = after, after.Clone() + return e +} + +// experimentListCases covers List: every row it returns must equal Get. +func experimentListCases(ctx context.Context) []repoCase { + return []repoCase{ + {"list-many", "positive: List of 50 experiments returns each in ID order, equal to Get", func(t *testing.T, rp store.Repos) { + for i := range 50 { + must(t, rp.Experiments.Save(ctx, Numbered(49-i))) + } + list, err := rp.Experiments.List(ctx, "", 1000) + must(t, err) + if len(list) != 50 { + t.Fatalf("expected 50 experiments, got %d", len(list)) + } + for i, got := range list { + sameExperiment(t, got.ID, got, Numbered(i)) + } + }}, + {"shared-evidence", "positive: latest evidence and phase 0 share an ID and each gets its own copy", func(t *testing.T, rp store.Repos) { + must(t, rp.Experiments.Save(ctx, populated())) + list, err := rp.Experiments.List(ctx, "exp", 10) + must(t, err) + if len(list) != 1 { + t.Fatalf("expected one experiment, got %d", len(list)) + } + e := list[0] + if e.LatestEvidence == nil || e.Phases[0].Evidence == nil || e.LatestEvidence == e.Phases[0].Evidence { + t.Fatal("expected two distinct copies of the shared evidence") + } + sameEvidence(t, "shared evidence", e.Phases[0].Evidence, e.LatestEvidence) + }}, + {"limit-one", "boundary: limit 1 returns only the lowest ID", func(t *testing.T, rp store.Repos) { + for i := range 4 { + must(t, rp.Experiments.Save(ctx, Numbered(3-i))) + } + list, err := rp.Experiments.List(ctx, "", 1) + must(t, err) + if len(list) != 1 || list[0].ID != "exp-000" { + t.Fatalf("expected only exp-000, got %d", len(list)) + } + }}, + {"no-children", "boundary: an experiment without phases, cohort or evidence lists with nil evidence", func(t *testing.T, rp store.Repos) { + e := &treatment.Experiment{ID: "bare", Location: locationCst, Hypothesis: "h", Treatment: fixtures.Treatment(), Policy: treatment.DefaultPolicy(), Status: treatment.StatusProposed, Revision: 1, ProposedAt: now, UpdatedAt: now} + must(t, rp.Experiments.Save(ctx, e)) + list, err := rp.Experiments.List(ctx, "", 10) + must(t, err) + if len(list) != 1 { + t.Fatalf("expected one experiment, got %d", len(list)) + } + got := list[0] + if got.Baseline != nil || got.LatestEvidence != nil || len(got.Phases) != 0 || len(got.CohortOrder) != 0 { + t.Fatalf("expected no children, got %+v", got) + } + sameExperiment(t, "bare experiment", got, e) + }}, + } +} + // experimentQueryCases covers lookups, conflicts, listing and status filters. func experimentQueryCases(ctx context.Context) []repoCase { return []repoCase{ @@ -766,6 +834,28 @@ func tx(t *testing.T, open Opener) { return nil })) }) + t.Run("list-in-tx", func(t *testing.T) { + t.Log("corner: List inside a Tx locks its rows and returns what List outside returns") + s := open(t) + for i := range 3 { + must(t, s.Repos().Experiments.Save(ctx, Numbered(i))) + } + outside, err := s.Repos().Experiments.List(ctx, "", 10) + must(t, err) + must(t, s.Tx(ctx, func(rp store.Repos) error { + inside, err := rp.Experiments.List(ctx, "", 10) + if err != nil { + return err + } + if len(inside) != len(outside) { + return fmt.Errorf("expected %d experiments inside the transaction, got %d", len(outside), len(inside)) + } + for i := range inside { + sameExperiment(t, inside[i].ID, inside[i], outside[i]) + } + return nil + })) + }) t.Run("canceled-context", func(t *testing.T) { t.Log("boundary: a canceled context fails the Tx and persists nothing") s := open(t) From 926c2c7422d4ee27c0aba85374df4a6bc0bae974 Mon Sep 17 00:00:00 2001 From: "randomizedcoder dave.seddon.ca@gmail.com" Date: Mon, 26 Oct 2026 07:29:58 -0700 Subject: [PATCH 21/27] nix(checks): help smoke for the treatment binaries and zstd-probe xtcp2-controller, xtcp2-controllerctl, xtcp2-treatment-agent, xtcp2-treatmentctl and zstd-probe join cmdNames. ipmeta-bootstrap stays out: its ContinueOnError flag set turns -help into exit 1. Co-Authored-By: Claude Opus 5.5 --- nix/checks/cli-help-smoke.nix | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nix/checks/cli-help-smoke.nix b/nix/checks/cli-help-smoke.nix index acf8864dc..55cae5d52 100644 --- a/nix/checks/cli-help-smoke.nix +++ b/nix/checks/cli-help-smoke.nix @@ -35,9 +35,14 @@ let # A tools/ binary, exported beside the cmd ones (nix/binaries.nix). "rtnlmon-probe" "xtcp2" + "xtcp2-controller" + "xtcp2-controllerctl" + "xtcp2-treatment-agent" + "xtcp2-treatmentctl" "xtcp2client" "xtcp2ctl" "xtcp2_kafka_client" + "zstd-probe" ]; mkSmoke = From 30a21cdd5f2a2117ebf56ee91e459cd52ec8b1db Mon Sep 17 00:00:00 2001 From: "randomizedcoder dave.seddon.ca@gmail.com" Date: Mon, 26 Oct 2026 07:33:03 -0700 Subject: [PATCH 22/27] test(treatment): reproducible index timings and a generated coverage table bench-tcp-controller-postgres-indexes runs TestPostgresExperimentQueryIndexes three times on the shared PostgreSQL fixture and writes the file under docs/benchmarks; the old file named a deleted test and the pre-rework index names. The unit target now also writes coverage-table.md from the go test -cover lines, which replaces the hand-collected three-package table in docs/tcp-controller-testing.md. Co-Authored-By: Claude Opus 5.5 --- .../tcp-controller/postgres-indexes.txt | 43 ++++++++---- docs/tcp-controller-testing.md | 55 +++++++++------ docs/tcp-controller.md | 8 +-- nix/default.nix | 1 + nix/tests/tcp-controller/quality.nix | 67 +++++++++++++++---- 5 files changed, 125 insertions(+), 49 deletions(-) diff --git a/docs/benchmarks/tcp-controller/postgres-indexes.txt b/docs/benchmarks/tcp-controller/postgres-indexes.txt index e398be938..c05e54e6e 100644 --- a/docs/benchmarks/tcp-controller/postgres-indexes.txt +++ b/docs/benchmarks/tcp-controller/postgres-indexes.txt @@ -1,27 +1,46 @@ +# nix build .#bench-tcp-controller-postgres-indexes +# postgres (PostgreSQL) 18.6, go version go1.27.3 linux/amd64 === RUN TestPostgresExperimentQueryIndexes === RUN TestPostgresExperimentQueryIndexes/old-destination - postgres_indexes_test.go:207: 100005 experiments: unindexed 26.153 ms, indexed 0.023 ms (xtcp_control_experiments_destination) + postgres_test.go:174: 100005 experiments: unindexed 22.274 ms, indexed 0.021 ms (xtcp_treatment_experiments_destination) === RUN TestPostgresExperimentQueryIndexes/absent-destination - postgres_indexes_test.go:207: 100005 experiments: unindexed 24.508 ms, indexed 0.015 ms (xtcp_control_experiments_destination) + postgres_test.go:174: 100005 experiments: unindexed 21.472 ms, indexed 0.020 ms (xtcp_treatment_experiments_destination) === RUN TestPostgresExperimentQueryIndexes/active-budget - postgres_indexes_test.go:207: 100005 experiments: unindexed 9.218 ms, indexed 0.018 ms (xtcp_control_experiments_destination) + postgres_test.go:174: 100005 experiments: unindexed 18.080 ms, indexed 0.016 ms (xtcp_treatment_experiments_destination) === RUN TestPostgresExperimentQueryIndexes/idle-budget - postgres_indexes_test.go:207: 100005 experiments: unindexed 27.249 ms, indexed 0.023 ms (xtcp_control_experiments_status_location) + postgres_test.go:174: 100005 experiments: unindexed 25.644 ms, indexed 0.020 ms (xtcp_treatment_experiments_location_status) +--- PASS: TestPostgresExperimentQueryIndexes (1.19s) + --- PASS: TestPostgresExperimentQueryIndexes/old-destination (0.02s) + --- PASS: TestPostgresExperimentQueryIndexes/absent-destination (0.02s) + --- PASS: TestPostgresExperimentQueryIndexes/active-budget (0.02s) + --- PASS: TestPostgresExperimentQueryIndexes/idle-budget (0.03s) === RUN TestPostgresExperimentQueryIndexes === RUN TestPostgresExperimentQueryIndexes/old-destination - postgres_indexes_test.go:207: 100005 experiments: unindexed 25.340 ms, indexed 0.022 ms (xtcp_control_experiments_destination) + postgres_test.go:174: 100005 experiments: unindexed 22.485 ms, indexed 0.023 ms (xtcp_treatment_experiments_destination) === RUN TestPostgresExperimentQueryIndexes/absent-destination - postgres_indexes_test.go:207: 100005 experiments: unindexed 23.556 ms, indexed 0.021 ms (xtcp_control_experiments_destination) + postgres_test.go:174: 100005 experiments: unindexed 19.972 ms, indexed 0.019 ms (xtcp_treatment_experiments_destination) === RUN TestPostgresExperimentQueryIndexes/active-budget - postgres_indexes_test.go:207: 100005 experiments: unindexed 9.146 ms, indexed 0.017 ms (xtcp_control_experiments_destination) + postgres_test.go:174: 100005 experiments: unindexed 18.097 ms, indexed 0.019 ms (xtcp_treatment_experiments_destination) === RUN TestPostgresExperimentQueryIndexes/idle-budget - postgres_indexes_test.go:207: 100005 experiments: unindexed 26.392 ms, indexed 0.036 ms (xtcp_control_experiments_status_location) + postgres_test.go:174: 100005 experiments: unindexed 24.627 ms, indexed 0.020 ms (xtcp_treatment_experiments_location_status) +--- PASS: TestPostgresExperimentQueryIndexes (1.18s) + --- PASS: TestPostgresExperimentQueryIndexes/old-destination (0.02s) + --- PASS: TestPostgresExperimentQueryIndexes/absent-destination (0.02s) + --- PASS: TestPostgresExperimentQueryIndexes/active-budget (0.02s) + --- PASS: TestPostgresExperimentQueryIndexes/idle-budget (0.03s) === RUN TestPostgresExperimentQueryIndexes === RUN TestPostgresExperimentQueryIndexes/old-destination - postgres_indexes_test.go:207: 100005 experiments: unindexed 24.946 ms, indexed 0.024 ms (xtcp_control_experiments_destination) + postgres_test.go:174: 100005 experiments: unindexed 20.488 ms, indexed 0.023 ms (xtcp_treatment_experiments_destination) === RUN TestPostgresExperimentQueryIndexes/absent-destination - postgres_indexes_test.go:207: 100005 experiments: unindexed 23.196 ms, indexed 0.014 ms (xtcp_control_experiments_destination) + postgres_test.go:174: 100005 experiments: unindexed 19.139 ms, indexed 0.016 ms (xtcp_treatment_experiments_destination) === RUN TestPostgresExperimentQueryIndexes/active-budget - postgres_indexes_test.go:207: 100005 experiments: unindexed 9.920 ms, indexed 0.025 ms (xtcp_control_experiments_destination) + postgres_test.go:174: 100005 experiments: unindexed 16.850 ms, indexed 0.016 ms (xtcp_treatment_experiments_destination) === RUN TestPostgresExperimentQueryIndexes/idle-budget - postgres_indexes_test.go:207: 100005 experiments: unindexed 26.413 ms, indexed 0.023 ms (xtcp_control_experiments_status_location) + postgres_test.go:174: 100005 experiments: unindexed 24.134 ms, indexed 0.019 ms (xtcp_treatment_experiments_location_status) +--- PASS: TestPostgresExperimentQueryIndexes (1.20s) + --- PASS: TestPostgresExperimentQueryIndexes/old-destination (0.02s) + --- PASS: TestPostgresExperimentQueryIndexes/absent-destination (0.02s) + --- PASS: TestPostgresExperimentQueryIndexes/active-budget (0.02s) + --- PASS: TestPostgresExperimentQueryIndexes/idle-budget (0.03s) +PASS +ok github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store/postgres 3.566s diff --git a/docs/tcp-controller-testing.md b/docs/tcp-controller-testing.md index 5cccdc4a2..c6613ea7a 100644 --- a/docs/tcp-controller-testing.md +++ b/docs/tcp-controller-testing.md @@ -53,32 +53,47 @@ cases; it is not based only on happy-path workflows. | Controller | mTLS role and enrollment boundaries, authenticated status/readiness, desired-state watch cancellation, notification coalescing/reconnects, HTTP failures/body closure/size limits, SQL counter deltas/resets, real transaction/review races, durable holds, discovery ASN/prefix filters and cooldown across controller restart. | | Route planning | Opt-in and private resource bounds, unsupported forwarding attributes, IPv6 encoding, explicit ECN/quickack enable/disable, preservation of unrelated metrics/locks, complete nearby profile and baseline immutability. | -Final direct Go run: race detector enabled, three repetitions, PostgreSQL and -ClickHouse fixtures enabled, no skips in `pkg/treatment/...` or the soak helper -suite. Route unit tests also passed three race-enabled repetitions. Focused -concurrency tests passed ten repetitions. `go vet` passed for the feature, -integration helper and goip packages. - -| Package | Direct race run | Nix unit check | -| --- | ---: | ---: | -| `pkg/treatment` | 89.1% | 89.0% | -| `pkg/treatment/agent` | 71.4% | 67.5% | -| `pkg/treatment/controller` | 74.3% | 73.9% | - -`Evaluate`, `HealthyEvidence`, and treatment, TCP-settings, policy, evidence and -proposal validators reached 100% statement coverage in both builds. In the direct -race run, `ValidateDesired` reached -91.3%, socket stamping 97.6%, discovery 80.2%, and agent `Run` 87.8%. -Statement coverage is not branch coverage or a proof of correctness. The final -Nix unit and race checks also passed; the figures above retain both measurements -rather than mixing coverage from different builds and environments. +The table below is generated, not hand-collected: +`nix build .#test-tcp-controller-unit` writes it to `result/coverage-table.md` +from the unit run's `go test -cover` lines (PostgreSQL and ClickHouse fixtures +enabled, no skips), and `result/coverage.txt` holds the per-function figures. +`internal/goip/actuate` runs only its `TestTreatment*` tests there. The race +target (`test-tcp-controller-race`) runs the same packages three times under +`-race` and reports no coverage. + +| Package | Statements covered | +| --- | ---: | +| `pkg/treatment` | 54.7% | +| `pkg/treatment/agent` | 61.9% | +| `pkg/treatment/attribution` | 88.1% | +| `pkg/treatment/controller` | 74.0% | +| `pkg/treatment/controller/evidence` | 100.0% | +| `pkg/treatment/controller/evidence/clickhouse` | 93.5% | +| `pkg/treatment/controller/store/memory` | 95.3% | +| `pkg/treatment/controller/store/postgres` | 85.5% | +| `pkg/treatment/experiment` | 93.8% | +| `pkg/treatment/identity` | 90.5% | +| `pkg/treatment/local` | 70.5% | +| `pkg/treatment/reconcile` | 87.0% | +| `pkg/treatment/wire` | 86.2% | +| `tools/tcp-controller-integration` | 4.1% | +| `internal/goip/actuate` | 22.1% | + +`HealthyEvidence` and the treatment, TCP-settings, policy, evidence, proposal +and report validators reach 100% statement coverage; `Evaluate` 94.7%, +`ValidateDesired` 91.7%, socket stamping 100%, discovery 87.3%, the agent's +`Run` 97.4%, the reconciler's `Run` 88.9% and the attribution watcher's `Run` +87.9%. `pkg/treatment` reads lower than before the split because coverage is per +package (no `-coverpkg`): its enum, `Clone` and `Equal` methods are exercised +by the `wire`, store and controller tests, which do not count toward it. +Statement coverage is not branch coverage or a proof of correctness. Remaining gaps include some filesystem/syscall error paths, live namespace inspection, TLS client construction, reconnect/backoff timing, and all combinations of controller database failures. Kernel route mutation and network behavior remain the responsibility of the existing disposable-namespace and seven-guest VM tests. The integration harness itself has low unit coverage -(3.8%); most of its code only executes inside those VMs. This audit did not rerun +(4.1%); most of its code only executes inside those VMs. This audit did not rerun the 30-minute soak; the previous three-cycle soak passed before these changes. The new RTT boundary regression exposed floating-point cancellation: `5.5/5-1` diff --git a/docs/tcp-controller.md b/docs/tcp-controller.md index ece1735cc..5641fa06c 100644 --- a/docs/tcp-controller.md +++ b/docs/tcp-controller.md @@ -393,10 +393,10 @@ in-memory store) covers all lifecycle states, cooldown boundaries and location/destination isolation. Concurrent proposal tests verify that only one automatic proposal is persisted. -On the local PostgreSQL 15.19 fixture, three repeated runs measured a median -25.34 ms without indexes versus 0.023 ms with them for an old destination's -cooldown lookup. These are synthetic query execution times, not end-to-end -production latency. [Raw query-plan timing results](benchmarks/tcp-controller/postgres-indexes.txt) +On the Nix PostgreSQL 18.6 fixture (`nix build .#bench-tcp-controller-postgres-indexes`), +three repeated runs measured a median 22.27 ms without indexes versus 0.023 ms +with them for an old destination's cooldown lookup. These are synthetic query +execution times, not end-to-end production latency. [Raw query-plan timing results](benchmarks/tcp-controller/postgres-indexes.txt) also include missing destinations and active/idle data-center budget checks. Startup migration creates missing indexes idempotently. For an existing busy diff --git a/nix/default.nix b/nix/default.nix index 2d7bd0026..a672888c0 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -1099,6 +1099,7 @@ in test-tcp-controller-unit = tests.tcp-controller-quality.unit; test-tcp-controller-race = tests.tcp-controller-quality.race; bench-tcp-controller = tests.tcp-controller-quality.bench; + bench-tcp-controller-postgres-indexes = tests.tcp-controller-quality.postgres-indexes; test-go-race = tests.go-race; test-listener-security = tests.listener-security; test-proto-deserialize-golden = tests.proto-deserialize-golden; diff --git a/nix/tests/tcp-controller/quality.nix b/nix/tests/tcp-controller/quality.nix index 111ec95bb..d28976514 100644 --- a/nix/tests/tcp-controller/quality.nix +++ b/nix/tests/tcp-controller/quality.nix @@ -2,6 +2,36 @@ { pkgs, vendoredSource }: let versions = import ../../versions.nix { inherit pkgs; }; + # Source tree plus a disposable PostgreSQL on a Unix socket, exported as + # XTCP_CONTROLLER_TEST_DSN for the store tests. + postgresFixture = '' + cp -r ${vendoredSource} source + chmod -R u+w source + cd source + export GOTOOLCHAIN=local GOFLAGS=-mod=vendor CGO_ENABLED=1 + export GOCACHE="$TMPDIR/go-cache" GOMODCACHE="$TMPDIR/go-modcache" + mkdir -p "$out" "$TMPDIR/pg-socket" + initdb -D "$TMPDIR/pg-data" -A trust --no-locale > "$out/postgres-init.log" 2>&1 + trap 'pg_ctl -D "$TMPDIR/pg-data" -m immediate -w stop >/dev/null 2>&1 || true' EXIT + pg_ctl -D "$TMPDIR/pg-data" -l "$out/postgres.log" \ + -o "-k $TMPDIR/pg-socket -h ''' -p 55439" -w start + export XTCP_CONTROLLER_TEST_DSN="host=$TMPDIR/pg-socket port=55439 dbname=postgres sslmode=disable" + ''; + # Per-package statement coverage from the unit logs as a markdown table, + # pasted into docs/tcp-controller-testing.md. actuate runs its treatment + # tests only. + coverageTable = '' + go tool cover -func "$out/coverage.out" > "$out/coverage.txt" + { + echo "| Package | Statements covered |" + echo "| --- | ---: |" + cat "$out/tests.log" "$out/routes.log" | awk -F'\t' '/^ok/ && /coverage:/ { + pkg = $2; sub("github.com/randomizedcoder/xtcp2/", "", pkg) + match($0, /coverage: [0-9.]+%/) + print "| `" pkg "` | " substr($0, RSTART + 10, RLENGTH - 10) " |" + }' + } > "$out/coverage-table.md" + ''; check = race: pkgs.runCommand "xtcp2-controller-${if race then "race" else "unit"}" @@ -14,17 +44,7 @@ let ]; } '' - cp -r ${vendoredSource} source - chmod -R u+w source - cd source - export GOTOOLCHAIN=local GOFLAGS=-mod=vendor CGO_ENABLED=1 - export GOCACHE="$TMPDIR/go-cache" GOMODCACHE="$TMPDIR/go-modcache" - mkdir -p "$out" "$TMPDIR/pg-socket" - initdb -D "$TMPDIR/pg-data" -A trust --no-locale > "$out/postgres-init.log" 2>&1 - trap 'pg_ctl -D "$TMPDIR/pg-data" -m immediate -w stop >/dev/null 2>&1 || true' EXIT - pg_ctl -D "$TMPDIR/pg-data" -l "$out/postgres.log" \ - -o "-k $TMPDIR/pg-socket -h ''' -p 55439" -w start - export XTCP_CONTROLLER_TEST_DSN="host=$TMPDIR/pg-socket port=55439 dbname=postgres sslmode=disable" + ${postgresFixture} go test -ldflags=-checklinkname=0 ${ if race then "-race -count=3" else "-count=1 -coverprofile=$out/coverage.out" } \ @@ -33,15 +53,36 @@ let # These fixtures must execute, never silently skip in the focused checks. grep -q -- '--- PASS: TestPostgresConcurrentReviewFencesReplicas' "$out/tests.log" grep -q -- '--- PASS: TestClickHouseAnalysisCounterDeltas' "$out/tests.log" - go test -ldflags=-checklinkname=0 ${if race then "-race -count=3" else "-count=1"} \ + go test -ldflags=-checklinkname=0 ${if race then "-race -count=3" else "-count=1 -cover"} \ -timeout 5m -v -run '^TestTreatment' ./internal/goip/actuate > "$out/routes.log" 2>&1 \ || { cat "$out/routes.log"; exit 1; } - ${if race then "" else ''go tool cover -func "$out/coverage.out" > "$out/coverage.txt"''} + ${if race then "" else coverageTable} ''; in { unit = check false; race = check true; + # Query-plan timings for the discovery indexes, three runs; the source of + # docs/benchmarks/tcp-controller/postgres-indexes.txt. + postgres-indexes = + pkgs.runCommand "xtcp2-controller-postgres-indexes" + { + nativeBuildInputs = [ + versions.go + pkgs.gcc + pkgs.postgresql + ]; + } + '' + ${postgresFixture} + { + echo "# nix build .#bench-tcp-controller-postgres-indexes" + echo "# $(postgres --version), $(go version)" + go test -ldflags=-checklinkname=0 -count=3 -timeout 10m -v \ + -run '^TestPostgresExperimentQueryIndexes$' ./pkg/treatment/controller/store/postgres + } > "$out/postgres-indexes.txt" 2>&1 || { cat "$out/postgres-indexes.txt"; exit 1; } + grep -q -- '--- PASS: TestPostgresExperimentQueryIndexes' "$out/postgres-indexes.txt" + ''; bench = pkgs.runCommand "xtcp2-controller-bench" { nativeBuildInputs = [ versions.go ]; } '' cp -r ${vendoredSource} source chmod -R u+w source From 5ccc671e7c9daa371f632c006a2cdc7e0d0d4eb0 Mon Sep 17 00:00:00 2001 From: "randomizedcoder dave.seddon.ca@gmail.com" Date: Mon, 26 Oct 2026 07:41:14 -0700 Subject: [PATCH 23/27] lint(treatment): move the List-in-Tx conformance row out of tx The row pushed storetest's tx past the comprehensive tier's funlen limit. Co-Authored-By: Claude Opus 5.5 --- .../controller/store/storetest/storetest.go | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/pkg/treatment/controller/store/storetest/storetest.go b/pkg/treatment/controller/store/storetest/storetest.go index ae21bc8ee..cfda3b130 100644 --- a/pkg/treatment/controller/store/storetest/storetest.go +++ b/pkg/treatment/controller/store/storetest/storetest.go @@ -834,28 +834,7 @@ func tx(t *testing.T, open Opener) { return nil })) }) - t.Run("list-in-tx", func(t *testing.T) { - t.Log("corner: List inside a Tx locks its rows and returns what List outside returns") - s := open(t) - for i := range 3 { - must(t, s.Repos().Experiments.Save(ctx, Numbered(i))) - } - outside, err := s.Repos().Experiments.List(ctx, "", 10) - must(t, err) - must(t, s.Tx(ctx, func(rp store.Repos) error { - inside, err := rp.Experiments.List(ctx, "", 10) - if err != nil { - return err - } - if len(inside) != len(outside) { - return fmt.Errorf("expected %d experiments inside the transaction, got %d", len(outside), len(inside)) - } - for i := range inside { - sameExperiment(t, inside[i].ID, inside[i], outside[i]) - } - return nil - })) - }) + t.Run("list-in-tx", func(t *testing.T) { listInTx(t, open) }) t.Run("canceled-context", func(t *testing.T) { t.Log("boundary: a canceled context fails the Tx and persists nothing") s := open(t) @@ -918,6 +897,32 @@ func tx(t *testing.T, open Opener) { // settle separates two stamps by more than a timestamptz microsecond so the // watermark tests order them deterministically on either store. +// listInTx: List inside a Tx locks its rows and returns what List outside +// returns. +func listInTx(t *testing.T, open Opener) { + ctx := context.Background() + t.Log("corner: List inside a Tx locks its rows and returns what List outside returns") + s := open(t) + for i := range 3 { + must(t, s.Repos().Experiments.Save(ctx, Numbered(i))) + } + outside, err := s.Repos().Experiments.List(ctx, "", 10) + must(t, err) + must(t, s.Tx(ctx, func(rp store.Repos) error { + inside, err := rp.Experiments.List(ctx, "", 10) + if err != nil { + return err + } + if len(inside) != len(outside) { + return fmt.Errorf("expected %d experiments inside the transaction, got %d", len(outside), len(inside)) + } + for i := range inside { + sameExperiment(t, inside[i].ID, inside[i], outside[i]) + } + return nil + })) +} + func settle() { time.Sleep(2 * time.Millisecond) } func lease(t *testing.T, open Opener) { From c2bf7d2df7f2d028579f596ab629c8d144db20b1 Mon Sep 17 00:00:00 2001 From: "randomizedcoder dave.seddon.ca@gmail.com" Date: Mon, 26 Oct 2026 07:45:17 -0700 Subject: [PATCH 24/27] feat(treatment): bind evidence to the sessions agents report Evidence now counts only telemetry records whose (agent_id, host_boot_id, collection_id) an agent reported over mTLS. The agent owns a per-process collection nonce: it writes it into the journal header, where a telemetry-only daemon reads and stamps it, and reports it at check-in as the now-required Capabilities.collection_id. The watchdog preserves the header's nonce. The controller records every session in xtcp_treatment_agent_sessions (last_seen refreshed at most every five minutes, pruned after eight days by housekeeping) and passes the population's sessions into every evidence window. analysis.sql (tcp-host-deltas-v2-bound) measures bound sockets only and returns unbound_sockets per host, so spoofed or stale records are shown to the reviewer instead of counted. Discovery proposes only enrolled candidates. The VM test gains TCP_CONTROLLER_BINDING_PASS: records copied under a fake boot and nonce must come back as unbound, and the safety analysis after it runs with them present. Co-Authored-By: Claude Opus 5.5 --- docs/status-tcp-controller.md | 70 ++- docs/tcp-controller.md | 41 +- gen/cpp/xtcp_treatment/v1/treatment.pb.cc | 480 +++++++++++------- gen/cpp/xtcp_treatment/v1/treatment.pb.h | 135 ++++- .../xtcp_treatment_journal/v1/journal.pb.cc | 163 ++++-- .../xtcp_treatment_journal/v1/journal.pb.h | 123 ++++- gen/dart/xtcp_treatment/v1/treatment.pb.dart | 32 ++ .../xtcp_treatment/v1/treatment.pbjson.dart | 19 +- .../xtcp_treatment_journal/v1/journal.pb.dart | 15 + .../v1/journal.pbjson.dart | 4 +- gen/go/xtcp_treatment/treatment.pb.go | 40 +- gen/go/xtcp_treatment/treatment_vtproto.pb.go | 70 +++ gen/go/xtcp_treatment_journal/journal.pb.go | 20 +- .../journal_vtproto.pb.go | 43 ++ gen/python/xtcp_treatment/v1/treatment_pb2.py | 54 +- .../xtcp_treatment/v1/treatment_pb2.pyi | 12 +- .../xtcp_treatment_journal/v1/journal_pb2.py | 12 +- .../xtcp_treatment_journal/v1/journal_pb2.pyi | 6 +- nix/tests/tcp-controller/default.nix | 1 + pkg/treatment/agent/agent.go | 5 +- pkg/treatment/agent/agent_test.go | 32 ++ pkg/treatment/attribution/index.go | 5 +- pkg/treatment/attribution/watch.go | 36 +- pkg/treatment/attribution/watch_test.go | 26 +- pkg/treatment/controller/analyze.go | 2 +- pkg/treatment/controller/bind.go | 43 ++ pkg/treatment/controller/bind_test.go | 82 +++ pkg/treatment/controller/discovery.go | 39 +- pkg/treatment/controller/discovery_test.go | 31 +- .../evidence/clickhouse/analysis.sql | 30 +- .../evidence/clickhouse/clickhouse.go | 29 +- .../evidence/clickhouse/clickhouse_test.go | 76 ++- pkg/treatment/controller/evidence/evidence.go | 20 +- .../controller/evidence/evidence_test.go | 24 + pkg/treatment/controller/helpers_test.go | 3 +- pkg/treatment/controller/maintenance.go | 5 +- .../controller/rpc_contracts_test.go | 3 +- .../controller/store/memory/memory.go | 56 ++ .../controller/store/postgres/agents.go | 54 +- .../controller/store/postgres/evidence.go | 17 +- .../store/postgres/heartbeat_test.go | 2 +- .../controller/store/postgres/schema.sql | 21 + pkg/treatment/controller/store/store.go | 13 + .../controller/store/storetest/storetest.go | 89 +++- pkg/treatment/internal/fixtures/fixtures.go | 3 + pkg/treatment/model.go | 13 + pkg/treatment/reconcile/journal_test.go | 2 + pkg/treatment/reconcile/journalpb.go | 6 +- pkg/treatment/reconcile/journalpb_test.go | 2 +- pkg/treatment/reconcile/reconcile.go | 35 +- pkg/treatment/reconcile/reconcile_test.go | 39 ++ pkg/treatment/reconcile/state.go | 5 +- pkg/treatment/validate.go | 26 +- pkg/treatment/validate_test.go | 5 +- pkg/treatment/wire/rules_test.go | 4 +- pkg/treatment/wire/wire.go | 8 +- pkg/treatment/wire/wire_test.go | 2 +- proto/xtcp_treatment/v1/treatment.proto | 8 + proto/xtcp_treatment_journal/v1/journal.proto | 4 + tools/tcp-controller-integration/main.go | 50 +- 60 files changed, 1810 insertions(+), 485 deletions(-) create mode 100644 pkg/treatment/controller/bind.go create mode 100644 pkg/treatment/controller/bind_test.go diff --git a/docs/status-tcp-controller.md b/docs/status-tcp-controller.md index e5d19a475..9487f882f 100644 --- a/docs/status-tcp-controller.md +++ b/docs/status-tcp-controller.md @@ -22,6 +22,11 @@ review being addressed. Items are listed in execution order. | 11 | scale: notification watermark, heartbeat split, leader lease | done | 91ccde4a | | 12 | docs: threat model, naming, SIGHUP, philosophy sentence | done | b6df3de9 | | L | lint: comprehensive tier clean on the new code; `lint-baseline` gate passes | done | c3981ff1 | +| F1 | follow-up: `Experiments.List` and `Get` in a fixed number of statements | done | 75093584, lint 5ccc671e | +| F2 | follow-up: help smoke for the four treatment binaries and `zstd-probe` | done | 926c2c74 | +| F3 | follow-up: reproducible index timings (`bench-tcp-controller-postgres-indexes`) | done | 30a21cdd | +| F4 | follow-up: generated coverage table (`coverage-table.md` from the unit target) | done | 30a21cdd | +| F5 | follow-up: evidence bound to `(agent_id, boot_id, collection_id)` sessions | done | this commit | Dual-mode agent (library + `cmd/xtcp2-treatment-agent` + `treatment` build tag in `cmd/xtcp2`, with an `attribution` journal reader for the untagged daemon) @@ -239,27 +244,58 @@ process. - The ClickHouse SQL test finds `clickhouse` on `PATH` (the unit check adds it to `nativeBuildInputs`) instead of `XTCP_CLICKHOUSE_TEST_BINARY`. -## TODO +## Follow-up notes (F1-F5) + +- F1: `loadMany` reads experiment rows, cohorts and phases with + `id = ANY($1)`, then every referenced evidence document and its hosts in + two more statements; `List` adds the ID lookup. `TestPostgresListStatements` + pins List at six statements and Get at five whatever the population, using + `pgtest.OpenCounting` (a pgx query tracer) rather than a hook in the store. +- F2: `ipmeta-bootstrap` was also missing from `cmdNames` but exits 1 on + `-help` (its `ContinueOnError` flag set returns `flag.ErrHelp` as an error), + so it stays out; see TODO. +- F3: the index timings come from the Nix PostgreSQL 18.6 fixture: median + 22.27 ms without indexes, 0.023 ms with them, for an old destination's + cooldown lookup. The fixture setup is shared with the unit and race targets. +- F4: per-package coverage, no `-coverpkg`, so `pkg/treatment` reads 54.7%: + its enum, `Clone` and `Equal` methods are exercised from other packages. +- F5: the agent owns a per-process nonce, writes it into the journal header + on its first pass after starting (`Reconciler.CollectionID`; a reconciler + without one, the watchdog, keeps the header's), and reports it as + `Capabilities.collection_id`, which is now required. `Agents.Observe` + upserts the session and refreshes `last_seen` at most every five minutes; + `Agents.Sessions` returns sessions overlapping a window and housekeeping + prunes those older than eight days. Every evidence window (analyze, + maintenance, discovery baseline, and the VM driver's own baseline) carries + the population's sessions widened by ten minutes; `analysis.sql` + (`tcp-host-deltas-v2-bound`) measures only bound sockets and returns + `unbound_sockets` per host. Discovery keeps only enrolled candidates and + skips a candidate with fewer than three. The evidence digest hashes + `unbound_sockets` only when non-zero, so evidence stored before the column + keeps its digest. Schema additions are `ADD COLUMN IF NOT EXISTS`. The VM + test gains `TCP_CONTROLLER_BINDING_PASS`: the driver copies a cohort host's + recent records under a fake boot and nonce and requires `Analyze` to count + them as unbound; the safety analysis that follows runs with them present. +- Proto regeneration: `regen-protos` now emits protoc-gen-go v1.36.12 and a + newer protoc for C++, while the committed output for the other protos is + older, so only the two treatment protos' generated files were committed and + the drift in the rest was reverted. -- Evidence binding (threat model): the warehouse query filters by location - and cohort agent IDs but does not check the boot ID or collection nonce a - record carries against the values the agent reported at check-in. Pass the - cohort's `(agent_id, boot_id)` pairs into `analysis.sql` and drop rows that - do not match; the nonce needs a check-in field first. +## TODO - The watchdog and `pkg/treatment/local` still link gRPC, through `pkg/xtcpnl` -> `gen/go/xtcp_flat_record`, whose Go package carries that proto's gRPC and gateway stubs. Generating the flat record's service stubs into a separate package would let the boundary test forbid gRPC for every goip user; `reconcile` is already gRPC-free. Boundary rule carries the TODO. -- `Experiments.List` loads each experiment with its own round trips - (cohort, phases, evidence). Fine for `inspect` limits; batch before any - fleet-wide listing path. -- `docs/benchmarks/tcp-controller/postgres-indexes.txt` still shows the JSONB - expression-index timings; refresh from `TestPostgresExperimentQueryIndexes` - on the Nix fixture. -- Coverage table in `docs/tcp-controller-testing.md` predates the split; regenerate. -- `nix/checks/cli-help-smoke.nix` keeps its own `cmdNames` list and does not - include the treatment binaries (`xtcp2-controller`, `xtcp2-controllerctl`, - `xtcp2-treatment-agent`, `xtcp2-treatmentctl`); add them when that list is - next touched. + Needs the service in its own `.proto` with a separate `go_package` (a buf + `PACKAGE_SAME_GO_PACKAGE` exception keeps the wire method names) and a + networked `regen-protos`. +- Evidence windows travel as ClickHouse URL parameters. At 10,000 agents the + `agents` and `sessions` arrays approach ClickHouse's default 1 MiB + `http_max_uri_size`; move them to external data in the request body before + fleets that large. +- `ipmeta-bootstrap -help` exits 1; return 0 on `flag.ErrHelp` and add it to + the help smoke list. +- Regenerate all protos with the pinned generators in one commit so the + committed `gen/` matches `regen-protos` again. diff --git a/docs/tcp-controller.md b/docs/tcp-controller.md index 5641fa06c..e4c571719 100644 --- a/docs/tcp-controller.md +++ b/docs/tcp-controller.md @@ -125,15 +125,32 @@ automatic rollback by faking harm, or mask real harm on a host it impersonates. What limits that today: evidence queries filter by the experiment's location and the frozen cohort's agent IDs, so records naming hosts outside the cohort -are never read; per-host sample minimums and the two-minute freshness window -bound how little data can swing a verdict; every expansion and completion is a -human review of typed evidence, and completed experiments only ever request -review on drift; evidence rows are immutable once stored, keyed by ID and -digest, and every decision is in the audit log with the operator's identity. -What is not done: binding a telemetry record to the boot ID and collection -nonce the same agent reported at check-in, which would let the warehouse -query reject records a host did not plausibly produce. That is the next -hardening step for this boundary and is tracked in the status document. +are never read. Within the cohort, evidence is bound to sessions: each agent +process draws a 16-byte collection nonce, writes it into its journal header +(where a telemetry-only daemon reads it) and reports it with its boot ID on +every mTLS check-in. The controller keeps every `(agent_id, boot_id, +collection_id)` it has heard (`xtcp_treatment_agent_sessions`, pruned after +eight days), passes the population's sessions for the window into the +analysis query, and measures only records stamped with one of them. Records +that name a cohort agent under any other boot or nonce are excluded from +every metric and counted per host as `unbound_sockets`, which `Analyze` and +`Inspect` return with the evidence a reviewer approves. Discovery proposes +only cohorts of enrolled agents, because only they have sessions. + +Per-host sample minimums and the two-minute freshness window bound how little +data can swing a verdict; every expansion and completion is a human review of +typed evidence, and completed experiments only ever request review on drift; +evidence rows are immutable once stored, keyed by ID and digest, and every +decision is in the audit log with the operator's identity. + +What binding does not stop: the nonce and boot ID travel in the same +unauthenticated telemetry they protect, so a principal that can read the +topic (or the warehouse) can copy a live session and forge records that bind. +It raises the bar from "know an agent ID" to "read the telemetry stream", and +it turns stale records from a previous boot or agent process into visible +unbound counts instead of evidence. A compromised host can still misreport +its own sockets; nothing short of signing records at the source would stop +that. Inside the controller, the advisory mutation lock and the housekeeping lease order work and bound cost; neither is an authorization mechanism. Request IDs @@ -502,7 +519,11 @@ observed congestion algorithm matches. Embedded, the index is rebuilt after every reconcile pass. Standalone, xtcp2 watches the journal directory (inotify on the atomic rename of `state.textproto`, a 30 s poll as fallback, SIGHUP to re-read now) and rebuilds it from the snapshot; a missing or invalid -snapshot clears attribution rather than stamping stale state. +snapshot clears attribution rather than stamping stale state. The collection +nonce is the agent's in both arrangements: the agent writes it into the +journal header on its first pass after starting, the watchdog preserves it, +and a journal without one (written by an older agent) is stamped without a +nonce, which the controller counts as unbound, and logged once. Location follows one rule in both arrangements, because the agent's location is certificate-bound and evidence queries filter by it. A daemon started diff --git a/gen/cpp/xtcp_treatment/v1/treatment.pb.cc b/gen/cpp/xtcp_treatment/v1/treatment.pb.cc index 75647b733..70b1a386d 100644 --- a/gen/cpp/xtcp_treatment/v1/treatment.pb.cc +++ b/gen/cpp/xtcp_treatment/v1/treatment.pb.cc @@ -258,11 +258,11 @@ constexpr HostEvidence::ParseTableT_ HostEvidence::InternalGenerateParseTable_(c { PROTOBUF_FIELD_OFFSET(HostEvidence, _impl_._has_bits_), 0, // no _extensions_ - 8, 56, // max_field_number, fast_idx_mask + 9, 120, // max_field_number, fast_idx_mask offsetof(ParseTableT_, field_lookup_table), - 4294967040, // skipmap + 4294966784, // skipmap offsetof(ParseTableT_, field_entries), - 8, // num_field_entries + 9, // num_field_entries 0, // num_aux_entries offsetof(ParseTableT_, field_names), // no aux_entries class_data, @@ -272,10 +272,7 @@ constexpr HostEvidence::ParseTableT_ HostEvidence::InternalGenerateParseTable_(c ::_pbi::TcParser::GetTable<::xtcp_treatment::v1::HostEvidence>(), // to_prefetch #endif // PROTOBUF_PREFETCH_PARSE_TABLE }, {{ - // bool collection_healthy = 8 [json_name = "collectionHealthy"]; - {::_pbi::TcParser::SingularVarintNoZag1(), - {64, 7, 0, - PROTOBUF_FIELD_OFFSET(HostEvidence, _impl_.collection_healthy_)}}, + {::_pbi::TcParser::MiniParse, {}}, // string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { {::_pbi::TcParser::FastUS1, {10, 0, 0, @@ -304,6 +301,20 @@ constexpr HostEvidence::ParseTableT_ HostEvidence::InternalGenerateParseTable_(c {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(HostEvidence, _impl_.exposed_sockets_), 6>(), {56, 6, 0, PROTOBUF_FIELD_OFFSET(HostEvidence, _impl_.exposed_sockets_)}}, + // bool collection_healthy = 8 [json_name = "collectionHealthy"]; + {::_pbi::TcParser::SingularVarintNoZag1(), + {64, 8, 0, + PROTOBUF_FIELD_OFFSET(HostEvidence, _impl_.collection_healthy_)}}, + // uint64 unbound_sockets = 9 [json_name = "unboundSockets"]; + {::_pbi::TcParser::SingularVarintNoZag1<::uint64_t, offsetof(HostEvidence, _impl_.unbound_sockets_), 7>(), + {72, 7, 0, + PROTOBUF_FIELD_OFFSET(HostEvidence, _impl_.unbound_sockets_)}}, + {::_pbi::TcParser::MiniParse, {}}, + {::_pbi::TcParser::MiniParse, {}}, + {::_pbi::TcParser::MiniParse, {}}, + {::_pbi::TcParser::MiniParse, {}}, + {::_pbi::TcParser::MiniParse, {}}, + {::_pbi::TcParser::MiniParse, {}}, }}, {{ 65535, 65535 }}, {{ @@ -322,7 +333,9 @@ constexpr HostEvidence::ParseTableT_ HostEvidence::InternalGenerateParseTable_(c // uint64 exposed_sockets = 7 [json_name = "exposedSockets"]; {PROTOBUF_FIELD_OFFSET(HostEvidence, _impl_.exposed_sockets_), _Internal::kHasBitsOffset + 6, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt64)}, // bool collection_healthy = 8 [json_name = "collectionHealthy"]; - {PROTOBUF_FIELD_OFFSET(HostEvidence, _impl_.collection_healthy_), _Internal::kHasBitsOffset + 7, 0, (0 | ::_fl::kFcOptional | ::_fl::kBool)}, + {PROTOBUF_FIELD_OFFSET(HostEvidence, _impl_.collection_healthy_), _Internal::kHasBitsOffset + 8, 0, (0 | ::_fl::kFcOptional | ::_fl::kBool)}, + // uint64 unbound_sockets = 9 [json_name = "unboundSockets"]; + {PROTOBUF_FIELD_OFFSET(HostEvidence, _impl_.unbound_sockets_), _Internal::kHasBitsOffset + 7, 0, (0 | ::_fl::kFcOptional | ::_fl::kUInt64)}, }}, // no aux_entries {{ @@ -346,6 +359,7 @@ inline constexpr HostEvidence::Impl_::Impl_( retransmission_ratio_{0}, rtt_p95_ms_{0}, exposed_sockets_{::uint64_t{0u}}, + unbound_sockets_{::uint64_t{0u}}, collection_healthy_{false} {} template @@ -452,11 +466,11 @@ constexpr Capabilities::ParseTableT_ Capabilities::InternalGenerateParseTable_(c { PROTOBUF_FIELD_OFFSET(Capabilities, _impl_._has_bits_), 0, // no _extensions_ - 5, 56, // max_field_number, fast_idx_mask + 6, 56, // max_field_number, fast_idx_mask offsetof(ParseTableT_, field_lookup_table), - 4294967264, // skipmap + 4294967232, // skipmap offsetof(ParseTableT_, field_entries), - 5, // num_field_entries + 6, // num_field_entries 0, // num_aux_entries offsetof(ParseTableT_, field_names), // no aux_entries class_data, @@ -480,14 +494,17 @@ constexpr Capabilities::ParseTableT_ Capabilities::InternalGenerateParseTable_(c {26, 0, 0, PROTOBUF_FIELD_OFFSET(Capabilities, _impl_.congestion_algorithms_)}}, // bool simple_host_routing = 4 [json_name = "simpleHostRouting"]; - {::_pbi::TcParser::SingularVarintNoZag1(), - {32, 3, 0, + {::_pbi::TcParser::SingularVarintNoZag1(), + {32, 4, 0, PROTOBUF_FIELD_OFFSET(Capabilities, _impl_.simple_host_routing_)}}, // bool actuator_enabled = 5 [json_name = "actuatorEnabled"]; - {::_pbi::TcParser::SingularVarintNoZag1(), - {40, 4, 0, + {::_pbi::TcParser::SingularVarintNoZag1(), + {40, 5, 0, PROTOBUF_FIELD_OFFSET(Capabilities, _impl_.actuator_enabled_)}}, - {::_pbi::TcParser::MiniParse, {}}, + // string collection_id = 6 [json_name = "collectionId", (.buf.validate.field) = { + {::_pbi::TcParser::FastUS1, + {50, 3, 0, + PROTOBUF_FIELD_OFFSET(Capabilities, _impl_.collection_id_)}}, {::_pbi::TcParser::MiniParse, {}}, }}, {{ 65535, 65535 @@ -499,17 +516,20 @@ constexpr Capabilities::ParseTableT_ Capabilities::InternalGenerateParseTable_(c // repeated string congestion_algorithms = 3 [json_name = "congestionAlgorithms", (.buf.validate.field) = { {PROTOBUF_FIELD_OFFSET(Capabilities, _impl_.congestion_algorithms_), _Internal::kHasBitsOffset + 0, 0, (0 | ::_fl::kFcRepeated | ::_fl::kUtf8String | ::_fl::kRepSString)}, // bool simple_host_routing = 4 [json_name = "simpleHostRouting"]; - {PROTOBUF_FIELD_OFFSET(Capabilities, _impl_.simple_host_routing_), _Internal::kHasBitsOffset + 3, 0, (0 | ::_fl::kFcOptional | ::_fl::kBool)}, + {PROTOBUF_FIELD_OFFSET(Capabilities, _impl_.simple_host_routing_), _Internal::kHasBitsOffset + 4, 0, (0 | ::_fl::kFcOptional | ::_fl::kBool)}, // bool actuator_enabled = 5 [json_name = "actuatorEnabled"]; - {PROTOBUF_FIELD_OFFSET(Capabilities, _impl_.actuator_enabled_), _Internal::kHasBitsOffset + 4, 0, (0 | ::_fl::kFcOptional | ::_fl::kBool)}, + {PROTOBUF_FIELD_OFFSET(Capabilities, _impl_.actuator_enabled_), _Internal::kHasBitsOffset + 5, 0, (0 | ::_fl::kFcOptional | ::_fl::kBool)}, + // string collection_id = 6 [json_name = "collectionId", (.buf.validate.field) = { + {PROTOBUF_FIELD_OFFSET(Capabilities, _impl_.collection_id_), _Internal::kHasBitsOffset + 3, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, }}, // no aux_entries {{ - "\36\7\16\25\0\0\0\0" + "\36\7\16\25\0\0\15\0" "xtcp_treatment.v1.Capabilities" "boot_id" "kernel_release" "congestion_algorithms" + "collection_id" }}, }; } @@ -529,6 +549,9 @@ inline constexpr Capabilities::Impl_::Impl_( kernel_release_( &::google::protobuf::internal::fixed_address_empty_string, ::_pbi::ConstantInitialized()), + collection_id_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), simple_host_routing_{false}, actuator_enabled_{false} {} @@ -2679,17 +2702,19 @@ const ::uint32_t 2, 0x081, // bitmap PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Capabilities, _impl_._has_bits_), - 8, // hasbit index offset + 9, // hasbit index offset PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Capabilities, _impl_.boot_id_), PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Capabilities, _impl_.kernel_release_), PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Capabilities, _impl_.congestion_algorithms_), PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Capabilities, _impl_.simple_host_routing_), PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Capabilities, _impl_.actuator_enabled_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Capabilities, _impl_.collection_id_), 1, 2, 0, - 3, 4, + 5, + 3, 0x081, // bitmap PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::ApplicationRecord, _impl_._has_bits_), 15, // hasbit index offset @@ -2732,7 +2757,7 @@ const ::uint32_t 3, 0x081, // bitmap PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::HostEvidence, _impl_._has_bits_), - 11, // hasbit index offset + 12, // hasbit index offset PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::HostEvidence, _impl_.agent_id_), PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::HostEvidence, _impl_.sockets_), PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::HostEvidence, _impl_.bytes_acked_), @@ -2741,6 +2766,7 @@ const ::uint32_t PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::HostEvidence, _impl_.rtt_p95_ms_), PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::HostEvidence, _impl_.exposed_sockets_), PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::HostEvidence, _impl_.collection_healthy_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::HostEvidence, _impl_.unbound_sockets_), 0, 1, 2, @@ -2748,6 +2774,7 @@ const ::uint32_t 4, 5, 6, + 8, 7, 0x081, // bitmap PROTOBUF_FIELD_OFFSET(::xtcp_treatment::v1::Evidence, _impl_._has_bits_), @@ -2848,13 +2875,13 @@ static const ::_pbi::MigrationSchema {30, sizeof(::xtcp_treatment::v1::Assignment)}, {45, sizeof(::xtcp_treatment::v1::DesiredState)}, {58, sizeof(::xtcp_treatment::v1::Capabilities)}, - {71, sizeof(::xtcp_treatment::v1::ApplicationRecord)}, - {98, sizeof(::xtcp_treatment::v1::LocalState)}, - {111, sizeof(::xtcp_treatment::v1::HostEvidence)}, - {130, sizeof(::xtcp_treatment::v1::Evidence)}, - {149, sizeof(::xtcp_treatment::v1::ExperimentPolicy)}, - {170, sizeof(::xtcp_treatment::v1::Phase)}, - {189, sizeof(::xtcp_treatment::v1::Experiment)}, + {73, sizeof(::xtcp_treatment::v1::ApplicationRecord)}, + {100, sizeof(::xtcp_treatment::v1::LocalState)}, + {113, sizeof(::xtcp_treatment::v1::HostEvidence)}, + {134, sizeof(::xtcp_treatment::v1::Evidence)}, + {153, sizeof(::xtcp_treatment::v1::ExperimentPolicy)}, + {174, sizeof(::xtcp_treatment::v1::Phase)}, + {193, sizeof(::xtcp_treatment::v1::Experiment)}, }; static const ::_pbi::MessageGlobalsBase* PROTOBUF_NONNULL const file_message_globals[] = { @@ -2937,164 +2964,166 @@ const char descriptor_table_protodef_xtcp_5ftreatment_2fv1_2ftreatment_2eproto[] "ed\022J\n\013assignments\030\004 \003(\0132\035.xtcp_treatment" ".v1.AssignmentB\t\272H\006\222\001\003\020\200 R\013assignments\022\?" "\n\tissued_at\030\005 \001(\0132\032.google.protobuf.Time" - "stampB\006\272H\003\310\001\001R\010issuedAt\"\243\002\n\014Capabilities" + "stampB\006\272H\003\310\001\001R\010issuedAt\"\337\002\n\014Capabilities" "\022B\n\007boot_id\030\001 \001(\tB)\272H&r$2\"^[a-zA-Z0-9][a" "-zA-Z0-9_.-]{0,127}$R\006bootId\022/\n\016kernel_r" "elease\030\002 \001(\tB\010\272H\005r\003\030\200\002R\rkernelRelease\022C\n" "\025congestion_algorithms\030\003 \003(\tB\016\272H\013\222\001\010\020@\"\004" "r\002\030\017R\024congestionAlgorithms\022.\n\023simple_hos" "t_routing\030\004 \001(\010R\021simpleHostRouting\022)\n\020ac" - "tuator_enabled\030\005 \001(\010R\017actuatorEnabled\"\250\005" - "\n\021ApplicationRecord\022E\n\nassignment\030\001 \001(\0132" - "\035.xtcp_treatment.v1.AssignmentB\006\272H\003\310\001\001R\n" - "assignment\022)\n\020desired_revision\030\002 \001(\004R\017de" - "siredRevision\022@\n\006status\030\003 \001(\0162\036.xtcp_tre" - "atment.v1.ApplyStatusB\010\272H\005\202\001\002\020\001R\006status\022" - ";\n\013received_at\030\004 \001(\0132\032.google.protobuf.T" - "imestampR\nreceivedAt\022=\n\014attempted_at\030\005 \001" - "(\0132\032.google.protobuf.TimestampR\013attempte" - "dAt\0229\n\napplied_at\030\006 \001(\0132\032.google.protobu" - "f.TimestampR\tappliedAt\022;\n\013verified_at\030\007 " - "\001(\0132\032.google.protobuf.TimestampR\nverifie" - "dAt\0229\n\nremoved_at\030\010 \001(\0132\032.google.protobu" - "f.TimestampR\tremovedAt\022\036\n\005error\030\t \001(\tB\010\272" - "H\005r\003\030\200 R\005error\0221\n\017installed_route\030\n \001(\014B" - "\010\272H\005z\003\030\200 R\016installedRoute\0224\n\021installed_b" - "oot_id\030\013 \001(\tB\010\272H\005r\003\030\200\001R\017installedBootId\022" - "\'\n\017expired_latched\030\014 \001(\010R\016expiredLatched" - "\"\261\002\n\nLocalState\0229\n\007desired\030\001 \001(\0132\037.xtcp_" - "treatment.v1.DesiredStateR\007desired\022+\n\021lo" - "cally_suspended\030\002 \001(\010R\020locallySuspended\022" - "5\n\021suspension_reason\030\003 \001(\tB\010\272H\005r\003\030\200\010R\020su" - "spensionReason\022I\n\007records\030\004 \003(\0132$.xtcp_t" - "reatment.v1.ApplicationRecordB\t\272H\006\222\001\003\020\200@" - "R\007records\0229\n\nupdated_at\030\005 \001(\0132\032.google.p" - "rotobuf.TimestampR\tupdatedAt\"\211\004\n\014HostEvi" - "dence\022D\n\010agent_id\030\001 \001(\tB)\272H&r$2\"^[a-zA-Z" - "0-9][a-zA-Z0-9_.-]{0,127}$R\007agentId\022\030\n\007s" - "ockets\030\002 \001(\004R\007sockets\022\037\n\013bytes_acked\030\003 \001" - "(\004R\nbytesAcked\0221\n\013goodput_bps\030\004 \001(\001B\020\272H\r" - "\022\013)\000\000\000\000\000\000\000\000@\001R\ngoodputBps\022C\n\024retransmiss" - "ion_ratio\030\005 \001(\001B\020\272H\r\022\013)\000\000\000\000\000\000\000\000@\001R\023retra" - "nsmissionRatio\022.\n\nrtt_p95_ms\030\006 \001(\001B\020\272H\r\022" - "\013)\000\000\000\000\000\000\000\000@\001R\010rttP95Ms\022\'\n\017exposed_socket" - "s\030\007 \001(\004R\016exposedSockets\022-\n\022collection_he" - "althy\030\010 \001(\010R\021collectionHealthy:x\272Hu\032s\n$h" - "ost_evidence.exposed_within_sockets\022%exp" - "osed_sockets cannot exceed sockets\032$this" - ".exposed_sockets <= this.sockets\"\274\005\n\010Evi" - "dence\0229\n\002id\030\001 \001(\tB)\272H&r$2\"^[a-zA-Z0-9][a" - "-zA-Z0-9_.-]{0,127}$R\002id\022C\n\013analyzed_at\030" - "\002 \001(\0132\032.google.protobuf.TimestampB\006\272H\003\310\001" - "\001R\nanalyzedAt\022E\n\014window_start\030\003 \001(\0132\032.go" - "ogle.protobuf.TimestampB\006\272H\003\310\001\001R\013windowS" - "tart\022A\n\nwindow_end\030\004 \001(\0132\032.google.protob" - "uf.TimestampB\006\272H\003\310\001\001R\twindowEnd\022/\n\rquery" - "_version\030\005 \001(\tB\n\272H\007r\005\020\001\030\200\002R\014queryVersion" - "\0227\n\022enrichment_version\030\006 \001(\tB\010\272H\005r\003\030\200\002R\021" - "enrichmentVersion\022B\n\005hosts\030\007 \003(\0132\037.xtcp_" - "treatment.v1.HostEvidenceB\013\272H\010\222\001\005\010\001\020\220NR\005" - "hosts\022>\n\017artifact_sha256\030\010 \001(\tB\025\272H\022r\0202\016^" - "[0-9a-f]{64}$R\016artifactSha256:\267\001\272H\263\001\032\260\001\n" - "\021evidence.interval\022Owindow_end must foll" - "ow window_start and analyzed_at must not" - " precede window_end\032Jthis.window_end > t" - "his.window_start && this.analyzed_at >= " - "this.window_end\"\354\006\n\020ExperimentPolicy\022]\n\023" - "minimum_observation\030\001 \001(\0132\031.google.proto" - "buf.DurationB\021\272H\016\252\001\010\"\004\010\200\365$*\000\310\001\001R\022minimum" - "Observation\022Q\n\rmaximum_phase\030\002 \001(\0132\031.goo" - "gle.protobuf.DurationB\021\272H\016\252\001\010\"\004\010\200\365$*\000\310\001\001" - "R\014maximumPhase\022@\n\030minimum_sockets_per_ho" - "st\030\003 \001(\004B\007\272H\0042\002 \000R\025minimumSocketsPerHost" - "\022<\n\026minimum_bytes_per_host\030\004 \001(\004B\007\272H\0042\002 " - "\000R\023minimumBytesPerHost\022>\n\025minimum_treate" - "d_hosts\030\005 \001(\rB\n\272H\007*\005\030\220N(\002R\023minimumTreate" - "dHosts\022J\n\023minimum_improvement\030\006 \001(\001B\031\272H\026" - "\022\024\031\000\000\000\000\000\000\360\?)\000\000\000\000\000\000\000\000@\001R\022minimumImproveme" - "nt\022a\n\037maximum_retransmission_increase\030\007 " - "\001(\001B\031\272H\026\022\024\031\000\000\000\000\000\000\360\?)\000\000\000\000\000\000\000\000@\001R\035maximumR" - "etransmissionIncrease\022K\n\024maximum_rtt_inc" - "rease\030\010 \001(\001B\031\272H\026\022\024\031\000\000\000\000\000\000\360\?)\000\000\000\000\000\000\000\000@\001R\022" - "maximumRttIncrease\022^\n\024maximum_evidence_a" - "ge\030\t \001(\0132\031.google.protobuf.DurationB\021\272H\016" - "\252\001\010\"\004\010\200\365$*\000\310\001\001R\022maximumEvidenceAge:\211\001\272H\205" - "\001\032\202\001\n\037policy.observation_within_phase\022/m" - "inimum_observation cannot exceed maximum" - "_phase\032.this.minimum_observation <= this" - ".maximum_phase\"\301\003\n\005Phase\022-\n\014target_hosts" - "\030\001 \001(\rB\n\272H\007*\005\030\220N(\001R\013targetHosts\0227\n\tissue" - "d_at\030\002 \001(\0132\032.google.protobuf.TimestampR\010" - "issuedAt\022P\n\026observation_started_at\030\003 \001(\013" - "2\032.google.protobuf.TimestampR\024observatio" - "nStartedAt\022=\n\014evaluated_at\030\004 \001(\0132\032.googl" - "e.protobuf.TimestampR\013evaluatedAt\0227\n\010evi" - "dence\030\005 \001(\0132\033.xtcp_treatment.v1.Evidence" - "R\010evidence\022>\n\007outcome\030\006 \001(\0162\032.xtcp_treat" - "ment.v1.OutcomeB\010\272H\005\202\001\002\020\001R\007outcome\022$\n\010re" - "viewer\030\007 \001(\tB\010\272H\005r\003\030\200\001R\010reviewer\022 \n\006reas" - "on\030\010 \001(\tB\010\272H\005r\003\030\200 R\006reason\"\210\007\n\nExperimen" - "t\0229\n\002id\030\001 \001(\tB)\272H&r$2\"^[a-zA-Z0-9][a-zA-" - "Z0-9_.-]{0,127}$R\002id\022E\n\010location\030\002 \001(\tB)" + "tuator_enabled\030\005 \001(\010R\017actuatorEnabled\022:\n" + "\rcollection_id\030\006 \001(\tB\025\272H\022r\0202\016^[0-9a-f]{3" + "2}$R\014collectionId\"\250\005\n\021ApplicationRecord\022" + "E\n\nassignment\030\001 \001(\0132\035.xtcp_treatment.v1." + "AssignmentB\006\272H\003\310\001\001R\nassignment\022)\n\020desire" + "d_revision\030\002 \001(\004R\017desiredRevision\022@\n\006sta" + "tus\030\003 \001(\0162\036.xtcp_treatment.v1.ApplyStatu" + "sB\010\272H\005\202\001\002\020\001R\006status\022;\n\013received_at\030\004 \001(\013" + "2\032.google.protobuf.TimestampR\nreceivedAt" + "\022=\n\014attempted_at\030\005 \001(\0132\032.google.protobuf" + ".TimestampR\013attemptedAt\0229\n\napplied_at\030\006 " + "\001(\0132\032.google.protobuf.TimestampR\tapplied" + "At\022;\n\013verified_at\030\007 \001(\0132\032.google.protobu" + "f.TimestampR\nverifiedAt\0229\n\nremoved_at\030\010 " + "\001(\0132\032.google.protobuf.TimestampR\tremoved" + "At\022\036\n\005error\030\t \001(\tB\010\272H\005r\003\030\200 R\005error\0221\n\017in" + "stalled_route\030\n \001(\014B\010\272H\005z\003\030\200 R\016installed" + "Route\0224\n\021installed_boot_id\030\013 \001(\tB\010\272H\005r\003\030" + "\200\001R\017installedBootId\022\'\n\017expired_latched\030\014" + " \001(\010R\016expiredLatched\"\261\002\n\nLocalState\0229\n\007d" + "esired\030\001 \001(\0132\037.xtcp_treatment.v1.Desired" + "StateR\007desired\022+\n\021locally_suspended\030\002 \001(" + "\010R\020locallySuspended\0225\n\021suspension_reason" + "\030\003 \001(\tB\010\272H\005r\003\030\200\010R\020suspensionReason\022I\n\007re" + "cords\030\004 \003(\0132$.xtcp_treatment.v1.Applicat" + "ionRecordB\t\272H\006\222\001\003\020\200@R\007records\0229\n\nupdated" + "_at\030\005 \001(\0132\032.google.protobuf.TimestampR\tu" + "pdatedAt\"\262\004\n\014HostEvidence\022D\n\010agent_id\030\001 " + "\001(\tB)\272H&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0" + ",127}$R\007agentId\022\030\n\007sockets\030\002 \001(\004R\007socket" + "s\022\037\n\013bytes_acked\030\003 \001(\004R\nbytesAcked\0221\n\013go" + "odput_bps\030\004 \001(\001B\020\272H\r\022\013)\000\000\000\000\000\000\000\000@\001R\ngoodp" + "utBps\022C\n\024retransmission_ratio\030\005 \001(\001B\020\272H\r" + "\022\013)\000\000\000\000\000\000\000\000@\001R\023retransmissionRatio\022.\n\nrt" + "t_p95_ms\030\006 \001(\001B\020\272H\r\022\013)\000\000\000\000\000\000\000\000@\001R\010rttP95" + "Ms\022\'\n\017exposed_sockets\030\007 \001(\004R\016exposedSock" + "ets\022-\n\022collection_healthy\030\010 \001(\010R\021collect" + "ionHealthy\022\'\n\017unbound_sockets\030\t \001(\004R\016unb" + "oundSockets:x\272Hu\032s\n$host_evidence.expose" + "d_within_sockets\022%exposed_sockets cannot" + " exceed sockets\032$this.exposed_sockets <=" + " this.sockets\"\274\005\n\010Evidence\0229\n\002id\030\001 \001(\tB)" "\272H&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}" - "$R\010location\022*\n\nhypothesis\030\003 \001(\tB\n\272H\007r\005\020\001" - "\030\200@R\nhypothesis\022B\n\ttreatment\030\004 \001(\0132\034.xtc" - "p_treatment.v1.TreatmentB\006\272H\003\310\001\001R\ttreatm" - "ent\022C\n\006policy\030\005 \001(\0132#.xtcp_treatment.v1." - "ExperimentPolicyB\006\272H\003\310\001\001R\006policy\022\?\n\010base" - "line\030\006 \001(\0132\033.xtcp_treatment.v1.EvidenceB" - "\006\272H\003\310\001\001R\010baseline\022X\n\014cohort_order\030\007 \003(\tB" - "5\272H2\222\001/\010\003\020\220N\030\001\"&r$2\"^[a-zA-Z0-9][a-zA-Z0" - "-9_.-]{0,127}$R\013cohortOrder\022:\n\006phases\030\010 " - "\003(\0132\030.xtcp_treatment.v1.PhaseB\010\272H\005\222\001\002\020@R" - "\006phases\022E\n\006status\030\t \001(\0162#.xtcp_treatment" - ".v1.ExperimentStatusB\010\272H\005\202\001\002\020\001R\006status\022\032" - "\n\010revision\030\n \001(\004R\010revision\022;\n\013proposed_a" - "t\030\013 \001(\0132\032.google.protobuf.TimestampR\npro" - "posedAt\022D\n\017latest_evidence\030\014 \001(\0132\033.xtcp_" - "treatment.v1.EvidenceR\016latestEvidence\022K\n" - "\016latest_outcome\030\r \001(\0162\032.xtcp_treatment.v" - "1.OutcomeB\010\272H\005\202\001\002\020\001R\rlatestOutcome\0229\n\nup" - "dated_at\030\016 \001(\0132\032.google.protobuf.Timesta" - "mpR\tupdatedAt*\?\n\004Role\022\024\n\020ROLE_UNSPECIFIE" - "D\020\000\022\016\n\nROLE_AGENT\020\001\022\021\n\rROLE_OPERATOR\020\002*\244" - "\001\n\016AssignmentMode\022\037\n\033ASSIGNMENT_MODE_UNS" - "PECIFIED\020\000\022\031\n\025ASSIGNMENT_MODE_TRIAL\020\001\022\032\n" - "\026ASSIGNMENT_MODE_STABLE\020\002\022\035\n\031ASSIGNMENT_" - "MODE_SUSPENDED\020\003\022\033\n\027ASSIGNMENT_MODE_RETI" - "RED\020\004*\255\001\n\013ApplyStatus\022\034\n\030APPLY_STATUS_UN" - "SPECIFIED\020\000\022\030\n\024APPLY_STATUS_PENDING\020\001\022\031\n" - "\025APPLY_STATUS_VERIFIED\020\002\022\030\n\024APPLY_STATUS" - "_REMOVED\020\003\022\030\n\024APPLY_STATUS_EXPIRED\020\004\022\027\n\023" - "APPLY_STATUS_FAILED\020\005*\244\004\n\007Outcome\022\027\n\023OUT" - "COME_UNSPECIFIED\020\000\022\024\n\020OUTCOME_BASELINE\020\001" - "\022 \n\034OUTCOME_AWAITING_APPLICATION\020\002\022\035\n\031OU" - "TCOME_AWAITING_EXPOSURE\020\003\022!\n\035OUTCOME_INS" - "UFFICIENT_DURATION\020\004\022\035\n\031OUTCOME_INSUFFIC" - "IENT_DATA\020\005\022!\n\035OUTCOME_INSUFFICIENT_CONT" - "ROLS\020\006\022\021\n\rOUTCOME_STALE\020\007\022\027\n\023OUTCOME_UNA" - "VAILABLE\020\010\022\030\n\024OUTCOME_INCONCLUSIVE\020\t\022\027\n\023" - "OUTCOME_SAFETY_ONLY\020\n\022\030\n\024OUTCOME_FINAL_S" - "AFETY\020\013\022\026\n\022OUTCOME_BENEFICIAL\020\014\022\023\n\017OUTCO" - "ME_HARMFUL\020\r\022\023\n\017OUTCOME_EXPIRED\020\016\022\033\n\027OUT" - "COME_STABLE_OBSERVED\020\017\022&\n\"OUTCOME_STABLE" - "_EVIDENCE_INCOMPLETE\020\020\022-\n)OUTCOME_STABLE" - "_REGRESSION_REVIEW_REQUIRED\020\021\022\025\n\021OUTCOME" - "_OBSERVING\020\022*\225\002\n\020ExperimentStatus\022!\n\035EXP" - "ERIMENT_STATUS_UNSPECIFIED\020\000\022\036\n\032EXPERIME" - "NT_STATUS_PROPOSED\020\001\022\035\n\031EXPERIMENT_STATU" - "S_RUNNING\020\002\022\034\n\030EXPERIMENT_STATUS_PAUSED\020" - "\003\022\037\n\033EXPERIMENT_STATUS_SUSPENDED\020\004\022\036\n\032EX" - "PERIMENT_STATUS_COMPLETE\020\005\022\035\n\031EXPERIMENT" - "_STATUS_RETIRED\020\006\022!\n\035EXPERIMENT_STATUS_R" - "OLLED_BACK\020\007:M\n\004role\022\036.google.protobuf.M" - "ethodOptions\030\272\216\003 \001(\0162\027.xtcp_treatment.v1" - ".RoleR\004roleB\300\001\n\025com.xtcp_treatment.v1B\016T" - "reatmentProtoP\001Z6github.com/randomizedco" - "der/xtcp2/gen/go/xtcp_treatment\242\002\003XXX\252\002\020" - "XtcpTreatment.V1\312\002\020XtcpTreatment\\V1\342\002\034Xt" - "cpTreatment\\V1\\GPBMetadata\352\002\021XtcpTreatme" - "nt::V1b\006proto3" + "$R\002id\022C\n\013analyzed_at\030\002 \001(\0132\032.google.prot" + "obuf.TimestampB\006\272H\003\310\001\001R\nanalyzedAt\022E\n\014wi" + "ndow_start\030\003 \001(\0132\032.google.protobuf.Times" + "tampB\006\272H\003\310\001\001R\013windowStart\022A\n\nwindow_end\030" + "\004 \001(\0132\032.google.protobuf.TimestampB\006\272H\003\310\001" + "\001R\twindowEnd\022/\n\rquery_version\030\005 \001(\tB\n\272H\007" + "r\005\020\001\030\200\002R\014queryVersion\0227\n\022enrichment_vers" + "ion\030\006 \001(\tB\010\272H\005r\003\030\200\002R\021enrichmentVersion\022B" + "\n\005hosts\030\007 \003(\0132\037.xtcp_treatment.v1.HostEv" + "idenceB\013\272H\010\222\001\005\010\001\020\220NR\005hosts\022>\n\017artifact_s" + "ha256\030\010 \001(\tB\025\272H\022r\0202\016^[0-9a-f]{64}$R\016arti" + "factSha256:\267\001\272H\263\001\032\260\001\n\021evidence.interval\022" + "Owindow_end must follow window_start and" + " analyzed_at must not precede window_end" + "\032Jthis.window_end > this.window_start &&" + " this.analyzed_at >= this.window_end\"\354\006\n" + "\020ExperimentPolicy\022]\n\023minimum_observation" + "\030\001 \001(\0132\031.google.protobuf.DurationB\021\272H\016\252\001" + "\010\"\004\010\200\365$*\000\310\001\001R\022minimumObservation\022Q\n\rmaxi" + "mum_phase\030\002 \001(\0132\031.google.protobuf.Durati" + "onB\021\272H\016\252\001\010\"\004\010\200\365$*\000\310\001\001R\014maximumPhase\022@\n\030m" + "inimum_sockets_per_host\030\003 \001(\004B\007\272H\0042\002 \000R\025" + "minimumSocketsPerHost\022<\n\026minimum_bytes_p" + "er_host\030\004 \001(\004B\007\272H\0042\002 \000R\023minimumBytesPerH" + "ost\022>\n\025minimum_treated_hosts\030\005 \001(\rB\n\272H\007*" + "\005\030\220N(\002R\023minimumTreatedHosts\022J\n\023minimum_i" + "mprovement\030\006 \001(\001B\031\272H\026\022\024\031\000\000\000\000\000\000\360\?)\000\000\000\000\000\000\000" + "\000@\001R\022minimumImprovement\022a\n\037maximum_retra" + "nsmission_increase\030\007 \001(\001B\031\272H\026\022\024\031\000\000\000\000\000\000\360\?" + ")\000\000\000\000\000\000\000\000@\001R\035maximumRetransmissionIncrea" + "se\022K\n\024maximum_rtt_increase\030\010 \001(\001B\031\272H\026\022\024\031" + "\000\000\000\000\000\000\360\?)\000\000\000\000\000\000\000\000@\001R\022maximumRttIncrease\022" + "^\n\024maximum_evidence_age\030\t \001(\0132\031.google.p" + "rotobuf.DurationB\021\272H\016\252\001\010\"\004\010\200\365$*\000\310\001\001R\022max" + "imumEvidenceAge:\211\001\272H\205\001\032\202\001\n\037policy.observ" + "ation_within_phase\022/minimum_observation " + "cannot exceed maximum_phase\032.this.minimu" + "m_observation <= this.maximum_phase\"\301\003\n\005" + "Phase\022-\n\014target_hosts\030\001 \001(\rB\n\272H\007*\005\030\220N(\001R" + "\013targetHosts\0227\n\tissued_at\030\002 \001(\0132\032.google" + ".protobuf.TimestampR\010issuedAt\022P\n\026observa" + "tion_started_at\030\003 \001(\0132\032.google.protobuf." + "TimestampR\024observationStartedAt\022=\n\014evalu" + "ated_at\030\004 \001(\0132\032.google.protobuf.Timestam" + "pR\013evaluatedAt\0227\n\010evidence\030\005 \001(\0132\033.xtcp_" + "treatment.v1.EvidenceR\010evidence\022>\n\007outco" + "me\030\006 \001(\0162\032.xtcp_treatment.v1.OutcomeB\010\272H" + "\005\202\001\002\020\001R\007outcome\022$\n\010reviewer\030\007 \001(\tB\010\272H\005r\003" + "\030\200\001R\010reviewer\022 \n\006reason\030\010 \001(\tB\010\272H\005r\003\030\200 R" + "\006reason\"\210\007\n\nExperiment\0229\n\002id\030\001 \001(\tB)\272H&r" + "$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\002i" + "d\022E\n\010location\030\002 \001(\tB)\272H&r$2\"^[a-zA-Z0-9]" + "[a-zA-Z0-9_.-]{0,127}$R\010location\022*\n\nhypo" + "thesis\030\003 \001(\tB\n\272H\007r\005\020\001\030\200@R\nhypothesis\022B\n\t" + "treatment\030\004 \001(\0132\034.xtcp_treatment.v1.Trea" + "tmentB\006\272H\003\310\001\001R\ttreatment\022C\n\006policy\030\005 \001(\013" + "2#.xtcp_treatment.v1.ExperimentPolicyB\006\272" + "H\003\310\001\001R\006policy\022\?\n\010baseline\030\006 \001(\0132\033.xtcp_t" + "reatment.v1.EvidenceB\006\272H\003\310\001\001R\010baseline\022X" + "\n\014cohort_order\030\007 \003(\tB5\272H2\222\001/\010\003\020\220N\030\001\"&r$2" + "\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\013coh" + "ortOrder\022:\n\006phases\030\010 \003(\0132\030.xtcp_treatmen" + "t.v1.PhaseB\010\272H\005\222\001\002\020@R\006phases\022E\n\006status\030\t" + " \001(\0162#.xtcp_treatment.v1.ExperimentStatu" + "sB\010\272H\005\202\001\002\020\001R\006status\022\032\n\010revision\030\n \001(\004R\010r" + "evision\022;\n\013proposed_at\030\013 \001(\0132\032.google.pr" + "otobuf.TimestampR\nproposedAt\022D\n\017latest_e" + "vidence\030\014 \001(\0132\033.xtcp_treatment.v1.Eviden" + "ceR\016latestEvidence\022K\n\016latest_outcome\030\r \001" + "(\0162\032.xtcp_treatment.v1.OutcomeB\010\272H\005\202\001\002\020\001" + "R\rlatestOutcome\0229\n\nupdated_at\030\016 \001(\0132\032.go" + "ogle.protobuf.TimestampR\tupdatedAt*\?\n\004Ro" + "le\022\024\n\020ROLE_UNSPECIFIED\020\000\022\016\n\nROLE_AGENT\020\001" + "\022\021\n\rROLE_OPERATOR\020\002*\244\001\n\016AssignmentMode\022\037" + "\n\033ASSIGNMENT_MODE_UNSPECIFIED\020\000\022\031\n\025ASSIG" + "NMENT_MODE_TRIAL\020\001\022\032\n\026ASSIGNMENT_MODE_ST" + "ABLE\020\002\022\035\n\031ASSIGNMENT_MODE_SUSPENDED\020\003\022\033\n" + "\027ASSIGNMENT_MODE_RETIRED\020\004*\255\001\n\013ApplyStat" + "us\022\034\n\030APPLY_STATUS_UNSPECIFIED\020\000\022\030\n\024APPL" + "Y_STATUS_PENDING\020\001\022\031\n\025APPLY_STATUS_VERIF" + "IED\020\002\022\030\n\024APPLY_STATUS_REMOVED\020\003\022\030\n\024APPLY" + "_STATUS_EXPIRED\020\004\022\027\n\023APPLY_STATUS_FAILED" + "\020\005*\244\004\n\007Outcome\022\027\n\023OUTCOME_UNSPECIFIED\020\000\022" + "\024\n\020OUTCOME_BASELINE\020\001\022 \n\034OUTCOME_AWAITIN" + "G_APPLICATION\020\002\022\035\n\031OUTCOME_AWAITING_EXPO" + "SURE\020\003\022!\n\035OUTCOME_INSUFFICIENT_DURATION\020" + "\004\022\035\n\031OUTCOME_INSUFFICIENT_DATA\020\005\022!\n\035OUTC" + "OME_INSUFFICIENT_CONTROLS\020\006\022\021\n\rOUTCOME_S" + "TALE\020\007\022\027\n\023OUTCOME_UNAVAILABLE\020\010\022\030\n\024OUTCO" + "ME_INCONCLUSIVE\020\t\022\027\n\023OUTCOME_SAFETY_ONLY" + "\020\n\022\030\n\024OUTCOME_FINAL_SAFETY\020\013\022\026\n\022OUTCOME_" + "BENEFICIAL\020\014\022\023\n\017OUTCOME_HARMFUL\020\r\022\023\n\017OUT" + "COME_EXPIRED\020\016\022\033\n\027OUTCOME_STABLE_OBSERVE" + "D\020\017\022&\n\"OUTCOME_STABLE_EVIDENCE_INCOMPLET" + "E\020\020\022-\n)OUTCOME_STABLE_REGRESSION_REVIEW_" + "REQUIRED\020\021\022\025\n\021OUTCOME_OBSERVING\020\022*\225\002\n\020Ex" + "perimentStatus\022!\n\035EXPERIMENT_STATUS_UNSP" + "ECIFIED\020\000\022\036\n\032EXPERIMENT_STATUS_PROPOSED\020" + "\001\022\035\n\031EXPERIMENT_STATUS_RUNNING\020\002\022\034\n\030EXPE" + "RIMENT_STATUS_PAUSED\020\003\022\037\n\033EXPERIMENT_STA" + "TUS_SUSPENDED\020\004\022\036\n\032EXPERIMENT_STATUS_COM" + "PLETE\020\005\022\035\n\031EXPERIMENT_STATUS_RETIRED\020\006\022!" + "\n\035EXPERIMENT_STATUS_ROLLED_BACK\020\007:M\n\004rol" + "e\022\036.google.protobuf.MethodOptions\030\272\216\003 \001(" + "\0162\027.xtcp_treatment.v1.RoleR\004roleB\300\001\n\025com" + ".xtcp_treatment.v1B\016TreatmentProtoP\001Z6gi" + "thub.com/randomizedcoder/xtcp2/gen/go/xt" + "cp_treatment\242\002\003XXX\252\002\020XtcpTreatment.V1\312\002\020" + "XtcpTreatment\\V1\342\002\034XtcpTreatment\\V1\\GPBM" + "etadata\352\002\021XtcpTreatment::V1b\006proto3" }; static const ::_pbi::DescriptorTable* PROTOBUF_NONNULL const descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto_deps[4] = { @@ -3107,7 +3136,7 @@ static ::absl::once_flag descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2epr PROTOBUF_CONSTINIT const ::_pbi::DescriptorTable descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto = { false, false, - 8854, + 8955, descriptor_table_protodef_xtcp_5ftreatment_2fv1_2ftreatment_2eproto, "xtcp_treatment/v1/treatment.proto", &descriptor_table_xtcp_5ftreatment_2fv1_2ftreatment_2eproto_once, @@ -4568,7 +4597,8 @@ PROTOBUF_NDEBUG_INLINE Capabilities::Impl_::Impl_( } , boot_id_(arena, from.boot_id_), - kernel_release_(arena, from.kernel_release_) {} + kernel_release_(arena, from.kernel_release_), + collection_id_(arena, from.collection_id_) {} Capabilities::Capabilities( ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, @@ -4603,7 +4633,8 @@ PROTOBUF_NDEBUG_INLINE Capabilities::Impl_::Impl_( } , boot_id_(arena), - kernel_release_(arena) {} + kernel_release_(arena), + collection_id_(arena) {} inline void Capabilities::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { new (&_impl_) Impl_(internal_visibility(), arena); @@ -4627,6 +4658,7 @@ inline void Capabilities::SharedDtor(MessageLite& self) { ABSL_DCHECK(this_.GetArena() == nullptr); this_._impl_.boot_id_.Destroy(); this_._impl_.kernel_release_.Destroy(); + this_._impl_.collection_id_.Destroy(); this_._impl_.~Impl_(); } @@ -4663,7 +4695,7 @@ PROTOBUF_NOINLINE void Capabilities::Clear() { ::uint32_t cached_has_bits [[maybe_unused]] = 0; cached_has_bits = this_._impl_._has_bits_[0]; - if (BatchCheckHasBit(cached_has_bits, 0x00000007U)) { + if (BatchCheckHasBit(cached_has_bits, 0x0000000fU)) { if (CheckHasBit(cached_has_bits, 0x00000001U)) { _impl_.congestion_algorithms_.Clear(); } @@ -4673,6 +4705,9 @@ PROTOBUF_NOINLINE void Capabilities::Clear() { if (CheckHasBit(cached_has_bits, 0x00000004U)) { this_._impl_.kernel_release_.ClearNonDefaultToEmpty(); } + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + this_._impl_.collection_id_.ClearNonDefaultToEmpty(); + } } ::memset(&this_._impl_.simple_host_routing_, 0, static_cast<::size_t>( @@ -4733,7 +4768,7 @@ ::uint8_t* PROTOBUF_NONNULL Capabilities::_InternalSerialize( } // bool simple_host_routing = 4 [json_name = "simpleHostRouting"]; - if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (CheckHasBit(cached_has_bits, 0x00000010U)) { if (this_._internal_simple_host_routing() != 0) { target = stream->EnsureSpace(target); target = ::_pbi::WireFormatLite::WriteBoolToArray( @@ -4742,7 +4777,7 @@ ::uint8_t* PROTOBUF_NONNULL Capabilities::_InternalSerialize( } // bool actuator_enabled = 5 [json_name = "actuatorEnabled"]; - if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (CheckHasBit(cached_has_bits, 0x00000020U)) { if (this_._internal_actuator_enabled() != 0) { target = stream->EnsureSpace(target); target = ::_pbi::WireFormatLite::WriteBoolToArray( @@ -4750,6 +4785,16 @@ ::uint8_t* PROTOBUF_NONNULL Capabilities::_InternalSerialize( } } + // string collection_id = 6 [json_name = "collectionId", (.buf.validate.field) = { + if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (!this_._internal_collection_id().empty()) { + const ::std::string& _s = this_._internal_collection_id(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment.v1.Capabilities.collection_id"); + target = stream->WriteStringMaybeAliased(6, _s, target); + } + } + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( @@ -4773,7 +4818,7 @@ ::size_t Capabilities::ByteSizeLong() const { ::_pbi::Prefetch5LinesFrom7Lines(&this_); cached_has_bits = this_._impl_._has_bits_[0]; - if (BatchCheckHasBit(cached_has_bits, 0x0000001fU)) { + if (BatchCheckHasBit(cached_has_bits, 0x0000003fU)) { // repeated string congestion_algorithms = 3 [json_name = "congestionAlgorithms", (.buf.validate.field) = { if (CheckHasBit(cached_has_bits, 0x00000001U)) { total_size += @@ -4797,14 +4842,21 @@ ::size_t Capabilities::ByteSizeLong() const { this_._internal_kernel_release()); } } - // bool simple_host_routing = 4 [json_name = "simpleHostRouting"]; + // string collection_id = 6 [json_name = "collectionId", (.buf.validate.field) = { if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (!this_._internal_collection_id().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_collection_id()); + } + } + // bool simple_host_routing = 4 [json_name = "simpleHostRouting"]; + if (CheckHasBit(cached_has_bits, 0x00000010U)) { if (this_._internal_simple_host_routing() != 0) { total_size += 2; } } // bool actuator_enabled = 5 [json_name = "actuatorEnabled"]; - if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (CheckHasBit(cached_has_bits, 0x00000020U)) { if (this_._internal_actuator_enabled() != 0) { total_size += 2; } @@ -4828,7 +4880,7 @@ void Capabilities::MergeImpl(::google::protobuf::MessageLite& to_msg, (void)cached_has_bits; cached_has_bits = from._impl_._has_bits_[0]; - if (BatchCheckHasBit(cached_has_bits, 0x0000001fU)) { + if (BatchCheckHasBit(cached_has_bits, 0x0000003fU)) { if (CheckHasBit(cached_has_bits, 0x00000001U)) { _this->_internal_mutable_congestion_algorithms()->InternalMergeFromWithArena( ::google::protobuf::MessageLite::internal_visibility(), arena, @@ -4853,11 +4905,20 @@ void Capabilities::MergeImpl(::google::protobuf::MessageLite& to_msg, } } if (CheckHasBit(cached_has_bits, 0x00000008U)) { + if (!from._internal_collection_id().empty()) { + _this->_internal_set_collection_id(from._internal_collection_id()); + } else { + if (_this->_impl_.collection_id_.IsDefault()) { + _this->_internal_set_collection_id(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000010U)) { if (from._internal_simple_host_routing() != 0) { _this->_impl_.simple_host_routing_ = from._impl_.simple_host_routing_; } } - if (CheckHasBit(cached_has_bits, 0x00000010U)) { + if (CheckHasBit(cached_has_bits, 0x00000020U)) { if (from._internal_actuator_enabled() != 0) { _this->_impl_.actuator_enabled_ = from._impl_.actuator_enabled_; } @@ -4885,6 +4946,7 @@ void Capabilities::InternalSwap(Capabilities* PROTOBUF_RESTRICT PROTOBUF_NONNULL _impl_.congestion_algorithms_.InternalSwap(&other->_impl_.congestion_algorithms_); ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.boot_id_, &other->_impl_.boot_id_, arena); ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.kernel_release_, &other->_impl_.kernel_release_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.collection_id_, &other->_impl_.collection_id_, arena); ::google::protobuf::internal::memswap< PROTOBUF_FIELD_OFFSET(Capabilities, _impl_.actuator_enabled_) + sizeof(Capabilities::_impl_.actuator_enabled_) @@ -5922,10 +5984,11 @@ PROTOBUF_NOINLINE void HostEvidence::Clear() { if (BatchCheckHasBit(cached_has_bits, 0x000000feU)) { ::memset(&this_._impl_.sockets_, 0, static_cast<::size_t>( - reinterpret_cast(&this_._impl_.collection_healthy_) - + reinterpret_cast(&this_._impl_.unbound_sockets_) - reinterpret_cast(&this_._impl_.sockets_)) + - sizeof(_impl_.collection_healthy_)); + sizeof(_impl_.unbound_sockets_)); } + this_._impl_.collection_healthy_ = false; _impl_._has_bits_.Clear(); _internal_metadata_.Clear<::google::protobuf::UnknownFieldSet>(); } @@ -6014,7 +6077,7 @@ ::uint8_t* PROTOBUF_NONNULL HostEvidence::_InternalSerialize( } // bool collection_healthy = 8 [json_name = "collectionHealthy"]; - if (CheckHasBit(cached_has_bits, 0x00000080U)) { + if (CheckHasBit(cached_has_bits, 0x00000100U)) { if (this_._internal_collection_healthy() != 0) { target = stream->EnsureSpace(target); target = ::_pbi::WireFormatLite::WriteBoolToArray( @@ -6022,6 +6085,15 @@ ::uint8_t* PROTOBUF_NONNULL HostEvidence::_InternalSerialize( } } + // uint64 unbound_sockets = 9 [json_name = "unboundSockets"]; + if (CheckHasBit(cached_has_bits, 0x00000080U)) { + if (this_._internal_unbound_sockets() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteUInt64ToArray( + 9, this_._internal_unbound_sockets(), target); + } + } + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( @@ -6092,8 +6164,17 @@ ::size_t HostEvidence::ByteSizeLong() const { this_._internal_exposed_sockets()); } } - // bool collection_healthy = 8 [json_name = "collectionHealthy"]; + // uint64 unbound_sockets = 9 [json_name = "unboundSockets"]; if (CheckHasBit(cached_has_bits, 0x00000080U)) { + if (this_._internal_unbound_sockets() != 0) { + total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne( + this_._internal_unbound_sockets()); + } + } + } + { + // bool collection_healthy = 8 [json_name = "collectionHealthy"]; + if (CheckHasBit(cached_has_bits, 0x00000100U)) { if (this_._internal_collection_healthy() != 0) { total_size += 2; } @@ -6157,11 +6238,16 @@ void HostEvidence::MergeImpl(::google::protobuf::MessageLite& to_msg, } } if (CheckHasBit(cached_has_bits, 0x00000080U)) { - if (from._internal_collection_healthy() != 0) { - _this->_impl_.collection_healthy_ = from._impl_.collection_healthy_; + if (from._internal_unbound_sockets() != 0) { + _this->_impl_.unbound_sockets_ = from._impl_.unbound_sockets_; } } } + if (CheckHasBit(cached_has_bits, 0x00000100U)) { + if (from._internal_collection_healthy() != 0) { + _this->_impl_.collection_healthy_ = from._impl_.collection_healthy_; + } + } _this->_impl_._has_bits_[0] |= cached_has_bits; _this->_internal_metadata_.MergeFrom<::google::protobuf::UnknownFieldSet>( from._internal_metadata_); diff --git a/gen/cpp/xtcp_treatment/v1/treatment.pb.h b/gen/cpp/xtcp_treatment/v1/treatment.pb.h index ac98f18eb..927a8842f 100644 --- a/gen/cpp/xtcp_treatment/v1/treatment.pb.h +++ b/gen/cpp/xtcp_treatment/v1/treatment.pb.h @@ -859,6 +859,7 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED HostEvidence final : public ::googl kRetransmissionRatioFieldNumber = 5, kRttP95MsFieldNumber = 6, kExposedSocketsFieldNumber = 7, + kUnboundSocketsFieldNumber = 9, kCollectionHealthyFieldNumber = 8, }; // string agent_id = 1 [json_name = "agentId", (.buf.validate.field) = { @@ -935,6 +936,16 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED HostEvidence final : public ::googl ::uint64_t _internal_exposed_sockets() const; void _internal_set_exposed_sockets(::uint64_t value); + public: + // uint64 unbound_sockets = 9 [json_name = "unboundSockets"]; + void clear_unbound_sockets() ; + [[nodiscard]] ::uint64_t unbound_sockets() const; + void set_unbound_sockets(::uint64_t value); + + private: + ::uint64_t _internal_unbound_sockets() const; + void _internal_set_unbound_sockets(::uint64_t value); + public: // bool collection_healthy = 8 [json_name = "collectionHealthy"]; void clear_collection_healthy() ; @@ -950,7 +961,7 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED HostEvidence final : public ::googl private: class _Internal; using ParseTableT_ = - ::google::protobuf::internal::TcParseTable<3, 8, + ::google::protobuf::internal::TcParseTable<4, 9, 0, 55, 2>; static constexpr ParseTableT_ InternalGenerateParseTable_( @@ -986,6 +997,7 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED HostEvidence final : public ::googl double retransmission_ratio_; double rtt_p95_ms_; ::uint64_t exposed_sockets_; + ::uint64_t unbound_sockets_; bool collection_healthy_; PROTOBUF_TSAN_DECLARE_MEMBER }; @@ -1145,6 +1157,7 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Capabilities final : public ::googl kCongestionAlgorithmsFieldNumber = 3, kBootIdFieldNumber = 1, kKernelReleaseFieldNumber = 2, + kCollectionIdFieldNumber = 6, kSimpleHostRoutingFieldNumber = 4, kActuatorEnabledFieldNumber = 5, }; @@ -1203,6 +1216,21 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Capabilities final : public ::googl PROTOBUF_ALWAYS_INLINE void _internal_set_kernel_release(const ::std::string& value); ::std::string* PROTOBUF_NONNULL _internal_mutable_kernel_release(); + public: + // string collection_id = 6 [json_name = "collectionId", (.buf.validate.field) = { + void clear_collection_id() ; + [[nodiscard]] const ::std::string& collection_id() const; + template + void set_collection_id(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_collection_id(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_collection_id(); + void set_allocated_collection_id(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_collection_id() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_collection_id(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_collection_id(); + public: // bool simple_host_routing = 4 [json_name = "simpleHostRouting"]; void clear_simple_host_routing() ; @@ -1228,8 +1256,8 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Capabilities final : public ::googl private: class _Internal; using ParseTableT_ = - ::google::protobuf::internal::TcParseTable<3, 5, - 0, 81, + ::google::protobuf::internal::TcParseTable<3, 6, + 0, 94, 2>; static constexpr ParseTableT_ InternalGenerateParseTable_( const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); @@ -1260,6 +1288,7 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Capabilities final : public ::googl ::google::protobuf::RepeatedPtrField<::std::string> congestion_algorithms_; ::google::protobuf::internal::ArenaStringPtr boot_id_; ::google::protobuf::internal::ArenaStringPtr kernel_release_; + ::google::protobuf::internal::ArenaStringPtr collection_id_; bool simple_host_routing_; bool actuator_enabled_; PROTOBUF_TSAN_DECLARE_MEMBER @@ -5525,7 +5554,7 @@ Capabilities::_internal_mutable_congestion_algorithms() { inline void Capabilities::clear_simple_host_routing() { ::google::protobuf::internal::TSanWrite(&_impl_); _impl_.simple_host_routing_ = false; - ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + ClearHasBit(_impl_._has_bits_[0], 0x00000010U); } inline bool Capabilities::simple_host_routing() const { // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Capabilities.simple_host_routing) @@ -5533,7 +5562,7 @@ inline bool Capabilities::simple_host_routing() const { } inline void Capabilities::set_simple_host_routing(bool value) { _internal_set_simple_host_routing(value); - SetHasBit(_impl_._has_bits_[0], 0x00000008U); + SetHasBit(_impl_._has_bits_[0], 0x00000010U); // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Capabilities.simple_host_routing) } inline bool Capabilities::_internal_simple_host_routing() const { @@ -5549,7 +5578,7 @@ inline void Capabilities::_internal_set_simple_host_routing(bool value) { inline void Capabilities::clear_actuator_enabled() { ::google::protobuf::internal::TSanWrite(&_impl_); _impl_.actuator_enabled_ = false; - ClearHasBit(_impl_._has_bits_[0], 0x00000010U); + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); } inline bool Capabilities::actuator_enabled() const { // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Capabilities.actuator_enabled) @@ -5557,7 +5586,7 @@ inline bool Capabilities::actuator_enabled() const { } inline void Capabilities::set_actuator_enabled(bool value) { _internal_set_actuator_enabled(value); - SetHasBit(_impl_._has_bits_[0], 0x00000010U); + SetHasBit(_impl_._has_bits_[0], 0x00000020U); // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Capabilities.actuator_enabled) } inline bool Capabilities::_internal_actuator_enabled() const { @@ -5569,6 +5598,70 @@ inline void Capabilities::_internal_set_actuator_enabled(bool value) { _impl_.actuator_enabled_ = value; } +// string collection_id = 6 [json_name = "collectionId", (.buf.validate.field) = { +inline void Capabilities::clear_collection_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.collection_id_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); +} +inline const ::std::string& Capabilities::collection_id() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.Capabilities.collection_id) + return _internal_collection_id(); +} +template +PROTOBUF_ALWAYS_INLINE void Capabilities::set_collection_id(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + _impl_.collection_id_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.Capabilities.collection_id) +} +inline ::std::string* PROTOBUF_NONNULL Capabilities::mutable_collection_id() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + ::std::string* _s = _internal_mutable_collection_id(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment.v1.Capabilities.collection_id) + return _s; +} +inline const ::std::string& Capabilities::_internal_collection_id() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.collection_id_.Get(); +} +inline void Capabilities::_internal_set_collection_id(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.collection_id_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL Capabilities::_internal_mutable_collection_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.collection_id_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE Capabilities::release_collection_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment.v1.Capabilities.collection_id) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000008U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + auto* released = _impl_.collection_id_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.collection_id_.Set("", GetArena()); + } + return released; +} +inline void Capabilities::set_allocated_collection_id(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000008U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000008U); + } + _impl_.collection_id_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.collection_id_.IsDefault()) { + _impl_.collection_id_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment.v1.Capabilities.collection_id) +} + // ------------------------------------------------------------------- // ApplicationRecord @@ -6954,7 +7047,7 @@ inline void HostEvidence::_internal_set_exposed_sockets(::uint64_t value) { inline void HostEvidence::clear_collection_healthy() { ::google::protobuf::internal::TSanWrite(&_impl_); _impl_.collection_healthy_ = false; - ClearHasBit(_impl_._has_bits_[0], 0x00000080U); + ClearHasBit(_impl_._has_bits_[0], 0x00000100U); } inline bool HostEvidence::collection_healthy() const { // @@protoc_insertion_point(field_get:xtcp_treatment.v1.HostEvidence.collection_healthy) @@ -6962,7 +7055,7 @@ inline bool HostEvidence::collection_healthy() const { } inline void HostEvidence::set_collection_healthy(bool value) { _internal_set_collection_healthy(value); - SetHasBit(_impl_._has_bits_[0], 0x00000080U); + SetHasBit(_impl_._has_bits_[0], 0x00000100U); // @@protoc_insertion_point(field_set:xtcp_treatment.v1.HostEvidence.collection_healthy) } inline bool HostEvidence::_internal_collection_healthy() const { @@ -6974,6 +7067,30 @@ inline void HostEvidence::_internal_set_collection_healthy(bool value) { _impl_.collection_healthy_ = value; } +// uint64 unbound_sockets = 9 [json_name = "unboundSockets"]; +inline void HostEvidence::clear_unbound_sockets() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.unbound_sockets_ = ::uint64_t{0u}; + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); +} +inline ::uint64_t HostEvidence::unbound_sockets() const { + // @@protoc_insertion_point(field_get:xtcp_treatment.v1.HostEvidence.unbound_sockets) + return _internal_unbound_sockets(); +} +inline void HostEvidence::set_unbound_sockets(::uint64_t value) { + _internal_set_unbound_sockets(value); + SetHasBit(_impl_._has_bits_[0], 0x00000080U); + // @@protoc_insertion_point(field_set:xtcp_treatment.v1.HostEvidence.unbound_sockets) +} +inline ::uint64_t HostEvidence::_internal_unbound_sockets() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.unbound_sockets_; +} +inline void HostEvidence::_internal_set_unbound_sockets(::uint64_t value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.unbound_sockets_ = value; +} + // ------------------------------------------------------------------- // Evidence diff --git a/gen/cpp/xtcp_treatment_journal/v1/journal.pb.cc b/gen/cpp/xtcp_treatment_journal/v1/journal.pb.cc index 6bc978812..9eb3f910c 100644 --- a/gen/cpp/xtcp_treatment_journal/v1/journal.pb.cc +++ b/gen/cpp/xtcp_treatment_journal/v1/journal.pb.cc @@ -1067,11 +1067,11 @@ constexpr Journal::ParseTableT_ Journal::InternalGenerateParseTable_(const ::_pb { PROTOBUF_FIELD_OFFSET(Journal, _impl_._has_bits_), 0, // no _extensions_ - 8, 56, // max_field_number, fast_idx_mask + 9, 120, // max_field_number, fast_idx_mask offsetof(ParseTableT_, field_lookup_table), - 4294967040, // skipmap + 4294966784, // skipmap offsetof(ParseTableT_, field_entries), - 8, // num_field_entries + 9, // num_field_entries 3, // num_aux_entries offsetof(ParseTableT_, aux_entries), class_data, @@ -1081,10 +1081,7 @@ constexpr Journal::ParseTableT_ Journal::InternalGenerateParseTable_(const ::_pb ::_pbi::TcParser::GetTable<::xtcp_treatment_journal::v1::Journal>(), // to_prefetch #endif // PROTOBUF_PREFETCH_PARSE_TABLE }, {{ - // .google.protobuf.Timestamp updated_at = 8 [json_name = "updatedAt"]; - {::_pbi::TcParser::FastMtS1, - {66, 6, 2, - PROTOBUF_FIELD_OFFSET(Journal, _impl_.updated_at_)}}, + {::_pbi::TcParser::MiniParse, {}}, // string agent_id = 1 [json_name = "agentId"]; {::_pbi::TcParser::FastUS1, {10, 1, 0, @@ -1099,11 +1096,11 @@ constexpr Journal::ParseTableT_ Journal::InternalGenerateParseTable_(const ::_pb PROTOBUF_FIELD_OFFSET(Journal, _impl_.boot_id_)}}, // .xtcp_treatment_journal.v1.Desired desired = 4 [json_name = "desired"]; {::_pbi::TcParser::FastMtS1, - {34, 5, 0, + {34, 6, 0, PROTOBUF_FIELD_OFFSET(Journal, _impl_.desired_)}}, // bool locally_suspended = 5 [json_name = "locallySuspended"]; - {::_pbi::TcParser::SingularVarintNoZag1(), - {40, 7, 0, + {::_pbi::TcParser::SingularVarintNoZag1(), + {40, 8, 0, PROTOBUF_FIELD_OFFSET(Journal, _impl_.locally_suspended_)}}, // string suspension_reason = 6 [json_name = "suspensionReason"]; {::_pbi::TcParser::FastUS1, @@ -1113,6 +1110,20 @@ constexpr Journal::ParseTableT_ Journal::InternalGenerateParseTable_(const ::_pb {::_pbi::TcParser::FastMtR1, {58, 0, 1, PROTOBUF_FIELD_OFFSET(Journal, _impl_.records_)}}, + // .google.protobuf.Timestamp updated_at = 8 [json_name = "updatedAt"]; + {::_pbi::TcParser::FastMtS1, + {66, 7, 2, + PROTOBUF_FIELD_OFFSET(Journal, _impl_.updated_at_)}}, + // string collection_id = 9 [json_name = "collectionId"]; + {::_pbi::TcParser::FastUS1, + {74, 5, 0, + PROTOBUF_FIELD_OFFSET(Journal, _impl_.collection_id_)}}, + {::_pbi::TcParser::MiniParse, {}}, + {::_pbi::TcParser::MiniParse, {}}, + {::_pbi::TcParser::MiniParse, {}}, + {::_pbi::TcParser::MiniParse, {}}, + {::_pbi::TcParser::MiniParse, {}}, + {::_pbi::TcParser::MiniParse, {}}, }}, {{ 65535, 65535 }}, {{ @@ -1123,15 +1134,17 @@ constexpr Journal::ParseTableT_ Journal::InternalGenerateParseTable_(const ::_pb // string boot_id = 3 [json_name = "bootId"]; {PROTOBUF_FIELD_OFFSET(Journal, _impl_.boot_id_), _Internal::kHasBitsOffset + 3, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, // .xtcp_treatment_journal.v1.Desired desired = 4 [json_name = "desired"]; - {PROTOBUF_FIELD_OFFSET(Journal, _impl_.desired_), _Internal::kHasBitsOffset + 5, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + {PROTOBUF_FIELD_OFFSET(Journal, _impl_.desired_), _Internal::kHasBitsOffset + 6, 0, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, // bool locally_suspended = 5 [json_name = "locallySuspended"]; - {PROTOBUF_FIELD_OFFSET(Journal, _impl_.locally_suspended_), _Internal::kHasBitsOffset + 7, 0, (0 | ::_fl::kFcOptional | ::_fl::kBool)}, + {PROTOBUF_FIELD_OFFSET(Journal, _impl_.locally_suspended_), _Internal::kHasBitsOffset + 8, 0, (0 | ::_fl::kFcOptional | ::_fl::kBool)}, // string suspension_reason = 6 [json_name = "suspensionReason"]; {PROTOBUF_FIELD_OFFSET(Journal, _impl_.suspension_reason_), _Internal::kHasBitsOffset + 4, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, // repeated .xtcp_treatment_journal.v1.Record records = 7 [json_name = "records"]; {PROTOBUF_FIELD_OFFSET(Journal, _impl_.records_), _Internal::kHasBitsOffset + 0, 1, (0 | ::_fl::kFcRepeated | ::_fl::kMessage | ::_fl::kTvTable)}, // .google.protobuf.Timestamp updated_at = 8 [json_name = "updatedAt"]; - {PROTOBUF_FIELD_OFFSET(Journal, _impl_.updated_at_), _Internal::kHasBitsOffset + 6, 2, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + {PROTOBUF_FIELD_OFFSET(Journal, _impl_.updated_at_), _Internal::kHasBitsOffset + 7, 2, (0 | ::_fl::kFcOptional | ::_fl::kMessage | ::_fl::kTvTable)}, + // string collection_id = 9 [json_name = "collectionId"]; + {PROTOBUF_FIELD_OFFSET(Journal, _impl_.collection_id_), _Internal::kHasBitsOffset + 5, 0, (0 | ::_fl::kFcOptional | ::_fl::kUtf8String | ::_fl::kRepAString)}, }}, {{ #ifndef PROTOBUF_MESSAGE_GLOBALS @@ -1151,12 +1164,13 @@ constexpr Journal::ParseTableT_ Journal::InternalGenerateParseTable_(const ::_pb #endif }}, {{ - "\41\10\10\7\0\0\21\0\0\0\0\0\0\0\0\0" + "\41\10\10\7\0\0\21\0\0\15\0\0\0\0\0\0" "xtcp_treatment_journal.v1.Journal" "agent_id" "location" "boot_id" "suspension_reason" + "collection_id" }}, }; } @@ -1182,6 +1196,9 @@ inline constexpr Journal::Impl_::Impl_( suspension_reason_( &::google::protobuf::internal::fixed_address_empty_string, ::_pbi::ConstantInitialized()), + collection_id_( + &::google::protobuf::internal::fixed_address_empty_string, + ::_pbi::ConstantInitialized()), desired_{nullptr}, updated_at_{nullptr}, locally_suspended_{false} {} @@ -1372,7 +1389,7 @@ const ::uint32_t 11, 0x081, // bitmap PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Journal, _impl_._has_bits_), - 11, // hasbit index offset + 12, // hasbit index offset PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Journal, _impl_.agent_id_), PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Journal, _impl_.location_), PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Journal, _impl_.boot_id_), @@ -1381,14 +1398,16 @@ const ::uint32_t PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Journal, _impl_.suspension_reason_), PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Journal, _impl_.records_), PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Journal, _impl_.updated_at_), + PROTOBUF_FIELD_OFFSET(::xtcp_treatment_journal::v1::Journal, _impl_.collection_id_), 1, 2, 3, - 5, - 7, + 6, + 8, 4, 0, - 6, + 7, + 5, }; static const ::_pbi::MigrationSchema @@ -1459,7 +1478,7 @@ const char descriptor_table_protodef_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2 "\022\024\n\005error\030\t \001(\tR\005error\022\'\n\017installed_rout" "e\030\n \001(\014R\016installedRoute\022*\n\021installed_boo" "t_id\030\013 \001(\tR\017installedBootId\022\'\n\017expired_l" - "atched\030\014 \001(\010R\016expiredLatched\"\351\002\n\007Journal" + "atched\030\014 \001(\010R\016expiredLatched\"\216\003\n\007Journal" "\022\031\n\010agent_id\030\001 \001(\tR\007agentId\022\032\n\010location\030" "\002 \001(\tR\010location\022\027\n\007boot_id\030\003 \001(\tR\006bootId" "\022<\n\007desired\030\004 \001(\0132\".xtcp_treatment_journ" @@ -1468,22 +1487,23 @@ const char descriptor_table_protodef_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2 "n_reason\030\006 \001(\tR\020suspensionReason\022;\n\007reco" "rds\030\007 \003(\0132!.xtcp_treatment_journal.v1.Re" "cordR\007records\0229\n\nupdated_at\030\010 \001(\0132\032.goog" - "le.protobuf.TimestampR\tupdatedAt*\244\001\n\016Ass" - "ignmentMode\022\037\n\033ASSIGNMENT_MODE_UNSPECIFI" - "ED\020\000\022\031\n\025ASSIGNMENT_MODE_TRIAL\020\001\022\032\n\026ASSIG" - "NMENT_MODE_STABLE\020\002\022\035\n\031ASSIGNMENT_MODE_S" - "USPENDED\020\003\022\033\n\027ASSIGNMENT_MODE_RETIRED\020\004*" - "\255\001\n\013ApplyStatus\022\034\n\030APPLY_STATUS_UNSPECIF" - "IED\020\000\022\030\n\024APPLY_STATUS_PENDING\020\001\022\031\n\025APPLY" - "_STATUS_VERIFIED\020\002\022\030\n\024APPLY_STATUS_REMOV" - "ED\020\003\022\030\n\024APPLY_STATUS_EXPIRED\020\004\022\027\n\023APPLY_" - "STATUS_FAILED\020\005B\352\001\n\035com.xtcp_treatment_j" - "ournal.v1B\014JournalProtoP\001Z>github.com/ra" - "ndomizedcoder/xtcp2/gen/go/xtcp_treatmen" - "t_journal\242\002\003XXX\252\002\027XtcpTreatmentJournal.V" - "1\312\002\027XtcpTreatmentJournal\\V1\342\002#XtcpTreatm" - "entJournal\\V1\\GPBMetadata\352\002\030XtcpTreatmen" - "tJournal::V1b\006proto3" + "le.protobuf.TimestampR\tupdatedAt\022#\n\rcoll" + "ection_id\030\t \001(\tR\014collectionId*\244\001\n\016Assign" + "mentMode\022\037\n\033ASSIGNMENT_MODE_UNSPECIFIED\020" + "\000\022\031\n\025ASSIGNMENT_MODE_TRIAL\020\001\022\032\n\026ASSIGNME" + "NT_MODE_STABLE\020\002\022\035\n\031ASSIGNMENT_MODE_SUSP" + "ENDED\020\003\022\033\n\027ASSIGNMENT_MODE_RETIRED\020\004*\255\001\n" + "\013ApplyStatus\022\034\n\030APPLY_STATUS_UNSPECIFIED" + "\020\000\022\030\n\024APPLY_STATUS_PENDING\020\001\022\031\n\025APPLY_ST" + "ATUS_VERIFIED\020\002\022\030\n\024APPLY_STATUS_REMOVED\020" + "\003\022\030\n\024APPLY_STATUS_EXPIRED\020\004\022\027\n\023APPLY_STA" + "TUS_FAILED\020\005B\352\001\n\035com.xtcp_treatment_jour" + "nal.v1B\014JournalProtoP\001Z>github.com/rando" + "mizedcoder/xtcp2/gen/go/xtcp_treatment_j" + "ournal\242\002\003XXX\252\002\027XtcpTreatmentJournal.V1\312\002" + "\027XtcpTreatmentJournal\\V1\342\002#XtcpTreatment" + "Journal\\V1\\GPBMetadata\352\002\030XtcpTreatmentJo" + "urnal::V1b\006proto3" }; static const ::_pbi::DescriptorTable* PROTOBUF_NONNULL const descriptor_table_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto_deps[1] = { @@ -1493,7 +1513,7 @@ static ::absl::once_flag descriptor_table_xtcp_5ftreatment_5fjournal_2fv1_2fjour PROTOBUF_CONSTINIT const ::_pbi::DescriptorTable descriptor_table_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto = { false, false, - 2900, + 2937, descriptor_table_protodef_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto, "xtcp_treatment_journal/v1/journal.proto", &descriptor_table_xtcp_5ftreatment_5fjournal_2fv1_2fjournal_2eproto_once, @@ -3444,7 +3464,7 @@ ::google::protobuf::Metadata Record::GetMetadata() const { void Journal::clear_updated_at() { ::google::protobuf::internal::TSanWrite(&_impl_); if (_impl_.updated_at_ != nullptr) _impl_.updated_at_->Clear(); - ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); } Journal::Journal(::google::protobuf::Arena* PROTOBUF_NULLABLE arena) #if defined(PROTOBUF_CUSTOM_VTABLE) @@ -3470,7 +3490,8 @@ PROTOBUF_NDEBUG_INLINE Journal::Impl_::Impl_( agent_id_(arena, from.agent_id_), location_(arena, from.location_), boot_id_(arena, from.boot_id_), - suspension_reason_(arena, from.suspension_reason_) {} + suspension_reason_(arena, from.suspension_reason_), + collection_id_(arena, from.collection_id_) {} Journal::Journal( ::google::protobuf::Arena* PROTOBUF_NULLABLE arena, @@ -3487,10 +3508,10 @@ Journal::Journal( from._internal_metadata_); new (&_impl_) Impl_(internal_visibility(), arena, from._impl_, from); ::uint32_t cached_has_bits = _impl_._has_bits_[0]; - _impl_.desired_ = (CheckHasBit(cached_has_bits, 0x00000020U)) + _impl_.desired_ = (CheckHasBit(cached_has_bits, 0x00000040U)) ? Super_::CopyConstruct(arena, *from._impl_.desired_) : nullptr; - _impl_.updated_at_ = (CheckHasBit(cached_has_bits, 0x00000040U)) + _impl_.updated_at_ = (CheckHasBit(cached_has_bits, 0x00000080U)) ? Super_::CopyConstruct(arena, *from._impl_.updated_at_) : nullptr; _impl_.locally_suspended_ = from._impl_.locally_suspended_; @@ -3508,7 +3529,8 @@ PROTOBUF_NDEBUG_INLINE Journal::Impl_::Impl_( agent_id_(arena), location_(arena), boot_id_(arena), - suspension_reason_(arena) {} + suspension_reason_(arena), + collection_id_(arena) {} inline void Journal::SharedCtor(::_pb::Arena* PROTOBUF_NULLABLE arena) { new (&_impl_) Impl_(internal_visibility(), arena); @@ -3534,6 +3556,7 @@ inline void Journal::SharedDtor(MessageLite& self) { this_._impl_.location_.Destroy(); this_._impl_.boot_id_.Destroy(); this_._impl_.suspension_reason_.Destroy(); + this_._impl_.collection_id_.Destroy(); delete this_._impl_.desired_; delete this_._impl_.updated_at_; this_._impl_.~Impl_(); @@ -3572,7 +3595,7 @@ PROTOBUF_NOINLINE void Journal::Clear() { ::uint32_t cached_has_bits [[maybe_unused]] = 0; cached_has_bits = this_._impl_._has_bits_[0]; - if (BatchCheckHasBit(cached_has_bits, 0x0000007fU)) { + if (BatchCheckHasBit(cached_has_bits, 0x000000ffU)) { if (CheckHasBit(cached_has_bits, 0x00000001U)) { _impl_.records_.Clear(); } @@ -3589,10 +3612,13 @@ PROTOBUF_NOINLINE void Journal::Clear() { this_._impl_.suspension_reason_.ClearNonDefaultToEmpty(); } if (CheckHasBit(cached_has_bits, 0x00000020U)) { + this_._impl_.collection_id_.ClearNonDefaultToEmpty(); + } + if (CheckHasBit(cached_has_bits, 0x00000040U)) { ABSL_DCHECK(this_._impl_.desired_ != nullptr); this_._impl_.desired_->Clear(); } - if (CheckHasBit(cached_has_bits, 0x00000040U)) { + if (CheckHasBit(cached_has_bits, 0x00000080U)) { ABSL_DCHECK(this_._impl_.updated_at_ != nullptr); this_._impl_.updated_at_->Clear(); } @@ -3652,14 +3678,14 @@ ::uint8_t* PROTOBUF_NONNULL Journal::_InternalSerialize( } // .xtcp_treatment_journal.v1.Desired desired = 4 [json_name = "desired"]; - if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (CheckHasBit(cached_has_bits, 0x00000040U)) { target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( 4, *this_._impl_.desired_, this_._impl_.desired_->GetCachedSize(), target, stream); } // bool locally_suspended = 5 [json_name = "locallySuspended"]; - if (CheckHasBit(cached_has_bits, 0x00000080U)) { + if (CheckHasBit(cached_has_bits, 0x00000100U)) { if (this_._internal_locally_suspended() != 0) { target = stream->EnsureSpace(target); target = ::_pbi::WireFormatLite::WriteBoolToArray( @@ -3690,12 +3716,22 @@ ::uint8_t* PROTOBUF_NONNULL Journal::_InternalSerialize( } // .google.protobuf.Timestamp updated_at = 8 [json_name = "updatedAt"]; - if (CheckHasBit(cached_has_bits, 0x00000040U)) { + if (CheckHasBit(cached_has_bits, 0x00000080U)) { target = ::google::protobuf::internal::WireFormatLite::InternalWriteMessage( 8, *this_._impl_.updated_at_, this_._impl_.updated_at_->GetCachedSize(), target, stream); } + // string collection_id = 9 [json_name = "collectionId"]; + if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (!this_._internal_collection_id().empty()) { + const ::std::string& _s = this_._internal_collection_id(); + ::google::protobuf::internal::WireFormatLite::VerifyUtf8String( + _s.data(), static_cast(_s.length()), ::google::protobuf::internal::WireFormatLite::SERIALIZE, "xtcp_treatment_journal.v1.Journal.collection_id"); + target = stream->WriteStringMaybeAliased(9, _s, target); + } + } + if (ABSL_PREDICT_FALSE(this_._internal_metadata_.have_unknown_fields())) { target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( @@ -3755,18 +3791,27 @@ ::size_t Journal::ByteSizeLong() const { this_._internal_suspension_reason()); } } - // .xtcp_treatment_journal.v1.Desired desired = 4 [json_name = "desired"]; + // string collection_id = 9 [json_name = "collectionId"]; if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (!this_._internal_collection_id().empty()) { + total_size += 1 + ::google::protobuf::internal::WireFormatLite::StringSize( + this_._internal_collection_id()); + } + } + // .xtcp_treatment_journal.v1.Desired desired = 4 [json_name = "desired"]; + if (CheckHasBit(cached_has_bits, 0x00000040U)) { total_size += 1 + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.desired_); } // .google.protobuf.Timestamp updated_at = 8 [json_name = "updatedAt"]; - if (CheckHasBit(cached_has_bits, 0x00000040U)) { + if (CheckHasBit(cached_has_bits, 0x00000080U)) { total_size += 1 + ::google::protobuf::internal::WireFormatLite::MessageSize(*this_._impl_.updated_at_); } + } + { // bool locally_suspended = 5 [json_name = "locallySuspended"]; - if (CheckHasBit(cached_has_bits, 0x00000080U)) { + if (CheckHasBit(cached_has_bits, 0x00000100U)) { if (this_._internal_locally_suspended() != 0) { total_size += 2; } @@ -3833,6 +3878,15 @@ void Journal::MergeImpl(::google::protobuf::MessageLite& to_msg, } } if (CheckHasBit(cached_has_bits, 0x00000020U)) { + if (!from._internal_collection_id().empty()) { + _this->_internal_set_collection_id(from._internal_collection_id()); + } else { + if (_this->_impl_.collection_id_.IsDefault()) { + _this->_internal_set_collection_id(""); + } + } + } + if (CheckHasBit(cached_has_bits, 0x00000040U)) { ABSL_DCHECK(from._impl_.desired_ != nullptr); if (_this->_impl_.desired_ == nullptr) { _this->_impl_.desired_ = Super_::CopyConstruct(arena, *from._impl_.desired_); @@ -3840,7 +3894,7 @@ void Journal::MergeImpl(::google::protobuf::MessageLite& to_msg, _this->_impl_.desired_->MergeFrom(*from._impl_.desired_); } } - if (CheckHasBit(cached_has_bits, 0x00000040U)) { + if (CheckHasBit(cached_has_bits, 0x00000080U)) { ABSL_DCHECK(from._impl_.updated_at_ != nullptr); if (_this->_impl_.updated_at_ == nullptr) { _this->_impl_.updated_at_ = Super_::CopyConstruct(arena, *from._impl_.updated_at_); @@ -3848,10 +3902,10 @@ void Journal::MergeImpl(::google::protobuf::MessageLite& to_msg, _this->_impl_.updated_at_->MergeFrom(*from._impl_.updated_at_); } } - if (CheckHasBit(cached_has_bits, 0x00000080U)) { - if (from._internal_locally_suspended() != 0) { - _this->_impl_.locally_suspended_ = from._impl_.locally_suspended_; - } + } + if (CheckHasBit(cached_has_bits, 0x00000100U)) { + if (from._internal_locally_suspended() != 0) { + _this->_impl_.locally_suspended_ = from._impl_.locally_suspended_; } } _this->_impl_._has_bits_[0] |= cached_has_bits; @@ -3878,6 +3932,7 @@ void Journal::InternalSwap(Journal* PROTOBUF_RESTRICT PROTOBUF_NONNULL other) { ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.location_, &other->_impl_.location_, arena); ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.boot_id_, &other->_impl_.boot_id_, arena); ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.suspension_reason_, &other->_impl_.suspension_reason_, arena); + ::_pbi::ArenaStringPtr::InternalSwap(&_impl_.collection_id_, &other->_impl_.collection_id_, arena); ::google::protobuf::internal::memswap< PROTOBUF_FIELD_OFFSET(Journal, _impl_.locally_suspended_) + sizeof(Journal::_impl_.locally_suspended_) diff --git a/gen/cpp/xtcp_treatment_journal/v1/journal.pb.h b/gen/cpp/xtcp_treatment_journal/v1/journal.pb.h index d5a53f057..22e236bd3 100644 --- a/gen/cpp/xtcp_treatment_journal/v1/journal.pb.h +++ b/gen/cpp/xtcp_treatment_journal/v1/journal.pb.h @@ -1822,6 +1822,7 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Journal final : public ::google::pr kLocationFieldNumber = 2, kBootIdFieldNumber = 3, kSuspensionReasonFieldNumber = 6, + kCollectionIdFieldNumber = 9, kDesiredFieldNumber = 4, kUpdatedAtFieldNumber = 8, kLocallySuspendedFieldNumber = 5, @@ -1905,6 +1906,21 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Journal final : public ::google::pr PROTOBUF_ALWAYS_INLINE void _internal_set_suspension_reason(const ::std::string& value); ::std::string* PROTOBUF_NONNULL _internal_mutable_suspension_reason(); + public: + // string collection_id = 9 [json_name = "collectionId"]; + void clear_collection_id() ; + [[nodiscard]] const ::std::string& collection_id() const; + template + void set_collection_id(Arg_&& arg, Args_... args); + ::std::string* PROTOBUF_NONNULL mutable_collection_id(); + [[nodiscard]] ::std::string* PROTOBUF_NULLABLE release_collection_id(); + void set_allocated_collection_id(::std::string* PROTOBUF_NULLABLE value); + + private: + const ::std::string& _internal_collection_id() const; + PROTOBUF_ALWAYS_INLINE void _internal_set_collection_id(const ::std::string& value); + ::std::string* PROTOBUF_NONNULL _internal_mutable_collection_id(); + public: // .xtcp_treatment_journal.v1.Desired desired = 4 [json_name = "desired"]; [[nodiscard]] bool has_desired() const; @@ -1950,8 +1966,8 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Journal final : public ::google::pr private: class _Internal; using ParseTableT_ = - ::google::protobuf::internal::TcParseTable<3, 8, - 3, 90, + ::google::protobuf::internal::TcParseTable<4, 9, + 3, 103, 2>; static constexpr ParseTableT_ InternalGenerateParseTable_( const ::google::protobuf::internal::ClassData* PROTOBUF_NONNULL class_data); @@ -1984,6 +2000,7 @@ class PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Journal final : public ::google::pr ::google::protobuf::internal::ArenaStringPtr location_; ::google::protobuf::internal::ArenaStringPtr boot_id_; ::google::protobuf::internal::ArenaStringPtr suspension_reason_; + ::google::protobuf::internal::ArenaStringPtr collection_id_; ::xtcp_treatment_journal::v1::Desired* PROTOBUF_NULLABLE desired_; ::google::protobuf::Timestamp* PROTOBUF_NULLABLE updated_at_; bool locally_suspended_; @@ -4153,14 +4170,14 @@ inline void Journal::set_allocated_boot_id(::std::string* PROTOBUF_NULLABLE valu // .xtcp_treatment_journal.v1.Desired desired = 4 [json_name = "desired"]; inline bool Journal::has_desired() const { - bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000020U); + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000040U); PROTOBUF_ASSUME(!value || _impl_.desired_ != nullptr); return value; } inline void Journal::clear_desired() { ::google::protobuf::internal::TSanWrite(&_impl_); if (_impl_.desired_ != nullptr) _impl_.desired_->Clear(); - ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); } inline const ::xtcp_treatment_journal::v1::Desired& Journal::_internal_desired() const { ::google::protobuf::internal::TSanRead(&_impl_); @@ -4179,16 +4196,16 @@ inline void Journal::unsafe_arena_set_allocated_desired( } _impl_.desired_ = reinterpret_cast<::xtcp_treatment_journal::v1::Desired*>(value); if (value != nullptr) { - SetHasBit(_impl_._has_bits_[0], 0x00000020U); + SetHasBit(_impl_._has_bits_[0], 0x00000040U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); } // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment_journal.v1.Journal.desired) } inline ::xtcp_treatment_journal::v1::Desired* PROTOBUF_NULLABLE Journal::release_desired() { ::google::protobuf::internal::TSanWrite(&_impl_); - ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); ::xtcp_treatment_journal::v1::Desired* released = _impl_.desired_; _impl_.desired_ = nullptr; if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { @@ -4208,7 +4225,7 @@ inline ::xtcp_treatment_journal::v1::Desired* PROTOBUF_NULLABLE Journal::unsafe_ ::google::protobuf::internal::TSanWrite(&_impl_); // @@protoc_insertion_point(field_release:xtcp_treatment_journal.v1.Journal.desired) - ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); ::xtcp_treatment_journal::v1::Desired* temp = _impl_.desired_; _impl_.desired_ = nullptr; return temp; @@ -4223,7 +4240,7 @@ inline ::xtcp_treatment_journal::v1::Desired* PROTOBUF_NONNULL Journal::_interna } inline ::xtcp_treatment_journal::v1::Desired* PROTOBUF_NONNULL Journal::mutable_desired() ABSL_ATTRIBUTE_LIFETIME_BOUND { - SetHasBit(_impl_._has_bits_[0], 0x00000020U); + SetHasBit(_impl_._has_bits_[0], 0x00000040U); ::xtcp_treatment_journal::v1::Desired* _msg = _internal_mutable_desired(); // @@protoc_insertion_point(field_mutable:xtcp_treatment_journal.v1.Journal.desired) return _msg; @@ -4240,9 +4257,9 @@ inline void Journal::set_allocated_desired(::xtcp_treatment_journal::v1::Desired if (message_arena != submessage_arena) { value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); } - SetHasBit(_impl_._has_bits_[0], 0x00000020U); + SetHasBit(_impl_._has_bits_[0], 0x00000040U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + ClearHasBit(_impl_._has_bits_[0], 0x00000040U); } _impl_.desired_ = reinterpret_cast<::xtcp_treatment_journal::v1::Desired*>(value); @@ -4253,7 +4270,7 @@ inline void Journal::set_allocated_desired(::xtcp_treatment_journal::v1::Desired inline void Journal::clear_locally_suspended() { ::google::protobuf::internal::TSanWrite(&_impl_); _impl_.locally_suspended_ = false; - ClearHasBit(_impl_._has_bits_[0], 0x00000080U); + ClearHasBit(_impl_._has_bits_[0], 0x00000100U); } inline bool Journal::locally_suspended() const { // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Journal.locally_suspended) @@ -4261,7 +4278,7 @@ inline bool Journal::locally_suspended() const { } inline void Journal::set_locally_suspended(bool value) { _internal_set_locally_suspended(value); - SetHasBit(_impl_._has_bits_[0], 0x00000080U); + SetHasBit(_impl_._has_bits_[0], 0x00000100U); // @@protoc_insertion_point(field_set:xtcp_treatment_journal.v1.Journal.locally_suspended) } inline bool Journal::_internal_locally_suspended() const { @@ -4394,7 +4411,7 @@ Journal::_internal_mutable_records() { // .google.protobuf.Timestamp updated_at = 8 [json_name = "updatedAt"]; inline bool Journal::has_updated_at() const { - bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000040U); + bool value = CheckHasBit(_impl_._has_bits_[0], 0x00000080U); PROTOBUF_ASSUME(!value || _impl_.updated_at_ != nullptr); return value; } @@ -4415,16 +4432,16 @@ inline void Journal::unsafe_arena_set_allocated_updated_at( } _impl_.updated_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); if (value != nullptr) { - SetHasBit(_impl_._has_bits_[0], 0x00000040U); + SetHasBit(_impl_._has_bits_[0], 0x00000080U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); } // @@protoc_insertion_point(field_unsafe_arena_set_allocated:xtcp_treatment_journal.v1.Journal.updated_at) } inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Journal::release_updated_at() { ::google::protobuf::internal::TSanWrite(&_impl_); - ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); ::google::protobuf::Timestamp* released = _impl_.updated_at_; _impl_.updated_at_ = nullptr; if (::google::protobuf::internal::DebugHardenForceCopyInRelease()) { @@ -4444,7 +4461,7 @@ inline ::google::protobuf::Timestamp* PROTOBUF_NULLABLE Journal::unsafe_arena_re ::google::protobuf::internal::TSanWrite(&_impl_); // @@protoc_insertion_point(field_release:xtcp_treatment_journal.v1.Journal.updated_at) - ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); ::google::protobuf::Timestamp* temp = _impl_.updated_at_; _impl_.updated_at_ = nullptr; return temp; @@ -4459,7 +4476,7 @@ inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Journal::_internal_mutabl } inline ::google::protobuf::Timestamp* PROTOBUF_NONNULL Journal::mutable_updated_at() ABSL_ATTRIBUTE_LIFETIME_BOUND { - SetHasBit(_impl_._has_bits_[0], 0x00000040U); + SetHasBit(_impl_._has_bits_[0], 0x00000080U); ::google::protobuf::Timestamp* _msg = _internal_mutable_updated_at(); // @@protoc_insertion_point(field_mutable:xtcp_treatment_journal.v1.Journal.updated_at) return _msg; @@ -4476,15 +4493,79 @@ inline void Journal::set_allocated_updated_at(::google::protobuf::Timestamp* PRO if (message_arena != submessage_arena) { value = ::google::protobuf::internal::GetOwnedMessage(message_arena, value, submessage_arena); } - SetHasBit(_impl_._has_bits_[0], 0x00000040U); + SetHasBit(_impl_._has_bits_[0], 0x00000080U); } else { - ClearHasBit(_impl_._has_bits_[0], 0x00000040U); + ClearHasBit(_impl_._has_bits_[0], 0x00000080U); } _impl_.updated_at_ = reinterpret_cast<::google::protobuf::Timestamp*>(value); // @@protoc_insertion_point(field_set_allocated:xtcp_treatment_journal.v1.Journal.updated_at) } +// string collection_id = 9 [json_name = "collectionId"]; +inline void Journal::clear_collection_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.collection_id_.ClearToEmpty(); + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); +} +inline const ::std::string& Journal::collection_id() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // @@protoc_insertion_point(field_get:xtcp_treatment_journal.v1.Journal.collection_id) + return _internal_collection_id(); +} +template +PROTOBUF_ALWAYS_INLINE void Journal::set_collection_id(Arg_&& arg, Args_... args) { + ::google::protobuf::internal::TSanWrite(&_impl_); + SetHasBit(_impl_._has_bits_[0], 0x00000020U); + _impl_.collection_id_.Set(static_cast(arg), args..., GetArena()); + // @@protoc_insertion_point(field_set:xtcp_treatment_journal.v1.Journal.collection_id) +} +inline ::std::string* PROTOBUF_NONNULL Journal::mutable_collection_id() + ABSL_ATTRIBUTE_LIFETIME_BOUND { + SetHasBit(_impl_._has_bits_[0], 0x00000020U); + ::std::string* _s = _internal_mutable_collection_id(); + // @@protoc_insertion_point(field_mutable:xtcp_treatment_journal.v1.Journal.collection_id) + return _s; +} +inline const ::std::string& Journal::_internal_collection_id() const { + ::google::protobuf::internal::TSanRead(&_impl_); + return _impl_.collection_id_.Get(); +} +inline void Journal::_internal_set_collection_id(const ::std::string& value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + _impl_.collection_id_.Set(value, GetArena()); +} +inline ::std::string* PROTOBUF_NONNULL Journal::_internal_mutable_collection_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + return _impl_.collection_id_.Mutable( GetArena()); +} +inline ::std::string* PROTOBUF_NULLABLE Journal::release_collection_id() { + ::google::protobuf::internal::TSanWrite(&_impl_); + // @@protoc_insertion_point(field_release:xtcp_treatment_journal.v1.Journal.collection_id) + if (!CheckHasBit(_impl_._has_bits_[0], 0x00000020U)) { + return nullptr; + } + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + auto* released = _impl_.collection_id_.Release(); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString()) { + _impl_.collection_id_.Set("", GetArena()); + } + return released; +} +inline void Journal::set_allocated_collection_id(::std::string* PROTOBUF_NULLABLE value) { + ::google::protobuf::internal::TSanWrite(&_impl_); + if (value != nullptr) { + SetHasBit(_impl_._has_bits_[0], 0x00000020U); + } else { + ClearHasBit(_impl_._has_bits_[0], 0x00000020U); + } + _impl_.collection_id_.SetAllocated(value, GetArena()); + if (::google::protobuf::internal::DebugHardenForceCopyDefaultString() && _impl_.collection_id_.IsDefault()) { + _impl_.collection_id_.Set("", GetArena()); + } + // @@protoc_insertion_point(field_set_allocated:xtcp_treatment_journal.v1.Journal.collection_id) +} + #ifdef __GNUC__ #pragma GCC diagnostic pop #endif // __GNUC__ diff --git a/gen/dart/xtcp_treatment/v1/treatment.pb.dart b/gen/dart/xtcp_treatment/v1/treatment.pb.dart index c9eafbe51..39aa7199c 100644 --- a/gen/dart/xtcp_treatment/v1/treatment.pb.dart +++ b/gen/dart/xtcp_treatment/v1/treatment.pb.dart @@ -519,6 +519,7 @@ class Capabilities extends $pb.GeneratedMessage { $core.Iterable<$core.String>? congestionAlgorithms, $core.bool? simpleHostRouting, $core.bool? actuatorEnabled, + $core.String? collectionId, }) { final result = Capabilities._(); if (bootId != null) result.bootId = bootId; @@ -527,6 +528,7 @@ class Capabilities extends $pb.GeneratedMessage { result.congestionAlgorithms.addAll(congestionAlgorithms); if (simpleHostRouting != null) result.simpleHostRouting = simpleHostRouting; if (actuatorEnabled != null) result.actuatorEnabled = actuatorEnabled; + if (collectionId != null) result.collectionId = collectionId; return result; } @@ -549,6 +551,7 @@ class Capabilities extends $pb.GeneratedMessage { ..pPS(3, _omitFieldNames ? '' : 'congestionAlgorithms') ..aOB(4, _omitFieldNames ? '' : 'simpleHostRouting') ..aOB(5, _omitFieldNames ? '' : 'actuatorEnabled') + ..aOS(6, _omitFieldNames ? '' : 'collectionId') ..hasRequiredFields = false; @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @@ -611,6 +614,18 @@ class Capabilities extends $pb.GeneratedMessage { $core.bool hasActuatorEnabled() => $_has(4); @$pb.TagNumber(5) void clearActuatorEnabled() => $_clearField(5); + + /// Per-process nonce the agent writes into its journal header and the + /// daemon stamps into every record as collection_id. Evidence counts only + /// records whose (agent_id, host_boot_id, collection_id) an agent reported. + @$pb.TagNumber(6) + $core.String get collectionId => $_getSZ(5); + @$pb.TagNumber(6) + set collectionId($core.String value) => $_setString(5, value); + @$pb.TagNumber(6) + $core.bool hasCollectionId() => $_has(5); + @$pb.TagNumber(6) + void clearCollectionId() => $_clearField(6); } /// ApplicationRecord is the agent's account of one assignment. Agents report @@ -945,6 +960,7 @@ class HostEvidence extends $pb.GeneratedMessage { $core.double? rttP95Ms, $fixnum.Int64? exposedSockets, $core.bool? collectionHealthy, + $fixnum.Int64? unboundSockets, }) { final result = HostEvidence._(); if (agentId != null) result.agentId = agentId; @@ -956,6 +972,7 @@ class HostEvidence extends $pb.GeneratedMessage { if (rttP95Ms != null) result.rttP95Ms = rttP95Ms; if (exposedSockets != null) result.exposedSockets = exposedSockets; if (collectionHealthy != null) result.collectionHealthy = collectionHealthy; + if (unboundSockets != null) result.unboundSockets = unboundSockets; return result; } @@ -986,6 +1003,9 @@ class HostEvidence extends $pb.GeneratedMessage { 7, _omitFieldNames ? '' : 'exposedSockets', $pb.PbFieldType.OU6, defaultOrMaker: $fixnum.Int64.ZERO) ..aOB(8, _omitFieldNames ? '' : 'collectionHealthy') + ..a<$fixnum.Int64>( + 9, _omitFieldNames ? '' : 'unboundSockets', $pb.PbFieldType.OU6, + defaultOrMaker: $fixnum.Int64.ZERO) ..hasRequiredFields = false; @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @@ -1081,6 +1101,18 @@ class HostEvidence extends $pb.GeneratedMessage { $core.bool hasCollectionHealthy() => $_has(7); @$pb.TagNumber(8) void clearCollectionHealthy() => $_clearField(8); + + /// Sockets stamped with this agent_id whose boot and collection nonce + /// match no session the agent reported. They are excluded from every other + /// field and counted here so a reviewer can see them. + @$pb.TagNumber(9) + $fixnum.Int64 get unboundSockets => $_getI64(8); + @$pb.TagNumber(9) + set unboundSockets($fixnum.Int64 value) => $_setInt64(8, value); + @$pb.TagNumber(9) + $core.bool hasUnboundSockets() => $_has(8); + @$pb.TagNumber(9) + void clearUnboundSockets() => $_clearField(9); } class Evidence extends $pb.GeneratedMessage { diff --git a/gen/dart/xtcp_treatment/v1/treatment.pbjson.dart b/gen/dart/xtcp_treatment/v1/treatment.pbjson.dart index 0a10a0873..44fea2b21 100644 --- a/gen/dart/xtcp_treatment/v1/treatment.pbjson.dart +++ b/gen/dart/xtcp_treatment/v1/treatment.pbjson.dart @@ -394,6 +394,14 @@ const Capabilities$json = { '10': 'simpleHostRouting' }, {'1': 'actuator_enabled', '3': 5, '4': 1, '5': 8, '10': 'actuatorEnabled'}, + { + '1': 'collection_id', + '3': 6, + '4': 1, + '5': 9, + '8': {}, + '10': 'collectionId' + }, ], }; @@ -404,7 +412,8 @@ final $typed_data.Uint8List capabilitiesDescriptor = $convert.base64Decode( 'cgMYgAJSDWtlcm5lbFJlbGVhc2USQwoVY29uZ2VzdGlvbl9hbGdvcml0aG1zGAMgAygJQg66SA' 'uSAQgQQCIEcgIYD1IUY29uZ2VzdGlvbkFsZ29yaXRobXMSLgoTc2ltcGxlX2hvc3Rfcm91dGlu' 'ZxgEIAEoCFIRc2ltcGxlSG9zdFJvdXRpbmcSKQoQYWN0dWF0b3JfZW5hYmxlZBgFIAEoCFIPYW' - 'N0dWF0b3JFbmFibGVk'); + 'N0dWF0b3JFbmFibGVkEjoKDWNvbGxlY3Rpb25faWQYBiABKAlCFbpIEnIQMg5eWzAtOWEtZl17' + 'MzJ9JFIMY29sbGVjdGlvbklk'); @$core.Deprecated('Use applicationRecordDescriptor instead') const ApplicationRecord$json = { @@ -587,6 +596,7 @@ const HostEvidence$json = { '5': 8, '10': 'collectionHealthy' }, + {'1': 'unbound_sockets', '3': 9, '4': 1, '5': 4, '10': 'unboundSockets'}, ], '7': {}, }; @@ -600,9 +610,10 @@ final $typed_data.Uint8List hostEvidenceDescriptor = $convert.base64Decode( 'KAFCELpIDRILQAEpAAAAAAAAAABSE3JldHJhbnNtaXNzaW9uUmF0aW8SLgoKcnR0X3A5NV9tcx' 'gGIAEoAUIQukgNEgtAASkAAAAAAAAAAFIIcnR0UDk1TXMSJwoPZXhwb3NlZF9zb2NrZXRzGAcg' 'ASgEUg5leHBvc2VkU29ja2V0cxItChJjb2xsZWN0aW9uX2hlYWx0aHkYCCABKAhSEWNvbGxlY3' - 'Rpb25IZWFsdGh5Oni6SHUacwokaG9zdF9ldmlkZW5jZS5leHBvc2VkX3dpdGhpbl9zb2NrZXRz' - 'EiVleHBvc2VkX3NvY2tldHMgY2Fubm90IGV4Y2VlZCBzb2NrZXRzGiR0aGlzLmV4cG9zZWRfc2' - '9ja2V0cyA8PSB0aGlzLnNvY2tldHM='); + 'Rpb25IZWFsdGh5EicKD3VuYm91bmRfc29ja2V0cxgJIAEoBFIOdW5ib3VuZFNvY2tldHM6eLpI' + 'dRpzCiRob3N0X2V2aWRlbmNlLmV4cG9zZWRfd2l0aGluX3NvY2tldHMSJWV4cG9zZWRfc29ja2' + 'V0cyBjYW5ub3QgZXhjZWVkIHNvY2tldHMaJHRoaXMuZXhwb3NlZF9zb2NrZXRzIDw9IHRoaXMu' + 'c29ja2V0cw=='); @$core.Deprecated('Use evidenceDescriptor instead') const Evidence$json = { diff --git a/gen/dart/xtcp_treatment_journal/v1/journal.pb.dart b/gen/dart/xtcp_treatment_journal/v1/journal.pb.dart index 6f9bef0c3..f1c732af0 100644 --- a/gen/dart/xtcp_treatment_journal/v1/journal.pb.dart +++ b/gen/dart/xtcp_treatment_journal/v1/journal.pb.dart @@ -717,6 +717,7 @@ class Journal extends $pb.GeneratedMessage { $core.String? suspensionReason, $core.Iterable? records, $0.Timestamp? updatedAt, + $core.String? collectionId, }) { final result = Journal._(); if (agentId != null) result.agentId = agentId; @@ -727,6 +728,7 @@ class Journal extends $pb.GeneratedMessage { if (suspensionReason != null) result.suspensionReason = suspensionReason; if (records != null) result.records.addAll(records); if (updatedAt != null) result.updatedAt = updatedAt; + if (collectionId != null) result.collectionId = collectionId; return result; } @@ -755,6 +757,7 @@ class Journal extends $pb.GeneratedMessage { subBuilder: Record.$_createMessage) ..aOM<$0.Timestamp>(8, _omitFieldNames ? '' : 'updatedAt', subBuilder: $0.Timestamp.$_createMessage) + ..aOS(9, _omitFieldNames ? '' : 'collectionId') ..hasRequiredFields = false; @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @@ -848,6 +851,18 @@ class Journal extends $pb.GeneratedMessage { void clearUpdatedAt() => $_clearField(8); @$pb.TagNumber(8) $0.Timestamp ensureUpdatedAt() => $_ensure(7); + + /// Per-process nonce of the agent that owns the journal. The agent writes + /// it; the watchdog preserves it; a telemetry-only daemon stamps it into + /// records as collection_id. + @$pb.TagNumber(9) + $core.String get collectionId => $_getSZ(8); + @$pb.TagNumber(9) + set collectionId($core.String value) => $_setString(8, value); + @$pb.TagNumber(9) + $core.bool hasCollectionId() => $_has(8); + @$pb.TagNumber(9) + void clearCollectionId() => $_clearField(9); } const $core.bool _omitFieldNames = diff --git a/gen/dart/xtcp_treatment_journal/v1/journal.pbjson.dart b/gen/dart/xtcp_treatment_journal/v1/journal.pbjson.dart index 1db4e94cc..a89954db8 100644 --- a/gen/dart/xtcp_treatment_journal/v1/journal.pbjson.dart +++ b/gen/dart/xtcp_treatment_journal/v1/journal.pbjson.dart @@ -375,6 +375,7 @@ const Journal$json = { '6': '.google.protobuf.Timestamp', '10': 'updatedAt' }, + {'1': 'collection_id', '3': 9, '4': 1, '5': 9, '10': 'collectionId'}, ], }; @@ -386,4 +387,5 @@ final $typed_data.Uint8List journalDescriptor = $convert.base64Decode( 'BlbmRlZBgFIAEoCFIQbG9jYWxseVN1c3BlbmRlZBIrChFzdXNwZW5zaW9uX3JlYXNvbhgGIAEo' 'CVIQc3VzcGVuc2lvblJlYXNvbhI7CgdyZWNvcmRzGAcgAygLMiEueHRjcF90cmVhdG1lbnRfam' '91cm5hbC52MS5SZWNvcmRSB3JlY29yZHMSOQoKdXBkYXRlZF9hdBgIIAEoCzIaLmdvb2dsZS5w' - 'cm90b2J1Zi5UaW1lc3RhbXBSCXVwZGF0ZWRBdA=='); + 'cm90b2J1Zi5UaW1lc3RhbXBSCXVwZGF0ZWRBdBIjCg1jb2xsZWN0aW9uX2lkGAkgASgJUgxjb2' + 'xsZWN0aW9uSWQ='); diff --git a/gen/go/xtcp_treatment/treatment.pb.go b/gen/go/xtcp_treatment/treatment.pb.go index b1ebb79d7..8f82e4dd9 100644 --- a/gen/go/xtcp_treatment/treatment.pb.go +++ b/gen/go/xtcp_treatment/treatment.pb.go @@ -729,8 +729,12 @@ type Capabilities struct { CongestionAlgorithms []string `protobuf:"bytes,3,rep,name=congestion_algorithms,json=congestionAlgorithms,proto3" json:"congestion_algorithms,omitempty"` SimpleHostRouting bool `protobuf:"varint,4,opt,name=simple_host_routing,json=simpleHostRouting,proto3" json:"simple_host_routing,omitempty"` ActuatorEnabled bool `protobuf:"varint,5,opt,name=actuator_enabled,json=actuatorEnabled,proto3" json:"actuator_enabled,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Per-process nonce the agent writes into its journal header and the + // daemon stamps into every record as collection_id. Evidence counts only + // records whose (agent_id, host_boot_id, collection_id) an agent reported. + CollectionId string `protobuf:"bytes,6,opt,name=collection_id,json=collectionId,proto3" json:"collection_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Capabilities) Reset() { @@ -798,6 +802,13 @@ func (x *Capabilities) GetActuatorEnabled() bool { return false } +func (x *Capabilities) GetCollectionId() string { + if x != nil { + return x.CollectionId + } + return "" +} + // ApplicationRecord is the agent's account of one assignment. Agents report // these on check-in with installed_route and installed_boot_id cleared. type ApplicationRecord struct { @@ -1021,8 +1032,12 @@ type HostEvidence struct { RttP95Ms float64 `protobuf:"fixed64,6,opt,name=rtt_p95_ms,json=rttP95Ms,proto3" json:"rtt_p95_ms,omitempty"` ExposedSockets uint64 `protobuf:"varint,7,opt,name=exposed_sockets,json=exposedSockets,proto3" json:"exposed_sockets,omitempty"` CollectionHealthy bool `protobuf:"varint,8,opt,name=collection_healthy,json=collectionHealthy,proto3" json:"collection_healthy,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Sockets stamped with this agent_id whose boot and collection nonce + // match no session the agent reported. They are excluded from every other + // field and counted here so a reviewer can see them. + UnboundSockets uint64 `protobuf:"varint,9,opt,name=unbound_sockets,json=unboundSockets,proto3" json:"unbound_sockets,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *HostEvidence) Reset() { @@ -1111,6 +1126,13 @@ func (x *HostEvidence) GetCollectionHealthy() bool { return false } +func (x *HostEvidence) GetUnboundSockets() uint64 { + if x != nil { + return x.UnboundSockets + } + return 0 +} + type Evidence struct { state protoimpl.MessageState `protogen:"open.v1"` Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` @@ -1647,13 +1669,14 @@ const file_xtcp_treatment_v1_treatment_proto_rawDesc = "" + "\brevision\x18\x02 \x01(\x04B\a\xbaH\x042\x02 \x00R\brevision\x12\x1c\n" + "\tsuspended\x18\x03 \x01(\bR\tsuspended\x12J\n" + "\vassignments\x18\x04 \x03(\v2\x1d.xtcp_treatment.v1.AssignmentB\t\xbaH\x06\x92\x01\x03\x10\x80 R\vassignments\x12?\n" + - "\tissued_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\bissuedAt\"\xa3\x02\n" + + "\tissued_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\bissuedAt\"\xdf\x02\n" + "\fCapabilities\x12B\n" + "\aboot_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x06bootId\x12/\n" + "\x0ekernel_release\x18\x02 \x01(\tB\b\xbaH\x05r\x03\x18\x80\x02R\rkernelRelease\x12C\n" + "\x15congestion_algorithms\x18\x03 \x03(\tB\x0e\xbaH\v\x92\x01\b\x10@\"\x04r\x02\x18\x0fR\x14congestionAlgorithms\x12.\n" + "\x13simple_host_routing\x18\x04 \x01(\bR\x11simpleHostRouting\x12)\n" + - "\x10actuator_enabled\x18\x05 \x01(\bR\x0factuatorEnabled\"\xa8\x05\n" + + "\x10actuator_enabled\x18\x05 \x01(\bR\x0factuatorEnabled\x12:\n" + + "\rcollection_id\x18\x06 \x01(\tB\x15\xbaH\x12r\x102\x0e^[0-9a-f]{32}$R\fcollectionId\"\xa8\x05\n" + "\x11ApplicationRecord\x12E\n" + "\n" + "assignment\x18\x01 \x01(\v2\x1d.xtcp_treatment.v1.AssignmentB\x06\xbaH\x03\xc8\x01\x01R\n" + @@ -1681,7 +1704,7 @@ const file_xtcp_treatment_v1_treatment_proto_rawDesc = "" + "\x11suspension_reason\x18\x03 \x01(\tB\b\xbaH\x05r\x03\x18\x80\bR\x10suspensionReason\x12I\n" + "\arecords\x18\x04 \x03(\v2$.xtcp_treatment.v1.ApplicationRecordB\t\xbaH\x06\x92\x01\x03\x10\x80@R\arecords\x129\n" + "\n" + - "updated_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\"\x89\x04\n" + + "updated_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\"\xb2\x04\n" + "\fHostEvidence\x12D\n" + "\bagent_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\aagentId\x12\x18\n" + "\asockets\x18\x02 \x01(\x04R\asockets\x12\x1f\n" + @@ -1693,7 +1716,8 @@ const file_xtcp_treatment_v1_treatment_proto_rawDesc = "" + "\n" + "rtt_p95_ms\x18\x06 \x01(\x01B\x10\xbaH\r\x12\v@\x01)\x00\x00\x00\x00\x00\x00\x00\x00R\brttP95Ms\x12'\n" + "\x0fexposed_sockets\x18\a \x01(\x04R\x0eexposedSockets\x12-\n" + - "\x12collection_healthy\x18\b \x01(\bR\x11collectionHealthy:x\xbaHu\x1as\n" + + "\x12collection_healthy\x18\b \x01(\bR\x11collectionHealthy\x12'\n" + + "\x0funbound_sockets\x18\t \x01(\x04R\x0eunboundSockets:x\xbaHu\x1as\n" + "$host_evidence.exposed_within_sockets\x12%exposed_sockets cannot exceed sockets\x1a$this.exposed_sockets <= this.sockets\"\xbc\x05\n" + "\bEvidence\x129\n" + "\x02id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x02id\x12C\n" + diff --git a/gen/go/xtcp_treatment/treatment_vtproto.pb.go b/gen/go/xtcp_treatment/treatment_vtproto.pb.go index f8e70c3b2..c36af2594 100644 --- a/gen/go/xtcp_treatment/treatment_vtproto.pb.go +++ b/gen/go/xtcp_treatment/treatment_vtproto.pb.go @@ -368,6 +368,13 @@ func (m *Capabilities) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if len(m.CollectionId) > 0 { + i -= len(m.CollectionId) + copy(dAtA[i:], m.CollectionId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.CollectionId))) + i-- + dAtA[i] = 0x32 + } if m.ActuatorEnabled { i-- if m.ActuatorEnabled { @@ -660,6 +667,11 @@ func (m *HostEvidence) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if m.UnboundSockets != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.UnboundSockets)) + i-- + dAtA[i] = 0x48 + } if m.CollectionHealthy { i-- if m.CollectionHealthy { @@ -1307,6 +1319,10 @@ func (m *Capabilities) SizeVT() (n int) { if m.ActuatorEnabled { n += 2 } + l = len(m.CollectionId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } n += len(m.unknownFields) return n } @@ -1428,6 +1444,9 @@ func (m *HostEvidence) SizeVT() (n int) { if m.CollectionHealthy { n += 2 } + if m.UnboundSockets != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.UnboundSockets)) + } n += len(m.unknownFields) return n } @@ -2605,6 +2624,38 @@ func (m *Capabilities) UnmarshalVT(dAtA []byte) error { } } m.ActuatorEnabled = bool(v != 0) + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CollectionId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CollectionId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -3430,6 +3481,25 @@ func (m *HostEvidence) UnmarshalVT(dAtA []byte) error { } } m.CollectionHealthy = bool(v != 0) + case 9: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field UnboundSockets", wireType) + } + m.UnboundSockets = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.UnboundSockets |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/gen/go/xtcp_treatment_journal/journal.pb.go b/gen/go/xtcp_treatment_journal/journal.pb.go index d5c35f61a..2ef98a786 100644 --- a/gen/go/xtcp_treatment_journal/journal.pb.go +++ b/gen/go/xtcp_treatment_journal/journal.pb.go @@ -631,8 +631,12 @@ type Journal struct { SuspensionReason string `protobuf:"bytes,6,opt,name=suspension_reason,json=suspensionReason,proto3" json:"suspension_reason,omitempty"` Records []*Record `protobuf:"bytes,7,rep,name=records,proto3" json:"records,omitempty"` UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Per-process nonce of the agent that owns the journal. The agent writes + // it; the watchdog preserves it; a telemetry-only daemon stamps it into + // records as collection_id. + CollectionId string `protobuf:"bytes,9,opt,name=collection_id,json=collectionId,proto3" json:"collection_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Journal) Reset() { @@ -721,6 +725,13 @@ func (x *Journal) GetUpdatedAt() *timestamppb.Timestamp { return nil } +func (x *Journal) GetCollectionId() string { + if x != nil { + return x.CollectionId + } + return "" +} + var File_xtcp_treatment_journal_v1_journal_proto protoreflect.FileDescriptor const file_xtcp_treatment_journal_v1_journal_proto_rawDesc = "" + @@ -782,7 +793,7 @@ const file_xtcp_treatment_journal_v1_journal_proto_rawDesc = "" + "\x0finstalled_route\x18\n" + " \x01(\fR\x0einstalledRoute\x12*\n" + "\x11installed_boot_id\x18\v \x01(\tR\x0finstalledBootId\x12'\n" + - "\x0fexpired_latched\x18\f \x01(\bR\x0eexpiredLatched\"\xe9\x02\n" + + "\x0fexpired_latched\x18\f \x01(\bR\x0eexpiredLatched\"\x8e\x03\n" + "\aJournal\x12\x19\n" + "\bagent_id\x18\x01 \x01(\tR\aagentId\x12\x1a\n" + "\blocation\x18\x02 \x01(\tR\blocation\x12\x17\n" + @@ -792,7 +803,8 @@ const file_xtcp_treatment_journal_v1_journal_proto_rawDesc = "" + "\x11suspension_reason\x18\x06 \x01(\tR\x10suspensionReason\x12;\n" + "\arecords\x18\a \x03(\v2!.xtcp_treatment_journal.v1.RecordR\arecords\x129\n" + "\n" + - "updated_at\x18\b \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt*\xa4\x01\n" + + "updated_at\x18\b \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\x12#\n" + + "\rcollection_id\x18\t \x01(\tR\fcollectionId*\xa4\x01\n" + "\x0eAssignmentMode\x12\x1f\n" + "\x1bASSIGNMENT_MODE_UNSPECIFIED\x10\x00\x12\x19\n" + "\x15ASSIGNMENT_MODE_TRIAL\x10\x01\x12\x1a\n" + diff --git a/gen/go/xtcp_treatment_journal/journal_vtproto.pb.go b/gen/go/xtcp_treatment_journal/journal_vtproto.pb.go index a57d3d035..3eeb5acf7 100644 --- a/gen/go/xtcp_treatment_journal/journal_vtproto.pb.go +++ b/gen/go/xtcp_treatment_journal/journal_vtproto.pb.go @@ -491,6 +491,13 @@ func (m *Journal) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if len(m.CollectionId) > 0 { + i -= len(m.CollectionId) + copy(dAtA[i:], m.CollectionId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.CollectionId))) + i-- + dAtA[i] = 0x4a + } if m.UpdatedAt != nil { size, err := (*timestamppb.Timestamp)(m.UpdatedAt).MarshalToSizedBufferVT(dAtA[:i]) if err != nil { @@ -776,6 +783,10 @@ func (m *Journal) SizeVT() (n int) { l = (*timestamppb.Timestamp)(m.UpdatedAt).SizeVT() n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } + l = len(m.CollectionId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } n += len(m.unknownFields) return n } @@ -2271,6 +2282,38 @@ func (m *Journal) UnmarshalVT(dAtA []byte) error { return err } iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CollectionId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CollectionId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/gen/python/xtcp_treatment/v1/treatment_pb2.py b/gen/python/xtcp_treatment/v1/treatment_pb2.py index 903ba9d94..9b4fa2a95 100644 --- a/gen/python/xtcp_treatment/v1/treatment_pb2.py +++ b/gen/python/xtcp_treatment/v1/treatment_pb2.py @@ -28,7 +28,7 @@ from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!xtcp_treatment/v1/treatment.proto\x12\x11xtcp_treatment.v1\x1a\x1b\x62uf/validate/validate.proto\x1a google/protobuf/descriptor.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\x8d\x07\n\x0bTCPSettings\x12Y\n\x12\x63ongestion_control\x18\x01 \x01(\tB%\xbaH\"r \x18\x0f\x32\x1c^[a-zA-Z0-9][a-zA-Z0-9_.-]*$H\x00R\x11\x63ongestionControl\x88\x01\x01\x12\'\n\x0f\x63ongestion_lock\x18\x02 \x01(\x08R\x0e\x63ongestionLock\x12.\n\nrto_min_ms\x18\x03 \x01(\rB\x0b\xbaH\x08*\x06\x18\xc0\xa9\x07(\x01H\x01R\x08rtoMinMs\x88\x01\x01\x12K\n\x19initial_congestion_window\x18\x04 \x01(\rB\n\xbaH\x07*\x05\x18\x80\x08(\x01H\x02R\x17initialCongestionWindow\x88\x01\x01\x12\x45\n\x16initial_receive_window\x18\x05 \x01(\rB\n\xbaH\x07*\x05\x18\x80\x08(\x01H\x03R\x14initialReceiveWindow\x88\x01\x01\x12 \n\tquick_ack\x18\x06 \x01(\x08H\x04R\x08quickAck\x88\x01\x01\x12\x15\n\x03\x65\x63n\x18\x07 \x01(\x08H\x05R\x03\x65\x63n\x88\x01\x01:\x87\x03\xbaH\x83\x03\x1a\xf3\x01\n\x16tcp_settings.non_empty\x12/a treatment must set at least one TCP parameter\x1a\xa7\x01has(this.congestion_control) || has(this.rto_min_ms) || has(this.initial_congestion_window) || has(this.initial_receive_window) || has(this.quick_ack) || has(this.ecn)\x1a\x8a\x01\n$tcp_settings.lock_requires_algorithm\x12+congestion_lock requires congestion_control\x1a\x35!this.congestion_lock || has(this.congestion_control)B\x15\n\x13_congestion_controlB\r\n\x0b_rto_min_msB\x1c\n\x1a_initial_congestion_windowB\x19\n\x17_initial_receive_windowB\x0c\n\n_quick_ackB\x06\n\x04_ecn\"\xd8\x03\n\tTreatment\x12\x39\n\x02id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x02id\x12#\n\x08revision\x18\x02 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\x08revision\x12,\n\x0b\x64\x65stination\x18\x03 \x01(\tB\n\xbaH\x07r\x05\x18\x31\xe8\x01\x01R\x0b\x64\x65stination\x12\x42\n\x08settings\x18\x04 \x01(\x0b\x32\x1e.xtcp_treatment.v1.TCPSettingsB\x06\xbaH\x03\xc8\x01\x01R\x08settings\x12.\n\x13\x65\x63n_fabric_verified\x18\x05 \x01(\x08R\x11\x65\x63nFabricVerified:\xc8\x01\xbaH\xc4\x01\x1a\xc1\x01\n\x1ctreatment.dctcp_requires_ecn\x12\x41\x44\x43TCP requires independently verified ECN fabric and explicit ECN\x1a^this.settings.congestion_control != \'dctcp\' || (this.ecn_fabric_verified && this.settings.ecn)\"\xcc\x05\n\nAssignment\x12N\n\rexperiment_id\x18\x02 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x0c\x65xperimentId\x12\x42\n\ttreatment\x18\x03 \x01(\x0b\x32\x1c.xtcp_treatment.v1.TreatmentB\x06\xbaH\x03\xc8\x01\x01R\ttreatment\x12\x41\n\x04mode\x18\x04 \x01(\x0e\x32!.xtcp_treatment.v1.AssignmentModeB\n\xbaH\x07\x82\x01\x04\x10\x01 \x00R\x04mode\x12\x44\n\x10lease_expires_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0eleaseExpiresAt\x12>\n\x0bpredecessor\x18\x06 \x01(\x0b\x32\x1c.xtcp_treatment.v1.TreatmentR\x0bpredecessor\x12\'\n\ngeneration\x18\x07 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\ngeneration:\xad\x02\xbaH\xa9\x02\x1a}\n\x1f\x61ssignment.trial_requires_lease\x12,a trial assignment requires lease_expires_at\x1a,this.mode != 1 || has(this.lease_expires_at)\x1a\xa7\x01\n\"assignment.predecessor_destination\x12+predecessor must cover the same destination\x1aT!has(this.predecessor) || this.predecessor.destination == this.treatment.destinationJ\x04\x08\x01\x10\x02R\x02id\"\xa4\x02\n\x0c\x44\x65siredState\x12\x44\n\x08\x61gent_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x07\x61gentId\x12#\n\x08revision\x18\x02 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\x08revision\x12\x1c\n\tsuspended\x18\x03 \x01(\x08R\tsuspended\x12J\n\x0b\x61ssignments\x18\x04 \x03(\x0b\x32\x1d.xtcp_treatment.v1.AssignmentB\t\xbaH\x06\x92\x01\x03\x10\x80 R\x0b\x61ssignments\x12?\n\tissued_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\x08issuedAt\"\xa3\x02\n\x0c\x43\x61pabilities\x12\x42\n\x07\x62oot_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x06\x62ootId\x12/\n\x0ekernel_release\x18\x02 \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x02R\rkernelRelease\x12\x43\n\x15\x63ongestion_algorithms\x18\x03 \x03(\tB\x0e\xbaH\x0b\x92\x01\x08\x10@\"\x04r\x02\x18\x0fR\x14\x63ongestionAlgorithms\x12.\n\x13simple_host_routing\x18\x04 \x01(\x08R\x11simpleHostRouting\x12)\n\x10\x61\x63tuator_enabled\x18\x05 \x01(\x08R\x0f\x61\x63tuatorEnabled\"\xa8\x05\n\x11\x41pplicationRecord\x12\x45\n\nassignment\x18\x01 \x01(\x0b\x32\x1d.xtcp_treatment.v1.AssignmentB\x06\xbaH\x03\xc8\x01\x01R\nassignment\x12)\n\x10\x64\x65sired_revision\x18\x02 \x01(\x04R\x0f\x64\x65siredRevision\x12@\n\x06status\x18\x03 \x01(\x0e\x32\x1e.xtcp_treatment.v1.ApplyStatusB\x08\xbaH\x05\x82\x01\x02\x10\x01R\x06status\x12;\n\x0breceived_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\nreceivedAt\x12=\n\x0c\x61ttempted_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0b\x61ttemptedAt\x12\x39\n\napplied_at\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tappliedAt\x12;\n\x0bverified_at\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\nverifiedAt\x12\x39\n\nremoved_at\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tremovedAt\x12\x1e\n\x05\x65rror\x18\t \x01(\tB\x08\xbaH\x05r\x03\x18\x80 R\x05\x65rror\x12\x31\n\x0finstalled_route\x18\n \x01(\x0c\x42\x08\xbaH\x05z\x03\x18\x80 R\x0einstalledRoute\x12\x34\n\x11installed_boot_id\x18\x0b \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x01R\x0finstalledBootId\x12\'\n\x0f\x65xpired_latched\x18\x0c \x01(\x08R\x0e\x65xpiredLatched\"\xb1\x02\n\nLocalState\x12\x39\n\x07\x64\x65sired\x18\x01 \x01(\x0b\x32\x1f.xtcp_treatment.v1.DesiredStateR\x07\x64\x65sired\x12+\n\x11locally_suspended\x18\x02 \x01(\x08R\x10locallySuspended\x12\x35\n\x11suspension_reason\x18\x03 \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x08R\x10suspensionReason\x12I\n\x07records\x18\x04 \x03(\x0b\x32$.xtcp_treatment.v1.ApplicationRecordB\t\xbaH\x06\x92\x01\x03\x10\x80@R\x07records\x12\x39\n\nupdated_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tupdatedAt\"\x89\x04\n\x0cHostEvidence\x12\x44\n\x08\x61gent_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x07\x61gentId\x12\x18\n\x07sockets\x18\x02 \x01(\x04R\x07sockets\x12\x1f\n\x0b\x62ytes_acked\x18\x03 \x01(\x04R\nbytesAcked\x12\x31\n\x0bgoodput_bps\x18\x04 \x01(\x01\x42\x10\xbaH\r\x12\x0b)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\ngoodputBps\x12\x43\n\x14retransmission_ratio\x18\x05 \x01(\x01\x42\x10\xbaH\r\x12\x0b)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x13retransmissionRatio\x12.\n\nrtt_p95_ms\x18\x06 \x01(\x01\x42\x10\xbaH\r\x12\x0b)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x08rttP95Ms\x12\'\n\x0f\x65xposed_sockets\x18\x07 \x01(\x04R\x0e\x65xposedSockets\x12-\n\x12\x63ollection_healthy\x18\x08 \x01(\x08R\x11\x63ollectionHealthy:x\xbaHu\x1as\n$host_evidence.exposed_within_sockets\x12%exposed_sockets cannot exceed sockets\x1a$this.exposed_sockets <= this.sockets\"\xbc\x05\n\x08\x45vidence\x12\x39\n\x02id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x02id\x12\x43\n\x0b\x61nalyzed_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\nanalyzedAt\x12\x45\n\x0cwindow_start\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\x0bwindowStart\x12\x41\n\nwindow_end\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\twindowEnd\x12/\n\rquery_version\x18\x05 \x01(\tB\n\xbaH\x07r\x05\x10\x01\x18\x80\x02R\x0cqueryVersion\x12\x37\n\x12\x65nrichment_version\x18\x06 \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x02R\x11\x65nrichmentVersion\x12\x42\n\x05hosts\x18\x07 \x03(\x0b\x32\x1f.xtcp_treatment.v1.HostEvidenceB\x0b\xbaH\x08\x92\x01\x05\x08\x01\x10\x90NR\x05hosts\x12>\n\x0f\x61rtifact_sha256\x18\x08 \x01(\tB\x15\xbaH\x12r\x10\x32\x0e^[0-9a-f]{64}$R\x0e\x61rtifactSha256:\xb7\x01\xbaH\xb3\x01\x1a\xb0\x01\n\x11\x65vidence.interval\x12Owindow_end must follow window_start and analyzed_at must not precede window_end\x1aJthis.window_end > this.window_start && this.analyzed_at >= this.window_end\"\xec\x06\n\x10\x45xperimentPolicy\x12]\n\x13minimum_observation\x18\x01 \x01(\x0b\x32\x19.google.protobuf.DurationB\x11\xbaH\x0e\xaa\x01\x08\"\x04\x08\x80\xf5$*\x00\xc8\x01\x01R\x12minimumObservation\x12Q\n\rmaximum_phase\x18\x02 \x01(\x0b\x32\x19.google.protobuf.DurationB\x11\xbaH\x0e\xaa\x01\x08\"\x04\x08\x80\xf5$*\x00\xc8\x01\x01R\x0cmaximumPhase\x12@\n\x18minimum_sockets_per_host\x18\x03 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\x15minimumSocketsPerHost\x12<\n\x16minimum_bytes_per_host\x18\x04 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\x13minimumBytesPerHost\x12>\n\x15minimum_treated_hosts\x18\x05 \x01(\rB\n\xbaH\x07*\x05\x18\x90N(\x02R\x13minimumTreatedHosts\x12J\n\x13minimum_improvement\x18\x06 \x01(\x01\x42\x19\xbaH\x16\x12\x14\x19\x00\x00\x00\x00\x00\x00\xf0?)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x12minimumImprovement\x12\x61\n\x1fmaximum_retransmission_increase\x18\x07 \x01(\x01\x42\x19\xbaH\x16\x12\x14\x19\x00\x00\x00\x00\x00\x00\xf0?)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x1dmaximumRetransmissionIncrease\x12K\n\x14maximum_rtt_increase\x18\x08 \x01(\x01\x42\x19\xbaH\x16\x12\x14\x19\x00\x00\x00\x00\x00\x00\xf0?)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x12maximumRttIncrease\x12^\n\x14maximum_evidence_age\x18\t \x01(\x0b\x32\x19.google.protobuf.DurationB\x11\xbaH\x0e\xaa\x01\x08\"\x04\x08\x80\xf5$*\x00\xc8\x01\x01R\x12maximumEvidenceAge:\x89\x01\xbaH\x85\x01\x1a\x82\x01\n\x1fpolicy.observation_within_phase\x12/minimum_observation cannot exceed maximum_phase\x1a.this.minimum_observation <= this.maximum_phase\"\xc1\x03\n\x05Phase\x12-\n\x0ctarget_hosts\x18\x01 \x01(\rB\n\xbaH\x07*\x05\x18\x90N(\x01R\x0btargetHosts\x12\x37\n\tissued_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x08issuedAt\x12P\n\x16observation_started_at\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x14observationStartedAt\x12=\n\x0c\x65valuated_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0b\x65valuatedAt\x12\x37\n\x08\x65vidence\x18\x05 \x01(\x0b\x32\x1b.xtcp_treatment.v1.EvidenceR\x08\x65vidence\x12>\n\x07outcome\x18\x06 \x01(\x0e\x32\x1a.xtcp_treatment.v1.OutcomeB\x08\xbaH\x05\x82\x01\x02\x10\x01R\x07outcome\x12$\n\x08reviewer\x18\x07 \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x01R\x08reviewer\x12 \n\x06reason\x18\x08 \x01(\tB\x08\xbaH\x05r\x03\x18\x80 R\x06reason\"\x88\x07\n\nExperiment\x12\x39\n\x02id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x02id\x12\x45\n\x08location\x18\x02 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x08location\x12*\n\nhypothesis\x18\x03 \x01(\tB\n\xbaH\x07r\x05\x10\x01\x18\x80@R\nhypothesis\x12\x42\n\ttreatment\x18\x04 \x01(\x0b\x32\x1c.xtcp_treatment.v1.TreatmentB\x06\xbaH\x03\xc8\x01\x01R\ttreatment\x12\x43\n\x06policy\x18\x05 \x01(\x0b\x32#.xtcp_treatment.v1.ExperimentPolicyB\x06\xbaH\x03\xc8\x01\x01R\x06policy\x12?\n\x08\x62\x61seline\x18\x06 \x01(\x0b\x32\x1b.xtcp_treatment.v1.EvidenceB\x06\xbaH\x03\xc8\x01\x01R\x08\x62\x61seline\x12X\n\x0c\x63ohort_order\x18\x07 \x03(\tB5\xbaH2\x92\x01/\x08\x03\x10\x90N\x18\x01\"&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x0b\x63ohortOrder\x12:\n\x06phases\x18\x08 \x03(\x0b\x32\x18.xtcp_treatment.v1.PhaseB\x08\xbaH\x05\x92\x01\x02\x10@R\x06phases\x12\x45\n\x06status\x18\t \x01(\x0e\x32#.xtcp_treatment.v1.ExperimentStatusB\x08\xbaH\x05\x82\x01\x02\x10\x01R\x06status\x12\x1a\n\x08revision\x18\n \x01(\x04R\x08revision\x12;\n\x0bproposed_at\x18\x0b \x01(\x0b\x32\x1a.google.protobuf.TimestampR\nproposedAt\x12\x44\n\x0flatest_evidence\x18\x0c \x01(\x0b\x32\x1b.xtcp_treatment.v1.EvidenceR\x0elatestEvidence\x12K\n\x0elatest_outcome\x18\r \x01(\x0e\x32\x1a.xtcp_treatment.v1.OutcomeB\x08\xbaH\x05\x82\x01\x02\x10\x01R\rlatestOutcome\x12\x39\n\nupdated_at\x18\x0e \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tupdatedAt*?\n\x04Role\x12\x14\n\x10ROLE_UNSPECIFIED\x10\x00\x12\x0e\n\nROLE_AGENT\x10\x01\x12\x11\n\rROLE_OPERATOR\x10\x02*\xa4\x01\n\x0e\x41ssignmentMode\x12\x1f\n\x1b\x41SSIGNMENT_MODE_UNSPECIFIED\x10\x00\x12\x19\n\x15\x41SSIGNMENT_MODE_TRIAL\x10\x01\x12\x1a\n\x16\x41SSIGNMENT_MODE_STABLE\x10\x02\x12\x1d\n\x19\x41SSIGNMENT_MODE_SUSPENDED\x10\x03\x12\x1b\n\x17\x41SSIGNMENT_MODE_RETIRED\x10\x04*\xad\x01\n\x0b\x41pplyStatus\x12\x1c\n\x18\x41PPLY_STATUS_UNSPECIFIED\x10\x00\x12\x18\n\x14\x41PPLY_STATUS_PENDING\x10\x01\x12\x19\n\x15\x41PPLY_STATUS_VERIFIED\x10\x02\x12\x18\n\x14\x41PPLY_STATUS_REMOVED\x10\x03\x12\x18\n\x14\x41PPLY_STATUS_EXPIRED\x10\x04\x12\x17\n\x13\x41PPLY_STATUS_FAILED\x10\x05*\xa4\x04\n\x07Outcome\x12\x17\n\x13OUTCOME_UNSPECIFIED\x10\x00\x12\x14\n\x10OUTCOME_BASELINE\x10\x01\x12 \n\x1cOUTCOME_AWAITING_APPLICATION\x10\x02\x12\x1d\n\x19OUTCOME_AWAITING_EXPOSURE\x10\x03\x12!\n\x1dOUTCOME_INSUFFICIENT_DURATION\x10\x04\x12\x1d\n\x19OUTCOME_INSUFFICIENT_DATA\x10\x05\x12!\n\x1dOUTCOME_INSUFFICIENT_CONTROLS\x10\x06\x12\x11\n\rOUTCOME_STALE\x10\x07\x12\x17\n\x13OUTCOME_UNAVAILABLE\x10\x08\x12\x18\n\x14OUTCOME_INCONCLUSIVE\x10\t\x12\x17\n\x13OUTCOME_SAFETY_ONLY\x10\n\x12\x18\n\x14OUTCOME_FINAL_SAFETY\x10\x0b\x12\x16\n\x12OUTCOME_BENEFICIAL\x10\x0c\x12\x13\n\x0fOUTCOME_HARMFUL\x10\r\x12\x13\n\x0fOUTCOME_EXPIRED\x10\x0e\x12\x1b\n\x17OUTCOME_STABLE_OBSERVED\x10\x0f\x12&\n\"OUTCOME_STABLE_EVIDENCE_INCOMPLETE\x10\x10\x12-\n)OUTCOME_STABLE_REGRESSION_REVIEW_REQUIRED\x10\x11\x12\x15\n\x11OUTCOME_OBSERVING\x10\x12*\x95\x02\n\x10\x45xperimentStatus\x12!\n\x1d\x45XPERIMENT_STATUS_UNSPECIFIED\x10\x00\x12\x1e\n\x1a\x45XPERIMENT_STATUS_PROPOSED\x10\x01\x12\x1d\n\x19\x45XPERIMENT_STATUS_RUNNING\x10\x02\x12\x1c\n\x18\x45XPERIMENT_STATUS_PAUSED\x10\x03\x12\x1f\n\x1b\x45XPERIMENT_STATUS_SUSPENDED\x10\x04\x12\x1e\n\x1a\x45XPERIMENT_STATUS_COMPLETE\x10\x05\x12\x1d\n\x19\x45XPERIMENT_STATUS_RETIRED\x10\x06\x12!\n\x1d\x45XPERIMENT_STATUS_ROLLED_BACK\x10\x07:M\n\x04role\x12\x1e.google.protobuf.MethodOptions\x18\xba\x8e\x03 \x01(\x0e\x32\x17.xtcp_treatment.v1.RoleR\x04roleB\xc0\x01\n\x15\x63om.xtcp_treatment.v1B\x0eTreatmentProtoP\x01Z6github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment\xa2\x02\x03XXX\xaa\x02\x10XtcpTreatment.V1\xca\x02\x10XtcpTreatment\\V1\xe2\x02\x1cXtcpTreatment\\V1\\GPBMetadata\xea\x02\x11XtcpTreatment::V1b\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!xtcp_treatment/v1/treatment.proto\x12\x11xtcp_treatment.v1\x1a\x1b\x62uf/validate/validate.proto\x1a google/protobuf/descriptor.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\x8d\x07\n\x0bTCPSettings\x12Y\n\x12\x63ongestion_control\x18\x01 \x01(\tB%\xbaH\"r \x18\x0f\x32\x1c^[a-zA-Z0-9][a-zA-Z0-9_.-]*$H\x00R\x11\x63ongestionControl\x88\x01\x01\x12\'\n\x0f\x63ongestion_lock\x18\x02 \x01(\x08R\x0e\x63ongestionLock\x12.\n\nrto_min_ms\x18\x03 \x01(\rB\x0b\xbaH\x08*\x06\x18\xc0\xa9\x07(\x01H\x01R\x08rtoMinMs\x88\x01\x01\x12K\n\x19initial_congestion_window\x18\x04 \x01(\rB\n\xbaH\x07*\x05\x18\x80\x08(\x01H\x02R\x17initialCongestionWindow\x88\x01\x01\x12\x45\n\x16initial_receive_window\x18\x05 \x01(\rB\n\xbaH\x07*\x05\x18\x80\x08(\x01H\x03R\x14initialReceiveWindow\x88\x01\x01\x12 \n\tquick_ack\x18\x06 \x01(\x08H\x04R\x08quickAck\x88\x01\x01\x12\x15\n\x03\x65\x63n\x18\x07 \x01(\x08H\x05R\x03\x65\x63n\x88\x01\x01:\x87\x03\xbaH\x83\x03\x1a\xf3\x01\n\x16tcp_settings.non_empty\x12/a treatment must set at least one TCP parameter\x1a\xa7\x01has(this.congestion_control) || has(this.rto_min_ms) || has(this.initial_congestion_window) || has(this.initial_receive_window) || has(this.quick_ack) || has(this.ecn)\x1a\x8a\x01\n$tcp_settings.lock_requires_algorithm\x12+congestion_lock requires congestion_control\x1a\x35!this.congestion_lock || has(this.congestion_control)B\x15\n\x13_congestion_controlB\r\n\x0b_rto_min_msB\x1c\n\x1a_initial_congestion_windowB\x19\n\x17_initial_receive_windowB\x0c\n\n_quick_ackB\x06\n\x04_ecn\"\xd8\x03\n\tTreatment\x12\x39\n\x02id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x02id\x12#\n\x08revision\x18\x02 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\x08revision\x12,\n\x0b\x64\x65stination\x18\x03 \x01(\tB\n\xbaH\x07r\x05\x18\x31\xe8\x01\x01R\x0b\x64\x65stination\x12\x42\n\x08settings\x18\x04 \x01(\x0b\x32\x1e.xtcp_treatment.v1.TCPSettingsB\x06\xbaH\x03\xc8\x01\x01R\x08settings\x12.\n\x13\x65\x63n_fabric_verified\x18\x05 \x01(\x08R\x11\x65\x63nFabricVerified:\xc8\x01\xbaH\xc4\x01\x1a\xc1\x01\n\x1ctreatment.dctcp_requires_ecn\x12\x41\x44\x43TCP requires independently verified ECN fabric and explicit ECN\x1a^this.settings.congestion_control != \'dctcp\' || (this.ecn_fabric_verified && this.settings.ecn)\"\xcc\x05\n\nAssignment\x12N\n\rexperiment_id\x18\x02 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x0c\x65xperimentId\x12\x42\n\ttreatment\x18\x03 \x01(\x0b\x32\x1c.xtcp_treatment.v1.TreatmentB\x06\xbaH\x03\xc8\x01\x01R\ttreatment\x12\x41\n\x04mode\x18\x04 \x01(\x0e\x32!.xtcp_treatment.v1.AssignmentModeB\n\xbaH\x07\x82\x01\x04\x10\x01 \x00R\x04mode\x12\x44\n\x10lease_expires_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0eleaseExpiresAt\x12>\n\x0bpredecessor\x18\x06 \x01(\x0b\x32\x1c.xtcp_treatment.v1.TreatmentR\x0bpredecessor\x12\'\n\ngeneration\x18\x07 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\ngeneration:\xad\x02\xbaH\xa9\x02\x1a}\n\x1f\x61ssignment.trial_requires_lease\x12,a trial assignment requires lease_expires_at\x1a,this.mode != 1 || has(this.lease_expires_at)\x1a\xa7\x01\n\"assignment.predecessor_destination\x12+predecessor must cover the same destination\x1aT!has(this.predecessor) || this.predecessor.destination == this.treatment.destinationJ\x04\x08\x01\x10\x02R\x02id\"\xa4\x02\n\x0c\x44\x65siredState\x12\x44\n\x08\x61gent_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x07\x61gentId\x12#\n\x08revision\x18\x02 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\x08revision\x12\x1c\n\tsuspended\x18\x03 \x01(\x08R\tsuspended\x12J\n\x0b\x61ssignments\x18\x04 \x03(\x0b\x32\x1d.xtcp_treatment.v1.AssignmentB\t\xbaH\x06\x92\x01\x03\x10\x80 R\x0b\x61ssignments\x12?\n\tissued_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\x08issuedAt\"\xdf\x02\n\x0c\x43\x61pabilities\x12\x42\n\x07\x62oot_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x06\x62ootId\x12/\n\x0ekernel_release\x18\x02 \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x02R\rkernelRelease\x12\x43\n\x15\x63ongestion_algorithms\x18\x03 \x03(\tB\x0e\xbaH\x0b\x92\x01\x08\x10@\"\x04r\x02\x18\x0fR\x14\x63ongestionAlgorithms\x12.\n\x13simple_host_routing\x18\x04 \x01(\x08R\x11simpleHostRouting\x12)\n\x10\x61\x63tuator_enabled\x18\x05 \x01(\x08R\x0f\x61\x63tuatorEnabled\x12:\n\rcollection_id\x18\x06 \x01(\tB\x15\xbaH\x12r\x10\x32\x0e^[0-9a-f]{32}$R\x0c\x63ollectionId\"\xa8\x05\n\x11\x41pplicationRecord\x12\x45\n\nassignment\x18\x01 \x01(\x0b\x32\x1d.xtcp_treatment.v1.AssignmentB\x06\xbaH\x03\xc8\x01\x01R\nassignment\x12)\n\x10\x64\x65sired_revision\x18\x02 \x01(\x04R\x0f\x64\x65siredRevision\x12@\n\x06status\x18\x03 \x01(\x0e\x32\x1e.xtcp_treatment.v1.ApplyStatusB\x08\xbaH\x05\x82\x01\x02\x10\x01R\x06status\x12;\n\x0breceived_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\nreceivedAt\x12=\n\x0c\x61ttempted_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0b\x61ttemptedAt\x12\x39\n\napplied_at\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tappliedAt\x12;\n\x0bverified_at\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\nverifiedAt\x12\x39\n\nremoved_at\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tremovedAt\x12\x1e\n\x05\x65rror\x18\t \x01(\tB\x08\xbaH\x05r\x03\x18\x80 R\x05\x65rror\x12\x31\n\x0finstalled_route\x18\n \x01(\x0c\x42\x08\xbaH\x05z\x03\x18\x80 R\x0einstalledRoute\x12\x34\n\x11installed_boot_id\x18\x0b \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x01R\x0finstalledBootId\x12\'\n\x0f\x65xpired_latched\x18\x0c \x01(\x08R\x0e\x65xpiredLatched\"\xb1\x02\n\nLocalState\x12\x39\n\x07\x64\x65sired\x18\x01 \x01(\x0b\x32\x1f.xtcp_treatment.v1.DesiredStateR\x07\x64\x65sired\x12+\n\x11locally_suspended\x18\x02 \x01(\x08R\x10locallySuspended\x12\x35\n\x11suspension_reason\x18\x03 \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x08R\x10suspensionReason\x12I\n\x07records\x18\x04 \x03(\x0b\x32$.xtcp_treatment.v1.ApplicationRecordB\t\xbaH\x06\x92\x01\x03\x10\x80@R\x07records\x12\x39\n\nupdated_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tupdatedAt\"\xb2\x04\n\x0cHostEvidence\x12\x44\n\x08\x61gent_id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x07\x61gentId\x12\x18\n\x07sockets\x18\x02 \x01(\x04R\x07sockets\x12\x1f\n\x0b\x62ytes_acked\x18\x03 \x01(\x04R\nbytesAcked\x12\x31\n\x0bgoodput_bps\x18\x04 \x01(\x01\x42\x10\xbaH\r\x12\x0b)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\ngoodputBps\x12\x43\n\x14retransmission_ratio\x18\x05 \x01(\x01\x42\x10\xbaH\r\x12\x0b)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x13retransmissionRatio\x12.\n\nrtt_p95_ms\x18\x06 \x01(\x01\x42\x10\xbaH\r\x12\x0b)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x08rttP95Ms\x12\'\n\x0f\x65xposed_sockets\x18\x07 \x01(\x04R\x0e\x65xposedSockets\x12-\n\x12\x63ollection_healthy\x18\x08 \x01(\x08R\x11\x63ollectionHealthy\x12\'\n\x0funbound_sockets\x18\t \x01(\x04R\x0eunboundSockets:x\xbaHu\x1as\n$host_evidence.exposed_within_sockets\x12%exposed_sockets cannot exceed sockets\x1a$this.exposed_sockets <= this.sockets\"\xbc\x05\n\x08\x45vidence\x12\x39\n\x02id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x02id\x12\x43\n\x0b\x61nalyzed_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\nanalyzedAt\x12\x45\n\x0cwindow_start\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\x0bwindowStart\x12\x41\n\nwindow_end\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x06\xbaH\x03\xc8\x01\x01R\twindowEnd\x12/\n\rquery_version\x18\x05 \x01(\tB\n\xbaH\x07r\x05\x10\x01\x18\x80\x02R\x0cqueryVersion\x12\x37\n\x12\x65nrichment_version\x18\x06 \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x02R\x11\x65nrichmentVersion\x12\x42\n\x05hosts\x18\x07 \x03(\x0b\x32\x1f.xtcp_treatment.v1.HostEvidenceB\x0b\xbaH\x08\x92\x01\x05\x08\x01\x10\x90NR\x05hosts\x12>\n\x0f\x61rtifact_sha256\x18\x08 \x01(\tB\x15\xbaH\x12r\x10\x32\x0e^[0-9a-f]{64}$R\x0e\x61rtifactSha256:\xb7\x01\xbaH\xb3\x01\x1a\xb0\x01\n\x11\x65vidence.interval\x12Owindow_end must follow window_start and analyzed_at must not precede window_end\x1aJthis.window_end > this.window_start && this.analyzed_at >= this.window_end\"\xec\x06\n\x10\x45xperimentPolicy\x12]\n\x13minimum_observation\x18\x01 \x01(\x0b\x32\x19.google.protobuf.DurationB\x11\xbaH\x0e\xaa\x01\x08\"\x04\x08\x80\xf5$*\x00\xc8\x01\x01R\x12minimumObservation\x12Q\n\rmaximum_phase\x18\x02 \x01(\x0b\x32\x19.google.protobuf.DurationB\x11\xbaH\x0e\xaa\x01\x08\"\x04\x08\x80\xf5$*\x00\xc8\x01\x01R\x0cmaximumPhase\x12@\n\x18minimum_sockets_per_host\x18\x03 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\x15minimumSocketsPerHost\x12<\n\x16minimum_bytes_per_host\x18\x04 \x01(\x04\x42\x07\xbaH\x04\x32\x02 \x00R\x13minimumBytesPerHost\x12>\n\x15minimum_treated_hosts\x18\x05 \x01(\rB\n\xbaH\x07*\x05\x18\x90N(\x02R\x13minimumTreatedHosts\x12J\n\x13minimum_improvement\x18\x06 \x01(\x01\x42\x19\xbaH\x16\x12\x14\x19\x00\x00\x00\x00\x00\x00\xf0?)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x12minimumImprovement\x12\x61\n\x1fmaximum_retransmission_increase\x18\x07 \x01(\x01\x42\x19\xbaH\x16\x12\x14\x19\x00\x00\x00\x00\x00\x00\xf0?)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x1dmaximumRetransmissionIncrease\x12K\n\x14maximum_rtt_increase\x18\x08 \x01(\x01\x42\x19\xbaH\x16\x12\x14\x19\x00\x00\x00\x00\x00\x00\xf0?)\x00\x00\x00\x00\x00\x00\x00\x00@\x01R\x12maximumRttIncrease\x12^\n\x14maximum_evidence_age\x18\t \x01(\x0b\x32\x19.google.protobuf.DurationB\x11\xbaH\x0e\xaa\x01\x08\"\x04\x08\x80\xf5$*\x00\xc8\x01\x01R\x12maximumEvidenceAge:\x89\x01\xbaH\x85\x01\x1a\x82\x01\n\x1fpolicy.observation_within_phase\x12/minimum_observation cannot exceed maximum_phase\x1a.this.minimum_observation <= this.maximum_phase\"\xc1\x03\n\x05Phase\x12-\n\x0ctarget_hosts\x18\x01 \x01(\rB\n\xbaH\x07*\x05\x18\x90N(\x01R\x0btargetHosts\x12\x37\n\tissued_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x08issuedAt\x12P\n\x16observation_started_at\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x14observationStartedAt\x12=\n\x0c\x65valuated_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0b\x65valuatedAt\x12\x37\n\x08\x65vidence\x18\x05 \x01(\x0b\x32\x1b.xtcp_treatment.v1.EvidenceR\x08\x65vidence\x12>\n\x07outcome\x18\x06 \x01(\x0e\x32\x1a.xtcp_treatment.v1.OutcomeB\x08\xbaH\x05\x82\x01\x02\x10\x01R\x07outcome\x12$\n\x08reviewer\x18\x07 \x01(\tB\x08\xbaH\x05r\x03\x18\x80\x01R\x08reviewer\x12 \n\x06reason\x18\x08 \x01(\tB\x08\xbaH\x05r\x03\x18\x80 R\x06reason\"\x88\x07\n\nExperiment\x12\x39\n\x02id\x18\x01 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x02id\x12\x45\n\x08location\x18\x02 \x01(\tB)\xbaH&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x08location\x12*\n\nhypothesis\x18\x03 \x01(\tB\n\xbaH\x07r\x05\x10\x01\x18\x80@R\nhypothesis\x12\x42\n\ttreatment\x18\x04 \x01(\x0b\x32\x1c.xtcp_treatment.v1.TreatmentB\x06\xbaH\x03\xc8\x01\x01R\ttreatment\x12\x43\n\x06policy\x18\x05 \x01(\x0b\x32#.xtcp_treatment.v1.ExperimentPolicyB\x06\xbaH\x03\xc8\x01\x01R\x06policy\x12?\n\x08\x62\x61seline\x18\x06 \x01(\x0b\x32\x1b.xtcp_treatment.v1.EvidenceB\x06\xbaH\x03\xc8\x01\x01R\x08\x62\x61seline\x12X\n\x0c\x63ohort_order\x18\x07 \x03(\tB5\xbaH2\x92\x01/\x08\x03\x10\x90N\x18\x01\"&r$2\"^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$R\x0b\x63ohortOrder\x12:\n\x06phases\x18\x08 \x03(\x0b\x32\x18.xtcp_treatment.v1.PhaseB\x08\xbaH\x05\x92\x01\x02\x10@R\x06phases\x12\x45\n\x06status\x18\t \x01(\x0e\x32#.xtcp_treatment.v1.ExperimentStatusB\x08\xbaH\x05\x82\x01\x02\x10\x01R\x06status\x12\x1a\n\x08revision\x18\n \x01(\x04R\x08revision\x12;\n\x0bproposed_at\x18\x0b \x01(\x0b\x32\x1a.google.protobuf.TimestampR\nproposedAt\x12\x44\n\x0flatest_evidence\x18\x0c \x01(\x0b\x32\x1b.xtcp_treatment.v1.EvidenceR\x0elatestEvidence\x12K\n\x0elatest_outcome\x18\r \x01(\x0e\x32\x1a.xtcp_treatment.v1.OutcomeB\x08\xbaH\x05\x82\x01\x02\x10\x01R\rlatestOutcome\x12\x39\n\nupdated_at\x18\x0e \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tupdatedAt*?\n\x04Role\x12\x14\n\x10ROLE_UNSPECIFIED\x10\x00\x12\x0e\n\nROLE_AGENT\x10\x01\x12\x11\n\rROLE_OPERATOR\x10\x02*\xa4\x01\n\x0e\x41ssignmentMode\x12\x1f\n\x1b\x41SSIGNMENT_MODE_UNSPECIFIED\x10\x00\x12\x19\n\x15\x41SSIGNMENT_MODE_TRIAL\x10\x01\x12\x1a\n\x16\x41SSIGNMENT_MODE_STABLE\x10\x02\x12\x1d\n\x19\x41SSIGNMENT_MODE_SUSPENDED\x10\x03\x12\x1b\n\x17\x41SSIGNMENT_MODE_RETIRED\x10\x04*\xad\x01\n\x0b\x41pplyStatus\x12\x1c\n\x18\x41PPLY_STATUS_UNSPECIFIED\x10\x00\x12\x18\n\x14\x41PPLY_STATUS_PENDING\x10\x01\x12\x19\n\x15\x41PPLY_STATUS_VERIFIED\x10\x02\x12\x18\n\x14\x41PPLY_STATUS_REMOVED\x10\x03\x12\x18\n\x14\x41PPLY_STATUS_EXPIRED\x10\x04\x12\x17\n\x13\x41PPLY_STATUS_FAILED\x10\x05*\xa4\x04\n\x07Outcome\x12\x17\n\x13OUTCOME_UNSPECIFIED\x10\x00\x12\x14\n\x10OUTCOME_BASELINE\x10\x01\x12 \n\x1cOUTCOME_AWAITING_APPLICATION\x10\x02\x12\x1d\n\x19OUTCOME_AWAITING_EXPOSURE\x10\x03\x12!\n\x1dOUTCOME_INSUFFICIENT_DURATION\x10\x04\x12\x1d\n\x19OUTCOME_INSUFFICIENT_DATA\x10\x05\x12!\n\x1dOUTCOME_INSUFFICIENT_CONTROLS\x10\x06\x12\x11\n\rOUTCOME_STALE\x10\x07\x12\x17\n\x13OUTCOME_UNAVAILABLE\x10\x08\x12\x18\n\x14OUTCOME_INCONCLUSIVE\x10\t\x12\x17\n\x13OUTCOME_SAFETY_ONLY\x10\n\x12\x18\n\x14OUTCOME_FINAL_SAFETY\x10\x0b\x12\x16\n\x12OUTCOME_BENEFICIAL\x10\x0c\x12\x13\n\x0fOUTCOME_HARMFUL\x10\r\x12\x13\n\x0fOUTCOME_EXPIRED\x10\x0e\x12\x1b\n\x17OUTCOME_STABLE_OBSERVED\x10\x0f\x12&\n\"OUTCOME_STABLE_EVIDENCE_INCOMPLETE\x10\x10\x12-\n)OUTCOME_STABLE_REGRESSION_REVIEW_REQUIRED\x10\x11\x12\x15\n\x11OUTCOME_OBSERVING\x10\x12*\x95\x02\n\x10\x45xperimentStatus\x12!\n\x1d\x45XPERIMENT_STATUS_UNSPECIFIED\x10\x00\x12\x1e\n\x1a\x45XPERIMENT_STATUS_PROPOSED\x10\x01\x12\x1d\n\x19\x45XPERIMENT_STATUS_RUNNING\x10\x02\x12\x1c\n\x18\x45XPERIMENT_STATUS_PAUSED\x10\x03\x12\x1f\n\x1b\x45XPERIMENT_STATUS_SUSPENDED\x10\x04\x12\x1e\n\x1a\x45XPERIMENT_STATUS_COMPLETE\x10\x05\x12\x1d\n\x19\x45XPERIMENT_STATUS_RETIRED\x10\x06\x12!\n\x1d\x45XPERIMENT_STATUS_ROLLED_BACK\x10\x07:M\n\x04role\x12\x1e.google.protobuf.MethodOptions\x18\xba\x8e\x03 \x01(\x0e\x32\x17.xtcp_treatment.v1.RoleR\x04roleB\xc0\x01\n\x15\x63om.xtcp_treatment.v1B\x0eTreatmentProtoP\x01Z6github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment\xa2\x02\x03XXX\xaa\x02\x10XtcpTreatment.V1\xca\x02\x10XtcpTreatment\\V1\xe2\x02\x1cXtcpTreatment\\V1\\GPBMetadata\xea\x02\x11XtcpTreatment::V1b\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -80,6 +80,8 @@ _globals['_CAPABILITIES'].fields_by_name['kernel_release']._serialized_options = b'\272H\005r\003\030\200\002' _globals['_CAPABILITIES'].fields_by_name['congestion_algorithms']._loaded_options = None _globals['_CAPABILITIES'].fields_by_name['congestion_algorithms']._serialized_options = b'\272H\013\222\001\010\020@\"\004r\002\030\017' + _globals['_CAPABILITIES'].fields_by_name['collection_id']._loaded_options = None + _globals['_CAPABILITIES'].fields_by_name['collection_id']._serialized_options = b'\272H\022r\0202\016^[0-9a-f]{32}$' _globals['_APPLICATIONRECORD'].fields_by_name['assignment']._loaded_options = None _globals['_APPLICATIONRECORD'].fields_by_name['assignment']._serialized_options = b'\272H\003\310\001\001' _globals['_APPLICATIONRECORD'].fields_by_name['status']._loaded_options = None @@ -170,16 +172,16 @@ _globals['_EXPERIMENT'].fields_by_name['status']._serialized_options = b'\272H\005\202\001\002\020\001' _globals['_EXPERIMENT'].fields_by_name['latest_outcome']._loaded_options = None _globals['_EXPERIMENT'].fields_by_name['latest_outcome']._serialized_options = b'\272H\005\202\001\002\020\001' - _globals['_ROLE']._serialized_start=7335 - _globals['_ROLE']._serialized_end=7398 - _globals['_ASSIGNMENTMODE']._serialized_start=7401 - _globals['_ASSIGNMENTMODE']._serialized_end=7565 - _globals['_APPLYSTATUS']._serialized_start=7568 - _globals['_APPLYSTATUS']._serialized_end=7741 - _globals['_OUTCOME']._serialized_start=7744 - _globals['_OUTCOME']._serialized_end=8292 - _globals['_EXPERIMENTSTATUS']._serialized_start=8295 - _globals['_EXPERIMENTSTATUS']._serialized_end=8572 + _globals['_ROLE']._serialized_start=7436 + _globals['_ROLE']._serialized_end=7499 + _globals['_ASSIGNMENTMODE']._serialized_start=7502 + _globals['_ASSIGNMENTMODE']._serialized_end=7666 + _globals['_APPLYSTATUS']._serialized_start=7669 + _globals['_APPLYSTATUS']._serialized_end=7842 + _globals['_OUTCOME']._serialized_start=7845 + _globals['_OUTCOME']._serialized_end=8393 + _globals['_EXPERIMENTSTATUS']._serialized_start=8396 + _globals['_EXPERIMENTSTATUS']._serialized_end=8673 _globals['_TCPSETTINGS']._serialized_start=185 _globals['_TCPSETTINGS']._serialized_end=1094 _globals['_TREATMENT']._serialized_start=1097 @@ -189,19 +191,19 @@ _globals['_DESIREDSTATE']._serialized_start=2291 _globals['_DESIREDSTATE']._serialized_end=2583 _globals['_CAPABILITIES']._serialized_start=2586 - _globals['_CAPABILITIES']._serialized_end=2877 - _globals['_APPLICATIONRECORD']._serialized_start=2880 - _globals['_APPLICATIONRECORD']._serialized_end=3560 - _globals['_LOCALSTATE']._serialized_start=3563 - _globals['_LOCALSTATE']._serialized_end=3868 - _globals['_HOSTEVIDENCE']._serialized_start=3871 - _globals['_HOSTEVIDENCE']._serialized_end=4392 - _globals['_EVIDENCE']._serialized_start=4395 - _globals['_EVIDENCE']._serialized_end=5095 - _globals['_EXPERIMENTPOLICY']._serialized_start=5098 - _globals['_EXPERIMENTPOLICY']._serialized_end=5974 - _globals['_PHASE']._serialized_start=5977 - _globals['_PHASE']._serialized_end=6426 - _globals['_EXPERIMENT']._serialized_start=6429 - _globals['_EXPERIMENT']._serialized_end=7333 + _globals['_CAPABILITIES']._serialized_end=2937 + _globals['_APPLICATIONRECORD']._serialized_start=2940 + _globals['_APPLICATIONRECORD']._serialized_end=3620 + _globals['_LOCALSTATE']._serialized_start=3623 + _globals['_LOCALSTATE']._serialized_end=3928 + _globals['_HOSTEVIDENCE']._serialized_start=3931 + _globals['_HOSTEVIDENCE']._serialized_end=4493 + _globals['_EVIDENCE']._serialized_start=4496 + _globals['_EVIDENCE']._serialized_end=5196 + _globals['_EXPERIMENTPOLICY']._serialized_start=5199 + _globals['_EXPERIMENTPOLICY']._serialized_end=6075 + _globals['_PHASE']._serialized_start=6078 + _globals['_PHASE']._serialized_end=6527 + _globals['_EXPERIMENT']._serialized_start=6530 + _globals['_EXPERIMENT']._serialized_end=7434 # @@protoc_insertion_point(module_scope) diff --git a/gen/python/xtcp_treatment/v1/treatment_pb2.pyi b/gen/python/xtcp_treatment/v1/treatment_pb2.pyi index b67c4d6e5..29fda970f 100644 --- a/gen/python/xtcp_treatment/v1/treatment_pb2.pyi +++ b/gen/python/xtcp_treatment/v1/treatment_pb2.pyi @@ -175,18 +175,20 @@ class DesiredState(_message.Message): def __init__(self, agent_id: _Optional[str] = ..., revision: _Optional[int] = ..., suspended: _Optional[bool] = ..., assignments: _Optional[_Iterable[_Union[Assignment, _Mapping]]] = ..., issued_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ... class Capabilities(_message.Message): - __slots__ = ("boot_id", "kernel_release", "congestion_algorithms", "simple_host_routing", "actuator_enabled") + __slots__ = ("boot_id", "kernel_release", "congestion_algorithms", "simple_host_routing", "actuator_enabled", "collection_id") BOOT_ID_FIELD_NUMBER: _ClassVar[int] KERNEL_RELEASE_FIELD_NUMBER: _ClassVar[int] CONGESTION_ALGORITHMS_FIELD_NUMBER: _ClassVar[int] SIMPLE_HOST_ROUTING_FIELD_NUMBER: _ClassVar[int] ACTUATOR_ENABLED_FIELD_NUMBER: _ClassVar[int] + COLLECTION_ID_FIELD_NUMBER: _ClassVar[int] boot_id: str kernel_release: str congestion_algorithms: _containers.RepeatedScalarFieldContainer[str] simple_host_routing: bool actuator_enabled: bool - def __init__(self, boot_id: _Optional[str] = ..., kernel_release: _Optional[str] = ..., congestion_algorithms: _Optional[_Iterable[str]] = ..., simple_host_routing: _Optional[bool] = ..., actuator_enabled: _Optional[bool] = ...) -> None: ... + collection_id: str + def __init__(self, boot_id: _Optional[str] = ..., kernel_release: _Optional[str] = ..., congestion_algorithms: _Optional[_Iterable[str]] = ..., simple_host_routing: _Optional[bool] = ..., actuator_enabled: _Optional[bool] = ..., collection_id: _Optional[str] = ...) -> None: ... class ApplicationRecord(_message.Message): __slots__ = ("assignment", "desired_revision", "status", "received_at", "attempted_at", "applied_at", "verified_at", "removed_at", "error", "installed_route", "installed_boot_id", "expired_latched") @@ -231,7 +233,7 @@ class LocalState(_message.Message): def __init__(self, desired: _Optional[_Union[DesiredState, _Mapping]] = ..., locally_suspended: _Optional[bool] = ..., suspension_reason: _Optional[str] = ..., records: _Optional[_Iterable[_Union[ApplicationRecord, _Mapping]]] = ..., updated_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ... class HostEvidence(_message.Message): - __slots__ = ("agent_id", "sockets", "bytes_acked", "goodput_bps", "retransmission_ratio", "rtt_p95_ms", "exposed_sockets", "collection_healthy") + __slots__ = ("agent_id", "sockets", "bytes_acked", "goodput_bps", "retransmission_ratio", "rtt_p95_ms", "exposed_sockets", "collection_healthy", "unbound_sockets") AGENT_ID_FIELD_NUMBER: _ClassVar[int] SOCKETS_FIELD_NUMBER: _ClassVar[int] BYTES_ACKED_FIELD_NUMBER: _ClassVar[int] @@ -240,6 +242,7 @@ class HostEvidence(_message.Message): RTT_P95_MS_FIELD_NUMBER: _ClassVar[int] EXPOSED_SOCKETS_FIELD_NUMBER: _ClassVar[int] COLLECTION_HEALTHY_FIELD_NUMBER: _ClassVar[int] + UNBOUND_SOCKETS_FIELD_NUMBER: _ClassVar[int] agent_id: str sockets: int bytes_acked: int @@ -248,7 +251,8 @@ class HostEvidence(_message.Message): rtt_p95_ms: float exposed_sockets: int collection_healthy: bool - def __init__(self, agent_id: _Optional[str] = ..., sockets: _Optional[int] = ..., bytes_acked: _Optional[int] = ..., goodput_bps: _Optional[float] = ..., retransmission_ratio: _Optional[float] = ..., rtt_p95_ms: _Optional[float] = ..., exposed_sockets: _Optional[int] = ..., collection_healthy: _Optional[bool] = ...) -> None: ... + unbound_sockets: int + def __init__(self, agent_id: _Optional[str] = ..., sockets: _Optional[int] = ..., bytes_acked: _Optional[int] = ..., goodput_bps: _Optional[float] = ..., retransmission_ratio: _Optional[float] = ..., rtt_p95_ms: _Optional[float] = ..., exposed_sockets: _Optional[int] = ..., collection_healthy: _Optional[bool] = ..., unbound_sockets: _Optional[int] = ...) -> None: ... class Evidence(_message.Message): __slots__ = ("id", "analyzed_at", "window_start", "window_end", "query_version", "enrichment_version", "hosts", "artifact_sha256") diff --git a/gen/python/xtcp_treatment_journal/v1/journal_pb2.py b/gen/python/xtcp_treatment_journal/v1/journal_pb2.py index 1e31376be..f1abdae3c 100644 --- a/gen/python/xtcp_treatment_journal/v1/journal_pb2.py +++ b/gen/python/xtcp_treatment_journal/v1/journal_pb2.py @@ -25,7 +25,7 @@ from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\'xtcp_treatment_journal/v1/journal.proto\x12\x19xtcp_treatment_journal.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"\xb7\x03\n\x0bTCPSettings\x12\x32\n\x12\x63ongestion_control\x18\x01 \x01(\tH\x00R\x11\x63ongestionControl\x88\x01\x01\x12\'\n\x0f\x63ongestion_lock\x18\x02 \x01(\x08R\x0e\x63ongestionLock\x12!\n\nrto_min_ms\x18\x03 \x01(\rH\x01R\x08rtoMinMs\x88\x01\x01\x12?\n\x19initial_congestion_window\x18\x04 \x01(\rH\x02R\x17initialCongestionWindow\x88\x01\x01\x12\x39\n\x16initial_receive_window\x18\x05 \x01(\rH\x03R\x14initialReceiveWindow\x88\x01\x01\x12 \n\tquick_ack\x18\x06 \x01(\x08H\x04R\x08quickAck\x88\x01\x01\x12\x15\n\x03\x65\x63n\x18\x07 \x01(\x08H\x05R\x03\x65\x63n\x88\x01\x01\x42\x15\n\x13_congestion_controlB\r\n\x0b_rto_min_msB\x1c\n\x1a_initial_congestion_windowB\x19\n\x17_initial_receive_windowB\x0c\n\n_quick_ackB\x06\n\x04_ecn\"\xcd\x01\n\tTreatment\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12\x1a\n\x08revision\x18\x02 \x01(\x04R\x08revision\x12 \n\x0b\x64\x65stination\x18\x03 \x01(\tR\x0b\x64\x65stination\x12\x42\n\x08settings\x18\x04 \x01(\x0b\x32&.xtcp_treatment_journal.v1.TCPSettingsR\x08settings\x12.\n\x13\x65\x63n_fabric_verified\x18\x05 \x01(\x08R\x11\x65\x63nFabricVerified\"\xe2\x02\n\nAssignment\x12#\n\rexperiment_id\x18\x01 \x01(\tR\x0c\x65xperimentId\x12\x42\n\ttreatment\x18\x02 \x01(\x0b\x32$.xtcp_treatment_journal.v1.TreatmentR\ttreatment\x12=\n\x04mode\x18\x03 \x01(\x0e\x32).xtcp_treatment_journal.v1.AssignmentModeR\x04mode\x12\x44\n\x10lease_expires_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0eleaseExpiresAt\x12\x46\n\x0bpredecessor\x18\x05 \x01(\x0b\x32$.xtcp_treatment_journal.v1.TreatmentR\x0bpredecessor\x12\x1e\n\ngeneration\x18\x06 \x01(\x04R\ngeneration\"\xc5\x01\n\x07\x44\x65sired\x12\x1a\n\x08revision\x18\x01 \x01(\x04R\x08revision\x12\x1c\n\tsuspended\x18\x02 \x01(\x08R\tsuspended\x12\x37\n\tissued_at\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x08issuedAt\x12G\n\x0b\x61ssignments\x18\x04 \x03(\x0b\x32%.xtcp_treatment_journal.v1.AssignmentR\x0b\x61ssignments\"\xfd\x04\n\x06Record\x12\x45\n\nassignment\x18\x01 \x01(\x0b\x32%.xtcp_treatment_journal.v1.AssignmentR\nassignment\x12)\n\x10\x64\x65sired_revision\x18\x02 \x01(\x04R\x0f\x64\x65siredRevision\x12>\n\x06status\x18\x03 \x01(\x0e\x32&.xtcp_treatment_journal.v1.ApplyStatusR\x06status\x12;\n\x0breceived_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\nreceivedAt\x12=\n\x0c\x61ttempted_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0b\x61ttemptedAt\x12\x39\n\napplied_at\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tappliedAt\x12;\n\x0bverified_at\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\nverifiedAt\x12\x39\n\nremoved_at\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tremovedAt\x12\x14\n\x05\x65rror\x18\t \x01(\tR\x05\x65rror\x12\'\n\x0finstalled_route\x18\n \x01(\x0cR\x0einstalledRoute\x12*\n\x11installed_boot_id\x18\x0b \x01(\tR\x0finstalledBootId\x12\'\n\x0f\x65xpired_latched\x18\x0c \x01(\x08R\x0e\x65xpiredLatched\"\xe9\x02\n\x07Journal\x12\x19\n\x08\x61gent_id\x18\x01 \x01(\tR\x07\x61gentId\x12\x1a\n\x08location\x18\x02 \x01(\tR\x08location\x12\x17\n\x07\x62oot_id\x18\x03 \x01(\tR\x06\x62ootId\x12<\n\x07\x64\x65sired\x18\x04 \x01(\x0b\x32\".xtcp_treatment_journal.v1.DesiredR\x07\x64\x65sired\x12+\n\x11locally_suspended\x18\x05 \x01(\x08R\x10locallySuspended\x12+\n\x11suspension_reason\x18\x06 \x01(\tR\x10suspensionReason\x12;\n\x07records\x18\x07 \x03(\x0b\x32!.xtcp_treatment_journal.v1.RecordR\x07records\x12\x39\n\nupdated_at\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tupdatedAt*\xa4\x01\n\x0e\x41ssignmentMode\x12\x1f\n\x1b\x41SSIGNMENT_MODE_UNSPECIFIED\x10\x00\x12\x19\n\x15\x41SSIGNMENT_MODE_TRIAL\x10\x01\x12\x1a\n\x16\x41SSIGNMENT_MODE_STABLE\x10\x02\x12\x1d\n\x19\x41SSIGNMENT_MODE_SUSPENDED\x10\x03\x12\x1b\n\x17\x41SSIGNMENT_MODE_RETIRED\x10\x04*\xad\x01\n\x0b\x41pplyStatus\x12\x1c\n\x18\x41PPLY_STATUS_UNSPECIFIED\x10\x00\x12\x18\n\x14\x41PPLY_STATUS_PENDING\x10\x01\x12\x19\n\x15\x41PPLY_STATUS_VERIFIED\x10\x02\x12\x18\n\x14\x41PPLY_STATUS_REMOVED\x10\x03\x12\x18\n\x14\x41PPLY_STATUS_EXPIRED\x10\x04\x12\x17\n\x13\x41PPLY_STATUS_FAILED\x10\x05\x42\xea\x01\n\x1d\x63om.xtcp_treatment_journal.v1B\x0cJournalProtoP\x01Z>github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment_journal\xa2\x02\x03XXX\xaa\x02\x17XtcpTreatmentJournal.V1\xca\x02\x17XtcpTreatmentJournal\\V1\xe2\x02#XtcpTreatmentJournal\\V1\\GPBMetadata\xea\x02\x18XtcpTreatmentJournal::V1b\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\'xtcp_treatment_journal/v1/journal.proto\x12\x19xtcp_treatment_journal.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"\xb7\x03\n\x0bTCPSettings\x12\x32\n\x12\x63ongestion_control\x18\x01 \x01(\tH\x00R\x11\x63ongestionControl\x88\x01\x01\x12\'\n\x0f\x63ongestion_lock\x18\x02 \x01(\x08R\x0e\x63ongestionLock\x12!\n\nrto_min_ms\x18\x03 \x01(\rH\x01R\x08rtoMinMs\x88\x01\x01\x12?\n\x19initial_congestion_window\x18\x04 \x01(\rH\x02R\x17initialCongestionWindow\x88\x01\x01\x12\x39\n\x16initial_receive_window\x18\x05 \x01(\rH\x03R\x14initialReceiveWindow\x88\x01\x01\x12 \n\tquick_ack\x18\x06 \x01(\x08H\x04R\x08quickAck\x88\x01\x01\x12\x15\n\x03\x65\x63n\x18\x07 \x01(\x08H\x05R\x03\x65\x63n\x88\x01\x01\x42\x15\n\x13_congestion_controlB\r\n\x0b_rto_min_msB\x1c\n\x1a_initial_congestion_windowB\x19\n\x17_initial_receive_windowB\x0c\n\n_quick_ackB\x06\n\x04_ecn\"\xcd\x01\n\tTreatment\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12\x1a\n\x08revision\x18\x02 \x01(\x04R\x08revision\x12 \n\x0b\x64\x65stination\x18\x03 \x01(\tR\x0b\x64\x65stination\x12\x42\n\x08settings\x18\x04 \x01(\x0b\x32&.xtcp_treatment_journal.v1.TCPSettingsR\x08settings\x12.\n\x13\x65\x63n_fabric_verified\x18\x05 \x01(\x08R\x11\x65\x63nFabricVerified\"\xe2\x02\n\nAssignment\x12#\n\rexperiment_id\x18\x01 \x01(\tR\x0c\x65xperimentId\x12\x42\n\ttreatment\x18\x02 \x01(\x0b\x32$.xtcp_treatment_journal.v1.TreatmentR\ttreatment\x12=\n\x04mode\x18\x03 \x01(\x0e\x32).xtcp_treatment_journal.v1.AssignmentModeR\x04mode\x12\x44\n\x10lease_expires_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0eleaseExpiresAt\x12\x46\n\x0bpredecessor\x18\x05 \x01(\x0b\x32$.xtcp_treatment_journal.v1.TreatmentR\x0bpredecessor\x12\x1e\n\ngeneration\x18\x06 \x01(\x04R\ngeneration\"\xc5\x01\n\x07\x44\x65sired\x12\x1a\n\x08revision\x18\x01 \x01(\x04R\x08revision\x12\x1c\n\tsuspended\x18\x02 \x01(\x08R\tsuspended\x12\x37\n\tissued_at\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x08issuedAt\x12G\n\x0b\x61ssignments\x18\x04 \x03(\x0b\x32%.xtcp_treatment_journal.v1.AssignmentR\x0b\x61ssignments\"\xfd\x04\n\x06Record\x12\x45\n\nassignment\x18\x01 \x01(\x0b\x32%.xtcp_treatment_journal.v1.AssignmentR\nassignment\x12)\n\x10\x64\x65sired_revision\x18\x02 \x01(\x04R\x0f\x64\x65siredRevision\x12>\n\x06status\x18\x03 \x01(\x0e\x32&.xtcp_treatment_journal.v1.ApplyStatusR\x06status\x12;\n\x0breceived_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\nreceivedAt\x12=\n\x0c\x61ttempted_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x0b\x61ttemptedAt\x12\x39\n\napplied_at\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tappliedAt\x12;\n\x0bverified_at\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\nverifiedAt\x12\x39\n\nremoved_at\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tremovedAt\x12\x14\n\x05\x65rror\x18\t \x01(\tR\x05\x65rror\x12\'\n\x0finstalled_route\x18\n \x01(\x0cR\x0einstalledRoute\x12*\n\x11installed_boot_id\x18\x0b \x01(\tR\x0finstalledBootId\x12\'\n\x0f\x65xpired_latched\x18\x0c \x01(\x08R\x0e\x65xpiredLatched\"\x8e\x03\n\x07Journal\x12\x19\n\x08\x61gent_id\x18\x01 \x01(\tR\x07\x61gentId\x12\x1a\n\x08location\x18\x02 \x01(\tR\x08location\x12\x17\n\x07\x62oot_id\x18\x03 \x01(\tR\x06\x62ootId\x12<\n\x07\x64\x65sired\x18\x04 \x01(\x0b\x32\".xtcp_treatment_journal.v1.DesiredR\x07\x64\x65sired\x12+\n\x11locally_suspended\x18\x05 \x01(\x08R\x10locallySuspended\x12+\n\x11suspension_reason\x18\x06 \x01(\tR\x10suspensionReason\x12;\n\x07records\x18\x07 \x03(\x0b\x32!.xtcp_treatment_journal.v1.RecordR\x07records\x12\x39\n\nupdated_at\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tupdatedAt\x12#\n\rcollection_id\x18\t \x01(\tR\x0c\x63ollectionId*\xa4\x01\n\x0e\x41ssignmentMode\x12\x1f\n\x1b\x41SSIGNMENT_MODE_UNSPECIFIED\x10\x00\x12\x19\n\x15\x41SSIGNMENT_MODE_TRIAL\x10\x01\x12\x1a\n\x16\x41SSIGNMENT_MODE_STABLE\x10\x02\x12\x1d\n\x19\x41SSIGNMENT_MODE_SUSPENDED\x10\x03\x12\x1b\n\x17\x41SSIGNMENT_MODE_RETIRED\x10\x04*\xad\x01\n\x0b\x41pplyStatus\x12\x1c\n\x18\x41PPLY_STATUS_UNSPECIFIED\x10\x00\x12\x18\n\x14\x41PPLY_STATUS_PENDING\x10\x01\x12\x19\n\x15\x41PPLY_STATUS_VERIFIED\x10\x02\x12\x18\n\x14\x41PPLY_STATUS_REMOVED\x10\x03\x12\x18\n\x14\x41PPLY_STATUS_EXPIRED\x10\x04\x12\x17\n\x13\x41PPLY_STATUS_FAILED\x10\x05\x42\xea\x01\n\x1d\x63om.xtcp_treatment_journal.v1B\x0cJournalProtoP\x01Z>github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment_journal\xa2\x02\x03XXX\xaa\x02\x17XtcpTreatmentJournal.V1\xca\x02\x17XtcpTreatmentJournal\\V1\xe2\x02#XtcpTreatmentJournal\\V1\\GPBMetadata\xea\x02\x18XtcpTreatmentJournal::V1b\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -33,10 +33,10 @@ if not _descriptor._USE_C_DESCRIPTORS: _globals['DESCRIPTOR']._loaded_options = None _globals['DESCRIPTOR']._serialized_options = b'\n\035com.xtcp_treatment_journal.v1B\014JournalProtoP\001Z>github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment_journal\242\002\003XXX\252\002\027XtcpTreatmentJournal.V1\312\002\027XtcpTreatmentJournal\\V1\342\002#XtcpTreatmentJournal\\V1\\GPBMetadata\352\002\030XtcpTreatmentJournal::V1' - _globals['_ASSIGNMENTMODE']._serialized_start=2315 - _globals['_ASSIGNMENTMODE']._serialized_end=2479 - _globals['_APPLYSTATUS']._serialized_start=2482 - _globals['_APPLYSTATUS']._serialized_end=2655 + _globals['_ASSIGNMENTMODE']._serialized_start=2352 + _globals['_ASSIGNMENTMODE']._serialized_end=2516 + _globals['_APPLYSTATUS']._serialized_start=2519 + _globals['_APPLYSTATUS']._serialized_end=2692 _globals['_TCPSETTINGS']._serialized_start=104 _globals['_TCPSETTINGS']._serialized_end=543 _globals['_TREATMENT']._serialized_start=546 @@ -48,5 +48,5 @@ _globals['_RECORD']._serialized_start=1311 _globals['_RECORD']._serialized_end=1948 _globals['_JOURNAL']._serialized_start=1951 - _globals['_JOURNAL']._serialized_end=2312 + _globals['_JOURNAL']._serialized_end=2349 # @@protoc_insertion_point(module_scope) diff --git a/gen/python/xtcp_treatment_journal/v1/journal_pb2.pyi b/gen/python/xtcp_treatment_journal/v1/journal_pb2.pyi index 3afc5df1e..af969ddab 100644 --- a/gen/python/xtcp_treatment_journal/v1/journal_pb2.pyi +++ b/gen/python/xtcp_treatment_journal/v1/journal_pb2.pyi @@ -127,7 +127,7 @@ class Record(_message.Message): def __init__(self, assignment: _Optional[_Union[Assignment, _Mapping]] = ..., desired_revision: _Optional[int] = ..., status: _Optional[_Union[ApplyStatus, str]] = ..., received_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., attempted_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., applied_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., verified_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., removed_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., error: _Optional[str] = ..., installed_route: _Optional[bytes] = ..., installed_boot_id: _Optional[str] = ..., expired_latched: _Optional[bool] = ...) -> None: ... class Journal(_message.Message): - __slots__ = ("agent_id", "location", "boot_id", "desired", "locally_suspended", "suspension_reason", "records", "updated_at") + __slots__ = ("agent_id", "location", "boot_id", "desired", "locally_suspended", "suspension_reason", "records", "updated_at", "collection_id") AGENT_ID_FIELD_NUMBER: _ClassVar[int] LOCATION_FIELD_NUMBER: _ClassVar[int] BOOT_ID_FIELD_NUMBER: _ClassVar[int] @@ -136,6 +136,7 @@ class Journal(_message.Message): SUSPENSION_REASON_FIELD_NUMBER: _ClassVar[int] RECORDS_FIELD_NUMBER: _ClassVar[int] UPDATED_AT_FIELD_NUMBER: _ClassVar[int] + COLLECTION_ID_FIELD_NUMBER: _ClassVar[int] agent_id: str location: str boot_id: str @@ -144,4 +145,5 @@ class Journal(_message.Message): suspension_reason: str records: _containers.RepeatedCompositeFieldContainer[Record] updated_at: _timestamp_pb2.Timestamp - def __init__(self, agent_id: _Optional[str] = ..., location: _Optional[str] = ..., boot_id: _Optional[str] = ..., desired: _Optional[_Union[Desired, _Mapping]] = ..., locally_suspended: _Optional[bool] = ..., suspension_reason: _Optional[str] = ..., records: _Optional[_Iterable[_Union[Record, _Mapping]]] = ..., updated_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ... + collection_id: str + def __init__(self, agent_id: _Optional[str] = ..., location: _Optional[str] = ..., boot_id: _Optional[str] = ..., desired: _Optional[_Union[Desired, _Mapping]] = ..., locally_suspended: _Optional[bool] = ..., suspension_reason: _Optional[str] = ..., records: _Optional[_Iterable[_Union[Record, _Mapping]]] = ..., updated_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., collection_id: _Optional[str] = ...) -> None: ... diff --git a/nix/tests/tcp-controller/default.nix b/nix/tests/tcp-controller/default.nix index 0a3f613c6..4ae592f12 100644 --- a/nix/tests/tcp-controller/default.nix +++ b/nix/tests/tcp-controller/default.nix @@ -368,6 +368,7 @@ let "TCP_CONTROLLER_CANARY_PASS" "TCP_CONTROLLER_PERSIST_PASS" "TCP_CONTROLLER_HOLD_PASS" + "TCP_CONTROLLER_BINDING_PASS" "TCP_CONTROLLER_ROLLBACK_PASS" "TCP_CONTROLLER_WATCHDOG_PASS" "TCP_CONTROLLER_PASS" diff --git a/pkg/treatment/agent/agent.go b/pkg/treatment/agent/agent.go index a8972f772..43ca5128f 100644 --- a/pkg/treatment/agent/agent.go +++ b/pkg/treatment/agent/agent.go @@ -65,6 +65,9 @@ func newLocal(c Config, id *local.Identity) (*Agent, error) { return nil, err } a := &Agent{Config: h.Config, Host: h, Reconciler: h.Reconciler, wake: make(chan struct{}, 1), collectionID: hex.EncodeToString(nonce[:])} + // The agent owns the nonce: it goes into every journal header it writes, + // where a telemetry-only daemon reads it, and into every check-in. + h.Reconciler.CollectionID = a.collectionID a.publish(nil) return a, nil } @@ -232,7 +235,7 @@ func (a *Agent) watch(ctx context.Context) { } func (a *Agent) capabilities(ctx context.Context) treatment.Capabilities { - c := treatment.Capabilities{BootID: a.Host.Identity.BootID, ActuatorEnabled: a.Config.EnableApply} + c := treatment.Capabilities{BootID: a.Host.Identity.BootID, CollectionID: a.collectionID, ActuatorEnabled: a.Config.EnableApply} var u unix.Utsname if unix.Uname(&u) == nil { c.KernelRelease = unix.ByteSliceToString(u.Release[:]) diff --git a/pkg/treatment/agent/agent_test.go b/pkg/treatment/agent/agent_test.go index c536a66b4..62d93ea45 100644 --- a/pkg/treatment/agent/agent_test.go +++ b/pkg/treatment/agent/agent_test.go @@ -1,7 +1,9 @@ package agent import ( + "context" "net/netip" + "strconv" "testing" "time" @@ -69,3 +71,33 @@ func TestStampBeforeFirstReconcile(t *testing.T) { t.Fatal("state published before any pass") } } + +// TestAgentOwnsCollectionID: one nonce per agent process, the same in the +// journal header a telemetry-only daemon reads, in the check-in report and +// on records the agent stamps itself; a second process draws another. +func TestAgentOwnsCollectionID(t *testing.T) { + a := testAgent(t) + a.reconcile(context.Background(), nil) + journal, err := reconcile.ReadSnapshot(a.Config.Directory) + if err != nil { + t.Fatal(err) + } + r := &flat.XtcpFlatRecord{} + a.Stamp(r) + reported := a.capabilities(context.Background()).CollectionID + tests := []struct { + description string + got string + expected string + }{ + {"positive: the nonce is well formed", strconv.FormatBool(treatment.ValidCollectionID(reported)), "true"}, + {"positive: the journal header carries the reported nonce", journal.CollectionID, reported}, + {"positive: embedded stamping uses the same nonce", r.CollectionId, reported}, + {"corner: another agent process draws a different nonce", strconv.FormatBool(testAgent(t).collectionID == reported), "false"}, + } + for _, tc := range tests { + if tc.got != tc.expected { + t.Errorf("%s: got %q, want %q", tc.description, tc.got, tc.expected) + } + } +} diff --git a/pkg/treatment/attribution/index.go b/pkg/treatment/attribution/index.go index b64e40304..e33419b9e 100644 --- a/pkg/treatment/attribution/index.go +++ b/pkg/treatment/attribution/index.go @@ -25,8 +25,9 @@ type Identity struct { AgentID string Location string BootID string - // CollectionID is a per-process nonce so restarts of the stamping - // process are distinguishable in evidence. + // CollectionID is the agent process's nonce (see + // treatment.ValidCollectionID). The controller counts a record as + // evidence only when its agent reported this nonce with this boot ID. CollectionID string // NetnsInode is the namespace the treatment routes live in; sockets in // any other namespace are never attributed. diff --git a/pkg/treatment/attribution/watch.go b/pkg/treatment/attribution/watch.go index d9c1590b1..988655efc 100644 --- a/pkg/treatment/attribution/watch.go +++ b/pkg/treatment/attribution/watch.go @@ -1,8 +1,6 @@ package attribution import ( - "crypto/rand" - "encoding/hex" "errors" "fmt" "log" @@ -44,15 +42,17 @@ type WatchConfig struct { // agent to speak for, and the daemon degrades to an observation-only // producer rather than attributing from stale state. type Watcher struct { - cfg WatchConfig - stamper *Stamper - collectionID string - wake chan struct{} - mu sync.Mutex - lastModTime time.Time - lastSize int64 - lastErr string - warnedFor string + cfg WatchConfig + stamper *Stamper + wake chan struct{} + mu sync.Mutex + lastModTime time.Time + lastSize int64 + lastErr string + warnedFor string + // warnedUnbound is set once a journal without a collection nonce has + // been logged, so the warning is not repeated on every refresh. + warnedUnbound bool } // NewWatcher validates the configuration and resolves the namespace. It @@ -74,11 +74,7 @@ func NewWatcher(cfg WatchConfig, s *Stamper) (*Watcher, error) { } cfg.NetnsInode = st.Ino } - var nonce [16]byte - if _, err := rand.Read(nonce[:]); err != nil { - return nil, err - } - return &Watcher{cfg: cfg, stamper: s, collectionID: hex.EncodeToString(nonce[:]), wake: make(chan struct{}, 1)}, nil + return &Watcher{cfg: cfg, stamper: s, wake: make(chan struct{}, 1)}, nil } // Wake asks Run to re-read the snapshot now; concurrent wakes coalesce. @@ -128,7 +124,13 @@ func (w *Watcher) Refresh(force bool) error { w.warnedFor = s.Location w.cfg.Log("treatment attribution: this daemon has no -location; records take the journal's %q", s.Location) } - w.stamper.Publish(Build(s, Identity{AgentID: s.AgentID, Location: s.Location, BootID: s.BootID, CollectionID: w.collectionID, NetnsInode: w.cfg.NetnsInode}, adoptLocation)) + if s.CollectionID == "" && !w.warnedUnbound { + w.warnedUnbound = true + w.cfg.Log("treatment attribution: the journal carries no collection nonce; records are stamped without one and the controller will not count them as evidence until the agent rewrites the journal") + } + // The nonce is the agent's, read from the journal, so the controller + // can bind these records to the session the agent reported. + w.stamper.Publish(Build(s, Identity{AgentID: s.AgentID, Location: s.Location, BootID: s.BootID, CollectionID: s.CollectionID, NetnsInode: w.cfg.NetnsInode}, adoptLocation)) return nil } diff --git a/pkg/treatment/attribution/watch_test.go b/pkg/treatment/attribution/watch_test.go index 39625bd0d..05e15f202 100644 --- a/pkg/treatment/attribution/watch_test.go +++ b/pkg/treatment/attribution/watch_test.go @@ -35,7 +35,7 @@ func verifiedState(revision uint64) *reconcile.State { d.Revision = revision d.Assignments[0].Mode = treatment.ModeStable d.Assignments[0].LeaseExpiresAt = time.Time{} - return &reconcile.State{AgentID: "host-1", Location: "dc1", BootID: "boot-1", Desired: d, Records: []reconcile.Record{{ApplicationRecord: treatment.ApplicationRecord{Assignment: d.Assignments[0], Status: treatment.ApplyVerified, AppliedAt: now, DesiredRevision: revision}, InstalledRoute: []byte{1}, InstalledBootID: "boot-1"}}} + return &reconcile.State{AgentID: "host-1", Location: "dc1", BootID: "boot-1", CollectionID: fixtures.CollectionID, Desired: d, Records: []reconcile.Record{{ApplicationRecord: treatment.ApplicationRecord{Assignment: d.Assignments[0], Status: treatment.ApplyVerified, AppliedAt: now, DesiredRevision: revision}, InstalledRoute: []byte{1}, InstalledBootID: "boot-1"}}} } func record() *flat.XtcpFlatRecord { @@ -87,34 +87,40 @@ func TestWatcherRefreshContracts(t *testing.T) { wantLogged string daemonLocation string wantLocation string + wantCollection string }{ - {"journal", "positive: a committed journal attributes records with the journal's agent and the daemon's location", func(t *testing.T, dir string) { writeJournal(t, dir, verifiedState(3)) }, "", "host-1", "trial-1", "", "dc1", "dc1"}, - {"missing-file", "negative: no snapshot yet means identity is not invented", func(*testing.T, string) {}, "", "", "", "", "dc1", "dc1"}, + {"journal", "positive: a committed journal attributes records with the journal's agent, nonce and the daemon's location", func(t *testing.T, dir string) { writeJournal(t, dir, verifiedState(3)) }, "", "host-1", "trial-1", "", "dc1", "dc1", fixtures.CollectionID}, + {"missing-file", "negative: no snapshot yet means identity is not invented", func(*testing.T, string) {}, "", "", "", "", "dc1", "dc1", ""}, {"missing-dir", "negative: a directory that does not exist yet is not an error", func(t *testing.T, dir string) { if err := os.RemoveAll(dir); err != nil { t.Fatal(err) } - }, "", "", "", "", "dc1", "dc1"}, + }, "", "", "", "", "dc1", "dc1", ""}, {"corrupt", "negative: an invalid snapshot clears attribution and is logged", func(t *testing.T, dir string) { if err := os.WriteFile(filepath.Join(dir, reconcile.SnapshotFile), []byte("nonsense {"), 0600); err != nil { t.Fatal(err) } - }, "invalid treatment journal", "", "", "not attributed", "dc1", "dc1"}, + }, "invalid treatment journal", "", "", "not attributed", "dc1", "dc1", ""}, {"held", "boundary: a journal under a hold stamps identity but no treatment", func(t *testing.T, dir string) { s := verifiedState(4) s.LocallySuspended = true writeJournal(t, dir, s) - }, "", "host-1", "", "", "dc1", "dc1"}, + }, "", "host-1", "", "", "dc1", "dc1", fixtures.CollectionID}, {"location-mismatch", "corner: a journal from another location keeps the daemon's label and warns once", func(t *testing.T, dir string) { s := verifiedState(5) s.Location = "elsewhere" writeJournal(t, dir, s) - }, "", "host-1", "trial-1", "differs from this daemon", "dc1", "dc1"}, + }, "", "host-1", "trial-1", "differs from this daemon", "dc1", "dc1", fixtures.CollectionID}, {"adopt-location", "corner: a daemon without its own -location takes the journal's so evidence queries can find it", func(t *testing.T, dir string) { s := verifiedState(6) s.Location = "journal-dc" writeJournal(t, dir, s) - }, "", "host-1", "trial-1", "records take the journal's", "", "journal-dc"}, + }, "", "host-1", "trial-1", "records take the journal's", "", "journal-dc", fixtures.CollectionID}, + {"no-nonce", "boundary: a journal written before the agent owned a nonce stamps none and warns once", func(t *testing.T, dir string) { + s := verifiedState(7) + s.CollectionID = "" + writeJournal(t, dir, s) + }, "", "host-1", "trial-1", "carries no collection nonce", "dc1", "dc1", ""}, } { t.Run(tc.name, func(t *testing.T) { dir := t.TempDir() @@ -134,7 +140,7 @@ func TestWatcherRefreshContracts(t *testing.T) { if r.AgentId != tc.wantAgent || r.TreatmentAssignmentId != tc.wantAssignment || r.Location != tc.wantLocation { t.Fatalf("%s: agent=%q assignment=%q location=%q", tc.description, r.AgentId, r.TreatmentAssignmentId, r.Location) } - if tc.wantAgent != "" && (r.HostBootId != "boot-1" || len(r.CollectionId) != 32) { + if tc.wantAgent != "" && r.HostBootId != "boot-1" || r.CollectionId != tc.wantCollection { t.Fatalf("%s: boot/collection not stamped: %+v", tc.description, r) } // A second refresh logs nothing new: errors and warnings are once per cause. @@ -142,7 +148,7 @@ func TestWatcherRefreshContracts(t *testing.T) { if tc.wantLogged != "" && sink.count(tc.wantLogged) != 1 { t.Fatalf("%s: expected one log line containing %q, got %v", tc.description, tc.wantLogged, sink.lines) } - if tc.wantLogged == "" && sink.count("not attributed")+sink.count("differs") != 0 { + if tc.wantLogged == "" && sink.count("not attributed")+sink.count("differs")+sink.count("collection nonce") != 0 { t.Fatalf("%s: unexpected log lines %v", tc.description, sink.lines) } }) diff --git a/pkg/treatment/controller/analyze.go b/pkg/treatment/controller/analyze.go index 821b8d9a6..e9696d820 100644 --- a/pkg/treatment/controller/analyze.go +++ b/pkg/treatment/controller/analyze.go @@ -51,7 +51,7 @@ func (s *Server) Analyze(ctx context.Context, r *pb.AnalyzeRequest) (*pb.Analyze if n.Status == treatment.StatusComplete { input = s.driftInput(n) } - ev, e := s.EvidenceSource.Evidence(ctx, evidence.WindowFor(input, s.now())) + ev, e := s.boundEvidence(ctx, s.EvidenceSource, evidence.WindowFor(input, s.now())) if e != nil { return nil, failure(e) } diff --git a/pkg/treatment/controller/bind.go b/pkg/treatment/controller/bind.go new file mode 100644 index 000000000..719c79c15 --- /dev/null +++ b/pkg/treatment/controller/bind.go @@ -0,0 +1,43 @@ +package controller + +import ( + "context" + "time" + + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/evidence" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store" +) + +// sessionSlack widens the session lookup beyond the window on both sides. A +// known session's LastSeen is refreshed only every store.SessionRefresh, and +// a new session is first seen at the check-in after the agent starts, so +// records near either edge may belong to a session the store places just +// outside the window. +const sessionSlack = store.SessionRefresh + 5*time.Minute + +// sessionRetention keeps sessions for longer than the longest window any +// evidence query may cover. +const sessionRetention = evidence.MaxWindow + 24*time.Hour + +// boundWindow attaches the sessions the population reported over mTLS, so +// the warehouse measures only records stamped with one of them and counts +// the rest as unbound. +func (s *Server) boundWindow(ctx context.Context, w evidence.Window) (evidence.Window, error) { + sessions, err := s.Store.Repos().Agents.Sessions(ctx, w.Agents, w.Start.Add(-sessionSlack), w.End.Add(sessionSlack)) + if err != nil { + return w, err + } + w.Sessions = sessions + return w, nil +} + +// boundEvidence measures w through source, bound to the population's +// sessions. +func (s *Server) boundEvidence(ctx context.Context, source evidence.Source, w evidence.Window) (*treatment.Evidence, error) { + w, err := s.boundWindow(ctx, w) + if err != nil { + return nil, err + } + return source.Evidence(ctx, w) +} diff --git a/pkg/treatment/controller/bind_test.go b/pkg/treatment/controller/bind_test.go new file mode 100644 index 000000000..d6c183654 --- /dev/null +++ b/pkg/treatment/controller/bind_test.go @@ -0,0 +1,82 @@ +package controller + +import ( + "context" + "testing" + "time" + + pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/evidence" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store" + "google.golang.org/protobuf/proto" +) + +// capturingSource returns fixed evidence and records the windows it saw. +type capturingSource struct { + value *treatment.Evidence + windows []evidence.Window +} + +func (c *capturingSource) Evidence(_ context.Context, w evidence.Window) (*treatment.Evidence, error) { + c.windows = append(c.windows, w) + return c.value, nil +} + +// TestEvidenceWindowsCarrySessions: every evidence window the controller +// asks for names the sessions its population reported, so the warehouse can +// drop records stamped by anyone else; housekeeping prunes old sessions. +func TestEvidenceWindowsCarrySessions(t *testing.T) { + const restarted = "fedcba9876543210fedcba9876543210" + for _, tc := range []struct { + name, description string + prepare func(t *testing.T, s *Server, now *time.Time) + expected int // sessions in the analysis window + }{ + {"one-each", "positive: each cohort host contributes the session it checked in with", func(*testing.T, *Server, *time.Time) {}, 8}, + {"restart", "positive: a host that restarted mid-window contributes both sessions", func(t *testing.T, s *Server, now *time.Time) { + *now = now.Add(time.Minute) + c := capabilities() + c.CollectionId = restarted + if _, e := s.CheckIn(testIdentity("agent/dc1/host-0"), &pb.CheckInRequest{AgentId: "host-0", Location: "dc1", Capabilities: c}); e != nil { + t.Fatal(e) + } + }, 9}, + {"pruned", "corner: sessions older than the retention are pruned and no longer bind evidence", func(t *testing.T, s *Server, now *time.Time) { + *now = now.Add(sessionRetention + time.Hour) + if _, e := s.Housekeep(context.Background(), nil, nil, nil); e != nil { + t.Fatal(e) + } + }, 0}, + } { + t.Run(tc.name, func(t *testing.T) { + eachStore(t, func(t *testing.T, st store.Store) { + now := time.Now().UTC().Truncate(time.Microsecond) + s := testServer(st, &now) + operator := testIdentity("operator/alice") + enrollFleet(t, s, 8) + p := proposal(now) + if _, e := s.Propose(operator, p); e != nil { + t.Fatal(e) + } + tc.prepare(t, s, &now) + ev := proto.Clone(p.Experiment.Baseline).(*pb.Evidence) + ev.Id = "bound-1" + source := &capturingSource{value: parseEvidence(t, ev)} + s.EvidenceSource = source + // Analysis outlives a pruned population too; only the window matters here. + _, _ = s.Analyze(operator, &pb.AnalyzeRequest{ExperimentId: p.Experiment.Id, ExpectedRevision: 1}) + if len(source.windows) != 1 { + t.Fatalf("%s: expected one window, got %d", tc.description, len(source.windows)) + } + w := source.windows[0] + if len(w.Sessions) != tc.expected { + t.Fatalf("%s: window carries %d sessions, want %d: %+v", tc.description, len(w.Sessions), tc.expected, w.Sessions) + } + if err := w.Validate(); err != nil { + t.Fatalf("%s: bound window invalid: %v", tc.description, err) + } + }) + }) + } +} diff --git a/pkg/treatment/controller/discovery.go b/pkg/treatment/controller/discovery.go index 4254f448e..6eb776573 100644 --- a/pkg/treatment/controller/discovery.go +++ b/pkg/treatment/controller/discovery.go @@ -129,6 +129,11 @@ func (s *Server) Discover(ctx context.Context, w evidence.Warehouse, templates [ failures = append(failures, e) continue } + enrolled, e := s.enrolled(ctx, t.Location) + if e != nil { + failures = append(failures, e) + continue + } for _, c := range candidates { if !t.inScope(c, prefixes) { continue @@ -145,12 +150,18 @@ func (s *Server) Discover(ctx context.Context, w evidence.Warehouse, templates [ } seed := sha256.Sum256([]byte(t.ID + "/" + c.Destination.String() + "/" + s.now().UTC().Format("2006-01-02"))) id := "hyp-" + hex.EncodeToString(seed[:12]) - baseline, e := w.Evidence(ctx, evidence.Window{Location: t.Location, Destination: c.Destination, ExperimentID: id, Agents: c.Agents, Start: start, End: end}) + // Only enrolled agents report sessions, so only they can carry + // bound evidence or receive an assignment. + cohort := enrolledIn(c.Agents, enrolled) + if len(cohort) < treatment.MinimumCohort { + continue + } + baseline, e := s.boundEvidence(ctx, w, evidence.Window{Location: t.Location, Destination: c.Destination, ExperimentID: id, Agents: cohort, Start: start, End: end}) if e != nil { failures = append(failures, e) continue } - n := &treatment.Experiment{ID: id, Location: t.Location, Hypothesis: fmt.Sprintf("Template %s: test %s for %s (ASN %d), observed RTT %.3f ms and retransmission ratio %.6f; this is a hypothesis, not an efficacy claim", t.ID, t.CongestionControl, c.Destination, c.ASN, c.RTTMs, c.RetransmissionRatio), Treatment: treatment.Treatment{ID: t.ID, Revision: 1, Destination: c.Destination, Settings: t.settings()}, Policy: t.policy(), Baseline: baseline, CohortOrder: c.Agents} + n := &treatment.Experiment{ID: id, Location: t.Location, Hypothesis: fmt.Sprintf("Template %s: test %s for %s (ASN %d), observed RTT %.3f ms and retransmission ratio %.6f; this is a hypothesis, not an efficacy claim", t.ID, t.CongestionControl, c.Destination, c.ASN, c.RTTMs, c.RetransmissionRatio), Treatment: treatment.Treatment{ID: t.ID, Revision: 1, Destination: c.Destination, Settings: t.settings()}, Policy: t.policy(), Baseline: baseline, CohortOrder: cohort} if _, e = s.propose(ctx, &pb.ProposeRequest{Experiment: wire.Experiment(n), RequestId: id}, "candidate-discovery", true); e != nil { if !errors.Is(e, errExperimentBudget) && !errors.Is(e, errDiscoverySuppressed) { failures = append(failures, e) @@ -161,3 +172,27 @@ func (s *Server) Discover(ctx context.Context, w evidence.Warehouse, templates [ } return errors.Join(failures...) } + +// enrolled returns the IDs of the agents enrolled in location. +func (s *Server) enrolled(ctx context.Context, location string) (map[string]bool, error) { + refs, err := s.Store.Repos().Agents.List(ctx, location) + if err != nil { + return nil, err + } + out := make(map[string]bool, len(refs)) + for _, r := range refs { + out[r.ID] = true + } + return out, nil +} + +// enrolledIn keeps the candidate agents that are enrolled, in order. +func enrolledIn(agents []string, enrolled map[string]bool) []string { + out := make([]string, 0, len(agents)) + for _, a := range agents { + if enrolled[a] { + out = append(out, a) + } + } + return out +} diff --git a/pkg/treatment/controller/discovery_test.go b/pkg/treatment/controller/discovery_test.go index 081372355..2f2baf8c6 100644 --- a/pkg/treatment/controller/discovery_test.go +++ b/pkg/treatment/controller/discovery_test.go @@ -6,6 +6,7 @@ import ( "errors" "math" "net/netip" + "slices" "strings" "testing" "time" @@ -51,32 +52,50 @@ func TestDiscoveryWindow(t *testing.T) { func TestDiscoveryWarehouseFailures(t *testing.T) { now := time.Unix(1800000000, 0) - candidate := evidence.Candidate{Destination: netip.MustParsePrefix("192.0.2.1/32"), ASN: 64512, Agents: []string{"a", "b", "c", "d", "e", "f"}, RTTMs: 5} + agents := []string{"a", "b", "c", "d", "e", "f"} + candidate := evidence.Candidate{Destination: netip.MustParsePrefix("192.0.2.1/32"), ASN: 64512, Agents: agents, RTTMs: 5} + failing := func() *fakeWarehouse { + return &fakeWarehouse{candidates: []evidence.Candidate{candidate}, evidenceErr: errors.New("baseline timeout")} + } for _, tc := range []struct { name, description string warehouse *fakeWarehouse nilWarehouse bool + enrolled []string wantErr string wantWindows int + wantPopulation []string }{ - {"candidates-fail", "negative: a failed scan is reported and takes no baseline", &fakeWarehouse{candidateErr: errors.New("warehouse offline")}, false, "warehouse offline", 0}, - {"baseline-fails", "negative: a failed baseline is reported and proposes nothing", &fakeWarehouse{candidates: []evidence.Candidate{candidate}, evidenceErr: errors.New("baseline timeout")}, false, "baseline timeout", 1}, - {"no-warehouse", "corner: discovery without a warehouse is a configuration error", nil, true, "requires a warehouse", 0}, - {"no-candidates", "boundary: an empty scan is not an error", &fakeWarehouse{}, false, "", 0}, + {"candidates-fail", "negative: a failed scan is reported and takes no baseline", &fakeWarehouse{candidateErr: errors.New("warehouse offline")}, false, agents, "warehouse offline", 0, nil}, + {"baseline-fails", "negative: a failed baseline is reported and proposes nothing", failing(), false, agents, "baseline timeout", 1, agents}, + {"no-warehouse", "corner: discovery without a warehouse is a configuration error", nil, true, agents, "requires a warehouse", 0, nil}, + {"no-candidates", "boundary: an empty scan is not an error", &fakeWarehouse{}, false, agents, "", 0, nil}, + {"unenrolled", "negative: candidate hosts that never checked in take no baseline and are not an error", failing(), false, nil, "", 0, nil}, + {"two-enrolled", "boundary: fewer than three enrolled candidate hosts cannot form a cohort", failing(), false, agents[:2], "", 0, nil}, + {"three-enrolled", "corner: the baseline measures only the enrolled candidates, each with its session", failing(), false, []string{"a", "c", "f"}, "baseline timeout", 1, []string{"a", "c", "f"}}, } { t.Run(tc.name, func(t *testing.T) { s := &Server{Store: memory.New(), Now: func() time.Time { return now }} + for _, id := range tc.enrolled { + enroll(t, s, id) + } var w evidence.Warehouse if !tc.nilWarehouse { w = tc.warehouse } - err := s.Discover(context.Background(), w, []DiscoveryTemplate{{ID: "near", Location: "test-dc", CongestionControl: "cubic", MaximumRTTMs: 15}}) + err := s.Discover(context.Background(), w, []DiscoveryTemplate{{ID: "near", Location: "dc1", CongestionControl: "cubic", MaximumRTTMs: 15}}) if tc.wantErr == "" && err != nil || tc.wantErr != "" && (err == nil || !strings.Contains(err.Error(), tc.wantErr)) { t.Fatalf("%s: err=%v want %q", tc.description, err, tc.wantErr) } if tc.warehouse != nil && len(tc.warehouse.windows) != tc.wantWindows { t.Fatalf("%s: baseline windows=%d want %d", tc.description, len(tc.warehouse.windows), tc.wantWindows) } + if tc.wantWindows > 0 { + win := tc.warehouse.windows[0] + if !slices.Equal(win.Agents, tc.wantPopulation) || len(win.Sessions) != len(tc.wantPopulation) { + t.Fatalf("%s: population %v with %d sessions, want %v with one session each", tc.description, win.Agents, len(win.Sessions), tc.wantPopulation) + } + } if n := len(experimentsOf(t, s.Store)); n != 0 { t.Fatalf("%s: proposals=%d", tc.description, n) } diff --git a/pkg/treatment/controller/evidence/clickhouse/analysis.sql b/pkg/treatment/controller/evidence/clickhouse/analysis.sql index 9387c1fa5..0a47dcee5 100644 --- a/pkg/treatment/controller/evidence/clickhouse/analysis.sql +++ b/pkg/treatment/controller/evidence/clickhouse/analysis.sql @@ -1,7 +1,10 @@ -- Scheduled whole-window query: an incremental MV cannot correctly compute -- cross-insert counter deltas. Deduplicate poll timestamps before differences. +-- A socket is bound when its (agent_id, host_boot_id, collection_id) is a +-- session the agent reported at check-in; only bound sockets are measured, +-- and unbound ones are counted so a reviewer sees spoofed or stale records. WITH sockets AS ( - SELECT agent_id, host_boot_id, netns_inode, inet_diag_msg_socket_cookie, + SELECT agent_id, host_boot_id, collection_id, netns_inode, inet_diag_msg_socket_cookie, min(timestamp_ns) AS first_at, max(timestamp_ns) AS last_at, countDistinct(timestamp_ns) AS samples, argMin(tcp_info_bytes_acked,timestamp_ns) AS first_acked, @@ -19,19 +22,24 @@ WITH sockets AS ( AND location={location:String} AND agent_id IN {agents:Array(String)} AND inet_diag_msg_state=1 AND hex(inet_diag_msg_socket_destination)={destination_hex:String} - GROUP BY agent_id,host_boot_id,netns_inode,inet_diag_msg_socket_cookie + GROUP BY agent_id,host_boot_id,collection_id,netns_inode,inet_diag_msg_socket_cookie +), classified AS ( + SELECT *, has({sessions:Array(Tuple(String,String,String))}, (agent_id, host_boot_id, collection_id)) AS bound + FROM sockets ) SELECT agent_id, - countIf(samples>=2) AS sockets, - sum(if(last_acked>=first_acked,last_acked-first_acked,0)) AS bytes_acked, + countIf(bound AND samples>=2) AS sockets, + sumIf(if(last_acked>=first_acked,last_acked-first_acked,0), bound) AS bytes_acked, bytes_acked*8/(({end:Int64}-{start:Int64})/1e9) AS goodput_bps, - sum(if(last_retrans>=first_retrans,last_retrans-first_retrans,0))/greatest(1,sum(if(last_sent>=first_sent,last_sent-first_sent,0))) AS retransmission_ratio, - quantileExact(0.95)(socket_rtt) AS rtt_p95_ms, - countIf(samples>=2 AND exposed) AS exposed_sockets, - toBool(max(last_at)>=fromUnixTimestamp64Nano({end:Int64}-30000000000) - AND max(maximum_gap_ns)<=30000000000 - AND countIf(last_acked=first_retrans,last_retrans-first_retrans,0), bound)/greatest(1,sumIf(if(last_sent>=first_sent,last_sent-first_sent,0), bound)) AS retransmission_ratio, + ifNotFinite(quantileExactIf(0.95)(socket_rtt, bound), 0) AS rtt_p95_ms, + countIf(bound AND samples>=2 AND exposed) AS exposed_sockets, + toBool(countIf(bound)>0 + AND maxIf(last_at, bound)>=fromUnixTimestamp64Nano({end:Int64}-30000000000) + AND maxIf(maximum_gap_ns, bound)<=30000000000 + AND countIf(bound AND (last_acked=2) AS unbound_sockets +FROM classified GROUP BY agent_id ORDER BY agent_id FORMAT JSONEachRow diff --git a/pkg/treatment/controller/evidence/clickhouse/clickhouse.go b/pkg/treatment/controller/evidence/clickhouse/clickhouse.go index 48fd2c490..5428ef321 100644 --- a/pkg/treatment/controller/evidence/clickhouse/clickhouse.go +++ b/pkg/treatment/controller/evidence/clickhouse/clickhouse.go @@ -37,7 +37,7 @@ var tableName = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*\.[a-zA-Z_][a-zA-Z0-9 // QueryVersion names the analysis query so evidence records which SQL // produced them; bump it when analysis.sql changes meaning. -const QueryVersion = "tcp-host-deltas-v1" +const QueryVersion = "tcp-host-deltas-v2-bound" // EnrichmentVersion names the telemetry schema the queries assume. const EnrichmentVersion = "xtcp-schema-2" @@ -119,6 +119,7 @@ type hostRow struct { RttP95Ms float64 `json:"rtt_p95_ms"` ExposedSockets uint64 `json:"exposed_sockets"` CollectionHealthy bool `json:"collection_healthy"` + UnboundSockets uint64 `json:"unbound_sockets"` } // destinationHex renders an address the way the telemetry stores it: a @@ -131,6 +132,26 @@ func destinationHex(a netip.Addr) string { return strings.ToUpper(hex.EncodeToString(raw)) } +// agentsParam renders an Array(String) literal. Quoting is safe only because +// Window.Validate has checked every ID against treatment.ValidID. +func agentsParam(agents []string) string { + quoted := make([]string, len(agents)) + for i, id := range agents { + quoted[i] = "'" + id + "'" + } + return "[" + strings.Join(quoted, ",") + "]" +} + +// sessionsParam renders an Array(Tuple(String,String,String)) literal of +// (agent_id, host_boot_id, collection_id), validated like agentsParam. +func sessionsParam(sessions []treatment.Session) string { + quoted := make([]string, len(sessions)) + for i, s := range sessions { + quoted[i] = "('" + s.AgentID + "','" + s.BootID + "','" + s.CollectionID + "')" + } + return "[" + strings.Join(quoted, ",") + "]" +} + // Evidence runs analysis.sql for the window. ArtifactSHA256 is the digest of // the raw response bytes as received; the response itself is not archived, // so reproducing it means re-running QueryVersion over the same window. @@ -138,11 +159,7 @@ func (c *Client) Evidence(ctx context.Context, w evidence.Window) (*treatment.Ev if e := w.Validate(); e != nil { return nil, e } - quoted := make([]string, len(w.Agents)) - for i, id := range w.Agents { - quoted[i] = "'" + id + "'" - } - params := map[string]string{"start": strconv.FormatInt(w.Start.UnixNano(), 10), "end": strconv.FormatInt(w.End.UnixNano(), 10), "location": w.Location, "assignment": w.ExperimentID, "agents": "[" + strings.Join(quoted, ",") + "]", "destination_hex": destinationHex(w.Destination.Addr())} + params := map[string]string{"start": strconv.FormatInt(w.Start.UnixNano(), 10), "end": strconv.FormatInt(w.End.UnixNano(), 10), "location": w.Location, "assignment": w.ExperimentID, "agents": agentsParam(w.Agents), "sessions": sessionsParam(w.Sessions), "destination_hex": destinationHex(w.Destination.Addr())} b, e := c.query(ctx, analysisSQL, params) if e != nil { return nil, e diff --git a/pkg/treatment/controller/evidence/clickhouse/clickhouse_test.go b/pkg/treatment/controller/evidence/clickhouse/clickhouse_test.go index 67e39b243..ea8eccf6a 100644 --- a/pkg/treatment/controller/evidence/clickhouse/clickhouse_test.go +++ b/pkg/treatment/controller/evidence/clickhouse/clickhouse_test.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "errors" + "fmt" "io" "net/http" "net/netip" @@ -14,6 +15,7 @@ import ( "testing" "time" + "github.com/randomizedcoder/xtcp2/pkg/treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/evidence" ) @@ -234,6 +236,16 @@ func TestClickHouseCandidateContracts(t *testing.T) { } } +// clickhouseRows inserts one socket's six polls, ten seconds apart, stamped +// with the given agent, boot and collection nonce. +func clickhouseRows(agent, boot, collection string, cookie int) string { + return fmt.Sprintf(`INSERT INTO default.sockets SELECT '%s','%s','%s',1,%d, + fromUnixTimestamp64Nano(1700000000000000000+toInt64(number)*10000000000), + 1000000+number*1000,1000000+number*1000,number*10,1000,500,true,'trial','dc1',1, + unhex('C0000201000000000000000000000000'),2,64512 FROM numbers(6); +`, agent, boot, collection, cookie) +} + // Execute the production SQL against an isolated ClickHouse local database. // No live service or production telemetry is accessed. func TestClickHouseAnalysisCounterDeltas(t *testing.T) { @@ -241,21 +253,18 @@ func TestClickHouseAnalysisCounterDeltas(t *testing.T) { if lerr != nil { t.Skip("clickhouse is not on PATH; the Nix quality check supplies it") } + const nonce1, nonce2, nonceX = "00000000000000000000000000000001", "00000000000000000000000000000002", "0000000000000000000000000000000f" + // The bound socket is inserted twice to prove poll deduplication. setup := `CREATE TABLE default.sockets ( - agent_id String,host_boot_id String,netns_inode UInt64,inet_diag_msg_socket_cookie UInt64, + agent_id String,host_boot_id String,collection_id String,netns_inode UInt64,inet_diag_msg_socket_cookie UInt64, timestamp_ns DateTime64(9),tcp_info_bytes_acked UInt64,tcp_info_bytes_sent UInt64, tcp_info_bytes_retrans UInt64,tcp_info_rtt UInt32,tcp_info_min_rtt UInt32, treatment_algorithm_exposed Bool,treatment_assignment_id String,location String, inet_diag_msg_state UInt8,inet_diag_msg_socket_destination FixedString(16), inet_diag_msg_family UInt8,enrich_socket_dest_asn UInt64) ENGINE=Memory; - INSERT INTO default.sockets SELECT 'host-1','boot-1',1,1, - fromUnixTimestamp64Nano(1700000000000000000+toInt64(number)*10000000000), - 1000000+number*1000,1000000+number*1000,number*10,1000,500,true,'trial','dc1',1, - unhex('C0000201000000000000000000000000'),2,64512 FROM numbers(6); - INSERT INTO default.sockets SELECT * FROM default.sockets; - ` +` + clickhouseRows("host-1", "boot-1", nonce1, 1) + "INSERT INTO default.sockets SELECT * FROM default.sockets;\n" query := strings.ReplaceAll(analysisSQL, "{{TABLE}}", "default.sockets") - run := func(sql string) []byte { + run := func(sql, sessions string) []byte { t.Helper() dir := t.TempDir() file := filepath.Join(dir, "query.sql") @@ -264,7 +273,7 @@ func TestClickHouseAnalysisCounterDeltas(t *testing.T) { } cmd := exec.Command(binary, "local", "--path", filepath.Join(dir, "data"), "--queries-file", file, "--multiquery", "--param_start=1700000000000000000", "--param_end=1700000060000000000", "--param_location=dc1", - "--param_agents=['host-1']", "--param_destination_hex=C0000201000000000000000000000000", "--param_assignment=trial", + "--param_agents=['host-1']", "--param_sessions="+sessions, "--param_destination_hex=C0000201000000000000000000000000", "--param_assignment=trial", "--param_minimum_rtt=0", "--param_maximum_rtt=10", "--param_minimum_retransmission=0", "--output_format_json_quote_64bit_integers=0") out, e := cmd.CombinedOutput() @@ -273,25 +282,46 @@ func TestClickHouseAnalysisCounterDeltas(t *testing.T) { } return out } - var h hostRow - if e := json.Unmarshal(run(setup+query), &h); e != nil { - t.Fatal(e) - } - if h.Sockets != 1 || h.BytesAcked != 5000 || h.ExposedSockets != 1 || !h.CollectionHealthy || h.RttP95Ms != 1 || h.RetransmissionRatio != .01 { - t.Fatalf("bad window deltas/deduplication: %+v", h) - } - reset := `INSERT INTO default.sockets SELECT 'host-1','boot-1',1,1, + session1 := sessionsParam([]treatment.Session{{AgentID: "host-1", BootID: "boot-1", CollectionID: nonce1}}) + bound := hostRow{AgentID: "host-1", Sockets: 1, BytesAcked: 5000, GoodputBps: 5000 * 8 / 60.0, RetransmissionRatio: .01, RttP95Ms: 1, ExposedSockets: 1, CollectionHealthy: true} + withUnbound := func(h hostRow, n uint64) hostRow { h.UnboundSockets = n; return h } + tests := []struct { + description string + extra string + sessions string + expected hostRow + healthOnly bool // a counter reset changes the deltas; only health is pinned + }{ + {"positive: rows of a reported session are measured, with deltas over deduplicated polls", "", session1, bound, false}, + {"positive: a nonce rotated mid-window counts both sessions' sockets", clickhouseRows("host-1", "boot-1", nonce2, 2), + sessionsParam([]treatment.Session{{AgentID: "host-1", BootID: "boot-1", CollectionID: nonce1}, {AgentID: "host-1", BootID: "boot-1", CollectionID: nonce2}}), + hostRow{AgentID: "host-1", Sockets: 2, BytesAcked: 10000, GoodputBps: 10000 * 8 / 60.0, RetransmissionRatio: .01, RttP95Ms: 1, ExposedSockets: 2, CollectionHealthy: true}, false}, + {"negative: rows claiming host-1 from another boot are excluded and counted", clickhouseRows("host-1", "boot-x", nonce1, 3), session1, withUnbound(bound, 1), false}, + {"negative: rows with the right boot and an unknown nonce are excluded and counted", clickhouseRows("host-1", "boot-1", nonceX, 4), session1, withUnbound(bound, 1), false}, + {"boundary: rows with an empty nonce (a journal without one) are excluded and counted", clickhouseRows("host-1", "boot-1", "", 5), session1, withUnbound(bound, 1), false}, + {"corner: with no sessions every socket is unbound and the host is not collection-healthy", "", "[]", hostRow{AgentID: "host-1", UnboundSockets: 1}, false}, + {"negative: a counter reset inside the window is not collection-healthy", `INSERT INTO default.sockets SELECT 'host-1','boot-1','` + nonce1 + `',1,1, fromUnixTimestamp64Nano(1700000055000000000),1,1,0,1000,500,true,'trial','dc1',1, - unhex('C0000201000000000000000000000000'),2,64512;` - if e := json.Unmarshal(run(setup+reset+query), &h); e != nil { - t.Fatal(e) + unhex('C0000201000000000000000000000000'),2,64512; +`, session1, hostRow{AgentID: "host-1"}, true}, } - if h.CollectionHealthy { - t.Fatal("counter reset accepted as healthy") + for _, tc := range tests { + t.Run(tc.description, func(t *testing.T) { + var h hostRow + if e := json.Unmarshal(run(setup+tc.extra+query, tc.sessions), &h); e != nil { + t.Fatal(e) + } + if tc.healthOnly { + h = hostRow{AgentID: h.AgentID, CollectionHealthy: h.CollectionHealthy, UnboundSockets: h.UnboundSockets} + } + if h != tc.expected { + t.Fatalf("%s:\n got %+v\nwant %+v", tc.description, h, tc.expected) + } + }) } // Syntax/type-check candidate discovery too; a one-host fixture cannot meet // its six-host eligibility threshold. - if out := run(setup + strings.ReplaceAll(discoverySQL, "{{TABLE}}", "default.sockets")); len(out) != 0 { + if out := run(setup+strings.ReplaceAll(discoverySQL, "{{TABLE}}", "default.sockets"), session1); len(out) != 0 { t.Fatalf("unexpected discovery: %s", out) } } diff --git a/pkg/treatment/controller/evidence/evidence.go b/pkg/treatment/controller/evidence/evidence.go index 0367136d5..ba7a7e7bb 100644 --- a/pkg/treatment/controller/evidence/evidence.go +++ b/pkg/treatment/controller/evidence/evidence.go @@ -16,24 +16,42 @@ import ( // Window is what a Source measures: one destination, one frozen population, // one half-open interval [Start, End). ExperimentID is the assignment the -// exposure columns are matched against. +// exposure columns are matched against. Sessions are the (agent, boot, +// collection) triples the population reported over mTLS; records carrying a +// population agent ID under any other triple are counted as unbound and +// excluded from the measurements. type Window struct { Location string Destination netip.Prefix ExperimentID string Agents []string + Sessions []treatment.Session Start, End time.Time } +// MaxSessions bounds Window.Sessions: several restarts per host over the +// longest window. +const MaxSessions = 8 * treatment.MaxAgents + // Validate applies the bounds every adapter must enforce before querying. func (w Window) Validate() error { if !w.End.After(w.Start) || w.End.Sub(w.Start) > MaxWindow || len(w.Agents) == 0 || len(w.Agents) > treatment.MaxAgents { return errors.New("invalid analysis window/population") } + population := make(map[string]bool, len(w.Agents)) for _, id := range w.Agents { if !treatment.ValidID(id) { return errors.New("invalid agent") } + population[id] = true + } + if len(w.Sessions) > MaxSessions { + return errors.New("too many sessions") + } + for _, s := range w.Sessions { + if !population[s.AgentID] || !treatment.ValidID(s.BootID) || !treatment.ValidCollectionID(s.CollectionID) { + return errors.New("invalid session") + } } if !w.Destination.IsValid() { return errors.New("invalid destination") diff --git a/pkg/treatment/controller/evidence/evidence_test.go b/pkg/treatment/controller/evidence/evidence_test.go index 9ba1d7d48..952daeb1d 100644 --- a/pkg/treatment/controller/evidence/evidence_test.go +++ b/pkg/treatment/controller/evidence/evidence_test.go @@ -7,6 +7,7 @@ import ( "time" "github.com/randomizedcoder/xtcp2/pkg/treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" ) func TestWindowFor(t *testing.T) { @@ -52,6 +53,11 @@ func TestWindowFor(t *testing.T) { } } +// session is a well-formed session of agent. +func session(agent string) treatment.Session { + return treatment.Session{AgentID: agent, BootID: "boot-1", CollectionID: fixtures.CollectionID} +} + func TestWindowValidate(t *testing.T) { now := time.Unix(1700000000, 0) valid := func() Window { @@ -76,6 +82,24 @@ func TestWindowValidate(t *testing.T) { {"bad-agent", "negative: an agent ID that could inject a parameter is refused", func(w *Window) { w.Agents = []string{"host'"} }, "invalid agent"}, {"bad-destination", "negative: the zero prefix is refused", func(w *Window) { w.Destination = netip.Prefix{} }, "invalid destination"}, {"bad-location", "corner: the location is an identifier too", func(w *Window) { w.Location = "dc 1" }, "location"}, + {"session", "positive: a session of a population agent passes", func(w *Window) { w.Sessions = []treatment.Session{session("a")} }, ""}, + {"session-outsider", "negative: a session for an agent outside the population is refused", func(w *Window) { w.Sessions = []treatment.Session{session("b")} }, "invalid session"}, + {"session-bad-nonce", "negative: a nonce that is not 32 lowercase hex digits could inject a parameter", func(w *Window) { + s := session("a") + s.CollectionID = strings.ToUpper(s.CollectionID) + w.Sessions = []treatment.Session{s} + }, "invalid session"}, + {"session-bad-boot", "negative: a boot ID with a quote is refused", func(w *Window) { + s := session("a") + s.BootID = "boot'" + w.Sessions = []treatment.Session{s} + }, "invalid session"}, + {"too-many-sessions", "boundary: one session over MaxSessions is refused", func(w *Window) { + w.Sessions = make([]treatment.Session, MaxSessions+1) + for i := range w.Sessions { + w.Sessions[i] = session("a") + } + }, "too many sessions"}, } { t.Run(tc.name, func(t *testing.T) { w := valid() diff --git a/pkg/treatment/controller/helpers_test.go b/pkg/treatment/controller/helpers_test.go index a014bc4e7..d662da77f 100644 --- a/pkg/treatment/controller/helpers_test.go +++ b/pkg/treatment/controller/helpers_test.go @@ -20,6 +20,7 @@ import ( "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store" "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store/memory" "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store/pgtest" + "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" "github.com/randomizedcoder/xtcp2/pkg/treatment/wire" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -48,7 +49,7 @@ func testServer(s store.Store, now *time.Time) *Server { } func capabilities() *pb.Capabilities { - return &pb.Capabilities{BootId: "boot-1", KernelRelease: "test", CongestionAlgorithms: []string{"cubic", "bbr"}, SimpleHostRouting: true, ActuatorEnabled: true} + return &pb.Capabilities{BootId: "boot-1", CollectionId: fixtures.CollectionID, KernelRelease: "test", CongestionAlgorithms: []string{"cubic", "bbr"}, SimpleHostRouting: true, ActuatorEnabled: true} } func enroll(t *testing.T, s *Server, id string) { diff --git a/pkg/treatment/controller/maintenance.go b/pkg/treatment/controller/maintenance.go index 792a1ad29..951abad7b 100644 --- a/pkg/treatment/controller/maintenance.go +++ b/pkg/treatment/controller/maintenance.go @@ -44,6 +44,9 @@ func (s *Server) Housekeep(ctx context.Context, source evidence.Source, warehous errs = append(errs, fmt.Errorf("candidate discovery: %w", e)) } } + if _, e := s.Store.Repos().Agents.PruneSessions(ctx, s.now().Add(-sessionRetention)); e != nil { + errs = append(errs, fmt.Errorf("session retention: %w", e)) + } return errors.Join(errs...) }) } @@ -131,7 +134,7 @@ func (s *Server) fetchEvidence(ctx context.Context, source evidence.Source, snap if snapshot.Status == treatment.StatusComplete { input = s.driftInput(snapshot) } - return source.Evidence(ctx, evidence.WindowFor(input, s.now())) + return s.boundEvidence(ctx, source, evidence.WindowFor(input, s.now())) } // driftPass records drift evidence for a completed treatment and flags a diff --git a/pkg/treatment/controller/rpc_contracts_test.go b/pkg/treatment/controller/rpc_contracts_test.go index fa75c8a53..b541905d9 100644 --- a/pkg/treatment/controller/rpc_contracts_test.go +++ b/pkg/treatment/controller/rpc_contracts_test.go @@ -12,6 +12,7 @@ import ( pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store" "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store/postgres" + "github.com/randomizedcoder/xtcp2/pkg/treatment/internal/fixtures" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) @@ -39,7 +40,7 @@ func TestCheckInValidation(t *testing.T) { }, codes.InvalidArgument}, } { t.Run(tc.name, func(t *testing.T) { - r := &pb.CheckInRequest{AgentId: "host", Location: "dc1", Capabilities: &pb.Capabilities{BootId: "boot", CongestionAlgorithms: []string{"cubic"}}} + r := &pb.CheckInRequest{AgentId: "host", Location: "dc1", Capabilities: &pb.Capabilities{BootId: "boot", CollectionId: fixtures.CollectionID, CongestionAlgorithms: []string{"cubic"}}} tc.change(r) _, err := (&Server{}).CheckIn(testIdentity("agent/dc1/host"), r) if status.Code(err) != tc.expected { diff --git a/pkg/treatment/controller/store/memory/memory.go b/pkg/treatment/controller/store/memory/memory.go index a74f71012..481de83e7 100644 --- a/pkg/treatment/controller/store/memory/memory.go +++ b/pkg/treatment/controller/store/memory/memory.go @@ -4,6 +4,7 @@ package memory import ( + "cmp" "context" "maps" "net/netip" @@ -20,6 +21,7 @@ type agentRow struct { store.Agent desired treatment.DesiredState updatedAt time.Time + sessions []treatment.Session } type evidenceRow struct { @@ -81,6 +83,7 @@ func cloneAgent(a *agentRow) *agentRow { c := *a c.Report = cloneReport(&a.Report) c.desired = cloneDesired(&a.desired) + c.sessions = slices.Clone(a.sessions) return &c } @@ -206,9 +209,62 @@ func (r agentsRepo) Observe(_ context.Context, report *treatment.AgentReport, se } a.Report = cloneReport(report) a.LastSeen = seen + a.observeSession(report.Capabilities, seen) return nil } +// observeSession mirrors the PostgreSQL upsert: insert a new session, +// refresh a known one only when its LastSeen is SessionRefresh old. +func (a *agentRow) observeSession(c treatment.Capabilities, seen time.Time) { + for i := range a.sessions { + s := &a.sessions[i] + if s.BootID == c.BootID && s.CollectionID == c.CollectionID { + if !s.LastSeen.After(seen.Add(-store.SessionRefresh)) { + s.LastSeen = seen + } + return + } + } + a.sessions = append(a.sessions, treatment.Session{AgentID: a.ID, BootID: c.BootID, CollectionID: c.CollectionID, FirstSeen: seen, LastSeen: seen}) +} + +func (r agentsRepo) Sessions(_ context.Context, ids []string, from, to time.Time) ([]treatment.Session, error) { + defer r.enter()() + var out []treatment.Session + for _, id := range ids { + a, ok := r.s.st.agents[id] + if !ok { + continue + } + for _, s := range a.sessions { + if !s.LastSeen.Before(from) && !s.FirstSeen.After(to) { + out = append(out, s) + } + } + } + slices.SortFunc(out, func(x, y treatment.Session) int { + return cmp.Or(cmp.Compare(x.AgentID, y.AgentID), x.FirstSeen.Compare(y.FirstSeen), cmp.Compare(x.BootID, y.BootID), cmp.Compare(x.CollectionID, y.CollectionID)) + }) + return out, nil +} + +func (r agentsRepo) PruneSessions(_ context.Context, before time.Time) (int64, error) { + defer r.enter()() + var n int64 + for _, a := range r.s.st.agents { + kept := a.sessions[:0] + for _, s := range a.sessions { + if s.LastSeen.Before(before) { + n++ + continue + } + kept = append(kept, s) + } + a.sessions = kept + } + return n, nil +} + func (r agentsRepo) Desired(_ context.Context, id string) (*treatment.DesiredState, error) { defer r.enter()() a, ok := r.s.st.agents[id] diff --git a/pkg/treatment/controller/store/postgres/agents.go b/pkg/treatment/controller/store/postgres/agents.go index 81d098fe8..db5d774db 100644 --- a/pkg/treatment/controller/store/postgres/agents.go +++ b/pkg/treatment/controller/store/postgres/agents.go @@ -35,8 +35,8 @@ func (a *agents) Get(ctx context.Context, id string) (*store.Agent, error) { out := &store.Agent{ID: id} var seen sql.NullTime var reported int64 - err := q.QueryRowContext(ctx, `SELECT location,enrolled_at,last_seen,boot_id,kernel_release,simple_host_routing,actuator_enabled,locally_suspended,reported_revision - FROM xtcp_treatment_agents WHERE id=$1`, id).Scan(&out.Location, &out.EnrolledAt, &seen, &out.Report.Capabilities.BootID, &out.Report.Capabilities.KernelRelease, &out.Report.Capabilities.SimpleHostRouting, &out.Report.Capabilities.ActuatorEnabled, &out.Report.LocallySuspended, &reported) + err := q.QueryRowContext(ctx, `SELECT location,enrolled_at,last_seen,boot_id,collection_id,kernel_release,simple_host_routing,actuator_enabled,locally_suspended,reported_revision + FROM xtcp_treatment_agents WHERE id=$1`, id).Scan(&out.Location, &out.EnrolledAt, &seen, &out.Report.Capabilities.BootID, &out.Report.Capabilities.CollectionID, &out.Report.Capabilities.KernelRelease, &out.Report.Capabilities.SimpleHostRouting, &out.Report.Capabilities.ActuatorEnabled, &out.Report.LocallySuspended, &reported) if err != nil { return nil, notFound(err) } @@ -77,10 +77,11 @@ func (a *agents) Get(ctx context.Context, id string) (*store.Agent, error) { return out, nil } -// Observe always stamps the heartbeat columns on the agents row. The -// algorithm and record rows are rewritten only when their digest differs -// from the one stored with the previous report, so a steady fleet costs one -// single-row update per check-in. +// Observe always stamps the heartbeat columns on the agents row and records +// the reported session. The algorithm and record rows are rewritten only +// when their digest differs from the one stored with the previous report, +// and a known session's last_seen only every SessionRefresh, so a steady +// fleet costs one single-row update and one no-op upsert per check-in. func (a *agents) Observe(ctx context.Context, report *treatment.AgentReport, seen time.Time) error { digest := reportDigest(report) return a.atomic(ctx, func(q querier) error { @@ -89,17 +90,20 @@ func (a *agents) Observe(ctx context.Context, report *treatment.AgentReport, see // The subquery's report_digest is the value before this update. err := q.QueryRowContext(ctx, `UPDATE xtcp_treatment_agents AS a SET last_seen=$3,boot_id=$4,kernel_release=$5,simple_host_routing=$6,actuator_enabled=$7, - locally_suspended=$8,reported_revision=$9,report_digest=$10 + locally_suspended=$8,reported_revision=$9,report_digest=$10,collection_id=$11 FROM (SELECT id,report_digest FROM xtcp_treatment_agents WHERE id=$1 AND location=$2 FOR UPDATE) AS o WHERE a.id=o.id RETURNING o.report_digest`, report.AgentID, report.Location, seen, c.BootID, c.KernelRelease, c.SimpleHostRouting, c.ActuatorEnabled, - report.LocallySuspended, int64(report.DesiredRevision), digest).Scan(&previous) + report.LocallySuspended, int64(report.DesiredRevision), digest, c.CollectionID).Scan(&previous) if errors.Is(err, sql.ErrNoRows) { return store.ErrNotEnrolled } if err != nil { return err } + if _, err = q.ExecContext(ctx, upsertSessionSQL, report.AgentID, c.BootID, c.CollectionID, seen, store.SessionRefresh.Seconds()); err != nil { + return err + } if previous == digest { return nil } @@ -239,3 +243,37 @@ func (a *agents) DesiredSince(ctx context.Context, since time.Time) ([]store.Des } return out, errors.Join(rows.Err(), rows.Close()) } + +// upsertSessionSQL records a session, refreshing last_seen only when it is +// at least SessionRefresh old so heartbeats do not rewrite the row. +const upsertSessionSQL = `INSERT INTO xtcp_treatment_agent_sessions(agent_id,boot_id,collection_id,first_seen,last_seen) + VALUES($1,$2,$3,$4,$4) ON CONFLICT(agent_id,boot_id,collection_id) DO UPDATE SET last_seen=EXCLUDED.last_seen + WHERE xtcp_treatment_agent_sessions.last_seen <= EXCLUDED.last_seen - make_interval(secs => $5)` + +func (a *agents) Sessions(ctx context.Context, ids []string, from, to time.Time) ([]treatment.Session, error) { + if len(ids) == 0 { + return nil, nil + } + rows, err := a.q().QueryContext(ctx, `SELECT agent_id,boot_id,collection_id,first_seen,last_seen FROM xtcp_treatment_agent_sessions + WHERE agent_id = ANY($1) AND last_seen >= $2 AND first_seen <= $3 ORDER BY agent_id,first_seen,boot_id,collection_id`, ids, from, to) + if err != nil { + return nil, err + } + var out []treatment.Session + for rows.Next() { + var s treatment.Session + if err = rows.Scan(&s.AgentID, &s.BootID, &s.CollectionID, &s.FirstSeen, &s.LastSeen); err != nil { + return nil, errors.Join(err, rows.Close()) + } + out = append(out, s) + } + return out, errors.Join(rows.Err(), rows.Close()) +} + +func (a *agents) PruneSessions(ctx context.Context, before time.Time) (int64, error) { + r, err := a.q().ExecContext(ctx, "DELETE FROM xtcp_treatment_agent_sessions WHERE last_seen < $1", before) + if err != nil { + return 0, err + } + return r.RowsAffected() +} diff --git a/pkg/treatment/controller/store/postgres/evidence.go b/pkg/treatment/controller/store/postgres/evidence.go index 53109a72a..01a897f7d 100644 --- a/pkg/treatment/controller/store/postgres/evidence.go +++ b/pkg/treatment/controller/store/postgres/evidence.go @@ -15,7 +15,7 @@ import ( type evidence struct{ *runner } -var hostColumns = []string{"evidence_id", positionColumnCst, agentIDColumnCst, "sockets", "bytes_acked", "goodput_bps", "retransmission_ratio", "rtt_p95_ms", "exposed_sockets", "collection_healthy"} +var hostColumns = []string{"evidence_id", positionColumnCst, agentIDColumnCst, "sockets", "bytes_acked", "goodput_bps", "retransmission_ratio", "rtt_p95_ms", "exposed_sockets", "collection_healthy", "unbound_sockets"} // digest is a content hash over the domain value so Put can detect reuse of // an evidence ID for different content without reloading every host row. @@ -28,6 +28,11 @@ func digest(e *treatment.Evidence) string { for i := range e.Hosts { v := &e.Hosts[i] fmt.Fprintf(h, "%s\x00%d\x00%d\x00%d\x00%d\x00%d\x00%d\x00%t\x00", v.AgentID, v.Sockets, v.BytesAcked, math.Float64bits(v.GoodputBps), math.Float64bits(v.RetransmissionRatio), math.Float64bits(v.RttP95Ms), v.ExposedSockets, v.CollectionHealthy) + // Hashed only when set, so evidence stored before the column + // existed keeps the digest it was stored with. + if v.UnboundSockets != 0 { + fmt.Fprintf(h, "u%d\x00", v.UnboundSockets) + } } return hex.EncodeToString(h.Sum(nil)) } @@ -75,18 +80,18 @@ func getEvidenceMany(ctx context.Context, q querier, ids []string) (map[string]* // loadEvidenceHosts appends each evidence document's host rows in position // order. func loadEvidenceHosts(ctx context.Context, q querier, ids []string, into map[string]*treatment.Evidence) error { - rows, err := q.QueryContext(ctx, "SELECT evidence_id,agent_id,sockets,bytes_acked,goodput_bps,retransmission_ratio,rtt_p95_ms,exposed_sockets,collection_healthy FROM xtcp_treatment_evidence_hosts WHERE evidence_id = ANY($1) ORDER BY evidence_id,position", ids) + rows, err := q.QueryContext(ctx, "SELECT evidence_id,agent_id,sockets,bytes_acked,goodput_bps,retransmission_ratio,rtt_p95_ms,exposed_sockets,collection_healthy,unbound_sockets FROM xtcp_treatment_evidence_hosts WHERE evidence_id = ANY($1) ORDER BY evidence_id,position", ids) if err != nil { return err } for rows.Next() { var id string var h treatment.HostEvidence - var sockets, bytes, exposed int64 - if err = rows.Scan(&id, &h.AgentID, &sockets, &bytes, &h.GoodputBps, &h.RetransmissionRatio, &h.RttP95Ms, &exposed, &h.CollectionHealthy); err != nil { + var sockets, bytes, exposed, unbound int64 + if err = rows.Scan(&id, &h.AgentID, &sockets, &bytes, &h.GoodputBps, &h.RetransmissionRatio, &h.RttP95Ms, &exposed, &h.CollectionHealthy, &unbound); err != nil { return errors.Join(err, rows.Close()) } - h.Sockets, h.BytesAcked, h.ExposedSockets = uint64(sockets), uint64(bytes), uint64(exposed) + h.Sockets, h.BytesAcked, h.ExposedSockets, h.UnboundSockets = uint64(sockets), uint64(bytes), uint64(exposed), uint64(unbound) if e, ok := into[id]; ok { e.Hosts = append(e.Hosts, h) } @@ -124,7 +129,7 @@ func putEvidence(ctx context.Context, q querier, experimentID string, e *treatme rows := make([][]any, len(e.Hosts)) for i := range e.Hosts { h := &e.Hosts[i] - rows[i] = []any{e.ID, i, h.AgentID, int64(h.Sockets), int64(h.BytesAcked), h.GoodputBps, h.RetransmissionRatio, h.RttP95Ms, int64(h.ExposedSockets), h.CollectionHealthy} + rows[i] = []any{e.ID, i, h.AgentID, int64(h.Sockets), int64(h.BytesAcked), h.GoodputBps, h.RetransmissionRatio, h.RttP95Ms, int64(h.ExposedSockets), h.CollectionHealthy, int64(h.UnboundSockets)} } return batchInsert(ctx, q, "xtcp_treatment_evidence_hosts", hostColumns, rows) } diff --git a/pkg/treatment/controller/store/postgres/heartbeat_test.go b/pkg/treatment/controller/store/postgres/heartbeat_test.go index bab7d57c9..9c6d025ae 100644 --- a/pkg/treatment/controller/store/postgres/heartbeat_test.go +++ b/pkg/treatment/controller/store/postgres/heartbeat_test.go @@ -40,7 +40,7 @@ func sampleReport() *treatment.AgentReport { d := fixtures.Desired(now) return &treatment.AgentReport{ AgentID: "host-1", Location: "dc1", - Capabilities: treatment.Capabilities{BootID: "boot-1", KernelRelease: "6.12.0", CongestionAlgorithms: []string{"cubic", "bbr"}, ActuatorEnabled: true}, + Capabilities: treatment.Capabilities{BootID: "boot-1", CollectionID: fixtures.CollectionID, KernelRelease: "6.12.0", CongestionAlgorithms: []string{"cubic", "bbr"}, ActuatorEnabled: true}, DesiredRevision: 3, Records: []treatment.ApplicationRecord{ {Assignment: d.Assignments[0], DesiredRevision: 3, Status: treatment.ApplyVerified, ReceivedAt: now, AppliedAt: now.Add(time.Second), VerifiedAt: now.Add(2 * time.Second)}, diff --git a/pkg/treatment/controller/store/postgres/schema.sql b/pkg/treatment/controller/store/postgres/schema.sql index 2c6e9f925..61f6f1b1f 100644 --- a/pkg/treatment/controller/store/postgres/schema.sql +++ b/pkg/treatment/controller/store/postgres/schema.sql @@ -9,6 +9,7 @@ CREATE TABLE IF NOT EXISTS xtcp_treatment_agents ( enrolled_at timestamptz NOT NULL DEFAULT now(), last_seen timestamptz, boot_id text NOT NULL DEFAULT '', + collection_id text NOT NULL DEFAULT '', kernel_release text NOT NULL DEFAULT '', simple_host_routing boolean NOT NULL DEFAULT false, actuator_enabled boolean NOT NULL DEFAULT false, @@ -25,6 +26,20 @@ CREATE TABLE IF NOT EXISTS xtcp_treatment_agents ( CREATE INDEX IF NOT EXISTS xtcp_treatment_agents_location ON xtcp_treatment_agents(location); CREATE INDEX IF NOT EXISTS xtcp_treatment_agents_desired_updated ON xtcp_treatment_agents(desired_updated_at); +-- Every (boot, collection nonce) an agent has reported, so evidence over a +-- window that spans a restart still binds the records stamped before it. +-- last_seen is refreshed at most every few minutes; Agents.PruneSessions +-- drops rows older than the longest analysis window. +CREATE TABLE IF NOT EXISTS xtcp_treatment_agent_sessions ( + agent_id text NOT NULL REFERENCES xtcp_treatment_agents(id) ON DELETE CASCADE, + boot_id text NOT NULL, + collection_id text NOT NULL, + first_seen timestamptz NOT NULL, + last_seen timestamptz NOT NULL, + PRIMARY KEY (agent_id, boot_id, collection_id) +); +CREATE INDEX IF NOT EXISTS xtcp_treatment_agent_sessions_last_seen ON xtcp_treatment_agent_sessions(last_seen); + CREATE TABLE IF NOT EXISTS xtcp_treatment_agent_algorithms ( agent_id text NOT NULL REFERENCES xtcp_treatment_agents(id) ON DELETE CASCADE, position integer NOT NULL, @@ -134,6 +149,7 @@ CREATE TABLE IF NOT EXISTS xtcp_treatment_evidence_hosts ( rtt_p95_ms double precision NOT NULL, exposed_sockets bigint NOT NULL, collection_healthy boolean NOT NULL, + unbound_sockets bigint NOT NULL DEFAULT 0, PRIMARY KEY (evidence_id, position) ); @@ -208,3 +224,8 @@ CREATE TABLE IF NOT EXISTS xtcp_treatment_events ( kind text NOT NULL, payload jsonb NOT NULL ); + +-- Columns added after the first pre-release schema. CREATE TABLE IF NOT +-- EXISTS leaves an existing table alone, so these bring it forward. +ALTER TABLE xtcp_treatment_agents ADD COLUMN IF NOT EXISTS collection_id text NOT NULL DEFAULT ''; +ALTER TABLE xtcp_treatment_evidence_hosts ADD COLUMN IF NOT EXISTS unbound_sockets bigint NOT NULL DEFAULT 0; diff --git a/pkg/treatment/controller/store/store.go b/pkg/treatment/controller/store/store.go index 050426e4e..3a7f5a4d8 100644 --- a/pkg/treatment/controller/store/store.go +++ b/pkg/treatment/controller/store/store.go @@ -77,8 +77,21 @@ type Agents interface { // own clock, so callers carry the newest stamp returned forward as the // next since rather than comparing it with their clock. DesiredSince(ctx context.Context, since time.Time) ([]DesiredRevision, error) + // Sessions returns the (boot, collection) sessions of the named agents + // that overlap [from, to]: first seen no later than to and last seen no + // earlier than from. Observe records sessions and refreshes a known + // session's LastSeen at most every SessionRefresh, so callers widen from + // by at least that much. + Sessions(ctx context.Context, ids []string, from, to time.Time) ([]treatment.Session, error) + // PruneSessions deletes sessions last seen before the cutoff and + // returns how many it deleted. + PruneSessions(ctx context.Context, before time.Time) (int64, error) } +// SessionRefresh is how stale a session's LastSeen may become before a +// check-in rewrites it. +const SessionRefresh = 5 * time.Minute + // DesiredRevision is one agent's desired revision and the store's stamp of // when it last changed. type DesiredRevision struct { diff --git a/pkg/treatment/controller/store/storetest/storetest.go b/pkg/treatment/controller/store/storetest/storetest.go index cfda3b130..cdae251a3 100644 --- a/pkg/treatment/controller/store/storetest/storetest.go +++ b/pkg/treatment/controller/store/storetest/storetest.go @@ -59,7 +59,7 @@ func report(id, location string) *treatment.AgentReport { return &treatment.AgentReport{ AgentID: id, Location: location, - Capabilities: treatment.Capabilities{BootID: "boot-1", KernelRelease: "6.12.0", CongestionAlgorithms: []string{"cubic", "bbr", "dctcp"}, SimpleHostRouting: true, ActuatorEnabled: true}, + Capabilities: treatment.Capabilities{BootID: "boot-1", CollectionID: fixtures.CollectionID, KernelRelease: "6.12.0", CongestionAlgorithms: []string{"cubic", "bbr", "dctcp"}, SimpleHostRouting: true, ActuatorEnabled: true}, DesiredRevision: 3, LocallySuspended: true, Records: []treatment.ApplicationRecord{{ @@ -109,7 +109,7 @@ type agentCase struct { func agents(t *testing.T, open Opener) { ctx := context.Background() initial := &treatment.DesiredState{AgentID: host1Cst, Revision: 1, Suspended: true, IssuedAt: now} - for _, tc := range slices.Concat(agentEnrollCases(ctx, initial), agentDesiredSinceCases(ctx, initial), agentLookupCases(ctx, initial)) { + for _, tc := range slices.Concat(agentEnrollCases(ctx, initial), agentDesiredSinceCases(ctx, initial), agentLookupCases(ctx, initial), agentSessionCases(ctx, initial)) { t.Run(tc.name, func(t *testing.T) { t.Log(tc.description) tc.run(t, open(t).Repos().Agents) @@ -117,6 +117,91 @@ func agents(t *testing.T, open Opener) { } } +// observeSession checks host-1 in with the given boot and nonce at seen. +func observeSession(ctx context.Context, t *testing.T, a store.Agents, boot, collection string, seen time.Time) { + t.Helper() + r := report(host1Cst, locationCst) + r.Capabilities.BootID, r.Capabilities.CollectionID = boot, collection + must(t, a.Observe(ctx, r, seen)) +} + +// sessionKeys renders sessions as boot/nonce@first..last for comparison. +func sessionKeys(sessions []treatment.Session) []string { + out := make([]string, len(sessions)) + for i, s := range sessions { + out[i] = fmt.Sprintf("%s/%s/%s@%d..%d", s.AgentID, s.BootID, s.CollectionID[len(s.CollectionID)-1:], s.FirstSeen.Unix()-now.Unix(), s.LastSeen.Unix()-now.Unix()) + } + return out +} + +// agentSessionCases covers the (boot, collection) history evidence binds to. +func agentSessionCases(ctx context.Context, initial *treatment.DesiredState) []agentCase { + const nonce1, nonce2 = "00000000000000000000000000000001", "00000000000000000000000000000002" + sessions := func(t *testing.T, a store.Agents, from, to time.Time, expected ...string) { + t.Helper() + got, err := a.Sessions(ctx, []string{host1Cst, "ghost"}, from, to) + must(t, err) + if keys := sessionKeys(got); !slices.Equal(keys, expected) { + t.Fatalf("expected sessions %v, got %v", expected, keys) + } + } + return []agentCase{ + {"session-recorded", "positive: a check-in records its session and the agent's current nonce", func(t *testing.T, a store.Agents) { + must(t, a.Enroll(ctx, host1Cst, locationCst, initial)) + observeSession(ctx, t, a, "boot-1", nonce1, now) + sessions(t, a, now.Add(-time.Hour), now.Add(time.Hour), "host-1/boot-1/1@0..0") + got, err := a.Get(ctx, host1Cst) + must(t, err) + if got.Report.Capabilities.CollectionID != nonce1 { + t.Fatalf("expected the reported nonce on the agent, got %q", got.Report.Capabilities.CollectionID) + } + }}, + {"rotation", "positive: a restarted agent adds a session and both overlap a window spanning the restart", func(t *testing.T, a store.Agents) { + must(t, a.Enroll(ctx, host1Cst, locationCst, initial)) + observeSession(ctx, t, a, "boot-1", nonce1, now) + observeSession(ctx, t, a, "boot-1", nonce2, now.Add(10*time.Minute)) + sessions(t, a, now.Add(-time.Minute), now.Add(time.Hour), "host-1/boot-1/1@0..0", "host-1/boot-1/2@600..600") + }}, + {"outside-window", "negative: sessions last seen before the window or first seen after it are not returned", func(t *testing.T, a store.Agents) { + must(t, a.Enroll(ctx, host1Cst, locationCst, initial)) + observeSession(ctx, t, a, "boot-1", nonce1, now) + observeSession(ctx, t, a, "boot-2", nonce2, now.Add(2*time.Hour)) + sessions(t, a, now.Add(time.Minute), now.Add(time.Hour)) + sessions(t, a, now, now, "host-1/boot-1/1@0..0") + }}, + {"heartbeat-throttle", "boundary: last_seen moves only once it is SessionRefresh old", func(t *testing.T, a store.Agents) { + must(t, a.Enroll(ctx, host1Cst, locationCst, initial)) + observeSession(ctx, t, a, "boot-1", nonce1, now) + observeSession(ctx, t, a, "boot-1", nonce1, now.Add(store.SessionRefresh-time.Second)) + sessions(t, a, now.Add(-time.Hour), now.Add(time.Hour), "host-1/boot-1/1@0..0") + observeSession(ctx, t, a, "boot-1", nonce1, now.Add(store.SessionRefresh)) + sessions(t, a, now.Add(-time.Hour), now.Add(time.Hour), "host-1/boot-1/1@0..300") + }}, + {"retention", "corner: pruning deletes only sessions last seen before the cutoff", func(t *testing.T, a store.Agents) { + must(t, a.Enroll(ctx, host1Cst, locationCst, initial)) + observeSession(ctx, t, a, "boot-1", nonce1, now) + observeSession(ctx, t, a, "boot-1", nonce2, now.Add(time.Hour)) + n, err := a.PruneSessions(ctx, now.Add(time.Minute)) + must(t, err) + if n != 1 { + t.Fatalf("expected one pruned session, got %d", n) + } + sessions(t, a, now.Add(-time.Hour), now.Add(2*time.Hour), "host-1/boot-1/2@3600..3600") + }}, + {"sessions-unenrolled", "negative: an unknown agent has no sessions and a report for it records none", func(t *testing.T, a store.Agents) { + r := report(host1Cst, locationCst) + if err := a.Observe(ctx, r, now); !errors.Is(err, store.ErrNotEnrolled) { + t.Fatalf("expected ErrNotEnrolled, got %v", err) + } + got, err := a.Sessions(ctx, []string{host1Cst}, now.Add(-time.Hour), now.Add(time.Hour)) + must(t, err) + if len(got) != 0 { + t.Fatalf("expected no sessions, got %d", len(got)) + } + }}, + } +} + // agentEnrollCases covers enrollment, reports and the desired-state round trip. func agentEnrollCases(ctx context.Context, initial *treatment.DesiredState) []agentCase { return []agentCase{ diff --git a/pkg/treatment/internal/fixtures/fixtures.go b/pkg/treatment/internal/fixtures/fixtures.go index bf152f4c9..7521a6ae8 100644 --- a/pkg/treatment/internal/fixtures/fixtures.go +++ b/pkg/treatment/internal/fixtures/fixtures.go @@ -16,6 +16,9 @@ import ( func ptr[T any](v T) *T { return &v } +// CollectionID is a well-formed collection nonce for reports and sessions. +const CollectionID = "0123456789abcdef0123456789abcdef" + // Treatment is CUBIC on a single /32. func Treatment() treatment.Treatment { return treatment.Treatment{ID: "cubic", Revision: 1, Destination: netip.MustParsePrefix("192.0.2.1/32"), Settings: treatment.TCPSettings{CongestionControl: ptr("cubic")}} diff --git a/pkg/treatment/model.go b/pkg/treatment/model.go index 556613cd9..0b8b415fc 100644 --- a/pkg/treatment/model.go +++ b/pkg/treatment/model.go @@ -152,6 +152,8 @@ type Capabilities struct { CongestionAlgorithms []string SimpleHostRouting bool ActuatorEnabled bool + // CollectionID is the agent process's nonce; see ValidCollectionID. + CollectionID string } // Supports reports whether the host offers the congestion algorithm. @@ -194,6 +196,14 @@ func (r *AgentReport) Record(experimentID string) *ApplicationRecord { return nil } +// Session is one (boot, collection nonce) pair an agent reported at +// check-in, with when the controller first and last heard it. Evidence +// counts only records stamped with a session of the agent they name. +type Session struct { + AgentID, BootID, CollectionID string + FirstSeen, LastSeen time.Time +} + // HostEvidence is one host's aggregate for one evidence window. type HostEvidence struct { AgentID string @@ -204,6 +214,9 @@ type HostEvidence struct { RttP95Ms float64 ExposedSockets uint64 CollectionHealthy bool + // UnboundSockets carry this agent ID but no (boot, collection) session + // the agent reported; they are excluded from every other field. + UnboundSockets uint64 } // Evidence is an immutable per-host measurement set for a window. diff --git a/pkg/treatment/reconcile/journal_test.go b/pkg/treatment/reconcile/journal_test.go index 5a473a896..964f4a338 100644 --- a/pkg/treatment/reconcile/journal_test.go +++ b/pkg/treatment/reconcile/journal_test.go @@ -48,6 +48,8 @@ func TestJournalRejectsInvalidSnapshots(t *testing.T) { {"unknown-status", "negative: an apply status from a newer writer is refused", func(j *jpb.Journal) { j.Records[0].Status = 42 }, "", "unknown apply status"}, {"bad-timestamp", "negative: an out-of-range timestamp is a parse error", func(j *jpb.Journal) { j.UpdatedAt = ×tamppb.Timestamp{Seconds: 1 << 60} }, "", "updated_at"}, {"bad-identity", "negative: a path-like agent id in the header is refused", func(j *jpb.Journal) { j.AgentId = "../x" }, "", "invalid journal identity"}, + {"bad-collection", "negative: a collection nonce that is not 32 hex digits is refused before it is stamped", func(j *jpb.Journal) { j.CollectionId = "x'" }, "", "invalid journal identity"}, + {"no-collection", "boundary: a journal written before the agent owned a nonce still opens", func(j *jpb.Journal) { j.CollectionId = "" }, "", ""}, {"legacy-format", "corner: the pre-journal-package LocalState text fails closed", nil, legacySnapshot, "invalid treatment journal"}, {"garbage", "corner: non-prototext bytes fail closed", nil, "\x00\x01 not text", "invalid treatment journal"}, } { diff --git a/pkg/treatment/reconcile/journalpb.go b/pkg/treatment/reconcile/journalpb.go index b56c9ef6e..13f9e9f3c 100644 --- a/pkg/treatment/reconcile/journalpb.go +++ b/pkg/treatment/reconcile/journalpb.go @@ -170,7 +170,7 @@ func recordFromJournal(p *jpb.Record) (Record, error) { } func toJournal(s *State) *jpb.Journal { - out := &jpb.Journal{AgentId: s.AgentID, Location: s.Location, BootId: s.BootID, Desired: desiredToJournal(s.Desired), LocallySuspended: s.LocallySuspended, SuspensionReason: s.SuspensionReason, UpdatedAt: stamp(s.UpdatedAt)} + out := &jpb.Journal{AgentId: s.AgentID, Location: s.Location, BootId: s.BootID, CollectionId: s.CollectionID, Desired: desiredToJournal(s.Desired), LocallySuspended: s.LocallySuspended, SuspensionReason: s.SuspensionReason, UpdatedAt: stamp(s.UpdatedAt)} for i := range s.Records { out.Records = append(out.Records, recordToJournal(&s.Records[i])) } @@ -178,7 +178,7 @@ func toJournal(s *State) *jpb.Journal { } func fromJournal(p *jpb.Journal) (*State, error) { - s := &State{AgentID: p.AgentId, Location: p.Location, BootID: p.BootId, LocallySuspended: p.LocallySuspended, SuspensionReason: p.SuspensionReason} + s := &State{AgentID: p.AgentId, Location: p.Location, BootID: p.BootId, CollectionID: p.CollectionId, LocallySuspended: p.LocallySuspended, SuspensionReason: p.SuspensionReason} var err error if s.Desired, err = desiredFromJournal(p.AgentId, p.Desired); err != nil { return nil, err @@ -198,7 +198,7 @@ func fromJournal(p *jpb.Journal) (*State, error) { // validate is the fail-closed gate every loaded journal passes through. func validate(s *State) error { - if s.AgentID != "" && !treatment.ValidID(s.AgentID) || s.Location != "" && !treatment.ValidID(s.Location) { + if s.AgentID != "" && !treatment.ValidID(s.AgentID) || s.Location != "" && !treatment.ValidID(s.Location) || s.CollectionID != "" && !treatment.ValidCollectionID(s.CollectionID) { return errors.New("invalid journal identity") } if s.Desired != nil { diff --git a/pkg/treatment/reconcile/journalpb_test.go b/pkg/treatment/reconcile/journalpb_test.go index 044898f0f..bd3d2f8b9 100644 --- a/pkg/treatment/reconcile/journalpb_test.go +++ b/pkg/treatment/reconcile/journalpb_test.go @@ -22,7 +22,7 @@ func populatedState(now time.Time) *State { d.Assignments[0].Predecessor = &pred d.Assignments = append(d.Assignments, treatment.Assignment{ExperimentID: "stable-6", Generation: 2, Mode: treatment.ModeStable, Treatment: treatment.Treatment{ID: "v6", Revision: 3, Destination: netip.MustParsePrefix("2001:db8::1/128"), Settings: treatment.TCPSettings{QuickAck: ptr(true)}}}) return &State{ - AgentID: "host-1", Location: "dc1", BootID: "boot-1", Desired: d, LocallySuspended: true, SuspensionReason: "incident 7", UpdatedAt: now, + AgentID: "host-1", Location: "dc1", BootID: "boot-1", CollectionID: fixtures.CollectionID, Desired: d, LocallySuspended: true, SuspensionReason: "incident 7", UpdatedAt: now, Records: []Record{ {ApplicationRecord: treatment.ApplicationRecord{Assignment: d.Assignments[0], DesiredRevision: 1, Status: treatment.ApplyVerified, ReceivedAt: now, AttemptedAt: now.Add(time.Second), AppliedAt: now.Add(2 * time.Second), VerifiedAt: now.Add(3 * time.Second)}, InstalledRoute: []byte{0x18, 0, 0, 0, 1, 2, 3}, InstalledBootID: "boot-1"}, {ApplicationRecord: treatment.ApplicationRecord{Assignment: d.Assignments[1], DesiredRevision: 1, Status: treatment.ApplyPending, ReceivedAt: now}}, diff --git a/pkg/treatment/reconcile/reconcile.go b/pkg/treatment/reconcile/reconcile.go index 33124000a..58d418a60 100644 --- a/pkg/treatment/reconcile/reconcile.go +++ b/pkg/treatment/reconcile/reconcile.go @@ -56,14 +56,17 @@ type Backend interface { // Reconciler drives one Backend from one Journal on behalf of one agent. // AgentID, Location and BootID are written into the journal header on every -// save so the snapshot identifies its writer. +// save so the snapshot identifies its writer. CollectionID is written when +// set; a reconciler without one (the watchdog) keeps the journal's value, so +// only the agent process that owns the nonce can change it. type Reconciler struct { - Journal Journal - Backend Backend - AgentID string - Location string - BootID string - Now func() time.Time + Journal Journal + Backend Backend + AgentID string + Location string + BootID string + CollectionID string + Now func() time.Time } func (r *Reconciler) now() time.Time { @@ -77,9 +80,24 @@ func (r *Reconciler) header(s *State, now time.Time) { s.AgentID = r.AgentID s.Location = r.Location s.BootID = r.BootID + if r.CollectionID != "" { + s.CollectionID = r.CollectionID + } s.UpdatedAt = now } +// claimCollection writes the reconciler's collection nonce into the header +// when the journal holds another (or none), even if nothing else changed: a +// telemetry-only daemon stamps whatever the journal says, and the controller +// binds evidence to the nonce this process reports. +func (r *Reconciler) claimCollection(s *State, now time.Time, save func() error) error { + if r.CollectionID == "" || s.CollectionID == r.CollectionID { + return nil + } + r.header(s, now) + return save() +} + // Run saves intent before touching the kernel and verifies before reporting // success. See Mode for what ModeExpiryOnly may and may not do. The returned // snapshot is a copy of the journal after the pass, even when it failed. @@ -100,6 +118,9 @@ func (r *Reconciler) Run(ctx context.Context, in *treatment.DesiredState, mode M return err } } + if err := r.claimCollection(s, now, save); err != nil { + return err + } if s.Desired == nil { return nil } diff --git a/pkg/treatment/reconcile/reconcile_test.go b/pkg/treatment/reconcile/reconcile_test.go index 5e145f6a5..71caf93f1 100644 --- a/pkg/treatment/reconcile/reconcile_test.go +++ b/pkg/treatment/reconcile/reconcile_test.go @@ -295,3 +295,42 @@ func TestJournalHeaderIdentifiesWriter(t *testing.T) { t.Fatal("foreign reconciler touched the kernel") } } + +// TestJournalCollectionIDOwnership: the agent's reconciler writes its nonce +// into the header; a reconciler without one (the watchdog) keeps whatever +// the journal holds, so only the owning agent process changes it. +func TestJournalCollectionIDOwnership(t *testing.T) { + const other = "fedcba9876543210fedcba9876543210" + for _, tc := range []struct { + name, description string + first, second string // CollectionID of the first and second writer + expected string + }{ + {"agent-writes", "positive: the agent's nonce lands in the header", fixtures.CollectionID, fixtures.CollectionID, fixtures.CollectionID}, + {"watchdog-preserves", "negative: a watchdog pass leaves the agent's nonce alone", fixtures.CollectionID, "", fixtures.CollectionID}, + {"agent-restart", "corner: a restarted agent replaces the nonce", fixtures.CollectionID, other, other}, + {"never-set", "boundary: a journal only the watchdog wrote has no nonce", "", "", ""}, + } { + t.Run(tc.name, func(t *testing.T) { + now := time.Unix(1700000000, 0) + b := &fakeBackend{routes: map[string]bool{}} + r := newReconciler(t, b, &now) + r.CollectionID = tc.first + if _, e := r.Run(context.Background(), fixtures.Desired(now), ModeFull); e != nil { + t.Fatal(e) + } + second := *r + second.CollectionID = tc.second + if _, e := second.Run(context.Background(), nil, ModeExpiryOnly); e != nil { + t.Fatal(e) + } + s, e := ReadSnapshot(r.Journal.Dir) + if e != nil { + t.Fatal(e) + } + if s.CollectionID != tc.expected { + t.Fatalf("%s: header nonce %q, want %q", tc.description, s.CollectionID, tc.expected) + } + }) + } +} diff --git a/pkg/treatment/reconcile/state.go b/pkg/treatment/reconcile/state.go index 12f08a813..ddcad037e 100644 --- a/pkg/treatment/reconcile/state.go +++ b/pkg/treatment/reconcile/state.go @@ -37,7 +37,10 @@ type State struct { AgentID string Location string // BootID is the boot that last wrote the journal. - BootID string + BootID string + // CollectionID is the owning agent process's nonce; see + // treatment.ValidCollectionID. Empty until an agent has written it. + CollectionID string Desired *treatment.DesiredState LocallySuspended bool SuspensionReason string diff --git a/pkg/treatment/validate.go b/pkg/treatment/validate.go index 3179208f8..2f40a6128 100644 --- a/pkg/treatment/validate.go +++ b/pkg/treatment/validate.go @@ -22,6 +22,28 @@ var identifier = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$`) func ValidID(s string) bool { return identifier.MatchString(s) } +// MinimumCohort is the smallest population an experiment may have. +const MinimumCohort = 3 + +// CollectionIDLength is the hex length of a collection nonce (16 bytes). +const CollectionIDLength = 32 + +// ValidCollectionID reports whether s is a collection nonce: 32 lowercase +// hex digits. An agent draws one per process, writes it to its journal and +// reports it at check-in; the daemon stamps it into every record so evidence +// can be bound to sessions the controller heard about over mTLS. +func ValidCollectionID(s string) bool { + if len(s) != CollectionIDLength { + return false + } + for _, c := range s { + if (c < '0' || c > '9') && (c < 'a' || c > 'f') { + return false + } + } + return true +} + // ValidDestination reports whether p is a canonical, globally routable /32 // or /128 host prefix. func ValidDestination(p netip.Prefix) bool { @@ -157,7 +179,7 @@ func ValidateEvidence(e *Evidence) error { } func ValidateProposal(e *Experiment) error { - if e == nil || !ValidID(e.ID) || !ValidID(e.Location) || len(e.Hypothesis) == 0 || len(e.Hypothesis) > 8192 || len(e.CohortOrder) < 3 || len(e.CohortOrder) > MaxAgents { + if e == nil || !ValidID(e.ID) || !ValidID(e.Location) || len(e.Hypothesis) == 0 || len(e.Hypothesis) > 8192 || len(e.CohortOrder) < MinimumCohort || len(e.CohortOrder) > MaxAgents { return errors.New("invalid experiment identity or population (minimum three hosts)") } if err := ValidateTreatment(&e.Treatment); err != nil { @@ -188,7 +210,7 @@ func ValidateProposal(e *Experiment) error { // ValidateReport checks a check-in before it is stored. Kernel bytes and boot // IDs of installed routes are local to the host and must not be reported. func ValidateReport(r *AgentReport) error { - if r == nil || !ValidID(r.AgentID) || !ValidID(r.Location) || !ValidID(r.Capabilities.BootID) || len(r.Capabilities.KernelRelease) > 256 || len(r.Capabilities.CongestionAlgorithms) > 64 || len(r.Records) > MaxAssignments*2 { + if r == nil || !ValidID(r.AgentID) || !ValidID(r.Location) || !ValidID(r.Capabilities.BootID) || !ValidCollectionID(r.Capabilities.CollectionID) || len(r.Capabilities.KernelRelease) > 256 || len(r.Capabilities.CongestionAlgorithms) > 64 || len(r.Records) > MaxAssignments*2 { return errors.New("invalid agent report") } for _, a := range r.Capabilities.CongestionAlgorithms { diff --git a/pkg/treatment/validate_test.go b/pkg/treatment/validate_test.go index 6b1ea70ee..f5d6c6411 100644 --- a/pkg/treatment/validate_test.go +++ b/pkg/treatment/validate_test.go @@ -338,13 +338,16 @@ func TestReportValidation(t *testing.T) { now := time.Unix(1700000000, 0) fresh := func() *treatment.AgentReport { d := fixtures.Desired(now) - return &treatment.AgentReport{AgentID: "host-1", Location: "dc1", Capabilities: treatment.Capabilities{BootID: "boot-1", KernelRelease: "6.12", CongestionAlgorithms: []string{"cubic", "bbr"}, SimpleHostRouting: true, ActuatorEnabled: true}, DesiredRevision: 1, Records: []treatment.ApplicationRecord{{Assignment: d.Assignments[0], DesiredRevision: 1, Status: treatment.ApplyVerified, AppliedAt: now}}} + return &treatment.AgentReport{AgentID: "host-1", Location: "dc1", Capabilities: treatment.Capabilities{BootID: "boot-1", CollectionID: fixtures.CollectionID, KernelRelease: "6.12", CongestionAlgorithms: []string{"cubic", "bbr"}, SimpleHostRouting: true, ActuatorEnabled: true}, DesiredRevision: 1, Records: []treatment.ApplicationRecord{{Assignment: d.Assignments[0], DesiredRevision: 1, Status: treatment.ApplyVerified, AppliedAt: now}}} } fixed := []validationCase[*treatment.AgentReport]{ {"valid", "a verified report is accepted", func(**treatment.AgentReport) {}, ""}, {"nil", "a report is required", func(p **treatment.AgentReport) { *p = nil }, "agent report"}, {"no-records", "a fresh host reports nothing", func(p **treatment.AgentReport) { (*p).Records = nil }, ""}, {"bad-boot-id", "boot IDs are identifiers", func(p **treatment.AgentReport) { (*p).Capabilities.BootID = "" }, "agent report"}, + {"missing-collection-id", "a report must carry the agent's collection nonce", func(p **treatment.AgentReport) { (*p).Capabilities.CollectionID = "" }, "agent report"}, + {"short-collection-id", "a 31-digit nonce is refused", func(p **treatment.AgentReport) { (*p).Capabilities.CollectionID = fixtures.CollectionID[1:] }, "agent report"}, + {"upper-collection-id", "the nonce is lowercase hex, so it can never carry a quote", func(p **treatment.AgentReport) { (*p).Capabilities.CollectionID = strings.ToUpper(fixtures.CollectionID) }, "agent report"}, {"long-kernel", "kernel release is bounded", func(p **treatment.AgentReport) { (*p).Capabilities.KernelRelease = strings.Repeat("k", 257) }, "agent report"}, {"long-algorithm", "algorithm names respect the kernel limit", func(p **treatment.AgentReport) { (*p).Capabilities.CongestionAlgorithms = []string{strings.Repeat("a", 16)} diff --git a/pkg/treatment/wire/rules_test.go b/pkg/treatment/wire/rules_test.go index 8fc5eb81e..93e7f011f 100644 --- a/pkg/treatment/wire/rules_test.go +++ b/pkg/treatment/wire/rules_test.go @@ -50,11 +50,13 @@ func TestProtoRulesMirrorValidators(t *testing.T) { {"positive: the desired-state fixture is valid", desired(func(*pb.DesiredState) {}), ""}, {"positive: the experiment fixture is valid", experiment(func(*pb.Experiment) {}), ""}, {"positive: a propose request wrapping the fixture is valid", &pb.ProposeRequest{Experiment: experiment(func(*pb.Experiment) {}).(*pb.Experiment), RequestId: "req-1"}, ""}, - {"positive: a minimal check-in is valid", &pb.CheckInRequest{AgentId: "host-1", Location: "dc1", Capabilities: &pb.Capabilities{BootId: "boot"}}, ""}, + {"positive: a minimal check-in is valid", &pb.CheckInRequest{AgentId: "host-1", Location: "dc1", Capabilities: &pb.Capabilities{BootId: "boot", CollectionId: fixtures.CollectionID}}, ""}, {"positive: a control request with exactly one selector is valid", control(func(*pb.ControlRequest) {}), ""}, {"positive: fleet control with no other selector is valid", control(func(c *pb.ControlRequest) { c.ExperimentId = ""; c.Fleet = true }), ""}, {"negative: identifier with a leading dash", desired(func(d *pb.DesiredState) { d.AgentId = "-host" }), "string.pattern"}, + {"negative: a check-in without a collection nonce", &pb.CheckInRequest{AgentId: "host-1", Location: "dc1", Capabilities: &pb.Capabilities{BootId: "boot"}}, "string.pattern"}, + {"negative: an uppercase collection nonce", &pb.CheckInRequest{AgentId: "host-1", Location: "dc1", Capabilities: &pb.Capabilities{BootId: "boot", CollectionId: strings.ToUpper(fixtures.CollectionID)}}, "string.pattern"}, {"negative: revision zero", desired(func(d *pb.DesiredState) { d.Revision = 0 }), "uint64.gt"}, {"negative: missing issued_at", desired(func(d *pb.DesiredState) { d.IssuedAt = nil }), "required"}, {"negative: empty TCP settings", desired(func(d *pb.DesiredState) { d.Assignments[0].Treatment.Settings = &pb.TCPSettings{} }), "tcp_settings.non_empty"}, diff --git a/pkg/treatment/wire/wire.go b/pkg/treatment/wire/wire.go index 41b8fa19e..afb820313 100644 --- a/pkg/treatment/wire/wire.go +++ b/pkg/treatment/wire/wire.go @@ -199,14 +199,14 @@ func ParseDesired(p *pb.DesiredState) (*treatment.DesiredState, error) { // Agent reports. func Capabilities(c *treatment.Capabilities) *pb.Capabilities { - return &pb.Capabilities{BootId: c.BootID, KernelRelease: c.KernelRelease, CongestionAlgorithms: append([]string(nil), c.CongestionAlgorithms...), SimpleHostRouting: c.SimpleHostRouting, ActuatorEnabled: c.ActuatorEnabled} + return &pb.Capabilities{BootId: c.BootID, KernelRelease: c.KernelRelease, CongestionAlgorithms: append([]string(nil), c.CongestionAlgorithms...), SimpleHostRouting: c.SimpleHostRouting, ActuatorEnabled: c.ActuatorEnabled, CollectionId: c.CollectionID} } func ParseCapabilities(p *pb.Capabilities) treatment.Capabilities { if p == nil { return treatment.Capabilities{} } - return treatment.Capabilities{BootID: p.BootId, KernelRelease: p.KernelRelease, CongestionAlgorithms: append([]string(nil), p.CongestionAlgorithms...), SimpleHostRouting: p.SimpleHostRouting, ActuatorEnabled: p.ActuatorEnabled} + return treatment.Capabilities{BootID: p.BootId, KernelRelease: p.KernelRelease, CongestionAlgorithms: append([]string(nil), p.CongestionAlgorithms...), SimpleHostRouting: p.SimpleHostRouting, ActuatorEnabled: p.ActuatorEnabled, CollectionID: p.CollectionId} } func Record(r *treatment.ApplicationRecord) *pb.ApplicationRecord { @@ -272,14 +272,14 @@ func ParseReport(p *pb.CheckInRequest) (*treatment.AgentReport, error) { // Evidence. func Host(h *treatment.HostEvidence) *pb.HostEvidence { - return &pb.HostEvidence{AgentId: h.AgentID, Sockets: h.Sockets, BytesAcked: h.BytesAcked, GoodputBps: h.GoodputBps, RetransmissionRatio: h.RetransmissionRatio, RttP95Ms: h.RttP95Ms, ExposedSockets: h.ExposedSockets, CollectionHealthy: h.CollectionHealthy} + return &pb.HostEvidence{AgentId: h.AgentID, Sockets: h.Sockets, BytesAcked: h.BytesAcked, GoodputBps: h.GoodputBps, RetransmissionRatio: h.RetransmissionRatio, RttP95Ms: h.RttP95Ms, ExposedSockets: h.ExposedSockets, CollectionHealthy: h.CollectionHealthy, UnboundSockets: h.UnboundSockets} } func ParseHost(p *pb.HostEvidence) treatment.HostEvidence { if p == nil { return treatment.HostEvidence{} } - return treatment.HostEvidence{AgentID: p.AgentId, Sockets: p.Sockets, BytesAcked: p.BytesAcked, GoodputBps: p.GoodputBps, RetransmissionRatio: p.RetransmissionRatio, RttP95Ms: p.RttP95Ms, ExposedSockets: p.ExposedSockets, CollectionHealthy: p.CollectionHealthy} + return treatment.HostEvidence{AgentID: p.AgentId, Sockets: p.Sockets, BytesAcked: p.BytesAcked, GoodputBps: p.GoodputBps, RetransmissionRatio: p.RetransmissionRatio, RttP95Ms: p.RttP95Ms, ExposedSockets: p.ExposedSockets, CollectionHealthy: p.CollectionHealthy, UnboundSockets: p.UnboundSockets} } func Evidence(e *treatment.Evidence) *pb.Evidence { diff --git a/pkg/treatment/wire/wire_test.go b/pkg/treatment/wire/wire_test.go index a7ee9c27a..b4f109703 100644 --- a/pkg/treatment/wire/wire_test.go +++ b/pkg/treatment/wire/wire_test.go @@ -155,7 +155,7 @@ func populatedReport(now time.Time) *treatment.AgentReport { pred := d.Assignments[0].Treatment pred.ID = "old" d.Assignments[0].Predecessor = &pred - return &treatment.AgentReport{AgentID: "host-1", Location: "dc1", Capabilities: treatment.Capabilities{BootID: "b", KernelRelease: "6.12", CongestionAlgorithms: []string{"cubic", "bbr"}, SimpleHostRouting: true, ActuatorEnabled: true}, DesiredRevision: 7, LocallySuspended: true, Records: []treatment.ApplicationRecord{{Assignment: d.Assignments[0], DesiredRevision: 7, Status: treatment.ApplyVerified, ReceivedAt: now, AttemptedAt: now.Add(time.Second), AppliedAt: now.Add(2 * time.Second), VerifiedAt: now.Add(3 * time.Second), ExpiredLatched: false}, {Assignment: treatment.Assignment{ExperimentID: "gone", Treatment: fixtures.Treatment(), Mode: treatment.ModeRetired, Generation: 3}, Status: treatment.ApplyRemoved, RemovedAt: now, Error: "x", ExpiredLatched: true}}} + return &treatment.AgentReport{AgentID: "host-1", Location: "dc1", Capabilities: treatment.Capabilities{BootID: "b", CollectionID: fixtures.CollectionID, KernelRelease: "6.12", CongestionAlgorithms: []string{"cubic", "bbr"}, SimpleHostRouting: true, ActuatorEnabled: true}, DesiredRevision: 7, LocallySuspended: true, Records: []treatment.ApplicationRecord{{Assignment: d.Assignments[0], DesiredRevision: 7, Status: treatment.ApplyVerified, ReceivedAt: now, AttemptedAt: now.Add(time.Second), AppliedAt: now.Add(2 * time.Second), VerifiedAt: now.Add(3 * time.Second), ExpiredLatched: false}, {Assignment: treatment.Assignment{ExperimentID: "gone", Treatment: fixtures.Treatment(), Mode: treatment.ModeRetired, Generation: 3}, Status: treatment.ApplyRemoved, RemovedAt: now, Error: "x", ExpiredLatched: true}}} } // TestRoundTrip converts domain → wire → domain and requires deep equality, diff --git a/proto/xtcp_treatment/v1/treatment.proto b/proto/xtcp_treatment/v1/treatment.proto index 6bba8e2a2..93456f4a8 100644 --- a/proto/xtcp_treatment/v1/treatment.proto +++ b/proto/xtcp_treatment/v1/treatment.proto @@ -197,6 +197,10 @@ message Capabilities { }]; bool simple_host_routing = 4; bool actuator_enabled = 5; + // Per-process nonce the agent writes into its journal header and the + // daemon stamps into every record as collection_id. Evidence counts only + // records whose (agent_id, host_boot_id, collection_id) an agent reported. + string collection_id = 6 [(buf.validate.field).string.pattern = "^[0-9a-f]{32}$"]; } // ApplicationRecord is the agent's account of one assignment. Agents report @@ -241,6 +245,10 @@ message HostEvidence { double rtt_p95_ms = 6 [(buf.validate.field).double = {gte: 0, finite: true}]; uint64 exposed_sockets = 7; bool collection_healthy = 8; + // Sockets stamped with this agent_id whose boot and collection nonce + // match no session the agent reported. They are excluded from every other + // field and counted here so a reviewer can see them. + uint64 unbound_sockets = 9; } message Evidence { diff --git a/proto/xtcp_treatment_journal/v1/journal.proto b/proto/xtcp_treatment_journal/v1/journal.proto index 8586042cf..9b24349e1 100644 --- a/proto/xtcp_treatment_journal/v1/journal.proto +++ b/proto/xtcp_treatment_journal/v1/journal.proto @@ -115,4 +115,8 @@ message Journal { string suspension_reason = 6; repeated Record records = 7; google.protobuf.Timestamp updated_at = 8; + // Per-process nonce of the agent that owns the journal. The agent writes + // it; the watchdog preserves it; a telemetry-only daemon stamps it into + // records as collection_id. + string collection_id = 9; } diff --git a/tools/tcp-controller-integration/main.go b/tools/tcp-controller-integration/main.go index 65e1369d4..752dc228d 100644 --- a/tools/tcp-controller-integration/main.go +++ b/tools/tcp-controller-integration/main.go @@ -20,8 +20,10 @@ import ( "time" pb "github.com/randomizedcoder/xtcp2/gen/go/xtcp_treatment" + "github.com/randomizedcoder/xtcp2/pkg/treatment" evport "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/evidence" "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/evidence/clickhouse" + "github.com/randomizedcoder/xtcp2/pkg/treatment/controller/store/postgres" "github.com/randomizedcoder/xtcp2/pkg/treatment/identity" "github.com/randomizedcoder/xtcp2/pkg/treatment/reconcile" "github.com/randomizedcoder/xtcp2/pkg/treatment/wire" @@ -32,6 +34,10 @@ import ( const pkiDir = "/var/lib/controller-test/pki" +// controllerDSN is the controller's database on this guest; the driver reads +// the agents' sessions from it to bind its own baseline query. +const controllerDSN = "host=/run/postgresql dbname=xtcp user=root sslmode=disable" + // agentUnit is the systemd unit running the treatment agent when xtcp2 does // not embed it (-agentUnit=xtcp2-treatment-agent). Empty means the embedded // arrangement, where the agent lives and dies with the xtcp2 unit. @@ -309,7 +315,8 @@ func propose(op pb.OperatorServiceClient, id string) *pb.Experiment { end := time.Now().Add(-20 * time.Second) ctx, cancel := rpcContext() defer cancel() - baseline = wire.Evidence(must(ch.Evidence(ctx, evport.Window{Location: "test-dc", Destination: netip.MustParsePrefix("198.18.0.1/32"), ExperimentID: id, Agents: hosts, Start: end.Add(-60 * time.Second), End: end}))) + start := end.Add(-60 * time.Second) + baseline = wire.Evidence(must(ch.Evidence(ctx, evport.Window{Location: "test-dc", Destination: netip.MustParsePrefix("198.18.0.1/32"), ExperimentID: id, Agents: hosts, Sessions: sessions(ctx, start, end), Start: start, End: end}))) require(len(baseline.Hosts) == 6, "baseline needs six hosts: %v", baseline.Hosts) for _, h := range baseline.Hosts { require(h.CollectionHealthy && h.Sockets >= 8 && h.BytesAcked >= 4096, "baseline not ready: %v", h) @@ -326,6 +333,43 @@ func propose(op pb.OperatorServiceClient, id string) *pb.Experiment { require(len(e.Phases) == 0 && routeCount() == 0, "unapproved proposal applied") return review(op, e, id+"-initial", nil) } + +// sessions reads the sessions the agents reported over a window, widened +// the way the controller widens it, from the controller's own store. +func sessions(ctx context.Context, from, to time.Time) []treatment.Session { + db := must(postgres.Open(ctx, controllerDSN)) + defer func() { + if cerr := db.Close(); cerr != nil { + log.Printf("controller database close: %v", cerr) + } + }() + return must(db.Repos().Agents.Sessions(ctx, hosts, from.Add(-15*time.Minute), to.Add(15*time.Minute))) +} + +// binding copies a cohort host's recent records under a boot and nonce that +// host never reported, as a host spoofing its agent_id would, and checks the +// controller counts them as unbound instead of as that host's evidence. +func binding(op pb.OperatorServiceClient, e *pb.Experiment) { + spoofed := e.CohortOrder[len(e.CohortOrder)-1] + sql(fmt.Sprintf(`INSERT INTO xtcp.xtcp_flat_records_v2 SELECT * REPLACE ('spoofed-boot' AS host_boot_id, 'ffffffffffffffffffffffffffffffff' AS collection_id, + inet_diag_msg_socket_cookie + 1000000000 AS inet_diag_msg_socket_cookie) + FROM xtcp.xtcp_flat_records_v2 WHERE agent_id='%s' AND timestamp_ns > now() - INTERVAL 2 MINUTE`, spoofed)) + eventually("spoofed records counted as unbound, not as evidence", 2*time.Minute, func() bool { + e = inspect(op, e.Id) + ctx, cancel := rpcContext() + defer cancel() + a := must(op.Analyze(ctx, &pb.AnalyzeRequest{ExperimentId: e.Id, ExpectedRevision: e.Revision})) + for _, h := range a.Evidence.Hosts { + if h.AgentId == spoofed { + require(h.UnboundSockets > 0, "spoofed records were not counted as unbound: %v", h) + write(evidenceDir+"/binding.json", must(json.MarshalIndent(a.Evidence, "", " "))) + return true + } + } + panic(fmt.Sprintf("no evidence row for %s", spoofed)) + }) +} + func control(op pb.OperatorServiceClient, id string, a pb.ControlAction) { ctx, cancel := rpcContext() defer cancel() @@ -437,6 +481,10 @@ func runController(soak bool) { assertNearbyRoute(canary, "198.18.0.1") act(canary, "restart-load") pass("HOLD") + // The safety analysis below then runs with the spoofed rows present, so + // its outcome shows they did not count. + binding(op, e) + pass("BINDING") var analysis *pb.AnalyzeResponse eventually("measured first-canary safety", 4*time.Minute, func() bool { e = inspect(op, e.Id) From 12cc5f6c08a8652c91fec1125200a04c280411e6 Mon Sep 17 00:00:00 2001 From: "randomizedcoder dave.seddon.ca@gmail.com" Date: Mon, 26 Oct 2026 07:47:22 -0700 Subject: [PATCH 25/27] fix(treatment): cap the sessions one agent adds to an evidence window An enrolled agent rotating its collection nonce on every check-in could grow its sessions until the window exceeded evidence.MaxSessions, failing evidence for the whole cohort. Agents.Sessions now returns at most the eight most recently seen sessions per agent, so such a host only loses binding for its own older records. Also records the F5 commit in the status table. Co-Authored-By: Claude Opus 5.5 --- docs/status-tcp-controller.md | 9 +++++++-- pkg/treatment/controller/evidence/evidence.go | 3 ++- pkg/treatment/controller/store/memory/memory.go | 8 +++++++- pkg/treatment/controller/store/postgres/agents.go | 6 ++++-- pkg/treatment/controller/store/store.go | 9 ++++++++- pkg/treatment/controller/store/storetest/storetest.go | 11 +++++++++++ 6 files changed, 39 insertions(+), 7 deletions(-) diff --git a/docs/status-tcp-controller.md b/docs/status-tcp-controller.md index 9487f882f..fd1c7d1d4 100644 --- a/docs/status-tcp-controller.md +++ b/docs/status-tcp-controller.md @@ -26,7 +26,7 @@ review being addressed. Items are listed in execution order. | F2 | follow-up: help smoke for the four treatment binaries and `zstd-probe` | done | 926c2c74 | | F3 | follow-up: reproducible index timings (`bench-tcp-controller-postgres-indexes`) | done | 30a21cdd | | F4 | follow-up: generated coverage table (`coverage-table.md` from the unit target) | done | 30a21cdd | -| F5 | follow-up: evidence bound to `(agent_id, boot_id, collection_id)` sessions | done | this commit | +| F5 | follow-up: evidence bound to `(agent_id, boot_id, collection_id)` sessions | done | 24f036c8 | Dual-mode agent (library + `cmd/xtcp2-treatment-agent` + `treatment` build tag in `cmd/xtcp2`, with an `attribution` journal reader for the untagged daemon) @@ -265,7 +265,12 @@ process. `Capabilities.collection_id`, which is now required. `Agents.Observe` upserts the session and refreshes `last_seen` at most every five minutes; `Agents.Sessions` returns sessions overlapping a window and housekeeping - prunes those older than eight days. Every evidence window (analyze, + prunes those older than eight days. `Sessions` returns at most + `MaxSessionsPerAgent` (8) per agent, the most recently seen, so an enrolled + host that rotates its nonce on every check-in unbinds only its own older + records instead of pushing the window past `evidence.MaxSessions` and + failing evidence for its whole cohort (raised by the commit security + review, fixed in the follow-up commit). Every evidence window (analyze, maintenance, discovery baseline, and the VM driver's own baseline) carries the population's sessions widened by ten minutes; `analysis.sql` (`tcp-host-deltas-v2-bound`) measures only bound sockets and returns diff --git a/pkg/treatment/controller/evidence/evidence.go b/pkg/treatment/controller/evidence/evidence.go index ba7a7e7bb..ebb219e3f 100644 --- a/pkg/treatment/controller/evidence/evidence.go +++ b/pkg/treatment/controller/evidence/evidence.go @@ -30,7 +30,8 @@ type Window struct { } // MaxSessions bounds Window.Sessions: several restarts per host over the -// longest window. +// longest window. The store caps each agent at eight, so a full population +// stays within it however often any one host rotates its nonce. const MaxSessions = 8 * treatment.MaxAgents // Validate applies the bounds every adapter must enforce before querying. diff --git a/pkg/treatment/controller/store/memory/memory.go b/pkg/treatment/controller/store/memory/memory.go index 481de83e7..0e3729fde 100644 --- a/pkg/treatment/controller/store/memory/memory.go +++ b/pkg/treatment/controller/store/memory/memory.go @@ -236,11 +236,17 @@ func (r agentsRepo) Sessions(_ context.Context, ids []string, from, to time.Time if !ok { continue } + var overlap []treatment.Session for _, s := range a.sessions { if !s.LastSeen.Before(from) && !s.FirstSeen.After(to) { - out = append(out, s) + overlap = append(overlap, s) } } + // Keep the most recently seen, as the PostgreSQL window does. + slices.SortFunc(overlap, func(x, y treatment.Session) int { + return cmp.Or(y.LastSeen.Compare(x.LastSeen), y.FirstSeen.Compare(x.FirstSeen), cmp.Compare(x.BootID, y.BootID), cmp.Compare(x.CollectionID, y.CollectionID)) + }) + out = append(out, overlap[:min(len(overlap), store.MaxSessionsPerAgent)]...) } slices.SortFunc(out, func(x, y treatment.Session) int { return cmp.Or(cmp.Compare(x.AgentID, y.AgentID), x.FirstSeen.Compare(y.FirstSeen), cmp.Compare(x.BootID, y.BootID), cmp.Compare(x.CollectionID, y.CollectionID)) diff --git a/pkg/treatment/controller/store/postgres/agents.go b/pkg/treatment/controller/store/postgres/agents.go index db5d774db..71070c0a0 100644 --- a/pkg/treatment/controller/store/postgres/agents.go +++ b/pkg/treatment/controller/store/postgres/agents.go @@ -254,8 +254,10 @@ func (a *agents) Sessions(ctx context.Context, ids []string, from, to time.Time) if len(ids) == 0 { return nil, nil } - rows, err := a.q().QueryContext(ctx, `SELECT agent_id,boot_id,collection_id,first_seen,last_seen FROM xtcp_treatment_agent_sessions - WHERE agent_id = ANY($1) AND last_seen >= $2 AND first_seen <= $3 ORDER BY agent_id,first_seen,boot_id,collection_id`, ids, from, to) + rows, err := a.q().QueryContext(ctx, `SELECT agent_id,boot_id,collection_id,first_seen,last_seen FROM ( + SELECT *, row_number() OVER (PARTITION BY agent_id ORDER BY last_seen DESC, first_seen DESC, boot_id, collection_id) AS recency + FROM xtcp_treatment_agent_sessions WHERE agent_id = ANY($1) AND last_seen >= $2 AND first_seen <= $3) AS s + WHERE recency <= $4 ORDER BY agent_id,first_seen,boot_id,collection_id`, ids, from, to, store.MaxSessionsPerAgent) if err != nil { return nil, err } diff --git a/pkg/treatment/controller/store/store.go b/pkg/treatment/controller/store/store.go index 3a7f5a4d8..380afbb02 100644 --- a/pkg/treatment/controller/store/store.go +++ b/pkg/treatment/controller/store/store.go @@ -79,7 +79,8 @@ type Agents interface { DesiredSince(ctx context.Context, since time.Time) ([]DesiredRevision, error) // Sessions returns the (boot, collection) sessions of the named agents // that overlap [from, to]: first seen no later than to and last seen no - // earlier than from. Observe records sessions and refreshes a known + // earlier than from, at most MaxSessionsPerAgent per agent (the most + // recently seen). Observe records sessions and refreshes a known // session's LastSeen at most every SessionRefresh, so callers widen from // by at least that much. Sessions(ctx context.Context, ids []string, from, to time.Time) ([]treatment.Session, error) @@ -92,6 +93,12 @@ type Agents interface { // check-in rewrites it. const SessionRefresh = 5 * time.Minute +// MaxSessionsPerAgent caps the sessions one agent contributes to a window. +// An agent that rotates its nonce on every check-in then loses binding for +// its own older records instead of growing the window past +// evidence.MaxSessions and failing evidence for its whole cohort. +const MaxSessionsPerAgent = 8 + // DesiredRevision is one agent's desired revision and the store's stamp of // when it last changed. type DesiredRevision struct { diff --git a/pkg/treatment/controller/store/storetest/storetest.go b/pkg/treatment/controller/store/storetest/storetest.go index cdae251a3..080cadc54 100644 --- a/pkg/treatment/controller/store/storetest/storetest.go +++ b/pkg/treatment/controller/store/storetest/storetest.go @@ -188,6 +188,17 @@ func agentSessionCases(ctx context.Context, initial *treatment.DesiredState) []a } sessions(t, a, now.Add(-time.Hour), now.Add(2*time.Hour), "host-1/boot-1/2@3600..3600") }}, + {"session-cap", "boundary: an agent rotating its nonce contributes only its most recent sessions", func(t *testing.T, a store.Agents) { + must(t, a.Enroll(ctx, host1Cst, locationCst, initial)) + for i := range store.MaxSessionsPerAgent + 3 { + observeSession(ctx, t, a, "boot-1", fmt.Sprintf("%032x", i+1), now.Add(time.Duration(i)*time.Minute)) + } + got, err := a.Sessions(ctx, []string{host1Cst}, now.Add(-time.Hour), now.Add(time.Hour)) + must(t, err) + if len(got) != store.MaxSessionsPerAgent || got[0].FirstSeen.Before(now.Add(3*time.Minute)) { + t.Fatalf("expected the %d most recent sessions, got %v", store.MaxSessionsPerAgent, sessionKeys(got)) + } + }}, {"sessions-unenrolled", "negative: an unknown agent has no sessions and a report for it records none", func(t *testing.T, a store.Agents) { r := report(host1Cst, locationCst) if err := a.Observe(ctx, r, now); !errors.Is(err, store.ErrNotEnrolled) { From 87261db8cad2472d447c7af9ce8d56afe748259d Mon Sep 17 00:00:00 2001 From: "randomizedcoder dave.seddon.ca@gmail.com" Date: Mon, 26 Oct 2026 07:51:17 -0700 Subject: [PATCH 26/27] lint(treatment): discovery's per-candidate step in its own method Binding evidence pushed Discover one over the gocyclo limit; also gofmt on validate_test.go. Co-Authored-By: Claude Opus 5.5 --- pkg/treatment/controller/discovery.go | 59 +++++++++++++++------------ pkg/treatment/validate_test.go | 4 +- 2 files changed, 37 insertions(+), 26 deletions(-) diff --git a/pkg/treatment/controller/discovery.go b/pkg/treatment/controller/discovery.go index 6eb776573..93e2543a6 100644 --- a/pkg/treatment/controller/discovery.go +++ b/pkg/treatment/controller/discovery.go @@ -138,34 +138,11 @@ func (s *Server) Discover(ctx context.Context, w evidence.Warehouse, templates [ if !t.inScope(c, prefixes) { continue } - // Completed or held treatments and recent rejected trials suppress - // repeat proposals. Cooldown is at least a day after terminal state. - exists, e := s.Store.Repos().Experiments.DestinationBlocked(ctx, t.Location, c.Destination, s.now().Add(-24*time.Hour)) + stop, e := s.discoverCandidate(ctx, w, t, c, enrolled, start, end) if e != nil { failures = append(failures, e) - continue - } - if exists { - continue - } - seed := sha256.Sum256([]byte(t.ID + "/" + c.Destination.String() + "/" + s.now().UTC().Format("2006-01-02"))) - id := "hyp-" + hex.EncodeToString(seed[:12]) - // Only enrolled agents report sessions, so only they can carry - // bound evidence or receive an assignment. - cohort := enrolledIn(c.Agents, enrolled) - if len(cohort) < treatment.MinimumCohort { - continue - } - baseline, e := s.boundEvidence(ctx, w, evidence.Window{Location: t.Location, Destination: c.Destination, ExperimentID: id, Agents: cohort, Start: start, End: end}) - if e != nil { - failures = append(failures, e) - continue } - n := &treatment.Experiment{ID: id, Location: t.Location, Hypothesis: fmt.Sprintf("Template %s: test %s for %s (ASN %d), observed RTT %.3f ms and retransmission ratio %.6f; this is a hypothesis, not an efficacy claim", t.ID, t.CongestionControl, c.Destination, c.ASN, c.RTTMs, c.RetransmissionRatio), Treatment: treatment.Treatment{ID: t.ID, Revision: 1, Destination: c.Destination, Settings: t.settings()}, Policy: t.policy(), Baseline: baseline, CohortOrder: cohort} - if _, e = s.propose(ctx, &pb.ProposeRequest{Experiment: wire.Experiment(n), RequestId: id}, "candidate-discovery", true); e != nil { - if !errors.Is(e, errExperimentBudget) && !errors.Is(e, errDiscoverySuppressed) { - failures = append(failures, e) - } + if stop { break } } @@ -173,6 +150,38 @@ func (s *Server) Discover(ctx context.Context, w evidence.Warehouse, templates [ return errors.Join(failures...) } +// discoverCandidate measures one in-scope candidate's enrolled hosts and +// proposes it. stop reports that the location took (or refused) a proposal, +// so the template's remaining candidates wait for the next pass. +func (s *Server) discoverCandidate(ctx context.Context, w evidence.Warehouse, t *DiscoveryTemplate, c evidence.Candidate, enrolled map[string]bool, start, end time.Time) (stop bool, err error) { + // Completed or held treatments and recent rejected trials suppress + // repeat proposals. Cooldown is at least a day after terminal state. + exists, err := s.Store.Repos().Experiments.DestinationBlocked(ctx, t.Location, c.Destination, s.now().Add(-24*time.Hour)) + if err != nil || exists { + return false, err + } + // Only enrolled agents report sessions, so only they can carry bound + // evidence or receive an assignment. + cohort := enrolledIn(c.Agents, enrolled) + if len(cohort) < treatment.MinimumCohort { + return false, nil + } + seed := sha256.Sum256([]byte(t.ID + "/" + c.Destination.String() + "/" + s.now().UTC().Format("2006-01-02"))) + id := "hyp-" + hex.EncodeToString(seed[:12]) + baseline, err := s.boundEvidence(ctx, w, evidence.Window{Location: t.Location, Destination: c.Destination, ExperimentID: id, Agents: cohort, Start: start, End: end}) + if err != nil { + return false, err + } + n := &treatment.Experiment{ID: id, Location: t.Location, Hypothesis: fmt.Sprintf("Template %s: test %s for %s (ASN %d), observed RTT %.3f ms and retransmission ratio %.6f; this is a hypothesis, not an efficacy claim", t.ID, t.CongestionControl, c.Destination, c.ASN, c.RTTMs, c.RetransmissionRatio), Treatment: treatment.Treatment{ID: t.ID, Revision: 1, Destination: c.Destination, Settings: t.settings()}, Policy: t.policy(), Baseline: baseline, CohortOrder: cohort} + if _, err = s.propose(ctx, &pb.ProposeRequest{Experiment: wire.Experiment(n), RequestId: id}, "candidate-discovery", true); err != nil { + if errors.Is(err, errExperimentBudget) || errors.Is(err, errDiscoverySuppressed) { + err = nil + } + return true, err + } + return false, nil +} + // enrolled returns the IDs of the agents enrolled in location. func (s *Server) enrolled(ctx context.Context, location string) (map[string]bool, error) { refs, err := s.Store.Repos().Agents.List(ctx, location) diff --git a/pkg/treatment/validate_test.go b/pkg/treatment/validate_test.go index f5d6c6411..b41a0088e 100644 --- a/pkg/treatment/validate_test.go +++ b/pkg/treatment/validate_test.go @@ -347,7 +347,9 @@ func TestReportValidation(t *testing.T) { {"bad-boot-id", "boot IDs are identifiers", func(p **treatment.AgentReport) { (*p).Capabilities.BootID = "" }, "agent report"}, {"missing-collection-id", "a report must carry the agent's collection nonce", func(p **treatment.AgentReport) { (*p).Capabilities.CollectionID = "" }, "agent report"}, {"short-collection-id", "a 31-digit nonce is refused", func(p **treatment.AgentReport) { (*p).Capabilities.CollectionID = fixtures.CollectionID[1:] }, "agent report"}, - {"upper-collection-id", "the nonce is lowercase hex, so it can never carry a quote", func(p **treatment.AgentReport) { (*p).Capabilities.CollectionID = strings.ToUpper(fixtures.CollectionID) }, "agent report"}, + {"upper-collection-id", "the nonce is lowercase hex, so it can never carry a quote", func(p **treatment.AgentReport) { + (*p).Capabilities.CollectionID = strings.ToUpper(fixtures.CollectionID) + }, "agent report"}, {"long-kernel", "kernel release is bounded", func(p **treatment.AgentReport) { (*p).Capabilities.KernelRelease = strings.Repeat("k", 257) }, "agent report"}, {"long-algorithm", "algorithm names respect the kernel limit", func(p **treatment.AgentReport) { (*p).Capabilities.CongestionAlgorithms = []string{strings.Repeat("a", 16)} From 9f044dc1cd7715565602de6f4c552be76657e12c Mon Sep 17 00:00:00 2001 From: "randomizedcoder dave.seddon.ca@gmail.com" Date: Mon, 26 Oct 2026 08:01:25 -0700 Subject: [PATCH 27/27] docs(treatment): record the follow-up commits, coverage and VM binding run Co-Authored-By: Claude Opus 5.5 --- docs/status-tcp-controller.md | 8 +++++--- docs/tcp-controller-testing.md | 24 ++++++++++++------------ 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/docs/status-tcp-controller.md b/docs/status-tcp-controller.md index fd1c7d1d4..c500e6043 100644 --- a/docs/status-tcp-controller.md +++ b/docs/status-tcp-controller.md @@ -26,7 +26,7 @@ review being addressed. Items are listed in execution order. | F2 | follow-up: help smoke for the four treatment binaries and `zstd-probe` | done | 926c2c74 | | F3 | follow-up: reproducible index timings (`bench-tcp-controller-postgres-indexes`) | done | 30a21cdd | | F4 | follow-up: generated coverage table (`coverage-table.md` from the unit target) | done | 30a21cdd | -| F5 | follow-up: evidence bound to `(agent_id, boot_id, collection_id)` sessions | done | 24f036c8 | +| F5 | follow-up: evidence bound to `(agent_id, boot_id, collection_id)` sessions | done | c2bf7d2d, cap 12cc5f6c, lint 87261db8 | Dual-mode agent (library + `cmd/xtcp2-treatment-agent` + `treatment` build tag in `cmd/xtcp2`, with an `attribution` journal reader for the untagged daemon) @@ -257,7 +257,7 @@ process. - F3: the index timings come from the Nix PostgreSQL 18.6 fixture: median 22.27 ms without indexes, 0.023 ms with them, for an old destination's cooldown lookup. The fixture setup is shared with the unit and race targets. -- F4: per-package coverage, no `-coverpkg`, so `pkg/treatment` reads 54.7%: +- F4: per-package coverage, no `-coverpkg`, so `pkg/treatment` reads 56.0%: its enum, `Clone` and `Equal` methods are exercised from other packages. - F5: the agent owns a per-process nonce, writes it into the journal header on its first pass after starting (`Reconciler.CollectionID`; a reconciler @@ -280,7 +280,9 @@ process. keeps its digest. Schema additions are `ADD COLUMN IF NOT EXISTS`. The VM test gains `TCP_CONTROLLER_BINDING_PASS`: the driver copies a cohort host's recent records under a fake boot and nonce and requires `Analyze` to count - them as unbound; the safety analysis that follows runs with them present. + them as unbound; the safety analysis that follows runs with them present. The run at 87261db8 passed all eight sentinels; in the + binding evidence the spoofed host kept its 32 bound sockets and reported 32 + unbound, and the other five hosts reported none. - Proto regeneration: `regen-protos` now emits protoc-gen-go v1.36.12 and a newer protoc for C++, while the committed output for the other protos is older, so only the two treatment protos' generated files were committed and diff --git a/docs/tcp-controller-testing.md b/docs/tcp-controller-testing.md index c6613ea7a..b3589dd86 100644 --- a/docs/tcp-controller-testing.md +++ b/docs/tcp-controller-testing.md @@ -63,27 +63,27 @@ target (`test-tcp-controller-race`) runs the same packages three times under | Package | Statements covered | | --- | ---: | -| `pkg/treatment` | 54.7% | -| `pkg/treatment/agent` | 61.9% | +| `pkg/treatment` | 56.0% | +| `pkg/treatment/agent` | 62.2% | | `pkg/treatment/attribution` | 88.1% | -| `pkg/treatment/controller` | 74.0% | +| `pkg/treatment/controller` | 74.4% | | `pkg/treatment/controller/evidence` | 100.0% | -| `pkg/treatment/controller/evidence/clickhouse` | 93.5% | -| `pkg/treatment/controller/store/memory` | 95.3% | -| `pkg/treatment/controller/store/postgres` | 85.5% | +| `pkg/treatment/controller/evidence/clickhouse` | 93.9% | +| `pkg/treatment/controller/store/memory` | 95.9% | +| `pkg/treatment/controller/store/postgres` | 85.0% | | `pkg/treatment/experiment` | 93.8% | | `pkg/treatment/identity` | 90.5% | | `pkg/treatment/local` | 70.5% | -| `pkg/treatment/reconcile` | 87.0% | +| `pkg/treatment/reconcile` | 87.2% | | `pkg/treatment/wire` | 86.2% | -| `tools/tcp-controller-integration` | 4.1% | +| `tools/tcp-controller-integration` | 4.0% | | `internal/goip/actuate` | 22.1% | `HealthyEvidence` and the treatment, TCP-settings, policy, evidence, proposal and report validators reach 100% statement coverage; `Evaluate` 94.7%, -`ValidateDesired` 91.7%, socket stamping 100%, discovery 87.3%, the agent's -`Run` 97.4%, the reconciler's `Run` 88.9% and the attribution watcher's `Run` -87.9%. `pkg/treatment` reads lower than before the split because coverage is per +`ValidateDesired` 91.7%, socket stamping 100%, discovery 89.4%, the agent's +`Run` 97.4%, the reconciler's `Run` 85.0% and the attribution watcher's `Run` +84.8%. `pkg/treatment` reads lower than before the split because coverage is per package (no `-coverpkg`): its enum, `Clone` and `Equal` methods are exercised by the `wire`, store and controller tests, which do not count toward it. Statement coverage is not branch coverage or a proof of correctness. @@ -93,7 +93,7 @@ inspection, TLS client construction, reconnect/backoff timing, and all combinations of controller database failures. Kernel route mutation and network behavior remain the responsibility of the existing disposable-namespace and seven-guest VM tests. The integration harness itself has low unit coverage -(4.1%); most of its code only executes inside those VMs. This audit did not rerun +(4.0%); most of its code only executes inside those VMs. This audit did not rerun the 30-minute soak; the previous three-cycle soak passed before these changes. The new RTT boundary regression exposed floating-point cancellation: `5.5/5-1`