From ef20530941b14c3d2a0c55e21966164ed9939ef6 Mon Sep 17 00:00:00 2001 From: Mike Cohen Date: Mon, 22 Nov 2021 17:11:49 +1000 Subject: [PATCH] Changes to support distributed architecture. (#1384) - Added SetSubjectWithCompletion() to allow asynchronous writes to control visibility. - Remove Walk() from Datastore and Filestore interfaces. - Added gRPC health check API - Added an env_var VELOCIRAPTOR_SLOW_FILESYSTEM which introduces artificial delays to all filesystem activity. This helps to simulate behavior on slow network filesystems such as EFS. - Minions now write client events themselves to the filesystem, as well as sending them over gRPC. In turn the master does not write the events but simply feeds them to any server event queries it needs. - Minion's gRPC now delay sending events and cache in memory to increase the send buffer size and reduce number of RPC calls. - Added File store implementation MemcacheFileDataStore. This filestore queues data in memory and writes it asynchronously to storage. This helps to combine multiple parts of the result sets into larger filesystem writes for more efficient writing. - System.Flow.Completion is now a server event artifact to ensure more efficient writing. - Client info manager now manages client ping information. The data is cached in memory for a while and then flushed asynchronously to the filestore in order to avoid writing client ping information too often. Data is sent to a new server event Server.Internal.ClientPing periodically to update the master in a more efficient batch way. - Minion hunt dispatchers are now all read only - they update their hunt status by receiving an Server.Internal.HuntUpdate message so they do not need to read it from storage. Only the master hunt dispatcher is allowed to update hunts in storage. - Notification service now batches notifications to reduce gRPC traffic. --- .github/workflows/codeql-analysis.yml | 71 -- Makefile | 5 + api/api.go | 14 +- api/clients.go | 23 +- api/datastore.go | 23 +- api/datastore_test.go | 25 +- api/download.go | 2 +- api/events.go | 21 +- api/health.go | 16 + api/mock/api_mock.go | 20 + api/proto/api.pb.go | 1042 +++++++++-------- api/proto/api.pb.gw.go | 28 +- api/proto/api.proto | 4 + api/proto/api_grpc.pb.go | 38 + api/proto/datastore.pb.go | 56 +- api/proto/datastore.proto | 5 + api/proto/health.pb.go | 267 +++++ api/proto/health.proto | 18 + api/proto/hunts.pb.go | 273 ++--- api/proto/hunts.proto | 2 + .../Server/Internal/ClientPing.yaml | 4 + .../Server/Internal/HuntUpdate.yaml | 12 + .../System/Hunt/Participation.yaml | 44 +- bin/binary_test.go | 7 +- bin/debug.go | 10 +- bin/frontend.go | 7 +- bin/golden.go | 4 + bin/index.go | 4 +- bin/main.go | 2 + bin/metrics.go | 20 + bin/minions.go | 10 + bin/repack.go | 3 +- clients/tasks.go | 2 + config/proto/config.pb.go | 852 +++++++------- config/proto/config.proto | 14 +- datastore/datastore.go | 65 +- datastore/datastore_test.go | 6 +- datastore/filebased.go | 51 +- datastore/filebased_test.go | 4 +- datastore/instrument.go | 13 +- datastore/memcache.go | 60 +- datastore/memcache_file.go | 85 +- datastore/memcache_file_test.go | 6 +- datastore/memory.go | 306 ----- datastore/memory_test.go | 28 - datastore/remote.go | 45 +- datastore/utils.go | 76 ++ file_store/api/file_store.go | 14 +- file_store/api/instrument.go | 108 ++ file_store/api/paths.go | 3 + file_store/api/queues.go | 4 + file_store/api/walk.go | 31 + file_store/directory/directory.go | 82 +- file_store/directory/listener.go | 6 +- file_store/directory/queue.go | 10 + file_store/directory/queue_test.go | 13 +- file_store/file_store.go | 76 +- file_store/memcache/memcache.go | 232 ++++ file_store/memory/memory.go | 61 +- file_store/memory/queue.go | 10 + file_store/path_specs/fs_path_spec.go | 23 +- file_store/path_specs/path_specs.go | 2 +- file_store/test_utils/testing.go | 6 +- file_store/test_utils/testsuite.go | 45 +- file_store/tests/testsuite.go | 6 +- flows/artifacts.go | 191 ++- flows/artifacts_test.go | 9 +- flows/foreman.go | 9 +- flows/hunts.go | 23 +- flows/limits.go | 2 +- flows/monitoring.go | 37 +- go.mod | 20 +- go.sum | 65 +- grpc_client/grpc.go | 103 +- .../src/components/utils/users.js | 4 + http_comms/e2e_test.go | 2 - http_comms/sender_test.go | 2 +- json/proto.go | 16 + logging/logging.go | 48 +- paths/artifacts/paths_test.go | 2 +- paths/client.go | 4 +- paths/client_test.go | 2 +- paths/notebooks_test.go | 2 +- reporting/gui.go | 6 + result_sets/api.go | 10 + result_sets/simple/simple.go | 37 +- result_sets/simple/simple_test.go | 65 +- result_sets/simple/sink.go | 2 + result_sets/timed/reader_test.go | 10 +- result_sets/timed/writer.go | 7 + result_sets/timed/writer_test.go | 75 +- search/clients.go | 35 +- server/comms.go | 6 +- server/server.go | 19 +- server/server_test.go | 23 +- services/client_info.go | 32 +- services/client_info/client_info.go | 345 +++++- services/client_info/client_info_test.go | 123 ++ services/frontend.go | 11 +- services/frontend/frontend.go | 52 +- services/hunt_dispatcher.go | 26 +- services/hunt_dispatcher/hunt_dispatcher.go | 348 +++--- services/hunt_manager/hunt_manager.go | 89 +- services/hunt_manager/hunt_manager_test.go | 6 +- services/interrogation/interrogation_test.go | 73 +- services/journal.go | 8 + services/journal/buffer.go | 6 +- services/journal/journal.go | 45 +- services/journal/journal_test.go | 115 ++ services/journal/replication.go | 145 ++- services/journal/replication_test.go | 25 +- services/journal/utils.go | 4 +- services/labels/labels.go | 2 +- services/notifications.go | 3 + services/notifications/notifications.go | 34 + services/repository/filestore.go | 2 +- services/repository/manager_test.go | 2 +- services/sanity/index_migration.go | 2 +- services/sanity/sanity_test.go | 73 +- .../server_artifacts/server_artifacts_test.go | 2 +- startup/startup.go | 22 +- timelines/supertimeline.go | 5 +- timelines/writer.go | 29 +- utils/clock.go | 17 +- utils/completer.go | 33 + utils/counter.go | 12 +- utils/debug.go | 12 + vql/filesystem/raw_registry.go | 3 - vql/parsers/authenticode/authenticode.go | 9 +- vql/parsers/authenticode/cat.go | 13 +- vql/parsers/authenticode/compat.go | 6 +- vql/server/clients/delete.go | 4 +- .../clients/fixtures/TestDeleteClient.golden | 6 + vql/server/flows/flows.go | 5 +- vql/server/notebooks/delete.go | 4 +- vql/server/repository.go | 12 +- vtesting/assert/wrapper.go | 4 + vtesting/metrics.go | 75 ++ 138 files changed, 4445 insertions(+), 2660 deletions(-) delete mode 100644 .github/workflows/codeql-analysis.yml create mode 100644 api/health.go create mode 100644 api/proto/health.pb.go create mode 100644 api/proto/health.proto create mode 100644 artifacts/definitions/Server/Internal/ClientPing.yaml create mode 100644 artifacts/definitions/Server/Internal/HuntUpdate.yaml create mode 100644 bin/metrics.go create mode 100644 bin/minions.go delete mode 100644 datastore/memory.go delete mode 100644 datastore/memory_test.go create mode 100644 datastore/utils.go create mode 100644 file_store/api/instrument.go create mode 100644 file_store/api/walk.go create mode 100644 file_store/memcache/memcache.go create mode 100644 json/proto.go create mode 100644 services/client_info/client_info_test.go create mode 100644 services/journal/journal_test.go create mode 100644 utils/completer.go create mode 100644 vtesting/metrics.go diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index 63040539be9..00000000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,71 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL" - -on: - push: - branches: [ master, v0.* ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ master ] - schedule: - - cron: '39 11 * * 0' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'go', 'javascript' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] - # Learn more: - # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 - - # ℹī¸ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # ✏ī¸ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 diff --git a/Makefile b/Makefile index c2b1dd119cb..5ba741adcd2 100644 --- a/Makefile +++ b/Makefile @@ -77,3 +77,8 @@ lint: KapeFilesSync: python3 scripts/kape_files.py ~/projects/KapeFiles/ > artifacts/definitions/Windows/KapeFiles/Targets.yaml + +# Do this after fetching the build artifacts with `gh run download ` +UpdateCIArtifacts: + mv artifact/server/* artifacts/testdata/server/testcases/ + mv artifact/windows/* artifacts/testdata/windows/ diff --git a/api/api.go b/api/api.go index 779ea16d5e5..9ff7baabc8c 100644 --- a/api/api.go +++ b/api/api.go @@ -257,7 +257,19 @@ func (self *ApiServer) ListClients( "User is not allowed to view clients.") } - return search.SearchClients(ctx, self.config, in, user_name) + result, err := search.SearchClients(ctx, self.config, in, user_name) + if err != nil { + return nil, err + } + + // Warm up the cache pre-emptively so we have fresh connected + // status + notifier := services.GetNotifier() + for _, item := range result.Items { + notifier.IsClientConnected( + ctx, self.config, item.ClientId, 0 /* timeout */) + } + return result, nil } func (self *ApiServer) NotifyClients( diff --git a/api/clients.go b/api/clients.go index cd03136e462..756f21c3a00 100644 --- a/api/clients.go +++ b/api/clients.go @@ -102,6 +102,7 @@ func (self *ApiServer) GetClient( "User is not allowed to view clients.") } + // Update the user's MRU if in.UpdateMru { err = search.UpdateMRU(self.config, user_name, in.ClientId) if err != nil { @@ -109,20 +110,22 @@ func (self *ApiServer) GetClient( } } - api_client, err := search.GetApiClient(ctx, - self.config, - in.ClientId, - !in.Lightweight, // Detailed - ) + api_client, err := search.FastGetApiClient(ctx, self.config, in.ClientId) if err != nil { return nil, err } - if self.server_obj != nil && !in.Lightweight && - // Wait up to 2 seconds to find out if clients are connected. - services.GetNotifier().IsClientConnected(ctx, - self.config, in.ClientId, 2) { - api_client.LastSeenAt = uint64(time.Now().UnixNano() / 1000) + if self.server_obj != nil { + if !in.Lightweight && + // Wait up to 2 seconds to find out if clients are connected. + services.GetNotifier().IsClientConnected(ctx, + self.config, in.ClientId, 2) { + api_client.LastSeenAt = uint64(time.Now().UnixNano() / 1000) + } else { + // Warm up the cache anyway. + go services.GetNotifier().IsClientConnected( + ctx, self.config, in.ClientId, 2) + } } return api_client, nil diff --git a/api/datastore.go b/api/datastore.go index 466c0dec7b1..253242a3bbb 100644 --- a/api/datastore.go +++ b/api/datastore.go @@ -1,6 +1,8 @@ package api import ( + "sync" + "github.com/golang/protobuf/ptypes/empty" context "golang.org/x/net/context" "google.golang.org/grpc/codes" @@ -65,7 +67,21 @@ func (self *ApiServer) SetSubject( "Datastore has no raw access.") } - err = raw_db.SetBuffer(self.config, getURN(in), in.Data) + if in.Sync { + var wg sync.WaitGroup + + // Wait for the data to hit the disk. + wg.Add(1) + err = raw_db.SetBuffer(self.config, getURN(in), in.Data, func() { + wg.Done() + }) + wg.Wait() + + } else { + + // Just write quickly. + err = raw_db.SetBuffer(self.config, getURN(in), in.Data, nil) + } return &api_proto.DataResponse{}, err } @@ -95,6 +111,7 @@ func (self *ApiServer) ListChildren( result.Children = append(result.Children, &api_proto.DSPathSpec{ Components: child.Components(), PathType: int64(child.Type()), + Tag: child.Tag(), IsDir: child.IsDir(), }) } @@ -128,5 +145,7 @@ func getURN(in *api_proto.DataRequest) api.DSPathSpec { } return path_specs.NewUnsafeDatastorePath( - path_spec.Components...).SetType(api.PathType(path_spec.PathType)) + path_spec.Components...). + SetType(api.PathType(path_spec.PathType)). + SetTag(path_spec.Tag) } diff --git a/api/datastore_test.go b/api/datastore_test.go index 2a03ec3d3a0..700dd8028c5 100644 --- a/api/datastore_test.go +++ b/api/datastore_test.go @@ -2,6 +2,7 @@ package api import ( "testing" + "time" "github.com/sebdah/goldie" "github.com/stretchr/testify/suite" @@ -13,6 +14,7 @@ import ( "www.velocidex.com/golang/velociraptor/file_store/test_utils" "www.velocidex.com/golang/velociraptor/grpc_client" "www.velocidex.com/golang/velociraptor/json" + "www.velocidex.com/golang/velociraptor/vtesting" "www.velocidex.com/golang/velociraptor/vtesting/assert" ) @@ -23,11 +25,10 @@ type DatastoreAPITest struct { } func (self *DatastoreAPITest) SetupTest() { - self.TestSuite.SetupTest() + self.ConfigObj = self.LoadConfig() + self.ConfigObj.API.BindPort = 8787 - // Now bring up an API server. - self.ConfigObj.Frontend.ServerServices = &config_proto.ServerServicesConfig{} - self.ConfigObj.API.BindPort = 8101 + self.TestSuite.SetupTest() server_builder, err := NewServerBuilder( self.Sm.Ctx, self.ConfigObj, self.Sm.Wg) @@ -36,6 +37,19 @@ func (self *DatastoreAPITest) SetupTest() { err = server_builder.WithAPIServer(self.Sm.Ctx, self.Sm.Wg) assert.NoError(self.T(), err) + // Now bring up an API server. + self.ConfigObj.Frontend.ServerServices = &config_proto.ServerServicesConfig{} + + // Wait for the server to come up. + conn, closer, err := grpc_client.Factory.GetAPIClient( + self.Sm.Ctx, self.ConfigObj) + assert.NoError(self.T(), err) + defer closer() + + vtesting.WaitUntil(2*time.Second, self.T(), func() bool { + res, err := conn.Check(self.Sm.Ctx, &api_proto.HealthCheckRequest{}) + return err == nil && res.Status == api_proto.HealthCheckResponse_SERVING + }) } func (self *DatastoreAPITest) TestDatastore() { @@ -53,9 +67,12 @@ func (self *DatastoreAPITest) TestDatastore() { assert.NoError(self.T(), err) defer closer() + test_utils.GetMemoryDataStore(self.T(), self.ConfigObj).Debug(self.ConfigObj) + res, err := conn.GetSubject(self.Sm.Ctx, &api_proto.DataRequest{ Pathspec: &api_proto.DSPathSpec{ Components: path_spec.Components(), + PathType: int64(path_spec.Type()), }}) assert.NoError(self.T(), err) assert.Equal(self.T(), res.Data, []byte("{\"name\":\"Velociraptor\"}")) diff --git a/api/download.go b/api/download.go index eaeb9143a51..0d7c3207067 100644 --- a/api/download.go +++ b/api/download.go @@ -248,7 +248,7 @@ func getTransformer( flow, err := flows.LoadCollectionContext(config_obj, client_id, flow_id) if err != nil { - flow = &flows_proto.ArtifactCollectorContext{} + flow = flows.NewCollectionContext(config_obj) } return ordereddict.NewDict(). diff --git a/api/events.go b/api/events.go index 05c20eefbc5..aec3755b454 100644 --- a/api/events.go +++ b/api/events.go @@ -79,16 +79,17 @@ func (self *ApiServer) PushEvents( } // Only return the first row - if true { - journal, err := services.GetJournal() - if err != nil { - return nil, err - } - - err = journal.PushRowsToArtifact(self.config, - rows, in.Artifact, in.ClientId, in.FlowId) - return &empty.Empty{}, err + journal, err := services.GetJournal() + if err != nil { + return nil, err } + + // only broadcast the events for local listeners. Minions + // write the events themselves, so we just need to broadcast + // for any server event artifacts that occur. + journal.Broadcast(self.config, + rows, in.Artifact, in.ClientId, in.FlowId) + return &empty.Empty{}, err } return nil, status.Error(codes.InvalidArgument, "no peer certs?") @@ -282,7 +283,7 @@ func getAllArtifacts( file_store_factory := file_store.GetFileStore(config_obj) - return file_store_factory.Walk(log_path, + return api.Walk(file_store_factory, log_path, func(full_path api.FSPathSpec, info os.FileInfo) error { // Walking the events directory will give us // all the day json files. Each day json file diff --git a/api/health.go b/api/health.go new file mode 100644 index 00000000000..727991aa178 --- /dev/null +++ b/api/health.go @@ -0,0 +1,16 @@ +package api + +import ( + context "golang.org/x/net/context" + "www.velocidex.com/golang/velociraptor/api/proto" + api_proto "www.velocidex.com/golang/velociraptor/api/proto" +) + +func (self *ApiServer) Check( + ctx context.Context, + in *api_proto.HealthCheckRequest) (*api_proto.HealthCheckResponse, error) { + + return &proto.HealthCheckResponse{ + Status: api_proto.HealthCheckResponse_SERVING, + }, nil +} diff --git a/api/mock/api_mock.go b/api/mock/api_mock.go index a45c8430e83..1506786f9d3 100644 --- a/api/mock/api_mock.go +++ b/api/mock/api_mock.go @@ -101,6 +101,26 @@ func (mr *MockAPIClientMockRecorder) CancelNotebookCell(arg0, arg1 interface{}, return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CancelNotebookCell", reflect.TypeOf((*MockAPIClient)(nil).CancelNotebookCell), varargs...) } +// Check mocks base method. +func (m *MockAPIClient) Check(arg0 context.Context, arg1 *proto0.HealthCheckRequest, arg2 ...grpc.CallOption) (*proto0.HealthCheckResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Check", varargs...) + ret0, _ := ret[0].(*proto0.HealthCheckResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Check indicates an expected call of Check. +func (mr *MockAPIClientMockRecorder) Check(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Check", reflect.TypeOf((*MockAPIClient)(nil).Check), varargs...) +} + // CollectArtifact mocks base method. func (m *MockAPIClient) CollectArtifact(arg0 context.Context, arg1 *proto2.ArtifactCollectorArgs, arg2 ...grpc.CallOption) (*proto2.ArtifactCollectorResponse, error) { m.ctrl.T.Helper() diff --git a/api/proto/api.pb.go b/api/proto/api.pb.go index 176bfb76eca..a1c2fa6a8c7 100644 --- a/api/proto/api.pb.go +++ b/api/proto/api.pb.go @@ -577,443 +577,448 @@ var file_api_proto_rawDesc = []byte{ 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0f, 0x64, 0x61, 0x74, 0x61, 0x73, - 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0b, 0x68, 0x75, 0x6e, 0x74, - 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0b, 0x66, 0x6c, 0x6f, 0x77, 0x73, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0f, 0x6e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x1a, 0x09, 0x63, 0x73, 0x76, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0e, 0x64, - 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x11, 0x63, - 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x1a, 0x0d, 0x76, 0x66, 0x73, 0x5f, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, - 0x2c, 0x0a, 0x11, 0x53, 0x74, 0x61, 0x72, 0x74, 0x46, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6c, 0x6f, 0x77, 0x49, 0x64, 0x22, 0x22, 0x0a, - 0x08, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x61, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, - 0x73, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, - 0x6e, 0x22, 0x35, 0x0a, 0x0c, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x61, 0x6c, 0x4c, 0x69, 0x73, - 0x74, 0x12, 0x25, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x0f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x61, - 0x6c, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0xe4, 0x01, 0x0a, 0x1a, 0x56, 0x46, 0x53, - 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, - 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x25, 0xe2, 0xfc, 0xe3, 0xc4, - 0x01, 0x1f, 0x0a, 0x06, 0x52, 0x44, 0x46, 0x55, 0x52, 0x4e, 0x12, 0x12, 0x54, 0x68, 0x65, 0x20, - 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x1a, 0x01, - 0x02, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x58, - 0x58, 0x58, 0x58, 0x76, 0x66, 0x73, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x58, 0x58, 0x58, 0x58, 0x76, 0x66, 0x73, 0x50, 0x61, 0x74, 0x68, 0x12, 0x25, - 0x0a, 0x0e, 0x76, 0x66, 0x73, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, - 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x76, 0x66, 0x73, 0x43, 0x6f, 0x6d, 0x70, 0x6f, - 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x38, 0x0a, 0x05, 0x64, 0x65, 0x70, 0x74, 0x68, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x04, 0x42, 0x22, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x1c, 0x12, 0x1a, 0x44, 0x65, - 0x70, 0x74, 0x68, 0x20, 0x6f, 0x66, 0x20, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, - 0x20, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x05, 0x64, 0x65, 0x70, 0x74, 0x68, 0x22, - 0x90, 0x01, 0x0a, 0x0d, 0x56, 0x46, 0x53, 0x46, 0x69, 0x6c, 0x65, 0x42, 0x75, 0x66, 0x66, 0x65, - 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x16, - 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, - 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x12, 0x12, - 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, - 0x74, 0x61, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, - 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, - 0x74, 0x73, 0x22, 0x51, 0x0a, 0x13, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, - 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, - 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, - 0x5f, 0x61, 0x6c, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6e, 0x6f, 0x74, 0x69, - 0x66, 0x79, 0x41, 0x6c, 0x6c, 0x22, 0x38, 0x0a, 0x0c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x75, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x75, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, - 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x22, - 0x3d, 0x0a, 0x0d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6a, 0x73, 0x6f, 0x6e, - 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x6a, 0x73, 0x6f, 0x6e, 0x6c, 0x22, 0x7a, - 0x0a, 0x10, 0x50, 0x75, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x12, 0x1b, - 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x66, - 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6c, - 0x6f, 0x77, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6a, 0x73, 0x6f, 0x6e, 0x6c, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x05, 0x6a, 0x73, 0x6f, 0x6e, 0x6c, 0x32, 0xbe, 0x2e, 0x0a, 0x03, 0x41, - 0x50, 0x49, 0x12, 0x52, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, 0x75, 0x6e, 0x74, - 0x12, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x75, 0x6e, 0x74, 0x1a, 0x18, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x46, 0x6c, 0x6f, 0x77, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x22, - 0x12, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, - 0x75, 0x6e, 0x74, 0x3a, 0x01, 0x2a, 0x12, 0x59, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x75, - 0x6e, 0x74, 0x73, 0x12, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x48, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, - 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x75, 0x6e, 0x74, - 0x73, 0x12, 0x46, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x48, 0x75, 0x6e, 0x74, 0x12, 0x15, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x75, 0x6e, 0x74, - 0x22, 0x17, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x12, 0x0f, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, - 0x31, 0x2f, 0x47, 0x65, 0x74, 0x48, 0x75, 0x6e, 0x74, 0x12, 0x50, 0x0a, 0x0a, 0x4d, 0x6f, 0x64, - 0x69, 0x66, 0x79, 0x48, 0x75, 0x6e, 0x74, 0x12, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x48, 0x75, 0x6e, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x1d, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x17, 0x22, 0x12, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x4d, 0x6f, - 0x64, 0x69, 0x66, 0x79, 0x48, 0x75, 0x6e, 0x74, 0x3a, 0x01, 0x2a, 0x12, 0x5d, 0x0a, 0x0c, 0x47, - 0x65, 0x74, 0x48, 0x75, 0x6e, 0x74, 0x46, 0x6c, 0x6f, 0x77, 0x73, 0x12, 0x16, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x54, - 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x16, 0x12, 0x14, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, - 0x74, 0x48, 0x75, 0x6e, 0x74, 0x46, 0x6c, 0x6f, 0x77, 0x73, 0x12, 0x67, 0x0a, 0x0e, 0x47, 0x65, - 0x74, 0x48, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x1c, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x75, - 0x6c, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x12, 0x16, 0x2f, 0x61, 0x70, - 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x48, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x75, - 0x6c, 0x74, 0x73, 0x12, 0x64, 0x0a, 0x0d, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x43, 0x6c, 0x69, - 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0c, 0x68, 0x65, 0x61, 0x6c, + 0x74, 0x68, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0b, 0x68, 0x75, 0x6e, 0x74, 0x73, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0b, 0x66, 0x6c, 0x6f, 0x77, 0x73, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x1a, 0x0f, 0x6e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x1a, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x1a, 0x09, 0x63, 0x73, 0x76, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0e, 0x64, 0x6f, 0x77, + 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x11, 0x63, 0x6f, 0x6d, + 0x70, 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0d, + 0x76, 0x66, 0x73, 0x5f, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x2c, 0x0a, + 0x11, 0x53, 0x74, 0x61, 0x72, 0x74, 0x46, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6c, 0x6f, 0x77, 0x49, 0x64, 0x22, 0x22, 0x0a, 0x08, 0x41, + 0x70, 0x70, 0x72, 0x6f, 0x76, 0x61, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, + 0x35, 0x0a, 0x0c, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x61, 0x6c, 0x4c, 0x69, 0x73, 0x74, 0x12, + 0x25, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x61, 0x6c, 0x52, + 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0xe4, 0x01, 0x0a, 0x1a, 0x56, 0x46, 0x53, 0x52, 0x65, + 0x66, 0x72, 0x65, 0x73, 0x68, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x25, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x1f, + 0x0a, 0x06, 0x52, 0x44, 0x46, 0x55, 0x52, 0x4e, 0x12, 0x12, 0x54, 0x68, 0x65, 0x20, 0x63, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x1a, 0x01, 0x02, 0x52, + 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x58, 0x58, 0x58, + 0x58, 0x76, 0x66, 0x73, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x58, 0x58, 0x58, 0x58, 0x76, 0x66, 0x73, 0x50, 0x61, 0x74, 0x68, 0x12, 0x25, 0x0a, 0x0e, + 0x76, 0x66, 0x73, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x04, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x76, 0x66, 0x73, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, + 0x6e, 0x74, 0x73, 0x12, 0x38, 0x0a, 0x05, 0x64, 0x65, 0x70, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x04, 0x42, 0x22, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x1c, 0x12, 0x1a, 0x44, 0x65, 0x70, 0x74, + 0x68, 0x20, 0x6f, 0x66, 0x20, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x20, 0x72, + 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x05, 0x64, 0x65, 0x70, 0x74, 0x68, 0x22, 0x90, 0x01, + 0x0a, 0x0d, 0x56, 0x46, 0x53, 0x46, 0x69, 0x6c, 0x65, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x12, + 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, + 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6f, 0x66, + 0x66, 0x73, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, + 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, + 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x06, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, + 0x22, 0x51, 0x0a, 0x13, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x5f, 0x61, + 0x6c, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, + 0x41, 0x6c, 0x6c, 0x22, 0x38, 0x0a, 0x0c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x75, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x6f, 0x64, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x22, 0x3d, 0x0a, + 0x0d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6a, 0x73, 0x6f, 0x6e, 0x6c, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x6a, 0x73, 0x6f, 0x6e, 0x6c, 0x22, 0x7a, 0x0a, 0x10, + 0x50, 0x75, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x12, 0x1b, 0x0a, 0x09, + 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x6c, 0x6f, + 0x77, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6c, 0x6f, 0x77, + 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6a, 0x73, 0x6f, 0x6e, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x05, 0x6a, 0x73, 0x6f, 0x6e, 0x6c, 0x32, 0xfe, 0x2e, 0x0a, 0x03, 0x41, 0x50, 0x49, + 0x12, 0x52, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, 0x75, 0x6e, 0x74, 0x12, 0x0b, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x75, 0x6e, 0x74, 0x1a, 0x18, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x46, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x22, 0x12, 0x2f, + 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x48, 0x75, 0x6e, + 0x74, 0x3a, 0x01, 0x2a, 0x12, 0x59, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x75, 0x6e, 0x74, + 0x73, 0x12, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x75, + 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, 0x2f, 0x61, + 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x75, 0x6e, 0x74, 0x73, 0x12, + 0x46, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x48, 0x75, 0x6e, 0x74, 0x12, 0x15, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x75, 0x6e, 0x74, 0x22, 0x17, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x12, 0x0f, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, + 0x47, 0x65, 0x74, 0x48, 0x75, 0x6e, 0x74, 0x12, 0x50, 0x0a, 0x0a, 0x4d, 0x6f, 0x64, 0x69, 0x66, + 0x79, 0x48, 0x75, 0x6e, 0x74, 0x12, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x75, + 0x6e, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x17, 0x22, 0x12, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x4d, 0x6f, 0x64, 0x69, + 0x66, 0x79, 0x48, 0x75, 0x6e, 0x74, 0x3a, 0x01, 0x2a, 0x12, 0x5d, 0x0a, 0x0c, 0x47, 0x65, 0x74, + 0x48, 0x75, 0x6e, 0x74, 0x46, 0x6c, 0x6f, 0x77, 0x73, 0x12, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x61, 0x62, + 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x16, 0x12, 0x14, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x48, + 0x75, 0x6e, 0x74, 0x46, 0x6c, 0x6f, 0x77, 0x73, 0x12, 0x67, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x48, + 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x1c, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x47, 0x65, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x12, 0x16, 0x2f, 0x61, 0x70, 0x69, 0x2f, + 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x48, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x73, 0x12, 0x64, 0x0a, 0x0d, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x43, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x73, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x22, 0x14, + 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x43, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x3a, 0x01, 0x2a, 0x12, 0x5f, 0x0a, 0x0c, 0x4c, 0x61, 0x62, 0x65, 0x6c, + 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x50, 0x49, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x22, + 0x14, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x43, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x67, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, + 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x65, 0x61, + 0x72, 0x63, 0x68, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x12, 0x15, 0x2f, 0x61, 0x70, 0x69, + 0x2f, 0x76, 0x31, 0x2f, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x73, 0x12, 0x5d, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x17, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x41, 0x70, 0x69, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x22, 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x1f, 0x12, 0x1d, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x43, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x2f, 0x7b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, + 0x12, 0x72, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, + 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x2d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x27, 0x12, 0x25, 0x2f, + 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x7b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x68, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, - 0x22, 0x14, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, - 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3a, 0x01, 0x2a, 0x12, 0x5f, 0x0a, 0x0c, 0x4c, 0x61, 0x62, - 0x65, 0x6c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x50, - 0x49, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x19, 0x22, 0x14, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x4c, 0x61, 0x62, 0x65, 0x6c, - 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x67, 0x0a, 0x0b, 0x4c, 0x69, - 0x73, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, - 0x65, 0x61, 0x72, 0x63, 0x68, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x12, 0x15, 0x2f, 0x61, - 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x43, 0x6c, 0x69, 0x65, - 0x6e, 0x74, 0x73, 0x12, 0x5d, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, - 0x12, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, - 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x41, 0x70, 0x69, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x22, 0x25, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x1f, 0x12, 0x1d, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, - 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2f, 0x7b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, - 0x64, 0x7d, 0x12, 0x72, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x2d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x27, 0x12, - 0x25, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, - 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x7b, 0x63, 0x6c, 0x69, 0x65, - 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x68, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x43, 0x6c, 0x69, - 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x15, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x1e, 0x22, 0x19, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x53, 0x65, 0x74, 0x43, - 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x3a, 0x01, 0x2a, - 0x12, 0x99, 0x01, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x46, 0x6c, - 0x6f, 0x77, 0x73, 0x12, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x70, 0x69, 0x46, - 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x41, 0x70, 0x69, 0x46, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x58, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x52, 0x12, 0x22, 0x2f, 0x61, 0x70, 0x69, - 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x46, 0x6c, 0x6f, - 0x77, 0x73, 0x2f, 0x7b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x5a, 0x2c, - 0x42, 0x2a, 0x0a, 0x04, 0x48, 0x45, 0x41, 0x44, 0x12, 0x22, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, + 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, + 0x22, 0x19, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x53, 0x65, 0x74, 0x43, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x3a, 0x01, 0x2a, 0x12, 0x99, + 0x01, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x46, 0x6c, 0x6f, 0x77, + 0x73, 0x12, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x70, 0x69, 0x46, 0x6c, 0x6f, + 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x41, 0x70, 0x69, 0x46, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x58, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x52, 0x12, 0x22, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x46, 0x6c, 0x6f, 0x77, 0x73, - 0x2f, 0x7b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x5d, 0x0a, 0x0f, - 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x55, 0x49, 0x54, 0x72, 0x61, 0x69, 0x74, 0x73, 0x12, - 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x41, 0x70, 0x69, 0x47, 0x72, 0x72, 0x55, 0x73, 0x65, 0x72, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x19, 0x12, 0x17, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x55, - 0x73, 0x65, 0x72, 0x55, 0x49, 0x54, 0x72, 0x61, 0x69, 0x74, 0x73, 0x12, 0x66, 0x0a, 0x0d, 0x53, - 0x65, 0x74, 0x47, 0x55, 0x49, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1b, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x65, 0x74, 0x47, 0x55, 0x49, 0x4f, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x22, 0x15, 0x2f, 0x61, 0x70, 0x69, 0x2f, - 0x76, 0x31, 0x2f, 0x53, 0x65, 0x74, 0x47, 0x55, 0x49, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x3a, 0x01, 0x2a, 0x12, 0x4a, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, - 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x55, 0x73, 0x65, 0x72, 0x73, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x12, 0x10, 0x2f, - 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, - 0x57, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, - 0x74, 0x65, 0x73, 0x12, 0x0f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x61, 0x76, 0x6f, - 0x72, 0x69, 0x74, 0x65, 0x1a, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x61, 0x76, - 0x6f, 0x72, 0x69, 0x74, 0x65, 0x73, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x12, 0x18, - 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x46, - 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x73, 0x12, 0x6f, 0x0a, 0x10, 0x56, 0x46, 0x53, 0x4c, - 0x69, 0x73, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x15, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x46, 0x53, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x46, 0x53, 0x4c, - 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2c, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x26, 0x12, 0x24, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x56, 0x46, 0x53, - 0x4c, 0x69, 0x73, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x7b, 0x63, - 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x82, 0x01, 0x0a, 0x13, 0x56, 0x46, - 0x53, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, - 0x79, 0x12, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x46, 0x53, 0x52, 0x65, 0x66, - 0x72, 0x65, 0x73, 0x68, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x72, 0x74, - 0x69, 0x66, 0x61, 0x63, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x26, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x22, 0x1b, - 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x56, 0x46, 0x53, 0x52, 0x65, 0x66, 0x72, 0x65, - 0x73, 0x68, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x3a, 0x01, 0x2a, 0x12, 0x63, - 0x0a, 0x10, 0x56, 0x46, 0x53, 0x53, 0x74, 0x61, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, - 0x72, 0x79, 0x12, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x46, 0x53, 0x4c, 0x69, - 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x56, 0x46, 0x53, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x12, 0x18, 0x2f, 0x61, 0x70, 0x69, 0x2f, - 0x76, 0x31, 0x2f, 0x56, 0x46, 0x53, 0x53, 0x74, 0x61, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, - 0x6f, 0x72, 0x79, 0x12, 0x69, 0x0a, 0x0f, 0x56, 0x46, 0x53, 0x53, 0x74, 0x61, 0x74, 0x44, 0x6f, - 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, - 0x46, 0x53, 0x53, 0x74, 0x61, 0x74, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x46, - 0x53, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x1f, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x12, 0x17, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x56, - 0x46, 0x53, 0x53, 0x74, 0x61, 0x74, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x55, - 0x0a, 0x08, 0x47, 0x65, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x16, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x61, - 0x62, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x12, 0x12, 0x10, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, - 0x54, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x75, 0x0a, 0x0f, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, - 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x12, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, - 0x6f, 0x72, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, + 0x2f, 0x7b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x5a, 0x2c, 0x42, 0x2a, + 0x0a, 0x04, 0x48, 0x45, 0x41, 0x44, 0x12, 0x22, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, + 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x46, 0x6c, 0x6f, 0x77, 0x73, 0x2f, 0x7b, + 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x5d, 0x0a, 0x0f, 0x47, 0x65, + 0x74, 0x55, 0x73, 0x65, 0x72, 0x55, 0x49, 0x54, 0x72, 0x61, 0x69, 0x74, 0x73, 0x12, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x70, + 0x69, 0x47, 0x72, 0x72, 0x55, 0x73, 0x65, 0x72, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, + 0x12, 0x17, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, + 0x72, 0x55, 0x49, 0x54, 0x72, 0x61, 0x69, 0x74, 0x73, 0x12, 0x66, 0x0a, 0x0d, 0x53, 0x65, 0x74, + 0x47, 0x55, 0x49, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1b, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x53, 0x65, 0x74, 0x47, 0x55, 0x49, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, + 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x22, 0x15, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, + 0x2f, 0x53, 0x65, 0x74, 0x47, 0x55, 0x49, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x3a, 0x01, + 0x2a, 0x12, 0x4a, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x55, 0x73, + 0x65, 0x72, 0x73, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x12, 0x10, 0x2f, 0x61, 0x70, + 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x57, 0x0a, + 0x10, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, + 0x73, 0x12, 0x0f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, + 0x74, 0x65, 0x1a, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x61, 0x76, 0x6f, 0x72, + 0x69, 0x74, 0x65, 0x73, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x12, 0x18, 0x2f, 0x61, + 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x46, 0x61, 0x76, + 0x6f, 0x72, 0x69, 0x74, 0x65, 0x73, 0x12, 0x6f, 0x0a, 0x10, 0x56, 0x46, 0x53, 0x4c, 0x69, 0x73, + 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x15, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x56, 0x46, 0x53, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x46, 0x53, 0x4c, 0x69, 0x73, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2c, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x26, 0x12, 0x24, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x56, 0x46, 0x53, 0x4c, 0x69, + 0x73, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x7b, 0x63, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x82, 0x01, 0x0a, 0x13, 0x56, 0x46, 0x53, 0x52, + 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, + 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x46, 0x53, 0x52, 0x65, 0x66, 0x72, 0x65, + 0x73, 0x68, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x72, 0x74, 0x69, 0x66, + 0x61, 0x63, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x26, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x22, 0x1b, 0x2f, 0x61, + 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x56, 0x46, 0x53, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, + 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x3a, 0x01, 0x2a, 0x12, 0x63, 0x0a, 0x10, + 0x56, 0x46, 0x53, 0x53, 0x74, 0x61, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, + 0x12, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x46, 0x53, 0x4c, 0x69, 0x73, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x56, 0x46, 0x53, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x12, 0x18, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, + 0x2f, 0x56, 0x46, 0x53, 0x53, 0x74, 0x61, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x79, 0x12, 0x69, 0x0a, 0x0f, 0x56, 0x46, 0x53, 0x53, 0x74, 0x61, 0x74, 0x44, 0x6f, 0x77, 0x6e, + 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x46, 0x53, + 0x53, 0x74, 0x61, 0x74, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x46, 0x53, 0x44, + 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x1f, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x19, 0x12, 0x17, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x56, 0x46, 0x53, + 0x53, 0x74, 0x61, 0x74, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x55, 0x0a, 0x08, + 0x47, 0x65, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x47, 0x65, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x61, 0x62, 0x6c, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x12, 0x12, 0x10, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x54, 0x61, + 0x62, 0x6c, 0x65, 0x12, 0x75, 0x0a, 0x0f, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x41, 0x72, + 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x12, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x22, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, - 0x22, 0x17, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, - 0x74, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x3a, 0x01, 0x2a, 0x12, 0x5c, 0x0a, 0x0a, - 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x15, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x41, 0x70, 0x69, 0x46, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x46, - 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x17, 0x22, 0x12, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x43, 0x61, 0x6e, - 0x63, 0x65, 0x6c, 0x46, 0x6c, 0x6f, 0x77, 0x3a, 0x01, 0x2a, 0x12, 0x5e, 0x0a, 0x0b, 0x41, 0x72, - 0x63, 0x68, 0x69, 0x76, 0x65, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x41, 0x70, 0x69, 0x46, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x46, 0x6c, - 0x6f, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x18, 0x22, 0x13, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x41, 0x72, 0x63, 0x68, - 0x69, 0x76, 0x65, 0x46, 0x6c, 0x6f, 0x77, 0x3a, 0x01, 0x2a, 0x12, 0x5b, 0x0a, 0x0e, 0x47, 0x65, - 0x74, 0x46, 0x6c, 0x6f, 0x77, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x15, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x70, 0x69, 0x46, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x6c, 0x6f, 0x77, - 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x12, - 0x16, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x46, 0x6c, 0x6f, 0x77, - 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x67, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x46, 0x6c, - 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x12, 0x15, 0x2e, 0x70, 0x72, 0x6f, + 0x41, 0x72, 0x67, 0x73, 0x1a, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x72, 0x74, + 0x69, 0x66, 0x61, 0x63, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x22, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x22, 0x17, + 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x41, + 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x3a, 0x01, 0x2a, 0x12, 0x5c, 0x0a, 0x0a, 0x43, 0x61, + 0x6e, 0x63, 0x65, 0x6c, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x41, 0x70, 0x69, 0x46, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x46, 0x6c, 0x6f, + 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x17, 0x22, 0x12, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x43, 0x61, 0x6e, 0x63, 0x65, + 0x6c, 0x46, 0x6c, 0x6f, 0x77, 0x3a, 0x01, 0x2a, 0x12, 0x5e, 0x0a, 0x0b, 0x41, 0x72, 0x63, 0x68, + 0x69, 0x76, 0x65, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x41, 0x70, 0x69, 0x46, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x46, 0x6c, 0x6f, 0x77, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, + 0x22, 0x13, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, + 0x65, 0x46, 0x6c, 0x6f, 0x77, 0x3a, 0x01, 0x2a, 0x12, 0x5b, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x46, + 0x6c, 0x6f, 0x77, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x70, 0x69, 0x46, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x70, 0x69, 0x46, 0x6c, 0x6f, - 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x22, - 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x12, 0x17, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, - 0x2f, 0x47, 0x65, 0x74, 0x46, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, - 0x12, 0x71, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x43, 0x6f, - 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x1a, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4b, 0x65, 0x79, 0x77, 0x6f, 0x72, - 0x64, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x25, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x1f, 0x12, 0x1d, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, - 0x74, 0x4b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x12, 0x67, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, - 0x63, 0x74, 0x73, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x41, - 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, - 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x73, 0x22, 0x1f, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x19, 0x22, 0x14, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, - 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x69, 0x0a, 0x0f, - 0x47, 0x65, 0x74, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x12, - 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x72, 0x74, 0x69, 0x66, - 0x61, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x12, 0x17, - 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x41, 0x72, 0x74, 0x69, 0x66, - 0x61, 0x63, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x64, 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x41, 0x72, - 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x19, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x53, 0x65, 0x74, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x50, - 0x49, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x22, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x1c, 0x22, 0x17, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x53, 0x65, 0x74, 0x41, 0x72, - 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x6e, 0x0a, - 0x10, 0x4c, 0x6f, 0x61, 0x64, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x50, 0x61, 0x63, - 0x6b, 0x12, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x46, 0x53, 0x46, 0x69, 0x6c, - 0x65, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x4c, 0x6f, 0x61, 0x64, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x50, 0x61, 0x63, 0x6b, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x23, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, - 0x22, 0x18, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x4c, 0x6f, 0x61, 0x64, 0x41, 0x72, - 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x50, 0x61, 0x63, 0x6b, 0x3a, 0x01, 0x2a, 0x12, 0x44, 0x0a, - 0x0b, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x6f, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x0b, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x6f, 0x6f, 0x6c, 0x1a, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x54, 0x6f, 0x6f, 0x6c, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, - 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x6f, 0x6c, 0x49, - 0x6e, 0x66, 0x6f, 0x12, 0x47, 0x0a, 0x0b, 0x53, 0x65, 0x74, 0x54, 0x6f, 0x6f, 0x6c, 0x49, 0x6e, - 0x66, 0x6f, 0x12, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x6f, 0x6f, 0x6c, 0x1a, - 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x6f, 0x6f, 0x6c, 0x22, 0x1e, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x18, 0x22, 0x13, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x53, 0x65, - 0x74, 0x54, 0x6f, 0x6f, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x3a, 0x01, 0x2a, 0x12, 0x5c, 0x0a, 0x09, - 0x47, 0x65, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x16, 0x22, 0x11, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, - 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x3a, 0x01, 0x2a, 0x12, 0x7a, 0x0a, 0x18, 0x47, 0x65, - 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, - 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x43, - 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x41, 0x72, 0x67, 0x73, 0x22, 0x28, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x22, 0x12, 0x20, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, - 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, - 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x83, 0x01, 0x0a, 0x18, 0x53, 0x65, 0x74, 0x53, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x53, 0x74, - 0x61, 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x72, 0x74, 0x69, - 0x66, 0x61, 0x63, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x41, 0x72, 0x67, - 0x73, 0x1a, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, - 0x63, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x41, 0x72, 0x67, 0x73, 0x22, + 0x74, 0x1a, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x6c, 0x6f, 0x77, 0x44, 0x65, + 0x74, 0x61, 0x69, 0x6c, 0x73, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x12, 0x16, 0x2f, + 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x46, 0x6c, 0x6f, 0x77, 0x44, 0x65, + 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x67, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x46, 0x6c, 0x6f, 0x77, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x12, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x41, 0x70, 0x69, 0x46, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x70, 0x69, 0x46, 0x6c, 0x6f, 0x77, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x22, 0x1f, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x12, 0x17, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, + 0x65, 0x74, 0x46, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x12, 0x71, + 0x0a, 0x15, 0x47, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x43, 0x6f, 0x6d, 0x70, + 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, + 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x43, + 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x25, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x1f, 0x12, 0x1d, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x4b, + 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x12, 0x67, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, + 0x73, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x72, 0x74, + 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x44, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x73, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x19, 0x22, 0x14, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x41, 0x72, + 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x69, 0x0a, 0x0f, 0x47, 0x65, + 0x74, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x19, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x47, 0x65, 0x74, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x12, 0x17, 0x2f, 0x61, + 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, + 0x74, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x64, 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x41, 0x72, 0x74, 0x69, + 0x66, 0x61, 0x63, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x53, 0x65, 0x74, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x50, 0x49, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x22, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x22, + 0x17, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x53, 0x65, 0x74, 0x41, 0x72, 0x74, 0x69, + 0x66, 0x61, 0x63, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x6e, 0x0a, 0x10, 0x4c, + 0x6f, 0x61, 0x64, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x50, 0x61, 0x63, 0x6b, 0x12, + 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x46, 0x53, 0x46, 0x69, 0x6c, 0x65, 0x42, + 0x75, 0x66, 0x66, 0x65, 0x72, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4c, 0x6f, + 0x61, 0x64, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x50, 0x61, 0x63, 0x6b, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x23, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, 0x22, 0x18, + 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x4c, 0x6f, 0x61, 0x64, 0x41, 0x72, 0x74, 0x69, + 0x66, 0x61, 0x63, 0x74, 0x50, 0x61, 0x63, 0x6b, 0x3a, 0x01, 0x2a, 0x12, 0x44, 0x0a, 0x0b, 0x47, + 0x65, 0x74, 0x54, 0x6f, 0x6f, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x0b, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x54, 0x6f, 0x6f, 0x6c, 0x1a, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x54, 0x6f, 0x6f, 0x6c, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, 0x2f, 0x61, + 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x6f, 0x6c, 0x49, 0x6e, 0x66, + 0x6f, 0x12, 0x47, 0x0a, 0x0b, 0x53, 0x65, 0x74, 0x54, 0x6f, 0x6f, 0x6c, 0x49, 0x6e, 0x66, 0x6f, + 0x12, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x6f, 0x6f, 0x6c, 0x1a, 0x0b, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x6f, 0x6f, 0x6c, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x18, 0x22, 0x13, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x53, 0x65, 0x74, 0x54, + 0x6f, 0x6f, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x3a, 0x01, 0x2a, 0x12, 0x5c, 0x0a, 0x09, 0x47, 0x65, + 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x47, 0x65, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x16, 0x22, 0x11, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x52, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x3a, 0x01, 0x2a, 0x12, 0x7a, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x53, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x43, 0x6f, 0x6c, + 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x41, 0x72, 0x67, 0x73, 0x22, 0x28, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x22, 0x12, 0x20, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x53, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x12, 0x83, 0x01, 0x0a, 0x18, 0x53, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x12, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, + 0x63, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x41, 0x72, 0x67, 0x73, 0x1a, + 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, + 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x41, 0x72, 0x67, 0x73, 0x22, 0x2b, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x25, 0x22, 0x20, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x53, + 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, + 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x85, 0x01, 0x0a, 0x18, 0x47, + 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, + 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, + 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x28, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x22, + 0x12, 0x20, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x12, 0x78, 0x0a, 0x18, 0x53, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, + 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x17, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x2b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25, 0x22, 0x20, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, - 0x2f, 0x53, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, - 0x72, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x85, 0x01, 0x0a, - 0x18, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, - 0x72, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x26, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x6f, 0x6e, 0x69, 0x74, - 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x28, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x22, 0x12, 0x20, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x43, - 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x53, - 0x74, 0x61, 0x74, 0x65, 0x12, 0x78, 0x0a, 0x18, 0x53, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, - 0x74, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, - 0x12, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x22, 0x2b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25, 0x22, 0x20, 0x2f, 0x61, 0x70, 0x69, 0x2f, - 0x76, 0x31, 0x2f, 0x53, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x6f, 0x6e, 0x69, - 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x9c, - 0x01, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x27, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, - 0x6c, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x2c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x26, 0x22, 0x21, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, - 0x2f, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x74, 0x0a, - 0x12, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x46, - 0x69, 0x6c, 0x65, 0x12, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x22, 0x16, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, - 0x31, 0x2f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, - 0x3a, 0x01, 0x2a, 0x12, 0x5a, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, - 0x6f, 0x6b, 0x73, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x65, - 0x62, 0x6f, 0x6f, 0x6b, 0x43, 0x65, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, - 0x73, 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x12, 0x14, 0x2f, 0x61, 0x70, 0x69, 0x2f, - 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0x12, - 0x5f, 0x0a, 0x0b, 0x4e, 0x65, 0x77, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x12, 0x17, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x4d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x22, 0x13, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, - 0x31, 0x2f, 0x4e, 0x65, 0x77, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x3a, 0x01, 0x2a, - 0x12, 0x65, 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, - 0x6f, 0x6b, 0x12, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x65, 0x62, - 0x6f, 0x6f, 0x6b, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x17, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x4d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x22, 0x16, 0x2f, 0x61, - 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x74, 0x65, - 0x62, 0x6f, 0x6f, 0x6b, 0x3a, 0x01, 0x2a, 0x12, 0x6a, 0x0a, 0x0f, 0x4e, 0x65, 0x77, 0x4e, 0x6f, - 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x43, 0x65, 0x6c, 0x6c, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x43, 0x65, 0x6c, 0x6c, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, - 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, - 0x22, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x22, 0x17, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, - 0x2f, 0x4e, 0x65, 0x77, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x43, 0x65, 0x6c, 0x6c, - 0x3a, 0x01, 0x2a, 0x12, 0x63, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, - 0x6f, 0x6b, 0x43, 0x65, 0x6c, 0x6c, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, - 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x43, 0x65, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x65, 0x62, - 0x6f, 0x6f, 0x6b, 0x43, 0x65, 0x6c, 0x6c, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x12, - 0x17, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x74, 0x65, - 0x62, 0x6f, 0x6f, 0x6b, 0x43, 0x65, 0x6c, 0x6c, 0x12, 0x6c, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x43, 0x65, 0x6c, 0x6c, 0x12, 0x1a, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x43, - 0x65, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x43, 0x65, 0x6c, 0x6c, 0x22, - 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x22, 0x1a, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, - 0x2f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x43, - 0x65, 0x6c, 0x6c, 0x3a, 0x01, 0x2a, 0x12, 0x6f, 0x0a, 0x12, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, + 0x2f, 0x53, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, + 0x72, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x9c, 0x01, 0x0a, + 0x19, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x27, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, + 0x73, 0x75, 0x6c, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2c, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x26, 0x22, 0x21, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x4c, + 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x74, 0x0a, 0x12, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x46, 0x69, 0x6c, + 0x65, 0x12, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, + 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x21, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x22, 0x16, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x3a, 0x01, + 0x2a, 0x12, 0x5a, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, + 0x73, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, + 0x6f, 0x6b, 0x43, 0x65, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0x22, + 0x1c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x12, 0x14, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, + 0x2f, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0x12, 0x5f, 0x0a, + 0x0b, 0x4e, 0x65, 0x77, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x12, 0x17, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, + 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x1e, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x22, 0x13, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, + 0x4e, 0x65, 0x77, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x3a, 0x01, 0x2a, 0x12, 0x65, + 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, + 0x12, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, + 0x6b, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x22, 0x16, 0x2f, 0x61, 0x70, 0x69, + 0x2f, 0x76, 0x31, 0x2f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, + 0x6f, 0x6b, 0x3a, 0x01, 0x2a, 0x12, 0x6a, 0x0a, 0x0f, 0x4e, 0x65, 0x77, 0x4e, 0x6f, 0x74, 0x65, + 0x62, 0x6f, 0x6f, 0x6b, 0x43, 0x65, 0x6c, 0x6c, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x43, 0x65, 0x6c, 0x6c, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, + 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x22, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x22, 0x17, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x4e, + 0x65, 0x77, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x43, 0x65, 0x6c, 0x6c, 0x3a, 0x01, + 0x2a, 0x12, 0x63, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, + 0x43, 0x65, 0x6c, 0x6c, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, + 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x43, 0x65, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, + 0x6b, 0x43, 0x65, 0x6c, 0x6c, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x12, 0x17, 0x2f, + 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, + 0x6f, 0x6b, 0x43, 0x65, 0x6c, 0x6c, 0x12, 0x6c, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x43, 0x65, 0x6c, 0x6c, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x43, 0x65, 0x6c, - 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x22, 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x22, 0x1a, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, - 0x31, 0x2f, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, - 0x43, 0x65, 0x6c, 0x6c, 0x3a, 0x01, 0x2a, 0x12, 0x81, 0x01, 0x0a, 0x1a, 0x43, 0x72, 0x65, 0x61, + 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x43, 0x65, 0x6c, 0x6c, 0x22, 0x25, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x22, 0x1a, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x43, 0x65, 0x6c, + 0x6c, 0x3a, 0x01, 0x2a, 0x12, 0x6f, 0x0a, 0x12, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4e, 0x6f, + 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x43, 0x65, 0x6c, 0x6c, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x43, 0x65, 0x6c, 0x6c, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x25, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x22, 0x1a, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, + 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x43, 0x65, + 0x6c, 0x6c, 0x3a, 0x01, 0x2a, 0x12, 0x81, 0x01, 0x0a, 0x1a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, + 0x46, 0x69, 0x6c, 0x65, 0x12, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, + 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x2d, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x27, 0x22, 0x22, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, - 0x61, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, - 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x2d, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x27, 0x22, 0x22, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x44, 0x6f, 0x77, 0x6e, - 0x6c, 0x6f, 0x61, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x8c, 0x01, 0x0a, 0x18, - 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x41, 0x74, - 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x61, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x8c, 0x01, 0x0a, 0x18, 0x55, 0x70, + 0x6c, 0x6f, 0x61, 0x64, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x41, 0x74, 0x74, 0x61, + 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, + 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x70, 0x6c, 0x6f, 0x61, + 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x46, 0x69, 0x6c, 0x65, 0x55, 0x70, 0x6c, - 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x46, 0x69, 0x6c, 0x65, 0x55, - 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2b, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x25, 0x22, 0x20, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x55, - 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x41, 0x74, 0x74, - 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x01, 0x2a, 0x12, 0x69, 0x0a, 0x0e, 0x45, 0x78, - 0x70, 0x6f, 0x72, 0x74, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x12, 0x1c, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x45, 0x78, 0x70, - 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x22, 0x16, 0x2f, 0x61, 0x70, 0x69, - 0x2f, 0x76, 0x31, 0x2f, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, - 0x6f, 0x6b, 0x3a, 0x01, 0x2a, 0x12, 0x3c, 0x0a, 0x0c, 0x56, 0x46, 0x53, 0x47, 0x65, 0x74, 0x42, - 0x75, 0x66, 0x66, 0x65, 0x72, 0x12, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x46, - 0x53, 0x46, 0x69, 0x6c, 0x65, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x1a, 0x14, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x46, 0x53, 0x46, 0x69, 0x6c, 0x65, 0x42, 0x75, 0x66, 0x66, 0x65, - 0x72, 0x22, 0x00, 0x12, 0x38, 0x0a, 0x05, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x17, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x51, 0x4c, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, - 0x72, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x51, - 0x4c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x3b, 0x0a, - 0x0a, 0x57, 0x61, 0x74, 0x63, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x13, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x3f, 0x0a, 0x0a, 0x50, 0x75, - 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x50, 0x75, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x3a, 0x0a, 0x0a, 0x57, - 0x72, 0x69, 0x74, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x56, 0x51, 0x4c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x16, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x37, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x53, 0x75, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x61, - 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0x37, 0x0a, 0x0a, 0x53, 0x65, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x12, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x0d, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x12, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, - 0x43, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x12, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, - 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x31, 0x5a, 0x2f, 0x77, - 0x77, 0x77, 0x2e, 0x76, 0x65, 0x6c, 0x6f, 0x63, 0x69, 0x64, 0x65, 0x78, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x76, 0x65, 0x6c, 0x6f, 0x63, 0x69, 0x72, 0x61, - 0x70, 0x74, 0x6f, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6f, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2b, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x25, 0x22, 0x20, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x55, 0x70, 0x6c, + 0x6f, 0x61, 0x64, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x41, 0x74, 0x74, 0x61, 0x63, + 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x01, 0x2a, 0x12, 0x69, 0x0a, 0x0e, 0x45, 0x78, 0x70, 0x6f, + 0x72, 0x74, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x12, 0x1c, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x45, 0x78, 0x70, 0x6f, 0x72, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x22, 0x16, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, + 0x31, 0x2f, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x4e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, + 0x3a, 0x01, 0x2a, 0x12, 0x3c, 0x0a, 0x0c, 0x56, 0x46, 0x53, 0x47, 0x65, 0x74, 0x42, 0x75, 0x66, + 0x66, 0x65, 0x72, 0x12, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x46, 0x53, 0x46, + 0x69, 0x6c, 0x65, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x1a, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x56, 0x46, 0x53, 0x46, 0x69, 0x6c, 0x65, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x22, + 0x00, 0x12, 0x38, 0x0a, 0x05, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x17, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x56, 0x51, 0x4c, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x41, + 0x72, 0x67, 0x73, 0x1a, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x51, 0x4c, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x3b, 0x0a, 0x0a, 0x57, + 0x61, 0x74, 0x63, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x3f, 0x0a, 0x0a, 0x50, 0x75, 0x73, 0x68, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, + 0x75, 0x73, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x3a, 0x0a, 0x0a, 0x57, 0x72, 0x69, + 0x74, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x56, 0x51, 0x4c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x37, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x53, 0x75, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x12, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x61, 0x74, 0x61, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x37, + 0x0a, 0x0a, 0x53, 0x65, 0x74, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x12, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, + 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x12, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, + 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x05, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x12, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, + 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, + 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x31, 0x5a, 0x2f, 0x77, 0x77, 0x77, + 0x2e, 0x76, 0x65, 0x6c, 0x6f, 0x63, 0x69, 0x64, 0x65, 0x78, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, + 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x76, 0x65, 0x6c, 0x6f, 0x63, 0x69, 0x72, 0x61, 0x70, 0x74, + 0x6f, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1071,32 +1076,34 @@ var file_api_proto_goTypes = []interface{}{ (*proto2.VQLCollectorArgs)(nil), // 38: proto.VQLCollectorArgs (*proto2.VQLResponse)(nil), // 39: proto.VQLResponse (*DataRequest)(nil), // 40: proto.DataRequest - (*ListHuntsResponse)(nil), // 41: proto.ListHuntsResponse - (*GetTableResponse)(nil), // 42: proto.GetTableResponse - (*APIResponse)(nil), // 43: proto.APIResponse - (*SearchClientsResponse)(nil), // 44: proto.SearchClientsResponse - (*ApiClient)(nil), // 45: proto.ApiClient - (*ApiFlowResponse)(nil), // 46: proto.ApiFlowResponse - (*ApiGrrUser)(nil), // 47: proto.ApiGrrUser - (*Users)(nil), // 48: proto.Users - (*Favorites)(nil), // 49: proto.Favorites - (*VFSListResponse)(nil), // 50: proto.VFSListResponse - (*proto.ArtifactCollectorResponse)(nil), // 51: proto.ArtifactCollectorResponse - (*proto.VFSDownloadInfo)(nil), // 52: proto.VFSDownloadInfo - (*FlowDetails)(nil), // 53: proto.FlowDetails - (*ApiFlowRequestDetails)(nil), // 54: proto.ApiFlowRequestDetails - (*KeywordCompletions)(nil), // 55: proto.KeywordCompletions - (*proto1.ArtifactDescriptors)(nil), // 56: proto.ArtifactDescriptors - (*GetArtifactResponse)(nil), // 57: proto.GetArtifactResponse - (*LoadArtifactPackResponse)(nil), // 58: proto.LoadArtifactPackResponse - (*GetReportResponse)(nil), // 59: proto.GetReportResponse - (*ListAvailableEventResultsResponse)(nil), // 60: proto.ListAvailableEventResultsResponse - (*CreateDownloadResponse)(nil), // 61: proto.CreateDownloadResponse - (*Notebooks)(nil), // 62: proto.Notebooks - (*NotebookCell)(nil), // 63: proto.NotebookCell - (*NotebookFileUploadResponse)(nil), // 64: proto.NotebookFileUploadResponse - (*DataResponse)(nil), // 65: proto.DataResponse - (*ListChildrenResponse)(nil), // 66: proto.ListChildrenResponse + (*HealthCheckRequest)(nil), // 41: proto.HealthCheckRequest + (*ListHuntsResponse)(nil), // 42: proto.ListHuntsResponse + (*GetTableResponse)(nil), // 43: proto.GetTableResponse + (*APIResponse)(nil), // 44: proto.APIResponse + (*SearchClientsResponse)(nil), // 45: proto.SearchClientsResponse + (*ApiClient)(nil), // 46: proto.ApiClient + (*ApiFlowResponse)(nil), // 47: proto.ApiFlowResponse + (*ApiGrrUser)(nil), // 48: proto.ApiGrrUser + (*Users)(nil), // 49: proto.Users + (*Favorites)(nil), // 50: proto.Favorites + (*VFSListResponse)(nil), // 51: proto.VFSListResponse + (*proto.ArtifactCollectorResponse)(nil), // 52: proto.ArtifactCollectorResponse + (*proto.VFSDownloadInfo)(nil), // 53: proto.VFSDownloadInfo + (*FlowDetails)(nil), // 54: proto.FlowDetails + (*ApiFlowRequestDetails)(nil), // 55: proto.ApiFlowRequestDetails + (*KeywordCompletions)(nil), // 56: proto.KeywordCompletions + (*proto1.ArtifactDescriptors)(nil), // 57: proto.ArtifactDescriptors + (*GetArtifactResponse)(nil), // 58: proto.GetArtifactResponse + (*LoadArtifactPackResponse)(nil), // 59: proto.LoadArtifactPackResponse + (*GetReportResponse)(nil), // 60: proto.GetReportResponse + (*ListAvailableEventResultsResponse)(nil), // 61: proto.ListAvailableEventResultsResponse + (*CreateDownloadResponse)(nil), // 62: proto.CreateDownloadResponse + (*Notebooks)(nil), // 63: proto.Notebooks + (*NotebookCell)(nil), // 64: proto.NotebookCell + (*NotebookFileUploadResponse)(nil), // 65: proto.NotebookFileUploadResponse + (*DataResponse)(nil), // 66: proto.DataResponse + (*ListChildrenResponse)(nil), // 67: proto.ListChildrenResponse + (*HealthCheckResponse)(nil), // 68: proto.HealthCheckResponse } var file_api_proto_depIdxs = []int32{ 1, // 0: proto.ApprovalList.items:type_name -> proto.Approval @@ -1160,68 +1167,70 @@ var file_api_proto_depIdxs = []int32{ 40, // 58: proto.API.SetSubject:input_type -> proto.DataRequest 40, // 59: proto.API.DeleteSubject:input_type -> proto.DataRequest 40, // 60: proto.API.ListChildren:input_type -> proto.DataRequest - 0, // 61: proto.API.CreateHunt:output_type -> proto.StartFlowResponse - 41, // 62: proto.API.ListHunts:output_type -> proto.ListHuntsResponse - 9, // 63: proto.API.GetHunt:output_type -> proto.Hunt - 19, // 64: proto.API.ModifyHunt:output_type -> google.protobuf.Empty - 42, // 65: proto.API.GetHuntFlows:output_type -> proto.GetTableResponse - 42, // 66: proto.API.GetHuntResults:output_type -> proto.GetTableResponse - 19, // 67: proto.API.NotifyClients:output_type -> google.protobuf.Empty - 43, // 68: proto.API.LabelClients:output_type -> proto.APIResponse - 44, // 69: proto.API.ListClients:output_type -> proto.SearchClientsResponse - 45, // 70: proto.API.GetClient:output_type -> proto.ApiClient - 17, // 71: proto.API.GetClientMetadata:output_type -> proto.ClientMetadata - 19, // 72: proto.API.SetClientMetadata:output_type -> google.protobuf.Empty - 46, // 73: proto.API.GetClientFlows:output_type -> proto.ApiFlowResponse - 47, // 74: proto.API.GetUserUITraits:output_type -> proto.ApiGrrUser - 19, // 75: proto.API.SetGUIOptions:output_type -> google.protobuf.Empty - 48, // 76: proto.API.GetUsers:output_type -> proto.Users - 49, // 77: proto.API.GetUserFavorites:output_type -> proto.Favorites - 50, // 78: proto.API.VFSListDirectory:output_type -> proto.VFSListResponse - 51, // 79: proto.API.VFSRefreshDirectory:output_type -> proto.ArtifactCollectorResponse - 50, // 80: proto.API.VFSStatDirectory:output_type -> proto.VFSListResponse - 52, // 81: proto.API.VFSStatDownload:output_type -> proto.VFSDownloadInfo - 42, // 82: proto.API.GetTable:output_type -> proto.GetTableResponse - 51, // 83: proto.API.CollectArtifact:output_type -> proto.ArtifactCollectorResponse - 0, // 84: proto.API.CancelFlow:output_type -> proto.StartFlowResponse - 0, // 85: proto.API.ArchiveFlow:output_type -> proto.StartFlowResponse - 53, // 86: proto.API.GetFlowDetails:output_type -> proto.FlowDetails - 54, // 87: proto.API.GetFlowRequests:output_type -> proto.ApiFlowRequestDetails - 55, // 88: proto.API.GetKeywordCompletions:output_type -> proto.KeywordCompletions - 56, // 89: proto.API.GetArtifacts:output_type -> proto.ArtifactDescriptors - 57, // 90: proto.API.GetArtifactFile:output_type -> proto.GetArtifactResponse - 43, // 91: proto.API.SetArtifactFile:output_type -> proto.APIResponse - 58, // 92: proto.API.LoadArtifactPack:output_type -> proto.LoadArtifactPackResponse - 28, // 93: proto.API.GetToolInfo:output_type -> proto.Tool - 28, // 94: proto.API.SetToolInfo:output_type -> proto.Tool - 59, // 95: proto.API.GetReport:output_type -> proto.GetReportResponse - 24, // 96: proto.API.GetServerMonitoringState:output_type -> proto.ArtifactCollectorArgs - 24, // 97: proto.API.SetServerMonitoringState:output_type -> proto.ArtifactCollectorArgs - 31, // 98: proto.API.GetClientMonitoringState:output_type -> proto.ClientEventTable - 19, // 99: proto.API.SetClientMonitoringState:output_type -> google.protobuf.Empty - 60, // 100: proto.API.ListAvailableEventResults:output_type -> proto.ListAvailableEventResultsResponse - 61, // 101: proto.API.CreateDownloadFile:output_type -> proto.CreateDownloadResponse - 62, // 102: proto.API.GetNotebooks:output_type -> proto.Notebooks - 35, // 103: proto.API.NewNotebook:output_type -> proto.NotebookMetadata - 35, // 104: proto.API.UpdateNotebook:output_type -> proto.NotebookMetadata - 35, // 105: proto.API.NewNotebookCell:output_type -> proto.NotebookMetadata - 63, // 106: proto.API.GetNotebookCell:output_type -> proto.NotebookCell - 63, // 107: proto.API.UpdateNotebookCell:output_type -> proto.NotebookCell - 19, // 108: proto.API.CancelNotebookCell:output_type -> google.protobuf.Empty - 19, // 109: proto.API.CreateNotebookDownloadFile:output_type -> google.protobuf.Empty - 64, // 110: proto.API.UploadNotebookAttachment:output_type -> proto.NotebookFileUploadResponse - 19, // 111: proto.API.ExportNotebook:output_type -> google.protobuf.Empty - 4, // 112: proto.API.VFSGetBuffer:output_type -> proto.VFSFileBuffer - 39, // 113: proto.API.Query:output_type -> proto.VQLResponse - 7, // 114: proto.API.WatchEvent:output_type -> proto.EventResponse - 19, // 115: proto.API.PushEvents:output_type -> google.protobuf.Empty - 19, // 116: proto.API.WriteEvent:output_type -> google.protobuf.Empty - 65, // 117: proto.API.GetSubject:output_type -> proto.DataResponse - 65, // 118: proto.API.SetSubject:output_type -> proto.DataResponse - 19, // 119: proto.API.DeleteSubject:output_type -> google.protobuf.Empty - 66, // 120: proto.API.ListChildren:output_type -> proto.ListChildrenResponse - 61, // [61:121] is the sub-list for method output_type - 1, // [1:61] is the sub-list for method input_type + 41, // 61: proto.API.Check:input_type -> proto.HealthCheckRequest + 0, // 62: proto.API.CreateHunt:output_type -> proto.StartFlowResponse + 42, // 63: proto.API.ListHunts:output_type -> proto.ListHuntsResponse + 9, // 64: proto.API.GetHunt:output_type -> proto.Hunt + 19, // 65: proto.API.ModifyHunt:output_type -> google.protobuf.Empty + 43, // 66: proto.API.GetHuntFlows:output_type -> proto.GetTableResponse + 43, // 67: proto.API.GetHuntResults:output_type -> proto.GetTableResponse + 19, // 68: proto.API.NotifyClients:output_type -> google.protobuf.Empty + 44, // 69: proto.API.LabelClients:output_type -> proto.APIResponse + 45, // 70: proto.API.ListClients:output_type -> proto.SearchClientsResponse + 46, // 71: proto.API.GetClient:output_type -> proto.ApiClient + 17, // 72: proto.API.GetClientMetadata:output_type -> proto.ClientMetadata + 19, // 73: proto.API.SetClientMetadata:output_type -> google.protobuf.Empty + 47, // 74: proto.API.GetClientFlows:output_type -> proto.ApiFlowResponse + 48, // 75: proto.API.GetUserUITraits:output_type -> proto.ApiGrrUser + 19, // 76: proto.API.SetGUIOptions:output_type -> google.protobuf.Empty + 49, // 77: proto.API.GetUsers:output_type -> proto.Users + 50, // 78: proto.API.GetUserFavorites:output_type -> proto.Favorites + 51, // 79: proto.API.VFSListDirectory:output_type -> proto.VFSListResponse + 52, // 80: proto.API.VFSRefreshDirectory:output_type -> proto.ArtifactCollectorResponse + 51, // 81: proto.API.VFSStatDirectory:output_type -> proto.VFSListResponse + 53, // 82: proto.API.VFSStatDownload:output_type -> proto.VFSDownloadInfo + 43, // 83: proto.API.GetTable:output_type -> proto.GetTableResponse + 52, // 84: proto.API.CollectArtifact:output_type -> proto.ArtifactCollectorResponse + 0, // 85: proto.API.CancelFlow:output_type -> proto.StartFlowResponse + 0, // 86: proto.API.ArchiveFlow:output_type -> proto.StartFlowResponse + 54, // 87: proto.API.GetFlowDetails:output_type -> proto.FlowDetails + 55, // 88: proto.API.GetFlowRequests:output_type -> proto.ApiFlowRequestDetails + 56, // 89: proto.API.GetKeywordCompletions:output_type -> proto.KeywordCompletions + 57, // 90: proto.API.GetArtifacts:output_type -> proto.ArtifactDescriptors + 58, // 91: proto.API.GetArtifactFile:output_type -> proto.GetArtifactResponse + 44, // 92: proto.API.SetArtifactFile:output_type -> proto.APIResponse + 59, // 93: proto.API.LoadArtifactPack:output_type -> proto.LoadArtifactPackResponse + 28, // 94: proto.API.GetToolInfo:output_type -> proto.Tool + 28, // 95: proto.API.SetToolInfo:output_type -> proto.Tool + 60, // 96: proto.API.GetReport:output_type -> proto.GetReportResponse + 24, // 97: proto.API.GetServerMonitoringState:output_type -> proto.ArtifactCollectorArgs + 24, // 98: proto.API.SetServerMonitoringState:output_type -> proto.ArtifactCollectorArgs + 31, // 99: proto.API.GetClientMonitoringState:output_type -> proto.ClientEventTable + 19, // 100: proto.API.SetClientMonitoringState:output_type -> google.protobuf.Empty + 61, // 101: proto.API.ListAvailableEventResults:output_type -> proto.ListAvailableEventResultsResponse + 62, // 102: proto.API.CreateDownloadFile:output_type -> proto.CreateDownloadResponse + 63, // 103: proto.API.GetNotebooks:output_type -> proto.Notebooks + 35, // 104: proto.API.NewNotebook:output_type -> proto.NotebookMetadata + 35, // 105: proto.API.UpdateNotebook:output_type -> proto.NotebookMetadata + 35, // 106: proto.API.NewNotebookCell:output_type -> proto.NotebookMetadata + 64, // 107: proto.API.GetNotebookCell:output_type -> proto.NotebookCell + 64, // 108: proto.API.UpdateNotebookCell:output_type -> proto.NotebookCell + 19, // 109: proto.API.CancelNotebookCell:output_type -> google.protobuf.Empty + 19, // 110: proto.API.CreateNotebookDownloadFile:output_type -> google.protobuf.Empty + 65, // 111: proto.API.UploadNotebookAttachment:output_type -> proto.NotebookFileUploadResponse + 19, // 112: proto.API.ExportNotebook:output_type -> google.protobuf.Empty + 4, // 113: proto.API.VFSGetBuffer:output_type -> proto.VFSFileBuffer + 39, // 114: proto.API.Query:output_type -> proto.VQLResponse + 7, // 115: proto.API.WatchEvent:output_type -> proto.EventResponse + 19, // 116: proto.API.PushEvents:output_type -> google.protobuf.Empty + 19, // 117: proto.API.WriteEvent:output_type -> google.protobuf.Empty + 66, // 118: proto.API.GetSubject:output_type -> proto.DataResponse + 66, // 119: proto.API.SetSubject:output_type -> proto.DataResponse + 19, // 120: proto.API.DeleteSubject:output_type -> google.protobuf.Empty + 67, // 121: proto.API.ListChildren:output_type -> proto.ListChildrenResponse + 68, // 122: proto.API.Check:output_type -> proto.HealthCheckResponse + 62, // [62:123] is the sub-list for method output_type + 1, // [1:62] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name 1, // [1:1] is the sub-list for extension extendee 0, // [0:1] is the sub-list for field type_name @@ -1235,6 +1244,7 @@ func file_api_proto_init() { file_artifacts_proto_init() file_clients_proto_init() file_datastore_proto_init() + file_health_proto_init() file_hunts_proto_init() file_flows_proto_init() file_notebooks_proto_init() diff --git a/api/proto/api.pb.gw.go b/api/proto/api.pb.gw.go index b27850128d1..746ecf7c330 100644 --- a/api/proto/api.pb.gw.go +++ b/api/proto/api.pb.gw.go @@ -22,8 +22,8 @@ import ( "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" - proto_6 "www.velocidex.com/golang/velociraptor/artifacts/proto" - proto_2 "www.velocidex.com/golang/velociraptor/flows/proto" + proto_1 "www.velocidex.com/golang/velociraptor/artifacts/proto" + proto_3 "www.velocidex.com/golang/velociraptor/flows/proto" ) // Suppress "imported and not used" errors @@ -983,7 +983,7 @@ func local_request_API_GetTable_0(ctx context.Context, marshaler runtime.Marshal } func request_API_CollectArtifact_0(ctx context.Context, marshaler runtime.Marshaler, client APIClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq proto_2.ArtifactCollectorArgs + var protoReq proto_3.ArtifactCollectorArgs var metadata runtime.ServerMetadata newReader, berr := utilities.IOReaderFactory(req.Body) @@ -1000,7 +1000,7 @@ func request_API_CollectArtifact_0(ctx context.Context, marshaler runtime.Marsha } func local_request_API_CollectArtifact_0(ctx context.Context, marshaler runtime.Marshaler, server APIServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq proto_2.ArtifactCollectorArgs + var protoReq proto_3.ArtifactCollectorArgs var metadata runtime.ServerMetadata newReader, berr := utilities.IOReaderFactory(req.Body) @@ -1317,7 +1317,7 @@ var ( ) func request_API_GetToolInfo_0(ctx context.Context, marshaler runtime.Marshaler, client APIClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq proto_6.Tool + var protoReq proto_1.Tool var metadata runtime.ServerMetadata if err := req.ParseForm(); err != nil { @@ -1333,7 +1333,7 @@ func request_API_GetToolInfo_0(ctx context.Context, marshaler runtime.Marshaler, } func local_request_API_GetToolInfo_0(ctx context.Context, marshaler runtime.Marshaler, server APIServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq proto_6.Tool + var protoReq proto_1.Tool var metadata runtime.ServerMetadata if err := req.ParseForm(); err != nil { @@ -1349,7 +1349,7 @@ func local_request_API_GetToolInfo_0(ctx context.Context, marshaler runtime.Mars } func request_API_SetToolInfo_0(ctx context.Context, marshaler runtime.Marshaler, client APIClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq proto_6.Tool + var protoReq proto_1.Tool var metadata runtime.ServerMetadata newReader, berr := utilities.IOReaderFactory(req.Body) @@ -1366,7 +1366,7 @@ func request_API_SetToolInfo_0(ctx context.Context, marshaler runtime.Marshaler, } func local_request_API_SetToolInfo_0(ctx context.Context, marshaler runtime.Marshaler, server APIServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq proto_6.Tool + var protoReq proto_1.Tool var metadata runtime.ServerMetadata newReader, berr := utilities.IOReaderFactory(req.Body) @@ -1435,7 +1435,7 @@ func local_request_API_GetServerMonitoringState_0(ctx context.Context, marshaler } func request_API_SetServerMonitoringState_0(ctx context.Context, marshaler runtime.Marshaler, client APIClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq proto_2.ArtifactCollectorArgs + var protoReq proto_3.ArtifactCollectorArgs var metadata runtime.ServerMetadata newReader, berr := utilities.IOReaderFactory(req.Body) @@ -1452,7 +1452,7 @@ func request_API_SetServerMonitoringState_0(ctx context.Context, marshaler runti } func local_request_API_SetServerMonitoringState_0(ctx context.Context, marshaler runtime.Marshaler, server APIServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq proto_2.ArtifactCollectorArgs + var protoReq proto_3.ArtifactCollectorArgs var metadata runtime.ServerMetadata newReader, berr := utilities.IOReaderFactory(req.Body) @@ -1473,7 +1473,7 @@ var ( ) func request_API_GetClientMonitoringState_0(ctx context.Context, marshaler runtime.Marshaler, client APIClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq proto_2.GetClientMonitoringStateRequest + var protoReq proto_3.GetClientMonitoringStateRequest var metadata runtime.ServerMetadata if err := req.ParseForm(); err != nil { @@ -1489,7 +1489,7 @@ func request_API_GetClientMonitoringState_0(ctx context.Context, marshaler runti } func local_request_API_GetClientMonitoringState_0(ctx context.Context, marshaler runtime.Marshaler, server APIServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq proto_2.GetClientMonitoringStateRequest + var protoReq proto_3.GetClientMonitoringStateRequest var metadata runtime.ServerMetadata if err := req.ParseForm(); err != nil { @@ -1505,7 +1505,7 @@ func local_request_API_GetClientMonitoringState_0(ctx context.Context, marshaler } func request_API_SetClientMonitoringState_0(ctx context.Context, marshaler runtime.Marshaler, client APIClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq proto_2.ClientEventTable + var protoReq proto_3.ClientEventTable var metadata runtime.ServerMetadata newReader, berr := utilities.IOReaderFactory(req.Body) @@ -1522,7 +1522,7 @@ func request_API_SetClientMonitoringState_0(ctx context.Context, marshaler runti } func local_request_API_SetClientMonitoringState_0(ctx context.Context, marshaler runtime.Marshaler, server APIServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq proto_2.ClientEventTable + var protoReq proto_3.ClientEventTable var metadata runtime.ServerMetadata newReader, berr := utilities.IOReaderFactory(req.Body) diff --git a/api/proto/api.proto b/api/proto/api.proto index a80302aecc3..b439a121d32 100644 --- a/api/proto/api.proto +++ b/api/proto/api.proto @@ -12,6 +12,7 @@ import "google/protobuf/empty.proto"; import "artifacts.proto"; import "clients.proto"; import "datastore.proto"; +import "health.proto"; import "hunts.proto"; import "flows.proto"; import "notebooks.proto"; @@ -480,4 +481,7 @@ service API { rpc SetSubject(DataRequest) returns (DataResponse) {} rpc DeleteSubject(DataRequest) returns (google.protobuf.Empty) {} rpc ListChildren(DataRequest) returns (ListChildrenResponse) {} + + // Health check protocol as in https://github.com/grpc/grpc/blob/master/doc/health-checking.md + rpc Check(HealthCheckRequest) returns (HealthCheckResponse); } \ No newline at end of file diff --git a/api/proto/api_grpc.pb.go b/api/proto/api_grpc.pb.go index b0b1793e88d..36554b4b650 100644 --- a/api/proto/api_grpc.pb.go +++ b/api/proto/api_grpc.pb.go @@ -107,6 +107,8 @@ type APIClient interface { SetSubject(ctx context.Context, in *DataRequest, opts ...grpc.CallOption) (*DataResponse, error) DeleteSubject(ctx context.Context, in *DataRequest, opts ...grpc.CallOption) (*empty.Empty, error) ListChildren(ctx context.Context, in *DataRequest, opts ...grpc.CallOption) (*ListChildrenResponse, error) + // Health check protocol as in https://github.com/grpc/grpc/blob/master/doc/health-checking.md + Check(ctx context.Context, in *HealthCheckRequest, opts ...grpc.CallOption) (*HealthCheckResponse, error) } type aPIClient struct { @@ -703,6 +705,15 @@ func (c *aPIClient) ListChildren(ctx context.Context, in *DataRequest, opts ...g return out, nil } +func (c *aPIClient) Check(ctx context.Context, in *HealthCheckRequest, opts ...grpc.CallOption) (*HealthCheckResponse, error) { + out := new(HealthCheckResponse) + err := c.cc.Invoke(ctx, "/proto.API/Check", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // APIServer is the server API for API service. // All implementations must embed UnimplementedAPIServer // for forward compatibility @@ -792,6 +803,8 @@ type APIServer interface { SetSubject(context.Context, *DataRequest) (*DataResponse, error) DeleteSubject(context.Context, *DataRequest) (*empty.Empty, error) ListChildren(context.Context, *DataRequest) (*ListChildrenResponse, error) + // Health check protocol as in https://github.com/grpc/grpc/blob/master/doc/health-checking.md + Check(context.Context, *HealthCheckRequest) (*HealthCheckResponse, error) mustEmbedUnimplementedAPIServer() } @@ -979,6 +992,9 @@ func (UnimplementedAPIServer) DeleteSubject(context.Context, *DataRequest) (*emp func (UnimplementedAPIServer) ListChildren(context.Context, *DataRequest) (*ListChildrenResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListChildren not implemented") } +func (UnimplementedAPIServer) Check(context.Context, *HealthCheckRequest) (*HealthCheckResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Check not implemented") +} func (UnimplementedAPIServer) mustEmbedUnimplementedAPIServer() {} // UnsafeAPIServer may be embedded to opt out of forward compatibility for this service. @@ -2078,6 +2094,24 @@ func _API_ListChildren_Handler(srv interface{}, ctx context.Context, dec func(in return interceptor(ctx, in, info, handler) } +func _API_Check_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HealthCheckRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(APIServer).Check(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proto.API/Check", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(APIServer).Check(ctx, req.(*HealthCheckRequest)) + } + return interceptor(ctx, in, info, handler) +} + // API_ServiceDesc is the grpc.ServiceDesc for API service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -2317,6 +2351,10 @@ var API_ServiceDesc = grpc.ServiceDesc{ MethodName: "ListChildren", Handler: _API_ListChildren_Handler, }, + { + MethodName: "Check", + Handler: _API_Check_Handler, + }, }, Streams: []grpc.StreamDesc{ { diff --git a/api/proto/datastore.pb.go b/api/proto/datastore.pb.go index 3b07536585d..56b539772c3 100644 --- a/api/proto/datastore.pb.go +++ b/api/proto/datastore.pb.go @@ -28,6 +28,7 @@ type DSPathSpec struct { Components []string `protobuf:"bytes,1,rep,name=components,proto3" json:"components,omitempty"` PathType int64 `protobuf:"varint,2,opt,name=path_type,json=pathType,proto3" json:"path_type,omitempty"` IsDir bool `protobuf:"varint,3,opt,name=is_dir,json=isDir,proto3" json:"is_dir,omitempty"` + Tag string `protobuf:"bytes,4,opt,name=tag,proto3" json:"tag,omitempty"` } func (x *DSPathSpec) Reset() { @@ -83,6 +84,13 @@ func (x *DSPathSpec) GetIsDir() bool { return false } +func (x *DSPathSpec) GetTag() string { + if x != nil { + return x.Tag + } + return "" +} + type DataRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -90,6 +98,9 @@ type DataRequest struct { Pathspec *DSPathSpec `protobuf:"bytes,1,opt,name=pathspec,proto3" json:"pathspec,omitempty"` Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + // If set the request will block until the data is committed to + // disk. + Sync bool `protobuf:"varint,3,opt,name=sync,proto3" json:"sync,omitempty"` } func (x *DataRequest) Reset() { @@ -138,6 +149,13 @@ func (x *DataRequest) GetData() []byte { return nil } +func (x *DataRequest) GetSync() bool { + if x != nil { + return x.Sync + } + return false +} + type DataResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -236,29 +254,31 @@ var File_datastore_proto protoreflect.FileDescriptor var file_datastore_proto_rawDesc = []byte{ 0x0a, 0x0f, 0x64, 0x61, 0x74, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x12, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x60, 0x0a, 0x0a, 0x44, 0x53, 0x50, 0x61, + 0x6f, 0x12, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x72, 0x0a, 0x0a, 0x44, 0x53, 0x50, 0x61, 0x74, 0x68, 0x53, 0x70, 0x65, 0x63, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, 0x61, 0x74, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x69, 0x73, 0x5f, 0x64, 0x69, 0x72, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x05, 0x69, 0x73, 0x44, 0x69, 0x72, 0x22, 0x50, 0x0a, 0x0b, 0x44, 0x61, - 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2d, 0x0a, 0x08, 0x70, 0x61, 0x74, - 0x68, 0x73, 0x70, 0x65, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x53, 0x50, 0x61, 0x74, 0x68, 0x53, 0x70, 0x65, 0x63, 0x52, 0x08, - 0x70, 0x61, 0x74, 0x68, 0x73, 0x70, 0x65, 0x63, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x22, 0x0a, 0x0c, - 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, - 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, - 0x22, 0x45, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x08, 0x63, 0x68, 0x69, 0x6c, - 0x64, 0x72, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x44, 0x53, 0x50, 0x61, 0x74, 0x68, 0x53, 0x70, 0x65, 0x63, 0x52, 0x08, 0x63, - 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x42, 0x31, 0x5a, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, - 0x65, 0x6c, 0x6f, 0x63, 0x69, 0x64, 0x65, 0x78, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c, - 0x61, 0x6e, 0x67, 0x2f, 0x76, 0x65, 0x6c, 0x6f, 0x63, 0x69, 0x72, 0x61, 0x70, 0x74, 0x6f, 0x72, - 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x01, 0x28, 0x08, 0x52, 0x05, 0x69, 0x73, 0x44, 0x69, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, + 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x22, 0x64, 0x0a, 0x0b, + 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2d, 0x0a, 0x08, 0x70, + 0x61, 0x74, 0x68, 0x73, 0x70, 0x65, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x53, 0x50, 0x61, 0x74, 0x68, 0x53, 0x70, 0x65, 0x63, + 0x52, 0x08, 0x70, 0x61, 0x74, 0x68, 0x73, 0x70, 0x65, 0x63, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, + 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x12, + 0x0a, 0x04, 0x73, 0x79, 0x6e, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x73, 0x79, + 0x6e, 0x63, 0x22, 0x22, 0x0a, 0x0c, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x45, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, + 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, + 0x0a, 0x08, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x53, 0x50, 0x61, 0x74, 0x68, 0x53, + 0x70, 0x65, 0x63, 0x52, 0x08, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x42, 0x31, 0x5a, + 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x6c, 0x6f, 0x63, 0x69, 0x64, 0x65, 0x78, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x76, 0x65, 0x6c, 0x6f, 0x63, 0x69, + 0x72, 0x61, 0x70, 0x74, 0x6f, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/api/proto/datastore.proto b/api/proto/datastore.proto index c9b22a5f714..c129d81635a 100644 --- a/api/proto/datastore.proto +++ b/api/proto/datastore.proto @@ -9,11 +9,16 @@ message DSPathSpec { int64 path_type = 2; bool is_dir = 3; + string tag = 4; } message DataRequest { DSPathSpec pathspec = 1; bytes data = 2; + + // If set the request will block until the data is committed to + // disk. + bool sync = 3; } message DataResponse { diff --git a/api/proto/health.pb.go b/api/proto/health.pb.go new file mode 100644 index 00000000000..70a9a6e80e2 --- /dev/null +++ b/api/proto/health.pb.go @@ -0,0 +1,267 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0-devel +// protoc v3.12.4 +// source: health.proto + +package proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +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 HealthCheckResponse_ServingStatus int32 + +const ( + HealthCheckResponse_UNKNOWN HealthCheckResponse_ServingStatus = 0 + HealthCheckResponse_SERVING HealthCheckResponse_ServingStatus = 1 + HealthCheckResponse_NOT_SERVING HealthCheckResponse_ServingStatus = 2 +) + +// Enum value maps for HealthCheckResponse_ServingStatus. +var ( + HealthCheckResponse_ServingStatus_name = map[int32]string{ + 0: "UNKNOWN", + 1: "SERVING", + 2: "NOT_SERVING", + } + HealthCheckResponse_ServingStatus_value = map[string]int32{ + "UNKNOWN": 0, + "SERVING": 1, + "NOT_SERVING": 2, + } +) + +func (x HealthCheckResponse_ServingStatus) Enum() *HealthCheckResponse_ServingStatus { + p := new(HealthCheckResponse_ServingStatus) + *p = x + return p +} + +func (x HealthCheckResponse_ServingStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (HealthCheckResponse_ServingStatus) Descriptor() protoreflect.EnumDescriptor { + return file_health_proto_enumTypes[0].Descriptor() +} + +func (HealthCheckResponse_ServingStatus) Type() protoreflect.EnumType { + return &file_health_proto_enumTypes[0] +} + +func (x HealthCheckResponse_ServingStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use HealthCheckResponse_ServingStatus.Descriptor instead. +func (HealthCheckResponse_ServingStatus) EnumDescriptor() ([]byte, []int) { + return file_health_proto_rawDescGZIP(), []int{1, 0} +} + +type HealthCheckRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Service string `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"` +} + +func (x *HealthCheckRequest) Reset() { + *x = HealthCheckRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_health_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HealthCheckRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HealthCheckRequest) ProtoMessage() {} + +func (x *HealthCheckRequest) ProtoReflect() protoreflect.Message { + mi := &file_health_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HealthCheckRequest.ProtoReflect.Descriptor instead. +func (*HealthCheckRequest) Descriptor() ([]byte, []int) { + return file_health_proto_rawDescGZIP(), []int{0} +} + +func (x *HealthCheckRequest) GetService() string { + if x != nil { + return x.Service + } + return "" +} + +type HealthCheckResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Status HealthCheckResponse_ServingStatus `protobuf:"varint,1,opt,name=status,proto3,enum=proto.HealthCheckResponse_ServingStatus" json:"status,omitempty"` +} + +func (x *HealthCheckResponse) Reset() { + *x = HealthCheckResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_health_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HealthCheckResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HealthCheckResponse) ProtoMessage() {} + +func (x *HealthCheckResponse) ProtoReflect() protoreflect.Message { + mi := &file_health_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HealthCheckResponse.ProtoReflect.Descriptor instead. +func (*HealthCheckResponse) Descriptor() ([]byte, []int) { + return file_health_proto_rawDescGZIP(), []int{1} +} + +func (x *HealthCheckResponse) GetStatus() HealthCheckResponse_ServingStatus { + if x != nil { + return x.Status + } + return HealthCheckResponse_UNKNOWN +} + +var File_health_proto protoreflect.FileDescriptor + +var file_health_proto_rawDesc = []byte{ + 0x0a, 0x0c, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x2e, 0x0a, 0x12, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, + 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, 0x93, 0x01, 0x0a, 0x13, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, + 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, + 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, + 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, + 0x3a, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, + 0x07, 0x53, 0x45, 0x52, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x4e, 0x4f, + 0x54, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x42, 0x31, 0x5a, 0x2f, 0x77, + 0x77, 0x77, 0x2e, 0x76, 0x65, 0x6c, 0x6f, 0x63, 0x69, 0x64, 0x65, 0x78, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x76, 0x65, 0x6c, 0x6f, 0x63, 0x69, 0x72, 0x61, + 0x70, 0x74, 0x6f, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_health_proto_rawDescOnce sync.Once + file_health_proto_rawDescData = file_health_proto_rawDesc +) + +func file_health_proto_rawDescGZIP() []byte { + file_health_proto_rawDescOnce.Do(func() { + file_health_proto_rawDescData = protoimpl.X.CompressGZIP(file_health_proto_rawDescData) + }) + return file_health_proto_rawDescData +} + +var file_health_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_health_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_health_proto_goTypes = []interface{}{ + (HealthCheckResponse_ServingStatus)(0), // 0: proto.HealthCheckResponse.ServingStatus + (*HealthCheckRequest)(nil), // 1: proto.HealthCheckRequest + (*HealthCheckResponse)(nil), // 2: proto.HealthCheckResponse +} +var file_health_proto_depIdxs = []int32{ + 0, // 0: proto.HealthCheckResponse.status:type_name -> proto.HealthCheckResponse.ServingStatus + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_health_proto_init() } +func file_health_proto_init() { + if File_health_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_health_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HealthCheckRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_health_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HealthCheckResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_health_proto_rawDesc, + NumEnums: 1, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_health_proto_goTypes, + DependencyIndexes: file_health_proto_depIdxs, + EnumInfos: file_health_proto_enumTypes, + MessageInfos: file_health_proto_msgTypes, + }.Build() + File_health_proto = out.File + file_health_proto_rawDesc = nil + file_health_proto_goTypes = nil + file_health_proto_depIdxs = nil +} diff --git a/api/proto/health.proto b/api/proto/health.proto new file mode 100644 index 00000000000..66e71403752 --- /dev/null +++ b/api/proto/health.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +package proto; + +option go_package = "www.velocidex.com/golang/velociraptor/api/proto"; + +message HealthCheckRequest { + string service = 1; +} + +message HealthCheckResponse { + enum ServingStatus { + UNKNOWN = 0; + SERVING = 1; + NOT_SERVING = 2; + } + ServingStatus status = 1; +} diff --git a/api/proto/hunts.pb.go b/api/proto/hunts.pb.go index c893f3b31df..696e7ad16fc 100644 --- a/api/proto/hunts.pb.go +++ b/api/proto/hunts.pb.go @@ -404,6 +404,7 @@ type Hunt struct { unknownFields protoimpl.UnknownFields HuntId string `protobuf:"bytes,1,opt,name=hunt_id,json=huntId,proto3" json:"hunt_id,omitempty"` + Version int64 `protobuf:"varint,20,opt,name=version,proto3" json:"version,omitempty"` CreateTime uint64 `protobuf:"varint,2,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty"` Creator string `protobuf:"bytes,12,opt,name=creator,proto3" json:"creator,omitempty"` StartTime uint64 `protobuf:"varint,21,opt,name=start_time,json=startTime,proto3" json:"start_time,omitempty"` @@ -457,6 +458,13 @@ func (x *Hunt) GetHuntId() string { return "" } +func (x *Hunt) GetVersion() int64 { + if x != nil { + return x.Version + } + return 0 +} + func (x *Hunt) GetCreateTime() uint64 { if x != nil { return x.CreateTime @@ -1008,141 +1016,142 @@ var file_hunts_proto_rawDesc = []byte{ 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x52, 0x12, 0x61, 0x76, 0x61, 0x69, 0x6c, - 0x61, 0x62, 0x6c, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x22, 0xf6, 0x0a, + 0x61, 0x62, 0x6c, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x22, 0x90, 0x0b, 0x0a, 0x04, 0x48, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x07, 0x68, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0f, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x09, 0x22, 0x07, 0x48, 0x75, 0x6e, 0x74, 0x20, 0x49, 0x44, 0x52, 0x06, 0x68, 0x75, 0x6e, 0x74, 0x49, 0x64, - 0x12, 0x60, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x3f, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x39, 0x0a, 0x0b, 0x52, - 0x44, 0x46, 0x44, 0x61, 0x74, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x1b, 0x57, 0x68, 0x65, 0x6e, - 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x68, 0x75, 0x6e, 0x74, 0x20, 0x77, 0x61, 0x73, 0x20, 0x63, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x2e, 0x22, 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, - 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x0c, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x1d, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x17, 0x12, 0x15, 0x57, 0x68, 0x6f, - 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x68, 0x75, 0x6e, - 0x74, 0x3f, 0x52, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x64, 0x0a, 0x0a, 0x73, - 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x15, 0x20, 0x01, 0x28, 0x04, 0x42, - 0x45, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x3f, 0x0a, 0x0b, 0x52, 0x44, 0x46, 0x44, 0x61, 0x74, 0x65, - 0x74, 0x69, 0x6d, 0x65, 0x12, 0x24, 0x57, 0x68, 0x65, 0x6e, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, - 0x68, 0x75, 0x6e, 0x74, 0x20, 0x77, 0x61, 0x73, 0x20, 0x61, 0x63, 0x74, 0x75, 0x61, 0x6c, 0x6c, - 0x79, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x2e, 0x22, 0x0a, 0x53, 0x74, 0x61, 0x72, - 0x74, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, - 0x65, 0x12, 0x57, 0x0a, 0x07, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, - 0x28, 0x04, 0x42, 0x3d, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x37, 0x0a, 0x0b, 0x52, 0x44, 0x46, 0x44, - 0x61, 0x74, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x1b, 0x57, 0x68, 0x65, 0x6e, 0x20, 0x64, 0x6f, - 0x65, 0x73, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x68, 0x75, 0x6e, 0x74, 0x20, 0x65, 0x78, 0x70, - 0x69, 0x72, 0x65, 0x3f, 0x22, 0x0b, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x20, 0x54, 0x69, 0x6d, - 0x65, 0x52, 0x07, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x12, 0x45, 0x0a, 0x10, 0x68, 0x75, - 0x6e, 0x74, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0b, - 0x20, 0x01, 0x28, 0x09, 0x42, 0x1a, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x14, 0x12, 0x12, 0x48, 0x75, - 0x6e, 0x74, 0x27, 0x73, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x0f, 0x68, 0x75, 0x6e, 0x74, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x88, 0x01, 0x0a, 0x0d, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, - 0x74, 0x6f, 0x72, 0x41, 0x72, 0x67, 0x73, 0x42, 0x45, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x3f, 0x12, - 0x3d, 0x4c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x63, 0x6f, 0x6c, - 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, - 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x69, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, - 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x73, 0x20, 0x74, 0x72, 0x75, 0x65, 0x52, 0x0c, - 0x73, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x8e, 0x01, 0x0a, - 0x09, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x75, 0x6e, 0x74, 0x43, 0x6f, 0x6e, - 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x5a, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x54, 0x12, 0x42, - 0x54, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x68, - 0x61, 0x74, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x73, 0x61, 0x74, 0x69, 0x73, - 0x66, 0x69, 0x65, 0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x68, 0x75, 0x6e, - 0x74, 0x20, 0x74, 0x6f, 0x20, 0x62, 0x65, 0x20, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, - 0x64, 0x2e, 0x22, 0x0e, 0x48, 0x75, 0x6e, 0x74, 0x20, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x59, 0x0a, - 0x0c, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x04, 0x42, 0x36, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x30, 0x12, 0x2e, 0x54, 0x6f, 0x74, - 0x61, 0x6c, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x20, 0x6f, 0x66, 0x20, 0x63, 0x6c, 0x69, - 0x65, 0x6e, 0x74, 0x73, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x68, 0x75, 0x6e, 0x74, 0x20, 0x77, - 0x69, 0x6c, 0x6c, 0x20, 0x72, 0x75, 0x6e, 0x20, 0x6f, 0x6e, 0x2e, 0x52, 0x0b, 0x63, 0x6c, 0x69, - 0x65, 0x6e, 0x74, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x26, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, - 0x73, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x48, 0x75, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, - 0x12, 0x4d, 0x0a, 0x09, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x18, 0x11, 0x20, - 0x03, 0x28, 0x09, 0x42, 0x2f, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x29, 0x12, 0x27, 0x41, 0x20, 0x6c, - 0x69, 0x73, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, - 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x68, 0x75, 0x6e, 0x74, 0x20, 0x70, 0x72, 0x6f, 0x64, 0x75, - 0x63, 0x65, 0x73, 0x2e, 0x52, 0x09, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x12, - 0x61, 0x0a, 0x10, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x5f, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x73, 0x18, 0x13, 0x20, 0x03, 0x28, 0x09, 0x42, 0x36, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, - 0x30, 0x12, 0x2e, 0x41, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x61, 0x72, 0x74, - 0x69, 0x66, 0x61, 0x63, 0x74, 0x20, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x20, 0x74, 0x68, - 0x69, 0x73, 0x20, 0x68, 0x75, 0x6e, 0x74, 0x20, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, 0x73, - 0x2e, 0x52, 0x0f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x73, 0x12, 0x71, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x75, 0x6e, 0x74, 0x2e, 0x53, - 0x74, 0x61, 0x74, 0x65, 0x42, 0x48, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x42, 0x12, 0x40, 0x54, 0x68, - 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x68, 0x75, 0x6e, 0x74, 0x2e, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, - 0x65, 0x6c, 0x64, 0x20, 0x69, 0x73, 0x20, 0x6d, 0x61, 0x6e, 0x75, 0x70, 0x75, 0x6c, 0x61, 0x74, - 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x55, 0x49, 0x2e, 0x52, 0x05, - 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0xde, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, - 0x09, 0x0a, 0x05, 0x55, 0x4e, 0x53, 0x45, 0x54, 0x10, 0x00, 0x12, 0x48, 0x0a, 0x06, 0x50, 0x41, - 0x55, 0x53, 0x45, 0x44, 0x10, 0x01, 0x1a, 0x3c, 0xea, 0xb9, 0xcb, 0xb9, 0x01, 0x36, 0x48, 0x75, - 0x6e, 0x74, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x73, 0x63, 0x68, 0x65, - 0x64, 0x75, 0x6c, 0x65, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, - 0x20, 0x62, 0x75, 0x74, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x62, 0x65, 0x20, 0x73, 0x74, 0x61, 0x72, - 0x74, 0x65, 0x64, 0x2e, 0x12, 0x2d, 0x0a, 0x07, 0x52, 0x55, 0x4e, 0x4e, 0x49, 0x4e, 0x47, 0x10, - 0x02, 0x1a, 0x20, 0xea, 0xb9, 0xcb, 0xb9, 0x01, 0x1a, 0x48, 0x75, 0x6e, 0x74, 0x20, 0x69, 0x73, - 0x20, 0x72, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x72, 0x65, 0x61, - 0x64, 0x79, 0x2e, 0x12, 0x24, 0x0a, 0x07, 0x53, 0x54, 0x4f, 0x50, 0x50, 0x45, 0x44, 0x10, 0x03, - 0x1a, 0x17, 0xea, 0xb9, 0xcb, 0xb9, 0x01, 0x11, 0x48, 0x75, 0x6e, 0x74, 0x20, 0x68, 0x61, 0x73, - 0x20, 0x73, 0x74, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x2e, 0x12, 0x2b, 0x0a, 0x08, 0x41, 0x52, 0x43, - 0x48, 0x49, 0x56, 0x45, 0x44, 0x10, 0x04, 0x1a, 0x1d, 0xea, 0xb9, 0xcb, 0xb9, 0x01, 0x17, 0x48, - 0x75, 0x6e, 0x74, 0x20, 0x68, 0x61, 0x73, 0x20, 0x62, 0x65, 0x65, 0x6e, 0x20, 0x61, 0x72, 0x63, - 0x68, 0x69, 0x76, 0x65, 0x64, 0x2e, 0x22, 0x85, 0x01, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x48, - 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, - 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6f, 0x66, 0x66, - 0x73, 0x65, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x6d, - 0x6d, 0x61, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x6d, 0x6d, - 0x61, 0x72, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x61, - 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x69, - 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x64, 0x22, 0x36, - 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x75, 0x6e, 0x74, 0x52, - 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x29, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x48, 0x75, 0x6e, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x68, 0x75, 0x6e, 0x74, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x68, 0x75, 0x6e, 0x74, 0x49, - 0x64, 0x22, 0x7a, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x48, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x75, - 0x6c, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, - 0x66, 0x73, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, - 0x65, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x68, 0x75, 0x6e, 0x74, - 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x68, 0x75, 0x6e, 0x74, 0x49, - 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x22, 0x46, 0x0a, - 0x0e, 0x46, 0x6c, 0x6f, 0x77, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x12, - 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, - 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, - 0x6c, 0x6f, 0x77, 0x49, 0x64, 0x22, 0xf0, 0x01, 0x0a, 0x0c, 0x48, 0x75, 0x6e, 0x74, 0x4d, 0x75, - 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x68, 0x75, 0x6e, 0x74, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x68, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, - 0x26, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x75, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, - 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x0a, 0x05, 0x73, 0x74, 0x61, - 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x48, 0x75, 0x6e, 0x74, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, - 0x74, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, - 0x65, 0x12, 0x35, 0x0a, 0x0a, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x6c, - 0x6f, 0x77, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0a, 0x61, 0x73, - 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x31, 0x5a, 0x2f, 0x77, 0x77, 0x77, 0x2e, - 0x76, 0x65, 0x6c, 0x6f, 0x63, 0x69, 0x64, 0x65, 0x78, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, - 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x76, 0x65, 0x6c, 0x6f, 0x63, 0x69, 0x72, 0x61, 0x70, 0x74, 0x6f, - 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x14, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x60, 0x0a, 0x0b, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, + 0x3f, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x39, 0x0a, 0x0b, 0x52, 0x44, 0x46, 0x44, 0x61, 0x74, 0x65, + 0x74, 0x69, 0x6d, 0x65, 0x12, 0x1b, 0x57, 0x68, 0x65, 0x6e, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, + 0x68, 0x75, 0x6e, 0x74, 0x20, 0x77, 0x61, 0x73, 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, + 0x2e, 0x22, 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x54, 0x69, 0x6d, 0x65, + 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x07, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1d, 0xe2, + 0xfc, 0xe3, 0xc4, 0x01, 0x17, 0x12, 0x15, 0x57, 0x68, 0x6f, 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x68, 0x75, 0x6e, 0x74, 0x3f, 0x52, 0x07, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x64, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, + 0x69, 0x6d, 0x65, 0x18, 0x15, 0x20, 0x01, 0x28, 0x04, 0x42, 0x45, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, + 0x3f, 0x0a, 0x0b, 0x52, 0x44, 0x46, 0x44, 0x61, 0x74, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x24, + 0x57, 0x68, 0x65, 0x6e, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x68, 0x75, 0x6e, 0x74, 0x20, 0x77, + 0x61, 0x73, 0x20, 0x61, 0x63, 0x74, 0x75, 0x61, 0x6c, 0x6c, 0x79, 0x20, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x65, 0x64, 0x2e, 0x22, 0x0a, 0x53, 0x74, 0x61, 0x72, 0x74, 0x20, 0x54, 0x69, 0x6d, 0x65, + 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x57, 0x0a, 0x07, 0x65, + 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x42, 0x3d, 0xe2, 0xfc, + 0xe3, 0xc4, 0x01, 0x37, 0x0a, 0x0b, 0x52, 0x44, 0x46, 0x44, 0x61, 0x74, 0x65, 0x74, 0x69, 0x6d, + 0x65, 0x12, 0x1b, 0x57, 0x68, 0x65, 0x6e, 0x20, 0x64, 0x6f, 0x65, 0x73, 0x20, 0x74, 0x68, 0x69, + 0x73, 0x20, 0x68, 0x75, 0x6e, 0x74, 0x20, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x3f, 0x22, 0x0b, + 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x07, 0x65, 0x78, 0x70, + 0x69, 0x72, 0x65, 0x73, 0x12, 0x45, 0x0a, 0x10, 0x68, 0x75, 0x6e, 0x74, 0x5f, 0x64, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1a, + 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x14, 0x12, 0x12, 0x48, 0x75, 0x6e, 0x74, 0x27, 0x73, 0x20, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0f, 0x68, 0x75, 0x6e, 0x74, + 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x88, 0x01, 0x0a, 0x0d, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x10, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x72, 0x74, 0x69, + 0x66, 0x61, 0x63, 0x74, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x41, 0x72, 0x67, + 0x73, 0x42, 0x45, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x3f, 0x12, 0x3d, 0x4c, 0x61, 0x75, 0x6e, 0x63, + 0x68, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, + 0x69, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x69, 0x73, 0x20, 0x74, 0x72, 0x75, 0x65, 0x52, 0x0c, 0x73, 0x74, 0x61, 0x72, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x8e, 0x01, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x64, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x48, 0x75, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x42, 0x5a, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x54, 0x12, 0x42, 0x54, 0x68, 0x65, 0x20, 0x63, 0x6f, + 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x6d, 0x75, 0x73, + 0x74, 0x20, 0x62, 0x65, 0x20, 0x73, 0x61, 0x74, 0x69, 0x73, 0x66, 0x69, 0x65, 0x64, 0x20, 0x66, + 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x68, 0x75, 0x6e, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x62, + 0x65, 0x20, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x64, 0x2e, 0x22, 0x0e, 0x48, 0x75, + 0x6e, 0x74, 0x20, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x63, 0x6f, + 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x59, 0x0a, 0x0c, 0x63, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x42, 0x36, 0xe2, + 0xfc, 0xe3, 0xc4, 0x01, 0x30, 0x12, 0x2e, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x20, 0x6e, 0x75, 0x6d, + 0x62, 0x65, 0x72, 0x20, 0x6f, 0x66, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x74, + 0x68, 0x69, 0x73, 0x20, 0x68, 0x75, 0x6e, 0x74, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x72, 0x75, + 0x6e, 0x20, 0x6f, 0x6e, 0x2e, 0x52, 0x0b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4c, 0x69, 0x6d, + 0x69, 0x74, 0x12, 0x26, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x12, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x75, 0x6e, 0x74, 0x53, 0x74, + 0x61, 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x12, 0x4d, 0x0a, 0x09, 0x61, 0x72, + 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x18, 0x11, 0x20, 0x03, 0x28, 0x09, 0x42, 0x2f, 0xe2, + 0xfc, 0xe3, 0xc4, 0x01, 0x29, 0x12, 0x27, 0x41, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x6f, 0x66, + 0x20, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, + 0x68, 0x75, 0x6e, 0x74, 0x20, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, 0x73, 0x2e, 0x52, 0x09, + 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x12, 0x61, 0x0a, 0x10, 0x61, 0x72, 0x74, + 0x69, 0x66, 0x61, 0x63, 0x74, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x13, 0x20, + 0x03, 0x28, 0x09, 0x42, 0x36, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x30, 0x12, 0x2e, 0x41, 0x20, 0x6c, + 0x69, 0x73, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x20, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x68, 0x75, 0x6e, + 0x74, 0x20, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, 0x73, 0x2e, 0x52, 0x0f, 0x61, 0x72, 0x74, + 0x69, 0x66, 0x61, 0x63, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x71, 0x0a, 0x05, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x75, 0x6e, 0x74, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x42, 0x48, + 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x42, 0x12, 0x40, 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x68, 0x75, 0x6e, + 0x74, 0x2e, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x69, 0x73, + 0x20, 0x6d, 0x61, 0x6e, 0x75, 0x70, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x47, 0x55, 0x49, 0x2e, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, + 0xde, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x55, 0x4e, 0x53, + 0x45, 0x54, 0x10, 0x00, 0x12, 0x48, 0x0a, 0x06, 0x50, 0x41, 0x55, 0x53, 0x45, 0x44, 0x10, 0x01, + 0x1a, 0x3c, 0xea, 0xb9, 0xcb, 0xb9, 0x01, 0x36, 0x48, 0x75, 0x6e, 0x74, 0x20, 0x77, 0x69, 0x6c, + 0x6c, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x20, 0x6e, + 0x65, 0x77, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x62, 0x75, 0x74, 0x20, 0x63, + 0x61, 0x6e, 0x20, 0x62, 0x65, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x2e, 0x12, 0x2d, + 0x0a, 0x07, 0x52, 0x55, 0x4e, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x1a, 0x20, 0xea, 0xb9, 0xcb, + 0xb9, 0x01, 0x1a, 0x48, 0x75, 0x6e, 0x74, 0x20, 0x69, 0x73, 0x20, 0x72, 0x75, 0x6e, 0x6e, 0x69, + 0x6e, 0x67, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x72, 0x65, 0x61, 0x64, 0x79, 0x2e, 0x12, 0x24, 0x0a, + 0x07, 0x53, 0x54, 0x4f, 0x50, 0x50, 0x45, 0x44, 0x10, 0x03, 0x1a, 0x17, 0xea, 0xb9, 0xcb, 0xb9, + 0x01, 0x11, 0x48, 0x75, 0x6e, 0x74, 0x20, 0x68, 0x61, 0x73, 0x20, 0x73, 0x74, 0x6f, 0x70, 0x70, + 0x65, 0x64, 0x2e, 0x12, 0x2b, 0x0a, 0x08, 0x41, 0x52, 0x43, 0x48, 0x49, 0x56, 0x45, 0x44, 0x10, + 0x04, 0x1a, 0x1d, 0xea, 0xb9, 0xcb, 0xb9, 0x01, 0x17, 0x48, 0x75, 0x6e, 0x74, 0x20, 0x68, 0x61, + 0x73, 0x20, 0x62, 0x65, 0x65, 0x6e, 0x20, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x64, 0x2e, + 0x22, 0x85, 0x01, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x14, 0x0a, + 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x29, 0x0a, + 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, + 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x64, 0x22, 0x36, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, + 0x48, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, + 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x75, 0x6e, 0x74, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, + 0x22, 0x29, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x48, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x68, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x68, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x7a, 0x0a, 0x15, 0x47, + 0x65, 0x74, 0x48, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x14, 0x0a, 0x05, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x68, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x68, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x61, + 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, + 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x22, 0x46, 0x0a, 0x0e, 0x46, 0x6c, 0x6f, 0x77, 0x41, + 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6c, 0x6f, 0x77, 0x49, 0x64, 0x22, + 0xf0, 0x01, 0x0a, 0x0c, 0x48, 0x75, 0x6e, 0x74, 0x4d, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x17, 0x0a, 0x07, 0x68, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x68, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x05, 0x73, 0x74, 0x61, + 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x48, 0x75, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, + 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x75, 0x6e, 0x74, 0x2e, + 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1d, 0x0a, 0x0a, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x0a, 0x61, + 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x6c, 0x6f, 0x77, 0x41, 0x73, 0x73, 0x69, + 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0a, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, + 0x6e, 0x74, 0x42, 0x31, 0x5a, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x6c, 0x6f, 0x63, 0x69, + 0x64, 0x65, 0x78, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x76, + 0x65, 0x6c, 0x6f, 0x63, 0x69, 0x72, 0x61, 0x70, 0x74, 0x6f, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/api/proto/hunts.proto b/api/proto/hunts.proto index c86efb2f672..d4dade9fef5 100644 --- a/api/proto/hunts.proto +++ b/api/proto/hunts.proto @@ -79,6 +79,8 @@ message Hunt { friendly_name: "Hunt ID", }]; + int64 version = 20; + uint64 create_time = 2 [(sem_type) = { description: "When this hunt was created.", friendly_name: "Creation Time", diff --git a/artifacts/definitions/Server/Internal/ClientPing.yaml b/artifacts/definitions/Server/Internal/ClientPing.yaml new file mode 100644 index 00000000000..63f3036f807 --- /dev/null +++ b/artifacts/definitions/Server/Internal/ClientPing.yaml @@ -0,0 +1,4 @@ +name: Server.Internal.ClientPing +type: INTERNAL +description: | + An internal event channel for notifying about client pings. diff --git a/artifacts/definitions/Server/Internal/HuntUpdate.yaml b/artifacts/definitions/Server/Internal/HuntUpdate.yaml new file mode 100644 index 00000000000..caf7580e66c --- /dev/null +++ b/artifacts/definitions/Server/Internal/HuntUpdate.yaml @@ -0,0 +1,12 @@ +name: Server.Internal.HuntUpdate +description: | + An internal queue to notify hunt dispatchers on all minions that a + certain hunt has changed and should be updated from the internal + cache. + +type: INTERNAL + +column_types: + - name: HuntId + - name: Hunt + type: json diff --git a/artifacts/definitions/System/Hunt/Participation.yaml b/artifacts/definitions/System/Hunt/Participation.yaml index 1a648ad6a97..4129e140891 100644 --- a/artifacts/definitions/System/Hunt/Participation.yaml +++ b/artifacts/definitions/System/Hunt/Participation.yaml @@ -1,43 +1,7 @@ name: System.Hunt.Participation description: | - Endpoints may participate in hunts. This artifact collects which hunt - each system participated in. + Endpoints may participate in hunts. This artifact collects which + hunt each system participated in. - Note: This is an automated system artifact. You do not need to start it. - -type: CLIENT_EVENT - -reports: - - type: MONITORING_DAILY - template: | - {{ define "all_hunts" }}LET allhunts <= SELECT * FROM hunts(){{ end }} - {{ define "hunts" }} - SELECT * FROM foreach( - row={ SELECT timestamp(epoch=Timestamp) AS Scheduled, - HuntId as ParticipatedHuntId - FROM source(client_id=ClientId, - artifact='System.Hunt.Participation') }, - query={ - SELECT Scheduled, - hunt_id, - hunt_description, - start_request.artifacts - FROM allhunts - WHERE hunt_id = ParticipatedHuntId - }) - {{ end }} - - {{ $client_info := Query "SELECT * FROM clients(client_id=ClientId) LIMIT 1" }} - - # Hunt participation for {{ Get $client_info "0.os_info.fqdn" }} - - The client with a client ID of {{ Get $client_info "0.client_id" }} participated in some hunts today. - - {{ Query "all_hunts" "hunts" | Table }} - - ## VQL Query - The following VQL query was used to plot the graph above. - - ```sql - {{ template "hunts" }} - ``` +# Will not be written but will be relayed between minions and server. +type: INTERNAL diff --git a/bin/binary_test.go b/bin/binary_test.go index 79fdce44cf9..5b8b4e5bb81 100644 --- a/bin/binary_test.go +++ b/bin/binary_test.go @@ -84,15 +84,16 @@ func (self *MainTestSuite) TestAutoexec() { config_file.Close() // Repack the config in the binary. - cmd := exec.Command(self.binary, "config", "repack", config_file.Name(), exe.Name()) + cmd := exec.Command(self.binary, + "config", "repack", config_file.Name(), exe.Name()) out, err := cmd.CombinedOutput() - require.NoError(self.T(), err) + require.NoError(self.T(), err, string(out)) // Run the repacked binary with no args - it should run the // `artifacts list` command. cmd = exec.Command(exe.Name()) out, err = cmd.CombinedOutput() - require.NoError(self.T(), err) + require.NoError(self.T(), err, string(out)) // The output should contain MySpecialArtifact as well as the // standard artifacts. diff --git a/bin/debug.go b/bin/debug.go index 0e2b0dacf6b..22e16d47f2f 100644 --- a/bin/debug.go +++ b/bin/debug.go @@ -18,6 +18,7 @@ package main import ( + "fmt" "log" "net/http" _ "net/http/pprof" @@ -27,16 +28,19 @@ import ( ) var ( - debug_flag = app.Flag("debug", "Enables debug and profile server.").Bool() + debug_flag = app.Flag("debug", "Enables debug and profile server.").Bool() + debug_flag_port = app.Flag("debug_port", "Port for the debug server."). + Default("6060").Int64() ) func initDebugServer(config_obj *config_proto.Config) error { if *debug_flag { logger := logging.GetLogger(config_obj, &logging.FrontendComponent) - logger.Info("Starting debug server on http://127.0.0.1:6060/debug/pprof") + logger.Info("Starting debug server on http://127.0.0.1:%v/debug/pprof", *debug_flag_port) go func() { - log.Println(http.ListenAndServe("127.0.0.1:6060", nil)) + log.Println(http.ListenAndServe( + fmt.Sprintf("127.0.0.1:%d", *debug_flag_port), nil)) }() } return nil diff --git a/bin/frontend.go b/bin/frontend.go index 6f2c294d140..8269b194870 100644 --- a/bin/frontend.go +++ b/bin/frontend.go @@ -97,13 +97,8 @@ func startFrontend(sm *services.Service) (*api.Builder, error) { // Increase resource limits. server.IncreaseLimits(config_obj) - // Start the frontend service if needed. This must happen - // first so other services can contact the master node. - - config_obj.Frontend.IsMaster = !*frontend_cmd_minion - // Minions use the RemoteFileDataStore to sync with the server. - if !config_obj.Frontend.IsMaster { + if !services.IsMaster(config_obj) { logger.Info("Frontend will run as a minion.") logger.Info("Enabling remote datastore since we are a minion.") config_obj.Datastore.Implementation = "RemoteFileDataStore" diff --git a/bin/golden.go b/bin/golden.go index d6e9908f4be..513acee9e1e 100644 --- a/bin/golden.go +++ b/bin/golden.go @@ -42,6 +42,7 @@ import ( logging "www.velocidex.com/golang/velociraptor/logging" "www.velocidex.com/golang/velociraptor/reporting" "www.velocidex.com/golang/velociraptor/services" + "www.velocidex.com/golang/velociraptor/services/client_info" "www.velocidex.com/golang/velociraptor/services/hunt_dispatcher" "www.velocidex.com/golang/velociraptor/services/indexing" "www.velocidex.com/golang/velociraptor/startup" @@ -256,6 +257,9 @@ func doGolden() { err = sm.Start(hunt_dispatcher.StartHuntDispatcher) kingpin.FatalIfError(err, "Starting services") + err = sm.Start(client_info.StartClientInfoService) + kingpin.FatalIfError(err, "Starting services") + err = sm.Start(indexing.StartIndexingService) kingpin.FatalIfError(err, "Starting services") diff --git a/bin/index.go b/bin/index.go index 5e11d8342be..23d56b72a44 100644 --- a/bin/index.go +++ b/bin/index.go @@ -66,8 +66,8 @@ func doRebuildIndex() { search.SetIndex(config_obj, client_id, "host:"+client_info.Hostname) } - if client_info.Info.Fqdn != "" { - search.SetIndex(config_obj, client_id, "host:"+client_info.Info.Fqdn) + if client_info.Fqdn != "" { + search.SetIndex(config_obj, client_id, "host:"+client_info.Fqdn) } for _, label := range labels { diff --git a/bin/main.go b/bin/main.go index 87be1b6838d..ce52cf1493a 100755 --- a/bin/main.go +++ b/bin/main.go @@ -163,6 +163,7 @@ func main() { WithEnvLoader("VELOCIRAPTOR_CONFIG"). WithCustomValidator(initFilestoreAccessor). WithCustomValidator(initDebugServer). + WithCustomValidator(applyMinionRole). WithLogFile(*logging_flag) if *trace_flag != "" { @@ -204,5 +205,6 @@ func makeDefaultConfigLoader() *config.Loader { WithCustomValidator(func(config_obj *config_proto.Config) error { return mergeFlagConfig(config_obj, default_config) }). + WithCustomValidator(applyMinionRole). WithCustomValidator(ensureProxy) } diff --git a/bin/metrics.go b/bin/metrics.go new file mode 100644 index 00000000000..d9f09be5bba --- /dev/null +++ b/bin/metrics.go @@ -0,0 +1,20 @@ +package main + +import ( + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +var ( + start = time.Now() + + CurrentTime = promauto.NewUntypedFunc( + prometheus.UntypedOpts{ + Name: "uptime", + Help: "Time since process start.", + }, func() float64 { + return float64(time.Now().Unix() - start.Unix()) + }) +) diff --git a/bin/minions.go b/bin/minions.go new file mode 100644 index 00000000000..6187368973f --- /dev/null +++ b/bin/minions.go @@ -0,0 +1,10 @@ +package main + +import config_proto "www.velocidex.com/golang/velociraptor/config/proto" + +func applyMinionRole(config_obj *config_proto.Config) error { + if config_obj.Frontend != nil { + config_obj.Frontend.IsMinion = *frontend_cmd_minion + } + return nil +} diff --git a/bin/repack.go b/bin/repack.go index 480f1ffc7fb..74dfb21e942 100644 --- a/bin/repack.go +++ b/bin/repack.go @@ -87,7 +87,8 @@ func validate_config(config_obj *config_proto.Config) error { } func doRepack() { - config_obj, err := new(config.Loader).WithFileLoader(*repack_command_config). + config_obj, err := new(config.Loader). + WithFileLoader(*repack_command_config). LoadAndValidate() kingpin.FatalIfError(err, "Unable to open config file") diff --git a/clients/tasks.go b/clients/tasks.go index 7acca934b2a..8ebba21aa1e 100644 --- a/clients/tasks.go +++ b/clients/tasks.go @@ -36,6 +36,8 @@ func GetClientTasks( } for _, task_urn := range tasks { + task_urn = task_urn.SetTag("ClientTask") + // Here we read the task from the task_urn and remove // it from the queue. message := &crypto_proto.VeloMessage{} diff --git a/config/proto/config.pb.go b/config/proto/config.pb.go index 8cc580bc582..c13b8ea19f8 100644 --- a/config/proto/config.pb.go +++ b/config/proto/config.pb.go @@ -1499,6 +1499,10 @@ type FrontendResourceControl struct { // less latency to respond to client events but also means more // TLS handshake and network overheads due to frequent POST. ClientEventMaxWait uint64 `protobuf:"varint,23,opt,name=client_event_max_wait,json=clientEventMaxWait,proto3" json:"client_event_max_wait,omitempty"` + // Minions batch updates to the master so as to minimize RPC as + // much as possible, this controls how often these batches are + // flushed to the master (default 10 sec). + MinionBatchWaitTimeMs uint64 `protobuf:"varint,25,opt,name=minion_batch_wait_time_ms,json=minionBatchWaitTimeMs,proto3" json:"minion_batch_wait_time_ms,omitempty"` } func (x *FrontendResourceControl) Reset() { @@ -1610,6 +1614,13 @@ func (x *FrontendResourceControl) GetClientEventMaxWait() uint64 { return 0 } +func (x *FrontendResourceControl) GetMinionBatchWaitTimeMs() uint64 { + if x != nil { + return x.MinionBatchWaitTimeMs + } + return 0 +} + type FrontendConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1662,7 +1673,7 @@ type FrontendConfig struct { ServerServices *ServerServicesConfig `protobuf:"bytes,20,opt,name=server_services,json=serverServices,proto3" json:"server_services,omitempty"` Resources *FrontendResourceControl `protobuf:"bytes,27,opt,name=resources,proto3" json:"resources,omitempty"` // Used internally to tag this frontend as the master. - IsMaster bool `protobuf:"varint,30,opt,name=is_master,json=isMaster,proto3" json:"is_master,omitempty"` + IsMinion bool `protobuf:"varint,30,opt,name=is_minion,json=isMinion,proto3" json:"is_minion,omitempty"` // Below options are DEPRECATED - moved to resources by migration code. Concurrency uint64 `protobuf:"varint,9,opt,name=concurrency,proto3" json:"concurrency,omitempty"` MaxUploadSize uint64 `protobuf:"varint,11,opt,name=max_upload_size,json=maxUploadSize,proto3" json:"max_upload_size,omitempty"` @@ -1868,9 +1879,9 @@ func (x *FrontendConfig) GetResources() *FrontendResourceControl { return nil } -func (x *FrontendConfig) GetIsMaster() bool { +func (x *FrontendConfig) GetIsMinion() bool { if x != nil { - return x.IsMaster + return x.IsMinion } return false } @@ -1949,8 +1960,11 @@ type DatastoreConfig struct { MemcacheExpirationSec uint64 `protobuf:"varint,4,opt,name=memcache_expiration_sec,json=memcacheExpirationSec,proto3" json:"memcache_expiration_sec,omitempty"` MemcacheWriteMutationBuffer int64 `protobuf:"varint,5,opt,name=memcache_write_mutation_buffer,json=memcacheWriteMutationBuffer,proto3" json:"memcache_write_mutation_buffer,omitempty"` // Number of writing threads - increase for high latency - // filesystems. + // filesystems (default 100). MemcacheWriteMutationWriters int64 `protobuf:"varint,6,opt,name=memcache_write_mutation_writers,json=memcacheWriteMutationWriters,proto3" json:"memcache_write_mutation_writers,omitempty"` + // Experimental - do not set in configs yet! + MinionImplementation string `protobuf:"bytes,7,opt,name=minion_implementation,json=minionImplementation,proto3" json:"minion_implementation,omitempty"` + MasterImplementation string `protobuf:"bytes,8,opt,name=master_implementation,json=masterImplementation,proto3" json:"master_implementation,omitempty"` } func (x *DatastoreConfig) Reset() { @@ -2027,6 +2041,20 @@ func (x *DatastoreConfig) GetMemcacheWriteMutationWriters() int64 { return 0 } +func (x *DatastoreConfig) GetMinionImplementation() string { + if x != nil { + return x.MinionImplementation + } + return "" +} + +func (x *DatastoreConfig) GetMasterImplementation() string { + if x != nil { + return x.MasterImplementation + } + return "" +} + // Configuration for the mail server. type MailConfig struct { state protoimpl.MessageState @@ -2398,6 +2426,7 @@ type ServerServicesConfig struct { ApiServer bool `protobuf:"varint,13,opt,name=api_server,json=apiServer,proto3" json:"api_server,omitempty"` FrontendServer bool `protobuf:"varint,14,opt,name=frontend_server,json=frontendServer,proto3" json:"frontend_server,omitempty"` GuiServer bool `protobuf:"varint,15,opt,name=gui_server,json=guiServer,proto3" json:"gui_server,omitempty"` + IndexServer bool `protobuf:"varint,16,opt,name=index_server,json=indexServer,proto3" json:"index_server,omitempty"` } func (x *ServerServicesConfig) Reset() { @@ -2537,6 +2566,13 @@ func (x *ServerServicesConfig) GetGuiServer() bool { return false } +func (x *ServerServicesConfig) GetIndexServer() bool { + if x != nil { + return x.IndexServer + } + return false +} + type Defaults struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -3361,7 +3397,7 @@ var file_config_proto_rawDesc = []byte{ 0x74, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x72, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x66, 0x72, 0x65, 0x71, - 0x75, 0x65, 0x6e, 0x63, 0x79, 0x22, 0xa6, 0x05, 0x0a, 0x17, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x65, + 0x75, 0x65, 0x6e, 0x63, 0x79, 0x22, 0xe0, 0x05, 0x0a, 0x17, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x34, 0x0a, 0x16, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, @@ -3403,406 +3439,418 @@ var file_config_proto_rawDesc = []byte{ 0x6c, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x61, 0x74, 0x65, 0x12, 0x31, 0x0a, 0x15, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x77, 0x61, 0x69, 0x74, 0x18, 0x17, 0x20, 0x01, 0x28, 0x04, 0x52, 0x12, 0x63, 0x6c, 0x69, 0x65, - 0x6e, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x4d, 0x61, 0x78, 0x57, 0x61, 0x69, 0x74, 0x22, 0xa5, - 0x12, 0x0a, 0x0e, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x12, 0x23, 0x0a, 0x0b, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x70, 0x61, 0x74, 0x68, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x70, 0x75, 0x62, 0x6c, - 0x69, 0x63, 0x50, 0x61, 0x74, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, - 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x69, 0x6e, 0x64, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x62, 0x69, 0x6e, 0x64, 0x41, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x69, 0x6e, 0x64, 0x5f, 0x70, 0x6f, - 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x62, 0x69, 0x6e, 0x64, 0x50, 0x6f, - 0x72, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, - 0x19, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x62, 0x61, 0x73, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, - 0x24, 0x0a, 0x0e, 0x75, 0x73, 0x65, 0x5f, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x74, - 0x70, 0x18, 0x18, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x75, 0x73, 0x65, 0x50, 0x6c, 0x61, 0x69, - 0x6e, 0x48, 0x74, 0x74, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x18, 0x21, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x12, 0x4e, 0x0a, 0x0b, 0x63, - 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x42, 0x2c, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x26, 0x12, 0x24, 0x58, 0x35, 0x30, 0x39, 0x20, 0x43, - 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x66, 0x72, - 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x52, 0x0b, - 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x66, 0x0a, 0x0b, 0x70, - 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x42, 0x45, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x3f, 0x12, 0x3d, 0x54, 0x68, 0x65, 0x20, 0x70, 0x72, - 0x69, 0x76, 0x61, 0x74, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x50, 0x45, 0x4d, 0x20, - 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, - 0x4b, 0x65, 0x79, 0x12, 0x38, 0x0a, 0x18, 0x74, 0x6c, 0x73, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x1c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x74, 0x6c, 0x73, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x37, 0x0a, - 0x18, 0x74, 0x6c, 0x73, 0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, - 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x1d, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x15, 0x74, 0x6c, 0x73, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x46, 0x69, - 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x40, 0x0a, 0x08, 0x64, 0x6e, 0x73, 0x5f, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x42, 0x25, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x1f, - 0x12, 0x1d, 0x54, 0x68, 0x65, 0x20, 0x44, 0x4e, 0x53, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6f, - 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x2e, 0x52, - 0x07, 0x64, 0x6e, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0xbb, 0x02, 0x0a, 0x19, 0x64, 0x6f, 0x5f, - 0x6e, 0x6f, 0x74, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x61, 0x72, 0x74, - 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x42, 0xff, 0x01, 0xe2, - 0xfc, 0xe3, 0xc4, 0x01, 0xf8, 0x01, 0x12, 0xf5, 0x01, 0x57, 0x68, 0x65, 0x6e, 0x20, 0x73, 0x65, - 0x74, 0x20, 0x77, 0x65, 0x20, 0x64, 0x6f, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x63, 0x6f, 0x6d, 0x70, - 0x72, 0x65, 0x73, 0x73, 0x20, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x20, 0x73, - 0x65, 0x6e, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, - 0x74, 0x2e, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, - 0x75, 0x73, 0x65, 0x66, 0x75, 0x6c, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x64, 0x65, 0x62, 0x75, 0x67, - 0x67, 0x69, 0x6e, 0x67, 0x2e, 0x20, 0x4e, 0x6f, 0x74, 0x65, 0x3a, 0x20, 0x53, 0x65, 0x74, 0x74, - 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x73, 0x65, - 0x6e, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x61, 0x72, - 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x20, 0x6c, 0x69, - 0x6b, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x6d, 0x61, - 0x79, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x20, 0x69, 0x6e, 0x20, 0x69, 0x6e, 0x66, 0x6f, - 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x64, 0x69, 0x73, 0x63, 0x6c, 0x6f, 0x73, 0x75, - 0x72, 0x65, 0x20, 0x69, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, - 0x20, 0x69, 0x73, 0x20, 0x75, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x2e, 0x52, 0x16, - 0x64, 0x6f, 0x4e, 0x6f, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x41, 0x72, 0x74, - 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x12, 0x5a, 0x0a, 0x07, 0x64, 0x79, 0x6e, 0x5f, 0x64, 0x6e, - 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x44, 0x79, 0x6e, 0x44, 0x4e, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x2c, 0xe2, 0xfc, - 0xe3, 0xc4, 0x01, 0x26, 0x12, 0x24, 0x49, 0x66, 0x20, 0x73, 0x65, 0x74, 0x20, 0x77, 0x65, 0x20, - 0x73, 0x74, 0x61, 0x72, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x79, 0x6e, 0x20, 0x64, 0x6e, - 0x73, 0x20, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x52, 0x06, 0x64, 0x79, 0x6e, 0x44, - 0x6e, 0x73, 0x12, 0x64, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x68, 0x65, 0x61, 0x64, - 0x65, 0x72, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x42, 0x41, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x3b, - 0x12, 0x39, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, - 0x20, 0x62, 0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x20, 0x63, 0x6f, - 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x6d, - 0x6f, 0x74, 0x65, 0x20, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x0b, 0x70, 0x72, 0x6f, - 0x78, 0x79, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x86, 0x01, 0x0a, 0x23, 0x64, 0x65, 0x66, - 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x6f, 0x6e, 0x69, - 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, - 0x18, 0x0e, 0x20, 0x03, 0x28, 0x09, 0x42, 0x37, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x31, 0x12, 0x2f, - 0x54, 0x68, 0x65, 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x20, 0x73, 0x65, 0x74, 0x20, - 0x6f, 0x66, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x6d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, - 0x72, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x2e, 0x52, - 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x6f, - 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, - 0x73, 0x12, 0x8d, 0x01, 0x0a, 0x23, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x73, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x5f, 0x6d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x5f, - 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x18, 0x1f, 0x20, 0x03, 0x28, 0x09, 0x42, - 0x3e, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x38, 0x12, 0x36, 0x41, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, - 0x6e, 0x61, 0x6c, 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x20, 0x73, 0x65, 0x74, 0x20, - 0x6f, 0x66, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x6d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, - 0x72, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x2e, 0x52, - 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4d, 0x6f, - 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, - 0x73, 0x12, 0x7e, 0x0a, 0x0b, 0x72, 0x75, 0x6e, 0x5f, 0x61, 0x73, 0x5f, 0x75, 0x73, 0x65, 0x72, - 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x42, 0x5e, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x58, 0x12, 0x56, - 0x54, 0x68, 0x65, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x74, 0x68, - 0x65, 0x20, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, - 0x64, 0x20, 0x72, 0x75, 0x6e, 0x20, 0x61, 0x73, 0x2e, 0x20, 0x49, 0x66, 0x20, 0x73, 0x65, 0x74, - 0x20, 0x77, 0x65, 0x20, 0x72, 0x65, 0x66, 0x75, 0x73, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x72, 0x75, - 0x6e, 0x20, 0x61, 0x73, 0x20, 0x61, 0x20, 0x64, 0x69, 0x66, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x74, - 0x20, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x52, 0x09, 0x72, 0x75, 0x6e, 0x41, 0x73, 0x55, 0x73, 0x65, - 0x72, 0x12, 0x2b, 0x0a, 0x12, 0x47, 0x52, 0x50, 0x43, 0x5f, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x6d, - 0x61, 0x78, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x11, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x47, - 0x52, 0x50, 0x43, 0x50, 0x6f, 0x6f, 0x6c, 0x4d, 0x61, 0x78, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x2b, - 0x0a, 0x12, 0x47, 0x52, 0x50, 0x43, 0x5f, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x6d, 0x61, 0x78, 0x5f, - 0x77, 0x61, 0x69, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x47, 0x52, 0x50, 0x43, - 0x50, 0x6f, 0x6f, 0x6c, 0x4d, 0x61, 0x78, 0x57, 0x61, 0x69, 0x74, 0x12, 0x44, 0x0a, 0x0f, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x14, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x52, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x73, 0x12, 0x3c, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x1b, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x72, 0x6f, - 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x6e, - 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, - 0x1b, 0x0a, 0x09, 0x69, 0x73, 0x5f, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x18, 0x1e, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x08, 0x69, 0x73, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x12, 0x60, 0x0a, 0x0b, - 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x04, 0x42, 0x3e, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x38, 0x12, 0x36, 0x54, 0x68, 0x65, 0x20, 0x6e, - 0x75, 0x6d, 0x62, 0x65, 0x72, 0x20, 0x6f, 0x66, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, - 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x70, 0x72, 0x6f, 0x63, - 0x65, 0x73, 0x73, 0x20, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x6c, 0x79, - 0x2e, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x60, - 0x0a, 0x0f, 0x6d, 0x61, 0x78, 0x5f, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x73, 0x69, 0x7a, - 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x42, 0x38, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x32, 0x12, - 0x30, 0x4d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x69, 0x6e, - 0x20, 0x42, 0x79, 0x74, 0x65, 0x73, 0x20, 0x77, 0x65, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x61, - 0x63, 0x63, 0x65, 0x70, 0x74, 0x20, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x20, 0x66, 0x6f, - 0x72, 0x52, 0x0d, 0x6d, 0x61, 0x78, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x69, 0x7a, 0x65, - 0x12, 0x70, 0x0a, 0x10, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6c, 0x69, - 0x65, 0x6e, 0x74, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x42, 0x45, 0xe2, 0xfc, 0xe3, 0xc4, - 0x01, 0x3f, 0x12, 0x3d, 0x45, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x20, 0x6e, 0x75, 0x6d, - 0x62, 0x65, 0x72, 0x20, 0x6f, 0x66, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x69, - 0x6e, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x20, 0x28, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x20, 0x31, 0x30, 0x30, 0x30, 0x30, - 0x29, 0x52, 0x0f, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x43, 0x6c, 0x69, 0x65, 0x6e, - 0x74, 0x73, 0x12, 0x33, 0x0a, 0x16, 0x70, 0x65, 0x72, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, - 0x5f, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x15, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x13, 0x70, 0x65, 0x72, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x6c, - 0x6f, 0x61, 0x64, 0x52, 0x61, 0x74, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x6c, 0x6f, 0x62, 0x61, - 0x6c, 0x5f, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x16, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x10, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x55, 0x70, 0x6c, 0x6f, 0x61, - 0x64, 0x52, 0x61, 0x74, 0x65, 0x12, 0x31, 0x0a, 0x15, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, - 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x77, 0x61, 0x69, 0x74, 0x18, 0x17, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x12, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x4d, 0x61, 0x78, 0x57, 0x61, 0x69, 0x74, 0x12, 0x44, 0x0a, 0x1e, 0x61, 0x72, 0x74, 0x69, - 0x66, 0x61, 0x63, 0x74, 0x5f, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x20, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x1c, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x26, - 0x0a, 0x0f, 0x64, 0x6f, 0x5f, 0x6e, 0x6f, 0x74, 0x5f, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, - 0x74, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x64, 0x6f, 0x4e, 0x6f, 0x74, 0x52, 0x65, - 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x22, 0xca, 0x02, 0x0a, 0x0f, 0x44, 0x61, 0x74, 0x61, 0x73, - 0x74, 0x6f, 0x72, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x26, 0x0a, 0x0e, 0x69, 0x6d, - 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0e, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2f, - 0x0a, 0x13, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x64, 0x69, 0x72, 0x65, - 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x66, 0x69, 0x6c, - 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, - 0x36, 0x0a, 0x17, 0x6d, 0x65, 0x6d, 0x63, 0x61, 0x63, 0x68, 0x65, 0x5f, 0x65, 0x78, 0x70, 0x69, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x65, 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x15, 0x6d, 0x65, 0x6d, 0x63, 0x61, 0x63, 0x68, 0x65, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x63, 0x12, 0x43, 0x0a, 0x1e, 0x6d, 0x65, 0x6d, 0x63, 0x61, - 0x63, 0x68, 0x65, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x6d, 0x75, 0x74, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x5f, 0x62, 0x75, 0x66, 0x66, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x1b, 0x6d, 0x65, 0x6d, 0x63, 0x61, 0x63, 0x68, 0x65, 0x57, 0x72, 0x69, 0x74, 0x65, 0x4d, 0x75, - 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x12, 0x45, 0x0a, 0x1f, - 0x6d, 0x65, 0x6d, 0x63, 0x61, 0x63, 0x68, 0x65, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x6d, - 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x72, 0x73, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x1c, 0x6d, 0x65, 0x6d, 0x63, 0x61, 0x63, 0x68, 0x65, 0x57, - 0x72, 0x69, 0x74, 0x65, 0x4d, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x57, 0x72, 0x69, 0x74, - 0x65, 0x72, 0x73, 0x22, 0x89, 0x03, 0x0a, 0x0a, 0x4d, 0x61, 0x69, 0x6c, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x12, 0x65, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x42, 0x51, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x4b, 0x12, 0x49, 0x57, 0x68, 0x65, 0x72, 0x65, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, - 0x20, 0x62, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x74, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x2e, 0x20, 0x49, - 0x66, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x73, 0x65, 0x74, 0x20, 0x77, 0x65, 0x20, 0x75, 0x73, 0x65, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, - 0x6d, 0x65, 0x2e, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x3b, 0x0a, 0x06, 0x73, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x23, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, - 0x1d, 0x12, 0x1b, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, - 0x65, 0x20, 0x53, 0x4d, 0x54, 0x50, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x52, 0x06, - 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x40, 0x0a, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x42, 0x1f, 0xe2, 0xfc, 0xe3, - 0xc4, 0x01, 0x19, 0x12, 0x17, 0x50, 0x6f, 0x72, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x53, 0x4d, 0x54, 0x50, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x0a, 0x73, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x48, 0x0a, 0x0d, 0x61, 0x75, 0x74, 0x68, - 0x5f, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, - 0x23, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x1d, 0x12, 0x1b, 0x4e, 0x61, 0x6d, 0x65, 0x20, 0x74, 0x6f, - 0x20, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x75, 0x6e, - 0x64, 0x65, 0x72, 0x2e, 0x52, 0x0c, 0x61, 0x75, 0x74, 0x68, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, - 0x6d, 0x65, 0x12, 0x4b, 0x0a, 0x0d, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, - 0x6f, 0x72, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x26, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, - 0x20, 0x12, 0x1e, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x61, - 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x77, 0x69, 0x74, 0x68, - 0x2e, 0x52, 0x0c, 0x61, 0x75, 0x74, 0x68, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, - 0x72, 0x0a, 0x16, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x74, 0x65, 0x6e, 0x74, - 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x6f, 0x74, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x0c, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x17, - 0x0a, 0x07, 0x6d, 0x61, 0x78, 0x5f, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, - 0x06, 0x6d, 0x61, 0x78, 0x41, 0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, - 0x6c, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, - 0x6c, 0x65, 0x64, 0x22, 0xd9, 0x04, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x75, 0x0a, 0x10, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, - 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, - 0x4a, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x44, 0x12, 0x42, 0x54, 0x68, 0x65, 0x20, 0x6c, 0x6f, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, 0x6c, - 0x6f, 0x67, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x20, 0x49, 0x66, 0x20, 0x6e, 0x6f, 0x74, - 0x20, 0x73, 0x65, 0x74, 0x20, 0x77, 0x65, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, 0x6e, 0x6f, - 0x20, 0x6c, 0x6f, 0x67, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x52, 0x0f, 0x6f, 0x75, 0x74, - 0x70, 0x75, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x7a, 0x0a, 0x1b, - 0x73, 0x65, 0x70, 0x61, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x6c, 0x6f, 0x67, 0x73, 0x5f, 0x70, 0x65, - 0x72, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x08, 0x42, 0x3b, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x35, 0x12, 0x33, 0x49, 0x66, 0x20, 0x73, 0x65, - 0x74, 0x2c, 0x20, 0x65, 0x61, 0x63, 0x68, 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, - 0x74, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x6c, 0x6f, 0x67, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, - 0x73, 0x65, 0x70, 0x61, 0x72, 0x61, 0x74, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x52, 0x18, - 0x73, 0x65, 0x70, 0x61, 0x72, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x50, 0x65, 0x72, 0x43, - 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x12, 0x4b, 0x0a, 0x0d, 0x72, 0x6f, 0x74, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x42, - 0x26, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x20, 0x12, 0x1e, 0x48, 0x6f, 0x77, 0x20, 0x6f, 0x66, 0x74, - 0x65, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x52, 0x0c, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x6b, 0x0a, 0x07, 0x6d, 0x61, 0x78, 0x5f, 0x61, 0x67, 0x65, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x42, 0x52, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x4c, 0x12, 0x40, - 0x4d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x20, 0x61, 0x67, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x65, - 0x61, 0x63, 0x68, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x28, 0x46, 0x69, 0x6c, 0x65, 0x20, 0x77, - 0x69, 0x6c, 0x6c, 0x20, 0x62, 0x65, 0x20, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x20, 0x61, - 0x66, 0x74, 0x65, 0x72, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x29, 0x2e, - 0x32, 0x08, 0x33, 0x31, 0x35, 0x33, 0x36, 0x30, 0x30, 0x30, 0x52, 0x06, 0x6d, 0x61, 0x78, 0x41, - 0x67, 0x65, 0x12, 0x33, 0x0a, 0x05, 0x64, 0x65, 0x62, 0x75, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, - 0x67, 0x52, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x52, 0x05, 0x64, 0x65, 0x62, 0x75, 0x67, 0x12, 0x31, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4c, 0x6f, + 0x6e, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x4d, 0x61, 0x78, 0x57, 0x61, 0x69, 0x74, 0x12, 0x38, + 0x0a, 0x19, 0x6d, 0x69, 0x6e, 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x77, + 0x61, 0x69, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x19, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x15, 0x6d, 0x69, 0x6e, 0x69, 0x6f, 0x6e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x57, 0x61, + 0x69, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x22, 0xa5, 0x12, 0x0a, 0x0e, 0x46, 0x72, 0x6f, + 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x23, 0x0a, 0x0b, 0x70, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x50, 0x61, 0x74, 0x68, + 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x13, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, + 0x62, 0x69, 0x6e, 0x64, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x62, 0x69, 0x6e, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, + 0x1b, 0x0a, 0x09, 0x62, 0x69, 0x6e, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x08, 0x62, 0x69, 0x6e, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x1b, 0x0a, 0x09, + 0x62, 0x61, 0x73, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x19, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x62, 0x61, 0x73, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x24, 0x0a, 0x0e, 0x75, 0x73, 0x65, + 0x5f, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x74, 0x70, 0x18, 0x18, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0c, 0x75, 0x73, 0x65, 0x50, 0x6c, 0x61, 0x69, 0x6e, 0x48, 0x74, 0x74, 0x70, 0x12, + 0x14, 0x0a, 0x05, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x18, 0x21, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x70, 0x72, 0x6f, 0x78, 0x79, 0x12, 0x4e, 0x0a, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x2c, 0xe2, 0xfc, 0xe3, 0xc4, + 0x01, 0x26, 0x12, 0x24, 0x58, 0x35, 0x30, 0x39, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, + 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x52, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x66, 0x0a, 0x0b, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, + 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x45, 0xe2, 0xfc, 0xe3, 0xc4, + 0x01, 0x3f, 0x12, 0x3d, 0x54, 0x68, 0x65, 0x20, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x20, + 0x6b, 0x65, 0x79, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x72, 0x6f, 0x6e, 0x74, + 0x65, 0x6e, 0x64, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x64, + 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x50, 0x45, 0x4d, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, + 0x2e, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x38, 0x0a, + 0x18, 0x74, 0x6c, 0x73, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, + 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x16, 0x74, 0x6c, 0x73, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x46, + 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x18, 0x74, 0x6c, 0x73, 0x5f, 0x70, + 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x1d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x74, 0x6c, 0x73, 0x50, 0x72, + 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x46, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x40, 0x0a, 0x08, 0x64, 0x6e, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x25, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x1f, 0x12, 0x1d, 0x54, 0x68, 0x65, 0x20, + 0x44, 0x4e, 0x53, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x2e, 0x52, 0x07, 0x64, 0x6e, 0x73, 0x4e, 0x61, + 0x6d, 0x65, 0x12, 0xbb, 0x02, 0x0a, 0x19, 0x64, 0x6f, 0x5f, 0x6e, 0x6f, 0x74, 0x5f, 0x63, 0x6f, + 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x42, 0xff, 0x01, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0xf8, 0x01, + 0x12, 0xf5, 0x01, 0x57, 0x68, 0x65, 0x6e, 0x20, 0x73, 0x65, 0x74, 0x20, 0x77, 0x65, 0x20, 0x64, + 0x6f, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x20, 0x61, + 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x20, 0x73, 0x65, 0x6e, 0x74, 0x20, 0x74, 0x6f, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x20, 0x54, 0x68, 0x69, + 0x73, 0x20, 0x69, 0x73, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x75, 0x73, 0x65, 0x66, 0x75, 0x6c, + 0x20, 0x66, 0x6f, 0x72, 0x20, 0x64, 0x65, 0x62, 0x75, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x2e, 0x20, + 0x4e, 0x6f, 0x74, 0x65, 0x3a, 0x20, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, + 0x69, 0x73, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, + 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x20, 0x6c, 0x69, 0x6b, 0x65, 0x20, 0x6e, 0x61, 0x6d, + 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x6d, 0x61, 0x79, 0x20, 0x72, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x20, 0x69, 0x6e, 0x20, 0x69, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x64, 0x69, 0x73, 0x63, 0x6c, 0x6f, 0x73, 0x75, 0x72, 0x65, 0x20, 0x69, 0x66, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x69, 0x73, 0x20, 0x75, 0x6e, + 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x2e, 0x52, 0x16, 0x64, 0x6f, 0x4e, 0x6f, 0x74, 0x43, + 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, + 0x12, 0x5a, 0x0a, 0x07, 0x64, 0x79, 0x6e, 0x5f, 0x64, 0x6e, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x79, 0x6e, 0x44, 0x4e, 0x53, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x2c, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x26, 0x12, 0x24, + 0x49, 0x66, 0x20, 0x73, 0x65, 0x74, 0x20, 0x77, 0x65, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x64, 0x79, 0x6e, 0x20, 0x64, 0x6e, 0x73, 0x20, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x2e, 0x52, 0x06, 0x64, 0x79, 0x6e, 0x44, 0x6e, 0x73, 0x12, 0x64, 0x0a, 0x0c, + 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x0d, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x41, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x3b, 0x12, 0x39, 0x48, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x69, + 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x20, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x48, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x12, 0x86, 0x01, 0x0a, 0x23, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, + 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x09, + 0x42, 0x37, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x31, 0x12, 0x2f, 0x54, 0x68, 0x65, 0x20, 0x69, 0x6e, + 0x69, 0x74, 0x69, 0x61, 0x6c, 0x20, 0x73, 0x65, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x63, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x20, 0x6d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x61, + 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x2e, 0x52, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, + 0x6c, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, + 0x6e, 0x67, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x12, 0x8d, 0x01, 0x0a, 0x23, + 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x6d, + 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, + 0x63, 0x74, 0x73, 0x18, 0x1f, 0x20, 0x03, 0x28, 0x09, 0x42, 0x3e, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, + 0x38, 0x12, 0x36, 0x41, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x69, 0x6e, + 0x69, 0x74, 0x69, 0x61, 0x6c, 0x20, 0x73, 0x65, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x20, 0x6d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x61, + 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x2e, 0x52, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, + 0x6c, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, + 0x6e, 0x67, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x12, 0x7e, 0x0a, 0x0b, 0x72, + 0x75, 0x6e, 0x5f, 0x61, 0x73, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x5e, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x58, 0x12, 0x56, 0x54, 0x68, 0x65, 0x20, 0x75, 0x73, + 0x65, 0x72, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x72, 0x6f, 0x6e, + 0x74, 0x65, 0x6e, 0x64, 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x72, 0x75, 0x6e, 0x20, + 0x61, 0x73, 0x2e, 0x20, 0x49, 0x66, 0x20, 0x73, 0x65, 0x74, 0x20, 0x77, 0x65, 0x20, 0x72, 0x65, + 0x66, 0x75, 0x73, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x72, 0x75, 0x6e, 0x20, 0x61, 0x73, 0x20, 0x61, + 0x20, 0x64, 0x69, 0x66, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x75, 0x73, 0x65, 0x72, 0x2e, + 0x52, 0x09, 0x72, 0x75, 0x6e, 0x41, 0x73, 0x55, 0x73, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x12, 0x47, + 0x52, 0x50, 0x43, 0x5f, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x69, 0x7a, + 0x65, 0x18, 0x11, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x47, 0x52, 0x50, 0x43, 0x50, 0x6f, 0x6f, + 0x6c, 0x4d, 0x61, 0x78, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x2b, 0x0a, 0x12, 0x47, 0x52, 0x50, 0x43, + 0x5f, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x77, 0x61, 0x69, 0x74, 0x18, 0x12, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x47, 0x52, 0x50, 0x43, 0x50, 0x6f, 0x6f, 0x6c, 0x4d, 0x61, + 0x78, 0x57, 0x61, 0x69, 0x74, 0x12, 0x44, 0x0a, 0x0f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x3c, 0x0a, 0x09, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x52, 0x09, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x73, 0x5f, + 0x6d, 0x69, 0x6e, 0x69, 0x6f, 0x6e, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x73, + 0x4d, 0x69, 0x6e, 0x69, 0x6f, 0x6e, 0x12, 0x60, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, + 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x42, 0x3e, 0xe2, 0xfc, 0xe3, + 0xc4, 0x01, 0x38, 0x12, 0x36, 0x54, 0x68, 0x65, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x20, + 0x6f, 0x66, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x20, 0x63, 0x6f, + 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x2e, 0x52, 0x0b, 0x63, 0x6f, 0x6e, + 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x60, 0x0a, 0x0f, 0x6d, 0x61, 0x78, 0x5f, + 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, + 0x04, 0x42, 0x38, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x32, 0x12, 0x30, 0x4d, 0x61, 0x78, 0x69, 0x6d, + 0x75, 0x6d, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x69, 0x6e, 0x20, 0x42, 0x79, 0x74, 0x65, 0x73, + 0x20, 0x77, 0x65, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x20, + 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x20, 0x66, 0x6f, 0x72, 0x52, 0x0d, 0x6d, 0x61, 0x78, + 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x70, 0x0a, 0x10, 0x65, 0x78, + 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x0f, + 0x20, 0x01, 0x28, 0x03, 0x42, 0x45, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x3f, 0x12, 0x3d, 0x45, 0x78, + 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x20, 0x6f, 0x66, + 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x69, 0x73, + 0x20, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x28, 0x64, 0x65, 0x66, + 0x61, 0x75, 0x6c, 0x74, 0x20, 0x31, 0x30, 0x30, 0x30, 0x30, 0x29, 0x52, 0x0f, 0x65, 0x78, 0x70, + 0x65, 0x63, 0x74, 0x65, 0x64, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x33, 0x0a, 0x16, + 0x70, 0x65, 0x72, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x75, 0x70, 0x6c, 0x6f, 0x61, + 0x64, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x15, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x70, 0x65, + 0x72, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x61, 0x74, + 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x5f, 0x75, 0x70, 0x6c, 0x6f, + 0x61, 0x64, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x16, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x67, + 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x61, 0x74, 0x65, 0x12, + 0x31, 0x0a, 0x15, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, + 0x6d, 0x61, 0x78, 0x5f, 0x77, 0x61, 0x69, 0x74, 0x18, 0x17, 0x20, 0x01, 0x28, 0x04, 0x52, 0x12, + 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x4d, 0x61, 0x78, 0x57, 0x61, + 0x69, 0x74, 0x12, 0x44, 0x0a, 0x1e, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x5f, 0x64, + 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x79, 0x18, 0x20, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1c, 0x61, 0x72, 0x74, 0x69, + 0x66, 0x61, 0x63, 0x74, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x44, + 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x26, 0x0a, 0x0f, 0x64, 0x6f, 0x5f, 0x6e, + 0x6f, 0x74, 0x5f, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x18, 0x1a, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0d, 0x64, 0x6f, 0x4e, 0x6f, 0x74, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, + 0x22, 0xb4, 0x03, 0x0a, 0x0f, 0x44, 0x61, 0x74, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x12, 0x26, 0x0a, 0x0e, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x69, 0x6d, + 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, + 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2f, 0x0a, 0x13, 0x66, 0x69, 0x6c, 0x65, + 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, + 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x36, 0x0a, 0x17, 0x6d, 0x65, 0x6d, + 0x63, 0x61, 0x63, 0x68, 0x65, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x73, 0x65, 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x15, 0x6d, 0x65, 0x6d, 0x63, + 0x61, 0x63, 0x68, 0x65, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, + 0x63, 0x12, 0x43, 0x0a, 0x1e, 0x6d, 0x65, 0x6d, 0x63, 0x61, 0x63, 0x68, 0x65, 0x5f, 0x77, 0x72, + 0x69, 0x74, 0x65, 0x5f, 0x6d, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x75, 0x66, + 0x66, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x1b, 0x6d, 0x65, 0x6d, 0x63, 0x61, + 0x63, 0x68, 0x65, 0x57, 0x72, 0x69, 0x74, 0x65, 0x4d, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x12, 0x45, 0x0a, 0x1f, 0x6d, 0x65, 0x6d, 0x63, 0x61, 0x63, + 0x68, 0x65, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x6d, 0x75, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x72, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x1c, 0x6d, 0x65, 0x6d, 0x63, 0x61, 0x63, 0x68, 0x65, 0x57, 0x72, 0x69, 0x74, 0x65, 0x4d, 0x75, + 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x72, 0x73, 0x12, 0x33, 0x0a, + 0x15, 0x6d, 0x69, 0x6e, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x6d, 0x69, + 0x6e, 0x69, 0x6f, 0x6e, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x33, 0x0a, 0x15, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x69, 0x6d, 0x70, + 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x14, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x89, 0x03, 0x0a, 0x0a, 0x4d, 0x61, 0x69, 0x6c, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x65, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x42, 0x51, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x4b, 0x12, 0x49, 0x57, 0x68, + 0x65, 0x72, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x20, 0x73, 0x68, + 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x74, 0x20, 0x66, 0x72, 0x6f, + 0x6d, 0x2e, 0x20, 0x49, 0x66, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x73, 0x65, 0x74, 0x20, 0x77, 0x65, + 0x20, 0x75, 0x73, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x75, 0x73, + 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x2e, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x3b, 0x0a, + 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x23, 0xe2, + 0xfc, 0xe3, 0xc4, 0x01, 0x1d, 0x12, 0x1b, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x20, 0x6f, + 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x53, 0x4d, 0x54, 0x50, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x2e, 0x52, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x40, 0x0a, 0x0b, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x42, + 0x1f, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x19, 0x12, 0x17, 0x50, 0x6f, 0x72, 0x74, 0x20, 0x6f, 0x66, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x53, 0x4d, 0x54, 0x50, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x52, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x48, 0x0a, 0x0d, + 0x61, 0x75, 0x74, 0x68, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x42, 0x23, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x1d, 0x12, 0x1b, 0x4e, 0x61, 0x6d, + 0x65, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, + 0x65, 0x20, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x2e, 0x52, 0x0c, 0x61, 0x75, 0x74, 0x68, 0x55, 0x73, + 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x4b, 0x0a, 0x0d, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, + 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x26, 0xe2, + 0xfc, 0xe3, 0xc4, 0x01, 0x20, 0x12, 0x1e, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x20, + 0x74, 0x6f, 0x20, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, + 0x77, 0x69, 0x74, 0x68, 0x2e, 0x52, 0x0c, 0x61, 0x75, 0x74, 0x68, 0x50, 0x61, 0x73, 0x73, 0x77, + 0x6f, 0x72, 0x64, 0x22, 0x72, 0x0a, 0x16, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x52, 0x65, + 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x23, 0x0a, + 0x0d, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, + 0x6d, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x6d, 0x61, 0x78, 0x5f, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x06, 0x6d, 0x61, 0x78, 0x41, 0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x64, + 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x64, + 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0xd9, 0x04, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x67, + 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x75, 0x0a, 0x10, 0x6f, 0x75, 0x74, + 0x70, 0x75, 0x74, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x42, 0x4a, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x44, 0x12, 0x42, 0x54, 0x68, 0x65, + 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x77, 0x72, 0x69, + 0x74, 0x65, 0x20, 0x6c, 0x6f, 0x67, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x20, 0x49, 0x66, + 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x73, 0x65, 0x74, 0x20, 0x77, 0x65, 0x20, 0x77, 0x72, 0x69, 0x74, + 0x65, 0x20, 0x6e, 0x6f, 0x20, 0x6c, 0x6f, 0x67, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x52, + 0x0f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, + 0x12, 0x7a, 0x0a, 0x1b, 0x73, 0x65, 0x70, 0x61, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x6c, 0x6f, 0x67, + 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x08, 0x42, 0x3b, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x35, 0x12, 0x33, 0x49, + 0x66, 0x20, 0x73, 0x65, 0x74, 0x2c, 0x20, 0x65, 0x61, 0x63, 0x68, 0x20, 0x63, 0x6f, 0x6d, 0x70, + 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x6c, 0x6f, 0x67, 0x20, 0x74, + 0x6f, 0x20, 0x61, 0x20, 0x73, 0x65, 0x70, 0x61, 0x72, 0x61, 0x74, 0x65, 0x20, 0x66, 0x69, 0x6c, + 0x65, 0x2e, 0x52, 0x18, 0x73, 0x65, 0x70, 0x61, 0x72, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, + 0x50, 0x65, 0x72, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x12, 0x4b, 0x0a, 0x0d, + 0x72, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x04, 0x42, 0x26, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x20, 0x12, 0x1e, 0x48, 0x6f, 0x77, + 0x20, 0x6f, 0x66, 0x74, 0x65, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x65, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x52, 0x0c, 0x72, 0x6f, 0x74, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x6b, 0x0a, 0x07, 0x6d, 0x61, 0x78, + 0x5f, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x42, 0x52, 0xe2, 0xfc, 0xe3, 0xc4, + 0x01, 0x4c, 0x12, 0x40, 0x4d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x20, 0x61, 0x67, 0x65, 0x20, + 0x6f, 0x66, 0x20, 0x65, 0x61, 0x63, 0x68, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x28, 0x46, 0x69, + 0x6c, 0x65, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x62, 0x65, 0x20, 0x64, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x64, 0x20, 0x61, 0x66, 0x74, 0x65, 0x72, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x74, 0x69, + 0x6d, 0x65, 0x29, 0x2e, 0x32, 0x08, 0x33, 0x31, 0x35, 0x33, 0x36, 0x30, 0x30, 0x30, 0x52, 0x06, + 0x6d, 0x61, 0x78, 0x41, 0x67, 0x65, 0x12, 0x33, 0x0a, 0x05, 0x64, 0x65, 0x62, 0x75, 0x67, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x33, 0x0a, 0x05, 0x65, 0x72, - 0x72, 0x6f, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6e, 0x66, 0x69, 0x67, 0x52, 0x05, 0x64, 0x65, 0x62, 0x75, 0x67, 0x12, 0x31, 0x0a, 0x04, 0x69, + 0x6e, 0x66, 0x6f, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, - 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, - 0xf8, 0x01, 0x0a, 0x10, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x12, 0x9f, 0x01, 0x0a, 0x0c, 0x62, 0x69, 0x6e, 0x64, 0x5f, 0x61, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x7c, 0xe2, 0xfc, 0xe3, - 0xc4, 0x01, 0x76, 0x12, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x20, 0x74, 0x6f, 0x20, - 0x62, 0x69, 0x6e, 0x64, 0x20, 0x6d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x20, - 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2e, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x73, - 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x75, 0x73, 0x75, 0x61, 0x6c, 0x6c, 0x79, 0x20, 0x6f, 0x6e, - 0x6c, 0x79, 0x20, 0x62, 0x65, 0x20, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x2c, - 0x20, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x77, 0x69, 0x73, 0x65, 0x20, 0x62, 0x65, 0x20, 0x73, 0x75, - 0x72, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x6c, 0x79, 0x20, 0x73, - 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x69, 0x74, 0x2e, 0x52, 0x0b, 0x62, 0x69, 0x6e, 0x64, 0x41, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x42, 0x0a, 0x09, 0x62, 0x69, 0x6e, 0x64, 0x5f, 0x70, - 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x25, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, - 0x1f, 0x12, 0x1d, 0x50, 0x6f, 0x72, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x20, - 0x6d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x70, 0x6f, 0x72, 0x74, 0x2e, - 0x52, 0x08, 0x62, 0x69, 0x6e, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x22, 0x68, 0x0a, 0x0e, 0x41, 0x75, - 0x74, 0x6f, 0x45, 0x78, 0x65, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, - 0x61, 0x72, 0x67, 0x76, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x61, 0x72, 0x67, 0x76, - 0x12, 0x42, 0x0a, 0x14, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x5f, 0x64, 0x65, 0x66, - 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x52, - 0x13, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xd0, 0x04, 0x0a, 0x14, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x21, 0x0a, - 0x0c, 0x68, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x0b, 0x68, 0x75, 0x6e, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, - 0x12, 0x27, 0x0a, 0x0f, 0x68, 0x75, 0x6e, 0x74, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, - 0x68, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x68, 0x75, 0x6e, 0x74, 0x44, - 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x74, 0x61, - 0x74, 0x73, 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, - 0x6f, 0x72, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x6d, 0x6f, 0x6e, - 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x12, - 0x29, 0x0a, 0x10, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, - 0x63, 0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x73, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x79, - 0x6e, 0x5f, 0x64, 0x6e, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x64, 0x79, 0x6e, - 0x44, 0x6e, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x72, 0x6f, 0x67, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x72, 0x6f, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x61, 0x6e, - 0x69, 0x74, 0x79, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x0d, 0x73, 0x61, 0x6e, 0x69, 0x74, 0x79, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x65, 0x72, - 0x12, 0x1f, 0x0a, 0x0b, 0x76, 0x66, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, - 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x76, 0x66, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, - 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x4d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x6d, - 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x10, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, - 0x67, 0x12, 0x2d, 0x0a, 0x12, 0x6d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x5f, - 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x6d, - 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x70, 0x69, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x0d, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x61, 0x70, 0x69, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, - 0x27, 0x0a, 0x0f, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x5f, 0x73, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x65, - 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x75, 0x69, 0x5f, - 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x67, 0x75, - 0x69, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x22, 0x71, 0x0a, 0x08, 0x44, 0x65, 0x66, 0x61, 0x75, - 0x6c, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x68, 0x75, 0x6e, 0x74, 0x5f, 0x65, 0x78, 0x70, 0x69, - 0x72, 0x79, 0x5f, 0x68, 0x6f, 0x75, 0x72, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, - 0x68, 0x75, 0x6e, 0x74, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x48, 0x6f, 0x75, 0x72, 0x73, 0x12, - 0x39, 0x0a, 0x19, 0x6e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x5f, 0x63, 0x65, 0x6c, 0x6c, - 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x6d, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x16, 0x6e, 0x6f, 0x74, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x43, 0x65, 0x6c, 0x6c, - 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x4d, 0x69, 0x6e, 0x22, 0xa7, 0x0b, 0x0a, 0x06, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2b, 0x0a, 0x0f, 0x61, 0x75, 0x74, 0x6f, 0x63, 0x65, 0x72, - 0x74, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, - 0x18, 0x01, 0x52, 0x0e, 0x61, 0x75, 0x74, 0x6f, 0x63, 0x65, 0x72, 0x74, 0x44, 0x6f, 0x6d, 0x61, - 0x69, 0x6e, 0x12, 0x46, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x42, 0x1c, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x16, 0x12, 0x14, 0x56, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x2e, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x4a, 0x0a, 0x06, 0x43, 0x6c, - 0x69, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, - 0x1d, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x17, 0x12, 0x15, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x06, - 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x50, 0x0a, 0x03, 0x41, 0x50, 0x49, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x50, 0x49, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x2c, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x26, 0x12, 0x24, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x6f, 0x72, - 0x20, 0x67, 0x52, 0x50, 0x43, 0x20, 0x41, 0x50, 0x49, 0x20, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, - 0x6e, 0x74, 0x2e, 0x52, 0x03, 0x41, 0x50, 0x49, 0x12, 0x22, 0x0a, 0x03, 0x47, 0x55, 0x49, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x55, - 0x49, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x03, 0x47, 0x55, 0x49, 0x12, 0x1f, 0x0a, 0x02, - 0x43, 0x41, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x43, 0x41, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x02, 0x43, 0x41, 0x12, 0x31, 0x0a, - 0x08, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x33, + 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x74, + 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x05, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x22, 0xf8, 0x01, 0x0a, 0x10, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, + 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x9f, 0x01, 0x0a, 0x0c, 0x62, 0x69, 0x6e, + 0x64, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, + 0x7c, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x76, 0x12, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x20, 0x74, 0x6f, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x20, 0x6d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, + 0x69, 0x6e, 0x67, 0x20, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2e, 0x20, 0x54, 0x68, + 0x69, 0x73, 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x75, 0x73, 0x75, 0x61, 0x6c, 0x6c, + 0x79, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x62, 0x65, 0x20, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, + 0x30, 0x2e, 0x31, 0x2c, 0x20, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x77, 0x69, 0x73, 0x65, 0x20, 0x62, + 0x65, 0x20, 0x73, 0x75, 0x72, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, + 0x6c, 0x79, 0x20, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x69, 0x74, 0x2e, 0x52, 0x0b, 0x62, + 0x69, 0x6e, 0x64, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x42, 0x0a, 0x09, 0x62, 0x69, + 0x6e, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x25, 0xe2, + 0xfc, 0xe3, 0xc4, 0x01, 0x1f, 0x12, 0x1d, 0x50, 0x6f, 0x72, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x62, + 0x69, 0x6e, 0x64, 0x20, 0x6d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x70, + 0x6f, 0x72, 0x74, 0x2e, 0x52, 0x08, 0x62, 0x69, 0x6e, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x22, 0x68, + 0x0a, 0x0e, 0x41, 0x75, 0x74, 0x6f, 0x45, 0x78, 0x65, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x12, 0x12, 0x0a, 0x04, 0x61, 0x72, 0x67, 0x76, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, + 0x61, 0x72, 0x67, 0x76, 0x12, 0x42, 0x0a, 0x14, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, + 0x5f, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x72, 0x74, 0x69, 0x66, + 0x61, 0x63, 0x74, 0x52, 0x13, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x44, 0x65, 0x66, + 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xf3, 0x04, 0x0a, 0x14, 0x53, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x12, 0x21, 0x0a, 0x0c, 0x68, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x68, 0x75, 0x6e, 0x74, 0x4d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x72, 0x12, 0x27, 0x0a, 0x0f, 0x68, 0x75, 0x6e, 0x74, 0x5f, 0x64, 0x69, 0x73, + 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x68, + 0x75, 0x6e, 0x74, 0x44, 0x69, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x12, 0x27, 0x0a, + 0x0f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x43, 0x6f, 0x6c, + 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x5f, 0x6d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x10, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, + 0x69, 0x6e, 0x67, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x61, 0x72, + 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x12, 0x17, + 0x0a, 0x07, 0x64, 0x79, 0x6e, 0x5f, 0x64, 0x6e, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x06, 0x64, 0x79, 0x6e, 0x44, 0x6e, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x72, 0x6f, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x72, 0x6f, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, + 0x0e, 0x73, 0x61, 0x6e, 0x69, 0x74, 0x79, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x65, 0x72, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x61, 0x6e, 0x69, 0x74, 0x79, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x76, 0x66, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x76, 0x66, 0x73, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x6d, 0x61, + 0x6e, 0x61, 0x67, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x75, 0x73, 0x65, + 0x72, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x5f, 0x6d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x10, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x6f, 0x6e, 0x69, 0x74, + 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x2d, 0x0a, 0x12, 0x6d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, + 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x11, 0x6d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x70, 0x69, 0x5f, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x61, 0x70, 0x69, 0x53, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x12, 0x27, 0x0a, 0x0f, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x5f, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x66, 0x72, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x1d, 0x0a, 0x0a, + 0x67, 0x75, 0x69, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x09, 0x67, 0x75, 0x69, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x69, + 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x10, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x22, 0x71, + 0x0a, 0x08, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x68, 0x75, + 0x6e, 0x74, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x5f, 0x68, 0x6f, 0x75, 0x72, 0x73, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x68, 0x75, 0x6e, 0x74, 0x45, 0x78, 0x70, 0x69, 0x72, + 0x79, 0x48, 0x6f, 0x75, 0x72, 0x73, 0x12, 0x39, 0x0a, 0x19, 0x6e, 0x6f, 0x74, 0x65, 0x62, 0x6f, + 0x6f, 0x6b, 0x5f, 0x63, 0x65, 0x6c, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, + 0x6d, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x16, 0x6e, 0x6f, 0x74, 0x65, 0x62, + 0x6f, 0x6f, 0x6b, 0x43, 0x65, 0x6c, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x4d, 0x69, + 0x6e, 0x22, 0xa7, 0x0b, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2b, 0x0a, 0x0f, + 0x61, 0x75, 0x74, 0x6f, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, + 0x15, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0e, 0x61, 0x75, 0x74, 0x6f, 0x63, + 0x65, 0x72, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x46, 0x0a, 0x07, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x1c, 0xe2, 0xfc, 0xe3, 0xc4, + 0x01, 0x16, 0x12, 0x14, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x6e, 0x66, 0x6f, + 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x4a, 0x0a, 0x06, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x1d, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x17, 0x12, 0x15, + 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x06, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x50, 0x0a, + 0x03, 0x41, 0x50, 0x49, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x41, 0x50, 0x49, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x2c, 0xe2, 0xfc, + 0xe3, 0xc4, 0x01, 0x26, 0x12, 0x24, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x67, 0x52, 0x50, 0x43, 0x20, 0x41, 0x50, 0x49, + 0x20, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2e, 0x52, 0x03, 0x41, 0x50, 0x49, 0x12, + 0x22, 0x0a, 0x03, 0x47, 0x55, 0x49, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x55, 0x49, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x03, + 0x47, 0x55, 0x49, 0x12, 0x1f, 0x0a, 0x02, 0x43, 0x41, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x0f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x41, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x52, 0x02, 0x43, 0x41, 0x12, 0x31, 0x0a, 0x08, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, + 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x46, + 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x12, 0x3d, 0x0a, 0x0e, 0x45, 0x78, 0x74, 0x72, 0x61, + 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x73, 0x18, 0x1f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, - 0x12, 0x3d, 0x0a, 0x0e, 0x45, 0x78, 0x74, 0x72, 0x61, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, - 0x64, 0x73, 0x18, 0x1f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, - 0x0e, 0x45, 0x78, 0x74, 0x72, 0x61, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x73, 0x12, - 0x34, 0x0a, 0x09, 0x44, 0x61, 0x74, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x73, - 0x74, 0x6f, 0x72, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x09, 0x44, 0x61, 0x74, 0x61, - 0x73, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x77, 0x0a, 0x09, 0x57, 0x72, 0x69, 0x74, 0x65, 0x62, 0x61, - 0x63, 0x6b, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x62, 0x61, 0x63, 0x6b, 0x42, 0x47, 0xe2, 0xfc, 0xe3, 0xc4, - 0x01, 0x41, 0x12, 0x3f, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x6f, 0x66, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x27, 0x73, 0x20, 0x73, 0x74, 0x61, - 0x74, 0x65, 0x20, 0x61, 0x73, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x62, 0x61, 0x63, 0x6b, 0x20, 0x66, 0x69, - 0x6c, 0x65, 0x2e, 0x52, 0x09, 0x57, 0x72, 0x69, 0x74, 0x65, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x25, - 0x0a, 0x04, 0x4d, 0x61, 0x69, 0x6c, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4d, 0x61, 0x69, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, - 0x04, 0x4d, 0x61, 0x69, 0x6c, 0x12, 0x2e, 0x0a, 0x07, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, - 0x18, 0x17, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4c, - 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, 0x4c, 0x6f, - 0x67, 0x67, 0x69, 0x6e, 0x67, 0x12, 0x40, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, - 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, 0x42, 0x26, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x20, 0x12, 0x1e, - 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x20, 0x6c, - 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x2e, 0x52, 0x07, - 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x12, 0x5c, 0x0a, 0x13, 0x61, 0x75, 0x74, 0x6f, 0x63, - 0x65, 0x72, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x18, 0x16, - 0x20, 0x01, 0x28, 0x09, 0x42, 0x2c, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x26, 0x12, 0x24, 0x50, 0x61, - 0x74, 0x68, 0x20, 0x74, 0x6f, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x20, 0x61, 0x75, 0x74, 0x6f, - 0x63, 0x65, 0x72, 0x74, 0x20, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, - 0x73, 0x2e, 0x52, 0x11, 0x61, 0x75, 0x74, 0x6f, 0x63, 0x65, 0x72, 0x74, 0x43, 0x65, 0x72, 0x74, - 0x43, 0x61, 0x63, 0x68, 0x65, 0x12, 0x6e, 0x0a, 0x0a, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, - 0x69, 0x6e, 0x67, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x42, 0x35, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x2f, 0x12, 0x2d, 0x57, 0x68, 0x65, 0x72, - 0x65, 0x20, 0x74, 0x6f, 0x20, 0x62, 0x69, 0x6e, 0x64, 0x20, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, - 0x68, 0x65, 0x75, 0x73, 0x20, 0x6d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x20, - 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2e, 0x52, 0x0a, 0x4d, 0x6f, 0x6e, 0x69, 0x74, - 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x7f, 0x0a, 0x0a, 0x61, 0x70, 0x69, 0x5f, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x41, 0x70, 0x69, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x42, 0x48, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x42, 0x12, 0x40, 0x49, 0x66, 0x20, 0x77, 0x65, - 0x20, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x70, 0x69, 0x20, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x20, 0x77, 0x65, 0x20, 0x6c, 0x6f, 0x61, 0x64, 0x20, 0x74, - 0x68, 0x69, 0x73, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x67, 0x6c, 0x6f, - 0x62, 0x61, 0x6c, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x52, 0x09, 0x61, 0x70, 0x69, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x8f, 0x01, 0x0a, 0x08, 0x61, 0x75, 0x74, 0x6f, 0x65, - 0x78, 0x65, 0x63, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x41, 0x75, 0x74, 0x6f, 0x45, 0x78, 0x65, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x42, 0x5c, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x56, 0x12, 0x54, 0x49, 0x66, 0x20, 0x74, 0x68, 0x69, - 0x73, 0x20, 0x69, 0x73, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x77, - 0x65, 0x20, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x62, 0x69, 0x6e, - 0x61, 0x72, 0x79, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x67, 0x69, 0x76, - 0x65, 0x6e, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x20, - 0x61, 0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x6c, 0x79, 0x2e, 0x52, 0x08, - 0x61, 0x75, 0x74, 0x6f, 0x65, 0x78, 0x65, 0x63, 0x12, 0x50, 0x0a, 0x0b, 0x73, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x42, 0x2f, 0xe2, - 0xfc, 0xe3, 0xc4, 0x01, 0x29, 0x12, 0x27, 0x54, 0x79, 0x70, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x28, 0x6c, 0x69, 0x6e, 0x75, 0x78, 0x2c, 0x20, 0x77, 0x69, - 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x2c, 0x20, 0x64, 0x61, 0x72, 0x77, 0x69, 0x6e, 0x29, 0x52, 0x0a, - 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x62, - 0x66, 0x75, 0x73, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, - 0x20, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x62, 0x66, 0x75, 0x73, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x2b, 0x0a, 0x08, 0x64, 0x65, 0x66, 0x61, 0x75, - 0x6c, 0x74, 0x73, 0x18, 0x21, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x52, 0x08, 0x64, 0x65, 0x66, 0x61, - 0x75, 0x6c, 0x74, 0x73, 0x42, 0x34, 0x5a, 0x32, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x65, 0x6c, 0x6f, - 0x63, 0x69, 0x64, 0x65, 0x78, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, - 0x2f, 0x76, 0x65, 0x6c, 0x6f, 0x63, 0x69, 0x72, 0x61, 0x70, 0x74, 0x6f, 0x72, 0x2f, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x45, 0x78, 0x74, 0x72, 0x61, 0x46, 0x72, 0x6f, + 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x73, 0x12, 0x34, 0x0a, 0x09, 0x44, 0x61, 0x74, 0x61, 0x73, 0x74, + 0x6f, 0x72, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x52, 0x09, 0x44, 0x61, 0x74, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x77, 0x0a, 0x09, + 0x57, 0x72, 0x69, 0x74, 0x65, 0x62, 0x61, 0x63, 0x6b, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x62, 0x61, 0x63, + 0x6b, 0x42, 0x47, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x41, 0x12, 0x3f, 0x43, 0x6f, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x27, 0x73, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65, 0x20, 0x61, 0x73, 0x20, 0x73, 0x74, 0x6f, + 0x72, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, + 0x62, 0x61, 0x63, 0x6b, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x52, 0x09, 0x57, 0x72, 0x69, 0x74, + 0x65, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x25, 0x0a, 0x04, 0x4d, 0x61, 0x69, 0x6c, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4d, 0x61, 0x69, 0x6c, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x04, 0x4d, 0x61, 0x69, 0x6c, 0x12, 0x2e, 0x0a, 0x07, + 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x52, 0x07, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x12, 0x40, 0x0a, 0x07, + 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, 0x42, 0x26, 0xe2, + 0xfc, 0xe3, 0xc4, 0x01, 0x20, 0x12, 0x1e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x76, 0x65, + 0x72, 0x62, 0x6f, 0x73, 0x65, 0x20, 0x6c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x2e, 0x52, 0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x12, 0x5c, + 0x0a, 0x13, 0x61, 0x75, 0x74, 0x6f, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x5f, + 0x63, 0x61, 0x63, 0x68, 0x65, 0x18, 0x16, 0x20, 0x01, 0x28, 0x09, 0x42, 0x2c, 0xe2, 0xfc, 0xe3, + 0xc4, 0x01, 0x26, 0x12, 0x24, 0x50, 0x61, 0x74, 0x68, 0x20, 0x74, 0x6f, 0x20, 0x73, 0x74, 0x6f, + 0x72, 0x65, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x63, 0x65, 0x72, 0x74, 0x20, 0x63, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, 0x52, 0x11, 0x61, 0x75, 0x74, 0x6f, 0x63, + 0x65, 0x72, 0x74, 0x43, 0x65, 0x72, 0x74, 0x43, 0x61, 0x63, 0x68, 0x65, 0x12, 0x6e, 0x0a, 0x0a, + 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, + 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x35, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, + 0x2f, 0x12, 0x2d, 0x57, 0x68, 0x65, 0x72, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x62, 0x69, 0x6e, 0x64, + 0x20, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x20, 0x6d, 0x6f, 0x6e, 0x69, + 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x2e, + 0x52, 0x0a, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x7f, 0x0a, 0x0a, + 0x61, 0x70, 0x69, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x70, 0x69, 0x43, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x48, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x42, + 0x12, 0x40, 0x49, 0x66, 0x20, 0x77, 0x65, 0x20, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x61, 0x70, 0x69, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x20, 0x77, 0x65, + 0x20, 0x6c, 0x6f, 0x61, 0x64, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x2e, 0x52, 0x09, 0x61, 0x70, 0x69, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x8f, 0x01, + 0x0a, 0x08, 0x61, 0x75, 0x74, 0x6f, 0x65, 0x78, 0x65, 0x63, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x75, 0x74, 0x6f, 0x45, 0x78, 0x65, + 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x5c, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x56, 0x12, + 0x54, 0x49, 0x66, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x73, 0x70, 0x65, 0x63, + 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x77, 0x65, 0x20, 0x6c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x67, 0x69, 0x76, 0x65, 0x6e, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, + 0x64, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x69, 0x63, + 0x61, 0x6c, 0x6c, 0x79, 0x2e, 0x52, 0x08, 0x61, 0x75, 0x74, 0x6f, 0x65, 0x78, 0x65, 0x63, 0x12, + 0x50, 0x0a, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x1e, + 0x20, 0x01, 0x28, 0x09, 0x42, 0x2f, 0xe2, 0xfc, 0xe3, 0xc4, 0x01, 0x29, 0x12, 0x27, 0x54, 0x79, + 0x70, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x28, 0x6c, 0x69, + 0x6e, 0x75, 0x78, 0x2c, 0x20, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x2c, 0x20, 0x64, 0x61, + 0x72, 0x77, 0x69, 0x6e, 0x29, 0x52, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x62, 0x66, 0x75, 0x73, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x20, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x62, + 0x66, 0x75, 0x73, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x2b, + 0x0a, 0x08, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x21, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x0f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, + 0x73, 0x52, 0x08, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x42, 0x34, 0x5a, 0x32, 0x77, + 0x77, 0x77, 0x2e, 0x76, 0x65, 0x6c, 0x6f, 0x63, 0x69, 0x64, 0x65, 0x78, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x76, 0x65, 0x6c, 0x6f, 0x63, 0x69, 0x72, 0x61, + 0x70, 0x74, 0x6f, 0x72, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/config/proto/config.proto b/config/proto/config.proto index 7bf5aff0f08..7f7ebff0b02 100644 --- a/config/proto/config.proto +++ b/config/proto/config.proto @@ -453,6 +453,11 @@ message FrontendResourceControl { // less latency to respond to client events but also means more // TLS handshake and network overheads due to frequent POST. uint64 client_event_max_wait = 23; + + // Minions batch updates to the master so as to minimize RPC as + // much as possible, this controls how often these batches are + // flushed to the master (default 10 sec). + uint64 minion_batch_wait_time_ms = 25; } @@ -552,7 +557,7 @@ message FrontendConfig { FrontendResourceControl resources = 27; // Used internally to tag this frontend as the master. - bool is_master = 30; + bool is_minion = 30; // DEPRECATED **************************************************** @@ -620,8 +625,12 @@ message DatastoreConfig { int64 memcache_write_mutation_buffer = 5; // Number of writing threads - increase for high latency - // filesystems. + // filesystems (default 100). int64 memcache_write_mutation_writers = 6; + + // Experimental - do not set in configs yet! + string minion_implementation = 7; + string master_implementation = 8; } // Configuration for the mail server. @@ -714,6 +723,7 @@ message ServerServicesConfig { bool api_server = 13; bool frontend_server = 14; bool gui_server = 15; + bool index_server = 16; } message Defaults { diff --git a/datastore/datastore.go b/datastore/datastore.go index acb157b1fdd..3b146f8f4fa 100644 --- a/datastore/datastore.go +++ b/datastore/datastore.go @@ -20,6 +20,7 @@ package datastore import ( "errors" + "sync" "time" "google.golang.org/protobuf/proto" @@ -29,6 +30,12 @@ import ( var ( StopIteration = errors.New("StopIteration") + + // Cache the datastore implementations. The datastore is + // essentially a singleton determined by the configuration at + // start time. + ds_mu sync.Mutex + g_impl DataStore ) type SortingSense int @@ -50,7 +57,8 @@ type WalkFunc func(urn api.DSPathSpec) error // Raw level access only used internally rarely. type RawDataStore interface { GetBuffer(config_obj *config_proto.Config, urn api.DSPathSpec) ([]byte, error) - SetBuffer(config_obj *config_proto.Config, urn api.DSPathSpec, data []byte) error + SetBuffer(config_obj *config_proto.Config, urn api.DSPathSpec, + data []byte, completion func()) error } type DataStore interface { @@ -62,11 +70,25 @@ type DataStore interface { urn api.DSPathSpec, message proto.Message) error + // SetSubject writes the data to the datastore. The data is + // written asynchronously and may not be immediately visible by + // other nodes. SetSubject( config_obj *config_proto.Config, urn api.DSPathSpec, message proto.Message) error + // Writes the data asynchronously and fires the completion + // callback when the data hits the disk and will become visibile + // to other nodes. + SetSubjectWithCompletion( + config_obj *config_proto.Config, + urn api.DSPathSpec, + message proto.Message, + completion func()) error + + // DeleteSubject will asynchronously remove the item from the data + // store. DeleteSubject( config_obj *config_proto.Config, urn api.DSPathSpec) error @@ -76,9 +98,6 @@ type DataStore interface { config_obj *config_proto.Config, urn api.DSPathSpec) ([]api.DSPathSpec, error) - Walk(config_obj *config_proto.Config, - root api.DSPathSpec, walkFn WalkFunc) error - Debug(config_obj *config_proto.Config) // Called to close all db handles etc. Not thread safe. @@ -86,29 +105,45 @@ type DataStore interface { } func GetDB(config_obj *config_proto.Config) (DataStore, error) { + ds_mu.Lock() + defer ds_mu.Unlock() + + if g_impl != nil { + return g_impl, nil + } + if config_obj.Datastore == nil { return nil, errors.New("no datastore configured") } - switch config_obj.Datastore.Implementation { - case "FileBaseDataStore": - if config_obj.Datastore.Location == "" { - return nil, errors.New( - "No Datastore_location is set in the config.") - } + return getImpl(config_obj.Datastore.Implementation, config_obj) +} +func getImpl(implementation string, + config_obj *config_proto.Config) (DataStore, error) { + switch implementation { + case "FileBaseDataStore": return file_based_imp, nil case "RemoteFileDataStore": return remote_datastopre_imp, nil case "Memcache": + if memcache_imp == nil { + memcache_imp = NewMemcacheDataStore() + } return memcache_imp, nil case "MemcacheFileDataStore": + if memcache_file_imp == nil { + memcache_file_imp = NewMemcacheFileDataStore() + } return memcache_file_imp, nil case "Test": + if memcache_imp == nil { + memcache_imp = NewMemcacheDataStore() + } return memcache_imp, nil default: @@ -116,3 +151,13 @@ func GetDB(config_obj *config_proto.Config) (DataStore, error) { config_obj.Datastore.Implementation) } } + +func SetGlobalDatastore( + implementation string, + config_obj *config_proto.Config) (err error) { + ds_mu.Lock() + defer ds_mu.Unlock() + + g_impl, err = getImpl(implementation, config_obj) + return err +} diff --git a/datastore/datastore_test.go b/datastore/datastore_test.go index e6f857fdf7e..e7f56f143dc 100644 --- a/datastore/datastore_test.go +++ b/datastore/datastore_test.go @@ -182,7 +182,7 @@ func (self BaseTestSuite) TestListChildren() { "/a/b/c/3"}, asStrings(children)) visited := []api.DSPathSpec{} - self.datastore.Walk(self.config_obj, + Walk(self.config_obj, self.datastore, path_specs.NewSafeDatastorePath("a", "b"), func(path_name api.DSPathSpec) error { visited = append(visited, path_name) @@ -223,7 +223,7 @@ func (self BaseTestSuite) TestListChildrenTypes() { "/a/b/c/3:dir"}, asStringsWithTypes(children)) visited := []api.DSPathSpec{} - self.datastore.Walk(self.config_obj, + Walk(self.config_obj, self.datastore, path_specs.NewSafeDatastorePath("a", "b"), func(path_name api.DSPathSpec) error { visited = append(visited, path_name) @@ -270,7 +270,7 @@ func (self BaseTestSuite) TestUnsafeListChildren() { "/a/b:b/c:b/3"}, asStrings(children)) visited := []api.DSPathSpec{} - self.datastore.Walk(self.config_obj, + Walk(self.config_obj, self.datastore, root, func(path_name api.DSPathSpec) error { visited = append(visited, path_name) diff --git a/datastore/filebased.go b/datastore/filebased.go index 75228731f4f..fbe5542d5fe 100644 --- a/datastore/filebased.go +++ b/datastore/filebased.go @@ -121,35 +121,6 @@ func (self *FileBaseDataStore) GetSubject( return nil } -func (self *FileBaseDataStore) Walk(config_obj *config_proto.Config, - root api.DSPathSpec, walkFn WalkFunc) error { - - TraceDirectory(config_obj, "Walk", root) - all_children, err := self.ListChildren(config_obj, root) - if err != nil { - return err - } - - for _, child := range all_children { - // Recurse into directories - if child.IsDir() { - err := self.Walk(config_obj, child, walkFn) - if err != nil { - // Do not quit the walk early. - } - - } else { - err := walkFn(child) - if err == StopIteration { - return nil - } - continue - } - } - - return nil -} - func (self *FileBaseDataStore) Debug(config_obj *config_proto.Config) { filepath.Walk(config_obj.Datastore.Location, func(path string, info fs.FileInfo, err error) error { @@ -162,6 +133,13 @@ func (self *FileBaseDataStore) SetSubject( config_obj *config_proto.Config, urn api.DSPathSpec, message proto.Message) error { + return self.SetSubjectWithCompletion(config_obj, urn, message, nil) +} + +func (self *FileBaseDataStore) SetSubjectWithCompletion( + config_obj *config_proto.Config, + urn api.DSPathSpec, + message proto.Message, completion func()) error { defer InstrumentWithDelay("write", "FileBaseDataStore", urn)() @@ -180,7 +158,11 @@ func (self *FileBaseDataStore) SetSubject( return errors.WithStack(err) } - return writeContentToFile(config_obj, urn, serialized_content) + err = writeContentToFile(config_obj, urn, serialized_content) + if completion != nil { + completion() + } + return err } func (self *FileBaseDataStore) DeleteSubject( @@ -400,7 +382,12 @@ func (self *FileBaseDataStore) GetBuffer( func (self *FileBaseDataStore) SetBuffer( config_obj *config_proto.Config, - urn api.DSPathSpec, data []byte) error { + urn api.DSPathSpec, data []byte, completion func()) error { + + err := writeContentToFile(config_obj, urn, data) - return writeContentToFile(config_obj, urn, data) + if completion != nil { + completion() + } + return err } diff --git a/datastore/filebased_test.go b/datastore/filebased_test.go index c70b161f2de..47adda37f9f 100644 --- a/datastore/filebased_test.go +++ b/datastore/filebased_test.go @@ -30,12 +30,12 @@ func (self FilebasedTestSuite) DumpDirectory() { func (self FilebasedTestSuite) TestSetGetSubjectWithEscaping() { self.BaseTestSuite.TestSetGetSubjectWithEscaping() - self.DumpDirectory() + // self.DumpDirectory() } func (self FilebasedTestSuite) TestSetGetJSON() { self.BaseTestSuite.TestSetGetJSON() - self.DumpDirectory() + // self.DumpDirectory() } func (self *FilebasedTestSuite) SetupTest() { diff --git a/datastore/instrument.go b/datastore/instrument.go index 8966358702f..f492a3da7d6 100644 --- a/datastore/instrument.go +++ b/datastore/instrument.go @@ -1,6 +1,8 @@ package datastore import ( + "os" + "strconv" "time" "github.com/prometheus/client_golang/prometheus" @@ -19,7 +21,6 @@ var ( ) // Simulate running on a very slow filesystem (EFS) - //inject_time = 70 inject_time = 0 ) @@ -57,3 +58,13 @@ func InstrumentWithDelay( return timer.ObserveDuration } + +func init() { + delay_str, pres := os.LookupEnv("VELOCIRAPTOR_SLOW_FILESYSTEM") + if pres { + delay, err := strconv.Atoi(delay_str) + if err == nil { + inject_time = delay + } + } +} diff --git a/datastore/memcache.go b/datastore/memcache.go index 421e2e39428..17f9756f9f6 100644 --- a/datastore/memcache.go +++ b/datastore/memcache.go @@ -11,7 +11,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" - "github.com/ReneKroon/ttlcache/v2" + "github.com/Velocidex/ttlcache/v2" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" config_proto "www.velocidex.com/golang/velociraptor/config/proto" @@ -19,7 +19,7 @@ import ( ) var ( - memcache_imp = NewMemcacheDataStore() + memcache_imp DataStore internalError = errors.New("Internal datastore error") errorNotFound = errors.New("Not found") @@ -259,6 +259,15 @@ func (self *MemcacheDatastore) SetSubject( urn api.DSPathSpec, message proto.Message) error { + return self.SetSubjectWithCompletion(config_obj, urn, message, nil) +} + +func (self *MemcacheDatastore) SetSubjectWithCompletion( + config_obj *config_proto.Config, + urn api.DSPathSpec, + message proto.Message, + completion func()) error { + defer Instrument("write", "MemcacheDatastore", urn)() var value []byte @@ -277,13 +286,16 @@ func (self *MemcacheDatastore) SetSubject( return err } - return self.SetData(config_obj, urn, value) + err = self.SetData(config_obj, urn, value) + if completion != nil { + completion() + } + return err } func (self *MemcacheDatastore) SetData( config_obj *config_proto.Config, - urn api.DSPathSpec, - data []byte) (err error) { + urn api.DSPathSpec, data []byte) (err error) { // Get new dir metadata md, err := self.get_dir_metadata(self.dir_cache, config_obj, urn.Dir()) @@ -299,9 +311,10 @@ func (self *MemcacheDatastore) SetData( parent_path := urn.Dir().AsDatastoreDirectory(config_obj) self.dir_cache.Set(parent_path, md) - return self.data_cache.Set(urn.AsClientPath(), &BulkData{ + err = self.data_cache.Set(urn.AsClientPath(), &BulkData{ data: data, }) + return err } func (self *MemcacheDatastore) DeleteSubject( @@ -370,33 +383,6 @@ func (self *MemcacheDatastore) ListChildren( return result, nil } -func (self *MemcacheDatastore) Walk(config_obj *config_proto.Config, - root api.DSPathSpec, walkFn WalkFunc) error { - - all_children, err := self.ListChildren(config_obj, root) - if err != nil { - return err - } - - for _, child := range all_children { - // Recurse into directories - if child.IsDir() { - err := self.Walk(config_obj, child, walkFn) - if err != nil { - // Do not quit the walk early. - } - } else { - err := walkFn(child) - if err == StopIteration { - return nil - } - continue - } - } - - return nil -} - // Called to close all db handles etc. Not thread safe. func (self *MemcacheDatastore) Close() {} @@ -423,9 +409,13 @@ func (self *MemcacheDatastore) GetBuffer( func (self *MemcacheDatastore) SetBuffer( config_obj *config_proto.Config, - urn api.DSPathSpec, data []byte) error { + urn api.DSPathSpec, data []byte, completion func()) error { - return self.SetData(config_obj, urn, data) + err := self.SetData(config_obj, urn, data) + if completion != nil { + completion() + } + return err } func (self *MemcacheDatastore) Debug(config_obj *config_proto.Config) { diff --git a/datastore/memcache_file.go b/datastore/memcache_file.go index d5bc188b46e..e4ccbbee91a 100644 --- a/datastore/memcache_file.go +++ b/datastore/memcache_file.go @@ -28,7 +28,7 @@ import ( ) var ( - memcache_file_imp = NewMemcacheFileDataStore() + memcache_file_imp *MemcacheFileDataStore metricLRUHit = promauto.NewCounter( prometheus.CounterOpts{ @@ -41,6 +41,12 @@ var ( Name: "memcache_lru_miss", Help: "LRU for memcache", }) + + metricIdleWriters = promauto.NewGauge( + prometheus.GaugeOpts{ + Name: "memcache_idle_writers", + Help: "Total available writers ready right now", + }) ) const ( @@ -54,6 +60,9 @@ type Mutation struct { urn api.DSPathSpec wg *sync.WaitGroup data []byte + + // Will run when committed to disk. + completion func() } type MemcacheFileDataStore struct { @@ -110,11 +119,13 @@ func (self *MemcacheFileDataStore) StartWriter( self.ctx = ctx if writers == 0 { - writers = 5 + writers = 100 } // Start some writers. for i := 0; i < writers; i++ { + metricIdleWriters.Inc() + wg.Add(1) go func() { defer wg.Done() @@ -129,15 +140,24 @@ func (self *MemcacheFileDataStore) StartWriter( return } + metricIdleWriters.Dec() switch mutation.op { case MUTATION_OP_SET_SUBJECT: writeContentToFile(config_obj, mutation.urn, mutation.data) self.invalidateDirCache(config_obj, mutation.urn) + // Call the completion function once we hit + // the directory datastore. + if mutation.completion != nil { + mutation.completion() + } + case MUTATION_OP_DEL_SUBJECT: file_based_imp.DeleteSubject(config_obj, mutation.urn) self.invalidateDirCache(config_obj, mutation.urn.Dir()) } + + metricIdleWriters.Inc() mutation.wg.Done() } } @@ -180,6 +200,15 @@ func (self *MemcacheFileDataStore) SetSubject( urn api.DSPathSpec, message proto.Message) error { + return self.SetSubjectWithCompletion(config_obj, urn, message, nil) +} + +func (self *MemcacheFileDataStore) SetSubjectWithCompletion( + config_obj *config_proto.Config, + urn api.DSPathSpec, + message proto.Message, + completion func()) error { + defer Instrument("write", "MemcacheFileDataStore", urn)() // Encode as JSON @@ -211,10 +240,11 @@ func (self *MemcacheFileDataStore) SetSubject( return nil case self.writer <- &Mutation{ - op: MUTATION_OP_SET_SUBJECT, - urn: urn, - wg: &wg, - data: serialized_content}: + op: MUTATION_OP_SET_SUBJECT, + urn: urn, + wg: &wg, + completion: completion, + data: serialized_content}: } if config_obj.Datastore.MemcacheWriteMutationBuffer < 0 { @@ -277,33 +307,6 @@ func (self *MemcacheFileDataStore) ListChildren( return children, err } -func (self *MemcacheFileDataStore) Walk(config_obj *config_proto.Config, - root api.DSPathSpec, walkFn WalkFunc) error { - - all_children, err := self.ListChildren(config_obj, root) - if err != nil { - return err - } - - for _, child := range all_children { - // Recurse into directories - if child.IsDir() { - err := self.Walk(config_obj, child, walkFn) - if err != nil { - // Do not quit the walk early. - } - } else { - err := walkFn(child) - if err == StopIteration { - return nil - } - continue - } - } - - return nil -} - func (self *MemcacheFileDataStore) Close() { self.cache.Close() } @@ -345,7 +348,7 @@ func (self *MemcacheFileDataStore) GetBuffer( func (self *MemcacheFileDataStore) SetBuffer( config_obj *config_proto.Config, - urn api.DSPathSpec, data []byte) error { + urn api.DSPathSpec, data []byte, completion func()) error { err := self.cache.SetData(config_obj, urn, data) if err != nil { @@ -359,10 +362,12 @@ func (self *MemcacheFileDataStore) SetBuffer( return nil case self.writer <- &Mutation{ - op: MUTATION_OP_SET_SUBJECT, - urn: urn, - wg: &wg, - data: data}: + op: MUTATION_OP_SET_SUBJECT, + urn: urn, + wg: &wg, + data: data, + completion: completion, + }: } if config_obj.Datastore.MemcacheWriteMutationBuffer < 0 { @@ -417,8 +422,8 @@ func NewMemcacheFileDataStore() *MemcacheFileDataStore { func StartMemcacheFileService( ctx context.Context, wg *sync.WaitGroup, config_obj *config_proto.Config) error { - if config_obj.Datastore != nil && - config_obj.Datastore.Implementation == "MemcacheFileDataStore" { + + if memcache_file_imp != nil { logger := logging.GetLogger(config_obj, &logging.FrontendComponent) logger.Info("Starting memcache service") memcache_file_imp.StartWriter(ctx, wg, config_obj) diff --git a/datastore/memcache_file_test.go b/datastore/memcache_file_test.go index 7abe233f88d..334255f098c 100644 --- a/datastore/memcache_file_test.go +++ b/datastore/memcache_file_test.go @@ -15,7 +15,6 @@ import ( "www.velocidex.com/golang/velociraptor/file_store/path_specs" flows_proto "www.velocidex.com/golang/velociraptor/flows/proto" "www.velocidex.com/golang/velociraptor/paths" - "www.velocidex.com/golang/velociraptor/utils" "www.velocidex.com/golang/velociraptor/vtesting" ) @@ -130,9 +129,8 @@ func (self MemcacheFileTestSuite) TestListChildren() { intermediate := path_specs.NewSafeDatastorePath("a") children, err := self.datastore.ListChildren(self.config_obj, intermediate) assert.NoError(self.T(), err) - utils.Debug(children) - - file_based_imp.Debug(self.config_obj) + assert.Equal(self.T(), len(children), 1) + assert.Equal(self.T(), children[0].AsClientPath(), "/a/b") } func TestMemCacheFileDatastore(t *testing.T) { diff --git a/datastore/memory.go b/datastore/memory.go deleted file mode 100644 index 1165cd502d6..00000000000 --- a/datastore/memory.go +++ /dev/null @@ -1,306 +0,0 @@ -// +build XXX - -package datastore - -/* - An in-memory data store for tests. -*/ - -import ( - "fmt" - "os" - "path" - "runtime" - "sort" - "strings" - "sync" - - errors "github.com/pkg/errors" - - "google.golang.org/protobuf/proto" - config_proto "www.velocidex.com/golang/velociraptor/config/proto" - crypto_proto "www.velocidex.com/golang/velociraptor/crypto/proto" - "www.velocidex.com/golang/velociraptor/file_store/api" - "www.velocidex.com/golang/velociraptor/file_store/path_specs" - "www.velocidex.com/golang/velociraptor/json" - "www.velocidex.com/golang/velociraptor/utils" -) - -var ( - gTestDatastore = NewTestDataStore() -) - -type TestDataStore struct { - mu sync.Mutex - - idx uint64 - Subjects map[string]proto.Message - Components map[string][]api.DSPathSpec - ClientTasks map[string][]*crypto_proto.VeloMessage - - clock utils.Clock -} - -func NewTestDataStore() *TestDataStore { - return &TestDataStore{ - Subjects: make(map[string]proto.Message), - Components: make(map[string][]api.DSPathSpec), - ClientTasks: make(map[string][]*crypto_proto.VeloMessage), - } -} - -func (self *TestDataStore) Get(path string) proto.Message { - self.mu.Lock() - defer self.mu.Unlock() - - result, _ := self.Subjects[path] - return result -} - -func (self *TestDataStore) Clear() { - self.mu.Lock() - defer self.mu.Unlock() - - self.Subjects = make(map[string]proto.Message) - self.Components = make(map[string][]api.DSPathSpec) - self.ClientTasks = make(map[string][]*crypto_proto.VeloMessage) -} - -func (self *TestDataStore) Debug() { - result := []string{} - - for k, v := range self.Subjects { - result = append(result, fmt.Sprintf("%v: %v", k, string( - json.MustMarshalIndent(v)))) - } - - fmt.Println(strings.Join(result, "\n")) -} - -// If child_components are a subpath of parent_components (i.e. are -// parent_components is an exact prefix of child_components) -func isSubPath(parent_components []string, child_components []string) bool { - if len(parent_components) > len(child_components) { - return false - } - - for i := 0; i < len(parent_components); i++ { - if parent_components[i] != child_components[i] { - return false - } - } - return true -} - -func (self *TestDataStore) Walk( - config_obj *config_proto.Config, - root api.DSPathSpec, walkFn WalkFunc) error { - - self.mu.Lock() - result_path_specs := []api.DSPathSpec{} - root_components := root.Components() - for k := range self.Subjects { - components := self.Components[k] - if !isSubPath(root_components, components) { - continue - } - - result_path_specs = append(result_path_specs, - path_specs.NewSafeDatastorePath(components...)) - } - self.mu.Unlock() - - // Sort entries by name - sort.Slice(result_path_specs, func(i, j int) bool { - return result_path_specs[i].Base() < result_path_specs[j].Base() - }) - - for _, path_spec := range result_path_specs { - err := walkFn(path_spec) - if err == StopIteration { - return err - } - } - - return nil -} - -func (self *TestDataStore) Trace(name, filename string) { - return - fmt.Printf("Trace TestDataStore: %v: %v\n", name, filename) -} - -func (self *TestDataStore) GetSubject( - config_obj *config_proto.Config, - urn api.DSPathSpec, - message proto.Message) error { - self.mu.Lock() - defer self.mu.Unlock() - - defer Instrument("read", urn)() - - path := pathSpecToPath(urn, config_obj) - self.Trace("GetSubject", path) - result, pres := self.Subjects[path] - if !pres { - fallback_path := pathSpecToPath( - urn.SetType(api.PATH_TYPE_DATASTORE_PROTO), config_obj) - result, pres = self.Subjects[fallback_path] - if !pres { - return errors.WithMessage(os.ErrNotExist, - fmt.Sprintf("While opening %v: not found", - urn.AsClientPath())) - } - } - proto.Merge(message, result) - return nil -} - -func (self *TestDataStore) SetSubject( - config_obj *config_proto.Config, - urn api.DSPathSpec, - message proto.Message) error { - self.mu.Lock() - defer self.mu.Unlock() - - defer Instrument("write", urn)() - - filename := pathSpecToPath(urn, config_obj) - self.Trace("SetSubject", filename) - - self.Subjects[filename] = proto.Clone(message) - self.Components[filename] = urn - - return nil -} - -func (self *TestDataStore) DeleteSubject( - config_obj *config_proto.Config, - urn api.DSPathSpec) error { - self.mu.Lock() - defer self.mu.Unlock() - - defer Instrument("delete", urn)() - - filename := pathSpecToPath(urn, config_obj) - self.Trace("DeleteSubject", filename) - delete(self.Subjects, filename) - delete(self.Components, filename) - - return nil -} - -// Lists all the children of a URN. -func (self *TestDataStore) ListChildren( - config_obj *config_proto.Config, - urn api.DSPathSpec) ([]api.DSPathSpec, error) { - self.mu.Lock() - defer self.mu.Unlock() - - defer Instrument("list", urn)() - - self.Trace("ListChildren", pathDirSpecToPath(urn, config_obj)) - - seen_dirs := make(map[string]bool) - seen_files := make(map[string]bool) - root_components := urn.Components() - file_names := []string{} - dir_names := []string{} - for _, components := range self.Components { - if !isSubPath(root_components, components) { - continue - } - - // Deeper directories - if len(components) > len(root_components)+1 { - name := components[len(root_components)] - _, pres := seen_dirs[name] - if !pres { - dir_names = append(dir_names, name) - seen_dirs[name] = true - } - continue - } - - name := components[len(root_components)] - _, pres := seen_files[name] - if !pres { - file_names = append(file_names, name) - seen_files[name] = true - } - } - - sort.Strings(file_names) - sort.Strings(dir_names) - - result := make([]api.DSPathSpec, 0, len(dir_names)+len(file_names)) - for _, name := range dir_names { - result = append(result, urn.AddChild(name).SetDir()) - } - - for _, name := range file_names { - spec_type, child_name := api.GetDataStorePathTypeFromExtension(name) - result = append(result, urn.AddChild(child_name).SetType(spec_type)) - } - - return result, nil -} - -// List all direct children -func (self *TestDataStore) listChildren(urn api.DSPathSpec) []string { - seen := make(map[string]bool) - result := []string{} - - root_components := urn.Components() - for _, components := range self.Components { - if !isSubPath(root_components, components) { - continue - } - - if len(root_components) < len(components) { - direct_child := components[len(root_components)] - _, pres := seen[direct_child] - if !pres { - result = append(result, direct_child) - seen[direct_child] = true - } - } - } - return result -} - -// Called to close all db handles etc. Not thread safe. -func (self *TestDataStore) Close() { - mu.Lock() - defer mu.Unlock() - - gTestDatastore = NewTestDataStore() -} - -func pathSpecToPath(p api.DSPathSpec, - config_obj *config_proto.Config) string { - result := p.AsDatastoreFilename(config_obj) - - // Sanitize it on windows to convert back to a common format - // for comparisons. - if runtime.GOOS == "windows" { - return path.Clean(strings.Replace(strings.TrimPrefix( - result, path_specs.WINDOWS_LFN_PREFIX), "\\", "/", -1)) - } - - return result -} - -func pathDirSpecToPath(p api.DSPathSpec, - config_obj *config_proto.Config) string { - result := p.AsDatastoreDirectory(config_obj) - - // Sanitize it on windows to convert back to a common format - // for comparisons. - if runtime.GOOS == "windows" { - return path.Clean(strings.Replace(strings.TrimPrefix( - result, path_specs.WINDOWS_LFN_PREFIX), "\\", "/", -1)) - } - - return result -} diff --git a/datastore/memory_test.go b/datastore/memory_test.go deleted file mode 100644 index d94e3e102f1..00000000000 --- a/datastore/memory_test.go +++ /dev/null @@ -1,28 +0,0 @@ -// +build XXX - -package datastore - -import ( - "testing" - - "github.com/stretchr/testify/suite" - "www.velocidex.com/golang/velociraptor/config" -) - -type MemoryTestSuite struct { - BaseTestSuite -} - -func (self *MemoryTestSuite) SetupTest() { - self.datastore.(*TestDataStore).Clear() -} - -func TestMemoryDatastore(t *testing.T) { - config_obj := config.GetDefaultConfig() - config_obj.Datastore.Implementation = "Test" - - suite.Run(t, &MemoryTestSuite{BaseTestSuite{ - datastore: NewTestDataStore(), - config_obj: config_obj, - }}) -} diff --git a/datastore/remote.go b/datastore/remote.go index b0f135d28b0..d8b400351af 100644 --- a/datastore/remote.go +++ b/datastore/remote.go @@ -40,6 +40,7 @@ func (self *RemoteDataStore) GetSubject( Pathspec: &api_proto.DSPathSpec{ Components: urn.Components(), PathType: int64(urn.Type()), + Tag: urn.Tag(), }}) if err != nil { @@ -66,6 +67,15 @@ func (self *RemoteDataStore) SetSubject( urn api.DSPathSpec, message proto.Message) error { + return self.SetSubjectWithCompletion(config_obj, urn, message, nil) +} + +func (self *RemoteDataStore) SetSubjectWithCompletion( + config_obj *config_proto.Config, + urn api.DSPathSpec, + message proto.Message, + completion func()) error { + defer Instrument("write", "RemoteDataStore", urn)() var value []byte @@ -93,11 +103,17 @@ func (self *RemoteDataStore) SetSubject( _, err = conn.SetSubject(ctx, &api_proto.DataRequest{ Data: value, + Sync: completion != nil, Pathspec: &api_proto.DSPathSpec{ Components: urn.Components(), PathType: int64(urn.Type()), + Tag: urn.Tag(), }}) + if completion != nil { + completion() + } + return err } @@ -117,6 +133,7 @@ func (self *RemoteDataStore) DeleteSubject( Pathspec: &api_proto.DSPathSpec{ Components: urn.Components(), PathType: int64(urn.Type()), + Tag: urn.Tag(), }}) return err @@ -140,6 +157,7 @@ func (self *RemoteDataStore) ListChildren( Pathspec: &api_proto.DSPathSpec{ Components: urn.Components(), PathType: int64(urn.Type()), + Tag: urn.Tag(), }}) if err != nil { @@ -159,33 +177,6 @@ func (self *RemoteDataStore) ListChildren( return children, err } -func (self *RemoteDataStore) Walk(config_obj *config_proto.Config, - root api.DSPathSpec, walkFn WalkFunc) error { - - all_children, err := self.ListChildren(config_obj, root) - if err != nil { - return err - } - - for _, child := range all_children { - // Recurse into directories - if child.IsDir() { - err := self.Walk(config_obj, child, walkFn) - if err != nil { - // Do not quit the walk early. - } - } else { - err := walkFn(child) - if err == StopIteration { - return nil - } - continue - } - } - - return nil -} - // Called to close all db handles etc. Not thread safe. func (self *RemoteDataStore) Close() {} func (self *RemoteDataStore) Debug(config_obj *config_proto.Config) { diff --git a/datastore/utils.go b/datastore/utils.go new file mode 100644 index 00000000000..6df150cffae --- /dev/null +++ b/datastore/utils.go @@ -0,0 +1,76 @@ +package datastore + +import ( + "sync" + + "google.golang.org/protobuf/proto" + config_proto "www.velocidex.com/golang/velociraptor/config/proto" + "www.velocidex.com/golang/velociraptor/file_store/api" +) + +type MultiGetSubjectRequest struct { + Path api.DSPathSpec + Message proto.Message + Err error + + // Free form data that goes with the request. + Data interface{} +} + +// A helper function to read multipe subjects at the same time. +func MultiGetSubject( + config_obj *config_proto.Config, + requests []*MultiGetSubjectRequest) error { + + var mu sync.Mutex + + db, err := GetDB(config_obj) + if err != nil { + return err + } + + var wg sync.WaitGroup + mu.Lock() + for _, request := range requests { + wg.Add(1) + go func(request *MultiGetSubjectRequest) { + mu.Lock() + defer mu.Unlock() + request.Err = db.GetSubject(config_obj, request.Path, request.Message) + wg.Done() + }(request) + } + mu.Unlock() + + wg.Wait() + return nil +} + +func Walk(config_obj *config_proto.Config, + datastore DataStore, root api.DSPathSpec, walkFn WalkFunc) error { + + TraceDirectory(config_obj, "Walk", root) + all_children, err := datastore.ListChildren(config_obj, root) + if err != nil { + return err + } + + for _, child := range all_children { + // Recurse into directories + if child.IsDir() { + err := Walk(config_obj, datastore, child, walkFn) + if err != nil { + // Do not quit the walk early. + } + + } else { + err := walkFn(child) + if err == StopIteration { + return nil + } + continue + } + } + + return nil +} diff --git a/file_store/api/file_store.go b/file_store/api/file_store.go index ed35b2cfc21..f4607fa4ab4 100644 --- a/file_store/api/file_store.go +++ b/file_store/api/file_store.go @@ -37,6 +37,9 @@ type FileWriter interface { Write(data []byte) (int, error) Truncate() error Close() error + + // Force the writer to be flushed to disk immediately. + Flush() error } type FileInfo interface { @@ -44,13 +47,20 @@ type FileInfo interface { PathSpec() FSPathSpec } -type WalkFunc func(urn FSPathSpec, info os.FileInfo) error type FileStore interface { ReadFile(filename FSPathSpec) (FileReader, error) WriteFile(filename FSPathSpec) (FileWriter, error) + + // Completion function will be called when the file is committed. + WriteFileWithCompletion( + filename FSPathSpec, completion func()) (FileWriter, error) + StatFile(filename FSPathSpec) (FileInfo, error) ListDirectory(dirname FSPathSpec) ([]FileInfo, error) - Walk(root FSPathSpec, cb WalkFunc) error Delete(filename FSPathSpec) error Move(src, dest FSPathSpec) error } + +type Flusher interface { + Flush() +} diff --git a/file_store/api/instrument.go b/file_store/api/instrument.go new file mode 100644 index 00000000000..592942c5c56 --- /dev/null +++ b/file_store/api/instrument.go @@ -0,0 +1,108 @@ +package api + +import ( + "os" + "strconv" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "www.velocidex.com/golang/velociraptor/utils" +) + +var ( + FilestoreHistorgram = promauto.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "filestore_latency", + Help: "Latency to access filestore.", + Buckets: prometheus.LinearBuckets(0.01, 0.05, 10), + }, + []string{"tag", "action", "datastore"}, + ) + + // Simulate running on a very slow filesystem (EFS) + inject_time = 0 + + Clock utils.Clock = &utils.RealClock{} +) + +// Used by tests to force a delay (ms) +func SetFilestoreDelay(delay int) { + inject_time = delay +} + +// Tests use this to install a new clock and latency delays. +func InstallClockForTests(clock utils.Clock, delay int) func() { + old_delay := inject_time + old_clock := Clock + + inject_time = delay + Clock = clock + + return func() { + Clock = old_clock + inject_time = old_delay + } +} + +func Instrument(access_type, datastore string, path_spec FSPathSpec) func() time.Duration { + var tag string + if path_spec != nil { + tag = path_spec.Tag() + } + if tag == "" { + tag = "Generic" + } + + // Mark the start of the time. + start := Clock.Now() + + // When this func is called we calculate the time difference and + // observe it into the histogram. + return func() time.Duration { + d := Clock.Now().Sub(start) + FilestoreHistorgram.WithLabelValues( + tag, access_type, datastore).Observe(d.Seconds()) + return d + } +} + +func InstrumentWithDelay( + access_type, datastore string, path_spec FSPathSpec) func() time.Duration { + + var tag string + if path_spec != nil { + tag = path_spec.Tag() + } + if tag == "" { + tag = "Generic" + } + + // Mark the start of the time. + start := Clock.Now() + + // Instrument a delay in API calls. + if inject_time > 0 { + Clock.Sleep(time.Duration(inject_time) * time.Millisecond) + } + + // When this func is called we calculate the time difference and + // observe it into the histogram. + return func() time.Duration { + d := Clock.Now().Sub(start) + FilestoreHistorgram.WithLabelValues( + tag, access_type, datastore).Observe(d.Seconds()) + return d + } +} + +func init() { + // Allow the delay to be specified by the env var. + delay_str, pres := os.LookupEnv("VELOCIRAPTOR_SLOW_FILESYSTEM") + if pres { + delay, err := strconv.Atoi(delay_str) + if err == nil { + SetFilestoreDelay(delay) + } + } +} diff --git a/file_store/api/paths.go b/file_store/api/paths.go index 53f11488605..6eb813ac682 100644 --- a/file_store/api/paths.go +++ b/file_store/api/paths.go @@ -153,6 +153,9 @@ type FSPathSpec interface { SetType(t PathType) FSPathSpec + Tag() string + SetTag(string) FSPathSpec + // Adds a child maintaining safety (i.e. for a safe path keeps // the path safe, and for unsafe paths keep them as unsafe). AddChild(child ...string) FSPathSpec diff --git a/file_store/api/queues.go b/file_store/api/queues.go index 5bbd9446606..1bacd339e33 100644 --- a/file_store/api/queues.go +++ b/file_store/api/queues.go @@ -10,6 +10,10 @@ import ( // A QueueManager writes query results into queues. The manager is // responsible for rotating the queue files as required. type QueueManager interface { + // Broadcast events only for local listeners without writing to + // storage. + Broadcast(path_manager PathManager, rows []*ordereddict.Dict) + PushEventRows(path_manager PathManager, rows []*ordereddict.Dict) error Watch(ctx context.Context, queue_name string) ( output <-chan *ordereddict.Dict, cancel func()) diff --git a/file_store/api/walk.go b/file_store/api/walk.go new file mode 100644 index 00000000000..43dfb9f75e2 --- /dev/null +++ b/file_store/api/walk.go @@ -0,0 +1,31 @@ +package api + +import "os" + +type WalkFunc func(urn FSPathSpec, info os.FileInfo) error + +func Walk( + file_store FileStore, + root FSPathSpec, + walkFn WalkFunc) error { + children, err := file_store.ListDirectory(root) + if err != nil { + // Walking a non existant directory just ignores it. + return nil + } + + for _, child_info := range children { + full_path := child_info.PathSpec() + if child_info.IsDir() { + err = Walk(file_store, full_path, walkFn) + if err != nil { + return err + } + continue + } + + walkFn(full_path, child_info) + } + + return nil +} diff --git a/file_store/directory/directory.go b/file_store/directory/directory.go index 9d7a5860d38..ac81b318638 100644 --- a/file_store/directory/directory.go +++ b/file_store/directory/directory.go @@ -34,8 +34,6 @@ import ( "strings" "github.com/pkg/errors" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" config_proto "www.velocidex.com/golang/velociraptor/config/proto" "www.velocidex.com/golang/velociraptor/file_store/accessors" "www.velocidex.com/golang/velociraptor/file_store/api" @@ -43,18 +41,6 @@ import ( "www.velocidex.com/golang/velociraptor/utils" ) -var ( - openCounter = promauto.NewCounter(prometheus.CounterOpts{ - Name: "file_store_open", - Help: "Total number of filestore open operations.", - }) - - listCounter = promauto.NewCounter(prometheus.CounterOpts{ - Name: "file_store_list", - Help: "Total number of filestore list children operations.", - }) -) - const ( // On windows all file paths must be prefixed by this to // support long paths. @@ -62,7 +48,8 @@ const ( ) type DirectoryFileWriter struct { - Fd *os.File + Fd *os.File + completion func() } func (self *DirectoryFileWriter) Size() (int64, error) { @@ -70,6 +57,9 @@ func (self *DirectoryFileWriter) Size() (int64, error) { } func (self *DirectoryFileWriter) Write(data []byte) (int, error) { + + defer api.InstrumentWithDelay("write", "DirectoryFileWriter", nil)() + _, err := self.Fd.Seek(0, os.SEEK_END) if err != nil { return 0, err @@ -82,8 +72,14 @@ func (self *DirectoryFileWriter) Truncate() error { return self.Fd.Truncate(0) } +func (self *DirectoryFileWriter) Flush() error { return nil } + func (self *DirectoryFileWriter) Close() error { - return self.Fd.Close() + err := self.Fd.Close() + if self.completion != nil { + self.completion() + } + return err } type DirectoryFileStore struct { @@ -104,7 +100,7 @@ func (self *DirectoryFileStore) Move(src, dest api.FSPathSpec) error { func (self *DirectoryFileStore) ListDirectory(dirname api.FSPathSpec) ( []api.FileInfo, error) { - listCounter.Inc() + defer api.InstrumentWithDelay("list", "DirectoryFileStore", dirname)() file_path := dirname.AsFilestoreDirectory(self.config_obj) files, err := utils.ReadDir(file_path) @@ -138,7 +134,9 @@ func (self *DirectoryFileStore) ListDirectory(dirname api.FSPathSpec) ( func (self *DirectoryFileStore) ReadFile( filename api.FSPathSpec) (api.FileReader, error) { file_path := filename.AsFilestoreFilename(self.config_obj) - openCounter.Inc() + + defer api.InstrumentWithDelay("open_read", "DirectoryFileStore", filename)() + file, err := os.Open(file_path) if err != nil { return nil, errors.WithStack(err) @@ -151,6 +149,9 @@ func (self *DirectoryFileStore) ReadFile( func (self *DirectoryFileStore) StatFile( filename api.FSPathSpec) (api.FileInfo, error) { + + defer api.Instrument("stat", "DirectoryFileStore", filename)() + file_path := filename.AsFilestoreFilename(self.config_obj) file, err := os.Stat(file_path) if err != nil { @@ -164,6 +165,17 @@ func (self *DirectoryFileStore) StatFile( func (self *DirectoryFileStore) WriteFile( filename api.FSPathSpec) (api.FileWriter, error) { + if strings.Contains(filename.AsClientPath(), "Generic.Client.Stats") { + utils.DlvBreak() + } + + return self.WriteFileWithCompletion(filename, nil) +} + +func (self *DirectoryFileStore) WriteFileWithCompletion( + filename api.FSPathSpec, completion func()) (api.FileWriter, error) { + + defer api.InstrumentWithDelay("open_write", "DirectoryFileStore", filename)() file_path := filename.AsFilestoreFilename(self.config_obj) err := os.MkdirAll(filepath.Dir(file_path), 0700) if err != nil { @@ -172,7 +184,6 @@ func (self *DirectoryFileStore) WriteFile( return nil, err } - openCounter.Inc() file, err := os.OpenFile(file_path, os.O_RDWR|os.O_CREATE, 0700) if err != nil { logger := logging.GetLogger(self.config_obj, &logging.FrontendComponent) @@ -181,10 +192,16 @@ func (self *DirectoryFileStore) WriteFile( return nil, errors.WithStack(err) } - return &DirectoryFileWriter{file}, nil + return &DirectoryFileWriter{ + Fd: file, + completion: completion, + }, nil } func (self *DirectoryFileStore) Delete(filename api.FSPathSpec) error { + + defer api.InstrumentWithDelay("delete", "DirectoryFileStore", filename)() + file_path := filename.AsFilestoreFilename(self.config_obj) err := os.Remove(file_path) if err != nil { @@ -209,28 +226,3 @@ func (self *DirectoryFileStore) Delete(filename api.FSPathSpec) error { // At least we succeeded deleting the file return nil } - -func (self *DirectoryFileStore) Walk(root api.FSPathSpec, walkFn api.WalkFunc) error { - // Walking a non-existant directory just returns no results. - children, err := self.ListDirectory(root) - if err != nil { - return nil - } - - for _, child := range children { - if child.IsDir() { - err = self.Walk(child.PathSpec(), walkFn) - if err != nil { - return err - } - continue - } - - if strings.HasSuffix(child.Name(), ".db") { - continue - } - - walkFn(child.PathSpec(), child) - } - return nil -} diff --git a/file_store/directory/listener.go b/file_store/directory/listener.go index 43317ee1e3f..17240d16890 100644 --- a/file_store/directory/listener.go +++ b/file_store/directory/listener.go @@ -166,7 +166,8 @@ func (self *Listener) Close() { for _, item := range items { select { case <-self.ctx.Done(): - return + // Just drop all work items on the floor + self.file_buffer.Wg.Done() // As each message is delivered we can let the // file buffer know it is delivered. @@ -267,7 +268,8 @@ func NewListener( for _, item := range items { select { case <-self.ctx.Done(): - return + // Just drain all work items so we can safely exit + self.file_buffer.Wg.Done() // As each message is delivered we can let the // file buffer know it is delivered. diff --git a/file_store/directory/queue.go b/file_store/directory/queue.go index 55e53889d65..c39b02da8e0 100644 --- a/file_store/directory/queue.go +++ b/file_store/directory/queue.go @@ -162,6 +162,16 @@ func (self *DirectoryQueueManager) Debug() *ordereddict.Dict { return self.queue_pool.Debug() } +// Sends the events without writing them to the filestore. +func (self *DirectoryQueueManager) Broadcast( + path_manager api.PathManager, dict_rows []*ordereddict.Dict) { + for _, row := range dict_rows { + // Set a timestamp per event for easier querying. + row.Set("_ts", int(self.Clock.Now().Unix())) + self.queue_pool.Broadcast(path_manager.GetQueueName(), row) + } +} + func (self *DirectoryQueueManager) PushEventRows( path_manager api.PathManager, dict_rows []*ordereddict.Dict) error { diff --git a/file_store/directory/queue_test.go b/file_store/directory/queue_test.go index db5e765c253..108b2313b11 100644 --- a/file_store/directory/queue_test.go +++ b/file_store/directory/queue_test.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "os" "testing" + "time" "github.com/Velocidex/ordereddict" "github.com/stretchr/testify/assert" @@ -17,6 +18,7 @@ import ( "www.velocidex.com/golang/velociraptor/paths/artifacts" "www.velocidex.com/golang/velociraptor/services" "www.velocidex.com/golang/velociraptor/utils" + "www.velocidex.com/golang/velociraptor/vtesting" _ "www.velocidex.com/golang/velociraptor/result_sets/simple" _ "www.velocidex.com/golang/velociraptor/result_sets/timed" @@ -105,11 +107,12 @@ func (self *TestSuite) TestQueueManager() { assert.NoError(self.T(), err) } - // The file should contain all the rows now. - dbg = manager.Debug() - - // File size is not accurate due to timestamps - assert.Greater(self.T(), utils.GetInt64(dbg, "TestQueue.0.Size"), int64(300)) + vtesting.WaitUntil(time.Second, self.T(), func() bool { + // The file should contain all the rows now. File size is not + // exact due to timestamps but it should be larger than 300. + dbg = manager.Debug() + return utils.GetInt64(dbg, "TestQueue.0.Size") > int64(300) + }) // Now read all the rows from the file. count := 0 diff --git a/file_store/file_store.go b/file_store/file_store.go index e0d5035f4a7..ce4183c5974 100644 --- a/file_store/file_store.go +++ b/file_store/file_store.go @@ -20,53 +20,111 @@ package file_store import ( "errors" "fmt" + "sync" config_proto "www.velocidex.com/golang/velociraptor/config/proto" "www.velocidex.com/golang/velociraptor/file_store/accessors" "www.velocidex.com/golang/velociraptor/file_store/api" "www.velocidex.com/golang/velociraptor/file_store/directory" + "www.velocidex.com/golang/velociraptor/file_store/memcache" "www.velocidex.com/golang/velociraptor/file_store/memory" "www.velocidex.com/golang/velociraptor/glob" ) +var ( + fs_mu sync.Mutex + memory_imp *memory.MemoryFileStore + memcache_imp *memcache.MemcacheFileStore + directory_imp *directory.DirectoryFileStore + + g_impl api.FileStore +) + // GetFileStore selects an appropriate FileStore object based on // config. func GetFileStore(config_obj *config_proto.Config) api.FileStore { + fs_mu.Lock() + defer fs_mu.Unlock() + + if g_impl != nil { + return g_impl + } + if config_obj.Datastore == nil { return nil } - switch config_obj.Datastore.Implementation { + res, _ := getImpl(config_obj.Datastore.Implementation, config_obj) + return res +} + +func getImpl(implementation string, + config_obj *config_proto.Config) (api.FileStore, error) { + switch implementation { case "Test": - return memory.NewMemoryFileStore(config_obj) + if memory_imp == nil { + memory_imp = memory.NewMemoryFileStore(config_obj) + } + return memory_imp, nil + + case "MemcacheFileDataStore", "RemoteFileDataStore": + if memcache_imp == nil { + memcache_imp = memcache.NewMemcacheFileStore(config_obj) + } + return memcache_imp, nil - case "FileBaseDataStore", "MemcacheFileDataStore", "RemoteFileDataStore": - return directory.NewDirectoryFileStore(config_obj) + case "FileBaseDataStore": + if directory_imp == nil { + directory_imp = directory.NewDirectoryFileStore(config_obj) + } + return directory_imp, nil default: - panic(fmt.Sprintf("Unsupported filestore %v", - config_obj.Datastore.Implementation)) + return nil, fmt.Errorf("Unsupported filestore %v", implementation) } } // Gets an accessor that can access the file store. func GetFileStoreFileSystemAccessor( config_obj *config_proto.Config) (glob.FileSystemAccessor, error) { + + fs_mu.Lock() + defer fs_mu.Unlock() + + if g_impl != nil { + return accessors.NewFileStoreFileSystemAccessor( + config_obj, g_impl), nil + } + if config_obj.Datastore == nil { return nil, errors.New("Datastore not configured") } switch config_obj.Datastore.Implementation { - case "FileBaseDataStore", "MemcacheFileDataStore", "RemoteFileDataStore": + case "MemcacheFileDataStore": return accessors.NewFileStoreFileSystemAccessor( - config_obj, directory.NewDirectoryFileStore(config_obj)), nil + config_obj, memcache_imp), nil + + case "FileBaseDataStore", "RemoteFileDataStore": + return accessors.NewFileStoreFileSystemAccessor( + config_obj, directory_imp), nil case "Test": return accessors.NewFileStoreFileSystemAccessor( - config_obj, memory.NewMemoryFileStore(config_obj)), nil + config_obj, memory_imp), nil } return nil, errors.New("Unknown file store implementation") } + +func SetGlobalFilestore( + implementation string, + config_obj *config_proto.Config) (err error) { + fs_mu.Lock() + defer fs_mu.Unlock() + + g_impl, err = getImpl(implementation, config_obj) + return err +} diff --git a/file_store/memcache/memcache.go b/file_store/memcache/memcache.go new file mode 100644 index 00000000000..dbb1856216b --- /dev/null +++ b/file_store/memcache/memcache.go @@ -0,0 +1,232 @@ +/* + This is an experimental file store implementation designed to work + on very slow filesystem, such as network filesystems. + +*/ + +package memcache + +import ( + "bytes" + "sync" + "time" + + "github.com/Velocidex/ttlcache/v2" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + config_proto "www.velocidex.com/golang/velociraptor/config/proto" + "www.velocidex.com/golang/velociraptor/file_store/api" + "www.velocidex.com/golang/velociraptor/file_store/directory" +) + +var ( + metricDataLRU = promauto.NewGauge( + prometheus.GaugeOpts{ + Name: "memcache_filestore_lru_total", + Help: "Total files cached in the filestore lru", + }) +) + +type MemcacheFileWriter struct { + mu sync.Mutex + + delegate *directory.DirectoryFileStore + filename api.FSPathSpec + truncated bool + buffer bytes.Buffer + size int64 + + completion func() +} + +func (self *MemcacheFileWriter) Size() (int64, error) { + self.mu.Lock() + defer self.mu.Unlock() + + return self._Size() +} + +func (self *MemcacheFileWriter) _Size() (int64, error) { + if self.size >= 0 { + return self.size, nil + } + + fs_info, err := self.delegate.StatFile(self.filename) + if err != nil { + self.size = 0 + return 0, nil + } + + self.size = fs_info.Size() + return self.size, nil +} + +func (self *MemcacheFileWriter) Write(data []byte) (int, error) { + defer api.Instrument("write", "MemcacheFileWriter", nil)() + self.mu.Lock() + defer self.mu.Unlock() + + size, err := self._Size() + if err != nil { + return 0, err + } + + self.size = size + int64(len(data)) + + return self.buffer.Write(data) +} + +func (self *MemcacheFileWriter) Truncate() error { + self.mu.Lock() + defer self.mu.Unlock() + + self.truncated = true + self.buffer.Truncate(0) + self.size = 0 + + return nil +} + +// Closing the file does not trigger a flush - we just return a +// success status and wait for the file to be written asynchronously. +func (self *MemcacheFileWriter) Close() error { + return nil +} + +func (self *MemcacheFileWriter) Flush() error { + + // While the file is flushed it blocks other writers to the same + // file (which will be blocked on the mutex. This ensures writes + // to the underlying filestore occur in order). + self.mu.Lock() + defer self.mu.Unlock() + + // Skip a noop action. + if !self.truncated && len(self.buffer.Bytes()) == 0 { + return nil + } + + writer, err := self.delegate.WriteFile(self.filename) + if err != nil { + return err + } + defer writer.Close() + + if self.truncated { + writer.Truncate() + } + + _, err = writer.Write(self.buffer.Bytes()) + + // Reset the writer for reuse + self.truncated = false + self.buffer.Truncate(0) + + if self.completion != nil { + self.completion() + } + + return err +} + +type MemcacheFileStore struct { + mu sync.Mutex + + config_obj *config_proto.Config + delegate *directory.DirectoryFileStore + + data_cache *ttlcache.Cache // map[urn]*MemcacheFileWriter +} + +func NewMemcacheFileStore(config_obj *config_proto.Config) *MemcacheFileStore { + result := &MemcacheFileStore{ + delegate: directory.NewDirectoryFileStore(config_obj), + data_cache: ttlcache.NewCache(), + } + + result.data_cache.SetTTL(5 * time.Second) + + result.data_cache.SetNewItemCallback(func(key string, value interface{}) { + metricDataLRU.Inc() + }) + + result.data_cache.SetExpirationCallback(func(key string, value interface{}) { + writer, ok := value.(*MemcacheFileWriter) + if ok { + writer.Flush() + } + + metricDataLRU.Dec() + }) + + return result +} + +func (self *MemcacheFileStore) ReadFile( + path api.FSPathSpec) (api.FileReader, error) { + defer api.Instrument("read_open", "MemcacheFileStore", path)() + + return self.delegate.ReadFile(path) +} + +func (self *MemcacheFileStore) WriteFile(path api.FSPathSpec) (api.FileWriter, error) { + return self.WriteFileWithCompletion(path, nil) +} + +func (self *MemcacheFileStore) WriteFileWithCompletion( + path api.FSPathSpec, completion func()) (api.FileWriter, error) { + defer api.Instrument("write_open", "MemcacheFileStore", path)() + self.mu.Lock() + defer self.mu.Unlock() + + key := path.AsClientPath() + + var result *MemcacheFileWriter + + result_any, err := self.data_cache.Get(key) + if err != nil { + result = &MemcacheFileWriter{ + delegate: self.delegate, + filename: path, + size: -1, + completion: completion, + } + } else { + result = result_any.(*MemcacheFileWriter) + result.completion = completion + } + + // Always set it so the time can be extended. + self.data_cache.Set(key, result) + + return result, nil +} + +func (self *MemcacheFileStore) StatFile(path api.FSPathSpec) (api.FileInfo, error) { + defer api.Instrument("stat", "MemcacheFileStore", path)() + + return self.delegate.StatFile(path) +} + +// Clear all the outstanding writers. +func (self *MemcacheFileStore) Flush() { + self.data_cache.Flush() +} + +func (self *MemcacheFileStore) Move(src, dest api.FSPathSpec) error { + defer api.Instrument("move", "MemcacheFileStore", src)() + + return self.delegate.Move(src, dest) +} + +func (self *MemcacheFileStore) ListDirectory(root_path api.FSPathSpec) ([]api.FileInfo, error) { + defer api.Instrument("list", "MemcacheFileStore", root_path)() + + return self.delegate.ListDirectory(root_path) +} + +func (self *MemcacheFileStore) Delete(path api.FSPathSpec) error { + defer api.Instrument("delete", "MemoryFileStore", nil)() + + return self.delegate.Delete(path) +} diff --git a/file_store/memory/memory.go b/file_store/memory/memory.go index 7c11a271391..11bf252c2fd 100644 --- a/file_store/memory/memory.go +++ b/file_store/memory/memory.go @@ -6,13 +6,11 @@ import ( "io" "os" "path" - "path/filepath" "runtime" "strings" "sync" "github.com/Velocidex/ordereddict" - "github.com/pkg/errors" config_proto "www.velocidex.com/golang/velociraptor/config/proto" "www.velocidex.com/golang/velociraptor/file_store/api" "www.velocidex.com/golang/velociraptor/file_store/path_specs" @@ -55,6 +53,8 @@ type MemoryReader struct { } func (self *MemoryReader) Read(buf []byte) (int, error) { + defer api.InstrumentWithDelay("read", "MemoryReader", nil)() + fs_buf, pres := self.memory_file_store.Get(self.filename) if !pres { return 0, os.ErrNotExist @@ -92,6 +92,8 @@ func (self *MemoryReader) Close() error { } func (self *MemoryReader) Stat() (api.FileInfo, error) { + defer api.InstrumentWithDelay("stat", "MemoryReader", nil)() + fs_buf, pres := self.memory_file_store.Get(self.filename) if !pres { return nil, os.ErrNotExist @@ -110,6 +112,7 @@ type MemoryWriter struct { memory_file_store *MemoryFileStore filename string closed bool + completion func() } func (self *MemoryWriter) Size() (int64, error) { @@ -117,10 +120,14 @@ func (self *MemoryWriter) Size() (int64, error) { } func (self *MemoryWriter) Write(data []byte) (int, error) { + defer api.InstrumentWithDelay("write", "MemoryReader", nil)() + self.buf = append(self.buf, data...) return len(data), nil } +func (self *MemoryWriter) Flush() error { return nil } + func (self *MemoryWriter) Close() error { if self.closed { panic("MemoryWriter already closed") @@ -131,10 +138,15 @@ func (self *MemoryWriter) Close() error { defer self.memory_file_store.mu.Unlock() self.memory_file_store.Data.Set(self.filename, self.buf) + if self.completion != nil { + self.completion() + } return nil } func (self *MemoryWriter) Truncate() error { + defer api.InstrumentWithDelay("truncate", "MemoryReader", nil)() + self.buf = nil return nil } @@ -174,6 +186,8 @@ func (self *MemoryFileStore) DebugString() string { } func (self *MemoryFileStore) ReadFile(path api.FSPathSpec) (api.FileReader, error) { + defer api.InstrumentWithDelay("read_open", "MemoryFileStore", nil)() + self.mu.Lock() defer self.mu.Unlock() @@ -188,10 +202,18 @@ func (self *MemoryFileStore) ReadFile(path api.FSPathSpec) (api.FileReader, erro }, nil } - return nil, errors.New("Not found") + return nil, os.ErrNotExist } func (self *MemoryFileStore) WriteFile(path api.FSPathSpec) (api.FileWriter, error) { + return self.WriteFileWithCompletion(path, nil) +} + +func (self *MemoryFileStore) WriteFileWithCompletion( + path api.FSPathSpec, completion func()) (api.FileWriter, error) { + + defer api.InstrumentWithDelay("write_open", "MemoryFileStore", nil)() + self.mu.Lock() defer self.mu.Unlock() @@ -208,10 +230,13 @@ func (self *MemoryFileStore) WriteFile(path api.FSPathSpec) (api.FileWriter, err buf: buf.([]byte), memory_file_store: self, filename: filename, + completion: completion, }, nil } func (self *MemoryFileStore) StatFile(path api.FSPathSpec) (api.FileInfo, error) { + defer api.InstrumentWithDelay("stat", "MemoryFileStore", nil)() + self.mu.Lock() defer self.mu.Unlock() @@ -230,6 +255,8 @@ func (self *MemoryFileStore) StatFile(path api.FSPathSpec) (api.FileInfo, error) } func (self *MemoryFileStore) Move(src, dest api.FSPathSpec) error { + defer api.InstrumentWithDelay("move", "MemoryFileStore", nil)() + self.mu.Lock() defer self.mu.Unlock() @@ -247,6 +274,7 @@ func (self *MemoryFileStore) Move(src, dest api.FSPathSpec) error { } func (self *MemoryFileStore) ListDirectory(root_path api.FSPathSpec) ([]api.FileInfo, error) { + defer api.InstrumentWithDelay("list", "MemoryFileStore", nil)() self.mu.Lock() defer self.mu.Unlock() @@ -326,32 +354,9 @@ func (self *MemoryFileStore) ListDirectory(root_path api.FSPathSpec) ([]api.File return result, nil } -func (self *MemoryFileStore) Walk( - root api.FSPathSpec, walkFn api.WalkFunc) error { - children, err := self.ListDirectory(root) - if err != nil { - return err - } - - for _, child_info := range children { - full_path := child_info.PathSpec() - if !child_info.IsDir() { - err := walkFn(full_path, child_info) - if err == filepath.SkipDir { - continue - } - } - - err := self.Walk(full_path, walkFn) - if err != nil { - return err - } - } - - return nil -} - func (self *MemoryFileStore) Delete(path api.FSPathSpec) error { + defer api.InstrumentWithDelay("delete", "MemoryFileStore", nil)() + self.mu.Lock() defer self.mu.Unlock() diff --git a/file_store/memory/queue.go b/file_store/memory/queue.go index 2001bad66a0..a9d3bb5b690 100644 --- a/file_store/memory/queue.go +++ b/file_store/memory/queue.go @@ -149,6 +149,16 @@ func (self *MemoryQueueManager) Debug() { } } +func (self *MemoryQueueManager) Broadcast( + path_manager api.PathManager, dict_rows []*ordereddict.Dict) { + pool := GlobalQueuePool(self.config_obj) + for _, row := range dict_rows { + // Set a timestamp per event for easier querying. + row.Set("_ts", int(self.Clock.Now().Unix())) + pool.Broadcast(path_manager.GetQueueName(), row) + } +} + func (self *MemoryQueueManager) PushEventRows( path_manager api.PathManager, dict_rows []*ordereddict.Dict) error { diff --git a/file_store/path_specs/fs_path_spec.go b/file_store/path_specs/fs_path_spec.go index 11b447c3479..60e7cec9587 100644 --- a/file_store/path_specs/fs_path_spec.go +++ b/file_store/path_specs/fs_path_spec.go @@ -10,6 +10,17 @@ import ( type FSPathSpec struct { DSPathSpec + + tag string +} + +func (self FSPathSpec) Tag() string { + return self.tag +} + +func (self FSPathSpec) SetTag(tag string) api.FSPathSpec { + self.tag = tag + return self } func (self FSPathSpec) String() string { @@ -21,7 +32,7 @@ func (self FSPathSpec) Dir() api.FSPathSpec { if len(new_components) > 0 { new_components = new_components[:len(new_components)-1] } - return FSPathSpec{DSPathSpec{ + return FSPathSpec{DSPathSpec: DSPathSpec{ components: new_components, path_type: self.path_type, }} @@ -33,7 +44,7 @@ func (self FSPathSpec) MarshalJSON() ([]byte, error) { // Adds an unsafe component to this path. func (self FSPathSpec) AddChild(child ...string) api.FSPathSpec { - return FSPathSpec{DSPathSpec{ + return FSPathSpec{DSPathSpec: DSPathSpec{ components: append(utils.CopySlice(self.components), child...), path_type: self.path_type, is_safe: self.is_safe, @@ -41,7 +52,7 @@ func (self FSPathSpec) AddChild(child ...string) api.FSPathSpec { } func (self FSPathSpec) AddUnsafeChild(child ...string) api.FSPathSpec { - return FSPathSpec{DSPathSpec{ + return FSPathSpec{DSPathSpec: DSPathSpec{ components: append(utils.CopySlice(self.components), child...), path_type: self.path_type, is_safe: false, @@ -49,7 +60,7 @@ func (self FSPathSpec) AddUnsafeChild(child ...string) api.FSPathSpec { } func (self FSPathSpec) SetType(ext api.PathType) api.FSPathSpec { - return FSPathSpec{DSPathSpec{ + return FSPathSpec{DSPathSpec: DSPathSpec{ components: self.components, path_type: ext, is_safe: self.is_safe, @@ -78,7 +89,7 @@ func (self FSPathSpec) AsClientPath() string { } func NewUnsafeFilestorePath(path_components ...string) api.FSPathSpec { - result := FSPathSpec{DSPathSpec{ + result := FSPathSpec{DSPathSpec: DSPathSpec{ components: path_components, // By default write JSON files. path_type: api.PATH_TYPE_FILESTORE_JSON, @@ -89,7 +100,7 @@ func NewUnsafeFilestorePath(path_components ...string) api.FSPathSpec { } func NewSafeFilestorePath(path_components ...string) api.FSPathSpec { - result := FSPathSpec{DSPathSpec{ + result := FSPathSpec{DSPathSpec: DSPathSpec{ components: path_components, path_type: api.PATH_TYPE_FILESTORE_JSON, is_safe: true, diff --git a/file_store/path_specs/path_specs.go b/file_store/path_specs/path_specs.go index 67e45a1fbb3..bb8634566e9 100644 --- a/file_store/path_specs/path_specs.go +++ b/file_store/path_specs/path_specs.go @@ -148,7 +148,7 @@ func (self DSPathSpec) AsDatastoreFilename( } func (self DSPathSpec) AsFilestorePath() api.FSPathSpec { - return &FSPathSpec{DSPathSpec{ + return &FSPathSpec{DSPathSpec: DSPathSpec{ components: self.components, path_type: api.PATH_TYPE_FILESTORE_JSON, is_safe: self.is_safe, diff --git a/file_store/test_utils/testing.go b/file_store/test_utils/testing.go index c8c5e50e754..9b1d975e39b 100644 --- a/file_store/test_utils/testing.go +++ b/file_store/test_utils/testing.go @@ -29,7 +29,11 @@ func GetMemoryDataStore( db, err := datastore.GetDB(config_obj) require.NoError(t, err) - return db.(*datastore.MemcacheDatastore) + memory_db, ok := db.(*datastore.MemcacheDatastore) + if ok { + return memory_db + } + return nil } func FileReadAll(t *testing.T, config_obj *config_proto.Config, diff --git a/file_store/test_utils/testsuite.go b/file_store/test_utils/testsuite.go index 5d08016c456..4a7c917266c 100644 --- a/file_store/test_utils/testsuite.go +++ b/file_store/test_utils/testsuite.go @@ -11,8 +11,11 @@ import ( "github.com/stretchr/testify/suite" "www.velocidex.com/golang/velociraptor/config" config_proto "www.velocidex.com/golang/velociraptor/config/proto" + "www.velocidex.com/golang/velociraptor/file_store" + "www.velocidex.com/golang/velociraptor/file_store/memory" "www.velocidex.com/golang/velociraptor/services" "www.velocidex.com/golang/velociraptor/services/client_info" + "www.velocidex.com/golang/velociraptor/services/frontend" "www.velocidex.com/golang/velociraptor/services/inventory" "www.velocidex.com/golang/velociraptor/services/journal" "www.velocidex.com/golang/velociraptor/services/labels" @@ -62,7 +65,7 @@ name: Generic.Client.Stats type: CLIENT_EVENT `, ` name: System.Hunt.Participation -type: CLIENT_EVENT +type: INTERNAL `, ` name: Generic.Client.Info type: CLIENT @@ -78,22 +81,29 @@ type TestSuite struct { Sm *services.Service } -func (self *TestSuite) SetupTest() { - var err error +func (self *TestSuite) LoadConfig() *config_proto.Config { os.Setenv("VELOCIRAPTOR_CONFIG", SERVER_CONFIG) - - self.ConfigObj, err = new(config.Loader). + config_obj, err := new(config.Loader). WithEnvLiteralLoader("VELOCIRAPTOR_CONFIG").WithRequiredFrontend(). WithWriteback().WithVerbose(true). LoadAndValidate() require.NoError(self.T(), err) - self.ConfigObj.Frontend.DoNotCompressArtifacts = true + config_obj.Frontend.DoNotCompressArtifacts = true + + return config_obj +} + +func (self *TestSuite) SetupTest() { + if self.ConfigObj == nil { + self.ConfigObj = self.LoadConfig() + } // Start essential services. self.Ctx, self.cancel = context.WithTimeout(context.Background(), time.Second*60) self.Sm = services.NewServiceManager(self.Ctx, self.ConfigObj) + require.NoError(self.T(), self.Sm.Start(frontend.StartFrontendService)) require.NoError(self.T(), self.Sm.Start(journal.StartJournalService)) require.NoError(self.T(), self.Sm.Start(notifications.StartNotificationService)) require.NoError(self.T(), self.Sm.Start(inventory.StartInventoryService)) @@ -134,8 +144,23 @@ func (self *TestSuite) LoadArtifactFiles(paths ...string) { } func (self *TestSuite) TearDownTest() { - self.cancel() - self.Sm.Close() - GetMemoryFileStore(self.T(), self.ConfigObj).Clear() - GetMemoryDataStore(self.T(), self.ConfigObj).Clear() + if self.cancel != nil { + self.cancel() + } + if self.Sm != nil { + self.Sm.Close() + } + + // These may not be memory based in the test switched to other + // data stores. + file_store_factory, ok := file_store.GetFileStore( + self.ConfigObj).(*memory.MemoryFileStore) + if ok { + file_store_factory.Clear() + } + + db := GetMemoryDataStore(self.T(), self.ConfigObj) + if db != nil { + db.Clear() + } } diff --git a/file_store/tests/testsuite.go b/file_store/tests/testsuite.go index c41fa744049..7379ac7a33e 100644 --- a/file_store/tests/testsuite.go +++ b/file_store/tests/testsuite.go @@ -142,7 +142,7 @@ func (self *FileStoreTestSuite) TestListChildrenWithTypes() { // Now check walk path_specs := []api.FSPathSpec{} - err = self.filestore.Walk(filename, func( + err = api.Walk(self.filestore, filename, func( path api.FSPathSpec, info os.FileInfo) error { // Ignore directories as they are not important. if !info.IsDir() { @@ -185,7 +185,7 @@ func (self *FileStoreTestSuite) TestListDirectory() { assert.Equal(self.T(), names, []string{"Bar", "Bar.txt", "Foo.txt"}) names = nil - err = self.filestore.Walk(filename, func( + err = api.Walk(self.filestore, filename, func( path api.FSPathSpec, info os.FileInfo) error { names = append(names, path.AsClientPath()) return nil @@ -201,7 +201,7 @@ func (self *FileStoreTestSuite) TestListDirectory() { // Walk non existent directory just returns no results. names = nil - err = self.filestore.Walk(filename.AddChild("nonexistant"), + err = api.Walk(self.filestore, filename.AddChild("nonexistant"), func(path api.FSPathSpec, info os.FileInfo) error { names = append(names, path.AsFilestoreFilename( self.config_obj)) diff --git a/flows/artifacts.go b/flows/artifacts.go index 0ef9e753c72..9d17d2a8d9b 100644 --- a/flows/artifacts.go +++ b/flows/artifacts.go @@ -25,6 +25,7 @@ import ( "time" "github.com/Velocidex/ordereddict" + "github.com/golang/protobuf/proto" errors "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" @@ -60,6 +61,79 @@ var ( notModified = errors.New("Not modified") ) +// The CollectionContext tracks collections as they are being +// processed. The client send back a bunch of results consisting of +// logs, monitoring results, status errors etc. As the server +// processes these, it loads the CollectionContext from the datastore, +// and updates the CollectionContext state. When the server completes +// processing, the CollectionContext context is flushed by to the +// filestore. +type CollectionContext struct { + mu sync.Mutex + + flows_proto.ArtifactCollectorContext + monitoring_batch map[string][]*ordereddict.Dict + + // The completer keeps track of all asynchronous filesystem + // operations that will occur so that when everything is written + // to disk, the completer can send the System.Flow.Completion + // event. This is important as we dont want watchers of + // System.Flow.Completion to attempt to open the collection before + // everything is written. + completer *utils.Completer + + // Indicate if the System.Flow.Completion should be sent. This + // only happens once the collection is complete and results are + // written. It only happens at most once per collection. + send_update bool +} + +func (self *CollectionContext) batchRows(flow_id string, rows []*ordereddict.Dict) { + batch, _ := self.monitoring_batch[flow_id] + batch = append(batch, rows...) + self.monitoring_batch[flow_id] = batch + if len(rows) > 0 { + self.Dirty = true + } +} + +func NewCollectionContext(config_obj *config_proto.Config) *CollectionContext { + self := &CollectionContext{ + ArtifactCollectorContext: flows_proto.ArtifactCollectorContext{}, + monitoring_batch: make(map[string][]*ordereddict.Dict), + } + + // If we need to send a notification we should wait until all parts of + // the collection are fully stored first to avoid a race with any + // listeners on System.Flow.Completion. + self.completer = utils.NewCompleter(func() { + self.mu.Lock() + defer self.mu.Unlock() + + if !self.send_update { + return + } + + // If this is the final response (i.e. the flow is not running) + // and we have not yet sent an update, then we will notify a flow + // completion. + row := ordereddict.NewDict(). + Set("Timestamp", time.Now().UTC().Unix()). + Set("Flow", proto.Clone(&self.ArtifactCollectorContext)). + Set("FlowId", self.SessionId). + Set("ClientId", self.ClientId) + + journal, err := services.GetJournal() + if err == nil { + journal.PushRowsToArtifactAsync( + config_obj, row, "System.Flow.Completion") + } + + }) + + return self +} + // closeContext is called after all messages from the clients are // processed in this group. Client messages are sent in groups inside // the same POST request. Most of the time they belong to the same @@ -77,7 +151,7 @@ var ( // once a status is received and not again. func closeContext( config_obj *config_proto.Config, - collection_context *flows_proto.ArtifactCollectorContext) error { + collection_context *CollectionContext) error { // Context is not dirty - nothing to do. if !collection_context.Dirty || collection_context.ClientId == "" { @@ -96,8 +170,28 @@ func closeContext( collection_context.ActiveTime = uint64(time.Now().UnixNano() / 1000) + // Figure out if we will send a System.Flow.Completion after + // this. This depends on: + // 1. The flow state is no longer running (error or finished). + // 2. We have not sent a System.Flow.Completion yet. + // + // Record that we sent it so we never send 2 completion messages + // for the same flow. + if collection_context.Request != nil && + collection_context.State != flows_proto.ArtifactCollectorContext_RUNNING && + !collection_context.UserNotified { + + // Record the message was sent - so we never resent the + // message, even with new data. + collection_context.UserNotified = true + + // Instruct the completion function to send the message. + collection_context.send_update = true + } + if len(collection_context.Logs) > 0 { - err := flushContextLogs(config_obj, collection_context) + err := flushContextLogs( + config_obj, collection_context, collection_context.completer) if err != nil { collection_context.State = flows_proto.ArtifactCollectorContext_ERROR collection_context.Status = err.Error() @@ -106,23 +200,21 @@ func closeContext( if len(collection_context.UploadedFiles) > 0 { err := flushContextUploadedFiles( - config_obj, collection_context) + config_obj, collection_context, collection_context.completer) if err != nil { collection_context.State = flows_proto.ArtifactCollectorContext_ERROR collection_context.Status = err.Error() } } - // We need to send a notification but we should wait until the - // collection_context is stored first to avoid a race. - var will_notify bool - - if collection_context.Request != nil && - collection_context.State != flows_proto.ArtifactCollectorContext_RUNNING && - !collection_context.UserNotified { - collection_context.UserNotified = true - will_notify = true + if len(collection_context.monitoring_batch) > 0 { + err = flushMonitoringLogs(config_obj, collection_context) + if err != nil { + collection_context.State = flows_proto.ArtifactCollectorContext_ERROR + collection_context.Status = err.Error() + } } + collection_context.Dirty = false // Write the data before we fire the event so the data is @@ -135,39 +227,18 @@ func closeContext( flow_path_manager := paths.NewFlowPathManager( collection_context.ClientId, collection_context.SessionId) - err = db.SetSubject(config_obj, flow_path_manager.Path(), collection_context) - if err != nil { - return err - } - - // This is the final time we update the context - send a - // journal message. - if will_notify { - row := ordereddict.NewDict(). - Set("Timestamp", time.Now().UTC().Unix()). - Set("Flow", collection_context). - Set("FlowId", collection_context.SessionId). - Set("ClientId", collection_context.ClientId) - journal, err := services.GetJournal() - if err != nil { - return err - } - return journal.PushRowsToArtifact(config_obj, - []*ordereddict.Dict{row}, - "System.Flow.Completion", collection_context.ClientId, - collection_context.SessionId, - ) - } - - return nil + return db.SetSubjectWithCompletion( + config_obj, flow_path_manager.Path(), + collection_context, collection_context.completer.GetCompletionFunc()) } // Flush the logs to disk. During execution the flow collects the logs // in memory and then flushes it all when done. func flushContextLogs( config_obj *config_proto.Config, - collection_context *flows_proto.ArtifactCollectorContext) error { + collection_context *CollectionContext, + completion *utils.Completer) error { // Handle monitoring flow specially. if collection_context.SessionId == constants.MONITORING_WELL_KNOWN_FLOW { @@ -187,6 +258,8 @@ func flushContextLogs( } defer rs_writer.Close() + rs_writer.SetCompletion(completion.GetCompletionFunc()) + for _, row := range collection_context.Logs { collection_context.TotalLogs++ rs_writer.Write(ordereddict.NewDict(). @@ -203,7 +276,8 @@ func flushContextLogs( func flushContextUploadedFiles( config_obj *config_proto.Config, - collection_context *flows_proto.ArtifactCollectorContext) error { + collection_context *CollectionContext, + completion *utils.Completer) error { flow_path_manager := paths.NewFlowPathManager( collection_context.ClientId, @@ -217,6 +291,8 @@ func flushContextUploadedFiles( } defer rs_writer.Close() + rs_writer.SetCompletion(completion.GetCompletionFunc()) + for _, row := range collection_context.UploadedFiles { rs_writer.Write(ordereddict.NewDict(). Set("Timestamp", time.Now().UTC().Unix()). @@ -234,24 +310,25 @@ func flushContextUploadedFiles( // Load the collector context from storage. func LoadCollectionContext( config_obj *config_proto.Config, - client_id, flow_id string) (*flows_proto.ArtifactCollectorContext, error) { + client_id, flow_id string) (*CollectionContext, error) { if flow_id == constants.MONITORING_WELL_KNOWN_FLOW { - return &flows_proto.ArtifactCollectorContext{ - SessionId: flow_id, - ClientId: client_id, - }, nil + result := NewCollectionContext(config_obj) + result.SessionId = flow_id + result.ClientId = client_id + + return result, nil } flow_path_manager := paths.NewFlowPathManager(client_id, flow_id) - collection_context := &flows_proto.ArtifactCollectorContext{} + collection_context := NewCollectionContext(config_obj) db, err := datastore.GetDB(config_obj) if err != nil { return nil, err } err = db.GetSubject(config_obj, flow_path_manager.Path(), - collection_context) + &collection_context.ArtifactCollectorContext) if err != nil { return nil, err } @@ -268,7 +345,7 @@ func LoadCollectionContext( // Process an incoming message from the client. func ArtifactCollectorProcessOneMessage( config_obj *config_proto.Config, - collection_context *flows_proto.ArtifactCollectorContext, + collection_context *CollectionContext, message *crypto_proto.VeloMessage) error { err := FailIfError(config_obj, collection_context, message) @@ -339,6 +416,9 @@ func ArtifactCollectorProcessOneMessage( } defer rs_writer.Close() + rs_writer.SetCompletion( + collection_context.completer.GetCompletionFunc()) + // Support the old clients which send JSON // array responses. We need to decode the JSON // response, then re-encode it into JSONL for @@ -387,7 +467,7 @@ func ArtifactCollectorProcessOneMessage( func IsRequestComplete( config_obj *config_proto.Config, - collection_context *flows_proto.ArtifactCollectorContext, + collection_context *CollectionContext, message *crypto_proto.VeloMessage) (bool, error) { // Nope request is not complete. @@ -415,7 +495,7 @@ func IsRequestComplete( func FailIfError( config_obj *config_proto.Config, - collection_context *flows_proto.ArtifactCollectorContext, + collection_context *CollectionContext, message *crypto_proto.VeloMessage) error { // Not a status message @@ -449,7 +529,7 @@ func FailIfError( func appendUploadDataToFile( config_obj *config_proto.Config, - collection_context *flows_proto.ArtifactCollectorContext, + collection_context *CollectionContext, message *crypto_proto.VeloMessage) error { file_buffer := message.FileBuffer @@ -573,7 +653,8 @@ func appendUploadDataToFile( return journal.PushRowsToArtifact(config_obj, []*ordereddict.Dict{row}, - "System.Upload.Completion", message.Source, collection_context.SessionId, + "System.Upload.Completion", + message.Source, collection_context.SessionId, ) } @@ -583,7 +664,7 @@ func appendUploadDataToFile( // Generate a flow log from a client LogMessage proto. Deobfuscates // the message. func LogMessage(config_obj *config_proto.Config, - collection_context *flows_proto.ArtifactCollectorContext, + collection_context *CollectionContext, msg *crypto_proto.LogMessage) { log_msg := artifacts.DeobfuscateString(config_obj, msg.Message) artifact_name := artifacts.DeobfuscateString(config_obj, msg.Artifact) @@ -597,7 +678,7 @@ func LogMessage(config_obj *config_proto.Config, } func Log(config_obj *config_proto.Config, - collection_context *flows_proto.ArtifactCollectorContext, + collection_context *CollectionContext, log_msg string) { log_msg = artifacts.DeobfuscateString(config_obj, log_msg) collection_context.Logs = append( @@ -611,14 +692,14 @@ func Log(config_obj *config_proto.Config, type FlowRunner struct { mu sync.Mutex - context_map map[string]*flows_proto.ArtifactCollectorContext + context_map map[string]*CollectionContext config_obj *config_proto.Config } func NewFlowRunner(config_obj *config_proto.Config) *FlowRunner { return &FlowRunner{ config_obj: config_obj, - context_map: make(map[string]*flows_proto.ArtifactCollectorContext), + context_map: make(map[string]*CollectionContext), } } diff --git a/flows/artifacts_test.go b/flows/artifacts_test.go index 0fd4e6c4a29..6fd4605098a 100644 --- a/flows/artifacts_test.go +++ b/flows/artifacts_test.go @@ -368,7 +368,8 @@ func (self *TestSuite) TestClientUploaderStoreFile() { nilTime, nilTime, nilTime, nilTime, reader) // Get a new collection context. - collection_context := &flows_proto.ArtifactCollectorContext{ + collection_context := NewCollectionContext(self.config_obj) + collection_context.ArtifactCollectorContext = flows_proto.ArtifactCollectorContext{ SessionId: self.flow_id, ClientId: self.client_id, OutstandingRequests: 1, @@ -470,7 +471,8 @@ func (self *TestSuite) TestClientUploaderStoreSparseFile() { nilTime, nilTime, nilTime, nilTime, reader) // Get a new collection context. - collection_context := &flows_proto.ArtifactCollectorContext{ + collection_context := NewCollectionContext(self.config_obj) + collection_context.ArtifactCollectorContext = flows_proto.ArtifactCollectorContext{ SessionId: self.flow_id, ClientId: self.client_id, OutstandingRequests: 1, @@ -594,7 +596,8 @@ func (self *TestSuite) TestClientUploaderStoreSparseFileNTFS() { nilTime, nilTime, nilTime, nilTime, fd) // Get a new collection context. - collection_context := &flows_proto.ArtifactCollectorContext{ + collection_context := NewCollectionContext(self.config_obj) + collection_context.ArtifactCollectorContext = flows_proto.ArtifactCollectorContext{ SessionId: self.flow_id, ClientId: self.client_id, Request: &flows_proto.ArtifactCollectorArgs{}, diff --git a/flows/foreman.go b/flows/foreman.go index 94ebf783aad..8a0ebb9aa9d 100644 --- a/flows/foreman.go +++ b/flows/foreman.go @@ -142,14 +142,11 @@ func ForemanProcessMessage( latest_timestamp := uint64(0) for _, hunt := range hunts { // Notify the hunt manager that we need to hunt this client. - err = journal.PushRowsToArtifact(config_obj, - []*ordereddict.Dict{ordereddict.NewDict(). + journal.PushRowsToArtifactAsync(config_obj, + ordereddict.NewDict(). Set("HuntId", hunt.HuntId). Set("ClientId", client_id), - }, "System.Hunt.Participation", client_id, "") - if err != nil { - return err - } + "System.Hunt.Participation") if hunt.StartTime > latest_timestamp { latest_timestamp = hunt.StartTime diff --git a/flows/hunts.go b/flows/hunts.go index 2d836cf6c61..0f51d8afe56 100644 --- a/flows/hunts.go +++ b/flows/hunts.go @@ -190,15 +190,6 @@ func CreateHunt( // set it started. } else if hunt.State == api_proto.Hunt_RUNNING { hunt.StartTime = hunt.CreateTime - - // Notify all the clients. - notifier := services.GetNotifier() - if notifier != nil { - err = notifier.NotifyByRegex(config_obj, "^[Cc]\\.") - if err != nil { - return "", err - } - } } row := ordereddict.NewDict(). @@ -385,17 +376,5 @@ func ModifyHunt( } dispatcher := services.GetHuntDispatcher() - err := dispatcher.MutateHunt(config_obj, mutation) - if err != nil { - return err - } - - // Notify all the clients about the new hunt. New hunts are - // not that common so notifying all the clients at once is - // probably ok. - notifier := services.GetNotifier() - if notifier == nil { - return errors.New("Notifier not ready") - } - return notifier.NotifyByRegex(config_obj, "^[Cc]\\.") + return dispatcher.MutateHunt(config_obj, mutation) } diff --git a/flows/limits.go b/flows/limits.go index 1aacbaa97b2..90e7f9478a7 100644 --- a/flows/limits.go +++ b/flows/limits.go @@ -26,7 +26,7 @@ var ( // the collection is terminated and the client is notified that it is // cancelled. func checkContextResourceLimits(config_obj *config_proto.Config, - collection_context *flows_proto.ArtifactCollectorContext) (err error) { + collection_context *CollectionContext) (err error) { // There are no resource limits on event flows. if collection_context.SessionId == constants.MONITORING_WELL_KNOWN_FLOW { diff --git a/flows/monitoring.go b/flows/monitoring.go index 5388862af58..efe18d30f61 100644 --- a/flows/monitoring.go +++ b/flows/monitoring.go @@ -9,7 +9,6 @@ import ( "www.velocidex.com/golang/velociraptor/constants" crypto_proto "www.velocidex.com/golang/velociraptor/crypto/proto" "www.velocidex.com/golang/velociraptor/file_store" - flows_proto "www.velocidex.com/golang/velociraptor/flows/proto" artifact_paths "www.velocidex.com/golang/velociraptor/paths/artifacts" "www.velocidex.com/golang/velociraptor/result_sets" "www.velocidex.com/golang/velociraptor/services" @@ -19,7 +18,7 @@ import ( // Receive monitoring messages from the client. func MonitoringProcessMessage( config_obj *config_proto.Config, - collection_context *flows_proto.ArtifactCollectorContext, + collection_context *CollectionContext, message *crypto_proto.VeloMessage) error { err := FailIfError(config_obj, collection_context, message) @@ -63,14 +62,9 @@ func MonitoringProcessMessage( for _, row := range rows { row.Set("ClientId", message.Source) } - journal, err := services.GetJournal() - if err != nil { - return err - } - return journal.PushRowsToArtifact( - config_obj, rows, response.Query.Name, - message.Source, message.SessionId) + // Batch the rows to send together. + collection_context.batchRows(response.Query.Name, rows) } return nil @@ -80,7 +74,7 @@ func MonitoringProcessMessage( // are written with a time index. func flushContextLogsMonitoring( config_obj *config_proto.Config, - collection_context *flows_proto.ArtifactCollectorContext) error { + collection_context *CollectionContext) error { // A single packet may have multiple log messages from // different artifacts. We cache the writers so we can send @@ -126,3 +120,26 @@ func flushContextLogsMonitoring( collection_context.Logs = nil return nil } + +func flushMonitoringLogs( + config_obj *config_proto.Config, + collection_context *CollectionContext) error { + + journal, err := services.GetJournal() + if err != nil { + return err + } + + for query_name, rows := range collection_context.monitoring_batch { + if len(rows) > 0 { + err := journal.PushRowsToArtifact( + config_obj, rows, query_name, + collection_context.ClientId, + collection_context.SessionId) + if err != nil { + return err + } + } + } + return nil +} diff --git a/go.mod b/go.mod index 04caa8dafa4..5cc17555618 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,6 @@ require ( github.com/Masterminds/semver v1.5.0 // indirect github.com/Masterminds/sprig v2.22.0+incompatible github.com/Netflix/go-expect v0.0.0-20210722184520-ef0bf57d82b3 // indirect - github.com/ReneKroon/ttlcache/v2 v2.9.0 github.com/Showmax/go-fqdn v1.0.0 github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect github.com/Velocidex/ahocorasick v0.0.0-20180712114356-e1c353eeaaee @@ -24,6 +23,7 @@ require ( github.com/Velocidex/pkcs7 v0.0.0-20210524015001-8d1eee94a157 github.com/Velocidex/sflags v0.3.1-0.20210402155316-b09f53df5162 github.com/Velocidex/survey v1.8.7-0.20190926071832-2ff99cc7aa49 + github.com/Velocidex/ttlcache/v2 v2.9.1-0.20211116035050-ddd93fed62f5 github.com/Velocidex/yaml/v2 v2.2.5 github.com/Velocidex/zip v0.0.0-20210101070220-e7ecefb7aad7 github.com/ZachtimusPrime/Go-Splunk-HTTP/splunk/v2 v2.0.1 @@ -84,9 +84,9 @@ require ( github.com/pkg/errors v0.9.1 github.com/pkg/sftp v1.12.0 github.com/pquerna/cachecontrol v0.0.0-20200921180117-858c6e7e6b7e // indirect - github.com/processout/grpc-go-pool v1.2.1 - github.com/prometheus/client_golang v1.2.1 - github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 + github.com/processout/grpc-go-pool v1.2.2-0.20200228131710-c0fcf3af0014 + github.com/prometheus/client_golang v1.11.0 + github.com/prometheus/client_model v0.2.0 github.com/qri-io/starlib v0.5.0 github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 github.com/robertkrimen/otto v0.0.0-20210614181706-373ff5438452 @@ -112,8 +112,8 @@ require ( golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 google.golang.org/api v0.51.0 - google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f - google.golang.org/grpc v1.39.0 + google.golang.org/genproto v0.0.0-20211112145013-271947fe86fd + google.golang.org/grpc v1.42.0 google.golang.org/protobuf v1.27.1 gopkg.in/alecthomas/kingpin.v2 v2.2.6 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect @@ -144,7 +144,7 @@ require ( github.com/beevik/etree v1.1.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e // indirect - github.com/cespare/xxhash/v2 v2.1.0 // indirect + github.com/cespare/xxhash/v2 v2.1.1 // indirect github.com/crewjam/httperr v0.2.0 // indirect github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect github.com/dlclark/regexp2 v1.2.0 // indirect @@ -169,8 +169,8 @@ require ( github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/paulmach/orb v0.1.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/common v0.7.0 // indirect - github.com/prometheus/procfs v0.0.5 // indirect + github.com/prometheus/common v0.29.0 // indirect + github.com/prometheus/procfs v0.7.2 // indirect github.com/rivo/uniseg v0.1.0 // indirect github.com/russellhaering/goxmldsig v1.1.0 // indirect github.com/tklauser/numcpus v0.2.3 // indirect @@ -198,7 +198,7 @@ require ( // replace github.com/Velocidex/json => /home/mic/projects/json // replace github.com/russross/blackfriday/v2 => /home/mic/projects/blackfriday // replace www.velocidex.com/golang/vtypes => /home/mic/projects/vtypes -// replace github.com/ReneKroon/ttlcache => /home/mic/projects/ttlcache +// replace github.com/Velocidex/ttlcache => /home/mic/projects/ttlcache // replace github.com/Velocidex/zip => /home/mic/projects/zip // replace github.com/Velocidex/sflags => /home/mic/projects/sflags diff --git a/go.sum b/go.sum index 2af36aaa873..4f67696ffa7 100644 --- a/go.sum +++ b/go.sum @@ -63,11 +63,10 @@ github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpz github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= github.com/Netflix/go-expect v0.0.0-20210722184520-ef0bf57d82b3 h1:DM0Olh3jQEm4gVPLF0mI49+fm1+M1fXDtumX/fN/G4A= github.com/Netflix/go-expect v0.0.0-20210722184520-ef0bf57d82b3/go.mod h1:68ORG0HSEWDuH5Eh73AFbYWZ1zT4Y+b0vhOa+vZRUdI= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/PuerkitoBio/goquery v1.6.1 h1:FgjbQZKl5HTmcn4sKBgvx8vv63nhyhIpv7lJpFGCWpk= github.com/PuerkitoBio/goquery v1.6.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= -github.com/ReneKroon/ttlcache/v2 v2.9.0 h1:NzwfErbifoNA3djEGwQJXKp/386imbyrc6Qmns5IX7c= -github.com/ReneKroon/ttlcache/v2 v2.9.0/go.mod h1:mBxvsNY+BT8qLLd6CuAJubbKo6r0jh3nb5et22bbfGY= github.com/Showmax/go-fqdn v1.0.0 h1:0rG5IbmVliNT5O19Mfuvna9LL7zlHyRfsSvBPZmF9tM= github.com/Showmax/go-fqdn v1.0.0/go.mod h1:SfrFBzmDCtCGrnHhoDjuvFnKsWjEQX/Q9ARZvOrJAko= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= @@ -103,6 +102,8 @@ github.com/Velocidex/sflags v0.3.1-0.20210402155316-b09f53df5162 h1:wcYgZ8Z8w0JN github.com/Velocidex/sflags v0.3.1-0.20210402155316-b09f53df5162/go.mod h1:VbiCXG8oNiOTw+Dv6B0Qc3lKlbhlArfBMX20om018ZA= github.com/Velocidex/survey v1.8.7-0.20190926071832-2ff99cc7aa49 h1:TJVN1zYl5sKJUq571gYqU/5VqFIap9MUo2KNujc0fcg= github.com/Velocidex/survey v1.8.7-0.20190926071832-2ff99cc7aa49/go.mod h1:kfPUQ2gP0xtIydiR52dirNYt4OvCr+iZuepL4XaIk58= +github.com/Velocidex/ttlcache/v2 v2.9.1-0.20211116035050-ddd93fed62f5 h1:ELXxIv+ozRkKsxbTgxCCsh8e2oE1pZKjZgmdznlfgMA= +github.com/Velocidex/ttlcache/v2 v2.9.1-0.20211116035050-ddd93fed62f5/go.mod h1:3/pI9BBAF7gydBWvMVtV7W1qRwshEG9lBwed/d8xfFg= github.com/Velocidex/yaml v2.1.0+incompatible h1:XtpJSzJ95+UbheJ5ZwHZOUH8sHhYpypGaL3zmaL2klc= github.com/Velocidex/yaml v2.1.0+incompatible/go.mod h1:CzH+PG+PA8o7RiH4/ZkHx5rHDWFZlzZVc3R6pe/gJMg= github.com/Velocidex/yaml/v2 v2.2.5 h1:8XZwR8tmm5UWAXotI3fL17s9fjKEMqZ293fgqUiMhBw= @@ -157,8 +158,10 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e h1:hHg27A0RSSp2Om9lubZpiMgVbvn39bsUmW9U5h0twqc= github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA= -github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -168,7 +171,11 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk= github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= @@ -205,6 +212,7 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -219,8 +227,10 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -365,19 +375,23 @@ github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xl github.com/jonboulle/clockwork v0.2.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/juju/ratelimit v1.0.1 h1:+7AIFJVQ0EQgq/K9+0Krm7m530Du7tIz0METWzN0RgY= github.com/juju/ratelimit v1.0.1/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -440,6 +454,7 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/octago/sflags v0.2.0 h1:XceYzkRXGAHa/lSFmKLcaxSrsh4MTuOMQdIGsUD0wlk= @@ -461,23 +476,29 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/cachecontrol v0.0.0-20200921180117-858c6e7e6b7e h1:BLqxdwZ6j771IpSCRx7s/GJjXHUE00Hmu7/YegCGdzA= github.com/pquerna/cachecontrol v0.0.0-20200921180117-858c6e7e6b7e/go.mod h1:hoLfEwdY11HjRfKFH6KqnPsfxlo3BP6bJehpDv8t6sQ= -github.com/processout/grpc-go-pool v1.2.1 h1:hbp1BOA02CIxEAoRLHGpUhhPFv77nwfBLBeO3Ya9P7I= -github.com/processout/grpc-go-pool v1.2.1/go.mod h1:F4hiNj96O6VQ87jv4rdz8R9tkHdelQQJ/J2B1a5VSt4= +github.com/processout/grpc-go-pool v1.2.2-0.20200228131710-c0fcf3af0014 h1:ZBlVnM3IzLIMpdsq8aZNB9f6jPe61avIHNlfc4+HqJI= +github.com/processout/grpc-go-pool v1.2.2-0.20200228131710-c0fcf3af0014/go.mod h1:F4hiNj96O6VQ87jv4rdz8R9tkHdelQQJ/J2B1a5VSt4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI= -github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY= -github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.29.0 h1:3jqPBvKT4OHAbje2Ql7KeaaSicDBCxMYwEJU1zRJceE= +github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8= -github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.2 h1:zE6zJjRS9S916ptrZ326OU0++1XRwHgxkvCFflxx6Fo= +github.com/prometheus/procfs v0.7.2/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/qri-io/starlib v0.5.0 h1:NlveoBAhO6mNgM7+JpM9QlHh3/3pOtOiH6iXaqSdVK0= github.com/qri-io/starlib v0.5.0/go.mod h1:FpVumyB2CMrKIrjf39fAi4uydYWVvnWEvXEOwfzZRHY= github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 h1:mZHayPoR0lNmnHyvtYjDeq0zlVHn9K/ZXoy17ylucdo= @@ -506,9 +527,11 @@ github.com/shirou/gopsutil v3.21.6+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -647,6 +670,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -693,11 +717,11 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -712,6 +736,7 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -730,6 +755,7 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -898,8 +924,8 @@ google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm google.golang.org/genproto v0.0.0-20210714021259-044028024a4f/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= google.golang.org/genproto v0.0.0-20210721163202-f1cecdd8b78a/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f h1:YORWxaStkWBnWgELOHTmDrqNlFXuVGEbhwbB5iK94bQ= -google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20211112145013-271947fe86fd h1:8jqRgiTTWyKMDOM2AvhjA5dZLBSKXg1yFupPRBV/4fQ= +google.golang.org/genproto v0.0.0-20211112145013-271947fe86fd/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -922,8 +948,10 @@ google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0 h1:Klz8I9kdtkIN6EpHHUOMLCYhTn/2WAe5a0s1hcBkdTI= google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0 h1:XT2/MFpuPFsEX2fWh3YQtHkZ+WYZFQRfaUgLZYj/p6A= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -959,6 +987,7 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/grpc_client/grpc.go b/grpc_client/grpc.go index 3a771663a3c..cb23b2822ff 100644 --- a/grpc_client/grpc.go +++ b/grpc_client/grpc.go @@ -42,7 +42,6 @@ var ( mu sync.Mutex creds credentials.TransportCredentials - pool_mu sync.Mutex pool *grpcpool.Pool address string @@ -52,12 +51,19 @@ var ( Name: "grpc_client_calls", Help: "Total number of internal gRPC calls.", }) + + grpcTimeoutCounter = promauto.NewCounter(prometheus.CounterOpts{ + Name: "grpc_client_timeouts", + Help: "Total number of timeouts in getting a connection from the pool.", + }) + + grpcPoolWaiters = promauto.NewGauge(prometheus.GaugeOpts{ + Name: "grpc_client_waiters", + Help: "Total number of waiters for a grpc client channel.", + }) ) func getCreds(config_obj *config_proto.Config) (credentials.TransportCredentials, error) { - mu.Lock() - defer mu.Unlock() - if creds == nil { var certificate, private_key, ca_certificate, server_name string @@ -130,43 +136,21 @@ func getChannel( ctx context.Context, config_obj *config_proto.Config) (*grpcpool.ClientConn, error) { - pool_mu.Lock() - defer pool_mu.Unlock() - - // Pool does not exist - make a new one. - if pool == nil { - address = GetAPIConnectionString(config_obj) - creds, err := getCreds(config_obj) - if err != nil { - return nil, err + // Collect number of callers waiting for a channel - this + // indicates backpressure from the grpc pool. + grpcPoolWaiters.Inc() + defer grpcPoolWaiters.Dec() + + for { + conn, err := pool.Get(ctx) + if err == grpcpool.ErrTimeout { + grpcTimeoutCounter.Inc() + time.Sleep(time.Second) + continue } - factory := func() (*grpc.ClientConn, error) { - return grpc.Dial(address, - grpc.WithTransportCredentials(creds)) - - } - - max_size := 100 - max_wait := 60 - if config_obj.Frontend != nil { - if config_obj.Frontend.GRPCPoolMaxSize > 0 { - max_size = int(config_obj.Frontend.GRPCPoolMaxSize) - } - - if config_obj.Frontend.GRPCPoolMaxWait > 0 { - max_size = int(config_obj.Frontend.GRPCPoolMaxWait) - } - } - - pool, err = grpcpool.New(factory, 1, max_size, time.Duration(max_wait)*time.Second) - if err != nil { - return nil, errors.Errorf( - "Unable to connect to gRPC server: %v: %v", address, err) - } + return conn, err } - - return pool.Get(ctx) } func GetAPIConnectionString(config_obj *config_proto.Config) string { @@ -195,3 +179,46 @@ func GetAPIConnectionString(config_obj *config_proto.Config) string { panic("Unknown API.BindScheme") } + +func Init( + ctx context.Context, + config_obj *config_proto.Config) error { + + mu.Lock() + defer mu.Unlock() + + if pool != nil { + return nil + } + + address = GetAPIConnectionString(config_obj) + creds, err := getCreds(config_obj) + if err != nil { + return err + } + + factory := func(ctx context.Context) (*grpc.ClientConn, error) { + return grpc.DialContext(ctx, address, + grpc.WithTransportCredentials(creds)) + } + + max_size := 100 + max_wait := 60 + if config_obj.Frontend != nil { + if config_obj.Frontend.GRPCPoolMaxSize > 0 { + max_size = int(config_obj.Frontend.GRPCPoolMaxSize) + } + + if config_obj.Frontend.GRPCPoolMaxWait > 0 { + max_wait = int(config_obj.Frontend.GRPCPoolMaxWait) + } + } + + pool, err = grpcpool.NewWithContext(ctx, + factory, 1, max_size, time.Duration(max_wait)*time.Second) + if err != nil { + return errors.Errorf( + "Unable to connect to gRPC server: %v: %v", address, err) + } + return nil +} diff --git a/gui/velociraptor/src/components/utils/users.js b/gui/velociraptor/src/components/utils/users.js index fc66c432c2a..a87c0360fce 100644 --- a/gui/velociraptor/src/components/utils/users.js +++ b/gui/velociraptor/src/components/utils/users.js @@ -56,10 +56,14 @@ export default class UserForm extends React.Component { }; render() { + let default_value = _.map(this.props.value, x=>{ + return {value: x, label: x};}); + return ( /0]+)>") closing_tag_regex = regexp.MustCompile("") @@ -70,16 +73,8 @@ func GetNodeName() string { return node_name } -// Each node in a cluster stores logs in its own directory. -func SetNodeName(config_obj *config_proto.Config, name string) error { - mu.Lock() - defer mu.Unlock() - - node_name = name - return InitLogging(config_obj) -} - func InitLogging(config_obj *config_proto.Config) error { + mu.Lock() Manager = &LogManager{ contexts: make(map[*string]*LogContext), } @@ -94,6 +89,7 @@ func InitLogging(config_obj *config_proto.Config) error { } Manager.contexts[component] = logger } + mu.Unlock() FlushPrelogs(config_obj) @@ -105,8 +101,8 @@ func ClearMemoryLogs() { } func GetMemoryLogs() []string { - mu.Lock() - defer mu.Unlock() + memory_log_mu.Lock() + defer memory_log_mu.Unlock() return append([]string{}, memory_logs...) } @@ -116,8 +112,8 @@ func GetMemoryLogs() []string { // load (because the config is not fully loaded yet). We therefore // queue these messages until we are able to flush them. func Prelog(format string, v ...interface{}) { - mu.Lock() - defer mu.Unlock() + memory_log_mu.Lock() + defer memory_log_mu.Unlock() // Truncate too many logs if len(prelogs) > 1000 { @@ -129,7 +125,12 @@ func Prelog(format string, v ...interface{}) { func FlushPrelogs(config_obj *config_proto.Config) { logger := GetLogger(config_obj, &GenericComponent) - for _, msg := range prelogs { + + memory_log_mu.Lock() + lprelogs := append([]string{}, prelogs...) + memory_log_mu.Unlock() + + for _, msg := range lprelogs { logger.Info(msg) } prelogs = make([]string, 0) @@ -179,8 +180,7 @@ func (self *LogManager) GetLogger( self.mu.Lock() defer self.mu.Unlock() - if config_obj != nil && - config_obj.Logging != nil && + if config_obj.Logging != nil && !config_obj.Logging.SeparateLogsPerComponent { component = &GenericComponent } @@ -353,7 +353,11 @@ func NewPlainLogger( } func GetLogger(config_obj *config_proto.Config, component *string) *LogContext { - if Manager == nil { + mu.Lock() + lManager := Manager + mu.Unlock() + + if lManager == nil { err := InitLogging(config_obj) if err != nil { panic(err) @@ -384,8 +388,8 @@ func clearTag(message string) string { type inMemoryLogWriter struct{} func (self inMemoryLogWriter) Write(p []byte) (n int, err error) { - mu.Lock() - defer mu.Unlock() + memory_log_mu.Lock() + defer memory_log_mu.Unlock() // Truncate too many logs if len(memory_logs) > 1000 { diff --git a/paths/artifacts/paths_test.go b/paths/artifacts/paths_test.go index 24f1c348334..08a2b7a0a34 100644 --- a/paths/artifacts/paths_test.go +++ b/paths/artifacts/paths_test.go @@ -112,7 +112,7 @@ func (self *PathManageTestSuite) TestPathManager() { testcase.full_artifact_name) assert.NoError(self.T(), err) - path_manager.Clock = utils.MockClock{MockNow: time.Unix(ts, 0)} + path_manager.Clock = &utils.MockClock{MockNow: time.Unix(ts, 0)} path, err := path_manager.GetPathForWriting() assert.NoError(self.T(), err) assert.Equal(self.T(), diff --git a/paths/client.go b/paths/client.go index 590850b9cad..5333d28dd49 100644 --- a/paths/client.go +++ b/paths/client.go @@ -28,7 +28,9 @@ func NewClientPathManager(client_id string) *ClientPathManager { // We store the last time we saw the client in this location. func (self ClientPathManager) Ping() api.DSPathSpec { - return self.root.AddChild("ping").SetTag("ClientPing") + return self.root.AddChild("ping"). + SetType(api.PATH_TYPE_DATASTORE_JSON). + SetTag("ClientPing") } // Keep a record of all the client's labels. diff --git a/paths/client_test.go b/paths/client_test.go index 4793fd1855d..5dcbd46c9c9 100644 --- a/paths/client_test.go +++ b/paths/client_test.go @@ -11,7 +11,7 @@ func (self *PathManagerTestSuite) TestClientPathManager() { assert.Equal(self.T(), "/ds/clients/C.123.db", self.getDatastorePath(manager.Path())) - assert.Equal(self.T(), "/ds/clients/C.123/ping.db", + assert.Equal(self.T(), "/ds/clients/C.123/ping.json.db", self.getDatastorePath(manager.Ping())) assert.Equal(self.T(), "/ds/clients/C.123/labels.json.db", diff --git a/paths/notebooks_test.go b/paths/notebooks_test.go index fdf64c5bd22..9a7dde38aeb 100644 --- a/paths/notebooks_test.go +++ b/paths/notebooks_test.go @@ -10,7 +10,7 @@ import ( func (self *PathManagerTestSuite) TestNotebookPathManager() { manager := paths.NewNotebookPathManager("N.123") - manager.Clock = utils.MockClock{ + manager.Clock = &utils.MockClock{ MockNow: time.Unix(1000000000, 0).UTC(), } diff --git a/reporting/gui.go b/reporting/gui.go index bcc68df3e3e..bf8931d2f3a 100644 --- a/reporting/gui.go +++ b/reporting/gui.go @@ -500,6 +500,12 @@ func (self *GuiTemplateEngine) Query(queries ...string) interface{} { self.Error("Error: %v\n", err) return nil } + + // We must ensure results are visible immediately because + // the GUI will need to refresh the cell content as soon + // as we complete. + rs_writer.SetSync() + defer rs_writer.Close() rs_writer.Flush() diff --git a/result_sets/api.go b/result_sets/api.go index d7cc7704935..2d497ee3744 100644 --- a/result_sets/api.go +++ b/result_sets/api.go @@ -13,12 +13,22 @@ type ResultSetWriter interface { Write(row *ordereddict.Dict) Flush() Close() + + // Will be called when the result set is flushed to hard storage. + SetCompletion(fn func()) + + // Ensures that results are flushed to storage as soon as the + // writer is closed. + SetSync() } type TimedResultSetWriter interface { Write(row *ordereddict.Dict) Flush() Close() + + // Will be called when the result set is flushed to hard storage. + SetCompletion(fn func()) } type ResultSetReader interface { diff --git a/result_sets/simple/simple.go b/result_sets/simple/simple.go index 3efd9076c22..59250ccf043 100644 --- a/result_sets/simple/simple.go +++ b/result_sets/simple/simple.go @@ -53,6 +53,24 @@ type ResultSetWriterImpl struct { opts *json.EncOpts fd api.FileWriter index_fd api.FileWriter + + completion func() + + sync bool +} + +func (self *ResultSetWriterImpl) SetCompletion(completion func()) { + self.mu.Lock() + defer self.mu.Unlock() + + self.completion = completion +} + +func (self *ResultSetWriterImpl) SetSync() { + self.mu.Lock() + defer self.mu.Unlock() + + self.sync = true } // WriteJSONL writes an entire JSONL blob to the end of the result @@ -145,6 +163,11 @@ func (self *ResultSetWriterImpl) Close() { self.Flush() self.fd.Close() self.index_fd.Close() + + if self.sync { + self.fd.Flush() + self.index_fd.Flush() + } } type ResultSetFactory struct{} @@ -155,12 +178,19 @@ func (self ResultSetFactory) NewResultSetWriter( opts *json.EncOpts, truncate bool) (result_sets.ResultSetWriter, error) { + result := &ResultSetWriterImpl{opts: opts} + // If no path is provided, we are just a log sink if utils.IsNil(log_path) { return &NullResultSetWriter{}, nil } - fd, err := file_store_factory.WriteFile(log_path) + fd, err := file_store_factory.WriteFileWithCompletion( + log_path, func() { + if result.completion != nil { + result.completion() + } + }) if err != nil { return nil, err } @@ -189,7 +219,10 @@ func (self ResultSetFactory) NewResultSetWriter( } - return &ResultSetWriterImpl{fd: fd, opts: opts, index_fd: idx_fd}, nil + result.fd = fd + result.index_fd = idx_fd + + return result, nil } // A ResultSetReader can produce rows from a result set. diff --git a/result_sets/simple/simple_test.go b/result_sets/simple/simple_test.go index 21c0c1d78cb..5d27fbc8b6c 100644 --- a/result_sets/simple/simple_test.go +++ b/result_sets/simple/simple_test.go @@ -5,15 +5,11 @@ import ( "io/ioutil" "os" "testing" - "time" "github.com/Velocidex/ordereddict" "github.com/alecthomas/assert" "github.com/sebdah/goldie" - "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" - "www.velocidex.com/golang/velociraptor/config" - config_proto "www.velocidex.com/golang/velociraptor/config/proto" "www.velocidex.com/golang/velociraptor/file_store" "www.velocidex.com/golang/velociraptor/file_store/api" "www.velocidex.com/golang/velociraptor/file_store/test_utils" @@ -22,12 +18,8 @@ import ( "www.velocidex.com/golang/velociraptor/paths/artifacts" "www.velocidex.com/golang/velociraptor/result_sets" "www.velocidex.com/golang/velociraptor/result_sets/simple" - "www.velocidex.com/golang/velociraptor/services" - "www.velocidex.com/golang/velociraptor/services/inventory" - "www.velocidex.com/golang/velociraptor/services/journal" - "www.velocidex.com/golang/velociraptor/services/launcher" - "www.velocidex.com/golang/velociraptor/services/notifications" - "www.velocidex.com/golang/velociraptor/services/repository" + + _ "www.velocidex.com/golang/velociraptor/result_sets/timed" ) var test_cases = []struct { @@ -41,44 +33,23 @@ var test_cases = []struct { } type ResultSetTestSuite struct { - suite.Suite + test_utils.TestSuite - config_obj *config_proto.Config file_store api.FileStore client_id, flow_id string - sm *services.Service } func (self *ResultSetTestSuite) SetupTest() { - var err error - self.config_obj, err = new(config.Loader).WithFileLoader( - "../../http_comms/test_data/server.config.yaml"). - WithRequiredFrontend().WithWriteback(). - LoadAndValidate() - require.NoError(self.T(), err) + self.TestSuite.SetupTest() self.client_id = "C.12312" self.flow_id = "F.1232" - self.file_store = file_store.GetFileStore(self.config_obj) - - // Start essential services. - ctx, _ := context.WithTimeout(context.Background(), time.Second*60) - self.sm = services.NewServiceManager(ctx, self.config_obj) - - require.NoError(self.T(), self.sm.Start(journal.StartJournalService)) - require.NoError(self.T(), self.sm.Start(notifications.StartNotificationService)) - require.NoError(self.T(), self.sm.Start(launcher.StartLauncherService)) - require.NoError(self.T(), self.sm.Start(inventory.StartInventoryService)) - require.NoError(self.T(), self.sm.Start(repository.StartRepositoryManager)) -} - -func (self *ResultSetTestSuite) TearDownTest() { - test_utils.GetMemoryFileStore(self.T(), self.config_obj).Clear() + self.file_store = file_store.GetFileStore(self.ConfigObj) } func (self *ResultSetTestSuite) TestResultSetSimple() { path_manager, err := artifacts.NewArtifactPathManager( - self.config_obj, + self.ConfigObj, self.client_id, self.flow_id, "Generic.Client.Info/BasicInformation") @@ -218,6 +189,9 @@ func (self *ResultSetTestSuite) TestResultSetWriterTruncate() { } func (self *ResultSetTestSuite) TestResultSetWriterWriteJSONL() { + // Use a new client id + self.client_id = "C.12313" + // WriteJSONL is supposed to optimize the write load by // writing large JSON chunks into the result set. We // deliberately do not want to parse it out so we just append @@ -231,9 +205,6 @@ func (self *ResultSetTestSuite) TestResultSetWriterWriteJSONL() { rs.WriteJSONL([]byte("{\"Foo\":2}\n{\"Foo\":3}\n"), 2) rs.Close() - //v := test_utils.GetMemoryFileStore(self.T(), self.config_obj) - //utils.Debug(v) - rs_reader, err := result_sets.NewResultSetReader(self.file_store, path_manager) assert.NoError(self.T(), err) @@ -258,23 +229,21 @@ type ResultSetTestSuiteFileBased struct { } func (self *ResultSetTestSuiteFileBased) SetupTest() { - var err error - self.config_obj, err = new(config.Loader).WithFileLoader( - "../../http_comms/test_data/server.config.yaml"). - WithRequiredFrontend().WithWriteback(). - LoadAndValidate() - require.NoError(self.T(), err) + self.ConfigObj = self.LoadConfig() + var err error self.dir, err = ioutil.TempDir("", "file_store_test") assert.NoError(self.T(), err) - self.config_obj.Datastore.Implementation = "FileBaseDataStore" - self.config_obj.Datastore.FilestoreDirectory = self.dir - self.config_obj.Datastore.Location = self.dir + self.ConfigObj.Datastore.Implementation = "FileBaseDataStore" + self.ConfigObj.Datastore.FilestoreDirectory = self.dir + self.ConfigObj.Datastore.Location = self.dir self.client_id = "C.12312" self.flow_id = "F.1232" - self.file_store = file_store.GetFileStore(self.config_obj) + self.file_store = file_store.GetFileStore(self.ConfigObj) + + self.TestSuite.SetupTest() } func (self *ResultSetTestSuiteFileBased) TearDownTest() { diff --git a/result_sets/simple/sink.go b/result_sets/simple/sink.go index 906a2cd3974..9922d307303 100644 --- a/result_sets/simple/sink.go +++ b/result_sets/simple/sink.go @@ -10,3 +10,5 @@ func (self NullResultSetWriter) WriteJSONL( func (self NullResultSetWriter) Write(row *ordereddict.Dict) {} func (self NullResultSetWriter) Flush() {} func (self NullResultSetWriter) Close() {} +func (self NullResultSetWriter) SetCompletion(f func()) {} +func (self NullResultSetWriter) SetSync() {} diff --git a/result_sets/timed/reader_test.go b/result_sets/timed/reader_test.go index ce51df604f6..a9313a3849e 100644 --- a/result_sets/timed/reader_test.go +++ b/result_sets/timed/reader_test.go @@ -20,7 +20,7 @@ func (self *TimedResultSetTestSuite) TestTimedResultSetMigration() { // Start off by writing some events on a queue. path_manager, err := artifacts.NewArtifactPathManager( - self.config_obj, + self.ConfigObj, self.client_id, self.flow_id, "Windows.Events.ProcessCreation") @@ -29,7 +29,7 @@ func (self *TimedResultSetTestSuite) TestTimedResultSetMigration() { // Recreate events from older version. Previously we used the // regular ResultSetWriter to write unindexed files. - file_store_factory := file_store.GetFileStore(self.config_obj) + file_store_factory := file_store.GetFileStore(self.ConfigObj) // Push an event every hour for 48 hours. for i := int64(0); i < 50; i++ { @@ -51,10 +51,10 @@ func (self *TimedResultSetTestSuite) TestTimedResultSetMigration() { result := ordereddict.NewDict() rs_reader, err := result_sets.NewTimedResultSetReader( - self.ctx, self.file_store, path_manager) + self.Sm.Ctx, file_store_factory, path_manager) assert.NoError(self.T(), err) - result.Set("Available Files", rs_reader.GetAvailableFiles(self.ctx)) + result.Set("Available Files", rs_reader.GetAvailableFiles(self.Sm.Ctx)) for _, testcase := range timed_result_set_tests { err = rs_reader.SeekToTime(time.Unix(int64(testcase.start_time), 0)) @@ -63,7 +63,7 @@ func (self *TimedResultSetTestSuite) TestTimedResultSetMigration() { rs_reader.SetMaxTime(time.Unix(int64(testcase.end_time), 0)) rows := make([]*ordereddict.Dict, 0) - for row := range rs_reader.Rows(self.ctx) { + for row := range rs_reader.Rows(self.Sm.Ctx) { rows = append(rows, row) } result.Set(testcase.name, rows) diff --git a/result_sets/timed/writer.go b/result_sets/timed/writer.go index 048e872428a..416466cf0ee 100644 --- a/result_sets/timed/writer.go +++ b/result_sets/timed/writer.go @@ -42,10 +42,15 @@ type TimedResultSetWriterImpl struct { log_path api.FSPathSpec last_log_base string writer *timelines.TimelineWriter + completion func() Clock utils.Clock } +func (self *TimedResultSetWriterImpl) SetCompletion(completion func()) { + self.completion = completion +} + func (self *TimedResultSetWriterImpl) Write(row *ordereddict.Dict) { self.rows = append(self.rows, rowContainer{ row: row, @@ -101,6 +106,8 @@ func (self *TimedResultSetWriterImpl) getWriter(ts time.Time) ( return nil, err } + writer.SetCompletion(self.completion) + self.log_path = log_path self.last_log_base = log_path.Base() diff --git a/result_sets/timed/writer_test.go b/result_sets/timed/writer_test.go index a082b527965..a6594144e2d 100644 --- a/result_sets/timed/writer_test.go +++ b/result_sets/timed/writer_test.go @@ -1,7 +1,6 @@ package timed import ( - "context" "io/ioutil" "os" "testing" @@ -10,22 +9,12 @@ import ( "github.com/Velocidex/ordereddict" "github.com/sebdah/goldie" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" - "www.velocidex.com/golang/velociraptor/config" - config_proto "www.velocidex.com/golang/velociraptor/config/proto" "www.velocidex.com/golang/velociraptor/file_store" - "www.velocidex.com/golang/velociraptor/file_store/api" "www.velocidex.com/golang/velociraptor/file_store/test_utils" "www.velocidex.com/golang/velociraptor/json" "www.velocidex.com/golang/velociraptor/paths/artifacts" "www.velocidex.com/golang/velociraptor/result_sets" - "www.velocidex.com/golang/velociraptor/services" - "www.velocidex.com/golang/velociraptor/services/inventory" - "www.velocidex.com/golang/velociraptor/services/journal" - "www.velocidex.com/golang/velociraptor/services/launcher" - "www.velocidex.com/golang/velociraptor/services/notifications" - "www.velocidex.com/golang/velociraptor/services/repository" "www.velocidex.com/golang/velociraptor/utils" ) @@ -62,40 +51,19 @@ var timed_result_set_tests = []struct { } type TimedResultSetTestSuite struct { - suite.Suite - - config_obj *config_proto.Config - file_store api.FileStore + test_utils.TestSuite client_id, flow_id string - sm *services.Service - ctx context.Context } func (self *TimedResultSetTestSuite) SetupTest() { - var err error - self.config_obj, err = new(config.Loader).WithFileLoader( - "../../http_comms/test_data/server.config.yaml"). - WithRequiredFrontend().WithWriteback(). - LoadAndValidate() - require.NoError(self.T(), err) + self.TestSuite.SetupTest() + self.LoadArtifacts([]string{` +name: Windows.Events.ProcessCreation +type: CLIENT_EVENT +`}) self.client_id = "C.12312" self.flow_id = "F.1232" - self.file_store = file_store.GetFileStore(self.config_obj) - - // Start essential services. - self.ctx, _ = context.WithTimeout(context.Background(), time.Second*60) - self.sm = services.NewServiceManager(self.ctx, self.config_obj) - - require.NoError(self.T(), self.sm.Start(journal.StartJournalService)) - require.NoError(self.T(), self.sm.Start(notifications.StartNotificationService)) - require.NoError(self.T(), self.sm.Start(launcher.StartLauncherService)) - require.NoError(self.T(), self.sm.Start(inventory.StartInventoryService)) - require.NoError(self.T(), self.sm.Start(repository.StartRepositoryManager)) -} - -func (self *TimedResultSetTestSuite) TearDownTest() { - test_utils.GetMemoryFileStore(self.T(), self.config_obj).Clear() } func (self *TimedResultSetTestSuite) TestTimedResultSetWriting() { @@ -104,14 +72,14 @@ func (self *TimedResultSetTestSuite) TestTimedResultSetWriting() { // Start off by writing some events on a queue. path_manager, err := artifacts.NewArtifactPathManager( - self.config_obj, + self.ConfigObj, self.client_id, self.flow_id, "Windows.Events.ProcessCreation") assert.NoError(self.T(), err) path_manager.Clock = clock - file_store_factory := file_store.GetFileStore(self.config_obj) + file_store_factory := file_store.GetFileStore(self.ConfigObj) writer, err := NewTimedResultSetWriter( file_store_factory, path_manager, nil) assert.NoError(self.T(), err) @@ -132,15 +100,13 @@ func (self *TimedResultSetTestSuite) TestTimedResultSetWriting() { writer.Close() - // test_utils.GetMemoryFileStore(self.T(), self.config_obj).Debug() - result := ordereddict.NewDict() rs_reader, err := result_sets.NewTimedResultSetReader( - self.ctx, self.file_store, path_manager) + self.Sm.Ctx, file_store_factory, path_manager) assert.NoError(self.T(), err) - result.Set("Available Files", rs_reader.GetAvailableFiles(self.ctx)) + result.Set("Available Files", rs_reader.GetAvailableFiles(self.Sm.Ctx)) for _, testcase := range timed_result_set_tests { err = rs_reader.SeekToTime(time.Unix(int64(testcase.start_time), 0)) @@ -149,7 +115,7 @@ func (self *TimedResultSetTestSuite) TestTimedResultSetWriting() { rs_reader.SetMaxTime(time.Unix(int64(testcase.end_time), 0)) rows := make([]*ordereddict.Dict, 0) - for row := range rs_reader.Rows(self.ctx) { + for row := range rs_reader.Rows(self.Sm.Ctx) { rows = append(rows, row) } result.Set(testcase.name, rows) @@ -170,26 +136,19 @@ type TimedResultSetTestSuiteFileBased struct { func (self *TimedResultSetTestSuiteFileBased) SetupTest() { var err error - self.config_obj, err = new(config.Loader).WithFileLoader( - "../../http_comms/test_data/server.config.yaml"). - WithRequiredFrontend().WithWriteback(). - LoadAndValidate() - require.NoError(self.T(), err) - self.dir, err = ioutil.TempDir("", "file_store_test") assert.NoError(self.T(), err) - self.ctx, _ = context.WithTimeout(context.Background(), time.Second*60) - self.config_obj.Datastore.Implementation = "FileBaseDataStore" - self.config_obj.Datastore.FilestoreDirectory = self.dir - self.config_obj.Datastore.Location = self.dir + self.ConfigObj = self.LoadConfig() + self.ConfigObj.Datastore.Implementation = "FileBaseDataStore" + self.ConfigObj.Datastore.FilestoreDirectory = self.dir + self.ConfigObj.Datastore.Location = self.dir - self.client_id = "C.12312" - self.flow_id = "F.1232" - self.file_store = file_store.GetFileStore(self.config_obj) + self.TimedResultSetTestSuite.SetupTest() } func (self *TimedResultSetTestSuiteFileBased) TearDownTest() { + self.TimedResultSetTestSuite.TearDownTest() os.RemoveAll(self.dir) } diff --git a/search/clients.go b/search/clients.go index 6a7675d4e2c..1d46f3cf84e 100644 --- a/search/clients.go +++ b/search/clients.go @@ -108,23 +108,10 @@ func FastGetApiClient( return nil, err } - if client_info.Info == nil { + if client_info == nil { return nil, errors.New("Invalid client_info") } - db, err := datastore.GetDB(config_obj) - if err != nil { - return nil, err - } - - ping_info := &actions_proto.ClientInfo{} - client_path_manager := paths.NewClientPathManager(client_id) - err = db.GetSubject(config_obj, client_path_manager.Ping(), ping_info) - if err != nil { - // Offline clients do not have ping files, so - // this is not actually an error. - } - labeler := services.GetLabeler() if labeler == nil { return nil, errors.New("Labeler not ready") @@ -134,18 +121,18 @@ func FastGetApiClient( ClientId: client_id, Labels: labeler.GetClientLabels(config_obj, client_id), AgentInformation: &api_proto.AgentInformation{ - Version: client_info.Info.ClientVersion, - Name: client_info.Info.ClientName, + Version: client_info.ClientVersion, + Name: client_info.ClientName, }, OsInfo: &api_proto.Uname{ - System: client_info.Info.System, - Hostname: client_info.Info.Hostname, - Release: client_info.Info.Release, - Machine: client_info.Info.Architecture, - Fqdn: client_info.Info.Fqdn, + System: client_info.System, + Hostname: client_info.Hostname, + Release: client_info.Release, + Machine: client_info.Architecture, + Fqdn: client_info.Fqdn, }, - LastSeenAt: ping_info.Ping, - LastIp: ping_info.IpAddress, - LastInterrogateFlowId: client_info.Info.LastInterrogateFlowId, + LastSeenAt: client_info.Ping, + LastIp: client_info.IpAddress, + LastInterrogateFlowId: client_info.LastInterrogateFlowId, }, nil } diff --git a/server/comms.go b/server/comms.go index 6a4fc01b50e..0874873a48b 100644 --- a/server/comms.go +++ b/server/comms.go @@ -350,7 +350,7 @@ func control(server_obj *Server) http.Handler { false, // drain_requests_for_client ) if err != nil { - server_obj.Error("Error:", err) + server_obj.Error("Error: %v", err) } else { sync <- response } @@ -477,7 +477,7 @@ func reader(config_obj *config_proto.Config, server_obj *Server) http.Handler { true, // drain_requests_for_client ) if err != nil { - server_obj.Error("Error:", err) + server_obj.Error("Error: %v", err) return } @@ -509,7 +509,7 @@ func reader(config_obj *config_proto.Config, server_obj *Server) http.Handler { true, // drain_requests_for_client ) if err != nil { - server_obj.Error("Error:", err) + server_obj.Error("Error: %v", err) return } diff --git a/server/server.go b/server/server.go index 577b62a3c41..7456423a994 100644 --- a/server/server.go +++ b/server/server.go @@ -31,7 +31,6 @@ import ( "github.com/juju/ratelimit" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" - actions_proto "www.velocidex.com/golang/velociraptor/actions/proto" "www.velocidex.com/golang/velociraptor/clients" config_proto "www.velocidex.com/golang/velociraptor/config/proto" "www.velocidex.com/golang/velociraptor/crypto" @@ -40,7 +39,7 @@ import ( "www.velocidex.com/golang/velociraptor/datastore" "www.velocidex.com/golang/velociraptor/flows" "www.velocidex.com/golang/velociraptor/logging" - "www.velocidex.com/golang/velociraptor/paths" + "www.velocidex.com/golang/velociraptor/services" "www.velocidex.com/golang/velociraptor/utils" ) @@ -274,19 +273,9 @@ func (self *Server) Process( return nil, 0, err } - // Record some stats about the client. - client_info := &actions_proto.ClientInfo{ - Ping: uint64(time.Now().UTC().UnixNano() / 1000), - IpAddress: message_info.RemoteAddr, - } - db, err := datastore.GetDB(self.config) - if err != nil { - return nil, 0, err - } - - client_path_manager := paths.NewClientPathManager(message_info.Source) - err = db.SetSubject( - self.config, client_path_manager.Ping(), client_info) + client_info_manager := services.GetClientInfoManager() + err = client_info_manager.UpdatePing( + message_info.Source, message_info.RemoteAddr) if err != nil { return nil, 0, err } diff --git a/server/server_test.go b/server/server_test.go index b0d2406ddf5..a60c58f0e64 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -22,14 +22,12 @@ import ( crypto_client "www.velocidex.com/golang/velociraptor/crypto/client" crypto_proto "www.velocidex.com/golang/velociraptor/crypto/proto" "www.velocidex.com/golang/velociraptor/datastore" - "www.velocidex.com/golang/velociraptor/file_store" file_store_api "www.velocidex.com/golang/velociraptor/file_store/api" "www.velocidex.com/golang/velociraptor/file_store/test_utils" "www.velocidex.com/golang/velociraptor/flows" flows_proto "www.velocidex.com/golang/velociraptor/flows/proto" "www.velocidex.com/golang/velociraptor/paths" "www.velocidex.com/golang/velociraptor/paths/artifacts" - "www.velocidex.com/golang/velociraptor/result_sets" "www.velocidex.com/golang/velociraptor/server" "www.velocidex.com/golang/velociraptor/services" "www.velocidex.com/golang/velociraptor/services/client_monitoring" @@ -247,7 +245,7 @@ func (self *ServerTestSuite) TestForeman() { // We do not want to trigger an event // table update in this test so we // pretend our version is later than - // the automatics table that will be + // the automatic table that will be // created. LastEventTableVersion: 10000000000000000000, }, @@ -264,20 +262,6 @@ func (self *ServerTestSuite) TestForeman() { require.NotNil(t, tasks[0].UpdateForeman) assert.Equal(t, tasks[0].UpdateForeman.LastHuntTimestamp, services.GetHuntDispatcher(). GetLastTimestamp()) - - path_manager, err := artifacts.NewArtifactPathManager(self.ConfigObj, - self.client_id, "", "System.Hunt.Participation") - assert.NoError(t, err) - - rows := []*ordereddict.Dict{} - file_store_factory := file_store.GetFileStore(self.ConfigObj) - rs_reader, err := result_sets.NewResultSetReader( - file_store_factory, path_manager.Path()) - assert.NoError(t, err) - for row := range rs_reader.Rows(self.Sm.Ctx) { - rows = append(rows, row) - } - assert.Equal(t, len(rows), 1) } func (self *ServerTestSuite) RequiredFilestoreContains( @@ -310,7 +294,7 @@ func (self *ServerTestSuite) TestMonitoring() { `[{"ClientId": "%s", "HuntId": "H.123"}]`, self.client_id), Query: &actions_proto.VQLRequest{ - Name: "System.Hunt.Participation", + Name: "Generic.Client.Stats", }, }, }) @@ -318,11 +302,10 @@ func (self *ServerTestSuite) TestMonitoring() { path_manager, err := artifacts.NewArtifactPathManager(self.ConfigObj, self.client_id, constants.MONITORING_WELL_KNOWN_FLOW, - "System.Hunt.Participation") + "Generic.Client.Stats") assert.NoError(self.T(), err) self.RequiredFilestoreContains(path_manager.Path(), self.client_id) - test_utils.GetMemoryFileStore(self.T(), self.ConfigObj).Debug() } // Monitoring queries which upload data. diff --git a/services/client_info.go b/services/client_info.go index 55dfecd316c..c648228b43f 100644 --- a/services/client_info.go +++ b/services/client_info.go @@ -3,6 +3,7 @@ package services import ( "sync" + "google.golang.org/protobuf/proto" actions_proto "www.velocidex.com/golang/velociraptor/actions/proto" ) @@ -35,26 +36,33 @@ func RegisterClientInfoManager(m ClientInfoManager) { } type ClientInfo struct { - Hostname string - OS ClientOS + // The original info from disk + actions_proto.ClientInfo +} - Info *actions_proto.ClientInfo +func (self ClientInfo) Copy() ClientInfo { + copy := proto.Clone(&self.ClientInfo).(*actions_proto.ClientInfo) + return ClientInfo{*copy} } -func (self ClientInfo) OSString() string { - switch self.OS { - case Windows: - return "windows" - case Linux: - return "Linux" - case MacOS: - return "MacOS" +func (self ClientInfo) OS() ClientOS { + switch self.System { + case "windows": + return Windows + case "linux": + return Linux + case "darwin": + return MacOS } - return "Unknown" + return Unknown } type ClientInfoManager interface { + UpdatePing(client_id, ip_address string) error Get(client_id string) (*ClientInfo, error) + + // Remove client id from the cache - this is needed when the + // record chages and we need to force a read from storage. Flush(client_id string) } diff --git a/services/client_info/client_info.go b/services/client_info/client_info.go index 8febc9aa430..e49e1b2d8cc 100644 --- a/services/client_info/client_info.go +++ b/services/client_info/client_info.go @@ -2,10 +2,12 @@ package client_info import ( "context" + "errors" "sync" "time" "github.com/Velocidex/ordereddict" + "github.com/Velocidex/ttlcache/v2" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" actions_proto "www.velocidex.com/golang/velociraptor/actions/proto" @@ -15,7 +17,7 @@ import ( "www.velocidex.com/golang/velociraptor/paths" "www.velocidex.com/golang/velociraptor/services" "www.velocidex.com/golang/velociraptor/services/journal" - "www.velocidex.com/golang/velociraptor/third_party/cache" + "www.velocidex.com/golang/velociraptor/utils" ) var ( @@ -24,24 +26,139 @@ var ( Name: "client_info_lru_size", Help: "Number of entries in client lru", }) + + invalidError = errors.New("Invalid") ) const ( - MAX_CACHE_AGE = 3600 + MAX_CACHE_AGE = 3600 + MAX_PING_SYNC_SEC = 10 ) type CachedInfo struct { + mu sync.Mutex + + owner *ClientInfoManager + + // Fresh data may not be flushed yet. + dirty bool + + // Data on disk record *services.ClientInfo - age time.Time + + last_flush uint64 +} + +// last seen is in uS +func (self *CachedInfo) UpdatePing( + last_seen uint64, ip_address string) { + self.mu.Lock() + defer self.mu.Unlock() + + if last_seen == 0 { + self.record.Ping = uint64(self.owner.Clock.Now().UnixNano() / 1000) + } else { + self.record.Ping = last_seen + } + + if ip_address != "" { + self.record.IpAddress = ip_address + } + self.dirty = true } -func (self CachedInfo) Size() int { - return 1 +// Update the ping and notify other client info managers of the change. +func (self *CachedInfo) UpdatePingAndNotify( + last_seen uint64, ip_address string) { + self.mu.Lock() + + if last_seen == 0 { + self.record.Ping = uint64(self.owner.Clock.Now().UnixNano() / 1000) + } else { + self.record.Ping = last_seen + } + + if ip_address != "" { + self.record.IpAddress = ip_address + } + self.dirty = true + + // Notify of the ping. + if self.record.Ping > self.last_flush+1000000*MAX_PING_SYNC_SEC { + self.mu.Unlock() + + self.owner.SendPing( + self.record.ClientId, ip_address, self.record.Ping) + return + + self.Flush() + return + } + + self.mu.Unlock() +} + +// Write ping record to data store +func (self *CachedInfo) Flush() error { + self.mu.Lock() + + // Nothing to do + if !self.dirty { + return nil + } + + ping_client_info := &actions_proto.ClientInfo{ + Ping: self.record.Ping, + IpAddress: self.record.IpAddress, + } + client_id := self.record.ClientId + self.dirty = false + self.last_flush = uint64(self.owner.Clock.Now().UnixNano() / 1000) + self.mu.Unlock() + + // Record some stats about the client. + db, err := datastore.GetDB(self.owner.config_obj) + if err != nil { + return err + } + + client_path_manager := paths.NewClientPathManager(client_id) + return db.SetSubject( + self.owner.config_obj, client_path_manager.Ping(), ping_client_info) } type ClientInfoManager struct { config_obj *config_proto.Config - lru *cache.LRUCache + + // Stores client_id -> *CachedInfo + lru *ttlcache.Cache + + Clock utils.Clock + + uuid int64 + + mu sync.Mutex + queue []*ordereddict.Dict +} + +func (self *ClientInfoManager) SendPing(client_id, ip_address string, ping uint64) { + self.mu.Lock() + self.queue = append(self.queue, ordereddict.NewDict(). + Set("ClientId", client_id). + Set("Ping", ping). + Set("IpAddress", ip_address). + Set("From", self.uuid)) + self.mu.Unlock() +} + +func (self *ClientInfoManager) UpdatePing(client_id, ip string) error { + cached_info, err := self.GetCacheInfo(client_id) + if err != nil { + return err + } + + cached_info.UpdatePingAndNotify(0, ip) + return nil } func (self *ClientInfoManager) Start( @@ -55,11 +172,61 @@ func (self *ClientInfoManager) Start( // Watch for clients that are deleted and remove from local cache. err := journal.WatchQueueWithCB(ctx, config_obj, wg, "Server.Internal.ClientDelete", self.ProcessInterrogateResults) - if err != nil { return err } + // The master will be informed when new clients appear. + is_master := services.IsMaster(config_obj) + if is_master { + err = journal.WatchQueueWithCB(ctx, config_obj, wg, + "Server.Internal.ClientPing", self.ProcessPing) + if err != nil { + return err + } + } else { + wait_time := uint64(10000) // 10 seconds + if config_obj.Frontend != nil && + config_obj.Frontend.Resources != nil && + config_obj.Frontend.Resources.MinionBatchWaitTimeMs > 0 { + wait_time = config_obj.Frontend.Resources.MinionBatchWaitTimeMs + } + + journal_manager, err := services.GetJournal() + if err != nil { + return err + } + + // Minions will push rows to the master to inform it about the + // new ping times. + wg.Add(1) + go func() { + defer wg.Done() + + for { + select { + case <-ctx.Done(): + return + + case <-time.After(time.Duration(wait_time) * time.Millisecond): + self.mu.Lock() + queue := self.queue + self.queue = nil + self.mu.Unlock() + + // Push the rows to the master. + if len(queue) > 0 { + err = journal_manager.PushRowsToArtifact( + self.config_obj, queue, + "Server.Internal.ClientPing", "server", "") + if err != nil { + logger.Debug("RPC Error: %v\n", err) + } + } + } + } + }() + } return journal.WatchQueueWithCB(ctx, config_obj, wg, "Server.Internal.Interrogation", self.ProcessInterrogateResults) } @@ -69,81 +236,173 @@ func (self *ClientInfoManager) ProcessInterrogateResults( row *ordereddict.Dict) error { client_id, pres := row.GetString("ClientId") if pres { - self.lru.Delete(client_id) + self.lru.Remove(client_id) } return nil } +func (self *ClientInfoManager) ProcessPing( + ctx context.Context, config_obj *config_proto.Config, + row *ordereddict.Dict) error { + + from, pres := row.GetInt64("From") + if !pres || from == 0 { + return invalidError + } + + // Ignore messages coming from us. + if from == self.uuid { + return nil + } + + client_id, pres := row.GetString("ClientId") + if !pres || client_id == "" { + return invalidError + } + + ping, pres := row.GetInt64("Ping") + if !pres || ping == 0 { + return invalidError + } + + ip_address, pres := row.GetString("IpAddress") + if !pres { + return invalidError + } + + cached_info, err := self.GetCacheInfo(client_id) + if err == nil { + // Update our internal cache but do not notify further (since + // this came from a notification anyway). + cached_info.UpdatePing(uint64(ping), ip_address) + } + + return nil +} + func (self *ClientInfoManager) Flush(client_id string) { - self.lru.Delete(client_id) - metricLRUCount.Dec() + cache_info, err := self.GetCacheInfo(client_id) + if err == nil { + cache_info.Flush() + } + self.lru.Remove(client_id) } func (self *ClientInfoManager) Clear() { - self.lru.Clear() + self.lru.Purge() } func (self *ClientInfoManager) Get(client_id string) (*services.ClientInfo, error) { - cached_any, ok := self.lru.Get(client_id) - if ok { - item := cached_any.(*CachedInfo) - duration := time.Since(item.age) - if duration.Seconds() < MAX_CACHE_AGE { - return item.record, nil + cached_info, err := self.GetCacheInfo(client_id) + if err != nil { + return nil, err + } + + // Return a copy so it can be read safely. + cached_info.mu.Lock() + res := cached_info.record.Copy() + cached_info.mu.Unlock() + + return &res, nil +} + +func (self *ClientInfoManager) GetCacheInfo(client_id string) (*CachedInfo, error) { + self.mu.Lock() + cached_any, err := self.lru.Get(client_id) + if err == nil { + cache_info, ok := cached_any.(*CachedInfo) + if ok { + self.mu.Unlock() + return cache_info, nil } } + // Unlock for potentially slow filesystem operations. + self.mu.Unlock() + db, err := datastore.GetDB(self.config_obj) if err != nil { return nil, err } - record := &actions_proto.ClientInfo{} + client_info := &services.ClientInfo{} client_path_manager := paths.NewClientPathManager(client_id) - err = db.GetSubject(self.config_obj, client_path_manager.Path(), record) + + // Read the main client record + err = db.GetSubject(self.config_obj, client_path_manager.Path(), + &client_info.ClientInfo) if err != nil { return nil, err } - os := services.Unknown - switch record.System { - case "windows": - os = services.Windows - case "linux": - os = services.Linux - case "darwin": - os = services.MacOS + cache_info := &CachedInfo{ + owner: self, + record: client_info, } - client_info := &services.ClientInfo{ - Hostname: record.Hostname, - OS: os, - Info: record, + // Now read the ping info in case it is there. + ping_info := &services.ClientInfo{} + err = db.GetSubject(self.config_obj, client_path_manager.Ping(), ping_info) + if err == nil { + client_info.Ping = ping_info.Ping + client_info.IpAddress = ping_info.IpAddress + cache_info.last_flush = ping_info.Ping } - self.lru.Set(client_id, &CachedInfo{ - record: client_info, - age: time.Now(), - }) + // Set the new cached info in the lru. + self.mu.Lock() + self.lru.Set(client_id, cache_info) + self.mu.Unlock() + metricLRUCount.Inc() - return client_info, nil + return cache_info, nil } -func StartClientInfoService( - ctx context.Context, - wg *sync.WaitGroup, - config_obj *config_proto.Config) error { - +func NewClientInfoManager(config_obj *config_proto.Config) *ClientInfoManager { expected_clients := int64(100) - if config_obj.Frontend != nil && config_obj.Frontend.Resources != nil { + if config_obj.Frontend != nil && + config_obj.Frontend.Resources != nil && + config_obj.Frontend.Resources.ExpectedClients > 0 { expected_clients = config_obj.Frontend.Resources.ExpectedClients } + // Calculate a unique id for each service. service := &ClientInfoManager{ config_obj: config_obj, - lru: cache.NewLRUCache(expected_clients), + uuid: utils.GetGUID(), + lru: ttlcache.NewCache(), + Clock: &utils.RealClock{}, } + + // When we teardown write the data to storage if needed. + defer service.lru.Purge() + + service.lru.SetCacheSizeLimit(int(expected_clients)) + service.lru.SetTTL(MAX_CACHE_AGE * time.Second) + + // Keep track of the total number of items in the lru. + service.lru.SetNewItemCallback(func(key string, value interface{}) { + metricLRUCount.Inc() + }) + + service.lru.SetExpirationCallback(func(key string, value interface{}) { + info, ok := value.(*CachedInfo) + if ok { + info.Flush() + } + metricLRUCount.Dec() + }) + + return service +} + +func StartClientInfoService( + ctx context.Context, + wg *sync.WaitGroup, + config_obj *config_proto.Config) error { + + service := NewClientInfoManager(config_obj) services.RegisterClientInfoManager(service) return service.Start(ctx, config_obj, wg) diff --git a/services/client_info/client_info_test.go b/services/client_info/client_info_test.go new file mode 100644 index 00000000000..fcf47b79750 --- /dev/null +++ b/services/client_info/client_info_test.go @@ -0,0 +1,123 @@ +package client_info_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/suite" + "google.golang.org/protobuf/proto" + actions_proto "www.velocidex.com/golang/velociraptor/actions/proto" + config_proto "www.velocidex.com/golang/velociraptor/config/proto" + "www.velocidex.com/golang/velociraptor/datastore" + "www.velocidex.com/golang/velociraptor/file_store/test_utils" + "www.velocidex.com/golang/velociraptor/paths" + "www.velocidex.com/golang/velociraptor/services" + "www.velocidex.com/golang/velociraptor/services/client_info" + "www.velocidex.com/golang/velociraptor/utils" + "www.velocidex.com/golang/velociraptor/vtesting" + "www.velocidex.com/golang/velociraptor/vtesting/assert" + + _ "www.velocidex.com/golang/velociraptor/result_sets/timed" +) + +type ClientInfoTestSuite struct { + test_utils.TestSuite + client_id string + clock *utils.MockClock +} + +func (self *ClientInfoTestSuite) SetupTest() { + self.TestSuite.SetupTest() + + // Create a client in the datastore + self.client_id = "C.1234" + db, err := datastore.GetDB(self.ConfigObj) + assert.NoError(self.T(), err) + + client_path_manager := paths.NewClientPathManager(self.client_id) + client_info := &actions_proto.ClientInfo{ + ClientId: self.client_id, + Hostname: "Hostname", + } + err = db.SetSubject(self.ConfigObj, client_path_manager.Path(), client_info) + assert.NoError(self.T(), err) + + self.clock = &utils.MockClock{ + MockNow: time.Unix(100, 0), + } + + self.LoadArtifacts([]string{` +name: Server.Internal.ClientPing +type: INTERNAL +`}) +} + +func (self *ClientInfoTestSuite) TestClientInfo() { + // Fetch the client from the manager + client_info_manager := services.GetClientInfoManager() + client_info_manager.(*client_info.ClientInfoManager).Clock = self.clock + + // Get a non-existing client id - should return an error + _, err := client_info_manager.Get("C.DOESNOTEXIT") + assert.Error(self.T(), err) + + info, err := client_info_manager.Get(self.client_id) + assert.NoError(self.T(), err) + assert.Equal(self.T(), info.ClientId, self.client_id) + assert.Equal(self.T(), info.Ping, uint64(0)) + + // Update the IP address + client_info_manager.UpdatePing(self.client_id, "127.0.0.1") + + // Now get the client record and check that it is updated + info, err = client_info_manager.Get(self.client_id) + assert.NoError(self.T(), err) + assert.Equal(self.T(), info.Ping, uint64(100*1000000)) + assert.Equal(self.T(), info.IpAddress, "127.0.0.1") + + // Now flush the record to storage + client_info_manager.Flush(self.client_id) + + // Check the stored ping record + db, err := datastore.GetDB(self.ConfigObj) + assert.NoError(self.T(), err) + + client_path_manager := paths.NewClientPathManager(self.client_id) + stored_client_info := &actions_proto.ClientInfo{} + err = db.GetSubject(self.ConfigObj, client_path_manager.Ping(), + stored_client_info) + assert.NoError(self.T(), err) + assert.Equal(self.T(), stored_client_info.IpAddress, "127.0.0.1") +} + +// Check that master and minion update each other. +func (self *ClientInfoTestSuite) TestMasterMinion() { + // Fetch the master client info manager + master_client_info_manager := services.GetClientInfoManager() + master_client_info_manager.(*client_info.ClientInfoManager).Clock = self.clock + + // Spin up a minion client_info manager + minion_config := proto.Clone(self.ConfigObj).(*config_proto.Config) + minion_config.Frontend.IsMinion = true + minion_config.Frontend.Resources.MinionBatchWaitTimeMs = 1 + + minion_client_info_manager := client_info.NewClientInfoManager(minion_config) + minion_client_info_manager.Clock = self.clock + + err := minion_client_info_manager.Start( + self.Sm.Ctx, minion_config, self.Sm.Wg) + assert.NoError(self.T(), err) + + // Update the minion timestamp + minion_client_info_manager.UpdatePing(self.client_id, "127.0.0.1") + + vtesting.WaitUntil(time.Second, self.T(), func() bool { + client_info, err := master_client_info_manager.Get(self.client_id) + assert.NoError(self.T(), err) + return client_info.IpAddress == "127.0.0.1" + }) +} + +func TestClientInfoService(t *testing.T) { + suite.Run(t, &ClientInfoTestSuite{}) +} diff --git a/services/frontend.go b/services/frontend.go index 893b5834048..b293d1913e4 100644 --- a/services/frontend.go +++ b/services/frontend.go @@ -6,6 +6,7 @@ import ( "sync" api_proto "www.velocidex.com/golang/velociraptor/api/proto" + config_proto "www.velocidex.com/golang/velociraptor/config/proto" ) // The frontend service manages load balancing between multiple @@ -35,8 +36,6 @@ func GetFrontendManager() FrontendManager { } type FrontendManager interface { - IsMaster() bool - // Establish a gRPC connection to the master node. If we are // running on the master node already then returns a // fs.ErrNotExist error. If we fail to connect returns another @@ -44,3 +43,11 @@ type FrontendManager interface { GetMasterAPIClient(ctx context.Context) ( api_proto.APIClient, func() error, error) } + +// Are we running on the master node? +func IsMaster(config_obj *config_proto.Config) bool { + if config_obj.Frontend != nil { + return !config_obj.Frontend.IsMinion + } + return true +} diff --git a/services/frontend/frontend.go b/services/frontend/frontend.go index abd39bb2d8e..589880ce7a6 100644 --- a/services/frontend/frontend.go +++ b/services/frontend/frontend.go @@ -18,6 +18,8 @@ import ( "google.golang.org/protobuf/proto" api_proto "www.velocidex.com/golang/velociraptor/api/proto" config_proto "www.velocidex.com/golang/velociraptor/config/proto" + "www.velocidex.com/golang/velociraptor/datastore" + "www.velocidex.com/golang/velociraptor/file_store" "www.velocidex.com/golang/velociraptor/grpc_client" "www.velocidex.com/golang/velociraptor/logging" "www.velocidex.com/golang/velociraptor/services" @@ -231,10 +233,6 @@ func (self *MasterFrontendManager) UpdateStats(ctx context.Context) { } } -func (self MasterFrontendManager) IsMaster() bool { - return true -} - // The master does not replicate anywhere. func (self MasterFrontendManager) GetMasterAPIClient(ctx context.Context) ( api_proto.APIClient, func() error, error) { @@ -264,14 +262,34 @@ func (self MasterFrontendManager) Start(ctx context.Context, wg *sync.WaitGroup, ApiServer: true, FrontendServer: true, GuiServer: true, + IndexServer: true, } } logger := logging.GetLogger(self.config_obj, &logging.FrontendComponent) logger.Info("Frontend: Server will be master.") + if config_obj.Datastore == nil { + return errors.New("Datastore must be specified") + } + + implementation := config_obj.Datastore.MasterImplementation + if implementation == "" { + implementation = config_obj.Datastore.Implementation + } + logger.Info("Filestore implementation %v.", implementation) + err := file_store.SetGlobalFilestore(implementation, config_obj) + if err != nil { + return err + } + + err = datastore.SetGlobalDatastore(implementation, config_obj) + if err != nil { + return err + } + // Push our metrics to the master node. - err := PushMetrics(ctx, wg, config_obj, "master") + err = PushMetrics(ctx, wg, config_obj, "master") if err != nil { return err } @@ -322,6 +340,22 @@ func (self *MinionFrontendManager) Start(ctx context.Context, wg *sync.WaitGroup logger := logging.GetLogger(self.config_obj, &logging.FrontendComponent) logger.Info("Frontend: Server will be a minion, with ID %v.", self.name) + implementation := config_obj.Datastore.MinionImplementation + if implementation == "" { + implementation = config_obj.Datastore.Implementation + } + + logger.Info("Filestore implementation %v.", implementation) + err := file_store.SetGlobalFilestore(implementation, config_obj) + if err != nil { + return err + } + + err = datastore.SetGlobalDatastore(implementation, config_obj) + if err != nil { + return err + } + // Push our metrics to the master node. return PushMetrics(ctx, wg, config_obj, self.name) } @@ -335,7 +369,13 @@ func StartFrontendService(ctx context.Context, wg *sync.WaitGroup, return errors.New("Frontend not configured") } - if config_obj.Frontend.IsMaster { + // Start the grpc clients + err := grpc_client.Init(ctx, config_obj) + if err != nil { + return err + } + + if services.IsMaster(config_obj) { manager := &MasterFrontendManager{ config_obj: config_obj, stats: make(map[string]*FrontendMetrics), diff --git a/services/hunt_dispatcher.go b/services/hunt_dispatcher.go index dce3fac10ce..aa576b7daac 100644 --- a/services/hunt_dispatcher.go +++ b/services/hunt_dispatcher.go @@ -35,6 +35,26 @@ var ( global_hunt_dispatcher IHuntDispatcher ) +// How was the hunt modified and what should be done about it? +type HuntModificationAction int + +const ( + // No modifications made - just ignore the changes. + HuntUnmodified HuntModificationAction = iota + + // Changes should be propagated to all other hunt dispatchers on + // all frontends. + HuntPropagateChanges + + // Just write to data store but do not propagate (good for very + // frequent changes). + HuntFlushToDatastore + + // Arrange for the change to be eventually written to the data + // store but not right away. Useful for very low priority events. + HuntFlushToDatastoreAsync +) + type IHuntDispatcher interface { // Applies the function on all the hunts. Functions may not // modify the hunt but will have read only access to the hunt @@ -49,8 +69,10 @@ type IHuntDispatcher interface { GetLastTimestamp() uint64 // Modify a hunt under lock. The hunt will be synchronized to - // all frontends. - ModifyHunt(hunt_id string, cb func(hunt *api_proto.Hunt) error) error + // all frontends. Return true to indicate the hunt was modified. + ModifyHunt(hunt_id string, + cb func(hunt *api_proto.Hunt) HuntModificationAction, + ) HuntModificationAction // Gets read only access to the hunt object. GetHunt(hunt_id string) (*api_proto.Hunt, bool) diff --git a/services/hunt_dispatcher/hunt_dispatcher.go b/services/hunt_dispatcher/hunt_dispatcher.go index 136aa71ce24..bf0a92d13df 100644 --- a/services/hunt_dispatcher/hunt_dispatcher.go +++ b/services/hunt_dispatcher/hunt_dispatcher.go @@ -1,6 +1,6 @@ /* - Velociraptor - Hunting Evil - Copyright (C) 2019 Velocidex Innovations. + Velociraptor - Digging Deeper + Copyright (C) 2021 Velocidex. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -23,17 +23,26 @@ package hunt_dispatcher // be in memory for quick access. The hunt dispatcher refreshes the // hunt list periodically from the data store to receive fresh data. -// In multi frontend deployments, each node has its own hunt -// dispatcher, initialized from the data store. On minion nodes, the -// hunt dispatcher is not allowed to write updates to the data store, -// only read them. The master's hunt dispatcher is responsible for -// maintaining the hunt state across all nodes. In order to update a -// hunt's property (e.g. TotalClientsScheduled etc), callers should -// call MutateHunt() to pass a mutation to the master. +// In multi frontend deployments, each node (master or minion) has its +// own hunt dispatcher, initialized from the data store. On minion +// nodes, the hunt dispatcher is not allowed to write updates to the +// data store, only read them. + +// The master's hunt dispatcher is responsible for maintaining the +// hunt state across all nodes. In order to update a hunt's property +// (e.g. TotalClientsScheduled etc), callers should call MutateHunt() +// on their local node to send a mutation to the master, which will +// actually update the hunt state. + +// As the hunt manager (singleton running on the master) updates the +// hunt record, it sends the new record to the +// Server.Internal.HuntUpdate queue, where all hunt dispatchers will +// receive it and update their internal state. The hunt dispatcher on +// the master will also write the new record to the data store. import ( "context" - "errors" + "fmt" "sync" "sync/atomic" "time" @@ -41,14 +50,18 @@ import ( "github.com/Velocidex/ordereddict" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" + "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" api_proto "www.velocidex.com/golang/velociraptor/api/proto" config_proto "www.velocidex.com/golang/velociraptor/config/proto" "www.velocidex.com/golang/velociraptor/constants" "www.velocidex.com/golang/velociraptor/datastore" + "www.velocidex.com/golang/velociraptor/json" "www.velocidex.com/golang/velociraptor/logging" "www.velocidex.com/golang/velociraptor/paths" "www.velocidex.com/golang/velociraptor/services" + "www.velocidex.com/golang/velociraptor/services/journal" + "www.velocidex.com/golang/velociraptor/utils" ) var ( @@ -58,6 +71,12 @@ var ( }) ) +type HuntRecord struct { + *api_proto.Hunt + + dirty bool +} + // The hunt dispatcher is a singlton which keeps hunt information in // memory under lock. We can modify hunt statistics, query for // applicable hunts etc. Hunts are flushed to disk periodically and @@ -65,7 +84,7 @@ var ( type HuntDispatcher struct { // This is the last timestamp of the latest hunt. At steady // state all clients will have run all hunts, therefore we can - // immediately serve their forman checks by simply comparing a + // immediately serve their foreman checks by simply comparing a // single number. // NOTE: This has to be aligned to 64 bits or 32 bit builds will break // https://github.com/golang/go/issues/13868 @@ -73,18 +92,75 @@ type HuntDispatcher struct { config_obj *config_proto.Config mu sync.Mutex - hunts map[string]*api_proto.Hunt - dirty bool + hunts map[string]*HuntRecord + + uuid int64 } func (self *HuntDispatcher) GetLastTimestamp() uint64 { return atomic.LoadUint64(&self.last_timestamp) } +func (self *HuntDispatcher) ProcessUpdate( + ctx context.Context, + config_obj *config_proto.Config, + row *ordereddict.Dict) error { + + // Ignore messages we sent ourselves. + from, pres := row.GetInt64("From") + if !pres || from == self.uuid { + return nil + } + + hunt_any, pres := row.Get("Hunt") + if !pres { + return nil + } + + serialized, err := json.Marshal(hunt_any) + if err != nil { + return err + } + + hunt_obj := &api_proto.Hunt{} + err = protojson.Unmarshal(serialized, hunt_obj) + if err != nil { + return err + } + + // The hunts start time could have been modified - we need to + // update ours then (and also the metrics). + if hunt_obj.StartTime > self.GetLastTimestamp() { + dispatcherCurrentTimestamp.Set(float64(hunt_obj.StartTime)) + atomic.StoreUint64(&self.last_timestamp, hunt_obj.StartTime) + } + + // Update the last time. + self.mu.Lock() + self.hunts[hunt_obj.HuntId] = &HuntRecord{Hunt: hunt_obj} + self.mu.Unlock() + + // On the master we also write it to storage. + if services.IsMaster(config_obj) { + hunt_path_manager := paths.NewHuntPathManager(hunt_obj.HuntId) + db, err := datastore.GetDB(config_obj) + if err != nil { + return err + } + + err = db.SetSubject(config_obj, hunt_path_manager.Path(), hunt_obj) + if err != nil { + return fmt.Errorf("Flushing hunt update %s to disk: %w", hunt_obj.HuntId, err) + } + } + + return nil +} + func (self *HuntDispatcher) getHunts() []*api_proto.Hunt { result := make([]*api_proto.Hunt, 0, len(self.hunts)) for _, hunt := range self.hunts { - result = append(result, hunt) + result = append(result, hunt.Hunt) } return result @@ -116,7 +192,7 @@ func (self *HuntDispatcher) GetHunt(hunt_id string) (*api_proto.Hunt, bool) { hunt_obj, pres := self.hunts[hunt_id] if pres { - return proto.Clone(hunt_obj).(*api_proto.Hunt), true + return proto.Clone(hunt_obj.Hunt).(*api_proto.Hunt), true } return nil, false @@ -130,116 +206,102 @@ func (self *HuntDispatcher) MutateHunt( return err } - return journal.PushRowsToArtifact(config_obj, - []*ordereddict.Dict{ordereddict.NewDict(). + journal.PushRowsToArtifactAsync(config_obj, + ordereddict.NewDict(). Set("hunt_id", mutation.HuntId). - Set("mutation", mutation)}, - "Server.Internal.HuntModification", "server", "") + Set("mutation", mutation), + "Server.Internal.HuntModification") + + return nil } -// Modify the hunt object under lock. +// Modify the hunt object under lock and also inform all other +// dispatchers about the new state. func (self *HuntDispatcher) ModifyHunt( - hunt_id string, cb func(hunt *api_proto.Hunt) error) error { - self.mu.Lock() - defer self.mu.Unlock() + hunt_id string, + cb func(hunt *api_proto.Hunt) services.HuntModificationAction) services.HuntModificationAction { - if !services.GetFrontendManager().IsMaster() { + logger := logging.GetLogger(self.config_obj, &logging.FrontendComponent) + if !services.IsMaster(self.config_obj) { // This is really a critical error. - logger := logging.GetLogger(self.config_obj, &logging.FrontendComponent) logger.Error("Unable to modify hunts on a minion node. Please use MutateHunt()") - return errors.New("Unable to modify hunts on a minion node. Please use MutateHunt()") + return services.HuntUnmodified } + self.mu.Lock() hunt_obj, pres := self.hunts[hunt_id] if !pres { - return errors.New("not found") + return services.HuntUnmodified } - err := cb(hunt_obj) - - // Hunt is only modified if the cb return no error - if err == nil { - // The hunts start time could have been modified - we - // need to update ours then. - if hunt_obj.StartTime > self.GetLastTimestamp() { - dispatcherCurrentTimestamp.Set(float64(hunt_obj.StartTime)) - atomic.StoreUint64(&self.last_timestamp, hunt_obj.StartTime) + hunt_obj.Hunt.Version = time.Now().UnixNano() + + // Call the callback to see if we need to change this hunt. + modification := cb(hunt_obj.Hunt) + switch modification { + case services.HuntUnmodified: + self.mu.Unlock() + + case services.HuntPropagateChanges: + // It is still modified so make sure to write it eventually. + hunt_obj.dirty = true + + hunt_obj_copy := proto.Clone(hunt_obj.Hunt).(*api_proto.Hunt) + self.mu.Unlock() + + // Relay the new update to all other hunt dispatchers. + journal, err := services.GetJournal() + if err == nil { + journal.PushRowsToArtifactAsync(self.config_obj, + ordereddict.NewDict(). + Set("HuntId", hunt_id). + Set("From", self.uuid). + Set("Hunt", hunt_obj_copy), + "Server.Internal.HuntUpdate") } - self.dirty = true - } - - return err -} -// Write the hunt stats to the data store. This is only called by the -// hunt manager and so should be concurrently safe. -func (self *HuntDispatcher) _flush_stats(config_obj *config_proto.Config) error { - // Already locked. - // Only do something if we are dirty. - if !self.dirty { - return nil - } + case services.HuntFlushToDatastore: + hunt_obj.dirty = true - // If we are a minion frontend we never flush data - it is up - // to the master to sync the hunts - if !services.GetFrontendManager().IsMaster() { - return nil - } - - self.dirty = false + hunt_obj_copy := proto.Clone(hunt_obj.Hunt).(*api_proto.Hunt) + self.mu.Unlock() - db, err := datastore.GetDB(config_obj) - if err != nil { - return err - } + hunt_path_manager := paths.NewHuntPathManager(hunt_id) + db, err := datastore.GetDB(self.config_obj) + if err != nil { + return services.HuntUnmodified + } - // Update the hunt object - for _, hunt_obj := range self.hunts { - hunt_path_manager := paths.NewHuntPathManager(hunt_obj.HuntId) - err = db.SetSubject(config_obj, hunt_path_manager.Path(), hunt_obj) + err = db.SetSubject(self.config_obj, + hunt_path_manager.Path(), hunt_obj_copy) if err != nil { - logger := logging.GetLogger(config_obj, &logging.FrontendComponent) - logger.Error("Flushing %s to disk: %v", hunt_obj.HuntId, err) - continue + logger := logging.GetLogger(self.config_obj, &logging.FrontendComponent) + logger.Error("Flushing %s to disk: %v", hunt_obj_copy, err) + return services.HuntUnmodified } + + case services.HuntFlushToDatastoreAsync: + hunt_obj.dirty = true + self.mu.Unlock() + + default: + self.mu.Unlock() } - return nil + return modification } func (self *HuntDispatcher) Close(config_obj *config_proto.Config) { self.mu.Lock() defer self.mu.Unlock() - atomic.SwapUint64(&self.last_timestamp, 0) - _ = self._flush_stats(config_obj) + atomic.SwapUint64(&self.last_timestamp, 0) } -// Check for new hunts from the db. This could take a while and be -// under lock. However, while we do this we do not block the foreman -// checks. +// Check for new hunts from the datastore. The master frontend will +// also flush updated hunt records to the datastore. func (self *HuntDispatcher) Refresh(config_obj *config_proto.Config) error { - // The foreman will now skip all hunts without blocking. This - // is OK because we will get those clients on the next foreman - // update - the important thing is that foreman checks are not - // blocked by this. - last_timestamp := atomic.SwapUint64(&self.last_timestamp, 0) - - defer func() { - dispatcherCurrentTimestamp.Set(float64(last_timestamp)) - atomic.StoreUint64(&self.last_timestamp, last_timestamp) - }() - - self.mu.Lock() - defer self.mu.Unlock() - - // First flush all the stats to the data store. - err := self._flush_stats(config_obj) - if err != nil { - return err - } - - // Now read all the data again. + // Now read all the data again from the data store. db, err := datastore.GetDB(config_obj) if err != nil { return err @@ -251,41 +313,57 @@ func (self *HuntDispatcher) Refresh(config_obj *config_proto.Config) error { return err } - for _, hunt_urn := range hunts { - if hunt_urn.IsDir() { - continue - } - - hunt_id := hunt_urn.Base() + requests := make([]*datastore.MultiGetSubjectRequest, 0, len(hunts)) + for _, hunt_path := range hunts { + hunt_id := hunt_path.Base() if !constants.HuntIdRegex.MatchString(hunt_id) { continue } - hunt_obj := &api_proto.Hunt{} - hunt_path_manager := paths.NewHuntPathManager(hunt_id) - err = db.GetSubject( - config_obj, hunt_path_manager.Path(), hunt_obj) - if err != nil || hunt_obj.HuntId == "" { + requests = append(requests, &datastore.MultiGetSubjectRequest{ + Path: paths.NewHuntPathManager(hunt_id).Path(), + Message: &api_proto.Hunt{}, + Data: hunt_id, + }) + } + + err = datastore.MultiGetSubject(config_obj, requests) + if err != nil { + return err + } + + // Now merge the database entries with the current in memory set. + self.mu.Lock() + defer self.mu.Unlock() + + for _, request := range requests { + hunt_id := request.Data.(string) + hunt_obj, ok := request.Message.(*api_proto.Hunt) + if !ok { continue } - if hunt_obj.Stats == nil { - hunt_obj.Stats = &api_proto.HuntStats{} + if request.Err != nil || hunt_obj.HuntId != hunt_id { + continue } - // Should not really happen but if the file is - // corrupted we skip it. - if hunt_obj.HuntId != hunt_id { + old_hunt_obj, pres := self.hunts[hunt_id] + if pres && old_hunt_obj.Version >= hunt_obj.Version { + // The in memory copy is newer than the stored version, + // Master node will synchronize + if services.IsMaster(config_obj) { + db.SetSubject(config_obj, request.Path, old_hunt_obj) + } continue } - // This hunt is newer than the last_timestamp, we need - // to update it. - if hunt_obj.StartTime > last_timestamp { - last_timestamp = hunt_obj.StartTime + // Maintain the last timestamp as the latest hunt start time. + if hunt_obj.StartTime > self.last_timestamp { + self.last_timestamp = hunt_obj.StartTime + dispatcherCurrentTimestamp.Set(float64(self.last_timestamp)) } - self.hunts[hunt_id] = hunt_obj + self.hunts[hunt_id] = &HuntRecord{Hunt: hunt_obj} } return nil } @@ -295,11 +373,12 @@ func StartHuntDispatcher( wg *sync.WaitGroup, config_obj *config_proto.Config) error { - result := &HuntDispatcher{ + service := &HuntDispatcher{ config_obj: config_obj, - hunts: make(map[string]*api_proto.Hunt), + hunts: make(map[string]*HuntRecord), + uuid: utils.GetGUID(), } - services.RegisterHuntDispatcher(result) + services.RegisterHuntDispatcher(service) // flush the hunts every 10 seconds. wg.Add(1) @@ -317,32 +396,16 @@ func StartHuntDispatcher( logger.Info("Starting Hunt Dispatcher Service.") for { - // Also listen for notifications so we can refresh as soon as - // the hunt is started. - notifier := services.GetNotifier() - // This only happens at shutdown so wait until - // we shutdown as well. - if notifier == nil { - <-ctx.Done() - return - } - notification, cancel := notifier.ListenForNotification( - "HuntDispatcher") - defer cancel() - select { case <-ctx.Done(): - result.Close(config_obj) + service.Close(config_obj) return - case <-notification: - cancel() - _ = result.Refresh(config_obj) - case <-time.After(10 * time.Second): - err := result.Refresh(config_obj) + // Re-read the hunts from the data store. + err := service.Refresh(config_obj) if err != nil { - logger.Error("Unable to flush hunts: %v", err) + logger.Error("Unable to sync hunts: %v", err) } } } @@ -350,11 +413,16 @@ func StartHuntDispatcher( // Try to refresh the hunts table the first time. If we cant // we will just keep trying anyway later. - err := result.Refresh(config_obj) + err := service.Refresh(config_obj) if err != nil { logger := logging.GetLogger(config_obj, &logging.FrontendComponent) logger.Error("Unable to Refresh hunt dispatcher: %v", err) } - return nil + return journal.WatchQueueWithCB(ctx, config_obj, wg, + "Server.Internal.HuntUpdate", service.ProcessUpdate) +} + +func init() { + json.RegisterCustomEncoder(&api_proto.Hunt{}, json.MarshalHuntProtobuf) } diff --git a/services/hunt_manager/hunt_manager.go b/services/hunt_manager/hunt_manager.go index 71230ced8bb..4a39cc3024d 100644 --- a/services/hunt_manager/hunt_manager.go +++ b/services/hunt_manager/hunt_manager.go @@ -158,20 +158,15 @@ func (self *HuntManager) ProcessMutation( return err } - // We notify all node's hunt dispatcher only when the hunt - // status is changed (started or stopped). - notifier := services.GetNotifier() - if notifier == nil { - return errors.New("Notifier not ready") - } - dispatcher := services.GetHuntDispatcher() if dispatcher == nil { return errors.New("Hunt Dispatcher not ready") } - return dispatcher.ModifyHunt(mutation.HuntId, - func(hunt_obj *api_proto.Hunt) error { + dispatcher.ModifyHunt(mutation.HuntId, + func(hunt_obj *api_proto.Hunt) services.HuntModificationAction { + modification := services.HuntUnmodified + if hunt_obj.Stats == nil { hunt_obj.Stats = &api_proto.HuntStats{} } @@ -180,44 +175,57 @@ func (self *HuntManager) ProcessMutation( mutation.Stats = &api_proto.HuntStats{} } - hunt_obj.Stats.TotalClientsScheduled += - mutation.Stats.TotalClientsScheduled + // The following are very frequent modifications that + // other frontends dont care about. + if mutation.Stats.TotalClientsScheduled > 0 { + hunt_obj.Stats.TotalClientsScheduled += + mutation.Stats.TotalClientsScheduled + + modification = services.HuntFlushToDatastoreAsync + } - hunt_obj.Stats.TotalClientsWithResults += - mutation.Stats.TotalClientsWithResults + if mutation.Stats.TotalClientsWithResults > 0 { + hunt_obj.Stats.TotalClientsWithResults += + mutation.Stats.TotalClientsWithResults + + modification = services.HuntFlushToDatastoreAsync + } // Have we stopped the hunt? if mutation.State == api_proto.Hunt_STOPPED || mutation.State == api_proto.Hunt_PAUSED { hunt_obj.Stats.Stopped = true hunt_obj.State = api_proto.Hunt_STOPPED - _ = notifier.NotifyListener( - config_obj, "HuntDispatcher") - } - if mutation.State == api_proto.Hunt_RUNNING { + modification = services.HuntPropagateChanges + + } else if mutation.State == api_proto.Hunt_RUNNING { hunt_obj.Stats.Stopped = false hunt_obj.State = api_proto.Hunt_RUNNING - _ = notifier.NotifyListener( - config_obj, "HuntDispatcher") - } - if mutation.State == api_proto.Hunt_ARCHIVED { + modification = services.HuntPropagateChanges + + } else if mutation.State == api_proto.Hunt_ARCHIVED { hunt_obj.State = api_proto.Hunt_ARCHIVED - _ = notifier.NotifyListener( - config_obj, "HuntDispatcher") + + modification = services.HuntPropagateChanges } if mutation.Description != "" { hunt_obj.HuntDescription = mutation.Description + + modification = services.HuntPropagateChanges } if mutation.StartTime > 0 { hunt_obj.StartTime = mutation.StartTime + + modification = services.HuntPropagateChanges } - return nil + return modification }) + return nil } // Check if the mutation requests a flow to be added to the hunt. @@ -398,11 +406,12 @@ func (self *HuntManager) participateInAllHunts(ctx context.Context, return nil } - return journal.PushRowsToArtifact(config_obj, - []*ordereddict.Dict{ordereddict.NewDict(). + journal.PushRowsToArtifactAsync(config_obj, + ordereddict.NewDict(). Set("HuntId", hunt.HuntId). - Set("ClientId", client_id)}, - "System.Hunt.Participation", "server", "") + Set("ClientId", client_id), "System.Hunt.Participation") + + return nil }) } @@ -422,7 +431,7 @@ func (self *HuntManager) ProcessParticipation( // Get some info about the client client_info_manager := services.GetClientInfoManager() if client_info_manager == nil { - return nil + return errors.New("Client_info_manager not set") } client_info, err := client_info_manager.Get(participation_row.ClientId) @@ -439,7 +448,8 @@ func (self *HuntManager) ProcessParticipation( err = checkHuntRanOnClient(config_obj, participation_row.ClientId, participation_row.HuntId) if err != nil { - return nil + return fmt.Errorf("hunt_manager: %v already ran on client %v", + participation_row.HuntId, participation_row.ClientId) } // Get hunt information about this hunt. @@ -462,17 +472,21 @@ func (self *HuntManager) ProcessParticipation( // Ignore stopped hunts. if hunt_obj.Stats.Stopped || hunt_obj.State != api_proto.Hunt_RUNNING { - return errors.New("hunt is stopped") + // Hunt is stopped. + return fmt.Errorf("Hunt %v is stopped", participation_row.HuntId) } else if !huntMatchesOS(hunt_obj, client_info) { - return errors.New("Hunt does not match OS condition") + // Hunt does not match OS condition + return fmt.Errorf("Hunt %v: %v does not match OS condition", + participation_row.HuntId, participation_row.ClientId) // Ignore hunts with label conditions which // exclude this client. } else if !huntHasLabel(config_obj, hunt_obj, participation_row.ClientId) { - return errors.New("hunt label does not match") + return fmt.Errorf("Hunt %v: hunt label does not match with %v", + participation_row.HuntId, participation_row.ClientId) } // Hunt limit exceeded or it expired - we stop it. @@ -577,13 +591,14 @@ func huntMatchesOS(hunt_obj *api_proto.Hunt, client_info *services.ClientInfo) b return true } + os := client_info.OS() switch os_condition.Os { case api_proto.HuntOsCondition_WINDOWS: - return client_info.OS == services.Windows + return os == services.Windows case api_proto.HuntOsCondition_LINUX: - return client_info.OS == services.Linux + return os == services.Linux case api_proto.HuntOsCondition_OSX: - return client_info.OS == services.MacOS + return os == services.MacOS } return true @@ -704,7 +719,7 @@ func scheduleHuntOnClient( // Notify the client that the hunt applies to it. notifier := services.GetNotifier() if notifier != nil { - _ = notifier.NotifyListener(config_obj, client_id) + notifier.NotifyListenerAsync(config_obj, client_id) } return nil diff --git a/services/hunt_manager/hunt_manager_test.go b/services/hunt_manager/hunt_manager_test.go index 38def173954..894aa8e5b8a 100644 --- a/services/hunt_manager/hunt_manager_test.go +++ b/services/hunt_manager/hunt_manager_test.go @@ -43,8 +43,6 @@ func (self *HuntTestSuite) SetupTest() { self.hunt_id += "A" self.expected.Creator = self.hunt_id - self.ConfigObj.Frontend.IsMaster = true - require.NoError(self.T(), self.Sm.Start(frontend.StartFrontendService)) require.NoError(self.T(), self.Sm.Start(hunt_dispatcher.StartHuntDispatcher)) require.NoError(self.T(), self.Sm.Start(StartHuntManager)) @@ -537,7 +535,9 @@ func (self *HuntTestSuite) TestHuntClientOSConditionInterrogation() { client_path_manager := paths.NewClientPathManager(self.client_id) err = db.SetSubject(self.ConfigObj, - client_path_manager.Path(), &actions_proto.ClientInfo{}) + client_path_manager.Path(), &actions_proto.ClientInfo{ + ClientId: self.client_id, + }) assert.NoError(t, err) launcher.SetFlowIdForTests("F.1234") diff --git a/services/interrogation/interrogation_test.go b/services/interrogation/interrogation_test.go index c228f56de74..b76a3567bdb 100644 --- a/services/interrogation/interrogation_test.go +++ b/services/interrogation/interrogation_test.go @@ -1,7 +1,6 @@ package interrogation import ( - "context" "testing" "time" @@ -10,64 +9,38 @@ import ( "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" actions_proto "www.velocidex.com/golang/velociraptor/actions/proto" - "www.velocidex.com/golang/velociraptor/config" - config_proto "www.velocidex.com/golang/velociraptor/config/proto" "www.velocidex.com/golang/velociraptor/datastore" "www.velocidex.com/golang/velociraptor/file_store/api" "www.velocidex.com/golang/velociraptor/file_store/test_utils" flows_proto "www.velocidex.com/golang/velociraptor/flows/proto" "www.velocidex.com/golang/velociraptor/paths" "www.velocidex.com/golang/velociraptor/services" - "www.velocidex.com/golang/velociraptor/services/client_info" - "www.velocidex.com/golang/velociraptor/services/inventory" - "www.velocidex.com/golang/velociraptor/services/journal" - "www.velocidex.com/golang/velociraptor/services/labels" - "www.velocidex.com/golang/velociraptor/services/launcher" - "www.velocidex.com/golang/velociraptor/services/notifications" - "www.velocidex.com/golang/velociraptor/services/repository" "www.velocidex.com/golang/velociraptor/vtesting" _ "www.velocidex.com/golang/velociraptor/result_sets/timed" ) type ServicesTestSuite struct { - suite.Suite - config_obj *config_proto.Config - client_id string - flow_id string - sm *services.Service + test_utils.TestSuite + client_id string + flow_id string } func (self *ServicesTestSuite) SetupTest() { - var err error - self.config_obj, err = new(config.Loader).WithFileLoader( - "../../http_comms/test_data/server.config.yaml"). - WithRequiredFrontend().WithWriteback(). - LoadAndValidate() - require.NoError(self.T(), err) - - // Start essential services. - ctx, _ := context.WithTimeout(context.Background(), time.Second*60) - self.sm = services.NewServiceManager(ctx, self.config_obj) - require.NoError(self.T(), self.sm.Start(journal.StartJournalService)) - require.NoError(self.T(), self.sm.Start(client_info.StartClientInfoService)) - require.NoError(self.T(), self.sm.Start(notifications.StartNotificationService)) - require.NoError(self.T(), self.sm.Start(inventory.StartInventoryService)) - require.NoError(self.T(), self.sm.Start(repository.StartRepositoryManager)) - require.NoError(self.T(), self.sm.Start(labels.StartLabelService)) - require.NoError(self.T(), self.sm.Start(launcher.StartLauncherService)) - require.NoError(self.T(), self.sm.Start(StartInterrogationService)) + self.TestSuite.SetupTest() + + require.NoError(self.T(), self.Sm.Start(StartInterrogationService)) + + self.LoadArtifacts([]string{` +name: Server.Internal.Enrollment +type: INTERNAL +`, + }) self.client_id = "C.12312" self.flow_id = "F.1232" } -func (self *ServicesTestSuite) TearDownTest() { - self.sm.Close() - test_utils.GetMemoryFileStore(self.T(), self.config_obj).Clear() - test_utils.GetMemoryDataStore(self.T(), self.config_obj).Clear() -} - func (self *ServicesTestSuite) EmulateCollection( artifact string, rows []*ordereddict.Dict) string { @@ -77,11 +50,11 @@ func (self *ServicesTestSuite) EmulateCollection( journal, err := services.GetJournal() assert.NoError(self.T(), err) - journal.PushRowsToArtifact(self.config_obj, + journal.PushRowsToArtifact(self.ConfigObj, rows, artifact, self.client_id, self.flow_id) // Emulate a flow completion message coming from the flow processor. - journal.PushRowsToArtifact(self.config_obj, + journal.PushRowsToArtifact(self.ConfigObj, []*ordereddict.Dict{ordereddict.NewDict(). Set("ClientId", self.client_id). Set("FlowId", self.flow_id). @@ -105,14 +78,14 @@ func (self *ServicesTestSuite) TestInterrogationService() { }) // Wait here until the client is fully interrogated - db, err := datastore.GetDB(self.config_obj) + db, err := datastore.GetDB(self.ConfigObj) assert.NoError(self.T(), err) client_path_manager := paths.NewClientPathManager(self.client_id) client_info := &actions_proto.ClientInfo{} vtesting.WaitUntil(2*time.Second, self.T(), func() bool { - db.GetSubject(self.config_obj, client_path_manager.Path(), client_info) + db.GetSubject(self.ConfigObj, client_path_manager.Path(), client_info) return client_info.Hostname == hostname }) @@ -124,19 +97,19 @@ func (self *ServicesTestSuite) TestInterrogationService() { // Check the label is set on the client. labeler := services.GetLabeler() - assert.True(self.T(), labeler.IsLabelSet(self.config_obj, self.client_id, "Foo")) + assert.True(self.T(), labeler.IsLabelSet(self.ConfigObj, self.client_id, "Foo")) assert.NoError(self.T(), err) } func (self *ServicesTestSuite) TestEnrollService() { enroll_message := ordereddict.NewDict().Set("ClientId", self.client_id) - db, err := datastore.GetDB(self.config_obj) + db, err := datastore.GetDB(self.ConfigObj) assert.NoError(self.T(), err) client_path_manager := paths.NewClientPathManager(self.client_id) client_info := &actions_proto.ClientInfo{} - db.GetSubject(self.config_obj, client_path_manager.Path(), client_info) + db.GetSubject(self.ConfigObj, client_path_manager.Path(), client_info) assert.Equal(self.T(), client_info.ClientId, "") @@ -150,7 +123,7 @@ func (self *ServicesTestSuite) TestEnrollService() { journal, err := services.GetJournal() assert.NoError(self.T(), err) - err = journal.PushRowsToArtifact(self.config_obj, + err = journal.PushRowsToArtifact(self.ConfigObj, []*ordereddict.Dict{ enroll_message, enroll_message, enroll_message, enroll_message, }, @@ -160,7 +133,7 @@ func (self *ServicesTestSuite) TestEnrollService() { // Wait here until the client is enrolled vtesting.WaitUntil(2*time.Second, self.T(), func() bool { - db.GetSubject(self.config_obj, client_path_manager.Path(), client_info) + db.GetSubject(self.ConfigObj, client_path_manager.Path(), client_info) return client_info.ClientId == self.client_id }) @@ -168,13 +141,13 @@ func (self *ServicesTestSuite) TestEnrollService() { flow_path_manager := paths.NewFlowPathManager(self.client_id, client_info.LastInterrogateFlowId) collection_context := &flows_proto.ArtifactCollectorContext{} - err = db.GetSubject(self.config_obj, flow_path_manager.Path(), collection_context) + err = db.GetSubject(self.ConfigObj, flow_path_manager.Path(), collection_context) assert.Equal(self.T(), collection_context.Request.Artifacts, []string{"Generic.Client.Info"}) // Make sure only one flow is generated all_children, err := db.ListChildren( - self.config_obj, flow_path_manager.ContainerPath()) + self.ConfigObj, flow_path_manager.ContainerPath()) assert.NoError(self.T(), err) children := []api.DSPathSpec{} diff --git a/services/journal.go b/services/journal.go index e5c1af57dd6..afb5a1a91af 100644 --- a/services/journal.go +++ b/services/journal.go @@ -61,7 +61,15 @@ type JournalService interface { AppendToResultSet(config_obj *config_proto.Config, path api.FSPathSpec, rows []*ordereddict.Dict) error + Broadcast(config_obj *config_proto.Config, + rows []*ordereddict.Dict, name, client_id, flows_id string) error + // Push the rows to the event artifact queue PushRowsToArtifact(config_obj *config_proto.Config, rows []*ordereddict.Dict, name, client_id, flows_id string) error + + // Push the rows to the event artifact queue with a potential + // unspecified delay. + PushRowsToArtifactAsync(config_obj *config_proto.Config, + row *ordereddict.Dict, name string) } diff --git a/services/journal/buffer.go b/services/journal/buffer.go index 2219c3cae9d..6b3bcd14d83 100644 --- a/services/journal/buffer.go +++ b/services/journal/buffer.go @@ -98,13 +98,13 @@ func (self *BufferFile) Enqueue(item *api_proto.PushEventRequest) error { _, err = self.fd.WriteAt(self.write_buf, int64(self.Header.WritePointer)) if err != nil { // File is corrupt now, reset it. - self.Reset() + self._Truncate() return err } n, err := self.fd.WriteAt(serialized, int64(self.Header.WritePointer+8)) if err != nil { - self.Reset() + self._Truncate() return err } @@ -117,7 +117,7 @@ func (self *BufferFile) Enqueue(item *api_proto.PushEventRequest) error { } _, err = self.fd.WriteAt(serialized, 0) if err != nil { - self.Reset() + self._Truncate() return err } diff --git a/services/journal/journal.go b/services/journal/journal.go index b375da67a63..bd668170a8d 100644 --- a/services/journal/journal.go +++ b/services/journal/journal.go @@ -25,6 +25,10 @@ import ( "www.velocidex.com/golang/velociraptor/utils" ) +var ( + notInitializedError = errors.New("Not initialized") +) + type JournalService struct { config_obj *config_proto.Config qm api.QueueManager @@ -92,6 +96,31 @@ func (self *JournalService) AppendToResultSet( return nil } +func (self *JournalService) PushRowsToArtifactAsync( + config_obj *config_proto.Config, row *ordereddict.Dict, + artifact string) { + + go self.PushRowsToArtifact(config_obj, []*ordereddict.Dict{row}, + artifact, "server", "") +} + +func (self *JournalService) Broadcast( + config_obj *config_proto.Config, rows []*ordereddict.Dict, + artifact, client_id, flows_id string) error { + if self == nil || self.qm == nil { + return notInitializedError + } + + path_manager, err := artifacts.NewArtifactPathManager( + config_obj, client_id, flows_id, artifact) + if err != nil { + return err + } + + self.qm.Broadcast(path_manager, rows) + return nil +} + func (self *JournalService) PushRowsToArtifact( config_obj *config_proto.Config, rows []*ordereddict.Dict, artifact, client_id, flows_id string) error { @@ -123,6 +152,7 @@ func (self *JournalService) PushRowsToArtifact( func (self *JournalService) Start(config_obj *config_proto.Config) error { logger := logging.GetLogger(config_obj, &logging.FrontendComponent) logger.Info("Starting Journal service.") + return nil } @@ -131,18 +161,9 @@ func StartJournalService( // Are we running on a minion frontend? If so we try to start // our replication service. - fe_manager := services.GetFrontendManager() - if fe_manager != nil && !fe_manager.IsMaster() { - service := &ReplicationService{ - config_obj: config_obj, - locks: make(map[string]*sync.Mutex), - } - - err := service.Start(ctx, wg) - if err == nil { - services.RegisterJournal(service) - return nil - } + if !services.IsMaster(config_obj) { + _, err := NewReplicationService(ctx, wg, config_obj) + return err } // It is valid to have a journal service with no configured datastore: diff --git a/services/journal/journal_test.go b/services/journal/journal_test.go new file mode 100644 index 00000000000..4fb6511b87f --- /dev/null +++ b/services/journal/journal_test.go @@ -0,0 +1,115 @@ +package journal_test + +import ( + "fmt" + "io/ioutil" + "os" + "testing" + + "github.com/Velocidex/ordereddict" + "github.com/alecthomas/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "www.velocidex.com/golang/velociraptor/config" + "www.velocidex.com/golang/velociraptor/file_store" + "www.velocidex.com/golang/velociraptor/file_store/api" + "www.velocidex.com/golang/velociraptor/file_store/test_utils" + "www.velocidex.com/golang/velociraptor/services" + "www.velocidex.com/golang/velociraptor/utils" + "www.velocidex.com/golang/velociraptor/vtesting" + + _ "www.velocidex.com/golang/velociraptor/result_sets/timed" +) + +type JournalTestSuite struct { + test_utils.TestSuite +} + +func (self *JournalTestSuite) SetupTest() { + var err error + os.Setenv("VELOCIRAPTOR_CONFIG", test_utils.SERVER_CONFIG) + self.ConfigObj, err = new(config.Loader). + WithEnvLiteralLoader("VELOCIRAPTOR_CONFIG").WithRequiredFrontend(). + WithVerbose(true).LoadAndValidate() + require.NoError(self.T(), err) + + dir, err := ioutil.TempDir("", "file_store_test") + assert.NoError(self.T(), err) + + self.ConfigObj.Datastore.Implementation = "MemcacheFileDataStore" + //self.ConfigObj.Datastore.Implementation = "FileBaseDataStore" + self.ConfigObj.Datastore.FilestoreDirectory = dir + self.ConfigObj.Datastore.Location = dir + + self.TestSuite.SetupTest() + + self.LoadArtifacts([]string{` +name: System.Flow.Completion +type: CLIENT_EVENT +`, ` +name: System.Hunt.Participation +type: SERVER_EVENT +`}) +} + +func (self *JournalTestSuite) TearDownTest() { + // clean up + fmt.Printf("Cleaning up %v\n", self.ConfigObj.Datastore.FilestoreDirectory) + os.RemoveAll(self.ConfigObj.Datastore.FilestoreDirectory) +} + +func (self *JournalTestSuite) TestJournalWriting() { + journal, err := services.GetJournal() + assert.NoError(self.T(), err) + + clock := &utils.MockClock{} + start := clock.Now() + + // Simulate a slow filesystem (70 ms per filesystem access). + defer api.InstallClockForTests(clock, 70)() + + // Get metrics snapshot + snapshot := vtesting.GetMetrics(self.T(), ".") + + // Write 10 rows in series + for i := 0; i < 10; i++ { + err = journal.PushRowsToArtifact(self.ConfigObj, + []*ordereddict.Dict{ordereddict.NewDict(). + Set("Foo", "Bar"). + Set("i", i), + }, + "System.Flow.Completion", "C.1234", "") + assert.NoError(self.T(), err) + } + + // Force the filestore to flush the data + file_store_factory := file_store.GetFileStore(self.ConfigObj) + flusher, ok := file_store_factory.(api.Flusher) + if ok { + flusher.Flush() + } + + // See the filestore metrics + metrics := vtesting.GetMetricsDifference(self.T(), ".", snapshot) + + // Total number of writes on the memcache layer + memcache_total, _ := metrics.GetInt64( + "filestore_latency__write_MemcacheFileWriter_Generic_inf") + + // Total number of writes on the directory layer + directory_total, _ := metrics.GetInt64( + "filestore_latency__write_DirectoryFileWriter_Generic_inf") + + // Memcache should be combining many of the writes into larger + // writes. + assert.True(self.T(), directory_total*5 < memcache_total) + + // Get the total time. It should be much less than 10 times 70ms + // (i.e. rows are not written serially). + total_time := api.Clock.Now().Sub(start).Seconds() + assert.True(self.T(), 0.07*10 > total_time) +} + +func TestJournalTestSuite(t *testing.T) { + suite.Run(t, &JournalTestSuite{}) +} diff --git a/services/journal/replication.go b/services/journal/replication.go index 362a5a888c0..01dc407ed63 100644 --- a/services/journal/replication.go +++ b/services/journal/replication.go @@ -5,6 +5,7 @@ package journal import ( "context" + "errors" "io/ioutil" "os" "sync" @@ -57,6 +58,9 @@ type ReplicationService struct { mu sync.Mutex locks map[string]*sync.Mutex retryDuration time.Duration + + // Store rows for async push + batch map[string][]*ordereddict.Dict } func (self *ReplicationService) RetryDuration() time.Duration { @@ -107,12 +111,52 @@ func (self *ReplicationService) pumpEventFromBufferFile() { } } +func (self *ReplicationService) startAsyncLoop( + ctx context.Context, wg *sync.WaitGroup, config_obj *config_proto.Config) { + + wg.Add(1) + go func() { + defer wg.Done() + + for { + select { + case <-ctx.Done(): + return + + case <-time.After(time.Second): + // Take a copy to work on without a lock. + todo := make(map[string][]*ordereddict.Dict) + self.mu.Lock() + + for k, v := range self.batch { + if len(v) > 0 { + todo[k] = v + } + delete(self.batch, k) + } + self.mu.Unlock() + + for k, v := range todo { + // Ignore errors since there is no way to report + // to the caller. + _ = self.PushRowsToArtifact(config_obj, v, k, "server", "") + } + } + } + }() +} + func (self *ReplicationService) Start( - ctx context.Context, wg *sync.WaitGroup) (err error) { + ctx context.Context, + config_obj *config_proto.Config, wg *sync.WaitGroup) (err error) { // If we are the master node we do not replicate anywhere. - api_client, closer, err := services.GetFrontendManager(). - GetMasterAPIClient(ctx) + frontend_manager := services.GetFrontendManager() + if frontend_manager == nil { + return errors.New("Frontend not configured") + } + + api_client, closer, err := frontend_manager.GetMasterAPIClient(ctx) if err != nil { return err } @@ -137,34 +181,40 @@ func (self *ReplicationService) Start( go self.pumpEventFromBufferFile() - wg.Add(1) - go func() { - defer wg.Done() - defer self.Close() + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + defer wg.Done() + defer self.Close() - for { - select { - case <-ctx.Done(): - return - - // Read events from the channel and - // try to send them - case request, ok := <-self.sender: - if !ok { + for { + select { + case <-ctx.Done(): return - } - _, err = self.api_client.PushEvents(ctx, request) - if err != nil { - replicationTotalSendErrors.Inc() - - // Attempt to push the events - // to the buffer file instead - // for later delivery. - _ = self.Buffer.Enqueue(request) + + // Read events from the channel and + // try to send them + case request, ok := <-self.sender: + if !ok { + return + } + _, err := self.api_client.PushEvents(ctx, request) + if err != nil { + replicationTotalSendErrors.Inc() + + // Attempt to push the events + // to the buffer file instead + // for later delivery. + _ = self.Buffer.Enqueue(request) + } } } - } - }() + }() + } + + // Startup the async writer. This is used to queue up multiple + // small events to write in larger chunks for gRPC efficiency. + self.startAsyncLoop(ctx, wg, config_obj) logger := logging.GetLogger(self.config_obj, &logging.FrontendComponent) logger.Debug("Starting Replication service to master frontend at %v", @@ -209,6 +259,28 @@ func (self *ReplicationService) AppendToResultSet( return nil } +func (self *ReplicationService) Broadcast( + config_obj *config_proto.Config, rows []*ordereddict.Dict, + artifact, client_id, flows_id string) error { + + return notInitializedError +} + +func (self *ReplicationService) PushRowsToArtifactAsync( + config_obj *config_proto.Config, row *ordereddict.Dict, + artifact string) { + self.mu.Lock() + defer self.mu.Unlock() + + queue, pres := self.batch[artifact] + if !pres { + queue = make([]*ordereddict.Dict, 0) + } + + queue = append(queue, row) + self.batch[artifact] = queue +} + func (self *ReplicationService) PushRowsToArtifact( config_obj *config_proto.Config, rows []*ordereddict.Dict, artifact, client_id, flow_id string) error { @@ -326,3 +398,22 @@ func (self *ReplicationService) Close() { self.Buffer.Close() os.Remove(self.tmpfile.Name()) // clean up file buffer } + +func NewReplicationService( + ctx context.Context, + wg *sync.WaitGroup, + config_obj *config_proto.Config) ( + *ReplicationService, error) { + + service := &ReplicationService{ + config_obj: config_obj, + locks: make(map[string]*sync.Mutex), + batch: make(map[string][]*ordereddict.Dict), + } + + err := service.Start(ctx, config_obj, wg) + if err == nil { + services.RegisterJournal(service) + } + return service, err +} diff --git a/services/journal/replication_test.go b/services/journal/replication_test.go index 8fcf22e4902..9cabd1790c3 100644 --- a/services/journal/replication_test.go +++ b/services/journal/replication_test.go @@ -20,6 +20,7 @@ import ( config_proto "www.velocidex.com/golang/velociraptor/config/proto" "www.velocidex.com/golang/velociraptor/file_store/test_utils" "www.velocidex.com/golang/velociraptor/services" + "www.velocidex.com/golang/velociraptor/services/frontend" "www.velocidex.com/golang/velociraptor/services/inventory" "www.velocidex.com/golang/velociraptor/services/journal" "www.velocidex.com/golang/velociraptor/services/launcher" @@ -33,10 +34,6 @@ type MockFrontendService struct { mock *mock_proto.MockAPIClient } -func (self MockFrontendService) IsMaster() bool { - return false -} - // The minion replicates to the master node. func (self MockFrontendService) GetMasterAPIClient(ctx context.Context) ( api_proto.APIClient, func() error, error) { @@ -52,22 +49,22 @@ type ReplicationTestSuite struct { } func (self *ReplicationTestSuite) startServices() { + t := self.T() + ctx, _ := context.WithTimeout(context.Background(), time.Second*60) self.sm = services.NewServiceManager(ctx, self.config_obj) - t := self.T() - assert.NoError(t, self.sm.Start(journal.StartJournalService)) + replicator, err := journal.NewReplicationService( + self.sm.Ctx, self.sm.Wg, self.config_obj) + assert.NoError(t, err) + + replicator.SetRetryDuration(100 * time.Millisecond) + + assert.NoError(t, self.sm.Start(frontend.StartFrontendService)) assert.NoError(t, self.sm.Start(notifications.StartNotificationService)) assert.NoError(t, self.sm.Start(inventory.StartInventoryService)) assert.NoError(t, self.sm.Start(launcher.StartLauncherService)) assert.NoError(t, self.sm.Start(repository.StartRepositoryManagerForTest)) - - // Set retry to be faster. - journal_service, err := services.GetJournal() - assert.NoError(self.T(), err) - - replicator := journal_service.(*journal.ReplicationService) - replicator.SetRetryDuration(100 * time.Millisecond) } func (self *ReplicationTestSuite) SetupTest() { @@ -78,6 +75,8 @@ func (self *ReplicationTestSuite) SetupTest() { LoadAndValidate() require.NoError(self.T(), err) + self.config_obj.Frontend.IsMinion = true + self.ctrl = gomock.NewController(self.T()) self.mock = mock_proto.NewMockAPIClient(self.ctrl) diff --git a/services/journal/utils.go b/services/journal/utils.go index 205dd043786..f027ee03340 100644 --- a/services/journal/utils.go +++ b/services/journal/utils.go @@ -82,9 +82,11 @@ func WatchQueueWithCB(ctx context.Context, } err := processor(ctx, config_obj, row) if err != nil { + // Processor errors are not generally serious so + // we just log them into the debug log. logger := logging.GetLogger(config_obj, &logging.FrontendComponent) - logger.Info("Error: %v.", err) + logger.Debug("Debug: %v.", err) } case <-ctx.Done(): diff --git a/services/labels/labels.go b/services/labels/labels.go index fda5daf949f..034c84941f3 100644 --- a/services/labels/labels.go +++ b/services/labels/labels.go @@ -5,8 +5,8 @@ import ( "strings" "sync" - "github.com/ReneKroon/ttlcache/v2" "github.com/Velocidex/ordereddict" + "github.com/Velocidex/ttlcache/v2" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" api_proto "www.velocidex.com/golang/velociraptor/api/proto" diff --git a/services/notifications.go b/services/notifications.go index 24a964333a0..330d88fc134 100644 --- a/services/notifications.go +++ b/services/notifications.go @@ -63,6 +63,9 @@ type Notifier interface { // that was registered above. NotifyListener(config_obj *config_proto.Config, id string) error + // Notify in the near future - no guarantee of delivery. + NotifyListenerAsync(config_obj *config_proto.Config, id string) + // Check if there is someone listening for the specified // id. This method queries all nodes to check if the client is // connected. diff --git a/services/notifications/notifications.go b/services/notifications/notifications.go index 804c3efbad5..1131b2e29f2 100644 --- a/services/notifications/notifications.go +++ b/services/notifications/notifications.go @@ -23,6 +23,8 @@ import ( "time" "github.com/Velocidex/ordereddict" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" config_proto "www.velocidex.com/golang/velociraptor/config/proto" "www.velocidex.com/golang/velociraptor/logging" "www.velocidex.com/golang/velociraptor/notifications" @@ -30,6 +32,13 @@ import ( "www.velocidex.com/golang/velociraptor/services/journal" ) +var ( + timeoutClientPing = promauto.NewCounter(prometheus.CounterOpts{ + Name: "client_ping_timeout", + Help: "Number of times the client ping has timed out.", + }) +) + type Notifier struct { pool_mu sync.Mutex notification_pool *notifications.NotificationPool @@ -137,6 +146,15 @@ func (self *Notifier) ProcessPing(ctx context.Context, return nil } + // Client is directly connected - inform the client info + // manager. Normally the Ping is sent by the frontend to find out + // which clients are connected to a minion. In this case it is + // worth the extra ping updates to deliver fresh data to the GUI - + // there are not too many clients but we need to know accurate + // data. + client_info_manager := services.GetClientInfoManager() + client_info_manager.UpdatePing(client_id, "") + notify_target, pres := row.GetString("NotifyTarget") if !pres { return nil @@ -195,6 +213,17 @@ func (self *Notifier) NotifyListener(config_obj *config_proto.Config, id string) ) } +func (self *Notifier) NotifyListenerAsync(config_obj *config_proto.Config, id string) { + journal, err := services.GetJournal() + if err != nil { + return + } + + journal.PushRowsToArtifactAsync(config_obj, + ordereddict.NewDict().Set("Target", id), + "Server.Internal.Notifications") +} + func (self *Notifier) IsClientDirectlyConnected(client_id string) bool { return self.notification_pool.IsClientConnected(client_id) } @@ -228,6 +257,10 @@ func (self *Notifier) IsClientConnected( return false } + if timeout == 0 { + return false + } + // Now wait here for the reply. select { case <-done: @@ -235,6 +268,7 @@ func (self *Notifier) IsClientConnected( return true case <-time.After(time.Duration(timeout) * time.Second): + timeoutClientPing.Inc() // Nope - not found within the timeout. return false } diff --git a/services/repository/filestore.go b/services/repository/filestore.go index b76e492b8f0..6f4f915e5c8 100644 --- a/services/repository/filestore.go +++ b/services/repository/filestore.go @@ -25,7 +25,7 @@ func InitializeGlobalRepositoryFromFilestore( // Load artifacts from the custom file store. file_store_factory := file_store.GetFileStore(config_obj) - err := file_store_factory.Walk(paths.ARTIFACT_DEFINITION_PREFIX, + err := api.Walk(file_store_factory, paths.ARTIFACT_DEFINITION_PREFIX, func(path api.FSPathSpec, info os.FileInfo) error { if path.Type() != api.PATH_TYPE_FILESTORE_YAML { return nil diff --git a/services/repository/manager_test.go b/services/repository/manager_test.go index bb9b2aafc39..63634042811 100644 --- a/services/repository/manager_test.go +++ b/services/repository/manager_test.go @@ -24,7 +24,7 @@ func (self *ManagerTestSuite) SetupTest() { } func (self *ManagerTestSuite) TestSetArtifact() { - clock := utils.MockClock{MockNow: time.Unix(1000000000, 0)} + clock := &utils.MockClock{MockNow: time.Unix(1000000000, 0)} journal_manager, err := services.GetJournal() assert.NoError(self.T(), err) diff --git a/services/sanity/index_migration.go b/services/sanity/index_migration.go index ea7f76da2d5..baaea354320 100644 --- a/services/sanity/index_migration.go +++ b/services/sanity/index_migration.go @@ -34,7 +34,7 @@ func maybeMigrateClientIndex( count := 0 // Migrate the old index to the new index. - err = db.Walk(config_obj, paths.CLIENT_INDEX_URN_DEPRECATED, + err = datastore.Walk(config_obj, db, paths.CLIENT_INDEX_URN_DEPRECATED, func(path api.DSPathSpec) error { client_id := path.Base() term := path.Dir().Base() diff --git a/services/sanity/sanity_test.go b/services/sanity/sanity_test.go index 8e2216e6b71..41b4f76629a 100644 --- a/services/sanity/sanity_test.go +++ b/services/sanity/sanity_test.go @@ -1,7 +1,6 @@ package sanity import ( - "context" "testing" "time" @@ -13,63 +12,24 @@ import ( acl_proto "www.velocidex.com/golang/velociraptor/acls/proto" api_proto "www.velocidex.com/golang/velociraptor/api/proto" artifacts_proto "www.velocidex.com/golang/velociraptor/artifacts/proto" - "www.velocidex.com/golang/velociraptor/config" config_proto "www.velocidex.com/golang/velociraptor/config/proto" "www.velocidex.com/golang/velociraptor/file_store/test_utils" "www.velocidex.com/golang/velociraptor/json" "www.velocidex.com/golang/velociraptor/paths" "www.velocidex.com/golang/velociraptor/services" "www.velocidex.com/golang/velociraptor/services/inventory" - "www.velocidex.com/golang/velociraptor/services/journal" - "www.velocidex.com/golang/velociraptor/services/notifications" - "www.velocidex.com/golang/velociraptor/services/repository" "www.velocidex.com/golang/velociraptor/utils" ) type ServicesTestSuite struct { - suite.Suite - config_obj *config_proto.Config - client_id string - flow_id string - sm *services.Service -} - -func (self *ServicesTestSuite) SetupTest() { - var err error - self.config_obj, err = new(config.Loader).WithFileLoader( - "../../http_comms/test_data/server.config.yaml"). - WithRequiredFrontend().WithWriteback(). - LoadAndValidate() - require.NoError(self.T(), err) - - // Start essential services. - ctx, _ := context.WithTimeout(context.Background(), time.Second*60) - self.sm = services.NewServiceManager(ctx, self.config_obj) - - require.NoError(self.T(), self.sm.Start(journal.StartJournalService)) - require.NoError(self.T(), self.sm.Start(notifications.StartNotificationService)) - require.NoError(self.T(), self.sm.Start(inventory.StartInventoryService)) - require.NoError(self.T(), self.sm.Start(repository.StartRepositoryManager)) - - manager, _ := services.GetRepositoryManager() - manager.SetGlobalRepositoryForTests(self.config_obj, manager.NewRepository()) -} - -func (self *ServicesTestSuite) TearDownTest() { - self.sm.Close() - test_utils.GetMemoryFileStore(self.T(), self.config_obj).Clear() - test_utils.GetMemoryDataStore(self.T(), self.config_obj).Clear() + test_utils.TestSuite + client_id string + flow_id string } // Check tool upgrade. func (self *ServicesTestSuite) TestUpgradeTools() { - manager, err := services.GetRepositoryManager() - assert.NoError(self.T(), err) - - repository, _ := manager.GetGlobalRepository(self.config_obj) - - // An an artifact with two tools. - repository.LoadYaml(` + self.LoadArtifacts([]string{` name: TestArtifact tools: - name: Tool1 @@ -78,18 +38,18 @@ tools: - name: Tool2 url: https://www.example2.com/ -`, true) +`}) // Admin forces Tool1 to non-default inventory := services.GetInventory().(*inventory.InventoryService) - inventory.Clock = utils.MockClock{MockNow: time.Unix(100, 0)} + inventory.Clock = &utils.MockClock{MockNow: time.Unix(100, 0)} inventory.ClearForTests() tool_definition := &artifacts_proto.Tool{ Name: "Tool1", Url: "https://www.company.com", } - err = inventory.AddTool(self.config_obj, tool_definition, + err := inventory.AddTool(self.ConfigObj, tool_definition, services.ToolOptions{ // This flag signifies that an admin explicitly set // this tool. We never overwrite an admin's setting. @@ -97,11 +57,11 @@ tools: }) assert.NoError(self.T(), err) - require.NoError(self.T(), self.sm.Start(StartSanityCheckService)) + require.NoError(self.T(), self.Sm.Start(StartSanityCheckService)) - db := test_utils.GetMemoryDataStore(self.T(), self.config_obj) + db := test_utils.GetMemoryDataStore(self.T(), self.ConfigObj) inventory_config := &artifacts_proto.ThirdParty{} - err = db.GetSubject(self.config_obj, + err = db.GetSubject(self.ConfigObj, paths.ThirdPartyInventory, inventory_config) assert.NoError(self.T(), err) @@ -116,26 +76,26 @@ tools: // Make sure initial user is created. func (self *ServicesTestSuite) TestCreateUser() { - self.config_obj.GUI.Authenticator = &config_proto.Authenticator{Type: "Basic"} - self.config_obj.GUI.InitialUsers = []*config_proto.GUIUser{ + self.ConfigObj.GUI.Authenticator = &config_proto.Authenticator{Type: "Basic"} + self.ConfigObj.GUI.InitialUsers = []*config_proto.GUIUser{ { Name: "User1", PasswordHash: "0d7dc4769a1d85162802703a1855b76e3b652bda3e0582ab32433f63dc6a0736", PasswordSalt: "0f61ad0fd6391513021242efb9ac780245cc21527fa3f9c5e552d47223e383a2", }, } - require.NoError(self.T(), self.sm.Start(StartSanityCheckService)) + require.NoError(self.T(), self.Sm.Start(StartSanityCheckService)) - db := test_utils.GetMemoryDataStore(self.T(), self.config_obj) + db := test_utils.GetMemoryDataStore(self.T(), self.ConfigObj) user1 := &api_proto.VelociraptorUser{} user_path_manager := paths.NewUserPathManager("User1") - err := db.GetSubject(self.config_obj, user_path_manager.Path(), user1) + err := db.GetSubject(self.ConfigObj, user_path_manager.Path(), user1) assert.NoError(self.T(), err) acl_obj := &acl_proto.ApiClientACL{} err = db.GetSubject( - self.config_obj, user_path_manager.ACL(), acl_obj) + self.ConfigObj, user_path_manager.ACL(), acl_obj) assert.NoError(self.T(), err) golden := ordereddict.NewDict(). @@ -146,7 +106,6 @@ func (self *ServicesTestSuite) TestCreateUser() { assert.NoError(self.T(), err) goldie.Assert(self.T(), "TestCreateUser", serialized) - // test_utils.GetMemoryDataStore(self.T(), self.config_obj).Debug() } func TestSanityService(t *testing.T) { diff --git a/services/server_artifacts/server_artifacts_test.go b/services/server_artifacts/server_artifacts_test.go index ea38df69d55..4147c6a1053 100644 --- a/services/server_artifacts/server_artifacts_test.go +++ b/services/server_artifacts/server_artifacts_test.go @@ -125,7 +125,7 @@ sources: // How long we took to run - should be immediate run_time := (details.Context.ActiveTime - details.Context.StartTime) / 1000000 - assert.True(self.T(), run_time < 1) + assert.True(self.T(), run_time < 2) } // Collect a long lived artifact with specified timeout. diff --git a/startup/startup.go b/startup/startup.go index 048dca1e4ff..1e1bdd2fc7d 100644 --- a/startup/startup.go +++ b/startup/startup.go @@ -113,13 +113,6 @@ func StartupEssentialServices(sm *services.Service) error { } } - if services.GetClientInfoManager() == nil { - err := sm.Start(client_info.StartClientInfoService) - if err != nil { - return err - } - } - return nil } @@ -127,14 +120,23 @@ func StartupEssentialServices(sm *services.Service) error { func StartupFrontendServices(sm *services.Service) error { spec := getServerServices(sm.Config) + if services.GetClientInfoManager() == nil { + err := sm.Start(client_info.StartClientInfoService) + if err != nil { + return err + } + } + err := sm.Start(datastore.StartMemcacheFileService) if err != nil { return err } - err = sm.Start(indexing.StartIndexingService) - if err != nil { - return err + if spec.IndexServer { + err = sm.Start(indexing.StartIndexingService) + if err != nil { + return err + } } // Check everything is ok before we can start. diff --git a/timelines/supertimeline.go b/timelines/supertimeline.go index cbf6584b205..df1b3dc6feb 100644 --- a/timelines/supertimeline.go +++ b/timelines/supertimeline.go @@ -2,13 +2,13 @@ package timelines import ( "context" - "fmt" "time" "google.golang.org/protobuf/proto" config_proto "www.velocidex.com/golang/velociraptor/config/proto" "www.velocidex.com/golang/velociraptor/datastore" "www.velocidex.com/golang/velociraptor/file_store" + "www.velocidex.com/golang/velociraptor/logging" "www.velocidex.com/golang/velociraptor/paths" timelines_proto "www.velocidex.com/golang/velociraptor/timelines/proto" "www.velocidex.com/golang/velociraptor/utils" @@ -143,7 +143,8 @@ func NewSuperTimelineReader( reader, err := NewTimelineReader( file_store_factory, path_manager.GetChild(timeline.Id)) if err != nil { - fmt.Printf("NewSuperTimelineReader err: %v\n", err) + logger := logging.GetLogger(config_obj, &logging.FrontendComponent) + logger.Debug("NewSuperTimelineReader err: %v\n", err) result.Close() return nil, err } diff --git a/timelines/writer.go b/timelines/writer.go index f1153bc01dc..32cb5f22734 100644 --- a/timelines/writer.go +++ b/timelines/writer.go @@ -27,10 +27,15 @@ type IndexRecord struct { } type TimelineWriter struct { - last_time time.Time - opts *json.EncOpts - fd api.FileWriter - index_fd api.FileWriter + last_time time.Time + opts *json.EncOpts + fd api.FileWriter + index_fd api.FileWriter + completion func() +} + +func (self *TimelineWriter) SetCompletion(completion func()) { + self.completion = completion } func (self *TimelineWriter) Write( @@ -85,8 +90,15 @@ func NewTimelineWriter( file_store_factory api.FileStore, path_manager paths.TimelinePathManagerInterface, truncate bool) (*TimelineWriter, error) { - fd, err := file_store_factory.WriteFile( - path_manager.Path()) + + result := &TimelineWriter{} + + fd, err := file_store_factory.WriteFileWithCompletion( + path_manager.Path(), func() { + if result.completion != nil { + result.completion() + } + }) if err != nil { return nil, err } @@ -103,6 +115,9 @@ func NewTimelineWriter( index_fd.Truncate() } - return &TimelineWriter{fd: fd, index_fd: index_fd}, nil + result.fd = fd + result.index_fd = index_fd + + return result, nil } diff --git a/utils/clock.go b/utils/clock.go index 7c93e1aa1ea..6c5d63d4944 100644 --- a/utils/clock.go +++ b/utils/clock.go @@ -26,20 +26,23 @@ func (self RealClock) Now() time.Time { } type MockClock struct { - MockNow time.Time - duration time.Duration + MockNow time.Time } -func (self MockClock) Now() time.Time { +func (self *MockClock) Now() time.Time { return self.MockNow } -func (self MockClock) After(d time.Duration) <-chan time.Time { - return time.After(self.duration) +// Advance the time and return immediately for sleeps. +func (self *MockClock) After(d time.Duration) <-chan time.Time { + self.MockNow = self.MockNow.Add(d) + res := make(chan time.Time) + close(res) + return res } -func (self MockClock) Sleep(d time.Duration) { - time.Sleep(self.duration) +func (self *MockClock) Sleep(d time.Duration) { + self.MockNow = self.MockNow.Add(d) } // A clock that increments each time someone calls Now() diff --git a/utils/completer.go b/utils/completer.go new file mode 100644 index 00000000000..7d5be1ff611 --- /dev/null +++ b/utils/completer.go @@ -0,0 +1,33 @@ +package utils + +import ( + "sync" +) + +type Completer struct { + mu sync.Mutex + count int + completion func() +} + +func NewCompleter(completion func()) *Completer { + return &Completer{ + completion: completion, + } +} + +func (self *Completer) GetCompletionFunc() func() { + self.mu.Lock() + defer self.mu.Unlock() + self.count++ + + return func() { + self.mu.Lock() + defer self.mu.Unlock() + + self.count-- + if self.count == 0 { + self.completion() + } + } +} diff --git a/utils/counter.go b/utils/counter.go index fdb721e3fcc..4226c959536 100644 --- a/utils/counter.go +++ b/utils/counter.go @@ -1,6 +1,11 @@ package utils -import "sync/atomic" +import ( + "encoding/binary" + "sync/atomic" + + "github.com/google/uuid" +) var ( idx uint64 @@ -9,3 +14,8 @@ var ( func GetId() uint64 { return atomic.AddUint64(&idx, 1) } + +func GetGUID() int64 { + u := uuid.New() + return int64(binary.BigEndian.Uint64(u[0:8]) >> 2) +} diff --git a/utils/debug.go b/utils/debug.go index e798621fbbf..ca72259b1bc 100644 --- a/utils/debug.go +++ b/utils/debug.go @@ -20,6 +20,7 @@ package utils import ( "fmt" + "os" "github.com/davecgh/go-spew/spew" ) @@ -33,3 +34,14 @@ func DlvBreak() { fmt.Printf("Break") } } + +func DebugToFile(filename, format string, v ...interface{}) { + fd, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0700) + if err != nil { + panic(err) + } + defer fd.Close() + + fd.Seek(0, os.SEEK_END) + fd.Write([]byte(fmt.Sprintf(format, v...) + "\n")) +} diff --git a/vql/filesystem/raw_registry.go b/vql/filesystem/raw_registry.go index 163fb3b9296..4401659a0b5 100644 --- a/vql/filesystem/raw_registry.go +++ b/vql/filesystem/raw_registry.go @@ -31,7 +31,6 @@ package filesystem import ( "bytes" "context" - "fmt" "net/url" "os" "path" @@ -331,8 +330,6 @@ func (self *RawRegFileSystemAccessor) PathSplit(path string) []string { func (self *RawRegFileSystemAccessor) PathJoin(root, stem string) string { url, err := url.Parse(root) if err != nil { - fmt.Printf("Error %v Joining %v and %v -> %v\n", - err, root, stem, path.Join(root, stem)) return path.Join(root, stem) } diff --git a/vql/parsers/authenticode/authenticode.go b/vql/parsers/authenticode/authenticode.go index 1933575ffc4..a918239a648 100644 --- a/vql/parsers/authenticode/authenticode.go +++ b/vql/parsers/authenticode/authenticode.go @@ -120,7 +120,14 @@ func (self *AuthenticodeFunction) Call(ctx context.Context, if err == nil { defer fd.Close() - cat_file, err := VerifyCatalogSignature(fd, normalized_path, output) + config_obj, ok := vql_subsystem.GetServerConfig(scope) + if !ok { + scope.Log("authenticode: %v", err) + return &vfilter.Null{} + } + + cat_file, err := VerifyCatalogSignature( + config_obj, fd, normalized_path, output) if err == nil { _ = ParseCatFile(cat_file, output, arg.Verbose) } diff --git a/vql/parsers/authenticode/cat.go b/vql/parsers/authenticode/cat.go index e189562727b..9d99603c8ec 100644 --- a/vql/parsers/authenticode/cat.go +++ b/vql/parsers/authenticode/cat.go @@ -13,6 +13,8 @@ import ( "github.com/Velocidex/ordereddict" "github.com/Velocidex/pkcs7" "www.velocidex.com/golang/go-pe" + config_proto "www.velocidex.com/golang/velociraptor/config/proto" + "www.velocidex.com/golang/velociraptor/logging" "www.velocidex.com/golang/velociraptor/utils" windows "www.velocidex.com/golang/velociraptor/vql/windows" "www.velocidex.com/golang/velociraptor/vql/windows/filesystems" @@ -33,7 +35,10 @@ var ( [8]byte{0x85, 0xe5, 0x0, 0xc0, 0x4f, 0xc2, 0x95, 0xee}} ) -func VerifyCatalogSignature(fd *os.File, normalized_path string, output *ordereddict.Dict) (string, error) { +func VerifyCatalogSignature( + config_obj *config_proto.Config, + fd *os.File, normalized_path string, + output *ordereddict.Dict) (string, error) { err := windows.HasWintrustDll() if err != nil { return "", err @@ -51,7 +56,8 @@ func VerifyCatalogSignature(fd *os.File, normalized_path string, output *ordered var CatAdminHandle syscall.Handle err = windows.CryptCATAdminAcquireContext2(&CatAdminHandle, nil, nil, nil, 0) if err != nil { - fmt.Printf("CryptCATAdminAcquireContext2 %v\n", err) + logger := logging.GetLogger(config_obj, &logging.ClientComponent) + logger.Error("CryptCATAdminAcquireContext2 %v\n", err) return "", err } defer windows.CryptCATAdminReleaseContext(CatAdminHandle, 0) @@ -62,7 +68,8 @@ func VerifyCatalogSignature(fd *os.File, normalized_path string, output *ordered err = windows.CryptCATAdminCalcHashFromFileHandle2(CatAdminHandle, fd.Fd(), &hash_length, (*byte)(unsafe.Pointer(&hash[0])), 0) if err != nil { - fmt.Printf("CryptCATAdminCalcHashFromFileHandle2 %v\n", err) + logger := logging.GetLogger(config_obj, &logging.ClientComponent) + logger.Error("CryptCATAdminCalcHashFromFileHandle2 %v\n", err) return "", err } diff --git a/vql/parsers/authenticode/compat.go b/vql/parsers/authenticode/compat.go index f9e5c099f1c..27e5fd38d65 100644 --- a/vql/parsers/authenticode/compat.go +++ b/vql/parsers/authenticode/compat.go @@ -6,6 +6,7 @@ import ( "os" "github.com/Velocidex/ordereddict" + config_proto "www.velocidex.com/golang/velociraptor/config/proto" ) // Placeholder for non windows system. This will mostly work except @@ -15,7 +16,10 @@ func VerifyFileSignature(normalized_path string) string { return "Unknown (No API access)" } -func VerifyCatalogSignature(fd *os.File, normalized_path string, output *ordereddict.Dict) (string, error) { +func VerifyCatalogSignature( + config_obj *config_proto.Config, + fd *os.File, normalized_path string, + output *ordereddict.Dict) (string, error) { return "Unknown (No API access)", nil } diff --git a/vql/server/clients/delete.go b/vql/server/clients/delete.go index 6e821be6526..539dcfc096b 100644 --- a/vql/server/clients/delete.go +++ b/vql/server/clients/delete.go @@ -63,7 +63,7 @@ func (self DeleteClientPlugin) Call(ctx context.Context, client_path_manager := paths.NewClientPathManager(arg.ClientId) // Indiscriminately delete all the client's datastore files. - err = db.Walk(config_obj, client_path_manager.Path(), + err = datastore.Walk(config_obj, db, client_path_manager.Path(), func(filename api.DSPathSpec) error { select { case <-ctx.Done(): @@ -101,7 +101,7 @@ func (self DeleteClientPlugin) Call(ctx context.Context, } // Delete the filestore files. - err = file_store_factory.Walk( + err = api.Walk(file_store_factory, client_path_manager.Path().AsFilestorePath(), func(filename api.FSPathSpec, info os.FileInfo) error { select { diff --git a/vql/server/clients/fixtures/TestDeleteClient.golden b/vql/server/clients/fixtures/TestDeleteClient.golden index ccd56a785ac..46693872059 100644 --- a/vql/server/clients/fixtures/TestDeleteClient.golden +++ b/vql/server/clients/fixtures/TestDeleteClient.golden @@ -130,6 +130,12 @@ "vfs_path": "/clients/C.123/ping.db", "really_do_it": true }, + { + "client_id": "C.123", + "type": "Datastore", + "vfs_path": "/clients/C.123/ping.json.db", + "really_do_it": true + }, { "client_id": "C.123", "type": "Datastore", diff --git a/vql/server/flows/flows.go b/vql/server/flows/flows.go index 7a9834a1133..971c51f9e48 100644 --- a/vql/server/flows/flows.go +++ b/vql/server/flows/flows.go @@ -262,13 +262,14 @@ func (self EnumerateFlowPlugin) Call( } r.emit_ds("Notebook", flow_path_manager.Notebook().Path()) - db.Walk(config_obj, flow_path_manager.Notebook().DSDirectory(), + datastore.Walk(config_obj, db, flow_path_manager.Notebook().DSDirectory(), func(path api.DSPathSpec) error { r.emit_ds("NotebookData", path) return nil }) - file_store_factory.Walk(flow_path_manager.Notebook().Directory(), + api.Walk(file_store_factory, + flow_path_manager.Notebook().Directory(), func(path api.FSPathSpec, info os.FileInfo) error { r.emit_fs("NotebookItem", path) return nil diff --git a/vql/server/notebooks/delete.go b/vql/server/notebooks/delete.go index 789f17e446f..4ea21857fac 100644 --- a/vql/server/notebooks/delete.go +++ b/vql/server/notebooks/delete.go @@ -69,7 +69,7 @@ func (self *DeleteNotebookPlugin) Call(ctx context.Context, } // Indiscriminately delete all the client's datastore files. - err = db.Walk(config_obj, notebook_path_manager.DSDirectory(), + err = datastore.Walk(config_obj, db, notebook_path_manager.DSDirectory(), func(filename api.DSPathSpec) error { select { case <-ctx.Done(): @@ -96,7 +96,7 @@ func (self *DeleteNotebookPlugin) Call(ctx context.Context, } // Delete the filestore files. - err = file_store_factory.Walk(notebook_path_manager.Directory(), + err = api.Walk(file_store_factory, notebook_path_manager.Directory(), func(filename api.FSPathSpec, info os.FileInfo) error { select { case <-ctx.Done(): diff --git a/vql/server/repository.go b/vql/server/repository.go index ec37e9a908a..441639f4e49 100644 --- a/vql/server/repository.go +++ b/vql/server/repository.go @@ -228,7 +228,17 @@ func (self ArtifactsPlugin) Call( // No args means just dump all artifacts if len(arg.Names) == 0 { - arg.Names = repository.List() + for _, name := range repository.List() { + artifact, pres := repository.Get(config_obj, name) + if pres { + select { + case <-ctx.Done(): + return + case output_chan <- json.ConvertProtoToOrderedDict(artifact): + } + } + } + return } seen := make(map[string]*artifacts_proto.Artifact) diff --git a/vtesting/assert/wrapper.go b/vtesting/assert/wrapper.go index 4cec3b63897..9cde2c8b748 100644 --- a/vtesting/assert/wrapper.go +++ b/vtesting/assert/wrapper.go @@ -37,6 +37,10 @@ func NoError(t TestingT, err error, msgAndArgs ...interface{}) { assert.NoError(t, err, msgAndArgs...) } +func Error(t TestingT, err error, msgAndArgs ...interface{}) { + assert.Error(t, err, msgAndArgs...) +} + func Regexp(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) { assert.Regexp(t, expected, actual, msgAndArgs...) } diff --git a/vtesting/metrics.go b/vtesting/metrics.go new file mode 100644 index 00000000000..a6d83594317 --- /dev/null +++ b/vtesting/metrics.go @@ -0,0 +1,75 @@ +package vtesting + +import ( + "fmt" + "regexp" + "testing" + + "github.com/Velocidex/ordereddict" + "github.com/prometheus/client_golang/prometheus" + "www.velocidex.com/golang/velociraptor/vtesting/assert" +) + +func GetMetrics(t *testing.T, name_regex string) *ordereddict.Dict { + return GetMetricsDifference(t, name_regex, nil) +} + +func GetMetricsDifference(t *testing.T, + name_regex string, snapshot *ordereddict.Dict) *ordereddict.Dict { + if snapshot == nil { + snapshot = ordereddict.NewDict() + } + + regex, err := regexp.Compile(name_regex) + assert.NoError(t, err) + + result := ordereddict.NewDict() + + gathering, err := prometheus.DefaultGatherer.Gather() + assert.NoError(t, err) + + for _, metric := range gathering { + if regex.MatchString(*metric.Name) { + for _, m := range metric.Metric { + if m.Gauge != nil { + value := int64(*m.Gauge.Value) + old_value, pres := snapshot.GetInt64(*metric.Name) + if pres { + value -= old_value + } + result.Set(*metric.Name, value) + + } else if m.Counter != nil { + value := int64(*m.Counter.Value) + old_value, pres := snapshot.GetInt64(*metric.Name) + if pres { + value -= old_value + } + result.Set(*metric.Name, value) + } else if m.Histogram != nil { + + label := "" + for _, l := range m.Label { + label += "_" + *l.Value + } + + for idx, b := range m.Histogram.Bucket { + name := fmt.Sprintf("%v_%v_%0.2f", *metric.Name, + label, *b.UpperBound) + if idx == len(m.Histogram.Bucket)-1 { + name = fmt.Sprintf("%v_%v_inf", *metric.Name, + label) + } + value := int64(*b.CumulativeCount) + old_value, pres := snapshot.GetInt64(name) + if pres { + value -= old_value + } + result.Set(name, value) + } + } + } + } + } + return result +}