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 +}