diff --git a/api/api.gen.go b/api/api.gen.go index 171c280..226032d 100644 --- a/api/api.gen.go +++ b/api/api.gen.go @@ -10,6 +10,7 @@ import ( "compress/gzip" "context" "encoding/base64" + "encoding/json" "fmt" "net/http" "net/url" @@ -93,7 +94,8 @@ type CMWTyp string // ChaResRequest defines model for ChaResRequest. type ChaResRequest struct { - Nonce string `json:"nonce"` + AttesterSelection json.RawMessage `json:"attester-selection,omitempty"` + Nonce string `json:"nonce"` } // EAT defines model for EAT. @@ -324,20 +326,20 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/7SV32/bNhDH/xWC21tVWcmCPWjYg+PloQ8FijRDH9JgOJNni5l05MiTO6/Q/z6QtGI7", - "URq3aB+lOx4/9+vLz1LZzllC4iDrz9KBhw4ZffpaNHCN4Rr/6THwuwdTCUqh4+hhSNayQdDoZSEJOpS1", - "3JkLGVSDHUQ/3rpoCewNreUwDKMx3XMJenfJlffWJxBvHXo2mBw0Mph2IlAhDQUGUjhpDAzcpwhIfSfr", - "24uquitGP+q7Jfrox4ZbPHCThjbQGi18xpL7Q/vg+cf+DMO6Xhtu+mWpbFecV+cX9QY9mGBp5oGDrjGm", - "V++CPx97KGQ0Go86BY7WEfIhqf0xu7xHxRFp8fbD0+Lx1h1ignOtUcDG0mxDuhwRS2VpZdar8JpD9+o+", - "WJrMegOpDyvrO2BZyyUE/PWi9608IQ2Zz0+iH87a0yTI7nr8DRfns1OXXs1vnl6FwH85b1em/cr2TtaL", - "MDDq12z/Rorhfva4krX8abZfvNluFWaxfY/hD2keRZvK6E+CnhvrzX+of/wynZ20TFEPQhD9Adr3XagX", - "In/jOsWUUfXe8PZ9bFCu3iWCRz/vuYlfqXPx0DL93o9iw+yyzhla2VTPXBZ5Pb95L642RiMpFAvbtqji", - "Moo/ADtLYv7uTVwT9MHYqK5VWZVnsTzWIYEzspa/lFVZyUI64CZB5WrMVAM+Uzqbl0hjUN44zqEWDbQt", - "0hqFx+AsBYy3lWKeBDsIEOrBA0gXoneWROhT/wqxRkIPjEFwgwLHFAyJq/mN+DQTi7cfRN7PUiZen2Tm", - "jY5pR8JFBiyO3pnb6aXYu8xeeIeGu9xgDHxp9TbmrSwxUirB83qXYLLSja2EF1f0SKaG49li32P6kYub", - "OnFeVV8AQuBX95/4N3Gw5r9/PGHyP8rTmaPKJdLjabhpcHzfRAMh9xk16jJO28UXuZ23yxa7ryze43f+", - "GaiAfoNeKNu3WpBl0ZNGH5VJp8kboXWPgq0Y3+qwJYZ/d/Bn3x3+qbJO4M+z1JljtSuPxCRN/KGM3N4N", - "d8MwDP8HAAD//wd3xRKNCQAA", + "H4sIAAAAAAAC/7SW32/bNhDH/xWC21sVWcmCPWjYg+PloQ8FijRDH9JgOJNni5lEcseTW6/Q/z6QtGI7", + "UZq0yB6lO54+9+N71FepXOedRctB1l+lB4IOGSk9LRq4wnCF//QY+P29qQSl0HP0MFbWskHQSLKQFjqU", + "tdyZCxlUgx1EP976aAlMxq7lMAyjMX3nAvTuI5dEjhIIOY/EBpODRgbTTgQqpLGBwSqcNAYG7lMEtH0n", + "65vzqrotRj/bd0uk6MeGWzxwk8ZuoDVaUMaS+0P74PnF/gzDul4bbvplqVxXnFVn5/UGCUxwdkbAQdcY", + "06t3wZ+OPRQyGg2hToGjdYS8T2p/zC3vUHFEWrz7+Lh4vPWHmOB9axSwcXa2sbocEUvl7MqsV+GEQ/fm", + "Ljg7mfUGUh9WjjpgWcslBPz1vKdWviANmc9Poh/O2uMkgBkDI50EbFFF+COKhPsQoJBfTlxnGDvPW1kz", + "9TgU0rrdtPxACvlsMUUzldPl/PpxJgj8lye3Mu13Ts9kO2zk0Cfs/sZUkZ8JV7KWP832up7tlDaL0/Ew", + "o0OaB9GmMvrTQs+NI/Mv6v9fq6cv0mpcNyGI/gDtdfX6TOQfVGtMGVVPhrcfYoNy9S4QCGnecxOfUufi", + "oWV6vZ/PhtnnNWrsyqV65rLIq/n1B3G5MRqtQrFw7W5AxR+AnbNi/v5tVCFSSBqSVVmVp7E8zqMFb2Qt", + "fymrspKF9MBNgsrVmKkGKFN6lzWqMSgyPssxSrht0a5REAbvbMD4tVLM030QBAh17wFWF6L3zorQp/4V", + "Yo0WCRiD4AYFjikYKy7n1+LzTCzefRRZtKVMvJS22Fsd046EiwxYHF1jN9Oi2LvMnrnmhtvcYAx84fQ2", + "5q2cZbSpBE+v0wSTF+nYSnhWokdbcDierd0CG4ubOnFWVd8AQuA3d5/5N3Eg898/vWDyP8mXM8ctl0iP", + "p+G6wfH6FA2E3GfUqMs4beff5Pbkli1231m8h78RT0AFpA2SUK5vtbCORW81UtxMOk3eCK17FOzE+CsQ", + "tpbhyw7+9NXhH2/WCfx5XnXmeNuVR8skTfzhGrm5HW6HYRj+CwAA///mvptn7AkAAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/server.go b/api/server.go index ac5a722..72a4773 100644 --- a/api/server.go +++ b/api/server.go @@ -111,6 +111,24 @@ func (s *Server) RatsdChares(w http.ResponseWriter, r *http.Request, param Ratsd return } + var options map[string]json.RawMessage + if len(requestData.AttesterSelection) > 0 { + err := json.Unmarshal(requestData.AttesterSelection, &options) + if err != nil { + errMsg := fmt.Sprintf( + "failed to parse attester selection: %s", err.Error()) + p := &problems.DefaultProblem{ + Type: string(TagGithubCom2024VeraisonratsdErrorInvalidrequest), + Title: string(InvalidRequest), + Detail: errMsg, + Status: http.StatusBadRequest, + } + s.reportProblem(w, p) + fmt.Println(errMsg) + return + } + } + for _, pn := range pl { attester, err := s.manager.LookupByName(pn) if err != nil { @@ -130,10 +148,16 @@ func (s *Server) RatsdChares(w http.ResponseWriter, r *http.Request, param Ratsd continue } + params, hasOption := options[pn] + if !hasOption { + params = json.RawMessage{} + } + s.logger.Info("output content type: ", formatOut.Formats[0].ContentType) in := &compositor.EvidenceIn{ ContentType: formatOut.Formats[0].ContentType, Nonce: nonce, + Options: params, } out := attester.GetEvidence(in) diff --git a/api/server_test.go b/api/server_test.go index 057d4a4..b335ea5 100644 --- a/api/server_test.go +++ b/api/server_test.go @@ -79,34 +79,57 @@ func TestRatsdChares_wrong_accept_type(t *testing.T) { assert.Equal(t, expectedBody, &body) } -func TestRatsdChares_missing_nonce(t *testing.T) { +func TestRatsdChares_invalid_body(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + var params RatsdCharesParams param := fmt.Sprintf(`application/eat-ucs+json; eat_profile=%q`, TagGithubCom2024Veraisonratsd) params.Accept = ¶m logger := log.Named("test") - s := &Server{logger: logger} - w := httptest.NewRecorder() - rb := strings.NewReader("{\"noncee\": \"MIDBNH28iioisjPy\"}") - r, _ := http.NewRequest(http.MethodPost, "/ratsd/chares", rb) - r.Header.Add("Content-Type", ApplicationvndVeraisonCharesJson) - s.RatsdChares(w, r, params) - expectedCode := http.StatusBadRequest - expectedType := problems.ProblemMediaType - expectedBody := &problems.DefaultProblem{ - Type: string(TagGithubCom2024VeraisonratsdErrorInvalidrequest), - Title: string(InvalidRequest), - Status: http.StatusBadRequest, - Detail: "fail to retrieve nonce from the request", - } + pluginList := []string{"mock-tsm"} + dm := mock_deps.NewMockIManager(ctrl) + dm.EXPECT().GetPluginList().Return(pluginList).AnyTimes() + dm.EXPECT().LookupByName("mock-tsm").Return(mocktsm.GetPlugin(), nil).AnyTimes() - var body problems.DefaultProblem - _ = json.Unmarshal(w.Body.Bytes(), &body) + s := NewServer(logger, dm) + tests := []struct{ name, body, msg string }{ + {"missing nonce", `{"noncee": "MIDBNH28iioisjPy"}`, + "fail to retrieve nonce from the request"}, + {"invlid attester selecton", + fmt.Sprintf(`{"nonce": "%s", + "attester-selection": "attester-slection"}`, validNonce), + "failed to parse attester selection: json: cannot unmarshal string into" + + ` Go value of type map[string]json.RawMessage`}, + } - assert.Equal(t, expectedCode, w.Code) - assert.Equal(t, expectedType, w.Result().Header.Get("Content-Type")) - assert.Equal(t, expectedBody, &body) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + w := httptest.NewRecorder() + rb := strings.NewReader(tt.body) + r, _ := http.NewRequest(http.MethodPost, "/ratsd/chares", rb) + r.Header.Add("Content-Type", ApplicationvndVeraisonCharesJson) + s.RatsdChares(w, r, params) + + expectedCode := http.StatusBadRequest + expectedType := problems.ProblemMediaType + expectedBody := &problems.DefaultProblem{ + Type: string(TagGithubCom2024VeraisonratsdErrorInvalidrequest), + Title: string(InvalidRequest), + Status: http.StatusBadRequest, + Detail: tt.msg, + } + + var body problems.DefaultProblem + _ = json.Unmarshal(w.Body.Bytes(), &body) + + assert.Equal(t, expectedCode, w.Code) + assert.Equal(t, expectedType, w.Result().Header.Get("Content-Type")) + assert.Equal(t, expectedBody, &body) + }) + } } func TestRatsdChares_valid_request_no_available_attester(t *testing.T) { @@ -125,7 +148,7 @@ func TestRatsdChares_valid_request_no_available_attester(t *testing.T) { s := NewServer(logger, dm) w := httptest.NewRecorder() - rs := fmt.Sprintf("{\"nonce\": \"%s\"}", validNonce) + rs := fmt.Sprintf(`{"nonce": "%s"}`, validNonce) rb := strings.NewReader(rs) r, _ := http.NewRequest(http.MethodPost, "/ratsd/chares", rb) r.Header.Add("Content-Type", ApplicationvndVeraisonCharesJson) @@ -156,49 +179,77 @@ func TestRatsdChares_valid_request(t *testing.T) { pluginList := []string{"mock-tsm"} dm := mock_deps.NewMockIManager(ctrl) - dm.EXPECT().GetPluginList().Return(pluginList) - dm.EXPECT().LookupByName("mock-tsm").Return(mocktsm.GetPlugin(), nil) + dm.EXPECT().GetPluginList().Return(pluginList).AnyTimes() + dm.EXPECT().LookupByName("mock-tsm").Return(mocktsm.GetPlugin(), nil).AnyTimes() s := NewServer(logger, dm) - w := httptest.NewRecorder() - rs := fmt.Sprintf("{\"nonce\": \"%s\"}", validNonce) - rb := strings.NewReader(rs) - r, _ := http.NewRequest(http.MethodPost, "/ratsd/chares", rb) - r.Header.Add("Content-Type", ApplicationvndVeraisonCharesJson) - s.RatsdChares(w, r, params) - - expectedCode := http.StatusOK - expectedType := param - - assert.Equal(t, expectedCode, w.Code) - assert.Equal(t, expectedType, w.Result().Header.Get("Content-Type")) - - var out map[string]string - json.Unmarshal(w.Body.Bytes(), &out) - assert.Equal(t, string(TagGithubCom2024Veraisonratsd), out["eat_profile"]) - assert.Equal(t, validNonce, out["eat_nonce"]) - assert.Contains(t, out, "cmw") - - data, err := base64.StdEncoding.DecodeString(out["cmw"]) - assert.NoError(t, err) - - collection := &cmw.CMW{} - err = collection.UnmarshalJSON([]byte(data)) - assert.NoError(t, err) - assert.Equal(t, cmw.KindCollection, collection.GetKind()) - - c, err := collection.GetCollectionItem("mock-tsm") - assert.NoError(t, err) - assert.Equal(t, cmw.KindMonad, c.GetKind()) - assert.Equal(t, c.GetMonadType(), "application/vnd.veraison.configfs-tsm+json") - - tsmout := &tokens.TSMReport{} - tsmout.FromJSON(c.GetMonadValue()) - assert.Equal(t, "fake\n", tsmout.Provider) + realNonce, _ := base64.RawURLEncoding.DecodeString(validNonce) - assert.Equal(t, tokens.BinaryString("auxblob"), tsmout.AuxBlob) + tests := []struct { + name, query string + privlevel int + }{ + { + "no params", + fmt.Sprintf(`{"nonce": "%s"}`, validNonce), + 0, + }, + { + "with params", + fmt.Sprintf(`{"nonce": "%s", + "attester-selection":{ + "mock-tsm":{ + "privilege_level":"1" + } + } + }`, validNonce), + 1, + }, + } - realNonce, _ := base64.RawURLEncoding.DecodeString(validNonce) - expectedOutblob := fmt.Sprintf("privlevel: 0\ninblob: %s", hex.EncodeToString([]byte(realNonce))) - assert.Equal(t, tokens.BinaryString(expectedOutblob), tsmout.OutBlob) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + w := httptest.NewRecorder() + rb := strings.NewReader(tt.query) + r, _ := http.NewRequest(http.MethodPost, "/ratsd/chares", rb) + r.Header.Add("Content-Type", ApplicationvndVeraisonCharesJson) + s.RatsdChares(w, r, params) + + expectedCode := http.StatusOK + expectedType := param + + assert.Equal(t, expectedCode, w.Code) + assert.Equal(t, expectedType, w.Result().Header.Get("Content-Type")) + + var out map[string]string + err := json.Unmarshal(w.Body.Bytes(), &out) + assert.NoError(t, err) + assert.Equal(t, string(TagGithubCom2024Veraisonratsd), out["eat_profile"]) + assert.Equal(t, validNonce, out["eat_nonce"]) + assert.Contains(t, out, "cmw") + + data, err := base64.StdEncoding.DecodeString(out["cmw"]) + assert.NoError(t, err) + + collection := &cmw.CMW{} + err = collection.UnmarshalJSON([]byte(data)) + assert.NoError(t, err) + assert.Equal(t, cmw.KindCollection, collection.GetKind()) + + c, err := collection.GetCollectionItem("mock-tsm") + assert.NoError(t, err) + assert.Equal(t, cmw.KindMonad, c.GetKind()) + assert.Equal(t, c.GetMonadType(), "application/vnd.veraison.configfs-tsm+json") + + tsmout := &tokens.TSMReport{} + tsmout.FromJSON(c.GetMonadValue()) + assert.Equal(t, "fake\n", tsmout.Provider) + + assert.Equal(t, tokens.BinaryString("auxblob"), tsmout.AuxBlob) + + expectedOutblob := fmt.Sprintf("privlevel: %d\ninblob: %s", tt.privlevel, + hex.EncodeToString([]byte(realNonce))) + assert.Equal(t, tokens.BinaryString(expectedOutblob), tsmout.OutBlob) + }) + } } diff --git a/attesters/mocktsm/mocktsm.go b/attesters/mocktsm/mocktsm.go index 4b12106..5730668 100644 --- a/attesters/mocktsm/mocktsm.go +++ b/attesters/mocktsm/mocktsm.go @@ -3,7 +3,9 @@ package mocktsm import ( + "encoding/json" "fmt" + "strconv" "github.com/google/go-configfs-tsm/configfs/configfsi" "github.com/google/go-configfs-tsm/configfs/faketsm" @@ -77,6 +79,25 @@ func (m *MockPlugin) GetEvidence(in *compositor.EvidenceIn) *compositor.Evidence GetAuxBlob: true, } + options := make(map[string]string) + if len(in.Options) > 0 { + if err := json.Unmarshal(in.Options, &options); err != nil { + errMsg := fmt.Errorf( + "failed to parse %s: %v", in.Options, err) + return getEvidenceError(errMsg) + } + } + + if privlevel, ok := options["privilege_level"]; ok { + level, err := strconv.Atoi(privlevel) + if err != nil || level < 0 { + errMsg := fmt.Errorf("privilege_level %s is invalid", + privlevel) + return getEvidenceError(errMsg) + } + req.Privilege = &report.Privilege{Level: uint(level)} + } + resp, err := report.Get(m.client, req) if err != nil { errMsg := fmt.Errorf("failed to get mock TSM report: %v", err) diff --git a/attesters/mocktsm/mocktsm_test.go b/attesters/mocktsm/mocktsm_test.go index 50abaab..7a4ac4d 100644 --- a/attesters/mocktsm/mocktsm_test.go +++ b/attesters/mocktsm/mocktsm_test.go @@ -74,7 +74,7 @@ func Test_GetEvidence_invalid_format(t *testing.T) { assert.Equal(t, expected, p.GetEvidence(in)) } -func Test_GetEvidence(t *testing.T) { +func Test_GetEvidence_No_Options(t *testing.T) { inblob := []byte(validNonceStr) in := &compositor.EvidenceIn{ ContentType: string(mediaType), @@ -97,3 +97,58 @@ func Test_GetEvidence(t *testing.T) { assert.Equal(t, expected, p.GetEvidence(in)) } + +func TestGetEvidence_With_Invalid_Options(t *testing.T) { + tests := []struct{name, params, msg string} { + {"privilege level not integer", `{"privilege_level": "invalid"}`, + "privilege_level invalid is invalid"}, + {"privilege level less than zero", `{"privilege_level": "-20"}`, + "privilege_level -20 is invalid"}, + {"invalid json", `{"privilege_level"}`, + `failed to parse {"privilege_level"}: invalid character '}' after object key`}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + inblob := []byte(validNonceStr) + in := &compositor.EvidenceIn{ + ContentType: string(mediaType), + Nonce: inblob, + Options: []byte(tt.params), + } + + expected := &compositor.EvidenceOut{ + Status: &compositor.Status{ + Result: false, + Error: tt.msg, + }, + } + + assert.Equal(t, expected, p.GetEvidence(in)) + }) + } +} + +func Test_GetEvidence_With_Valid_Privilege_level(t *testing.T) { + inblob := []byte(validNonceStr) + in := &compositor.EvidenceIn{ + ContentType: string(mediaType), + Nonce: inblob, + Options: []byte(`{"privilege_level": "1"}`), + } + + expectedOutblob := fmt.Sprintf("privlevel: 1\ninblob: %s", hex.EncodeToString(inblob)) + out := &tokens.TSMReport { + Provider: "fake\n", + OutBlob: []byte(expectedOutblob), + AuxBlob: []byte("auxblob"), + } + + outEncoded, _ := out.ToJSON() + + expected := &compositor.EvidenceOut{ + Status: statusSucceeded, + Evidence: outEncoded, + } + + assert.Equal(t, expected, p.GetEvidence(in)) +} diff --git a/docs/api/ratsd.yaml b/docs/api/ratsd.yaml index 7ad404e..df9f1c7 100644 --- a/docs/api/ratsd.yaml +++ b/docs/api/ratsd.yaml @@ -86,10 +86,15 @@ components: type: object required: - nonce + - attester-selection properties: nonce: type: string format: base64url + attester-selection: + type: string + format: json + x-omitempty: true EAT: type: object required: diff --git a/proto/compositor.proto b/proto/compositor.proto index 94093cb..4a1315c 100644 --- a/proto/compositor.proto +++ b/proto/compositor.proto @@ -44,6 +44,7 @@ message SupportedFormatsOut { message EvidenceIn { string contentType = 1; bytes nonce = 2; + bytes options = 3; } message EvidenceOut { diff --git a/proto/compositor/compositor.pb.go b/proto/compositor/compositor.pb.go index 1a17cd0..6864987 100644 --- a/proto/compositor/compositor.pb.go +++ b/proto/compositor/compositor.pb.go @@ -24,6 +24,7 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +// Status.result = true on sucess, status.result = false on failure. type Status struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -306,6 +307,7 @@ type EvidenceIn struct { ContentType string `protobuf:"bytes,1,opt,name=contentType,proto3" json:"contentType,omitempty"` Nonce []byte `protobuf:"bytes,2,opt,name=nonce,proto3" json:"nonce,omitempty"` + Options []byte `protobuf:"bytes,3,opt,name=options,proto3" json:"options,omitempty"` } func (x *EvidenceIn) Reset() { @@ -354,6 +356,13 @@ func (x *EvidenceIn) GetNonce() []byte { return nil } +func (x *EvidenceIn) GetOptions() []byte { + if x != nil { + return x.Options + } + return nil +} + type EvidenceOut struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -442,35 +451,37 @@ var file_compositor_proto_rawDesc = []byte{ 0x72, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2c, 0x0a, 0x07, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x2e, 0x46, - 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x52, 0x07, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x22, 0x44, + 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x52, 0x07, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x22, 0x5e, 0x0a, 0x0a, 0x45, 0x76, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x49, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x6e, - 0x6f, 0x6e, 0x63, 0x65, 0x22, 0x55, 0x0a, 0x0b, 0x45, 0x76, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, - 0x4f, 0x75, 0x74, 0x12, 0x2a, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, - 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x1a, 0x0a, 0x08, 0x65, 0x76, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x08, 0x65, 0x76, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x32, 0xe6, 0x01, 0x0a, 0x0a, - 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x12, 0x48, 0x0a, 0x10, 0x47, 0x65, - 0x74, 0x53, 0x75, 0x62, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, 0x72, 0x49, 0x44, 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, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, - 0x74, 0x6f, 0x72, 0x2e, 0x53, 0x75, 0x62, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, 0x72, 0x49, - 0x44, 0x4f, 0x75, 0x74, 0x12, 0x4e, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, - 0x72, 0x74, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 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, 0x1f, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, - 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, - 0x73, 0x4f, 0x75, 0x74, 0x12, 0x3e, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x45, 0x76, 0x69, 0x64, 0x65, - 0x6e, 0x63, 0x65, 0x12, 0x16, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, - 0x2e, 0x45, 0x76, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x49, 0x6e, 0x1a, 0x17, 0x2e, 0x63, 0x6f, - 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x2e, 0x45, 0x76, 0x69, 0x64, 0x65, 0x6e, 0x63, - 0x65, 0x4f, 0x75, 0x74, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x76, 0x65, 0x72, 0x61, 0x69, 0x73, 0x6f, 0x6e, 0x2f, 0x72, 0x61, 0x74, 0x73, - 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, - 0x6f, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x55, + 0x0a, 0x0b, 0x45, 0x76, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x4f, 0x75, 0x74, 0x12, 0x2a, 0x0a, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, + 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x76, 0x69, + 0x64, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x65, 0x76, 0x69, + 0x64, 0x65, 0x6e, 0x63, 0x65, 0x32, 0xe6, 0x01, 0x0a, 0x0a, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x73, + 0x69, 0x74, 0x6f, 0x72, 0x12, 0x48, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, 0x75, 0x62, 0x41, 0x74, + 0x74, 0x65, 0x73, 0x74, 0x65, 0x72, 0x49, 0x44, 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, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x2e, 0x53, 0x75, + 0x62, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x65, 0x72, 0x49, 0x44, 0x4f, 0x75, 0x74, 0x12, 0x4e, + 0x0a, 0x13, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x46, 0x6f, + 0x72, 0x6d, 0x61, 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, 0x1f, 0x2e, + 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6f, + 0x72, 0x74, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x4f, 0x75, 0x74, 0x12, 0x3e, + 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x45, 0x76, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x16, 0x2e, + 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x2e, 0x45, 0x76, 0x69, 0x64, 0x65, + 0x6e, 0x63, 0x65, 0x49, 0x6e, 0x1a, 0x17, 0x2e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, + 0x6f, 0x72, 0x2e, 0x45, 0x76, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x4f, 0x75, 0x74, 0x42, 0x2c, + 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x65, 0x72, + 0x61, 0x69, 0x73, 0x6f, 0x6e, 0x2f, 0x72, 0x61, 0x74, 0x73, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var (