From 240f46cde255141b8586362a8fba5c68b936944c Mon Sep 17 00:00:00 2001 From: kirari04 Date: Tue, 16 Jun 2026 18:06:35 +0200 Subject: [PATCH] Add dashboard diagnostics --- gen/proto/p2pstream/v1/management.pb.go | 1144 +++++++++++++---- .../p2pstreamv1connect/management.connect.go | 29 + internal/db/connection.go | 81 +- internal/db/connection_test.go | 16 +- internal/db/models.go | 18 + internal/db/querier.go | 10 + internal/db/query.sql.go | 620 ++++++++- internal/server/dashboard.go | 416 +++++- internal/server/dashboard_test.go | 245 +++- internal/server/observability_rollup.go | 27 +- internal/server/public_cache.go | 3 +- internal/server/public_proxy_pipeline.go | 17 +- internal/server/public_routing.go | 9 +- proto/p2pstream/v1/management.proto | 61 + sql/query.sql | 216 +++- sql/schema.sql | 23 + tests/integration/observability_test.go | 2 +- web/management/src/App.vue | 1 + .../proto/p2pstream/v1/management_connect.ts | 11 +- .../gen/proto/p2pstream/v1/management_pb.ts | 339 ++++- web/management/src/lib/dashboardStats.test.ts | 26 +- web/management/src/lib/dashboardStats.ts | 32 +- web/management/src/router.ts | 2 + web/management/src/views/Diagnostics.vue | 694 ++++++++++ web/management/src/views/Overview.vue | 57 +- 25 files changed, 3756 insertions(+), 343 deletions(-) create mode 100644 web/management/src/views/Diagnostics.vue diff --git a/gen/proto/p2pstream/v1/management.pb.go b/gen/proto/p2pstream/v1/management.pb.go index 4a53fa7..f39aff8 100644 --- a/gen/proto/p2pstream/v1/management.pb.go +++ b/gen/proto/p2pstream/v1/management.pb.go @@ -14245,6 +14245,546 @@ func (x *GetDashboardResponse) GetTopRouteTargets() []*DashboardProxyDimensionSu return nil } +type GetDashboardDiagnosticsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + WindowLabel string `protobuf:"bytes,1,opt,name=window_label,json=windowLabel,proto3" json:"window_label,omitempty"` + SampleLimit int64 `protobuf:"varint,2,opt,name=sample_limit,json=sampleLimit,proto3" json:"sample_limit,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetDashboardDiagnosticsRequest) Reset() { + *x = GetDashboardDiagnosticsRequest{} + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[155] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetDashboardDiagnosticsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetDashboardDiagnosticsRequest) ProtoMessage() {} + +func (x *GetDashboardDiagnosticsRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[155] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetDashboardDiagnosticsRequest.ProtoReflect.Descriptor instead. +func (*GetDashboardDiagnosticsRequest) Descriptor() ([]byte, []int) { + return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{155} +} + +func (x *GetDashboardDiagnosticsRequest) GetWindowLabel() string { + if x != nil { + return x.WindowLabel + } + return "" +} + +func (x *GetDashboardDiagnosticsRequest) GetSampleLimit() int64 { + if x != nil { + return x.SampleLimit + } + return 0 +} + +type DashboardDiagnosticsOutcomeSummary struct { + state protoimpl.MessageState `protogen:"open.v1"` + Label string `protobuf:"bytes,1,opt,name=label,proto3" json:"label,omitempty"` + SinceUnixMillis int64 `protobuf:"varint,2,opt,name=since_unix_millis,json=sinceUnixMillis,proto3" json:"since_unix_millis,omitempty"` + Requests int64 `protobuf:"varint,3,opt,name=requests,proto3" json:"requests,omitempty"` + Success int64 `protobuf:"varint,4,opt,name=success,proto3" json:"success,omitempty"` + ClientError int64 `protobuf:"varint,5,opt,name=client_error,json=clientError,proto3" json:"client_error,omitempty"` + ServerError int64 `protobuf:"varint,6,opt,name=server_error,json=serverError,proto3" json:"server_error,omitempty"` + NonSuccess int64 `protobuf:"varint,7,opt,name=non_success,json=nonSuccess,proto3" json:"non_success,omitempty"` + ProxyFailure int64 `protobuf:"varint,8,opt,name=proxy_failure,json=proxyFailure,proto3" json:"proxy_failure,omitempty"` + AvgDurationMs int64 `protobuf:"varint,9,opt,name=avg_duration_ms,json=avgDurationMs,proto3" json:"avg_duration_ms,omitempty"` + MaxDurationMs int64 `protobuf:"varint,10,opt,name=max_duration_ms,json=maxDurationMs,proto3" json:"max_duration_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DashboardDiagnosticsOutcomeSummary) Reset() { + *x = DashboardDiagnosticsOutcomeSummary{} + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[156] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DashboardDiagnosticsOutcomeSummary) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DashboardDiagnosticsOutcomeSummary) ProtoMessage() {} + +func (x *DashboardDiagnosticsOutcomeSummary) ProtoReflect() protoreflect.Message { + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[156] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DashboardDiagnosticsOutcomeSummary.ProtoReflect.Descriptor instead. +func (*DashboardDiagnosticsOutcomeSummary) Descriptor() ([]byte, []int) { + return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{156} +} + +func (x *DashboardDiagnosticsOutcomeSummary) GetLabel() string { + if x != nil { + return x.Label + } + return "" +} + +func (x *DashboardDiagnosticsOutcomeSummary) GetSinceUnixMillis() int64 { + if x != nil { + return x.SinceUnixMillis + } + return 0 +} + +func (x *DashboardDiagnosticsOutcomeSummary) GetRequests() int64 { + if x != nil { + return x.Requests + } + return 0 +} + +func (x *DashboardDiagnosticsOutcomeSummary) GetSuccess() int64 { + if x != nil { + return x.Success + } + return 0 +} + +func (x *DashboardDiagnosticsOutcomeSummary) GetClientError() int64 { + if x != nil { + return x.ClientError + } + return 0 +} + +func (x *DashboardDiagnosticsOutcomeSummary) GetServerError() int64 { + if x != nil { + return x.ServerError + } + return 0 +} + +func (x *DashboardDiagnosticsOutcomeSummary) GetNonSuccess() int64 { + if x != nil { + return x.NonSuccess + } + return 0 +} + +func (x *DashboardDiagnosticsOutcomeSummary) GetProxyFailure() int64 { + if x != nil { + return x.ProxyFailure + } + return 0 +} + +func (x *DashboardDiagnosticsOutcomeSummary) GetAvgDurationMs() int64 { + if x != nil { + return x.AvgDurationMs + } + return 0 +} + +func (x *DashboardDiagnosticsOutcomeSummary) GetMaxDurationMs() int64 { + if x != nil { + return x.MaxDurationMs + } + return 0 +} + +type DashboardStatusCodeSummary struct { + state protoimpl.MessageState `protogen:"open.v1"` + StatusCode int64 `protobuf:"varint,1,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"` + Requests int64 `protobuf:"varint,2,opt,name=requests,proto3" json:"requests,omitempty"` + Success int64 `protobuf:"varint,3,opt,name=success,proto3" json:"success,omitempty"` + ClientError int64 `protobuf:"varint,4,opt,name=client_error,json=clientError,proto3" json:"client_error,omitempty"` + ServerError int64 `protobuf:"varint,5,opt,name=server_error,json=serverError,proto3" json:"server_error,omitempty"` + ProxyFailure int64 `protobuf:"varint,6,opt,name=proxy_failure,json=proxyFailure,proto3" json:"proxy_failure,omitempty"` + AvgDurationMs int64 `protobuf:"varint,7,opt,name=avg_duration_ms,json=avgDurationMs,proto3" json:"avg_duration_ms,omitempty"` + RequestBytes uint64 `protobuf:"varint,8,opt,name=request_bytes,json=requestBytes,proto3" json:"request_bytes,omitempty"` + ResponseBytes uint64 `protobuf:"varint,9,opt,name=response_bytes,json=responseBytes,proto3" json:"response_bytes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DashboardStatusCodeSummary) Reset() { + *x = DashboardStatusCodeSummary{} + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[157] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DashboardStatusCodeSummary) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DashboardStatusCodeSummary) ProtoMessage() {} + +func (x *DashboardStatusCodeSummary) ProtoReflect() protoreflect.Message { + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[157] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DashboardStatusCodeSummary.ProtoReflect.Descriptor instead. +func (*DashboardStatusCodeSummary) Descriptor() ([]byte, []int) { + return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{157} +} + +func (x *DashboardStatusCodeSummary) GetStatusCode() int64 { + if x != nil { + return x.StatusCode + } + return 0 +} + +func (x *DashboardStatusCodeSummary) GetRequests() int64 { + if x != nil { + return x.Requests + } + return 0 +} + +func (x *DashboardStatusCodeSummary) GetSuccess() int64 { + if x != nil { + return x.Success + } + return 0 +} + +func (x *DashboardStatusCodeSummary) GetClientError() int64 { + if x != nil { + return x.ClientError + } + return 0 +} + +func (x *DashboardStatusCodeSummary) GetServerError() int64 { + if x != nil { + return x.ServerError + } + return 0 +} + +func (x *DashboardStatusCodeSummary) GetProxyFailure() int64 { + if x != nil { + return x.ProxyFailure + } + return 0 +} + +func (x *DashboardStatusCodeSummary) GetAvgDurationMs() int64 { + if x != nil { + return x.AvgDurationMs + } + return 0 +} + +func (x *DashboardStatusCodeSummary) GetRequestBytes() uint64 { + if x != nil { + return x.RequestBytes + } + return 0 +} + +func (x *DashboardStatusCodeSummary) GetResponseBytes() uint64 { + if x != nil { + return x.ResponseBytes + } + return 0 +} + +type DashboardDiagnosticsSample struct { + state protoimpl.MessageState `protogen:"open.v1"` + OccurredAtUnixMillis int64 `protobuf:"varint,1,opt,name=occurred_at_unix_millis,json=occurredAtUnixMillis,proto3" json:"occurred_at_unix_millis,omitempty"` + Method string `protobuf:"bytes,2,opt,name=method,proto3" json:"method,omitempty"` + Host string `protobuf:"bytes,3,opt,name=host,proto3" json:"host,omitempty"` + PathPrefix string `protobuf:"bytes,4,opt,name=path_prefix,json=pathPrefix,proto3" json:"path_prefix,omitempty"` + StatusCode int64 `protobuf:"varint,5,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"` + ErrorKind string `protobuf:"bytes,6,opt,name=error_kind,json=errorKind,proto3" json:"error_kind,omitempty"` + ListenerLabel string `protobuf:"bytes,7,opt,name=listener_label,json=listenerLabel,proto3" json:"listener_label,omitempty"` + RouteLabel string `protobuf:"bytes,8,opt,name=route_label,json=routeLabel,proto3" json:"route_label,omitempty"` + RouteTargetLabel string `protobuf:"bytes,9,opt,name=route_target_label,json=routeTargetLabel,proto3" json:"route_target_label,omitempty"` + AgentLabel string `protobuf:"bytes,10,opt,name=agent_label,json=agentLabel,proto3" json:"agent_label,omitempty"` + DurationMs int64 `protobuf:"varint,11,opt,name=duration_ms,json=durationMs,proto3" json:"duration_ms,omitempty"` + RequestBytes uint64 `protobuf:"varint,12,opt,name=request_bytes,json=requestBytes,proto3" json:"request_bytes,omitempty"` + ResponseBytes uint64 `protobuf:"varint,13,opt,name=response_bytes,json=responseBytes,proto3" json:"response_bytes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DashboardDiagnosticsSample) Reset() { + *x = DashboardDiagnosticsSample{} + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[158] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DashboardDiagnosticsSample) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DashboardDiagnosticsSample) ProtoMessage() {} + +func (x *DashboardDiagnosticsSample) ProtoReflect() protoreflect.Message { + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[158] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DashboardDiagnosticsSample.ProtoReflect.Descriptor instead. +func (*DashboardDiagnosticsSample) Descriptor() ([]byte, []int) { + return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{158} +} + +func (x *DashboardDiagnosticsSample) GetOccurredAtUnixMillis() int64 { + if x != nil { + return x.OccurredAtUnixMillis + } + return 0 +} + +func (x *DashboardDiagnosticsSample) GetMethod() string { + if x != nil { + return x.Method + } + return "" +} + +func (x *DashboardDiagnosticsSample) GetHost() string { + if x != nil { + return x.Host + } + return "" +} + +func (x *DashboardDiagnosticsSample) GetPathPrefix() string { + if x != nil { + return x.PathPrefix + } + return "" +} + +func (x *DashboardDiagnosticsSample) GetStatusCode() int64 { + if x != nil { + return x.StatusCode + } + return 0 +} + +func (x *DashboardDiagnosticsSample) GetErrorKind() string { + if x != nil { + return x.ErrorKind + } + return "" +} + +func (x *DashboardDiagnosticsSample) GetListenerLabel() string { + if x != nil { + return x.ListenerLabel + } + return "" +} + +func (x *DashboardDiagnosticsSample) GetRouteLabel() string { + if x != nil { + return x.RouteLabel + } + return "" +} + +func (x *DashboardDiagnosticsSample) GetRouteTargetLabel() string { + if x != nil { + return x.RouteTargetLabel + } + return "" +} + +func (x *DashboardDiagnosticsSample) GetAgentLabel() string { + if x != nil { + return x.AgentLabel + } + return "" +} + +func (x *DashboardDiagnosticsSample) GetDurationMs() int64 { + if x != nil { + return x.DurationMs + } + return 0 +} + +func (x *DashboardDiagnosticsSample) GetRequestBytes() uint64 { + if x != nil { + return x.RequestBytes + } + return 0 +} + +func (x *DashboardDiagnosticsSample) GetResponseBytes() uint64 { + if x != nil { + return x.ResponseBytes + } + return 0 +} + +type GetDashboardDiagnosticsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Label string `protobuf:"bytes,1,opt,name=label,proto3" json:"label,omitempty"` + SinceUnixMillis int64 `protobuf:"varint,2,opt,name=since_unix_millis,json=sinceUnixMillis,proto3" json:"since_unix_millis,omitempty"` + GeneratedAtUnixMillis int64 `protobuf:"varint,3,opt,name=generated_at_unix_millis,json=generatedAtUnixMillis,proto3" json:"generated_at_unix_millis,omitempty"` + Outcome *DashboardDiagnosticsOutcomeSummary `protobuf:"bytes,4,opt,name=outcome,proto3" json:"outcome,omitempty"` + StatusCodes []*DashboardStatusCodeSummary `protobuf:"bytes,5,rep,name=status_codes,json=statusCodes,proto3" json:"status_codes,omitempty"` + ErrorKinds []*DashboardProxyDimensionSummary `protobuf:"bytes,6,rep,name=error_kinds,json=errorKinds,proto3" json:"error_kinds,omitempty"` + ProblemListeners []*DashboardProxyDimensionSummary `protobuf:"bytes,7,rep,name=problem_listeners,json=problemListeners,proto3" json:"problem_listeners,omitempty"` + ProblemRoutes []*DashboardProxyDimensionSummary `protobuf:"bytes,8,rep,name=problem_routes,json=problemRoutes,proto3" json:"problem_routes,omitempty"` + ProblemRouteTargets []*DashboardProxyDimensionSummary `protobuf:"bytes,9,rep,name=problem_route_targets,json=problemRouteTargets,proto3" json:"problem_route_targets,omitempty"` + ProblemAgents []*DashboardProxyDimensionSummary `protobuf:"bytes,10,rep,name=problem_agents,json=problemAgents,proto3" json:"problem_agents,omitempty"` + RecentSamples []*DashboardDiagnosticsSample `protobuf:"bytes,11,rep,name=recent_samples,json=recentSamples,proto3" json:"recent_samples,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetDashboardDiagnosticsResponse) Reset() { + *x = GetDashboardDiagnosticsResponse{} + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[159] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetDashboardDiagnosticsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetDashboardDiagnosticsResponse) ProtoMessage() {} + +func (x *GetDashboardDiagnosticsResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[159] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetDashboardDiagnosticsResponse.ProtoReflect.Descriptor instead. +func (*GetDashboardDiagnosticsResponse) Descriptor() ([]byte, []int) { + return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{159} +} + +func (x *GetDashboardDiagnosticsResponse) GetLabel() string { + if x != nil { + return x.Label + } + return "" +} + +func (x *GetDashboardDiagnosticsResponse) GetSinceUnixMillis() int64 { + if x != nil { + return x.SinceUnixMillis + } + return 0 +} + +func (x *GetDashboardDiagnosticsResponse) GetGeneratedAtUnixMillis() int64 { + if x != nil { + return x.GeneratedAtUnixMillis + } + return 0 +} + +func (x *GetDashboardDiagnosticsResponse) GetOutcome() *DashboardDiagnosticsOutcomeSummary { + if x != nil { + return x.Outcome + } + return nil +} + +func (x *GetDashboardDiagnosticsResponse) GetStatusCodes() []*DashboardStatusCodeSummary { + if x != nil { + return x.StatusCodes + } + return nil +} + +func (x *GetDashboardDiagnosticsResponse) GetErrorKinds() []*DashboardProxyDimensionSummary { + if x != nil { + return x.ErrorKinds + } + return nil +} + +func (x *GetDashboardDiagnosticsResponse) GetProblemListeners() []*DashboardProxyDimensionSummary { + if x != nil { + return x.ProblemListeners + } + return nil +} + +func (x *GetDashboardDiagnosticsResponse) GetProblemRoutes() []*DashboardProxyDimensionSummary { + if x != nil { + return x.ProblemRoutes + } + return nil +} + +func (x *GetDashboardDiagnosticsResponse) GetProblemRouteTargets() []*DashboardProxyDimensionSummary { + if x != nil { + return x.ProblemRouteTargets + } + return nil +} + +func (x *GetDashboardDiagnosticsResponse) GetProblemAgents() []*DashboardProxyDimensionSummary { + if x != nil { + return x.ProblemAgents + } + return nil +} + +func (x *GetDashboardDiagnosticsResponse) GetRecentSamples() []*DashboardDiagnosticsSample { + if x != nil { + return x.RecentSamples + } + return nil +} + type TrafficTraceSettings struct { state protoimpl.MessageState `protogen:"open.v1"` Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` @@ -14259,7 +14799,7 @@ type TrafficTraceSettings struct { func (x *TrafficTraceSettings) Reset() { *x = TrafficTraceSettings{} - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[155] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[160] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14271,7 +14811,7 @@ func (x *TrafficTraceSettings) String() string { func (*TrafficTraceSettings) ProtoMessage() {} func (x *TrafficTraceSettings) ProtoReflect() protoreflect.Message { - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[155] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[160] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14284,7 +14824,7 @@ func (x *TrafficTraceSettings) ProtoReflect() protoreflect.Message { // Deprecated: Use TrafficTraceSettings.ProtoReflect.Descriptor instead. func (*TrafficTraceSettings) Descriptor() ([]byte, []int) { - return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{155} + return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{160} } func (x *TrafficTraceSettings) GetEnabled() bool { @@ -14337,7 +14877,7 @@ type GetTrafficTraceSettingsRequest struct { func (x *GetTrafficTraceSettingsRequest) Reset() { *x = GetTrafficTraceSettingsRequest{} - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[156] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[161] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14349,7 +14889,7 @@ func (x *GetTrafficTraceSettingsRequest) String() string { func (*GetTrafficTraceSettingsRequest) ProtoMessage() {} func (x *GetTrafficTraceSettingsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[156] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[161] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14362,7 +14902,7 @@ func (x *GetTrafficTraceSettingsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetTrafficTraceSettingsRequest.ProtoReflect.Descriptor instead. func (*GetTrafficTraceSettingsRequest) Descriptor() ([]byte, []int) { - return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{156} + return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{161} } type GetTrafficTraceSettingsResponse struct { @@ -14374,7 +14914,7 @@ type GetTrafficTraceSettingsResponse struct { func (x *GetTrafficTraceSettingsResponse) Reset() { *x = GetTrafficTraceSettingsResponse{} - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[157] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[162] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14386,7 +14926,7 @@ func (x *GetTrafficTraceSettingsResponse) String() string { func (*GetTrafficTraceSettingsResponse) ProtoMessage() {} func (x *GetTrafficTraceSettingsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[157] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[162] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14399,7 +14939,7 @@ func (x *GetTrafficTraceSettingsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetTrafficTraceSettingsResponse.ProtoReflect.Descriptor instead. func (*GetTrafficTraceSettingsResponse) Descriptor() ([]byte, []int) { - return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{157} + return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{162} } func (x *GetTrafficTraceSettingsResponse) GetSettings() *TrafficTraceSettings { @@ -14419,7 +14959,7 @@ type SetTrafficTraceSettingsRequest struct { func (x *SetTrafficTraceSettingsRequest) Reset() { *x = SetTrafficTraceSettingsRequest{} - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[158] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[163] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14431,7 +14971,7 @@ func (x *SetTrafficTraceSettingsRequest) String() string { func (*SetTrafficTraceSettingsRequest) ProtoMessage() {} func (x *SetTrafficTraceSettingsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[158] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[163] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14444,7 +14984,7 @@ func (x *SetTrafficTraceSettingsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetTrafficTraceSettingsRequest.ProtoReflect.Descriptor instead. func (*SetTrafficTraceSettingsRequest) Descriptor() ([]byte, []int) { - return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{158} + return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{163} } func (x *SetTrafficTraceSettingsRequest) GetEnabled() bool { @@ -14470,7 +15010,7 @@ type SetTrafficTraceSettingsResponse struct { func (x *SetTrafficTraceSettingsResponse) Reset() { *x = SetTrafficTraceSettingsResponse{} - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[159] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[164] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14482,7 +15022,7 @@ func (x *SetTrafficTraceSettingsResponse) String() string { func (*SetTrafficTraceSettingsResponse) ProtoMessage() {} func (x *SetTrafficTraceSettingsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[159] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[164] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14495,7 +15035,7 @@ func (x *SetTrafficTraceSettingsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetTrafficTraceSettingsResponse.ProtoReflect.Descriptor instead. func (*SetTrafficTraceSettingsResponse) Descriptor() ([]byte, []int) { - return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{159} + return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{164} } func (x *SetTrafficTraceSettingsResponse) GetSettings() *TrafficTraceSettings { @@ -14515,7 +15055,7 @@ type StreamTrafficTraceEventsRequest struct { func (x *StreamTrafficTraceEventsRequest) Reset() { *x = StreamTrafficTraceEventsRequest{} - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[160] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[165] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14527,7 +15067,7 @@ func (x *StreamTrafficTraceEventsRequest) String() string { func (*StreamTrafficTraceEventsRequest) ProtoMessage() {} func (x *StreamTrafficTraceEventsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[160] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[165] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14540,7 +15080,7 @@ func (x *StreamTrafficTraceEventsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use StreamTrafficTraceEventsRequest.ProtoReflect.Descriptor instead. func (*StreamTrafficTraceEventsRequest) Descriptor() ([]byte, []int) { - return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{160} + return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{165} } func (x *StreamTrafficTraceEventsRequest) GetReplayRecent() bool { @@ -14614,7 +15154,7 @@ type TrafficTraceEvent struct { func (x *TrafficTraceEvent) Reset() { *x = TrafficTraceEvent{} - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[161] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[166] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -14626,7 +15166,7 @@ func (x *TrafficTraceEvent) String() string { func (*TrafficTraceEvent) ProtoMessage() {} func (x *TrafficTraceEvent) ProtoReflect() protoreflect.Message { - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[161] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[166] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -14639,7 +15179,7 @@ func (x *TrafficTraceEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use TrafficTraceEvent.ProtoReflect.Descriptor instead. func (*TrafficTraceEvent) Descriptor() ([]byte, []int) { - return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{161} + return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{166} } func (x *TrafficTraceEvent) GetSequence() uint64 { @@ -14996,7 +15536,7 @@ type StreamTrafficTraceEventsResponse struct { func (x *StreamTrafficTraceEventsResponse) Reset() { *x = StreamTrafficTraceEventsResponse{} - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[162] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[167] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15008,7 +15548,7 @@ func (x *StreamTrafficTraceEventsResponse) String() string { func (*StreamTrafficTraceEventsResponse) ProtoMessage() {} func (x *StreamTrafficTraceEventsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[162] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[167] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15021,7 +15561,7 @@ func (x *StreamTrafficTraceEventsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use StreamTrafficTraceEventsResponse.ProtoReflect.Descriptor instead. func (*StreamTrafficTraceEventsResponse) Descriptor() ([]byte, []int) { - return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{162} + return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{167} } func (x *StreamTrafficTraceEventsResponse) GetSettings() *TrafficTraceSettings { @@ -15053,7 +15593,7 @@ type GetSetupStateRequest struct { func (x *GetSetupStateRequest) Reset() { *x = GetSetupStateRequest{} - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[163] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[168] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15065,7 +15605,7 @@ func (x *GetSetupStateRequest) String() string { func (*GetSetupStateRequest) ProtoMessage() {} func (x *GetSetupStateRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[163] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[168] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15078,7 +15618,7 @@ func (x *GetSetupStateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSetupStateRequest.ProtoReflect.Descriptor instead. func (*GetSetupStateRequest) Descriptor() ([]byte, []int) { - return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{163} + return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{168} } type GetSetupStateResponse struct { @@ -15093,7 +15633,7 @@ type GetSetupStateResponse struct { func (x *GetSetupStateResponse) Reset() { *x = GetSetupStateResponse{} - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[164] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[169] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15105,7 +15645,7 @@ func (x *GetSetupStateResponse) String() string { func (*GetSetupStateResponse) ProtoMessage() {} func (x *GetSetupStateResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[164] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[169] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15118,7 +15658,7 @@ func (x *GetSetupStateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSetupStateResponse.ProtoReflect.Descriptor instead. func (*GetSetupStateResponse) Descriptor() ([]byte, []int) { - return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{164} + return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{169} } func (x *GetSetupStateResponse) GetSetupRequired() bool { @@ -15160,7 +15700,7 @@ type SetupAdminRequest struct { func (x *SetupAdminRequest) Reset() { *x = SetupAdminRequest{} - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[165] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[170] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15172,7 +15712,7 @@ func (x *SetupAdminRequest) String() string { func (*SetupAdminRequest) ProtoMessage() {} func (x *SetupAdminRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[165] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[170] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15185,7 +15725,7 @@ func (x *SetupAdminRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetupAdminRequest.ProtoReflect.Descriptor instead. func (*SetupAdminRequest) Descriptor() ([]byte, []int) { - return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{165} + return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{170} } func (x *SetupAdminRequest) GetUsername() string { @@ -15218,7 +15758,7 @@ type SetupAdminResponse struct { func (x *SetupAdminResponse) Reset() { *x = SetupAdminResponse{} - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[166] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[171] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15230,7 +15770,7 @@ func (x *SetupAdminResponse) String() string { func (*SetupAdminResponse) ProtoMessage() {} func (x *SetupAdminResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[166] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[171] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15243,7 +15783,7 @@ func (x *SetupAdminResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetupAdminResponse.ProtoReflect.Descriptor instead. func (*SetupAdminResponse) Descriptor() ([]byte, []int) { - return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{166} + return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{171} } func (x *SetupAdminResponse) GetUser() *User { @@ -15263,7 +15803,7 @@ type LoginRequest struct { func (x *LoginRequest) Reset() { *x = LoginRequest{} - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[167] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[172] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15275,7 +15815,7 @@ func (x *LoginRequest) String() string { func (*LoginRequest) ProtoMessage() {} func (x *LoginRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[167] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[172] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15288,7 +15828,7 @@ func (x *LoginRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LoginRequest.ProtoReflect.Descriptor instead. func (*LoginRequest) Descriptor() ([]byte, []int) { - return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{167} + return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{172} } func (x *LoginRequest) GetUsername() string { @@ -15314,7 +15854,7 @@ type LoginResponse struct { func (x *LoginResponse) Reset() { *x = LoginResponse{} - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[168] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[173] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15326,7 +15866,7 @@ func (x *LoginResponse) String() string { func (*LoginResponse) ProtoMessage() {} func (x *LoginResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[168] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[173] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15339,7 +15879,7 @@ func (x *LoginResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LoginResponse.ProtoReflect.Descriptor instead. func (*LoginResponse) Descriptor() ([]byte, []int) { - return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{168} + return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{173} } func (x *LoginResponse) GetUser() *User { @@ -15357,7 +15897,7 @@ type LogoutRequest struct { func (x *LogoutRequest) Reset() { *x = LogoutRequest{} - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[169] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[174] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15369,7 +15909,7 @@ func (x *LogoutRequest) String() string { func (*LogoutRequest) ProtoMessage() {} func (x *LogoutRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[169] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[174] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15382,7 +15922,7 @@ func (x *LogoutRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LogoutRequest.ProtoReflect.Descriptor instead. func (*LogoutRequest) Descriptor() ([]byte, []int) { - return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{169} + return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{174} } type LogoutResponse struct { @@ -15393,7 +15933,7 @@ type LogoutResponse struct { func (x *LogoutResponse) Reset() { *x = LogoutResponse{} - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[170] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[175] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15405,7 +15945,7 @@ func (x *LogoutResponse) String() string { func (*LogoutResponse) ProtoMessage() {} func (x *LogoutResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[170] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[175] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15418,7 +15958,7 @@ func (x *LogoutResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LogoutResponse.ProtoReflect.Descriptor instead. func (*LogoutResponse) Descriptor() ([]byte, []int) { - return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{170} + return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{175} } type GetCurrentUserRequest struct { @@ -15429,7 +15969,7 @@ type GetCurrentUserRequest struct { func (x *GetCurrentUserRequest) Reset() { *x = GetCurrentUserRequest{} - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[171] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[176] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15441,7 +15981,7 @@ func (x *GetCurrentUserRequest) String() string { func (*GetCurrentUserRequest) ProtoMessage() {} func (x *GetCurrentUserRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[171] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[176] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15454,7 +15994,7 @@ func (x *GetCurrentUserRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetCurrentUserRequest.ProtoReflect.Descriptor instead. func (*GetCurrentUserRequest) Descriptor() ([]byte, []int) { - return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{171} + return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{176} } type GetCurrentUserResponse struct { @@ -15466,7 +16006,7 @@ type GetCurrentUserResponse struct { func (x *GetCurrentUserResponse) Reset() { *x = GetCurrentUserResponse{} - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[172] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[177] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15478,7 +16018,7 @@ func (x *GetCurrentUserResponse) String() string { func (*GetCurrentUserResponse) ProtoMessage() {} func (x *GetCurrentUserResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[172] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[177] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15491,7 +16031,7 @@ func (x *GetCurrentUserResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetCurrentUserResponse.ProtoReflect.Descriptor instead. func (*GetCurrentUserResponse) Descriptor() ([]byte, []int) { - return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{172} + return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{177} } func (x *GetCurrentUserResponse) GetUser() *User { @@ -15509,7 +16049,7 @@ type StartProxyRequest struct { func (x *StartProxyRequest) Reset() { *x = StartProxyRequest{} - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[173] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[178] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15521,7 +16061,7 @@ func (x *StartProxyRequest) String() string { func (*StartProxyRequest) ProtoMessage() {} func (x *StartProxyRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[173] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[178] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15534,7 +16074,7 @@ func (x *StartProxyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use StartProxyRequest.ProtoReflect.Descriptor instead. func (*StartProxyRequest) Descriptor() ([]byte, []int) { - return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{173} + return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{178} } type StartProxyResponse struct { @@ -15546,7 +16086,7 @@ type StartProxyResponse struct { func (x *StartProxyResponse) Reset() { *x = StartProxyResponse{} - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[174] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[179] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15558,7 +16098,7 @@ func (x *StartProxyResponse) String() string { func (*StartProxyResponse) ProtoMessage() {} func (x *StartProxyResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[174] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[179] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15571,7 +16111,7 @@ func (x *StartProxyResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use StartProxyResponse.ProtoReflect.Descriptor instead. func (*StartProxyResponse) Descriptor() ([]byte, []int) { - return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{174} + return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{179} } func (x *StartProxyResponse) GetProxy() *ProxyStatus { @@ -15589,7 +16129,7 @@ type StopProxyRequest struct { func (x *StopProxyRequest) Reset() { *x = StopProxyRequest{} - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[175] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[180] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15601,7 +16141,7 @@ func (x *StopProxyRequest) String() string { func (*StopProxyRequest) ProtoMessage() {} func (x *StopProxyRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[175] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[180] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15614,7 +16154,7 @@ func (x *StopProxyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use StopProxyRequest.ProtoReflect.Descriptor instead. func (*StopProxyRequest) Descriptor() ([]byte, []int) { - return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{175} + return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{180} } type StopProxyResponse struct { @@ -15626,7 +16166,7 @@ type StopProxyResponse struct { func (x *StopProxyResponse) Reset() { *x = StopProxyResponse{} - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[176] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[181] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -15638,7 +16178,7 @@ func (x *StopProxyResponse) String() string { func (*StopProxyResponse) ProtoMessage() {} func (x *StopProxyResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_p2pstream_v1_management_proto_msgTypes[176] + mi := &file_proto_p2pstream_v1_management_proto_msgTypes[181] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -15651,7 +16191,7 @@ func (x *StopProxyResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use StopProxyResponse.ProtoReflect.Descriptor instead. func (*StopProxyResponse) Descriptor() ([]byte, []int) { - return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{176} + return file_proto_p2pstream_v1_management_proto_rawDescGZIP(), []int{181} } func (x *StopProxyResponse) GetProxy() *ProxyStatus { @@ -16792,7 +17332,69 @@ const file_proto_p2pstream_v1_management_proto_rawDesc = "" + "\x13management_security\x18\r \x01(\v2 .p2pstream.v1.ManagementSecurityR\x12managementSecurity\x12V\n" + "\x16agent_uptime_summaries\x18\x0e \x03(\v2 .p2pstream.v1.AgentUptimeSummaryR\x14agentUptimeSummaries\x12^\n" + "\x18recent_agent_connections\x18\x0f \x03(\v2$.p2pstream.v1.AgentConnectionSessionR\x16recentAgentConnections\x12X\n" + - "\x11top_route_targets\x18\x10 \x03(\v2,.p2pstream.v1.DashboardProxyDimensionSummaryR\x0ftopRouteTargetsJ\x04\b\a\x10\bR\ftop_backends\"\x95\x02\n" + + "\x11top_route_targets\x18\x10 \x03(\v2,.p2pstream.v1.DashboardProxyDimensionSummaryR\x0ftopRouteTargetsJ\x04\b\a\x10\bR\ftop_backends\"f\n" + + "\x1eGetDashboardDiagnosticsRequest\x12!\n" + + "\fwindow_label\x18\x01 \x01(\tR\vwindowLabel\x12!\n" + + "\fsample_limit\x18\x02 \x01(\x03R\vsampleLimit\"\xf8\x02\n" + + "\"DashboardDiagnosticsOutcomeSummary\x12\x14\n" + + "\x05label\x18\x01 \x01(\tR\x05label\x12*\n" + + "\x11since_unix_millis\x18\x02 \x01(\x03R\x0fsinceUnixMillis\x12\x1a\n" + + "\brequests\x18\x03 \x01(\x03R\brequests\x12\x18\n" + + "\asuccess\x18\x04 \x01(\x03R\asuccess\x12!\n" + + "\fclient_error\x18\x05 \x01(\x03R\vclientError\x12!\n" + + "\fserver_error\x18\x06 \x01(\x03R\vserverError\x12\x1f\n" + + "\vnon_success\x18\a \x01(\x03R\n" + + "nonSuccess\x12#\n" + + "\rproxy_failure\x18\b \x01(\x03R\fproxyFailure\x12&\n" + + "\x0favg_duration_ms\x18\t \x01(\x03R\ravgDurationMs\x12&\n" + + "\x0fmax_duration_ms\x18\n" + + " \x01(\x03R\rmaxDurationMs\"\xd2\x02\n" + + "\x1aDashboardStatusCodeSummary\x12\x1f\n" + + "\vstatus_code\x18\x01 \x01(\x03R\n" + + "statusCode\x12\x1a\n" + + "\brequests\x18\x02 \x01(\x03R\brequests\x12\x18\n" + + "\asuccess\x18\x03 \x01(\x03R\asuccess\x12!\n" + + "\fclient_error\x18\x04 \x01(\x03R\vclientError\x12!\n" + + "\fserver_error\x18\x05 \x01(\x03R\vserverError\x12#\n" + + "\rproxy_failure\x18\x06 \x01(\x03R\fproxyFailure\x12&\n" + + "\x0favg_duration_ms\x18\a \x01(\x03R\ravgDurationMs\x12#\n" + + "\rrequest_bytes\x18\b \x01(\x04R\frequestBytes\x12%\n" + + "\x0eresponse_bytes\x18\t \x01(\x04R\rresponseBytes\"\xe4\x03\n" + + "\x1aDashboardDiagnosticsSample\x125\n" + + "\x17occurred_at_unix_millis\x18\x01 \x01(\x03R\x14occurredAtUnixMillis\x12\x16\n" + + "\x06method\x18\x02 \x01(\tR\x06method\x12\x12\n" + + "\x04host\x18\x03 \x01(\tR\x04host\x12\x1f\n" + + "\vpath_prefix\x18\x04 \x01(\tR\n" + + "pathPrefix\x12\x1f\n" + + "\vstatus_code\x18\x05 \x01(\x03R\n" + + "statusCode\x12\x1d\n" + + "\n" + + "error_kind\x18\x06 \x01(\tR\terrorKind\x12%\n" + + "\x0elistener_label\x18\a \x01(\tR\rlistenerLabel\x12\x1f\n" + + "\vroute_label\x18\b \x01(\tR\n" + + "routeLabel\x12,\n" + + "\x12route_target_label\x18\t \x01(\tR\x10routeTargetLabel\x12\x1f\n" + + "\vagent_label\x18\n" + + " \x01(\tR\n" + + "agentLabel\x12\x1f\n" + + "\vduration_ms\x18\v \x01(\x03R\n" + + "durationMs\x12#\n" + + "\rrequest_bytes\x18\f \x01(\x04R\frequestBytes\x12%\n" + + "\x0eresponse_bytes\x18\r \x01(\x04R\rresponseBytes\"\xbc\x06\n" + + "\x1fGetDashboardDiagnosticsResponse\x12\x14\n" + + "\x05label\x18\x01 \x01(\tR\x05label\x12*\n" + + "\x11since_unix_millis\x18\x02 \x01(\x03R\x0fsinceUnixMillis\x127\n" + + "\x18generated_at_unix_millis\x18\x03 \x01(\x03R\x15generatedAtUnixMillis\x12J\n" + + "\aoutcome\x18\x04 \x01(\v20.p2pstream.v1.DashboardDiagnosticsOutcomeSummaryR\aoutcome\x12K\n" + + "\fstatus_codes\x18\x05 \x03(\v2(.p2pstream.v1.DashboardStatusCodeSummaryR\vstatusCodes\x12M\n" + + "\verror_kinds\x18\x06 \x03(\v2,.p2pstream.v1.DashboardProxyDimensionSummaryR\n" + + "errorKinds\x12Y\n" + + "\x11problem_listeners\x18\a \x03(\v2,.p2pstream.v1.DashboardProxyDimensionSummaryR\x10problemListeners\x12S\n" + + "\x0eproblem_routes\x18\b \x03(\v2,.p2pstream.v1.DashboardProxyDimensionSummaryR\rproblemRoutes\x12`\n" + + "\x15problem_route_targets\x18\t \x03(\v2,.p2pstream.v1.DashboardProxyDimensionSummaryR\x13problemRouteTargets\x12S\n" + + "\x0eproblem_agents\x18\n" + + " \x03(\v2,.p2pstream.v1.DashboardProxyDimensionSummaryR\rproblemAgents\x12O\n" + + "\x0erecent_samples\x18\v \x03(\v2(.p2pstream.v1.DashboardDiagnosticsSampleR\rrecentSamples\"\x95\x02\n" + "\x14TrafficTraceSettings\x12\x18\n" + "\aenabled\x18\x01 \x01(\bR\aenabled\x125\n" + "\x05level\x18\x02 \x01(\x0e2\x1f.p2pstream.v1.TrafficTraceLevelR\x05level\x123\n" + @@ -17121,11 +17723,12 @@ const file_proto_p2pstream_v1_management_proto_rawDesc = "" + "\x1fDASHBOARD_PROXY_DIMENSION_AGENT\x10\x04\x12(\n" + "$DASHBOARD_PROXY_DIMENSION_ERROR_KIND\x10\x05\x12*\n" + "&DASHBOARD_PROXY_DIMENSION_STATUS_CLASS\x10\x06\x12*\n" + - "&DASHBOARD_PROXY_DIMENSION_ROUTE_TARGET\x10\a2\xd0;\n" + + "&DASHBOARD_PROXY_DIMENSION_ROUTE_TARGET\x10\a2\xca<\n" + "\x16AgentManagementService\x12R\n" + "\vReportStats\x12\x1f.p2pstream.v1.AgentStatsRequest\x1a .p2pstream.v1.AgentStatsResponse\"\x00\x12N\n" + "\tGetStatus\x12\x1e.p2pstream.v1.GetStatusRequest\x1a\x1f.p2pstream.v1.GetStatusResponse\"\x00\x12W\n" + "\fGetDashboard\x12!.p2pstream.v1.GetDashboardRequest\x1a\".p2pstream.v1.GetDashboardResponse\"\x00\x12x\n" + + "\x17GetDashboardDiagnostics\x12,.p2pstream.v1.GetDashboardDiagnosticsRequest\x1a-.p2pstream.v1.GetDashboardDiagnosticsResponse\"\x00\x12x\n" + "\x17GetTrafficTraceSettings\x12,.p2pstream.v1.GetTrafficTraceSettingsRequest\x1a-.p2pstream.v1.GetTrafficTraceSettingsResponse\"\x00\x12x\n" + "\x17SetTrafficTraceSettings\x12,.p2pstream.v1.SetTrafficTraceSettingsRequest\x1a-.p2pstream.v1.SetTrafficTraceSettingsResponse\"\x00\x12}\n" + "\x18StreamTrafficTraceEvents\x12-.p2pstream.v1.StreamTrafficTraceEventsRequest\x1a..p2pstream.v1.StreamTrafficTraceEventsResponse\"\x000\x01\x12Z\n" + @@ -17206,7 +17809,7 @@ func file_proto_p2pstream_v1_management_proto_rawDescGZIP() []byte { } var file_proto_p2pstream_v1_management_proto_enumTypes = make([]protoimpl.EnumInfo, 35) -var file_proto_p2pstream_v1_management_proto_msgTypes = make([]protoimpl.MessageInfo, 185) +var file_proto_p2pstream_v1_management_proto_msgTypes = make([]protoimpl.MessageInfo, 190) var file_proto_p2pstream_v1_management_proto_goTypes = []any{ (UserRole)(0), // 0: p2pstream.v1.UserRole (ProxyState)(0), // 1: p2pstream.v1.ProxyState @@ -17398,36 +18001,41 @@ var file_proto_p2pstream_v1_management_proto_goTypes = []any{ (*AgentUptimeSummary)(nil), // 187: p2pstream.v1.AgentUptimeSummary (*AgentConnectionSession)(nil), // 188: p2pstream.v1.AgentConnectionSession (*GetDashboardResponse)(nil), // 189: p2pstream.v1.GetDashboardResponse - (*TrafficTraceSettings)(nil), // 190: p2pstream.v1.TrafficTraceSettings - (*GetTrafficTraceSettingsRequest)(nil), // 191: p2pstream.v1.GetTrafficTraceSettingsRequest - (*GetTrafficTraceSettingsResponse)(nil), // 192: p2pstream.v1.GetTrafficTraceSettingsResponse - (*SetTrafficTraceSettingsRequest)(nil), // 193: p2pstream.v1.SetTrafficTraceSettingsRequest - (*SetTrafficTraceSettingsResponse)(nil), // 194: p2pstream.v1.SetTrafficTraceSettingsResponse - (*StreamTrafficTraceEventsRequest)(nil), // 195: p2pstream.v1.StreamTrafficTraceEventsRequest - (*TrafficTraceEvent)(nil), // 196: p2pstream.v1.TrafficTraceEvent - (*StreamTrafficTraceEventsResponse)(nil), // 197: p2pstream.v1.StreamTrafficTraceEventsResponse - (*GetSetupStateRequest)(nil), // 198: p2pstream.v1.GetSetupStateRequest - (*GetSetupStateResponse)(nil), // 199: p2pstream.v1.GetSetupStateResponse - (*SetupAdminRequest)(nil), // 200: p2pstream.v1.SetupAdminRequest - (*SetupAdminResponse)(nil), // 201: p2pstream.v1.SetupAdminResponse - (*LoginRequest)(nil), // 202: p2pstream.v1.LoginRequest - (*LoginResponse)(nil), // 203: p2pstream.v1.LoginResponse - (*LogoutRequest)(nil), // 204: p2pstream.v1.LogoutRequest - (*LogoutResponse)(nil), // 205: p2pstream.v1.LogoutResponse - (*GetCurrentUserRequest)(nil), // 206: p2pstream.v1.GetCurrentUserRequest - (*GetCurrentUserResponse)(nil), // 207: p2pstream.v1.GetCurrentUserResponse - (*StartProxyRequest)(nil), // 208: p2pstream.v1.StartProxyRequest - (*StartProxyResponse)(nil), // 209: p2pstream.v1.StartProxyResponse - (*StopProxyRequest)(nil), // 210: p2pstream.v1.StopProxyRequest - (*StopProxyResponse)(nil), // 211: p2pstream.v1.StopProxyResponse - nil, // 212: p2pstream.v1.Agent.LabelsEntry - nil, // 213: p2pstream.v1.PublicAgentSelector.MatchLabelsEntry - nil, // 214: p2pstream.v1.PublicRouteTargetHealthTrace.DebugAttributesEntry - nil, // 215: p2pstream.v1.CreateAgentRequest.LabelsEntry - nil, // 216: p2pstream.v1.UpdateAgentRequest.LabelsEntry - nil, // 217: p2pstream.v1.TrafficTraceEvent.RequestHeadersEntry - nil, // 218: p2pstream.v1.TrafficTraceEvent.ResponseHeadersEntry - nil, // 219: p2pstream.v1.TrafficTraceEvent.DebugAttributesEntry + (*GetDashboardDiagnosticsRequest)(nil), // 190: p2pstream.v1.GetDashboardDiagnosticsRequest + (*DashboardDiagnosticsOutcomeSummary)(nil), // 191: p2pstream.v1.DashboardDiagnosticsOutcomeSummary + (*DashboardStatusCodeSummary)(nil), // 192: p2pstream.v1.DashboardStatusCodeSummary + (*DashboardDiagnosticsSample)(nil), // 193: p2pstream.v1.DashboardDiagnosticsSample + (*GetDashboardDiagnosticsResponse)(nil), // 194: p2pstream.v1.GetDashboardDiagnosticsResponse + (*TrafficTraceSettings)(nil), // 195: p2pstream.v1.TrafficTraceSettings + (*GetTrafficTraceSettingsRequest)(nil), // 196: p2pstream.v1.GetTrafficTraceSettingsRequest + (*GetTrafficTraceSettingsResponse)(nil), // 197: p2pstream.v1.GetTrafficTraceSettingsResponse + (*SetTrafficTraceSettingsRequest)(nil), // 198: p2pstream.v1.SetTrafficTraceSettingsRequest + (*SetTrafficTraceSettingsResponse)(nil), // 199: p2pstream.v1.SetTrafficTraceSettingsResponse + (*StreamTrafficTraceEventsRequest)(nil), // 200: p2pstream.v1.StreamTrafficTraceEventsRequest + (*TrafficTraceEvent)(nil), // 201: p2pstream.v1.TrafficTraceEvent + (*StreamTrafficTraceEventsResponse)(nil), // 202: p2pstream.v1.StreamTrafficTraceEventsResponse + (*GetSetupStateRequest)(nil), // 203: p2pstream.v1.GetSetupStateRequest + (*GetSetupStateResponse)(nil), // 204: p2pstream.v1.GetSetupStateResponse + (*SetupAdminRequest)(nil), // 205: p2pstream.v1.SetupAdminRequest + (*SetupAdminResponse)(nil), // 206: p2pstream.v1.SetupAdminResponse + (*LoginRequest)(nil), // 207: p2pstream.v1.LoginRequest + (*LoginResponse)(nil), // 208: p2pstream.v1.LoginResponse + (*LogoutRequest)(nil), // 209: p2pstream.v1.LogoutRequest + (*LogoutResponse)(nil), // 210: p2pstream.v1.LogoutResponse + (*GetCurrentUserRequest)(nil), // 211: p2pstream.v1.GetCurrentUserRequest + (*GetCurrentUserResponse)(nil), // 212: p2pstream.v1.GetCurrentUserResponse + (*StartProxyRequest)(nil), // 213: p2pstream.v1.StartProxyRequest + (*StartProxyResponse)(nil), // 214: p2pstream.v1.StartProxyResponse + (*StopProxyRequest)(nil), // 215: p2pstream.v1.StopProxyRequest + (*StopProxyResponse)(nil), // 216: p2pstream.v1.StopProxyResponse + nil, // 217: p2pstream.v1.Agent.LabelsEntry + nil, // 218: p2pstream.v1.PublicAgentSelector.MatchLabelsEntry + nil, // 219: p2pstream.v1.PublicRouteTargetHealthTrace.DebugAttributesEntry + nil, // 220: p2pstream.v1.CreateAgentRequest.LabelsEntry + nil, // 221: p2pstream.v1.UpdateAgentRequest.LabelsEntry + nil, // 222: p2pstream.v1.TrafficTraceEvent.RequestHeadersEntry + nil, // 223: p2pstream.v1.TrafficTraceEvent.ResponseHeadersEntry + nil, // 224: p2pstream.v1.TrafficTraceEvent.DebugAttributesEntry } var file_proto_p2pstream_v1_management_proto_depIdxs = []int32{ 0, // 0: p2pstream.v1.User.role:type_name -> p2pstream.v1.UserRole @@ -17438,8 +18046,8 @@ var file_proto_p2pstream_v1_management_proto_depIdxs = []int32{ 8, // 5: p2pstream.v1.PublicRouteTargetHealthCheck.status:type_name -> p2pstream.v1.PublicRouteTargetHealthStatus 8, // 6: p2pstream.v1.PublicRouteTargetAgentHealth.status:type_name -> p2pstream.v1.PublicRouteTargetHealthStatus 39, // 7: p2pstream.v1.Agent.latest_stats:type_name -> p2pstream.v1.AgentStatsSnapshot - 212, // 8: p2pstream.v1.Agent.labels:type_name -> p2pstream.v1.Agent.LabelsEntry - 213, // 9: p2pstream.v1.PublicAgentSelector.match_labels:type_name -> p2pstream.v1.PublicAgentSelector.MatchLabelsEntry + 217, // 8: p2pstream.v1.Agent.labels:type_name -> p2pstream.v1.Agent.LabelsEntry + 218, // 9: p2pstream.v1.PublicAgentSelector.match_labels:type_name -> p2pstream.v1.PublicAgentSelector.MatchLabelsEntry 8, // 10: p2pstream.v1.PublicRouteTargetHealth.status:type_name -> p2pstream.v1.PublicRouteTargetHealthStatus 5, // 11: p2pstream.v1.PublicRouteTarget.target_type:type_name -> p2pstream.v1.PublicRouteTargetType 6, // 12: p2pstream.v1.PublicRouteTarget.transport:type_name -> p2pstream.v1.PublicRouteTargetTransport @@ -17511,15 +18119,15 @@ var file_proto_p2pstream_v1_management_proto_depIdxs = []int32{ 10, // 78: p2pstream.v1.PublicRouteTargetHealthTrace.outcome:type_name -> p2pstream.v1.PublicRouteTargetHealthTraceOutcome 8, // 79: p2pstream.v1.PublicRouteTargetHealthTrace.status_before:type_name -> p2pstream.v1.PublicRouteTargetHealthStatus 8, // 80: p2pstream.v1.PublicRouteTargetHealthTrace.status_after:type_name -> p2pstream.v1.PublicRouteTargetHealthStatus - 214, // 81: p2pstream.v1.PublicRouteTargetHealthTrace.debug_attributes:type_name -> p2pstream.v1.PublicRouteTargetHealthTrace.DebugAttributesEntry + 219, // 81: p2pstream.v1.PublicRouteTargetHealthTrace.debug_attributes:type_name -> p2pstream.v1.PublicRouteTargetHealthTrace.DebugAttributesEntry 73, // 82: p2pstream.v1.ListPublicRouteTargetHealthTracesResponse.traces:type_name -> p2pstream.v1.PublicRouteTargetHealthTrace 4, // 83: p2pstream.v1.CreatePublicResponseTemplateRequest.kind:type_name -> p2pstream.v1.PublicResponseTemplateKind 67, // 84: p2pstream.v1.CreatePublicResponseTemplateResponse.template:type_name -> p2pstream.v1.PublicResponseTemplate 4, // 85: p2pstream.v1.UpdatePublicResponseTemplateRequest.kind:type_name -> p2pstream.v1.PublicResponseTemplateKind 67, // 86: p2pstream.v1.UpdatePublicResponseTemplateResponse.template:type_name -> p2pstream.v1.PublicResponseTemplate - 215, // 87: p2pstream.v1.CreateAgentRequest.labels:type_name -> p2pstream.v1.CreateAgentRequest.LabelsEntry + 220, // 87: p2pstream.v1.CreateAgentRequest.labels:type_name -> p2pstream.v1.CreateAgentRequest.LabelsEntry 47, // 88: p2pstream.v1.CreateAgentResponse.agent:type_name -> p2pstream.v1.Agent - 216, // 89: p2pstream.v1.UpdateAgentRequest.labels:type_name -> p2pstream.v1.UpdateAgentRequest.LabelsEntry + 221, // 89: p2pstream.v1.UpdateAgentRequest.labels:type_name -> p2pstream.v1.UpdateAgentRequest.LabelsEntry 47, // 90: p2pstream.v1.UpdateAgentResponse.agent:type_name -> p2pstream.v1.Agent 47, // 91: p2pstream.v1.RotateAgentTokenResponse.agent:type_name -> p2pstream.v1.Agent 90, // 92: p2pstream.v1.CreateManagementAccessTokenResponse.access_token:type_name -> p2pstream.v1.ManagementAccessToken @@ -17646,164 +18254,174 @@ var file_proto_p2pstream_v1_management_proto_depIdxs = []int32{ 187, // 213: p2pstream.v1.GetDashboardResponse.agent_uptime_summaries:type_name -> p2pstream.v1.AgentUptimeSummary 188, // 214: p2pstream.v1.GetDashboardResponse.recent_agent_connections:type_name -> p2pstream.v1.AgentConnectionSession 183, // 215: p2pstream.v1.GetDashboardResponse.top_route_targets:type_name -> p2pstream.v1.DashboardProxyDimensionSummary - 30, // 216: p2pstream.v1.TrafficTraceSettings.level:type_name -> p2pstream.v1.TrafficTraceLevel - 190, // 217: p2pstream.v1.GetTrafficTraceSettingsResponse.settings:type_name -> p2pstream.v1.TrafficTraceSettings - 30, // 218: p2pstream.v1.SetTrafficTraceSettingsRequest.level:type_name -> p2pstream.v1.TrafficTraceLevel - 190, // 219: p2pstream.v1.SetTrafficTraceSettingsResponse.settings:type_name -> p2pstream.v1.TrafficTraceSettings - 31, // 220: p2pstream.v1.TrafficTraceEvent.stage:type_name -> p2pstream.v1.TrafficTraceStage - 217, // 221: p2pstream.v1.TrafficTraceEvent.request_headers:type_name -> p2pstream.v1.TrafficTraceEvent.RequestHeadersEntry - 218, // 222: p2pstream.v1.TrafficTraceEvent.response_headers:type_name -> p2pstream.v1.TrafficTraceEvent.ResponseHeadersEntry - 219, // 223: p2pstream.v1.TrafficTraceEvent.debug_attributes:type_name -> p2pstream.v1.TrafficTraceEvent.DebugAttributesEntry - 13, // 224: p2pstream.v1.TrafficTraceEvent.rate_limit_algorithm:type_name -> p2pstream.v1.PublicRateLimitAlgorithm - 18, // 225: p2pstream.v1.TrafficTraceEvent.traffic_shaper_budget_scope:type_name -> p2pstream.v1.PublicTrafficShaperBudgetScope - 20, // 226: p2pstream.v1.TrafficTraceEvent.waf_action:type_name -> p2pstream.v1.PublicWafRuleAction - 21, // 227: p2pstream.v1.TrafficTraceEvent.waf_activation_mode:type_name -> p2pstream.v1.PublicWafActivationMode - 5, // 228: p2pstream.v1.TrafficTraceEvent.route_target_type:type_name -> p2pstream.v1.PublicRouteTargetType - 6, // 229: p2pstream.v1.TrafficTraceEvent.route_target_transport:type_name -> p2pstream.v1.PublicRouteTargetTransport - 190, // 230: p2pstream.v1.StreamTrafficTraceEventsResponse.settings:type_name -> p2pstream.v1.TrafficTraceSettings - 196, // 231: p2pstream.v1.StreamTrafficTraceEventsResponse.event:type_name -> p2pstream.v1.TrafficTraceEvent - 37, // 232: p2pstream.v1.SetupAdminResponse.user:type_name -> p2pstream.v1.User - 37, // 233: p2pstream.v1.LoginResponse.user:type_name -> p2pstream.v1.User - 37, // 234: p2pstream.v1.GetCurrentUserResponse.user:type_name -> p2pstream.v1.User - 41, // 235: p2pstream.v1.StartProxyResponse.proxy:type_name -> p2pstream.v1.ProxyStatus - 41, // 236: p2pstream.v1.StopProxyResponse.proxy:type_name -> p2pstream.v1.ProxyStatus - 35, // 237: p2pstream.v1.AgentManagementService.ReportStats:input_type -> p2pstream.v1.AgentStatsRequest - 38, // 238: p2pstream.v1.AgentManagementService.GetStatus:input_type -> p2pstream.v1.GetStatusRequest - 181, // 239: p2pstream.v1.AgentManagementService.GetDashboard:input_type -> p2pstream.v1.GetDashboardRequest - 191, // 240: p2pstream.v1.AgentManagementService.GetTrafficTraceSettings:input_type -> p2pstream.v1.GetTrafficTraceSettingsRequest - 193, // 241: p2pstream.v1.AgentManagementService.SetTrafficTraceSettings:input_type -> p2pstream.v1.SetTrafficTraceSettingsRequest - 195, // 242: p2pstream.v1.AgentManagementService.StreamTrafficTraceEvents:input_type -> p2pstream.v1.StreamTrafficTraceEventsRequest - 198, // 243: p2pstream.v1.AgentManagementService.GetSetupState:input_type -> p2pstream.v1.GetSetupStateRequest - 200, // 244: p2pstream.v1.AgentManagementService.SetupAdmin:input_type -> p2pstream.v1.SetupAdminRequest - 202, // 245: p2pstream.v1.AgentManagementService.Login:input_type -> p2pstream.v1.LoginRequest - 204, // 246: p2pstream.v1.AgentManagementService.Logout:input_type -> p2pstream.v1.LogoutRequest - 206, // 247: p2pstream.v1.AgentManagementService.GetCurrentUser:input_type -> p2pstream.v1.GetCurrentUserRequest - 208, // 248: p2pstream.v1.AgentManagementService.StartProxy:input_type -> p2pstream.v1.StartProxyRequest - 210, // 249: p2pstream.v1.AgentManagementService.StopProxy:input_type -> p2pstream.v1.StopProxyRequest - 71, // 250: p2pstream.v1.AgentManagementService.GetPublicProxyConfig:input_type -> p2pstream.v1.GetPublicProxyConfigRequest - 76, // 251: p2pstream.v1.AgentManagementService.CreatePublicResponseTemplate:input_type -> p2pstream.v1.CreatePublicResponseTemplateRequest - 78, // 252: p2pstream.v1.AgentManagementService.UpdatePublicResponseTemplate:input_type -> p2pstream.v1.UpdatePublicResponseTemplateRequest - 80, // 253: p2pstream.v1.AgentManagementService.DeletePublicResponseTemplate:input_type -> p2pstream.v1.DeletePublicResponseTemplateRequest - 74, // 254: p2pstream.v1.AgentManagementService.ListPublicRouteTargetHealthTraces:input_type -> p2pstream.v1.ListPublicRouteTargetHealthTracesRequest - 82, // 255: p2pstream.v1.AgentManagementService.CreateAgent:input_type -> p2pstream.v1.CreateAgentRequest - 84, // 256: p2pstream.v1.AgentManagementService.UpdateAgent:input_type -> p2pstream.v1.UpdateAgentRequest - 86, // 257: p2pstream.v1.AgentManagementService.DeleteAgent:input_type -> p2pstream.v1.DeleteAgentRequest - 88, // 258: p2pstream.v1.AgentManagementService.RotateAgentToken:input_type -> p2pstream.v1.RotateAgentTokenRequest - 91, // 259: p2pstream.v1.AgentManagementService.CreateManagementAccessToken:input_type -> p2pstream.v1.CreateManagementAccessTokenRequest - 93, // 260: p2pstream.v1.AgentManagementService.ListManagementAccessTokens:input_type -> p2pstream.v1.ListManagementAccessTokensRequest - 95, // 261: p2pstream.v1.AgentManagementService.DeleteManagementAccessToken:input_type -> p2pstream.v1.DeleteManagementAccessTokenRequest - 99, // 262: p2pstream.v1.AgentManagementService.ListEnvironments:input_type -> p2pstream.v1.ListEnvironmentsRequest - 101, // 263: p2pstream.v1.AgentManagementService.CreateEnvironment:input_type -> p2pstream.v1.CreateEnvironmentRequest - 103, // 264: p2pstream.v1.AgentManagementService.UpdateEnvironment:input_type -> p2pstream.v1.UpdateEnvironmentRequest - 105, // 265: p2pstream.v1.AgentManagementService.DeleteEnvironment:input_type -> p2pstream.v1.DeleteEnvironmentRequest - 107, // 266: p2pstream.v1.AgentManagementService.DiscoverEnvironmentCertificate:input_type -> p2pstream.v1.DiscoverEnvironmentCertificateRequest - 109, // 267: p2pstream.v1.AgentManagementService.TrustEnvironmentCertificate:input_type -> p2pstream.v1.TrustEnvironmentCertificateRequest - 111, // 268: p2pstream.v1.AgentManagementService.TestEnvironment:input_type -> p2pstream.v1.TestEnvironmentRequest - 113, // 269: p2pstream.v1.AgentManagementService.CreatePublicListener:input_type -> p2pstream.v1.CreatePublicListenerRequest - 115, // 270: p2pstream.v1.AgentManagementService.UpdatePublicListener:input_type -> p2pstream.v1.UpdatePublicListenerRequest - 117, // 271: p2pstream.v1.AgentManagementService.DeletePublicListener:input_type -> p2pstream.v1.DeletePublicListenerRequest - 119, // 272: p2pstream.v1.AgentManagementService.EnablePublicListener:input_type -> p2pstream.v1.EnablePublicListenerRequest - 121, // 273: p2pstream.v1.AgentManagementService.DisablePublicListener:input_type -> p2pstream.v1.DisablePublicListenerRequest - 123, // 274: p2pstream.v1.AgentManagementService.StartPublicListener:input_type -> p2pstream.v1.StartPublicListenerRequest - 125, // 275: p2pstream.v1.AgentManagementService.StopPublicListener:input_type -> p2pstream.v1.StopPublicListenerRequest - 127, // 276: p2pstream.v1.AgentManagementService.CreatePublicRoute:input_type -> p2pstream.v1.CreatePublicRouteRequest - 129, // 277: p2pstream.v1.AgentManagementService.UpdatePublicRoute:input_type -> p2pstream.v1.UpdatePublicRouteRequest - 131, // 278: p2pstream.v1.AgentManagementService.DeletePublicRoute:input_type -> p2pstream.v1.DeletePublicRouteRequest - 133, // 279: p2pstream.v1.AgentManagementService.CreatePublicTlsDnsCredential:input_type -> p2pstream.v1.CreatePublicTlsDnsCredentialRequest - 135, // 280: p2pstream.v1.AgentManagementService.UpdatePublicTlsDnsCredential:input_type -> p2pstream.v1.UpdatePublicTlsDnsCredentialRequest - 137, // 281: p2pstream.v1.AgentManagementService.DeletePublicTlsDnsCredential:input_type -> p2pstream.v1.DeletePublicTlsDnsCredentialRequest - 139, // 282: p2pstream.v1.AgentManagementService.CreatePublicTlsCertificate:input_type -> p2pstream.v1.CreatePublicTlsCertificateRequest - 141, // 283: p2pstream.v1.AgentManagementService.UpdatePublicTlsCertificate:input_type -> p2pstream.v1.UpdatePublicTlsCertificateRequest - 143, // 284: p2pstream.v1.AgentManagementService.DeletePublicTlsCertificate:input_type -> p2pstream.v1.DeletePublicTlsCertificateRequest - 145, // 285: p2pstream.v1.AgentManagementService.RenewPublicTlsCertificate:input_type -> p2pstream.v1.RenewPublicTlsCertificateRequest - 147, // 286: p2pstream.v1.AgentManagementService.CreatePublicRateLimitRule:input_type -> p2pstream.v1.CreatePublicRateLimitRuleRequest - 149, // 287: p2pstream.v1.AgentManagementService.UpdatePublicRateLimitRule:input_type -> p2pstream.v1.UpdatePublicRateLimitRuleRequest - 151, // 288: p2pstream.v1.AgentManagementService.DeletePublicRateLimitRule:input_type -> p2pstream.v1.DeletePublicRateLimitRuleRequest - 153, // 289: p2pstream.v1.AgentManagementService.CreatePublicTrafficShaperRule:input_type -> p2pstream.v1.CreatePublicTrafficShaperRuleRequest - 155, // 290: p2pstream.v1.AgentManagementService.UpdatePublicTrafficShaperRule:input_type -> p2pstream.v1.UpdatePublicTrafficShaperRuleRequest - 157, // 291: p2pstream.v1.AgentManagementService.DeletePublicTrafficShaperRule:input_type -> p2pstream.v1.DeletePublicTrafficShaperRuleRequest - 159, // 292: p2pstream.v1.AgentManagementService.CreatePublicWafCaptchaProvider:input_type -> p2pstream.v1.CreatePublicWafCaptchaProviderRequest - 161, // 293: p2pstream.v1.AgentManagementService.UpdatePublicWafCaptchaProvider:input_type -> p2pstream.v1.UpdatePublicWafCaptchaProviderRequest - 163, // 294: p2pstream.v1.AgentManagementService.DeletePublicWafCaptchaProvider:input_type -> p2pstream.v1.DeletePublicWafCaptchaProviderRequest - 165, // 295: p2pstream.v1.AgentManagementService.CreatePublicWafRule:input_type -> p2pstream.v1.CreatePublicWafRuleRequest - 167, // 296: p2pstream.v1.AgentManagementService.UpdatePublicWafRule:input_type -> p2pstream.v1.UpdatePublicWafRuleRequest - 169, // 297: p2pstream.v1.AgentManagementService.DeletePublicWafRule:input_type -> p2pstream.v1.DeletePublicWafRuleRequest - 171, // 298: p2pstream.v1.AgentManagementService.CreatePublicCacheRule:input_type -> p2pstream.v1.CreatePublicCacheRuleRequest - 173, // 299: p2pstream.v1.AgentManagementService.UpdatePublicCacheRule:input_type -> p2pstream.v1.UpdatePublicCacheRuleRequest - 175, // 300: p2pstream.v1.AgentManagementService.DeletePublicCacheRule:input_type -> p2pstream.v1.DeletePublicCacheRuleRequest - 177, // 301: p2pstream.v1.AgentManagementService.UpdatePublicCacheSettings:input_type -> p2pstream.v1.UpdatePublicCacheSettingsRequest - 179, // 302: p2pstream.v1.AgentManagementService.PurgePublicCache:input_type -> p2pstream.v1.PurgePublicCacheRequest - 36, // 303: p2pstream.v1.AgentManagementService.ReportStats:output_type -> p2pstream.v1.AgentStatsResponse - 40, // 304: p2pstream.v1.AgentManagementService.GetStatus:output_type -> p2pstream.v1.GetStatusResponse - 189, // 305: p2pstream.v1.AgentManagementService.GetDashboard:output_type -> p2pstream.v1.GetDashboardResponse - 192, // 306: p2pstream.v1.AgentManagementService.GetTrafficTraceSettings:output_type -> p2pstream.v1.GetTrafficTraceSettingsResponse - 194, // 307: p2pstream.v1.AgentManagementService.SetTrafficTraceSettings:output_type -> p2pstream.v1.SetTrafficTraceSettingsResponse - 197, // 308: p2pstream.v1.AgentManagementService.StreamTrafficTraceEvents:output_type -> p2pstream.v1.StreamTrafficTraceEventsResponse - 199, // 309: p2pstream.v1.AgentManagementService.GetSetupState:output_type -> p2pstream.v1.GetSetupStateResponse - 201, // 310: p2pstream.v1.AgentManagementService.SetupAdmin:output_type -> p2pstream.v1.SetupAdminResponse - 203, // 311: p2pstream.v1.AgentManagementService.Login:output_type -> p2pstream.v1.LoginResponse - 205, // 312: p2pstream.v1.AgentManagementService.Logout:output_type -> p2pstream.v1.LogoutResponse - 207, // 313: p2pstream.v1.AgentManagementService.GetCurrentUser:output_type -> p2pstream.v1.GetCurrentUserResponse - 209, // 314: p2pstream.v1.AgentManagementService.StartProxy:output_type -> p2pstream.v1.StartProxyResponse - 211, // 315: p2pstream.v1.AgentManagementService.StopProxy:output_type -> p2pstream.v1.StopProxyResponse - 72, // 316: p2pstream.v1.AgentManagementService.GetPublicProxyConfig:output_type -> p2pstream.v1.GetPublicProxyConfigResponse - 77, // 317: p2pstream.v1.AgentManagementService.CreatePublicResponseTemplate:output_type -> p2pstream.v1.CreatePublicResponseTemplateResponse - 79, // 318: p2pstream.v1.AgentManagementService.UpdatePublicResponseTemplate:output_type -> p2pstream.v1.UpdatePublicResponseTemplateResponse - 81, // 319: p2pstream.v1.AgentManagementService.DeletePublicResponseTemplate:output_type -> p2pstream.v1.DeletePublicResponseTemplateResponse - 75, // 320: p2pstream.v1.AgentManagementService.ListPublicRouteTargetHealthTraces:output_type -> p2pstream.v1.ListPublicRouteTargetHealthTracesResponse - 83, // 321: p2pstream.v1.AgentManagementService.CreateAgent:output_type -> p2pstream.v1.CreateAgentResponse - 85, // 322: p2pstream.v1.AgentManagementService.UpdateAgent:output_type -> p2pstream.v1.UpdateAgentResponse - 87, // 323: p2pstream.v1.AgentManagementService.DeleteAgent:output_type -> p2pstream.v1.DeleteAgentResponse - 89, // 324: p2pstream.v1.AgentManagementService.RotateAgentToken:output_type -> p2pstream.v1.RotateAgentTokenResponse - 92, // 325: p2pstream.v1.AgentManagementService.CreateManagementAccessToken:output_type -> p2pstream.v1.CreateManagementAccessTokenResponse - 94, // 326: p2pstream.v1.AgentManagementService.ListManagementAccessTokens:output_type -> p2pstream.v1.ListManagementAccessTokensResponse - 96, // 327: p2pstream.v1.AgentManagementService.DeleteManagementAccessToken:output_type -> p2pstream.v1.DeleteManagementAccessTokenResponse - 100, // 328: p2pstream.v1.AgentManagementService.ListEnvironments:output_type -> p2pstream.v1.ListEnvironmentsResponse - 102, // 329: p2pstream.v1.AgentManagementService.CreateEnvironment:output_type -> p2pstream.v1.CreateEnvironmentResponse - 104, // 330: p2pstream.v1.AgentManagementService.UpdateEnvironment:output_type -> p2pstream.v1.UpdateEnvironmentResponse - 106, // 331: p2pstream.v1.AgentManagementService.DeleteEnvironment:output_type -> p2pstream.v1.DeleteEnvironmentResponse - 108, // 332: p2pstream.v1.AgentManagementService.DiscoverEnvironmentCertificate:output_type -> p2pstream.v1.DiscoverEnvironmentCertificateResponse - 110, // 333: p2pstream.v1.AgentManagementService.TrustEnvironmentCertificate:output_type -> p2pstream.v1.TrustEnvironmentCertificateResponse - 112, // 334: p2pstream.v1.AgentManagementService.TestEnvironment:output_type -> p2pstream.v1.TestEnvironmentResponse - 114, // 335: p2pstream.v1.AgentManagementService.CreatePublicListener:output_type -> p2pstream.v1.CreatePublicListenerResponse - 116, // 336: p2pstream.v1.AgentManagementService.UpdatePublicListener:output_type -> p2pstream.v1.UpdatePublicListenerResponse - 118, // 337: p2pstream.v1.AgentManagementService.DeletePublicListener:output_type -> p2pstream.v1.DeletePublicListenerResponse - 120, // 338: p2pstream.v1.AgentManagementService.EnablePublicListener:output_type -> p2pstream.v1.EnablePublicListenerResponse - 122, // 339: p2pstream.v1.AgentManagementService.DisablePublicListener:output_type -> p2pstream.v1.DisablePublicListenerResponse - 124, // 340: p2pstream.v1.AgentManagementService.StartPublicListener:output_type -> p2pstream.v1.StartPublicListenerResponse - 126, // 341: p2pstream.v1.AgentManagementService.StopPublicListener:output_type -> p2pstream.v1.StopPublicListenerResponse - 128, // 342: p2pstream.v1.AgentManagementService.CreatePublicRoute:output_type -> p2pstream.v1.CreatePublicRouteResponse - 130, // 343: p2pstream.v1.AgentManagementService.UpdatePublicRoute:output_type -> p2pstream.v1.UpdatePublicRouteResponse - 132, // 344: p2pstream.v1.AgentManagementService.DeletePublicRoute:output_type -> p2pstream.v1.DeletePublicRouteResponse - 134, // 345: p2pstream.v1.AgentManagementService.CreatePublicTlsDnsCredential:output_type -> p2pstream.v1.CreatePublicTlsDnsCredentialResponse - 136, // 346: p2pstream.v1.AgentManagementService.UpdatePublicTlsDnsCredential:output_type -> p2pstream.v1.UpdatePublicTlsDnsCredentialResponse - 138, // 347: p2pstream.v1.AgentManagementService.DeletePublicTlsDnsCredential:output_type -> p2pstream.v1.DeletePublicTlsDnsCredentialResponse - 140, // 348: p2pstream.v1.AgentManagementService.CreatePublicTlsCertificate:output_type -> p2pstream.v1.CreatePublicTlsCertificateResponse - 142, // 349: p2pstream.v1.AgentManagementService.UpdatePublicTlsCertificate:output_type -> p2pstream.v1.UpdatePublicTlsCertificateResponse - 144, // 350: p2pstream.v1.AgentManagementService.DeletePublicTlsCertificate:output_type -> p2pstream.v1.DeletePublicTlsCertificateResponse - 146, // 351: p2pstream.v1.AgentManagementService.RenewPublicTlsCertificate:output_type -> p2pstream.v1.RenewPublicTlsCertificateResponse - 148, // 352: p2pstream.v1.AgentManagementService.CreatePublicRateLimitRule:output_type -> p2pstream.v1.CreatePublicRateLimitRuleResponse - 150, // 353: p2pstream.v1.AgentManagementService.UpdatePublicRateLimitRule:output_type -> p2pstream.v1.UpdatePublicRateLimitRuleResponse - 152, // 354: p2pstream.v1.AgentManagementService.DeletePublicRateLimitRule:output_type -> p2pstream.v1.DeletePublicRateLimitRuleResponse - 154, // 355: p2pstream.v1.AgentManagementService.CreatePublicTrafficShaperRule:output_type -> p2pstream.v1.CreatePublicTrafficShaperRuleResponse - 156, // 356: p2pstream.v1.AgentManagementService.UpdatePublicTrafficShaperRule:output_type -> p2pstream.v1.UpdatePublicTrafficShaperRuleResponse - 158, // 357: p2pstream.v1.AgentManagementService.DeletePublicTrafficShaperRule:output_type -> p2pstream.v1.DeletePublicTrafficShaperRuleResponse - 160, // 358: p2pstream.v1.AgentManagementService.CreatePublicWafCaptchaProvider:output_type -> p2pstream.v1.CreatePublicWafCaptchaProviderResponse - 162, // 359: p2pstream.v1.AgentManagementService.UpdatePublicWafCaptchaProvider:output_type -> p2pstream.v1.UpdatePublicWafCaptchaProviderResponse - 164, // 360: p2pstream.v1.AgentManagementService.DeletePublicWafCaptchaProvider:output_type -> p2pstream.v1.DeletePublicWafCaptchaProviderResponse - 166, // 361: p2pstream.v1.AgentManagementService.CreatePublicWafRule:output_type -> p2pstream.v1.CreatePublicWafRuleResponse - 168, // 362: p2pstream.v1.AgentManagementService.UpdatePublicWafRule:output_type -> p2pstream.v1.UpdatePublicWafRuleResponse - 170, // 363: p2pstream.v1.AgentManagementService.DeletePublicWafRule:output_type -> p2pstream.v1.DeletePublicWafRuleResponse - 172, // 364: p2pstream.v1.AgentManagementService.CreatePublicCacheRule:output_type -> p2pstream.v1.CreatePublicCacheRuleResponse - 174, // 365: p2pstream.v1.AgentManagementService.UpdatePublicCacheRule:output_type -> p2pstream.v1.UpdatePublicCacheRuleResponse - 176, // 366: p2pstream.v1.AgentManagementService.DeletePublicCacheRule:output_type -> p2pstream.v1.DeletePublicCacheRuleResponse - 178, // 367: p2pstream.v1.AgentManagementService.UpdatePublicCacheSettings:output_type -> p2pstream.v1.UpdatePublicCacheSettingsResponse - 180, // 368: p2pstream.v1.AgentManagementService.PurgePublicCache:output_type -> p2pstream.v1.PurgePublicCacheResponse - 303, // [303:369] is the sub-list for method output_type - 237, // [237:303] is the sub-list for method input_type - 237, // [237:237] is the sub-list for extension type_name - 237, // [237:237] is the sub-list for extension extendee - 0, // [0:237] is the sub-list for field type_name + 191, // 216: p2pstream.v1.GetDashboardDiagnosticsResponse.outcome:type_name -> p2pstream.v1.DashboardDiagnosticsOutcomeSummary + 192, // 217: p2pstream.v1.GetDashboardDiagnosticsResponse.status_codes:type_name -> p2pstream.v1.DashboardStatusCodeSummary + 183, // 218: p2pstream.v1.GetDashboardDiagnosticsResponse.error_kinds:type_name -> p2pstream.v1.DashboardProxyDimensionSummary + 183, // 219: p2pstream.v1.GetDashboardDiagnosticsResponse.problem_listeners:type_name -> p2pstream.v1.DashboardProxyDimensionSummary + 183, // 220: p2pstream.v1.GetDashboardDiagnosticsResponse.problem_routes:type_name -> p2pstream.v1.DashboardProxyDimensionSummary + 183, // 221: p2pstream.v1.GetDashboardDiagnosticsResponse.problem_route_targets:type_name -> p2pstream.v1.DashboardProxyDimensionSummary + 183, // 222: p2pstream.v1.GetDashboardDiagnosticsResponse.problem_agents:type_name -> p2pstream.v1.DashboardProxyDimensionSummary + 193, // 223: p2pstream.v1.GetDashboardDiagnosticsResponse.recent_samples:type_name -> p2pstream.v1.DashboardDiagnosticsSample + 30, // 224: p2pstream.v1.TrafficTraceSettings.level:type_name -> p2pstream.v1.TrafficTraceLevel + 195, // 225: p2pstream.v1.GetTrafficTraceSettingsResponse.settings:type_name -> p2pstream.v1.TrafficTraceSettings + 30, // 226: p2pstream.v1.SetTrafficTraceSettingsRequest.level:type_name -> p2pstream.v1.TrafficTraceLevel + 195, // 227: p2pstream.v1.SetTrafficTraceSettingsResponse.settings:type_name -> p2pstream.v1.TrafficTraceSettings + 31, // 228: p2pstream.v1.TrafficTraceEvent.stage:type_name -> p2pstream.v1.TrafficTraceStage + 222, // 229: p2pstream.v1.TrafficTraceEvent.request_headers:type_name -> p2pstream.v1.TrafficTraceEvent.RequestHeadersEntry + 223, // 230: p2pstream.v1.TrafficTraceEvent.response_headers:type_name -> p2pstream.v1.TrafficTraceEvent.ResponseHeadersEntry + 224, // 231: p2pstream.v1.TrafficTraceEvent.debug_attributes:type_name -> p2pstream.v1.TrafficTraceEvent.DebugAttributesEntry + 13, // 232: p2pstream.v1.TrafficTraceEvent.rate_limit_algorithm:type_name -> p2pstream.v1.PublicRateLimitAlgorithm + 18, // 233: p2pstream.v1.TrafficTraceEvent.traffic_shaper_budget_scope:type_name -> p2pstream.v1.PublicTrafficShaperBudgetScope + 20, // 234: p2pstream.v1.TrafficTraceEvent.waf_action:type_name -> p2pstream.v1.PublicWafRuleAction + 21, // 235: p2pstream.v1.TrafficTraceEvent.waf_activation_mode:type_name -> p2pstream.v1.PublicWafActivationMode + 5, // 236: p2pstream.v1.TrafficTraceEvent.route_target_type:type_name -> p2pstream.v1.PublicRouteTargetType + 6, // 237: p2pstream.v1.TrafficTraceEvent.route_target_transport:type_name -> p2pstream.v1.PublicRouteTargetTransport + 195, // 238: p2pstream.v1.StreamTrafficTraceEventsResponse.settings:type_name -> p2pstream.v1.TrafficTraceSettings + 201, // 239: p2pstream.v1.StreamTrafficTraceEventsResponse.event:type_name -> p2pstream.v1.TrafficTraceEvent + 37, // 240: p2pstream.v1.SetupAdminResponse.user:type_name -> p2pstream.v1.User + 37, // 241: p2pstream.v1.LoginResponse.user:type_name -> p2pstream.v1.User + 37, // 242: p2pstream.v1.GetCurrentUserResponse.user:type_name -> p2pstream.v1.User + 41, // 243: p2pstream.v1.StartProxyResponse.proxy:type_name -> p2pstream.v1.ProxyStatus + 41, // 244: p2pstream.v1.StopProxyResponse.proxy:type_name -> p2pstream.v1.ProxyStatus + 35, // 245: p2pstream.v1.AgentManagementService.ReportStats:input_type -> p2pstream.v1.AgentStatsRequest + 38, // 246: p2pstream.v1.AgentManagementService.GetStatus:input_type -> p2pstream.v1.GetStatusRequest + 181, // 247: p2pstream.v1.AgentManagementService.GetDashboard:input_type -> p2pstream.v1.GetDashboardRequest + 190, // 248: p2pstream.v1.AgentManagementService.GetDashboardDiagnostics:input_type -> p2pstream.v1.GetDashboardDiagnosticsRequest + 196, // 249: p2pstream.v1.AgentManagementService.GetTrafficTraceSettings:input_type -> p2pstream.v1.GetTrafficTraceSettingsRequest + 198, // 250: p2pstream.v1.AgentManagementService.SetTrafficTraceSettings:input_type -> p2pstream.v1.SetTrafficTraceSettingsRequest + 200, // 251: p2pstream.v1.AgentManagementService.StreamTrafficTraceEvents:input_type -> p2pstream.v1.StreamTrafficTraceEventsRequest + 203, // 252: p2pstream.v1.AgentManagementService.GetSetupState:input_type -> p2pstream.v1.GetSetupStateRequest + 205, // 253: p2pstream.v1.AgentManagementService.SetupAdmin:input_type -> p2pstream.v1.SetupAdminRequest + 207, // 254: p2pstream.v1.AgentManagementService.Login:input_type -> p2pstream.v1.LoginRequest + 209, // 255: p2pstream.v1.AgentManagementService.Logout:input_type -> p2pstream.v1.LogoutRequest + 211, // 256: p2pstream.v1.AgentManagementService.GetCurrentUser:input_type -> p2pstream.v1.GetCurrentUserRequest + 213, // 257: p2pstream.v1.AgentManagementService.StartProxy:input_type -> p2pstream.v1.StartProxyRequest + 215, // 258: p2pstream.v1.AgentManagementService.StopProxy:input_type -> p2pstream.v1.StopProxyRequest + 71, // 259: p2pstream.v1.AgentManagementService.GetPublicProxyConfig:input_type -> p2pstream.v1.GetPublicProxyConfigRequest + 76, // 260: p2pstream.v1.AgentManagementService.CreatePublicResponseTemplate:input_type -> p2pstream.v1.CreatePublicResponseTemplateRequest + 78, // 261: p2pstream.v1.AgentManagementService.UpdatePublicResponseTemplate:input_type -> p2pstream.v1.UpdatePublicResponseTemplateRequest + 80, // 262: p2pstream.v1.AgentManagementService.DeletePublicResponseTemplate:input_type -> p2pstream.v1.DeletePublicResponseTemplateRequest + 74, // 263: p2pstream.v1.AgentManagementService.ListPublicRouteTargetHealthTraces:input_type -> p2pstream.v1.ListPublicRouteTargetHealthTracesRequest + 82, // 264: p2pstream.v1.AgentManagementService.CreateAgent:input_type -> p2pstream.v1.CreateAgentRequest + 84, // 265: p2pstream.v1.AgentManagementService.UpdateAgent:input_type -> p2pstream.v1.UpdateAgentRequest + 86, // 266: p2pstream.v1.AgentManagementService.DeleteAgent:input_type -> p2pstream.v1.DeleteAgentRequest + 88, // 267: p2pstream.v1.AgentManagementService.RotateAgentToken:input_type -> p2pstream.v1.RotateAgentTokenRequest + 91, // 268: p2pstream.v1.AgentManagementService.CreateManagementAccessToken:input_type -> p2pstream.v1.CreateManagementAccessTokenRequest + 93, // 269: p2pstream.v1.AgentManagementService.ListManagementAccessTokens:input_type -> p2pstream.v1.ListManagementAccessTokensRequest + 95, // 270: p2pstream.v1.AgentManagementService.DeleteManagementAccessToken:input_type -> p2pstream.v1.DeleteManagementAccessTokenRequest + 99, // 271: p2pstream.v1.AgentManagementService.ListEnvironments:input_type -> p2pstream.v1.ListEnvironmentsRequest + 101, // 272: p2pstream.v1.AgentManagementService.CreateEnvironment:input_type -> p2pstream.v1.CreateEnvironmentRequest + 103, // 273: p2pstream.v1.AgentManagementService.UpdateEnvironment:input_type -> p2pstream.v1.UpdateEnvironmentRequest + 105, // 274: p2pstream.v1.AgentManagementService.DeleteEnvironment:input_type -> p2pstream.v1.DeleteEnvironmentRequest + 107, // 275: p2pstream.v1.AgentManagementService.DiscoverEnvironmentCertificate:input_type -> p2pstream.v1.DiscoverEnvironmentCertificateRequest + 109, // 276: p2pstream.v1.AgentManagementService.TrustEnvironmentCertificate:input_type -> p2pstream.v1.TrustEnvironmentCertificateRequest + 111, // 277: p2pstream.v1.AgentManagementService.TestEnvironment:input_type -> p2pstream.v1.TestEnvironmentRequest + 113, // 278: p2pstream.v1.AgentManagementService.CreatePublicListener:input_type -> p2pstream.v1.CreatePublicListenerRequest + 115, // 279: p2pstream.v1.AgentManagementService.UpdatePublicListener:input_type -> p2pstream.v1.UpdatePublicListenerRequest + 117, // 280: p2pstream.v1.AgentManagementService.DeletePublicListener:input_type -> p2pstream.v1.DeletePublicListenerRequest + 119, // 281: p2pstream.v1.AgentManagementService.EnablePublicListener:input_type -> p2pstream.v1.EnablePublicListenerRequest + 121, // 282: p2pstream.v1.AgentManagementService.DisablePublicListener:input_type -> p2pstream.v1.DisablePublicListenerRequest + 123, // 283: p2pstream.v1.AgentManagementService.StartPublicListener:input_type -> p2pstream.v1.StartPublicListenerRequest + 125, // 284: p2pstream.v1.AgentManagementService.StopPublicListener:input_type -> p2pstream.v1.StopPublicListenerRequest + 127, // 285: p2pstream.v1.AgentManagementService.CreatePublicRoute:input_type -> p2pstream.v1.CreatePublicRouteRequest + 129, // 286: p2pstream.v1.AgentManagementService.UpdatePublicRoute:input_type -> p2pstream.v1.UpdatePublicRouteRequest + 131, // 287: p2pstream.v1.AgentManagementService.DeletePublicRoute:input_type -> p2pstream.v1.DeletePublicRouteRequest + 133, // 288: p2pstream.v1.AgentManagementService.CreatePublicTlsDnsCredential:input_type -> p2pstream.v1.CreatePublicTlsDnsCredentialRequest + 135, // 289: p2pstream.v1.AgentManagementService.UpdatePublicTlsDnsCredential:input_type -> p2pstream.v1.UpdatePublicTlsDnsCredentialRequest + 137, // 290: p2pstream.v1.AgentManagementService.DeletePublicTlsDnsCredential:input_type -> p2pstream.v1.DeletePublicTlsDnsCredentialRequest + 139, // 291: p2pstream.v1.AgentManagementService.CreatePublicTlsCertificate:input_type -> p2pstream.v1.CreatePublicTlsCertificateRequest + 141, // 292: p2pstream.v1.AgentManagementService.UpdatePublicTlsCertificate:input_type -> p2pstream.v1.UpdatePublicTlsCertificateRequest + 143, // 293: p2pstream.v1.AgentManagementService.DeletePublicTlsCertificate:input_type -> p2pstream.v1.DeletePublicTlsCertificateRequest + 145, // 294: p2pstream.v1.AgentManagementService.RenewPublicTlsCertificate:input_type -> p2pstream.v1.RenewPublicTlsCertificateRequest + 147, // 295: p2pstream.v1.AgentManagementService.CreatePublicRateLimitRule:input_type -> p2pstream.v1.CreatePublicRateLimitRuleRequest + 149, // 296: p2pstream.v1.AgentManagementService.UpdatePublicRateLimitRule:input_type -> p2pstream.v1.UpdatePublicRateLimitRuleRequest + 151, // 297: p2pstream.v1.AgentManagementService.DeletePublicRateLimitRule:input_type -> p2pstream.v1.DeletePublicRateLimitRuleRequest + 153, // 298: p2pstream.v1.AgentManagementService.CreatePublicTrafficShaperRule:input_type -> p2pstream.v1.CreatePublicTrafficShaperRuleRequest + 155, // 299: p2pstream.v1.AgentManagementService.UpdatePublicTrafficShaperRule:input_type -> p2pstream.v1.UpdatePublicTrafficShaperRuleRequest + 157, // 300: p2pstream.v1.AgentManagementService.DeletePublicTrafficShaperRule:input_type -> p2pstream.v1.DeletePublicTrafficShaperRuleRequest + 159, // 301: p2pstream.v1.AgentManagementService.CreatePublicWafCaptchaProvider:input_type -> p2pstream.v1.CreatePublicWafCaptchaProviderRequest + 161, // 302: p2pstream.v1.AgentManagementService.UpdatePublicWafCaptchaProvider:input_type -> p2pstream.v1.UpdatePublicWafCaptchaProviderRequest + 163, // 303: p2pstream.v1.AgentManagementService.DeletePublicWafCaptchaProvider:input_type -> p2pstream.v1.DeletePublicWafCaptchaProviderRequest + 165, // 304: p2pstream.v1.AgentManagementService.CreatePublicWafRule:input_type -> p2pstream.v1.CreatePublicWafRuleRequest + 167, // 305: p2pstream.v1.AgentManagementService.UpdatePublicWafRule:input_type -> p2pstream.v1.UpdatePublicWafRuleRequest + 169, // 306: p2pstream.v1.AgentManagementService.DeletePublicWafRule:input_type -> p2pstream.v1.DeletePublicWafRuleRequest + 171, // 307: p2pstream.v1.AgentManagementService.CreatePublicCacheRule:input_type -> p2pstream.v1.CreatePublicCacheRuleRequest + 173, // 308: p2pstream.v1.AgentManagementService.UpdatePublicCacheRule:input_type -> p2pstream.v1.UpdatePublicCacheRuleRequest + 175, // 309: p2pstream.v1.AgentManagementService.DeletePublicCacheRule:input_type -> p2pstream.v1.DeletePublicCacheRuleRequest + 177, // 310: p2pstream.v1.AgentManagementService.UpdatePublicCacheSettings:input_type -> p2pstream.v1.UpdatePublicCacheSettingsRequest + 179, // 311: p2pstream.v1.AgentManagementService.PurgePublicCache:input_type -> p2pstream.v1.PurgePublicCacheRequest + 36, // 312: p2pstream.v1.AgentManagementService.ReportStats:output_type -> p2pstream.v1.AgentStatsResponse + 40, // 313: p2pstream.v1.AgentManagementService.GetStatus:output_type -> p2pstream.v1.GetStatusResponse + 189, // 314: p2pstream.v1.AgentManagementService.GetDashboard:output_type -> p2pstream.v1.GetDashboardResponse + 194, // 315: p2pstream.v1.AgentManagementService.GetDashboardDiagnostics:output_type -> p2pstream.v1.GetDashboardDiagnosticsResponse + 197, // 316: p2pstream.v1.AgentManagementService.GetTrafficTraceSettings:output_type -> p2pstream.v1.GetTrafficTraceSettingsResponse + 199, // 317: p2pstream.v1.AgentManagementService.SetTrafficTraceSettings:output_type -> p2pstream.v1.SetTrafficTraceSettingsResponse + 202, // 318: p2pstream.v1.AgentManagementService.StreamTrafficTraceEvents:output_type -> p2pstream.v1.StreamTrafficTraceEventsResponse + 204, // 319: p2pstream.v1.AgentManagementService.GetSetupState:output_type -> p2pstream.v1.GetSetupStateResponse + 206, // 320: p2pstream.v1.AgentManagementService.SetupAdmin:output_type -> p2pstream.v1.SetupAdminResponse + 208, // 321: p2pstream.v1.AgentManagementService.Login:output_type -> p2pstream.v1.LoginResponse + 210, // 322: p2pstream.v1.AgentManagementService.Logout:output_type -> p2pstream.v1.LogoutResponse + 212, // 323: p2pstream.v1.AgentManagementService.GetCurrentUser:output_type -> p2pstream.v1.GetCurrentUserResponse + 214, // 324: p2pstream.v1.AgentManagementService.StartProxy:output_type -> p2pstream.v1.StartProxyResponse + 216, // 325: p2pstream.v1.AgentManagementService.StopProxy:output_type -> p2pstream.v1.StopProxyResponse + 72, // 326: p2pstream.v1.AgentManagementService.GetPublicProxyConfig:output_type -> p2pstream.v1.GetPublicProxyConfigResponse + 77, // 327: p2pstream.v1.AgentManagementService.CreatePublicResponseTemplate:output_type -> p2pstream.v1.CreatePublicResponseTemplateResponse + 79, // 328: p2pstream.v1.AgentManagementService.UpdatePublicResponseTemplate:output_type -> p2pstream.v1.UpdatePublicResponseTemplateResponse + 81, // 329: p2pstream.v1.AgentManagementService.DeletePublicResponseTemplate:output_type -> p2pstream.v1.DeletePublicResponseTemplateResponse + 75, // 330: p2pstream.v1.AgentManagementService.ListPublicRouteTargetHealthTraces:output_type -> p2pstream.v1.ListPublicRouteTargetHealthTracesResponse + 83, // 331: p2pstream.v1.AgentManagementService.CreateAgent:output_type -> p2pstream.v1.CreateAgentResponse + 85, // 332: p2pstream.v1.AgentManagementService.UpdateAgent:output_type -> p2pstream.v1.UpdateAgentResponse + 87, // 333: p2pstream.v1.AgentManagementService.DeleteAgent:output_type -> p2pstream.v1.DeleteAgentResponse + 89, // 334: p2pstream.v1.AgentManagementService.RotateAgentToken:output_type -> p2pstream.v1.RotateAgentTokenResponse + 92, // 335: p2pstream.v1.AgentManagementService.CreateManagementAccessToken:output_type -> p2pstream.v1.CreateManagementAccessTokenResponse + 94, // 336: p2pstream.v1.AgentManagementService.ListManagementAccessTokens:output_type -> p2pstream.v1.ListManagementAccessTokensResponse + 96, // 337: p2pstream.v1.AgentManagementService.DeleteManagementAccessToken:output_type -> p2pstream.v1.DeleteManagementAccessTokenResponse + 100, // 338: p2pstream.v1.AgentManagementService.ListEnvironments:output_type -> p2pstream.v1.ListEnvironmentsResponse + 102, // 339: p2pstream.v1.AgentManagementService.CreateEnvironment:output_type -> p2pstream.v1.CreateEnvironmentResponse + 104, // 340: p2pstream.v1.AgentManagementService.UpdateEnvironment:output_type -> p2pstream.v1.UpdateEnvironmentResponse + 106, // 341: p2pstream.v1.AgentManagementService.DeleteEnvironment:output_type -> p2pstream.v1.DeleteEnvironmentResponse + 108, // 342: p2pstream.v1.AgentManagementService.DiscoverEnvironmentCertificate:output_type -> p2pstream.v1.DiscoverEnvironmentCertificateResponse + 110, // 343: p2pstream.v1.AgentManagementService.TrustEnvironmentCertificate:output_type -> p2pstream.v1.TrustEnvironmentCertificateResponse + 112, // 344: p2pstream.v1.AgentManagementService.TestEnvironment:output_type -> p2pstream.v1.TestEnvironmentResponse + 114, // 345: p2pstream.v1.AgentManagementService.CreatePublicListener:output_type -> p2pstream.v1.CreatePublicListenerResponse + 116, // 346: p2pstream.v1.AgentManagementService.UpdatePublicListener:output_type -> p2pstream.v1.UpdatePublicListenerResponse + 118, // 347: p2pstream.v1.AgentManagementService.DeletePublicListener:output_type -> p2pstream.v1.DeletePublicListenerResponse + 120, // 348: p2pstream.v1.AgentManagementService.EnablePublicListener:output_type -> p2pstream.v1.EnablePublicListenerResponse + 122, // 349: p2pstream.v1.AgentManagementService.DisablePublicListener:output_type -> p2pstream.v1.DisablePublicListenerResponse + 124, // 350: p2pstream.v1.AgentManagementService.StartPublicListener:output_type -> p2pstream.v1.StartPublicListenerResponse + 126, // 351: p2pstream.v1.AgentManagementService.StopPublicListener:output_type -> p2pstream.v1.StopPublicListenerResponse + 128, // 352: p2pstream.v1.AgentManagementService.CreatePublicRoute:output_type -> p2pstream.v1.CreatePublicRouteResponse + 130, // 353: p2pstream.v1.AgentManagementService.UpdatePublicRoute:output_type -> p2pstream.v1.UpdatePublicRouteResponse + 132, // 354: p2pstream.v1.AgentManagementService.DeletePublicRoute:output_type -> p2pstream.v1.DeletePublicRouteResponse + 134, // 355: p2pstream.v1.AgentManagementService.CreatePublicTlsDnsCredential:output_type -> p2pstream.v1.CreatePublicTlsDnsCredentialResponse + 136, // 356: p2pstream.v1.AgentManagementService.UpdatePublicTlsDnsCredential:output_type -> p2pstream.v1.UpdatePublicTlsDnsCredentialResponse + 138, // 357: p2pstream.v1.AgentManagementService.DeletePublicTlsDnsCredential:output_type -> p2pstream.v1.DeletePublicTlsDnsCredentialResponse + 140, // 358: p2pstream.v1.AgentManagementService.CreatePublicTlsCertificate:output_type -> p2pstream.v1.CreatePublicTlsCertificateResponse + 142, // 359: p2pstream.v1.AgentManagementService.UpdatePublicTlsCertificate:output_type -> p2pstream.v1.UpdatePublicTlsCertificateResponse + 144, // 360: p2pstream.v1.AgentManagementService.DeletePublicTlsCertificate:output_type -> p2pstream.v1.DeletePublicTlsCertificateResponse + 146, // 361: p2pstream.v1.AgentManagementService.RenewPublicTlsCertificate:output_type -> p2pstream.v1.RenewPublicTlsCertificateResponse + 148, // 362: p2pstream.v1.AgentManagementService.CreatePublicRateLimitRule:output_type -> p2pstream.v1.CreatePublicRateLimitRuleResponse + 150, // 363: p2pstream.v1.AgentManagementService.UpdatePublicRateLimitRule:output_type -> p2pstream.v1.UpdatePublicRateLimitRuleResponse + 152, // 364: p2pstream.v1.AgentManagementService.DeletePublicRateLimitRule:output_type -> p2pstream.v1.DeletePublicRateLimitRuleResponse + 154, // 365: p2pstream.v1.AgentManagementService.CreatePublicTrafficShaperRule:output_type -> p2pstream.v1.CreatePublicTrafficShaperRuleResponse + 156, // 366: p2pstream.v1.AgentManagementService.UpdatePublicTrafficShaperRule:output_type -> p2pstream.v1.UpdatePublicTrafficShaperRuleResponse + 158, // 367: p2pstream.v1.AgentManagementService.DeletePublicTrafficShaperRule:output_type -> p2pstream.v1.DeletePublicTrafficShaperRuleResponse + 160, // 368: p2pstream.v1.AgentManagementService.CreatePublicWafCaptchaProvider:output_type -> p2pstream.v1.CreatePublicWafCaptchaProviderResponse + 162, // 369: p2pstream.v1.AgentManagementService.UpdatePublicWafCaptchaProvider:output_type -> p2pstream.v1.UpdatePublicWafCaptchaProviderResponse + 164, // 370: p2pstream.v1.AgentManagementService.DeletePublicWafCaptchaProvider:output_type -> p2pstream.v1.DeletePublicWafCaptchaProviderResponse + 166, // 371: p2pstream.v1.AgentManagementService.CreatePublicWafRule:output_type -> p2pstream.v1.CreatePublicWafRuleResponse + 168, // 372: p2pstream.v1.AgentManagementService.UpdatePublicWafRule:output_type -> p2pstream.v1.UpdatePublicWafRuleResponse + 170, // 373: p2pstream.v1.AgentManagementService.DeletePublicWafRule:output_type -> p2pstream.v1.DeletePublicWafRuleResponse + 172, // 374: p2pstream.v1.AgentManagementService.CreatePublicCacheRule:output_type -> p2pstream.v1.CreatePublicCacheRuleResponse + 174, // 375: p2pstream.v1.AgentManagementService.UpdatePublicCacheRule:output_type -> p2pstream.v1.UpdatePublicCacheRuleResponse + 176, // 376: p2pstream.v1.AgentManagementService.DeletePublicCacheRule:output_type -> p2pstream.v1.DeletePublicCacheRuleResponse + 178, // 377: p2pstream.v1.AgentManagementService.UpdatePublicCacheSettings:output_type -> p2pstream.v1.UpdatePublicCacheSettingsResponse + 180, // 378: p2pstream.v1.AgentManagementService.PurgePublicCache:output_type -> p2pstream.v1.PurgePublicCacheResponse + 312, // [312:379] is the sub-list for method output_type + 245, // [245:312] is the sub-list for method input_type + 245, // [245:245] is the sub-list for extension type_name + 245, // [245:245] is the sub-list for extension extendee + 0, // [0:245] is the sub-list for field type_name } func init() { file_proto_p2pstream_v1_management_proto_init() } @@ -17817,7 +18435,7 @@ func file_proto_p2pstream_v1_management_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_p2pstream_v1_management_proto_rawDesc), len(file_proto_p2pstream_v1_management_proto_rawDesc)), NumEnums: 35, - NumMessages: 185, + NumMessages: 190, NumExtensions: 0, NumServices: 1, }, diff --git a/gen/proto/p2pstream/v1/p2pstreamv1connect/management.connect.go b/gen/proto/p2pstream/v1/p2pstreamv1connect/management.connect.go index 396d4c8..ac25048 100644 --- a/gen/proto/p2pstream/v1/p2pstreamv1connect/management.connect.go +++ b/gen/proto/p2pstream/v1/p2pstreamv1connect/management.connect.go @@ -42,6 +42,9 @@ const ( // AgentManagementServiceGetDashboardProcedure is the fully-qualified name of the // AgentManagementService's GetDashboard RPC. AgentManagementServiceGetDashboardProcedure = "/p2pstream.v1.AgentManagementService/GetDashboard" + // AgentManagementServiceGetDashboardDiagnosticsProcedure is the fully-qualified name of the + // AgentManagementService's GetDashboardDiagnostics RPC. + AgentManagementServiceGetDashboardDiagnosticsProcedure = "/p2pstream.v1.AgentManagementService/GetDashboardDiagnostics" // AgentManagementServiceGetTrafficTraceSettingsProcedure is the fully-qualified name of the // AgentManagementService's GetTrafficTraceSettings RPC. AgentManagementServiceGetTrafficTraceSettingsProcedure = "/p2pstream.v1.AgentManagementService/GetTrafficTraceSettings" @@ -238,6 +241,7 @@ type AgentManagementServiceClient interface { ReportStats(context.Context, *connect.Request[v1.AgentStatsRequest]) (*connect.Response[v1.AgentStatsResponse], error) GetStatus(context.Context, *connect.Request[v1.GetStatusRequest]) (*connect.Response[v1.GetStatusResponse], error) GetDashboard(context.Context, *connect.Request[v1.GetDashboardRequest]) (*connect.Response[v1.GetDashboardResponse], error) + GetDashboardDiagnostics(context.Context, *connect.Request[v1.GetDashboardDiagnosticsRequest]) (*connect.Response[v1.GetDashboardDiagnosticsResponse], error) GetTrafficTraceSettings(context.Context, *connect.Request[v1.GetTrafficTraceSettingsRequest]) (*connect.Response[v1.GetTrafficTraceSettingsResponse], error) SetTrafficTraceSettings(context.Context, *connect.Request[v1.SetTrafficTraceSettingsRequest]) (*connect.Response[v1.SetTrafficTraceSettingsResponse], error) StreamTrafficTraceEvents(context.Context, *connect.Request[v1.StreamTrafficTraceEventsRequest]) (*connect.ServerStreamForClient[v1.StreamTrafficTraceEventsResponse], error) @@ -332,6 +336,12 @@ func NewAgentManagementServiceClient(httpClient connect.HTTPClient, baseURL stri connect.WithSchema(agentManagementServiceMethods.ByName("GetDashboard")), connect.WithClientOptions(opts...), ), + getDashboardDiagnostics: connect.NewClient[v1.GetDashboardDiagnosticsRequest, v1.GetDashboardDiagnosticsResponse]( + httpClient, + baseURL+AgentManagementServiceGetDashboardDiagnosticsProcedure, + connect.WithSchema(agentManagementServiceMethods.ByName("GetDashboardDiagnostics")), + connect.WithClientOptions(opts...), + ), getTrafficTraceSettings: connect.NewClient[v1.GetTrafficTraceSettingsRequest, v1.GetTrafficTraceSettingsResponse]( httpClient, baseURL+AgentManagementServiceGetTrafficTraceSettingsProcedure, @@ -718,6 +728,7 @@ type agentManagementServiceClient struct { reportStats *connect.Client[v1.AgentStatsRequest, v1.AgentStatsResponse] getStatus *connect.Client[v1.GetStatusRequest, v1.GetStatusResponse] getDashboard *connect.Client[v1.GetDashboardRequest, v1.GetDashboardResponse] + getDashboardDiagnostics *connect.Client[v1.GetDashboardDiagnosticsRequest, v1.GetDashboardDiagnosticsResponse] getTrafficTraceSettings *connect.Client[v1.GetTrafficTraceSettingsRequest, v1.GetTrafficTraceSettingsResponse] setTrafficTraceSettings *connect.Client[v1.SetTrafficTraceSettingsRequest, v1.SetTrafficTraceSettingsResponse] streamTrafficTraceEvents *connect.Client[v1.StreamTrafficTraceEventsRequest, v1.StreamTrafficTraceEventsResponse] @@ -798,6 +809,11 @@ func (c *agentManagementServiceClient) GetDashboard(ctx context.Context, req *co return c.getDashboard.CallUnary(ctx, req) } +// GetDashboardDiagnostics calls p2pstream.v1.AgentManagementService.GetDashboardDiagnostics. +func (c *agentManagementServiceClient) GetDashboardDiagnostics(ctx context.Context, req *connect.Request[v1.GetDashboardDiagnosticsRequest]) (*connect.Response[v1.GetDashboardDiagnosticsResponse], error) { + return c.getDashboardDiagnostics.CallUnary(ctx, req) +} + // GetTrafficTraceSettings calls p2pstream.v1.AgentManagementService.GetTrafficTraceSettings. func (c *agentManagementServiceClient) GetTrafficTraceSettings(ctx context.Context, req *connect.Request[v1.GetTrafficTraceSettingsRequest]) (*connect.Response[v1.GetTrafficTraceSettingsResponse], error) { return c.getTrafficTraceSettings.CallUnary(ctx, req) @@ -1136,6 +1152,7 @@ type AgentManagementServiceHandler interface { ReportStats(context.Context, *connect.Request[v1.AgentStatsRequest]) (*connect.Response[v1.AgentStatsResponse], error) GetStatus(context.Context, *connect.Request[v1.GetStatusRequest]) (*connect.Response[v1.GetStatusResponse], error) GetDashboard(context.Context, *connect.Request[v1.GetDashboardRequest]) (*connect.Response[v1.GetDashboardResponse], error) + GetDashboardDiagnostics(context.Context, *connect.Request[v1.GetDashboardDiagnosticsRequest]) (*connect.Response[v1.GetDashboardDiagnosticsResponse], error) GetTrafficTraceSettings(context.Context, *connect.Request[v1.GetTrafficTraceSettingsRequest]) (*connect.Response[v1.GetTrafficTraceSettingsResponse], error) SetTrafficTraceSettings(context.Context, *connect.Request[v1.SetTrafficTraceSettingsRequest]) (*connect.Response[v1.SetTrafficTraceSettingsResponse], error) StreamTrafficTraceEvents(context.Context, *connect.Request[v1.StreamTrafficTraceEventsRequest], *connect.ServerStream[v1.StreamTrafficTraceEventsResponse]) error @@ -1226,6 +1243,12 @@ func NewAgentManagementServiceHandler(svc AgentManagementServiceHandler, opts .. connect.WithSchema(agentManagementServiceMethods.ByName("GetDashboard")), connect.WithHandlerOptions(opts...), ) + agentManagementServiceGetDashboardDiagnosticsHandler := connect.NewUnaryHandler( + AgentManagementServiceGetDashboardDiagnosticsProcedure, + svc.GetDashboardDiagnostics, + connect.WithSchema(agentManagementServiceMethods.ByName("GetDashboardDiagnostics")), + connect.WithHandlerOptions(opts...), + ) agentManagementServiceGetTrafficTraceSettingsHandler := connect.NewUnaryHandler( AgentManagementServiceGetTrafficTraceSettingsProcedure, svc.GetTrafficTraceSettings, @@ -1612,6 +1635,8 @@ func NewAgentManagementServiceHandler(svc AgentManagementServiceHandler, opts .. agentManagementServiceGetStatusHandler.ServeHTTP(w, r) case AgentManagementServiceGetDashboardProcedure: agentManagementServiceGetDashboardHandler.ServeHTTP(w, r) + case AgentManagementServiceGetDashboardDiagnosticsProcedure: + agentManagementServiceGetDashboardDiagnosticsHandler.ServeHTTP(w, r) case AgentManagementServiceGetTrafficTraceSettingsProcedure: agentManagementServiceGetTrafficTraceSettingsHandler.ServeHTTP(w, r) case AgentManagementServiceSetTrafficTraceSettingsProcedure: @@ -1759,6 +1784,10 @@ func (UnimplementedAgentManagementServiceHandler) GetDashboard(context.Context, return nil, connect.NewError(connect.CodeUnimplemented, errors.New("p2pstream.v1.AgentManagementService.GetDashboard is not implemented")) } +func (UnimplementedAgentManagementServiceHandler) GetDashboardDiagnostics(context.Context, *connect.Request[v1.GetDashboardDiagnosticsRequest]) (*connect.Response[v1.GetDashboardDiagnosticsResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("p2pstream.v1.AgentManagementService.GetDashboardDiagnostics is not implemented")) +} + func (UnimplementedAgentManagementServiceHandler) GetTrafficTraceSettings(context.Context, *connect.Request[v1.GetTrafficTraceSettingsRequest]) (*connect.Response[v1.GetTrafficTraceSettingsResponse], error) { return nil, connect.NewError(connect.CodeUnimplemented, errors.New("p2pstream.v1.AgentManagementService.GetTrafficTraceSettings is not implemented")) } diff --git a/internal/db/connection.go b/internal/db/connection.go index 4c27f2d..f9a1e09 100644 --- a/internal/db/connection.go +++ b/internal/db/connection.go @@ -212,6 +212,9 @@ func (db *DB) migrate() error { status_code INTEGER NOT NULL, duration_ms INTEGER NOT NULL, error_kind TEXT NOT NULL DEFAULT '', + method TEXT NOT NULL DEFAULT '', + host TEXT NOT NULL DEFAULT '', + path_prefix TEXT NOT NULL DEFAULT '', listener_id INTEGER, route_target_id INTEGER, route_id INTEGER, @@ -266,7 +269,23 @@ func (db *DB) migrate() error { response_bytes INTEGER NOT NULL DEFAULT 0, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (bucket_unix_millis, listener_id, route_target_id, route_id, agent_id, error_kind, status_class) + PRIMARY KEY (bucket_unix_millis, listener_id, route_target_id, route_id, agent_id, error_kind, status_class) + ); + + CREATE TABLE IF NOT EXISTS proxy_request_status_rollup_minutes ( + bucket_unix_millis INTEGER NOT NULL, + status_code INTEGER NOT NULL, + requests INTEGER NOT NULL DEFAULT 0, + success INTEGER NOT NULL DEFAULT 0, + client_error INTEGER NOT NULL DEFAULT 0, + server_error INTEGER NOT NULL DEFAULT 0, + internal_error INTEGER NOT NULL DEFAULT 0, + duration_ms_sum INTEGER NOT NULL DEFAULT 0, + request_bytes INTEGER NOT NULL DEFAULT 0, + response_bytes INTEGER NOT NULL DEFAULT 0, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (bucket_unix_millis, status_code) ); CREATE TABLE IF NOT EXISTS agent_stat_rollup_minutes ( @@ -605,6 +624,9 @@ func (db *DB) migrate() error { `ALTER TABLE proxy_request_events ADD COLUMN cache_rule_id INTEGER`, `ALTER TABLE proxy_request_events ADD COLUMN cache_status TEXT NOT NULL DEFAULT ''`, `ALTER TABLE proxy_request_events ADD COLUMN cache_bytes INTEGER NOT NULL DEFAULT 0`, + `ALTER TABLE proxy_request_events ADD COLUMN method TEXT NOT NULL DEFAULT ''`, + `ALTER TABLE proxy_request_events ADD COLUMN host TEXT NOT NULL DEFAULT ''`, + `ALTER TABLE proxy_request_events ADD COLUMN path_prefix TEXT NOT NULL DEFAULT ''`, `ALTER TABLE public_routes ADD COLUMN target_load_balancing TEXT NOT NULL DEFAULT 'round_robin'`, `ALTER TABLE public_routes ADD COLUMN is_default INTEGER NOT NULL DEFAULT 0`, `ALTER TABLE public_tls_certificates ADD COLUMN source TEXT NOT NULL DEFAULT 'manual'`, @@ -655,6 +677,9 @@ func (db *DB) migrate() error { if _, err := db.Exec(`CREATE INDEX IF NOT EXISTS idx_proxy_request_events_agent_id ON proxy_request_events (agent_id)`); err != nil { return err } + if _, err := db.Exec(`CREATE INDEX IF NOT EXISTS idx_proxy_request_events_recent_problem ON proxy_request_events (occurred_at DESC) WHERE status_code >= 400 OR error_kind != ''`); err != nil { + return err + } if _, err := db.Exec(`CREATE INDEX IF NOT EXISTS idx_agent_stats_agent_id ON agent_stats (agent_id)`); err != nil { return err } @@ -1112,6 +1137,22 @@ func (db *DB) migrateObservabilityRollups() error { PRIMARY KEY (bucket_unix_millis, listener_id, route_target_id, route_id, agent_id, error_kind, status_class) ); + CREATE TABLE IF NOT EXISTS proxy_request_status_rollup_minutes ( + bucket_unix_millis INTEGER NOT NULL, + status_code INTEGER NOT NULL, + requests INTEGER NOT NULL DEFAULT 0, + success INTEGER NOT NULL DEFAULT 0, + client_error INTEGER NOT NULL DEFAULT 0, + server_error INTEGER NOT NULL DEFAULT 0, + internal_error INTEGER NOT NULL DEFAULT 0, + duration_ms_sum INTEGER NOT NULL DEFAULT 0, + request_bytes INTEGER NOT NULL DEFAULT 0, + response_bytes INTEGER NOT NULL DEFAULT 0, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (bucket_unix_millis, status_code) + ); + CREATE TABLE IF NOT EXISTS agent_stat_rollup_minutes ( bucket_unix_millis INTEGER PRIMARY KEY, samples INTEGER NOT NULL DEFAULT 0, @@ -1155,6 +1196,40 @@ func (db *DB) migrateObservabilityRollups() error { if err != nil { return err } + var statusRollupRows, proxyBackfilledThroughID, proxyBackfillUpperID int64 + if err := db.QueryRow(` + SELECT + (SELECT COUNT(*) FROM proxy_request_status_rollup_minutes), + proxy_backfilled_through_id, + proxy_backfill_upper_id + FROM observability_rollup_state + WHERE id = 1 + `).Scan(&statusRollupRows, &proxyBackfilledThroughID, &proxyBackfillUpperID); err != nil { + return err + } + if statusRollupRows == 0 && proxyBackfilledThroughID >= proxyBackfillUpperID { + if _, err := db.Exec(` + INSERT INTO proxy_request_status_rollup_minutes ( + bucket_unix_millis, status_code, requests, success, client_error, server_error, + internal_error, duration_ms_sum, request_bytes, response_bytes + ) + SELECT + CAST((unixepoch(occurred_at) / 60) * 60 * 1000 AS INTEGER) AS bucket_unix_millis, + status_code, + COUNT(*) AS requests, + CAST(COALESCE(SUM(CASE WHEN status_code >= 200 AND status_code < 400 THEN 1 ELSE 0 END), 0) AS INTEGER) AS success, + CAST(COALESCE(SUM(CASE WHEN status_code >= 400 AND status_code < 500 THEN 1 ELSE 0 END), 0) AS INTEGER) AS client_error, + CAST(COALESCE(SUM(CASE WHEN status_code >= 500 THEN 1 ELSE 0 END), 0) AS INTEGER) AS server_error, + CAST(COALESCE(SUM(CASE WHEN error_kind != '' THEN 1 ELSE 0 END), 0) AS INTEGER) AS internal_error, + CAST(COALESCE(SUM(duration_ms), 0) AS INTEGER) AS duration_ms_sum, + CAST(COALESCE(SUM(request_bytes), 0) AS INTEGER) AS request_bytes, + CAST(COALESCE(SUM(response_bytes), 0) AS INTEGER) AS response_bytes + FROM proxy_request_events + GROUP BY bucket_unix_millis, status_code + `); err != nil { + return err + } + } return db.migrateProxyObservabilityTargetOnly() } @@ -1184,6 +1259,7 @@ func (db *DB) migrateProxyObservabilityTargetOnly() error { DELETE FROM proxy_request_events; DELETE FROM proxy_request_rollup_minutes; DELETE FROM proxy_request_tuple_rollup_minutes; + DELETE FROM proxy_request_status_rollup_minutes; UPDATE observability_rollup_state SET proxy_backfill_upper_id = 0, proxy_backfilled_through_id = 0, @@ -1201,6 +1277,9 @@ func (db *DB) migrateProxyObservabilityTargetOnly() error { status_code INTEGER NOT NULL, duration_ms INTEGER NOT NULL, error_kind TEXT NOT NULL DEFAULT '', + method TEXT NOT NULL DEFAULT '', + host TEXT NOT NULL DEFAULT '', + path_prefix TEXT NOT NULL DEFAULT '', listener_id INTEGER, route_target_id INTEGER, route_id INTEGER, diff --git a/internal/db/connection_test.go b/internal/db/connection_test.go index 60c027f..cd79264 100644 --- a/internal/db/connection_test.go +++ b/internal/db/connection_test.go @@ -84,7 +84,7 @@ func TestMigrationCreatesMultiAgentRoutingSchema(t *testing.T) { } defer func() { _ = database.Close() }() - for _, table := range []string{"agents", "public_agent_labels", "public_route_targets", "public_route_target_upstream_headers", "public_route_target_response_headers", "public_waf_captcha_providers", "public_waf_rules", "public_waf_settings", "public_cache_settings", "public_cache_rules", "public_cache_entries", "proxy_request_rollup_minutes", "proxy_request_tuple_rollup_minutes", "agent_stat_rollup_minutes", "observability_rollup_state"} { + for _, table := range []string{"agents", "public_agent_labels", "public_route_targets", "public_route_target_upstream_headers", "public_route_target_response_headers", "public_waf_captcha_providers", "public_waf_rules", "public_waf_settings", "public_cache_settings", "public_cache_rules", "public_cache_entries", "proxy_request_rollup_minutes", "proxy_request_tuple_rollup_minutes", "proxy_request_status_rollup_minutes", "agent_stat_rollup_minutes", "observability_rollup_state"} { var name string if err := database.QueryRowContext(context.Background(), `SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?`, table).Scan(&name); err != nil { t.Fatalf("expected table %s: %v", table, err) @@ -97,7 +97,7 @@ func TestMigrationCreatesMultiAgentRoutingSchema(t *testing.T) { } proxyEventColumns := tableColumns(t, database, "proxy_request_events") - for _, column := range []string{"request_bytes", "response_bytes", "waf_rule_id", "waf_action", "cache_rule_id", "cache_status", "cache_bytes"} { + for _, column := range []string{"request_bytes", "response_bytes", "waf_rule_id", "waf_action", "cache_rule_id", "cache_status", "cache_bytes", "method", "host", "path_prefix"} { if !containsString(proxyEventColumns, column) { t.Fatalf("proxy_request_events missing column %s in %v", column, proxyEventColumns) } @@ -116,6 +116,7 @@ func TestMigrationCreatesMultiAgentRoutingSchema(t *testing.T) { for _, index := range []string{ "idx_proxy_request_events_route_id", "idx_proxy_request_events_agent_id", + "idx_proxy_request_events_recent_problem", "idx_proxy_request_events_waf_rule_id", "idx_proxy_request_events_cache_rule_id", "idx_public_waf_rules_priority", @@ -427,7 +428,7 @@ func TestMigrationUpgradesLegacySchemaWithAgentColumns(t *testing.T) { for table, columns := range map[string][]string{ "connections": {"agent_id"}, "agent_stats": {"agent_id", "req_internal_error", "cpu_percent"}, - "proxy_request_events": {"agent_id", "listener_id", "route_id", "route_target_id", "waf_rule_id", "waf_action", "request_bytes", "response_bytes", "cache_rule_id", "cache_status", "cache_bytes"}, + "proxy_request_events": {"agent_id", "listener_id", "route_id", "route_target_id", "waf_rule_id", "waf_action", "request_bytes", "response_bytes", "cache_rule_id", "cache_status", "cache_bytes", "method", "host", "path_prefix"}, "public_cache_rules": {"allow_cookie_requests"}, } { got := tableColumns(t, database, table) @@ -455,6 +456,7 @@ func TestMigrationUpgradesLegacySchemaWithAgentColumns(t *testing.T) { for _, index := range []string{ "idx_proxy_request_events_route_id", "idx_proxy_request_events_agent_id", + "idx_proxy_request_events_recent_problem", "idx_proxy_request_events_waf_rule_id", "idx_proxy_request_events_cache_rule_id", } { @@ -462,7 +464,7 @@ func TestMigrationUpgradesLegacySchemaWithAgentColumns(t *testing.T) { t.Fatalf("expected %s after migration", index) } } - for _, table := range []string{"public_cache_settings", "public_cache_rules", "public_cache_entries", "proxy_request_rollup_minutes", "proxy_request_tuple_rollup_minutes", "agent_stat_rollup_minutes", "observability_rollup_state"} { + for _, table := range []string{"public_cache_settings", "public_cache_rules", "public_cache_entries", "proxy_request_rollup_minutes", "proxy_request_tuple_rollup_minutes", "proxy_request_status_rollup_minutes", "agent_stat_rollup_minutes", "observability_rollup_state"} { var name string if err := database.QueryRowContext(context.Background(), `SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?`, table).Scan(&name); err != nil { t.Fatalf("expected migrated table %s: %v", table, err) @@ -637,6 +639,9 @@ func TestMigrationResetsProxyObservabilityAndRenamesWAFRouteTargetTrigger(t *tes if countRows(t, database, `SELECT COUNT(*) FROM proxy_request_tuple_rollup_minutes`) != 0 { t.Fatal("expected proxy request tuple rollups to be reset") } + if countRows(t, database, `SELECT COUNT(*) FROM proxy_request_status_rollup_minutes`) != 0 { + t.Fatal("expected proxy request status rollups to be reset") + } if countRows(t, database, `SELECT COUNT(*) FROM agent_stats`) != 1 { t.Fatal("expected agent stats to be preserved") } @@ -674,7 +679,8 @@ func TestMigrationResetsProxyObservabilityAndRenamesWAFRouteTargetTrigger(t *tes defer func() { _ = database.Close() }() if countRows(t, database, `SELECT COUNT(*) FROM proxy_request_events`) != 0 || countRows(t, database, `SELECT COUNT(*) FROM proxy_request_rollup_minutes`) != 0 || - countRows(t, database, `SELECT COUNT(*) FROM proxy_request_tuple_rollup_minutes`) != 0 { + countRows(t, database, `SELECT COUNT(*) FROM proxy_request_tuple_rollup_minutes`) != 0 || + countRows(t, database, `SELECT COUNT(*) FROM proxy_request_status_rollup_minutes`) != 0 { t.Fatal("expected proxy observability reset migration to be idempotent") } if countRows(t, database, `SELECT COUNT(*) FROM agent_stats`) != 1 { diff --git a/internal/db/models.go b/internal/db/models.go index 1eb43ae..92dd97d 100644 --- a/internal/db/models.go +++ b/internal/db/models.go @@ -111,6 +111,9 @@ type ProxyRequestEvent struct { StatusCode int64 `json:"status_code"` DurationMs int64 `json:"duration_ms"` ErrorKind string `json:"error_kind"` + Method string `json:"method"` + Host string `json:"host"` + PathPrefix string `json:"path_prefix"` ListenerID sql.NullInt64 `json:"listener_id"` RouteID sql.NullInt64 `json:"route_id"` RouteTargetID sql.NullInt64 `json:"route_target_id"` @@ -147,6 +150,21 @@ type ProxyRequestRollupMinute struct { UpdatedAt time.Time `json:"updated_at"` } +type ProxyRequestStatusRollupMinute struct { + BucketUnixMillis int64 `json:"bucket_unix_millis"` + StatusCode int64 `json:"status_code"` + Requests int64 `json:"requests"` + Success int64 `json:"success"` + ClientError int64 `json:"client_error"` + ServerError int64 `json:"server_error"` + InternalError int64 `json:"internal_error"` + DurationMsSum int64 `json:"duration_ms_sum"` + RequestBytes int64 `json:"request_bytes"` + ResponseBytes int64 `json:"response_bytes"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + type ProxyRequestTupleRollupMinute struct { BucketUnixMillis int64 `json:"bucket_unix_millis"` ListenerID int64 `json:"listener_id"` diff --git a/internal/db/querier.go b/internal/db/querier.go index d7831a0..c1a566d 100644 --- a/internal/db/querier.go +++ b/internal/db/querier.go @@ -13,6 +13,7 @@ import ( type Querier interface { BackfillAgentStatRollupMinutesRange(ctx context.Context, arg BackfillAgentStatRollupMinutesRangeParams) error BackfillProxyRequestRollupMinutesRange(ctx context.Context, arg BackfillProxyRequestRollupMinutesRangeParams) error + BackfillProxyRequestStatusRollupMinutesRange(ctx context.Context, arg BackfillProxyRequestStatusRollupMinutesRangeParams) error BackfillProxyRequestTupleRollupMinutesRange(ctx context.Context, arg BackfillProxyRequestTupleRollupMinutesRangeParams) error ClearEnvironmentTrust(ctx context.Context, id int64) (Environment, error) CloseOpenConnectionsAt(ctx context.Context, disconnectedAt sql.NullTime) error @@ -49,6 +50,7 @@ type Querier interface { DeleteOldestProxyRequestEventsOverLimit(ctx context.Context, arg DeleteOldestProxyRequestEventsOverLimitParams) (int64, error) DeleteProxyRequestEventsBefore(ctx context.Context, occurredAt time.Time) error DeleteProxyRequestRollupsBefore(ctx context.Context, bucketUnixMillis int64) error + DeleteProxyRequestStatusRollupsBefore(ctx context.Context, bucketUnixMillis int64) error DeleteProxyRequestTupleRollupsBefore(ctx context.Context, bucketUnixMillis int64) error DeletePublicCacheEntry(ctx context.Context, keyDigest string) error DeletePublicCacheRule(ctx context.Context, id int64) error @@ -111,10 +113,16 @@ type Querier interface { ListConnectionsSince(ctx context.Context, arg ListConnectionsSinceParams) ([]ListConnectionsSinceRow, error) ListEnvironments(ctx context.Context) ([]Environment, error) ListManagementAccessTokens(ctx context.Context) ([]ManagementAccessToken, error) + ListProblemProxyAgentsRollupsSince(ctx context.Context, bucketUnixMillis int64) ([]ListProblemProxyAgentsRollupsSinceRow, error) + ListProblemProxyErrorKindsRollupsSince(ctx context.Context, bucketUnixMillis int64) ([]ListProblemProxyErrorKindsRollupsSinceRow, error) + ListProblemProxyListenersRollupsSince(ctx context.Context, bucketUnixMillis int64) ([]ListProblemProxyListenersRollupsSinceRow, error) + ListProblemProxyRouteTargetsRollupsSince(ctx context.Context, bucketUnixMillis int64) ([]ListProblemProxyRouteTargetsRollupsSinceRow, error) + ListProblemProxyRoutesRollupsSince(ctx context.Context, bucketUnixMillis int64) ([]ListProblemProxyRoutesRollupsSinceRow, error) ListProxyRequestRollupMinutesSince(ctx context.Context, bucketUnixMillis int64) ([]ListProxyRequestRollupMinutesSinceRow, error) ListProxyRequestTupleRollupMinutesSince(ctx context.Context, bucketUnixMillis int64) ([]ListProxyRequestTupleRollupMinutesSinceRow, error) ListProxyStatusClassesRollupsSince(ctx context.Context, bucketUnixMillis int64) ([]ListProxyStatusClassesRollupsSinceRow, error) ListProxyStatusClassesSince(ctx context.Context, occurredAt time.Time) ([]ListProxyStatusClassesSinceRow, error) + ListProxyStatusCodeRollupsSince(ctx context.Context, bucketUnixMillis int64) ([]ListProxyStatusCodeRollupsSinceRow, error) ListProxyTrafficBucketRollupsSince(ctx context.Context, arg ListProxyTrafficBucketRollupsSinceParams) ([]ListProxyTrafficBucketRollupsSinceRow, error) ListProxyTrafficBucketsSince(ctx context.Context, arg ListProxyTrafficBucketsSinceParams) ([]ListProxyTrafficBucketsSinceRow, error) ListPublicCacheEntriesForCleanup(ctx context.Context, limit int64) ([]ListPublicCacheEntriesForCleanupRow, error) @@ -136,6 +144,7 @@ type Querier interface { ListPublicWafCaptchaProviders(ctx context.Context) ([]PublicWafCaptchaProvider, error) ListPublicWafRules(ctx context.Context) ([]PublicWafRule, error) ListRecentConnections(ctx context.Context, limit int64) ([]ListRecentConnectionsRow, error) + ListRecentProxyProblemSamplesSince(ctx context.Context, arg ListRecentProxyProblemSamplesSinceParams) ([]ListRecentProxyProblemSamplesSinceRow, error) ListTopProxyAgentsRollupsSince(ctx context.Context, bucketUnixMillis int64) ([]ListTopProxyAgentsRollupsSinceRow, error) ListTopProxyAgentsSince(ctx context.Context, occurredAt time.Time) ([]ListTopProxyAgentsSinceRow, error) ListTopProxyErrorKindsRollupsSince(ctx context.Context, bucketUnixMillis int64) ([]ListTopProxyErrorKindsRollupsSinceRow, error) @@ -187,6 +196,7 @@ type Querier interface { UpsertAgentStatRollupMinute(ctx context.Context, arg UpsertAgentStatRollupMinuteParams) error UpsertBootstrapAgent(ctx context.Context, arg UpsertBootstrapAgentParams) (Agent, error) UpsertProxyRequestRollupMinute(ctx context.Context, arg UpsertProxyRequestRollupMinuteParams) error + UpsertProxyRequestStatusRollupMinute(ctx context.Context, arg UpsertProxyRequestStatusRollupMinuteParams) error UpsertProxyRequestTupleRollupMinute(ctx context.Context, arg UpsertProxyRequestTupleRollupMinuteParams) error UpsertPublicCacheEntry(ctx context.Context, arg UpsertPublicCacheEntryParams) (PublicCacheEntry, error) UpsertPublicCacheSettingsDefaults(ctx context.Context) (PublicCacheSetting, error) diff --git a/internal/db/query.sql.go b/internal/db/query.sql.go index d74bbd1..902680b 100644 --- a/internal/db/query.sql.go +++ b/internal/db/query.sql.go @@ -124,6 +124,48 @@ func (q *Queries) BackfillProxyRequestRollupMinutesRange(ctx context.Context, ar return err } +const backfillProxyRequestStatusRollupMinutesRange = `-- name: BackfillProxyRequestStatusRollupMinutesRange :exec +INSERT INTO proxy_request_status_rollup_minutes ( + bucket_unix_millis, status_code, requests, success, client_error, server_error, + internal_error, duration_ms_sum, request_bytes, response_bytes +) +SELECT + CAST((unixepoch(occurred_at) / 60) * 60 * 1000 AS INTEGER) AS bucket_unix_millis, + status_code, + COUNT(*) AS requests, + CAST(COALESCE(SUM(CASE WHEN status_code >= 200 AND status_code < 400 THEN 1 ELSE 0 END), 0) AS INTEGER) AS success, + CAST(COALESCE(SUM(CASE WHEN status_code >= 400 AND status_code < 500 THEN 1 ELSE 0 END), 0) AS INTEGER) AS client_error, + CAST(COALESCE(SUM(CASE WHEN status_code >= 500 THEN 1 ELSE 0 END), 0) AS INTEGER) AS server_error, + CAST(COALESCE(SUM(CASE WHEN error_kind != '' THEN 1 ELSE 0 END), 0) AS INTEGER) AS internal_error, + CAST(COALESCE(SUM(duration_ms), 0) AS INTEGER) AS duration_ms_sum, + CAST(COALESCE(SUM(request_bytes), 0) AS INTEGER) AS request_bytes, + CAST(COALESCE(SUM(response_bytes), 0) AS INTEGER) AS response_bytes +FROM proxy_request_events +WHERE id > ?1 + AND id <= ?2 +GROUP BY bucket_unix_millis, status_code +ON CONFLICT(bucket_unix_millis, status_code) DO UPDATE SET + requests = proxy_request_status_rollup_minutes.requests + excluded.requests, + success = proxy_request_status_rollup_minutes.success + excluded.success, + client_error = proxy_request_status_rollup_minutes.client_error + excluded.client_error, + server_error = proxy_request_status_rollup_minutes.server_error + excluded.server_error, + internal_error = proxy_request_status_rollup_minutes.internal_error + excluded.internal_error, + duration_ms_sum = proxy_request_status_rollup_minutes.duration_ms_sum + excluded.duration_ms_sum, + request_bytes = proxy_request_status_rollup_minutes.request_bytes + excluded.request_bytes, + response_bytes = proxy_request_status_rollup_minutes.response_bytes + excluded.response_bytes, + updated_at = CURRENT_TIMESTAMP +` + +type BackfillProxyRequestStatusRollupMinutesRangeParams struct { + FromID int64 `json:"from_id"` + ThroughID int64 `json:"through_id"` +} + +func (q *Queries) BackfillProxyRequestStatusRollupMinutesRange(ctx context.Context, arg BackfillProxyRequestStatusRollupMinutesRangeParams) error { + _, err := q.db.ExecContext(ctx, backfillProxyRequestStatusRollupMinutesRange, arg.FromID, arg.ThroughID) + return err +} + const backfillProxyRequestTupleRollupMinutesRange = `-- name: BackfillProxyRequestTupleRollupMinutesRange :exec INSERT INTO proxy_request_tuple_rollup_minutes ( bucket_unix_millis, listener_id, route_target_id, route_id, agent_id, error_kind, status_class, @@ -1540,6 +1582,16 @@ func (q *Queries) DeleteProxyRequestRollupsBefore(ctx context.Context, bucketUni return err } +const deleteProxyRequestStatusRollupsBefore = `-- name: DeleteProxyRequestStatusRollupsBefore :exec +DELETE FROM proxy_request_status_rollup_minutes +WHERE bucket_unix_millis < ? +` + +func (q *Queries) DeleteProxyRequestStatusRollupsBefore(ctx context.Context, bucketUnixMillis int64) error { + _, err := q.db.ExecContext(ctx, deleteProxyRequestStatusRollupsBefore, bucketUnixMillis) + return err +} + const deleteProxyRequestTupleRollupsBefore = `-- name: DeleteProxyRequestTupleRollupsBefore :exec DELETE FROM proxy_request_tuple_rollup_minutes WHERE bucket_unix_millis < ? @@ -2883,9 +2935,9 @@ func (q *Queries) InsertConnection(ctx context.Context, agentID sql.NullInt64) ( const insertProxyRequestEvent = `-- name: InsertProxyRequestEvent :exec INSERT INTO proxy_request_events ( - status_code, duration_ms, error_kind, listener_id, route_id, route_target_id, waf_rule_id, waf_action, agent_id, request_bytes, response_bytes, cache_rule_id, cache_status, cache_bytes + status_code, duration_ms, error_kind, method, host, path_prefix, listener_id, route_id, route_target_id, waf_rule_id, waf_action, agent_id, request_bytes, response_bytes, cache_rule_id, cache_status, cache_bytes ) VALUES ( - ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? + ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) ` @@ -2893,6 +2945,9 @@ type InsertProxyRequestEventParams struct { StatusCode int64 `json:"status_code"` DurationMs int64 `json:"duration_ms"` ErrorKind string `json:"error_kind"` + Method string `json:"method"` + Host string `json:"host"` + PathPrefix string `json:"path_prefix"` ListenerID sql.NullInt64 `json:"listener_id"` RouteID sql.NullInt64 `json:"route_id"` RouteTargetID sql.NullInt64 `json:"route_target_id"` @@ -2911,6 +2966,9 @@ func (q *Queries) InsertProxyRequestEvent(ctx context.Context, arg InsertProxyRe arg.StatusCode, arg.DurationMs, arg.ErrorKind, + arg.Method, + arg.Host, + arg.PathPrefix, arg.ListenerID, arg.RouteID, arg.RouteTargetID, @@ -2928,9 +2986,9 @@ func (q *Queries) InsertProxyRequestEvent(ctx context.Context, arg InsertProxyRe const insertProxyRequestEventAt = `-- name: InsertProxyRequestEventAt :one INSERT INTO proxy_request_events ( - occurred_at, status_code, duration_ms, error_kind, listener_id, route_id, route_target_id, waf_rule_id, waf_action, agent_id, request_bytes, response_bytes, cache_rule_id, cache_status, cache_bytes + occurred_at, status_code, duration_ms, error_kind, method, host, path_prefix, listener_id, route_id, route_target_id, waf_rule_id, waf_action, agent_id, request_bytes, response_bytes, cache_rule_id, cache_status, cache_bytes ) VALUES ( - ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? + ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) RETURNING id ` @@ -2940,6 +2998,9 @@ type InsertProxyRequestEventAtParams struct { StatusCode int64 `json:"status_code"` DurationMs int64 `json:"duration_ms"` ErrorKind string `json:"error_kind"` + Method string `json:"method"` + Host string `json:"host"` + PathPrefix string `json:"path_prefix"` ListenerID sql.NullInt64 `json:"listener_id"` RouteID sql.NullInt64 `json:"route_id"` RouteTargetID sql.NullInt64 `json:"route_target_id"` @@ -2959,6 +3020,9 @@ func (q *Queries) InsertProxyRequestEventAt(ctx context.Context, arg InsertProxy arg.StatusCode, arg.DurationMs, arg.ErrorKind, + arg.Method, + arg.Host, + arg.PathPrefix, arg.ListenerID, arg.RouteID, arg.RouteTargetID, @@ -3314,6 +3378,353 @@ func (q *Queries) ListManagementAccessTokens(ctx context.Context) ([]ManagementA return items, nil } +const listProblemProxyAgentsRollupsSince = `-- name: ListProblemProxyAgentsRollupsSince :many +SELECT + r.agent_id AS id, + COALESCE(a.name, 'agent #' || r.agent_id) AS label, + CAST(COALESCE(SUM(r.requests), 0) AS INTEGER) AS requests, + CAST(COALESCE(SUM(r.success), 0) AS INTEGER) AS success, + CAST(COALESCE(SUM(r.client_error), 0) AS INTEGER) AS client_error, + CAST(COALESCE(SUM(r.server_error), 0) AS INTEGER) AS server_error, + CAST(COALESCE(SUM(r.internal_error), 0) AS INTEGER) AS internal_error, + CAST(CASE WHEN COALESCE(SUM(r.requests), 0) > 0 THEN COALESCE(SUM(r.duration_ms_sum), 0) / SUM(r.requests) ELSE 0 END AS INTEGER) AS avg_duration_ms, + CAST(COALESCE(SUM(r.request_bytes), 0) AS INTEGER) AS request_bytes, + CAST(COALESCE(SUM(r.response_bytes), 0) AS INTEGER) AS response_bytes +FROM proxy_request_tuple_rollup_minutes r +LEFT JOIN agents a ON a.id = r.agent_id +WHERE r.bucket_unix_millis >= ? + AND r.agent_id != 0 +GROUP BY r.agent_id, a.name +HAVING SUM(r.client_error) > 0 OR SUM(r.server_error) > 0 OR SUM(r.internal_error) > 0 +ORDER BY internal_error DESC, server_error DESC, client_error DESC, requests DESC, id ASC +LIMIT 10 +` + +type ListProblemProxyAgentsRollupsSinceRow struct { + ID int64 `json:"id"` + Label string `json:"label"` + Requests int64 `json:"requests"` + Success int64 `json:"success"` + ClientError int64 `json:"client_error"` + ServerError int64 `json:"server_error"` + InternalError int64 `json:"internal_error"` + AvgDurationMs int64 `json:"avg_duration_ms"` + RequestBytes int64 `json:"request_bytes"` + ResponseBytes int64 `json:"response_bytes"` +} + +func (q *Queries) ListProblemProxyAgentsRollupsSince(ctx context.Context, bucketUnixMillis int64) ([]ListProblemProxyAgentsRollupsSinceRow, error) { + rows, err := q.db.QueryContext(ctx, listProblemProxyAgentsRollupsSince, bucketUnixMillis) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ListProblemProxyAgentsRollupsSinceRow + for rows.Next() { + var i ListProblemProxyAgentsRollupsSinceRow + if err := rows.Scan( + &i.ID, + &i.Label, + &i.Requests, + &i.Success, + &i.ClientError, + &i.ServerError, + &i.InternalError, + &i.AvgDurationMs, + &i.RequestBytes, + &i.ResponseBytes, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listProblemProxyErrorKindsRollupsSince = `-- name: ListProblemProxyErrorKindsRollupsSince :many +SELECT + CAST(0 AS INTEGER) AS id, + r.error_kind AS label, + CAST(COALESCE(SUM(r.requests), 0) AS INTEGER) AS requests, + CAST(COALESCE(SUM(r.success), 0) AS INTEGER) AS success, + CAST(COALESCE(SUM(r.client_error), 0) AS INTEGER) AS client_error, + CAST(COALESCE(SUM(r.server_error), 0) AS INTEGER) AS server_error, + CAST(COALESCE(SUM(r.internal_error), 0) AS INTEGER) AS internal_error, + CAST(CASE WHEN COALESCE(SUM(r.requests), 0) > 0 THEN COALESCE(SUM(r.duration_ms_sum), 0) / SUM(r.requests) ELSE 0 END AS INTEGER) AS avg_duration_ms, + CAST(COALESCE(SUM(r.request_bytes), 0) AS INTEGER) AS request_bytes, + CAST(COALESCE(SUM(r.response_bytes), 0) AS INTEGER) AS response_bytes +FROM proxy_request_tuple_rollup_minutes r +WHERE r.bucket_unix_millis >= ? + AND r.error_kind != '' +GROUP BY r.error_kind +ORDER BY internal_error DESC, server_error DESC, client_error DESC, requests DESC, label ASC +LIMIT 10 +` + +type ListProblemProxyErrorKindsRollupsSinceRow struct { + ID int64 `json:"id"` + Label string `json:"label"` + Requests int64 `json:"requests"` + Success int64 `json:"success"` + ClientError int64 `json:"client_error"` + ServerError int64 `json:"server_error"` + InternalError int64 `json:"internal_error"` + AvgDurationMs int64 `json:"avg_duration_ms"` + RequestBytes int64 `json:"request_bytes"` + ResponseBytes int64 `json:"response_bytes"` +} + +func (q *Queries) ListProblemProxyErrorKindsRollupsSince(ctx context.Context, bucketUnixMillis int64) ([]ListProblemProxyErrorKindsRollupsSinceRow, error) { + rows, err := q.db.QueryContext(ctx, listProblemProxyErrorKindsRollupsSince, bucketUnixMillis) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ListProblemProxyErrorKindsRollupsSinceRow + for rows.Next() { + var i ListProblemProxyErrorKindsRollupsSinceRow + if err := rows.Scan( + &i.ID, + &i.Label, + &i.Requests, + &i.Success, + &i.ClientError, + &i.ServerError, + &i.InternalError, + &i.AvgDurationMs, + &i.RequestBytes, + &i.ResponseBytes, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listProblemProxyListenersRollupsSince = `-- name: ListProblemProxyListenersRollupsSince :many +SELECT + r.listener_id AS id, + COALESCE(pl.name, CASE WHEN r.listener_id = 0 THEN 'unknown listener' ELSE 'listener #' || r.listener_id END) AS label, + CAST(COALESCE(SUM(r.requests), 0) AS INTEGER) AS requests, + CAST(COALESCE(SUM(r.success), 0) AS INTEGER) AS success, + CAST(COALESCE(SUM(r.client_error), 0) AS INTEGER) AS client_error, + CAST(COALESCE(SUM(r.server_error), 0) AS INTEGER) AS server_error, + CAST(COALESCE(SUM(r.internal_error), 0) AS INTEGER) AS internal_error, + CAST(CASE WHEN COALESCE(SUM(r.requests), 0) > 0 THEN COALESCE(SUM(r.duration_ms_sum), 0) / SUM(r.requests) ELSE 0 END AS INTEGER) AS avg_duration_ms, + CAST(COALESCE(SUM(r.request_bytes), 0) AS INTEGER) AS request_bytes, + CAST(COALESCE(SUM(r.response_bytes), 0) AS INTEGER) AS response_bytes +FROM proxy_request_tuple_rollup_minutes r +LEFT JOIN public_listeners pl ON pl.id = r.listener_id +WHERE r.bucket_unix_millis >= ? +GROUP BY r.listener_id, pl.name +HAVING SUM(r.client_error) > 0 OR SUM(r.server_error) > 0 OR SUM(r.internal_error) > 0 +ORDER BY internal_error DESC, server_error DESC, client_error DESC, requests DESC, id ASC +LIMIT 10 +` + +type ListProblemProxyListenersRollupsSinceRow struct { + ID int64 `json:"id"` + Label string `json:"label"` + Requests int64 `json:"requests"` + Success int64 `json:"success"` + ClientError int64 `json:"client_error"` + ServerError int64 `json:"server_error"` + InternalError int64 `json:"internal_error"` + AvgDurationMs int64 `json:"avg_duration_ms"` + RequestBytes int64 `json:"request_bytes"` + ResponseBytes int64 `json:"response_bytes"` +} + +func (q *Queries) ListProblemProxyListenersRollupsSince(ctx context.Context, bucketUnixMillis int64) ([]ListProblemProxyListenersRollupsSinceRow, error) { + rows, err := q.db.QueryContext(ctx, listProblemProxyListenersRollupsSince, bucketUnixMillis) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ListProblemProxyListenersRollupsSinceRow + for rows.Next() { + var i ListProblemProxyListenersRollupsSinceRow + if err := rows.Scan( + &i.ID, + &i.Label, + &i.Requests, + &i.Success, + &i.ClientError, + &i.ServerError, + &i.InternalError, + &i.AvgDurationMs, + &i.RequestBytes, + &i.ResponseBytes, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listProblemProxyRouteTargetsRollupsSince = `-- name: ListProblemProxyRouteTargetsRollupsSince :many +SELECT + r.route_target_id AS id, + COALESCE(prt.name, CASE WHEN r.route_target_id = 0 THEN 'unknown target' ELSE 'target #' || r.route_target_id END) AS label, + CAST(COALESCE(SUM(r.requests), 0) AS INTEGER) AS requests, + CAST(COALESCE(SUM(r.success), 0) AS INTEGER) AS success, + CAST(COALESCE(SUM(r.client_error), 0) AS INTEGER) AS client_error, + CAST(COALESCE(SUM(r.server_error), 0) AS INTEGER) AS server_error, + CAST(COALESCE(SUM(r.internal_error), 0) AS INTEGER) AS internal_error, + CAST(CASE WHEN COALESCE(SUM(r.requests), 0) > 0 THEN COALESCE(SUM(r.duration_ms_sum), 0) / SUM(r.requests) ELSE 0 END AS INTEGER) AS avg_duration_ms, + CAST(COALESCE(SUM(r.request_bytes), 0) AS INTEGER) AS request_bytes, + CAST(COALESCE(SUM(r.response_bytes), 0) AS INTEGER) AS response_bytes +FROM proxy_request_tuple_rollup_minutes r +LEFT JOIN public_route_targets prt ON prt.id = r.route_target_id +WHERE r.bucket_unix_millis >= ? +GROUP BY r.route_target_id, prt.name +HAVING SUM(r.client_error) > 0 OR SUM(r.server_error) > 0 OR SUM(r.internal_error) > 0 +ORDER BY internal_error DESC, server_error DESC, client_error DESC, requests DESC, id ASC +LIMIT 10 +` + +type ListProblemProxyRouteTargetsRollupsSinceRow struct { + ID int64 `json:"id"` + Label string `json:"label"` + Requests int64 `json:"requests"` + Success int64 `json:"success"` + ClientError int64 `json:"client_error"` + ServerError int64 `json:"server_error"` + InternalError int64 `json:"internal_error"` + AvgDurationMs int64 `json:"avg_duration_ms"` + RequestBytes int64 `json:"request_bytes"` + ResponseBytes int64 `json:"response_bytes"` +} + +func (q *Queries) ListProblemProxyRouteTargetsRollupsSince(ctx context.Context, bucketUnixMillis int64) ([]ListProblemProxyRouteTargetsRollupsSinceRow, error) { + rows, err := q.db.QueryContext(ctx, listProblemProxyRouteTargetsRollupsSince, bucketUnixMillis) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ListProblemProxyRouteTargetsRollupsSinceRow + for rows.Next() { + var i ListProblemProxyRouteTargetsRollupsSinceRow + if err := rows.Scan( + &i.ID, + &i.Label, + &i.Requests, + &i.Success, + &i.ClientError, + &i.ServerError, + &i.InternalError, + &i.AvgDurationMs, + &i.RequestBytes, + &i.ResponseBytes, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listProblemProxyRoutesRollupsSince = `-- name: ListProblemProxyRoutesRollupsSince :many +SELECT + r.route_id AS id, + CASE + WHEN r.route_id = 0 THEN 'Default route' + WHEN pr.id IS NULL THEN 'route #' || r.route_id + WHEN pr.host_pattern != '' AND pr.path_prefix != '' THEN pr.host_pattern || ' ' || pr.path_prefix + WHEN pr.host_pattern != '' THEN pr.host_pattern + WHEN pr.path_prefix != '' THEN pr.path_prefix + ELSE 'route #' || pr.id + END AS label, + CAST(COALESCE(SUM(r.requests), 0) AS INTEGER) AS requests, + CAST(COALESCE(SUM(r.success), 0) AS INTEGER) AS success, + CAST(COALESCE(SUM(r.client_error), 0) AS INTEGER) AS client_error, + CAST(COALESCE(SUM(r.server_error), 0) AS INTEGER) AS server_error, + CAST(COALESCE(SUM(r.internal_error), 0) AS INTEGER) AS internal_error, + CAST(CASE WHEN COALESCE(SUM(r.requests), 0) > 0 THEN COALESCE(SUM(r.duration_ms_sum), 0) / SUM(r.requests) ELSE 0 END AS INTEGER) AS avg_duration_ms, + CAST(COALESCE(SUM(r.request_bytes), 0) AS INTEGER) AS request_bytes, + CAST(COALESCE(SUM(r.response_bytes), 0) AS INTEGER) AS response_bytes +FROM proxy_request_tuple_rollup_minutes r +LEFT JOIN public_routes pr ON pr.id = r.route_id +WHERE r.bucket_unix_millis >= ? +GROUP BY r.route_id, pr.id, pr.host_pattern, pr.path_prefix +HAVING SUM(r.client_error) > 0 OR SUM(r.server_error) > 0 OR SUM(r.internal_error) > 0 +ORDER BY internal_error DESC, server_error DESC, client_error DESC, requests DESC, id ASC +LIMIT 10 +` + +type ListProblemProxyRoutesRollupsSinceRow struct { + ID int64 `json:"id"` + Label interface{} `json:"label"` + Requests int64 `json:"requests"` + Success int64 `json:"success"` + ClientError int64 `json:"client_error"` + ServerError int64 `json:"server_error"` + InternalError int64 `json:"internal_error"` + AvgDurationMs int64 `json:"avg_duration_ms"` + RequestBytes int64 `json:"request_bytes"` + ResponseBytes int64 `json:"response_bytes"` +} + +func (q *Queries) ListProblemProxyRoutesRollupsSince(ctx context.Context, bucketUnixMillis int64) ([]ListProblemProxyRoutesRollupsSinceRow, error) { + rows, err := q.db.QueryContext(ctx, listProblemProxyRoutesRollupsSince, bucketUnixMillis) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ListProblemProxyRoutesRollupsSinceRow + for rows.Next() { + var i ListProblemProxyRoutesRollupsSinceRow + if err := rows.Scan( + &i.ID, + &i.Label, + &i.Requests, + &i.Success, + &i.ClientError, + &i.ServerError, + &i.InternalError, + &i.AvgDurationMs, + &i.RequestBytes, + &i.ResponseBytes, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const listProxyRequestRollupMinutesSince = `-- name: ListProxyRequestRollupMinutesSince :many SELECT bucket_unix_millis, @@ -3615,6 +4026,68 @@ func (q *Queries) ListProxyStatusClassesSince(ctx context.Context, occurredAt ti return items, nil } +const listProxyStatusCodeRollupsSince = `-- name: ListProxyStatusCodeRollupsSince :many +SELECT + r.status_code, + CAST(COALESCE(SUM(r.requests), 0) AS INTEGER) AS requests, + CAST(COALESCE(SUM(r.success), 0) AS INTEGER) AS success, + CAST(COALESCE(SUM(r.client_error), 0) AS INTEGER) AS client_error, + CAST(COALESCE(SUM(r.server_error), 0) AS INTEGER) AS server_error, + CAST(COALESCE(SUM(r.internal_error), 0) AS INTEGER) AS internal_error, + CAST(CASE WHEN COALESCE(SUM(r.requests), 0) > 0 THEN COALESCE(SUM(r.duration_ms_sum), 0) / SUM(r.requests) ELSE 0 END AS INTEGER) AS avg_duration_ms, + CAST(COALESCE(SUM(r.request_bytes), 0) AS INTEGER) AS request_bytes, + CAST(COALESCE(SUM(r.response_bytes), 0) AS INTEGER) AS response_bytes +FROM proxy_request_status_rollup_minutes r +WHERE r.bucket_unix_millis >= ? +GROUP BY r.status_code +ORDER BY requests DESC, status_code ASC +` + +type ListProxyStatusCodeRollupsSinceRow struct { + StatusCode int64 `json:"status_code"` + Requests int64 `json:"requests"` + Success int64 `json:"success"` + ClientError int64 `json:"client_error"` + ServerError int64 `json:"server_error"` + InternalError int64 `json:"internal_error"` + AvgDurationMs int64 `json:"avg_duration_ms"` + RequestBytes int64 `json:"request_bytes"` + ResponseBytes int64 `json:"response_bytes"` +} + +func (q *Queries) ListProxyStatusCodeRollupsSince(ctx context.Context, bucketUnixMillis int64) ([]ListProxyStatusCodeRollupsSinceRow, error) { + rows, err := q.db.QueryContext(ctx, listProxyStatusCodeRollupsSince, bucketUnixMillis) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ListProxyStatusCodeRollupsSinceRow + for rows.Next() { + var i ListProxyStatusCodeRollupsSinceRow + if err := rows.Scan( + &i.StatusCode, + &i.Requests, + &i.Success, + &i.ClientError, + &i.ServerError, + &i.InternalError, + &i.AvgDurationMs, + &i.RequestBytes, + &i.ResponseBytes, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const listProxyTrafficBucketRollupsSince = `-- name: ListProxyTrafficBucketRollupsSince :many SELECT CAST((bucket_unix_millis / (CAST(?1 AS INTEGER) * 1000)) * (CAST(?1 AS INTEGER) * 1000) AS INTEGER) AS bucket_unix_millis, @@ -4671,6 +5144,97 @@ func (q *Queries) ListRecentConnections(ctx context.Context, limit int64) ([]Lis return items, nil } +const listRecentProxyProblemSamplesSince = `-- name: ListRecentProxyProblemSamplesSince :many +SELECT + pre.occurred_at, + pre.method, + pre.host, + pre.path_prefix, + pre.status_code, + pre.error_kind, + COALESCE(pl.name, CASE WHEN pre.listener_id IS NULL THEN '' ELSE 'listener #' || pre.listener_id END) AS listener_label, + CASE + WHEN pre.route_id IS NULL THEN '' + WHEN pr.id IS NULL THEN 'route #' || pre.route_id + WHEN pr.host_pattern != '' AND pr.path_prefix != '' THEN pr.host_pattern || ' ' || pr.path_prefix + WHEN pr.host_pattern != '' THEN pr.host_pattern + WHEN pr.path_prefix != '' THEN pr.path_prefix + ELSE 'route #' || pr.id + END AS route_label, + COALESCE(prt.name, CASE WHEN pre.route_target_id IS NULL THEN '' ELSE 'target #' || pre.route_target_id END) AS route_target_label, + COALESCE(a.name, CASE WHEN pre.agent_id IS NULL THEN '' ELSE 'agent #' || pre.agent_id END) AS agent_label, + pre.duration_ms, + pre.request_bytes, + pre.response_bytes +FROM proxy_request_events AS pre INDEXED BY idx_proxy_request_events_occurred_at +LEFT JOIN public_listeners pl ON pl.id = pre.listener_id +LEFT JOIN public_routes pr ON pr.id = pre.route_id +LEFT JOIN public_route_targets prt ON prt.id = pre.route_target_id +LEFT JOIN agents a ON a.id = pre.agent_id +WHERE pre.occurred_at >= ?1 + AND (pre.status_code >= 400 OR pre.error_kind != '') +ORDER BY pre.occurred_at DESC, pre.id DESC +LIMIT ?2 +` + +type ListRecentProxyProblemSamplesSinceParams struct { + Since time.Time `json:"since"` + Limit int64 `json:"limit"` +} + +type ListRecentProxyProblemSamplesSinceRow struct { + OccurredAt time.Time `json:"occurred_at"` + Method string `json:"method"` + Host string `json:"host"` + PathPrefix string `json:"path_prefix"` + StatusCode int64 `json:"status_code"` + ErrorKind string `json:"error_kind"` + ListenerLabel string `json:"listener_label"` + RouteLabel interface{} `json:"route_label"` + RouteTargetLabel string `json:"route_target_label"` + AgentLabel string `json:"agent_label"` + DurationMs int64 `json:"duration_ms"` + RequestBytes int64 `json:"request_bytes"` + ResponseBytes int64 `json:"response_bytes"` +} + +func (q *Queries) ListRecentProxyProblemSamplesSince(ctx context.Context, arg ListRecentProxyProblemSamplesSinceParams) ([]ListRecentProxyProblemSamplesSinceRow, error) { + rows, err := q.db.QueryContext(ctx, listRecentProxyProblemSamplesSince, arg.Since, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ListRecentProxyProblemSamplesSinceRow + for rows.Next() { + var i ListRecentProxyProblemSamplesSinceRow + if err := rows.Scan( + &i.OccurredAt, + &i.Method, + &i.Host, + &i.PathPrefix, + &i.StatusCode, + &i.ErrorKind, + &i.ListenerLabel, + &i.RouteLabel, + &i.RouteTargetLabel, + &i.AgentLabel, + &i.DurationMs, + &i.RequestBytes, + &i.ResponseBytes, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const listTopProxyAgentsRollupsSince = `-- name: ListTopProxyAgentsRollupsSince :many SELECT r.agent_id AS id, @@ -7211,6 +7775,54 @@ func (q *Queries) UpsertProxyRequestRollupMinute(ctx context.Context, arg Upsert return err } +const upsertProxyRequestStatusRollupMinute = `-- name: UpsertProxyRequestStatusRollupMinute :exec +INSERT INTO proxy_request_status_rollup_minutes ( + bucket_unix_millis, status_code, requests, success, client_error, server_error, + internal_error, duration_ms_sum, request_bytes, response_bytes +) VALUES ( + ?, ?, ?, ?, ?, ?, ?, ?, ?, ? +) +ON CONFLICT(bucket_unix_millis, status_code) DO UPDATE SET + requests = proxy_request_status_rollup_minutes.requests + excluded.requests, + success = proxy_request_status_rollup_minutes.success + excluded.success, + client_error = proxy_request_status_rollup_minutes.client_error + excluded.client_error, + server_error = proxy_request_status_rollup_minutes.server_error + excluded.server_error, + internal_error = proxy_request_status_rollup_minutes.internal_error + excluded.internal_error, + duration_ms_sum = proxy_request_status_rollup_minutes.duration_ms_sum + excluded.duration_ms_sum, + request_bytes = proxy_request_status_rollup_minutes.request_bytes + excluded.request_bytes, + response_bytes = proxy_request_status_rollup_minutes.response_bytes + excluded.response_bytes, + updated_at = CURRENT_TIMESTAMP +` + +type UpsertProxyRequestStatusRollupMinuteParams struct { + BucketUnixMillis int64 `json:"bucket_unix_millis"` + StatusCode int64 `json:"status_code"` + Requests int64 `json:"requests"` + Success int64 `json:"success"` + ClientError int64 `json:"client_error"` + ServerError int64 `json:"server_error"` + InternalError int64 `json:"internal_error"` + DurationMsSum int64 `json:"duration_ms_sum"` + RequestBytes int64 `json:"request_bytes"` + ResponseBytes int64 `json:"response_bytes"` +} + +func (q *Queries) UpsertProxyRequestStatusRollupMinute(ctx context.Context, arg UpsertProxyRequestStatusRollupMinuteParams) error { + _, err := q.db.ExecContext(ctx, upsertProxyRequestStatusRollupMinute, + arg.BucketUnixMillis, + arg.StatusCode, + arg.Requests, + arg.Success, + arg.ClientError, + arg.ServerError, + arg.InternalError, + arg.DurationMsSum, + arg.RequestBytes, + arg.ResponseBytes, + ) + return err +} + const upsertProxyRequestTupleRollupMinute = `-- name: UpsertProxyRequestTupleRollupMinute :exec INSERT INTO proxy_request_tuple_rollup_minutes ( bucket_unix_millis, listener_id, route_target_id, route_id, agent_id, error_kind, status_class, diff --git a/internal/server/dashboard.go b/internal/server/dashboard.go index 8a622fa..a323074 100644 --- a/internal/server/dashboard.go +++ b/internal/server/dashboard.go @@ -31,6 +31,8 @@ const ( dashboardTrafficBucketWindow = time.Hour dashboardTrafficBucketSeconds = int64(5 * 60) observabilityCleanupInterval = time.Hour + diagnosticsDefaultSampleLimit = int64(25) + diagnosticsMaxSampleLimit = int64(100) ) func (a *App) GetDashboard( @@ -56,6 +58,95 @@ func (a *App) GetDashboard( return connect.NewResponse(resp), nil } +func (a *App) GetDashboardDiagnostics( + ctx context.Context, + req *connect.Request[p2pstreamv1.GetDashboardDiagnosticsRequest], +) (*connect.Response[p2pstreamv1.GetDashboardDiagnosticsResponse], error) { + if _, err := a.requireUser(ctx, req.Header()); err != nil { + return nil, err + } + if a.DB == nil { + return nil, connect.NewError(connect.CodeFailedPrecondition, errors.New("database is required for dashboard diagnostics")) + } + + now := time.Now().UTC() + label, window := dashboardDiagnosticsWindow(req.Msg.GetWindowLabel()) + sampleLimit := dashboardDiagnosticsSampleLimit(req.Msg.GetSampleLimit()) + sinceUnixMillis := rollupBucketUnixMillis(now.Add(-window)) + since := time.UnixMilli(sinceUnixMillis).UTC() + + proxySummary, err := a.DB.GetProxyRequestRollupSummarySince(ctx, sinceUnixMillis) + if err != nil { + return nil, connect.NewError(connect.CodeInternal, err) + } + statusRows, err := a.DB.ListProxyStatusCodeRollupsSince(ctx, sinceUnixMillis) + if err != nil { + return nil, connect.NewError(connect.CodeInternal, err) + } + errorKindRows, err := a.DB.ListProblemProxyErrorKindsRollupsSince(ctx, sinceUnixMillis) + if err != nil { + return nil, connect.NewError(connect.CodeInternal, err) + } + listenerRows, err := a.DB.ListProblemProxyListenersRollupsSince(ctx, sinceUnixMillis) + if err != nil { + return nil, connect.NewError(connect.CodeInternal, err) + } + routeRows, err := a.DB.ListProblemProxyRoutesRollupsSince(ctx, sinceUnixMillis) + if err != nil { + return nil, connect.NewError(connect.CodeInternal, err) + } + routeTargetRows, err := a.DB.ListProblemProxyRouteTargetsRollupsSince(ctx, sinceUnixMillis) + if err != nil { + return nil, connect.NewError(connect.CodeInternal, err) + } + agentRows, err := a.DB.ListProblemProxyAgentsRollupsSince(ctx, sinceUnixMillis) + if err != nil { + return nil, connect.NewError(connect.CodeInternal, err) + } + sampleRows, err := a.DB.ListRecentProxyProblemSamplesSince(ctx, db.ListRecentProxyProblemSamplesSinceParams{ + Since: since, + Limit: sampleLimit, + }) + if err != nil { + return nil, connect.NewError(connect.CodeInternal, err) + } + + resp := &p2pstreamv1.GetDashboardDiagnosticsResponse{ + Label: label, + SinceUnixMillis: sinceUnixMillis, + GeneratedAtUnixMillis: now.UnixMilli(), + Outcome: dashboardDiagnosticsOutcome(label, sinceUnixMillis, proxySummary), + StatusCodes: dashboardDiagnosticsStatusCodeSummaries(statusRows), + ErrorKinds: dashboardProblemErrorKindSummaries(errorKindRows), + ProblemListeners: dashboardProblemListenerSummaries(listenerRows), + ProblemRoutes: dashboardProblemRouteSummaries(routeRows), + ProblemRouteTargets: dashboardProblemRouteTargetSummaries(routeTargetRows), + ProblemAgents: dashboardProblemAgentSummaries(agentRows), + RecentSamples: dashboardDiagnosticsSamples(sampleRows), + } + return connect.NewResponse(resp), nil +} + +func dashboardDiagnosticsWindow(label string) (string, time.Duration) { + label = strings.TrimSpace(label) + for _, window := range dashboardWindows { + if label == window.Label { + return window.Label, window.Since + } + } + return "1h", time.Hour +} + +func dashboardDiagnosticsSampleLimit(limit int64) int64 { + if limit <= 0 { + return diagnosticsDefaultSampleLimit + } + if limit > diagnosticsMaxSampleLimit { + return diagnosticsMaxSampleLimit + } + return limit +} + func (a *App) buildDashboardDirect(ctx context.Context, now time.Time) (*p2pstreamv1.GetDashboardResponse, error) { useRollups, err := a.observabilityRollupsReady(ctx) if err != nil { @@ -279,6 +370,91 @@ type dashboardAgentWindowMetrics struct { MaxCpuPercent float64 } +type proxyRequestContext struct { + Method string + Host string + PathPrefix string +} + +func proxyRequestContextFromHTTP(r *http.Request) proxyRequestContext { + if r == nil { + return proxyRequestContext{} + } + path := "/" + if r.URL != nil { + path = r.URL.EscapedPath() + if path == "" { + path = r.URL.Path + } + } + host := normalizeRequestHost(r.Host) + if host == "" && r.URL != nil { + host = normalizeRequestHost(r.URL.Host) + } + return proxyRequestContext{ + Method: truncateProxyRequestContextValue(strings.ToUpper(strings.TrimSpace(r.Method)), 16), + Host: truncateProxyRequestContextValue(host, 255), + PathPrefix: truncateProxyRequestContextValue(redactedProxyPathPrefix(path), 256), + } +} + +func redactedProxyPathPrefix(path string) string { + path = strings.ToValidUTF8(strings.TrimSpace(path), "") + if idx := strings.IndexByte(path, '?'); idx >= 0 { + path = path[:idx] + } + if path == "" { + return "/" + } + if !strings.HasPrefix(path, "/") { + path = "/" + path + } + allSegments := make([]string, 0, 3) + for _, segment := range strings.Split(strings.Trim(path, "/"), "/") { + if segment == "" { + continue + } + allSegments = append(allSegments, segment) + } + segments := allSegments + if len(segments) > 2 { + segments = segments[:2] + } + if len(segments) == 0 { + return "/" + } + prefix := "/" + strings.Join(segments, "/") + if len(allSegments) > len(segments) { + prefix += "/..." + } + return prefix +} + +func truncateProxyRequestContextValue(value string, maxLen int) string { + if maxLen <= 0 { + return "" + } + value = strings.ToValidUTF8(strings.TrimSpace(value), "") + value = strings.Map(func(r rune) rune { + if r < 0x20 || r == 0x7f { + return -1 + } + return r + }, value) + if len(value) <= maxLen { + return value + } + var out strings.Builder + for _, r := range value { + next := string(r) + if out.Len()+len(next) > maxLen { + break + } + out.WriteString(next) + } + return out.String() +} + func dashboardWindowSummary( label string, sinceUnixMillis int64, @@ -425,7 +601,22 @@ func (a *App) recordProxyRequestEventWithIDs( requestBytes uint64, responseBytes uint64, ) { - a.recordProxyRequestEventWithPolicyIDs(ctx, statusCode, duration, errorKind, listenerID, routeID, sql.NullInt64{}, "", agentID, requestBytes, responseBytes) + a.recordProxyRequestEventWithIDsAndContext(ctx, statusCode, duration, errorKind, listenerID, routeID, agentID, requestBytes, responseBytes, proxyRequestContext{}) +} + +func (a *App) recordProxyRequestEventWithIDsAndContext( + ctx context.Context, + statusCode int, + duration time.Duration, + errorKind string, + listenerID sql.NullInt64, + routeID sql.NullInt64, + agentID sql.NullInt64, + requestBytes uint64, + responseBytes uint64, + requestContext proxyRequestContext, +) { + a.recordProxyRequestEventWithPolicyIDsAndContext(ctx, statusCode, duration, errorKind, listenerID, routeID, sql.NullInt64{}, "", agentID, requestBytes, responseBytes, requestContext) } func (a *App) recordProxyRequestEventWithPolicyIDs( @@ -441,7 +632,24 @@ func (a *App) recordProxyRequestEventWithPolicyIDs( requestBytes uint64, responseBytes uint64, ) { - a.recordProxyRequestEventWithCache(ctx, statusCode, duration, errorKind, listenerID, routeID, wafRuleID, wafAction, agentID, sql.NullInt64{}, "", 0, requestBytes, responseBytes) + a.recordProxyRequestEventWithPolicyIDsAndContext(ctx, statusCode, duration, errorKind, listenerID, routeID, wafRuleID, wafAction, agentID, requestBytes, responseBytes, proxyRequestContext{}) +} + +func (a *App) recordProxyRequestEventWithPolicyIDsAndContext( + ctx context.Context, + statusCode int, + duration time.Duration, + errorKind string, + listenerID sql.NullInt64, + routeID sql.NullInt64, + wafRuleID sql.NullInt64, + wafAction string, + agentID sql.NullInt64, + requestBytes uint64, + responseBytes uint64, + requestContext proxyRequestContext, +) { + a.recordProxyRequestEventWithCacheAndContext(ctx, statusCode, duration, errorKind, listenerID, routeID, wafRuleID, wafAction, agentID, sql.NullInt64{}, "", 0, requestBytes, responseBytes, requestContext) } func (a *App) recordProxyRequestEventWithCache( @@ -460,7 +668,27 @@ func (a *App) recordProxyRequestEventWithCache( requestBytes uint64, responseBytes uint64, ) { - a.recordProxyRequestEventWithRouteTargetCache(ctx, statusCode, duration, errorKind, listenerID, routeID, sql.NullInt64{}, wafRuleID, wafAction, agentID, cacheRuleID, cacheStatus, cacheBytes, requestBytes, responseBytes) + a.recordProxyRequestEventWithCacheAndContext(ctx, statusCode, duration, errorKind, listenerID, routeID, wafRuleID, wafAction, agentID, cacheRuleID, cacheStatus, cacheBytes, requestBytes, responseBytes, proxyRequestContext{}) +} + +func (a *App) recordProxyRequestEventWithCacheAndContext( + ctx context.Context, + statusCode int, + duration time.Duration, + errorKind string, + listenerID sql.NullInt64, + routeID sql.NullInt64, + wafRuleID sql.NullInt64, + wafAction string, + agentID sql.NullInt64, + cacheRuleID sql.NullInt64, + cacheStatus string, + cacheBytes uint64, + requestBytes uint64, + responseBytes uint64, + requestContext proxyRequestContext, +) { + a.recordProxyRequestEventWithRouteTargetCacheAndContext(ctx, statusCode, duration, errorKind, listenerID, routeID, sql.NullInt64{}, wafRuleID, wafAction, agentID, cacheRuleID, cacheStatus, cacheBytes, requestBytes, responseBytes, requestContext) } func (a *App) recordProxyRequestEventWithRouteTargetCache( @@ -479,6 +707,27 @@ func (a *App) recordProxyRequestEventWithRouteTargetCache( cacheBytes uint64, requestBytes uint64, responseBytes uint64, +) { + a.recordProxyRequestEventWithRouteTargetCacheAndContext(ctx, statusCode, duration, errorKind, listenerID, routeID, routeTargetID, wafRuleID, wafAction, agentID, cacheRuleID, cacheStatus, cacheBytes, requestBytes, responseBytes, proxyRequestContext{}) +} + +func (a *App) recordProxyRequestEventWithRouteTargetCacheAndContext( + ctx context.Context, + statusCode int, + duration time.Duration, + errorKind string, + listenerID sql.NullInt64, + routeID sql.NullInt64, + routeTargetID sql.NullInt64, + wafRuleID sql.NullInt64, + wafAction string, + agentID sql.NullInt64, + cacheRuleID sql.NullInt64, + cacheStatus string, + cacheBytes uint64, + requestBytes uint64, + responseBytes uint64, + requestContext proxyRequestContext, ) { if a.DB == nil { return @@ -496,6 +745,9 @@ func (a *App) recordProxyRequestEventWithRouteTargetCache( StatusCode: int64(statusCode), DurationMs: duration.Milliseconds(), ErrorKind: errorKind, + Method: requestContext.Method, + Host: requestContext.Host, + PathPrefix: requestContext.PathPrefix, ListenerID: listenerID, RouteID: routeID, RouteTargetID: routeTargetID, @@ -752,6 +1004,161 @@ func dashboardRollupStatusClassSummaries(rows []db.ListProxyStatusClassesRollups return items } +func dashboardDiagnosticsOutcome(label string, sinceUnixMillis int64, row db.GetProxyRequestRollupSummarySinceRow) *p2pstreamv1.DashboardDiagnosticsOutcomeSummary { + return &p2pstreamv1.DashboardDiagnosticsOutcomeSummary{ + Label: label, + SinceUnixMillis: sinceUnixMillis, + Requests: row.TotalRequests, + Success: row.Success, + ClientError: row.ClientError, + ServerError: row.ServerError, + NonSuccess: row.ClientError + row.ServerError, + ProxyFailure: row.InternalError, + AvgDurationMs: row.AvgDurationMs, + MaxDurationMs: row.MaxDurationMs, + } +} + +func dashboardDiagnosticsStatusCodeSummaries(rows []db.ListProxyStatusCodeRollupsSinceRow) []*p2pstreamv1.DashboardStatusCodeSummary { + items := make([]*p2pstreamv1.DashboardStatusCodeSummary, 0, len(rows)) + for _, row := range rows { + items = append(items, &p2pstreamv1.DashboardStatusCodeSummary{ + StatusCode: row.StatusCode, + Requests: row.Requests, + Success: row.Success, + ClientError: row.ClientError, + ServerError: row.ServerError, + ProxyFailure: row.InternalError, + AvgDurationMs: row.AvgDurationMs, + RequestBytes: uint64FromInt64(row.RequestBytes), + ResponseBytes: uint64FromInt64(row.ResponseBytes), + }) + } + return items +} + +func dashboardProblemListenerSummaries(rows []db.ListProblemProxyListenersRollupsSinceRow) []*p2pstreamv1.DashboardProxyDimensionSummary { + items := make([]*p2pstreamv1.DashboardProxyDimensionSummary, 0, len(rows)) + for _, row := range rows { + items = append(items, dashboardDimensionSummary( + p2pstreamv1.DashboardProxyDimension_DASHBOARD_PROXY_DIMENSION_LISTENER, + row.ID, + row.Label, + row.Requests, + row.Success, + row.ClientError, + row.ServerError, + row.InternalError, + row.AvgDurationMs, + row.RequestBytes, + row.ResponseBytes, + )) + } + return items +} + +func dashboardProblemRouteSummaries(rows []db.ListProblemProxyRoutesRollupsSinceRow) []*p2pstreamv1.DashboardProxyDimensionSummary { + items := make([]*p2pstreamv1.DashboardProxyDimensionSummary, 0, len(rows)) + for _, row := range rows { + items = append(items, dashboardDimensionSummary( + p2pstreamv1.DashboardProxyDimension_DASHBOARD_PROXY_DIMENSION_ROUTE, + row.ID, + row.Label, + row.Requests, + row.Success, + row.ClientError, + row.ServerError, + row.InternalError, + row.AvgDurationMs, + row.RequestBytes, + row.ResponseBytes, + )) + } + return items +} + +func dashboardProblemRouteTargetSummaries(rows []db.ListProblemProxyRouteTargetsRollupsSinceRow) []*p2pstreamv1.DashboardProxyDimensionSummary { + items := make([]*p2pstreamv1.DashboardProxyDimensionSummary, 0, len(rows)) + for _, row := range rows { + items = append(items, dashboardDimensionSummary( + p2pstreamv1.DashboardProxyDimension_DASHBOARD_PROXY_DIMENSION_ROUTE_TARGET, + row.ID, + row.Label, + row.Requests, + row.Success, + row.ClientError, + row.ServerError, + row.InternalError, + row.AvgDurationMs, + row.RequestBytes, + row.ResponseBytes, + )) + } + return items +} + +func dashboardProblemAgentSummaries(rows []db.ListProblemProxyAgentsRollupsSinceRow) []*p2pstreamv1.DashboardProxyDimensionSummary { + items := make([]*p2pstreamv1.DashboardProxyDimensionSummary, 0, len(rows)) + for _, row := range rows { + items = append(items, dashboardDimensionSummary( + p2pstreamv1.DashboardProxyDimension_DASHBOARD_PROXY_DIMENSION_AGENT, + row.ID, + row.Label, + row.Requests, + row.Success, + row.ClientError, + row.ServerError, + row.InternalError, + row.AvgDurationMs, + row.RequestBytes, + row.ResponseBytes, + )) + } + return items +} + +func dashboardProblemErrorKindSummaries(rows []db.ListProblemProxyErrorKindsRollupsSinceRow) []*p2pstreamv1.DashboardProxyDimensionSummary { + items := make([]*p2pstreamv1.DashboardProxyDimensionSummary, 0, len(rows)) + for _, row := range rows { + items = append(items, dashboardDimensionSummary( + p2pstreamv1.DashboardProxyDimension_DASHBOARD_PROXY_DIMENSION_ERROR_KIND, + row.ID, + row.Label, + row.Requests, + row.Success, + row.ClientError, + row.ServerError, + row.InternalError, + row.AvgDurationMs, + row.RequestBytes, + row.ResponseBytes, + )) + } + return items +} + +func dashboardDiagnosticsSamples(rows []db.ListRecentProxyProblemSamplesSinceRow) []*p2pstreamv1.DashboardDiagnosticsSample { + items := make([]*p2pstreamv1.DashboardDiagnosticsSample, 0, len(rows)) + for _, row := range rows { + items = append(items, &p2pstreamv1.DashboardDiagnosticsSample{ + OccurredAtUnixMillis: row.OccurredAt.UnixMilli(), + Method: row.Method, + Host: row.Host, + PathPrefix: row.PathPrefix, + StatusCode: row.StatusCode, + ErrorKind: row.ErrorKind, + ListenerLabel: row.ListenerLabel, + RouteLabel: dashboardLabelString(row.RouteLabel), + RouteTargetLabel: row.RouteTargetLabel, + AgentLabel: row.AgentLabel, + DurationMs: row.DurationMs, + RequestBytes: uint64FromInt64(row.RequestBytes), + ResponseBytes: uint64FromInt64(row.ResponseBytes), + }) + } + return items +} + func dashboardDimensionSummary( dimension p2pstreamv1.DashboardProxyDimension, id int64, @@ -865,6 +1272,9 @@ func (a *App) cleanupObservability(ctx context.Context, now time.Time) { if err := a.DB.DeleteProxyRequestTupleRollupsBefore(ctx, cutoffBucketUnixMillis); err != nil { log.Warn().Err(err).Msg("Failed to clean up old proxy request tuple rollups") } + if err := a.DB.DeleteProxyRequestStatusRollupsBefore(ctx, cutoffBucketUnixMillis); err != nil { + log.Warn().Err(err).Msg("Failed to clean up old proxy request status rollups") + } if err := a.DB.DeleteAgentStatRollupsBefore(ctx, cutoffBucketUnixMillis); err != nil { log.Warn().Err(err).Msg("Failed to clean up old agent stat rollups") } diff --git a/internal/server/dashboard_test.go b/internal/server/dashboard_test.go index 42acc34..fa1b582 100644 --- a/internal/server/dashboard_test.go +++ b/internal/server/dashboard_test.go @@ -3,6 +3,7 @@ package server import ( "context" "database/sql" + "fmt" "net/http" "testing" "time" @@ -76,6 +77,132 @@ func TestGetDashboardIncludesCacheSummary(t *testing.T) { } } +func TestGetDashboardDiagnosticsRequiresAuth(t *testing.T) { + ctx := context.Background() + app := NewApp(nil, newServerTestDB(t)) + + req := connect.NewRequest(&p2pstreamv1.GetDashboardDiagnosticsRequest{}) + if _, err := app.GetDashboardDiagnostics(ctx, req); connect.CodeOf(err) != connect.CodeUnauthenticated { + t.Fatalf("GetDashboardDiagnostics error code = %s, want unauthenticated: %v", connect.CodeOf(err), err) + } +} + +func TestGetDashboardDiagnosticsAggregatesStatusesFailuresAndSamples(t *testing.T) { + ctx := context.Background() + app := NewApp(nil, newServerTestDB(t)) + header := createTestAdminSession(t, app) + seedDashboardRollupDimensionFixtures(t, app.DB) + insertDashboardRollupAgentFixture(t, app.DB, 10) + + now := time.Now().UTC() + insertDiagnosticsProxyEvent(t, app, now.Add(-10*time.Minute), http.StatusOK, "", proxyRequestContext{Method: "GET", Host: "example.test", PathPrefix: "/ok"}, sqlNullInt64(1), sqlNullInt64(1), sqlNullInt64(1), sqlNullInt64(10), 10, 100, 25) + insertDiagnosticsProxyEvent(t, app, now.Add(-9*time.Minute), http.StatusNotFound, "no_route", proxyRequestContext{Method: "GET", Host: "example.test", PathPrefix: "/api/users/..."}, sqlNullInt64(1), sql.NullInt64{}, sql.NullInt64{}, sql.NullInt64{}, 11, 101, 35) + insertDiagnosticsProxyEvent(t, app, now.Add(-8*time.Minute), http.StatusTooManyRequests, "", proxyRequestContext{Method: "POST", Host: "example.test", PathPrefix: "/api/rate"}, sqlNullInt64(1), sqlNullInt64(1), sql.NullInt64{}, sql.NullInt64{}, 12, 102, 45) + insertDiagnosticsProxyEvent(t, app, now.Add(-7*time.Minute), http.StatusBadGateway, "direct_proxy_failed", proxyRequestContext{Method: "GET", Host: "example.test", PathPrefix: "/api/proxy"}, sqlNullInt64(1), sqlNullInt64(1), sqlNullInt64(1), sql.NullInt64{}, 13, 103, 55) + insertDiagnosticsProxyEvent(t, app, now.Add(-6*time.Minute), http.StatusGatewayTimeout, "agent_dial_timeout", proxyRequestContext{}, sqlNullInt64(1), sqlNullInt64(1), sqlNullInt64(1), sqlNullInt64(10), 14, 104, 65) + + req := connect.NewRequest(&p2pstreamv1.GetDashboardDiagnosticsRequest{ + WindowLabel: "bogus", + SampleLimit: 500, + }) + req.Header().Set("Cookie", header.Get("Cookie")) + resp, err := app.GetDashboardDiagnostics(ctx, req) + if err != nil { + t.Fatalf("get diagnostics: %v", err) + } + + if resp.Msg.Label != "1h" { + t.Fatalf("diagnostics label = %q, want 1h", resp.Msg.Label) + } + outcome := resp.Msg.Outcome + if outcome == nil { + t.Fatal("missing diagnostics outcome") + } + if outcome.Requests != 5 || outcome.Success != 1 || outcome.ClientError != 2 || outcome.ServerError != 2 { + t.Fatalf("unexpected outcome status counts: %+v", outcome) + } + if outcome.NonSuccess != 4 || outcome.ProxyFailure != 3 { + t.Fatalf("non-success/proxy failure = %d/%d, want 4/3", outcome.NonSuccess, outcome.ProxyFailure) + } + + statuses := diagnosticsStatusCounts(resp.Msg.StatusCodes) + for _, status := range []int64{200, 404, 429, 502, 504} { + if statuses[status] != 1 { + t.Fatalf("status %d count = %d, want 1 in %+v", status, statuses[status], resp.Msg.StatusCodes) + } + } + for _, status := range resp.Msg.StatusCodes { + if status.StatusCode == http.StatusNotFound && status.ProxyFailure != 1 { + t.Fatalf("404 proxy failures = %d, want 1", status.ProxyFailure) + } + } + if !dashboardDimensionHasLabel(resp.Msg.ErrorKinds, "no_route") || !dashboardDimensionHasLabel(resp.Msg.ErrorKinds, "direct_proxy_failed") { + t.Fatalf("diagnostics error kinds missing expected labels: %+v", resp.Msg.ErrorKinds) + } + if !dashboardDimensionHasLabel(resp.Msg.ProblemListeners, "listener-one") || + !dashboardDimensionHasLabel(resp.Msg.ProblemRoutes, "example.com /api") || + !dashboardDimensionHasLabel(resp.Msg.ProblemRouteTargets, "target-one") || + !dashboardDimensionHasLabel(resp.Msg.ProblemAgents, "agent-10") { + t.Fatalf("diagnostics problem dimensions missing expected labels: listeners=%+v routes=%+v targets=%+v agents=%+v", resp.Msg.ProblemListeners, resp.Msg.ProblemRoutes, resp.Msg.ProblemRouteTargets, resp.Msg.ProblemAgents) + } + + if len(resp.Msg.RecentSamples) != 4 { + t.Fatalf("recent sample count = %d, want 4", len(resp.Msg.RecentSamples)) + } + var sawNoRoute, sawBlankContext bool + for _, sample := range resp.Msg.RecentSamples { + if sample.StatusCode < 400 && sample.ErrorKind == "" { + t.Fatalf("sample includes success without proxy failure: %+v", sample) + } + if sample.StatusCode == http.StatusNotFound && sample.ErrorKind == "no_route" { + sawNoRoute = true + if sample.Method != "GET" || sample.Host != "example.test" || sample.PathPrefix != "/api/users/..." { + t.Fatalf("no_route sample context = method %q host %q path %q", sample.Method, sample.Host, sample.PathPrefix) + } + if sample.ListenerLabel != "listener-one" { + t.Fatalf("no_route sample listener = %q, want listener-one", sample.ListenerLabel) + } + } + if sample.StatusCode == http.StatusGatewayTimeout { + sawBlankContext = sample.Method == "" && sample.Host == "" && sample.PathPrefix == "" + } + } + if !sawNoRoute || !sawBlankContext { + t.Fatalf("recent samples missing no_route or blank-context row: %+v", resp.Msg.RecentSamples) + } +} + +func TestDashboardDiagnosticsSampleLimitClamp(t *testing.T) { + if got := dashboardDiagnosticsSampleLimit(0); got != diagnosticsDefaultSampleLimit { + t.Fatalf("default sample limit = %d, want %d", got, diagnosticsDefaultSampleLimit) + } + if got := dashboardDiagnosticsSampleLimit(500); got != diagnosticsMaxSampleLimit { + t.Fatalf("max sample limit = %d, want %d", got, diagnosticsMaxSampleLimit) + } + if got := dashboardDiagnosticsSampleLimit(12); got != 12 { + t.Fatalf("sample limit = %d, want 12", got) + } +} + +func TestRedactedProxyPathPrefix(t *testing.T) { + tests := []struct { + path string + want string + }{ + {path: "", want: "/"}, + {path: "/", want: "/"}, + {path: "/api?token=secret", want: "/api"}, + {path: "/api/users", want: "/api/users"}, + {path: "/api/users/123/token", want: "/api/users/..."}, + {path: "api/users/123", want: "/api/users/..."}, + } + for _, tt := range tests { + if got := redactedProxyPathPrefix(tt.path); got != tt.want { + t.Fatalf("redactedProxyPathPrefix(%q) = %q, want %q", tt.path, got, tt.want) + } + } +} + func TestProxyRequestEventRollupsAreWrittenWithRawEvent(t *testing.T) { ctx := context.Background() app := NewApp(nil, newServerTestDB(t)) @@ -128,6 +255,17 @@ func TestProxyRequestEventRollupsAreWrittenWithRawEvent(t *testing.T) { if listenerID != 7 || routeID != 9 || agentID != 10 || errorKind != "agent_timeout" || statusClass != 5 { t.Fatalf("unexpected tuple rollup dimensions: listener=%d route=%d agent=%d error=%q status_class=%d", listenerID, routeID, agentID, errorKind, statusClass) } + + var statusCode int64 + if err := app.DB.QueryRowContext(ctx, ` + SELECT status_code, requests, server_error, internal_error + FROM proxy_request_status_rollup_minutes + `).Scan(&statusCode, &requests, &serverError, &internalError); err != nil { + t.Fatalf("read proxy status rollup: %v", err) + } + if statusCode != http.StatusGatewayTimeout || requests != 1 || serverError != 1 || internalError != 1 { + t.Fatalf("unexpected status rollup: status=%d requests=%d server=%d internal=%d", statusCode, requests, serverError, internalError) + } } func TestAgentStatRollupsAreWrittenWithRawStat(t *testing.T) { @@ -262,6 +400,40 @@ func TestGetDashboardDoesNotRunObservabilityCleanup(t *testing.T) { } } +func TestCleanupObservabilityDeletesOldExactStatusRollups(t *testing.T) { + ctx := context.Background() + app := NewApp(&config.Config{ObservabilityRetentionDays: 30}, newServerTestDB(t)) + now := time.Now().UTC() + oldBucket := rollupBucketUnixMillis(now.AddDate(0, 0, -31)) + recentBucket := rollupBucketUnixMillis(now.Add(-time.Hour)) + + if err := app.DB.UpsertProxyRequestStatusRollupMinute(ctx, db.UpsertProxyRequestStatusRollupMinuteParams{ + BucketUnixMillis: oldBucket, + StatusCode: http.StatusNotFound, + Requests: 1, + ClientError: 1, + }); err != nil { + t.Fatalf("insert old status rollup: %v", err) + } + if err := app.DB.UpsertProxyRequestStatusRollupMinute(ctx, db.UpsertProxyRequestStatusRollupMinuteParams{ + BucketUnixMillis: recentBucket, + StatusCode: http.StatusOK, + Requests: 1, + Success: 1, + }); err != nil { + t.Fatalf("insert recent status rollup: %v", err) + } + + app.cleanupObservability(ctx, now) + + if countRows(t, app.DB, `SELECT COUNT(*) FROM proxy_request_status_rollup_minutes WHERE bucket_unix_millis = ?`, oldBucket) != 0 { + t.Fatal("expected old status rollup to be cleaned up") + } + if countRows(t, app.DB, `SELECT COUNT(*) FROM proxy_request_status_rollup_minutes WHERE bucket_unix_millis = ?`, recentBucket) != 1 { + t.Fatal("expected recent status rollup to remain") + } +} + func TestCleanupObservabilityEnforcesProxyRequestRowCap(t *testing.T) { ctx := context.Background() app := NewApp(&config.Config{ObservabilityRetentionDays: 30, ObservabilityMaxRows: 3}, newServerTestDB(t)) @@ -722,6 +894,32 @@ func dashboardTestWindowsByLabel(windows []*p2pstreamv1.DashboardWindowSummary) return byLabel } +func diagnosticsStatusCounts(rows []*p2pstreamv1.DashboardStatusCodeSummary) map[int64]int64 { + counts := make(map[int64]int64, len(rows)) + for _, row := range rows { + counts[row.StatusCode] = row.Requests + } + return counts +} + +func dashboardDimensionHasLabel(rows []*p2pstreamv1.DashboardProxyDimensionSummary, label string) bool { + for _, row := range rows { + if row.Label == label { + return true + } + } + return false +} + +func countRows(t *testing.T, database *db.DB, query string, args ...any) int64 { + t.Helper() + var count int64 + if err := database.QueryRowContext(context.Background(), query, args...).Scan(&count); err != nil { + t.Fatalf("count rows: %v", err) + } + return count +} + func sqlNullInt64(value int64) sql.NullInt64 { return sql.NullInt64{Int64: value, Valid: true} } @@ -829,14 +1027,20 @@ func seedDashboardRollupDimensionFixtures(t *testing.T, database *db.DB) { func insertDashboardRollupAgentFixture(t *testing.T, database *db.DB, id int64) { t.Helper() + publicID := fmt.Sprintf("agent-%d-public", id) + name := fmt.Sprintf("agent-%d", id) + if id == 1 { + publicID = "agent-one-public" + name = "agent-one" + } if _, err := database.ExecContext( context.Background(), `INSERT INTO agents (id, public_id, name, token_hash, enabled) VALUES (?, ?, ?, ?, 1)`, id, - "agent-one-public", - "agent-one", - "hash-one", + publicID, + name, + "hash-"+publicID, ); err != nil { t.Fatalf("insert dashboard agent fixture: %v", err) } @@ -975,6 +1179,41 @@ func insertRollupEventAt( } } +func insertDiagnosticsProxyEvent( + t *testing.T, + app *App, + occurredAt time.Time, + statusCode int, + errorKind string, + requestContext proxyRequestContext, + listenerID sql.NullInt64, + routeID sql.NullInt64, + routeTargetID sql.NullInt64, + agentID sql.NullInt64, + requestBytes uint64, + responseBytes uint64, + durationMs int64, +) { + t.Helper() + if err := app.insertProxyRequestEventWithRollups(context.Background(), db.InsertProxyRequestEventAtParams{ + OccurredAt: occurredAt.UTC(), + StatusCode: int64(statusCode), + DurationMs: durationMs, + ErrorKind: errorKind, + Method: requestContext.Method, + Host: requestContext.Host, + PathPrefix: requestContext.PathPrefix, + ListenerID: listenerID, + RouteID: routeID, + RouteTargetID: routeTargetID, + AgentID: agentID, + RequestBytes: int64FromUint64(requestBytes), + ResponseBytes: int64FromUint64(responseBytes), + }); err != nil { + t.Fatalf("insert diagnostics proxy event: %v", err) + } +} + func insertPublicListenerRow(t *testing.T, app *App, name string) int64 { t.Helper() var id int64 diff --git a/internal/server/observability_rollup.go b/internal/server/observability_rollup.go index c6f7721..d8490e2 100644 --- a/internal/server/observability_rollup.go +++ b/internal/server/observability_rollup.go @@ -33,13 +33,16 @@ func (a *App) insertProxyRequestEventWithRollups(ctx context.Context, event db.I if _, err := qtx.InsertProxyRequestEventAt(ctx, event); err != nil { return err } - total, tuple := proxyRequestRollupParams(event) + total, tuple, status := proxyRequestRollupParams(event) if err := qtx.UpsertProxyRequestRollupMinute(ctx, total); err != nil { return err } if err := qtx.UpsertProxyRequestTupleRollupMinute(ctx, tuple); err != nil { return err } + if err := qtx.UpsertProxyRequestStatusRollupMinute(ctx, status); err != nil { + return err + } return tx.Commit() } @@ -79,7 +82,7 @@ func (a *App) insertAgentStatWithRollup(ctx context.Context, stat db.InsertAgent return tx.Commit() } -func proxyRequestRollupParams(event db.InsertProxyRequestEventAtParams) (db.UpsertProxyRequestRollupMinuteParams, db.UpsertProxyRequestTupleRollupMinuteParams) { +func proxyRequestRollupParams(event db.InsertProxyRequestEventAtParams) (db.UpsertProxyRequestRollupMinuteParams, db.UpsertProxyRequestTupleRollupMinuteParams, db.UpsertProxyRequestStatusRollupMinuteParams) { success := int64Bool(event.StatusCode >= 200 && event.StatusCode < 400) clientError := int64Bool(event.StatusCode >= 400 && event.StatusCode < 500) serverError := int64Bool(event.StatusCode >= 500) @@ -136,7 +139,19 @@ func proxyRequestRollupParams(event db.InsertProxyRequestEventAtParams) (db.Upse RequestBytes: event.RequestBytes, ResponseBytes: event.ResponseBytes, } - return total, tuple + status := db.UpsertProxyRequestStatusRollupMinuteParams{ + BucketUnixMillis: bucketUnixMillis, + StatusCode: event.StatusCode, + Requests: 1, + Success: success, + ClientError: clientError, + ServerError: serverError, + InternalError: internalError, + DurationMsSum: event.DurationMs, + RequestBytes: event.RequestBytes, + ResponseBytes: event.ResponseBytes, + } + return total, tuple, status } func (a *App) observabilityRollupsReady(ctx context.Context) (bool, error) { @@ -195,6 +210,12 @@ func (a *App) backfillObservabilityRollupBatch(ctx context.Context) (bool, error }); err != nil { return false, err } + if err := qtx.BackfillProxyRequestStatusRollupMinutesRange(ctx, db.BackfillProxyRequestStatusRollupMinutesRangeParams{ + FromID: state.ProxyBackfilledThroughID, + ThroughID: next, + }); err != nil { + return false, err + } } if err := qtx.MarkProxyRollupBackfilledThrough(ctx, next); err != nil { return false, err diff --git a/internal/server/public_cache.go b/internal/server/public_cache.go index 7e8fc87..ccec414 100644 --- a/internal/server/public_cache.go +++ b/internal/server/public_cache.go @@ -620,7 +620,7 @@ func (a *App) servePublicCacheHit(w http.ResponseWriter, r *http.Request, resolu attrs["handler"] = "cache" trace.emit(p2pstreamv1.TrafficTraceStage_TRAFFIC_TRACE_STAGE_RESPONSE_SENT, &resolution, nil, statusCode, errorKind, w.Header(), attrs) } - a.recordProxyRequestEventWithRouteTargetCache( + a.recordProxyRequestEventWithRouteTargetCacheAndContext( context.Background(), statusCode, time.Since(startedAt), @@ -636,6 +636,7 @@ func (a *App) servePublicCacheHit(w http.ResponseWriter, r *http.Request, resolu uint64FromInt64(decision.Entry.SizeBytes), observability.requestBytesValue(), observability.responseBytesValue(), + proxyRequestContextFromHTTP(r), ) }() if decision.Entry == nil { diff --git a/internal/server/public_proxy_pipeline.go b/internal/server/public_proxy_pipeline.go index ccda434..d2b0a2c 100644 --- a/internal/server/public_proxy_pipeline.go +++ b/internal/server/public_proxy_pipeline.go @@ -31,6 +31,7 @@ type publicProxyContext struct { Recorder *proxyResponseRecorder Request *http.Request Observability proxyRequestObservability + RequestContext proxyRequestContext Trace *trafficRequestTrace Resolution publicRouteResolution @@ -76,6 +77,7 @@ func newPublicProxyContext(app *App, listenerID int64, w http.ResponseWriter, r Recorder: recorder, Request: r, Observability: observability, + RequestContext: proxyRequestContextFromHTTP(r), Trace: trace, } } @@ -110,7 +112,7 @@ func serveACMEChallengeStage(ctx *publicProxyContext) publicProxyStageResult { if statusCode == 0 { statusCode = http.StatusOK } - ctx.App.recordProxyRequestEventWithIDs( + ctx.App.recordProxyRequestEventWithIDsAndContext( context.Background(), statusCode, time.Since(ctx.StartedAt), @@ -120,6 +122,7 @@ func serveACMEChallengeStage(ctx *publicProxyContext) publicProxyStageResult { sql.NullInt64{}, ctx.Observability.requestBytesValue(), ctx.Observability.responseBytesValue(), + ctx.RequestContext, ) return publicProxyStageDone } @@ -148,7 +151,7 @@ func serveWAFReservedStage(ctx *publicProxyContext) publicProxyStageResult { wafDebugAttributes(decision), ) } - ctx.App.recordProxyRequestEventWithPolicyIDs( + ctx.App.recordProxyRequestEventWithPolicyIDsAndContext( context.Background(), statusCode, time.Since(ctx.StartedAt), @@ -160,6 +163,7 @@ func serveWAFReservedStage(ctx *publicProxyContext) publicProxyStageResult { sql.NullInt64{}, ctx.Observability.requestBytesValue(), ctx.Observability.responseBytesValue(), + ctx.RequestContext, ) return publicProxyStageDone } @@ -196,7 +200,7 @@ func wafPolicyStage(ctx *publicProxyContext) publicProxyStageResult { wafDebugAttributes(decision), ) } - ctx.App.recordProxyRequestEventWithPolicyIDs( + ctx.App.recordProxyRequestEventWithPolicyIDsAndContext( context.Background(), statusCode, time.Since(ctx.StartedAt), @@ -208,6 +212,7 @@ func wafPolicyStage(ctx *publicProxyContext) publicProxyStageResult { sql.NullInt64{}, ctx.Observability.requestBytesValue(), ctx.Observability.responseBytesValue(), + ctx.RequestContext, ) return publicProxyStageDone } @@ -241,7 +246,7 @@ func rateLimitStage(ctx *publicProxyContext) publicProxyStageResult { }, ) } - ctx.App.recordProxyRequestEventWithIDs( + ctx.App.recordProxyRequestEventWithIDsAndContext( context.Background(), decision.StatusCode, time.Since(ctx.StartedAt), @@ -251,6 +256,7 @@ func rateLimitStage(ctx *publicProxyContext) publicProxyStageResult { sql.NullInt64{}, ctx.Observability.requestBytesValue(), ctx.Observability.responseBytesValue(), + ctx.RequestContext, ) return publicProxyStageDone } @@ -306,7 +312,7 @@ func routeResolutionStage(ctx *publicProxyContext) publicProxyStageResult { } else { http.Error(ctx.ResponseWriter, err.Error(), statusCode) } - ctx.App.recordProxyRequestEventWithIDs( + ctx.App.recordProxyRequestEventWithIDsAndContext( context.Background(), statusCode, time.Since(ctx.StartedAt), @@ -316,6 +322,7 @@ func routeResolutionStage(ctx *publicProxyContext) publicProxyStageResult { sql.NullInt64{}, ctx.Observability.requestBytesValue(), ctx.Observability.responseBytesValue(), + ctx.RequestContext, ) if ctx.Trace != nil { failureResolution := publicRouteResolution{ListenerID: sql.NullInt64{Int64: ctx.ListenerID, Valid: true}} diff --git a/internal/server/public_routing.go b/internal/server/public_routing.go index d14d32c..b21799b 100644 --- a/internal/server/public_routing.go +++ b/internal/server/public_routing.go @@ -306,7 +306,7 @@ func (a *App) redirectRouteResponse(w http.ResponseWriter, r *http.Request, reso } trace.emit(stage, &resolution, nil, statusCode, errorKind, w.Header(), attributes) } - a.recordProxyRequestEventWithIDs( + a.recordProxyRequestEventWithIDsAndContext( context.Background(), statusCode, time.Since(startedAt), @@ -316,6 +316,7 @@ func (a *App) redirectRouteResponse(w http.ResponseWriter, r *http.Request, reso sql.NullInt64{}, observability.requestBytesValue(), observability.responseBytesValue(), + proxyRequestContextFromHTTP(r), ) }() } @@ -335,7 +336,7 @@ func (a *App) staticTargetResponse(w http.ResponseWriter, r *http.Request, resol } trace.emit(stage, &resolution, nil, statusCode, errorKind, w.Header(), map[string]string{"handler": "static"}) } - a.recordProxyRequestEventWithRouteTargetCache( + a.recordProxyRequestEventWithRouteTargetCacheAndContext( context.Background(), statusCode, time.Since(startedAt), @@ -351,6 +352,7 @@ func (a *App) staticTargetResponse(w http.ResponseWriter, r *http.Request, resol 0, observability.requestBytesValue(), observability.responseBytesValue(), + proxyRequestContextFromHTTP(r), ) }() @@ -405,7 +407,7 @@ func (a *App) proxyRouteTargetRequest(w http.ResponseWriter, r *http.Request, re } trace.emit(stage, &resolution, agent, statusCode, errorKind, w.Header(), map[string]string{"handler": handler}) } - a.recordProxyRequestEventWithRouteTargetCache( + a.recordProxyRequestEventWithRouteTargetCacheAndContext( context.Background(), statusCode, time.Since(startedAt), @@ -421,6 +423,7 @@ func (a *App) proxyRouteTargetRequest(w http.ResponseWriter, r *http.Request, re cacheBytes(cacheDecision), observability.requestBytesValue(), observability.responseBytesValue(), + proxyRequestContextFromHTTP(r), ) }() diff --git a/proto/p2pstream/v1/management.proto b/proto/p2pstream/v1/management.proto index 8459c6b..48587b0 100644 --- a/proto/p2pstream/v1/management.proto +++ b/proto/p2pstream/v1/management.proto @@ -1654,6 +1654,66 @@ message GetDashboardResponse { repeated DashboardProxyDimensionSummary top_route_targets = 16; } +message GetDashboardDiagnosticsRequest { + string window_label = 1; + int64 sample_limit = 2; +} + +message DashboardDiagnosticsOutcomeSummary { + string label = 1; + int64 since_unix_millis = 2; + int64 requests = 3; + int64 success = 4; + int64 client_error = 5; + int64 server_error = 6; + int64 non_success = 7; + int64 proxy_failure = 8; + int64 avg_duration_ms = 9; + int64 max_duration_ms = 10; +} + +message DashboardStatusCodeSummary { + int64 status_code = 1; + int64 requests = 2; + int64 success = 3; + int64 client_error = 4; + int64 server_error = 5; + int64 proxy_failure = 6; + int64 avg_duration_ms = 7; + uint64 request_bytes = 8; + uint64 response_bytes = 9; +} + +message DashboardDiagnosticsSample { + int64 occurred_at_unix_millis = 1; + string method = 2; + string host = 3; + string path_prefix = 4; + int64 status_code = 5; + string error_kind = 6; + string listener_label = 7; + string route_label = 8; + string route_target_label = 9; + string agent_label = 10; + int64 duration_ms = 11; + uint64 request_bytes = 12; + uint64 response_bytes = 13; +} + +message GetDashboardDiagnosticsResponse { + string label = 1; + int64 since_unix_millis = 2; + int64 generated_at_unix_millis = 3; + DashboardDiagnosticsOutcomeSummary outcome = 4; + repeated DashboardStatusCodeSummary status_codes = 5; + repeated DashboardProxyDimensionSummary error_kinds = 6; + repeated DashboardProxyDimensionSummary problem_listeners = 7; + repeated DashboardProxyDimensionSummary problem_routes = 8; + repeated DashboardProxyDimensionSummary problem_route_targets = 9; + repeated DashboardProxyDimensionSummary problem_agents = 10; + repeated DashboardDiagnosticsSample recent_samples = 11; +} + message TrafficTraceSettings { bool enabled = 1; TrafficTraceLevel level = 2; @@ -1804,6 +1864,7 @@ service AgentManagementService { rpc ReportStats(AgentStatsRequest) returns (AgentStatsResponse) {} rpc GetStatus(GetStatusRequest) returns (GetStatusResponse) {} rpc GetDashboard(GetDashboardRequest) returns (GetDashboardResponse) {} + rpc GetDashboardDiagnostics(GetDashboardDiagnosticsRequest) returns (GetDashboardDiagnosticsResponse) {} rpc GetTrafficTraceSettings(GetTrafficTraceSettingsRequest) returns (GetTrafficTraceSettingsResponse) {} rpc SetTrafficTraceSettings(SetTrafficTraceSettingsRequest) returns (SetTrafficTraceSettingsResponse) {} rpc StreamTrafficTraceEvents(StreamTrafficTraceEventsRequest) returns (stream StreamTrafficTraceEventsResponse) {} diff --git a/sql/query.sql b/sql/query.sql index cb0be6a..ff7b70f 100644 --- a/sql/query.sql +++ b/sql/query.sql @@ -46,16 +46,16 @@ LIMIT 1; -- name: InsertProxyRequestEvent :exec INSERT INTO proxy_request_events ( - status_code, duration_ms, error_kind, listener_id, route_id, route_target_id, waf_rule_id, waf_action, agent_id, request_bytes, response_bytes, cache_rule_id, cache_status, cache_bytes + status_code, duration_ms, error_kind, method, host, path_prefix, listener_id, route_id, route_target_id, waf_rule_id, waf_action, agent_id, request_bytes, response_bytes, cache_rule_id, cache_status, cache_bytes ) VALUES ( - ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? + ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ); -- name: InsertProxyRequestEventAt :one INSERT INTO proxy_request_events ( - occurred_at, status_code, duration_ms, error_kind, listener_id, route_id, route_target_id, waf_rule_id, waf_action, agent_id, request_bytes, response_bytes, cache_rule_id, cache_status, cache_bytes + occurred_at, status_code, duration_ms, error_kind, method, host, path_prefix, listener_id, route_id, route_target_id, waf_rule_id, waf_action, agent_id, request_bytes, response_bytes, cache_rule_id, cache_status, cache_bytes ) VALUES ( - ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? + ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) RETURNING id; @@ -107,6 +107,24 @@ ON CONFLICT(bucket_unix_millis, listener_id, route_target_id, route_id, agent_id response_bytes = proxy_request_tuple_rollup_minutes.response_bytes + excluded.response_bytes, updated_at = CURRENT_TIMESTAMP; +-- name: UpsertProxyRequestStatusRollupMinute :exec +INSERT INTO proxy_request_status_rollup_minutes ( + bucket_unix_millis, status_code, requests, success, client_error, server_error, + internal_error, duration_ms_sum, request_bytes, response_bytes +) VALUES ( + ?, ?, ?, ?, ?, ?, ?, ?, ?, ? +) +ON CONFLICT(bucket_unix_millis, status_code) DO UPDATE SET + requests = proxy_request_status_rollup_minutes.requests + excluded.requests, + success = proxy_request_status_rollup_minutes.success + excluded.success, + client_error = proxy_request_status_rollup_minutes.client_error + excluded.client_error, + server_error = proxy_request_status_rollup_minutes.server_error + excluded.server_error, + internal_error = proxy_request_status_rollup_minutes.internal_error + excluded.internal_error, + duration_ms_sum = proxy_request_status_rollup_minutes.duration_ms_sum + excluded.duration_ms_sum, + request_bytes = proxy_request_status_rollup_minutes.request_bytes + excluded.request_bytes, + response_bytes = proxy_request_status_rollup_minutes.response_bytes + excluded.response_bytes, + updated_at = CURRENT_TIMESTAMP; + -- name: UpsertAgentStatRollupMinute :exec INSERT INTO agent_stat_rollup_minutes ( bucket_unix_millis, samples, req_success, req_client_error, req_server_error, req_internal_error, @@ -477,6 +495,129 @@ WHERE r.bucket_unix_millis >= ? GROUP BY r.status_class ORDER BY id ASC; +-- name: ListProxyStatusCodeRollupsSince :many +SELECT + r.status_code, + CAST(COALESCE(SUM(r.requests), 0) AS INTEGER) AS requests, + CAST(COALESCE(SUM(r.success), 0) AS INTEGER) AS success, + CAST(COALESCE(SUM(r.client_error), 0) AS INTEGER) AS client_error, + CAST(COALESCE(SUM(r.server_error), 0) AS INTEGER) AS server_error, + CAST(COALESCE(SUM(r.internal_error), 0) AS INTEGER) AS internal_error, + CAST(CASE WHEN COALESCE(SUM(r.requests), 0) > 0 THEN COALESCE(SUM(r.duration_ms_sum), 0) / SUM(r.requests) ELSE 0 END AS INTEGER) AS avg_duration_ms, + CAST(COALESCE(SUM(r.request_bytes), 0) AS INTEGER) AS request_bytes, + CAST(COALESCE(SUM(r.response_bytes), 0) AS INTEGER) AS response_bytes +FROM proxy_request_status_rollup_minutes r +WHERE r.bucket_unix_millis >= ? +GROUP BY r.status_code +ORDER BY requests DESC, status_code ASC; + +-- name: ListProblemProxyListenersRollupsSince :many +SELECT + r.listener_id AS id, + COALESCE(pl.name, CASE WHEN r.listener_id = 0 THEN 'unknown listener' ELSE 'listener #' || r.listener_id END) AS label, + CAST(COALESCE(SUM(r.requests), 0) AS INTEGER) AS requests, + CAST(COALESCE(SUM(r.success), 0) AS INTEGER) AS success, + CAST(COALESCE(SUM(r.client_error), 0) AS INTEGER) AS client_error, + CAST(COALESCE(SUM(r.server_error), 0) AS INTEGER) AS server_error, + CAST(COALESCE(SUM(r.internal_error), 0) AS INTEGER) AS internal_error, + CAST(CASE WHEN COALESCE(SUM(r.requests), 0) > 0 THEN COALESCE(SUM(r.duration_ms_sum), 0) / SUM(r.requests) ELSE 0 END AS INTEGER) AS avg_duration_ms, + CAST(COALESCE(SUM(r.request_bytes), 0) AS INTEGER) AS request_bytes, + CAST(COALESCE(SUM(r.response_bytes), 0) AS INTEGER) AS response_bytes +FROM proxy_request_tuple_rollup_minutes r +LEFT JOIN public_listeners pl ON pl.id = r.listener_id +WHERE r.bucket_unix_millis >= ? +GROUP BY r.listener_id, pl.name +HAVING SUM(r.client_error) > 0 OR SUM(r.server_error) > 0 OR SUM(r.internal_error) > 0 +ORDER BY internal_error DESC, server_error DESC, client_error DESC, requests DESC, id ASC +LIMIT 10; + +-- name: ListProblemProxyRoutesRollupsSince :many +SELECT + r.route_id AS id, + CASE + WHEN r.route_id = 0 THEN 'Default route' + WHEN pr.id IS NULL THEN 'route #' || r.route_id + WHEN pr.host_pattern != '' AND pr.path_prefix != '' THEN pr.host_pattern || ' ' || pr.path_prefix + WHEN pr.host_pattern != '' THEN pr.host_pattern + WHEN pr.path_prefix != '' THEN pr.path_prefix + ELSE 'route #' || pr.id + END AS label, + CAST(COALESCE(SUM(r.requests), 0) AS INTEGER) AS requests, + CAST(COALESCE(SUM(r.success), 0) AS INTEGER) AS success, + CAST(COALESCE(SUM(r.client_error), 0) AS INTEGER) AS client_error, + CAST(COALESCE(SUM(r.server_error), 0) AS INTEGER) AS server_error, + CAST(COALESCE(SUM(r.internal_error), 0) AS INTEGER) AS internal_error, + CAST(CASE WHEN COALESCE(SUM(r.requests), 0) > 0 THEN COALESCE(SUM(r.duration_ms_sum), 0) / SUM(r.requests) ELSE 0 END AS INTEGER) AS avg_duration_ms, + CAST(COALESCE(SUM(r.request_bytes), 0) AS INTEGER) AS request_bytes, + CAST(COALESCE(SUM(r.response_bytes), 0) AS INTEGER) AS response_bytes +FROM proxy_request_tuple_rollup_minutes r +LEFT JOIN public_routes pr ON pr.id = r.route_id +WHERE r.bucket_unix_millis >= ? +GROUP BY r.route_id, pr.id, pr.host_pattern, pr.path_prefix +HAVING SUM(r.client_error) > 0 OR SUM(r.server_error) > 0 OR SUM(r.internal_error) > 0 +ORDER BY internal_error DESC, server_error DESC, client_error DESC, requests DESC, id ASC +LIMIT 10; + +-- name: ListProblemProxyRouteTargetsRollupsSince :many +SELECT + r.route_target_id AS id, + COALESCE(prt.name, CASE WHEN r.route_target_id = 0 THEN 'unknown target' ELSE 'target #' || r.route_target_id END) AS label, + CAST(COALESCE(SUM(r.requests), 0) AS INTEGER) AS requests, + CAST(COALESCE(SUM(r.success), 0) AS INTEGER) AS success, + CAST(COALESCE(SUM(r.client_error), 0) AS INTEGER) AS client_error, + CAST(COALESCE(SUM(r.server_error), 0) AS INTEGER) AS server_error, + CAST(COALESCE(SUM(r.internal_error), 0) AS INTEGER) AS internal_error, + CAST(CASE WHEN COALESCE(SUM(r.requests), 0) > 0 THEN COALESCE(SUM(r.duration_ms_sum), 0) / SUM(r.requests) ELSE 0 END AS INTEGER) AS avg_duration_ms, + CAST(COALESCE(SUM(r.request_bytes), 0) AS INTEGER) AS request_bytes, + CAST(COALESCE(SUM(r.response_bytes), 0) AS INTEGER) AS response_bytes +FROM proxy_request_tuple_rollup_minutes r +LEFT JOIN public_route_targets prt ON prt.id = r.route_target_id +WHERE r.bucket_unix_millis >= ? +GROUP BY r.route_target_id, prt.name +HAVING SUM(r.client_error) > 0 OR SUM(r.server_error) > 0 OR SUM(r.internal_error) > 0 +ORDER BY internal_error DESC, server_error DESC, client_error DESC, requests DESC, id ASC +LIMIT 10; + +-- name: ListProblemProxyAgentsRollupsSince :many +SELECT + r.agent_id AS id, + COALESCE(a.name, 'agent #' || r.agent_id) AS label, + CAST(COALESCE(SUM(r.requests), 0) AS INTEGER) AS requests, + CAST(COALESCE(SUM(r.success), 0) AS INTEGER) AS success, + CAST(COALESCE(SUM(r.client_error), 0) AS INTEGER) AS client_error, + CAST(COALESCE(SUM(r.server_error), 0) AS INTEGER) AS server_error, + CAST(COALESCE(SUM(r.internal_error), 0) AS INTEGER) AS internal_error, + CAST(CASE WHEN COALESCE(SUM(r.requests), 0) > 0 THEN COALESCE(SUM(r.duration_ms_sum), 0) / SUM(r.requests) ELSE 0 END AS INTEGER) AS avg_duration_ms, + CAST(COALESCE(SUM(r.request_bytes), 0) AS INTEGER) AS request_bytes, + CAST(COALESCE(SUM(r.response_bytes), 0) AS INTEGER) AS response_bytes +FROM proxy_request_tuple_rollup_minutes r +LEFT JOIN agents a ON a.id = r.agent_id +WHERE r.bucket_unix_millis >= ? + AND r.agent_id != 0 +GROUP BY r.agent_id, a.name +HAVING SUM(r.client_error) > 0 OR SUM(r.server_error) > 0 OR SUM(r.internal_error) > 0 +ORDER BY internal_error DESC, server_error DESC, client_error DESC, requests DESC, id ASC +LIMIT 10; + +-- name: ListProblemProxyErrorKindsRollupsSince :many +SELECT + CAST(0 AS INTEGER) AS id, + r.error_kind AS label, + CAST(COALESCE(SUM(r.requests), 0) AS INTEGER) AS requests, + CAST(COALESCE(SUM(r.success), 0) AS INTEGER) AS success, + CAST(COALESCE(SUM(r.client_error), 0) AS INTEGER) AS client_error, + CAST(COALESCE(SUM(r.server_error), 0) AS INTEGER) AS server_error, + CAST(COALESCE(SUM(r.internal_error), 0) AS INTEGER) AS internal_error, + CAST(CASE WHEN COALESCE(SUM(r.requests), 0) > 0 THEN COALESCE(SUM(r.duration_ms_sum), 0) / SUM(r.requests) ELSE 0 END AS INTEGER) AS avg_duration_ms, + CAST(COALESCE(SUM(r.request_bytes), 0) AS INTEGER) AS request_bytes, + CAST(COALESCE(SUM(r.response_bytes), 0) AS INTEGER) AS response_bytes +FROM proxy_request_tuple_rollup_minutes r +WHERE r.bucket_unix_millis >= ? + AND r.error_kind != '' +GROUP BY r.error_kind +ORDER BY internal_error DESC, server_error DESC, client_error DESC, requests DESC, label ASC +LIMIT 10; + -- name: ListProxyTrafficBucketRollupsSince :many SELECT CAST((bucket_unix_millis / (CAST(sqlc.arg(bucket_seconds) AS INTEGER) * 1000)) * (CAST(sqlc.arg(bucket_seconds) AS INTEGER) * 1000) AS INTEGER) AS bucket_unix_millis, @@ -538,6 +679,38 @@ FROM proxy_request_tuple_rollup_minutes WHERE bucket_unix_millis >= ? ORDER BY bucket_unix_millis ASC; +-- name: ListRecentProxyProblemSamplesSince :many +SELECT + pre.occurred_at, + pre.method, + pre.host, + pre.path_prefix, + pre.status_code, + pre.error_kind, + COALESCE(pl.name, CASE WHEN pre.listener_id IS NULL THEN '' ELSE 'listener #' || pre.listener_id END) AS listener_label, + CASE + WHEN pre.route_id IS NULL THEN '' + WHEN pr.id IS NULL THEN 'route #' || pre.route_id + WHEN pr.host_pattern != '' AND pr.path_prefix != '' THEN pr.host_pattern || ' ' || pr.path_prefix + WHEN pr.host_pattern != '' THEN pr.host_pattern + WHEN pr.path_prefix != '' THEN pr.path_prefix + ELSE 'route #' || pr.id + END AS route_label, + COALESCE(prt.name, CASE WHEN pre.route_target_id IS NULL THEN '' ELSE 'target #' || pre.route_target_id END) AS route_target_label, + COALESCE(a.name, CASE WHEN pre.agent_id IS NULL THEN '' ELSE 'agent #' || pre.agent_id END) AS agent_label, + pre.duration_ms, + pre.request_bytes, + pre.response_bytes +FROM proxy_request_events AS pre INDEXED BY idx_proxy_request_events_occurred_at +LEFT JOIN public_listeners pl ON pl.id = pre.listener_id +LEFT JOIN public_routes pr ON pr.id = pre.route_id +LEFT JOIN public_route_targets prt ON prt.id = pre.route_target_id +LEFT JOIN agents a ON a.id = pre.agent_id +WHERE pre.occurred_at >= sqlc.arg(since) + AND (pre.status_code >= 400 OR pre.error_kind != '') +ORDER BY pre.occurred_at DESC, pre.id DESC +LIMIT sqlc.arg(limit); + -- name: ListAgentStatRollupMinutesSince :many SELECT bucket_unix_millis, @@ -752,6 +925,10 @@ WHERE bucket_unix_millis < ?; DELETE FROM proxy_request_tuple_rollup_minutes WHERE bucket_unix_millis < ?; +-- name: DeleteProxyRequestStatusRollupsBefore :exec +DELETE FROM proxy_request_status_rollup_minutes +WHERE bucket_unix_millis < ?; + -- name: DeleteAgentStatRollupsBefore :exec DELETE FROM agent_stat_rollup_minutes WHERE bucket_unix_millis < ?; @@ -870,6 +1047,37 @@ ON CONFLICT(bucket_unix_millis, listener_id, route_target_id, route_id, agent_id response_bytes = proxy_request_tuple_rollup_minutes.response_bytes + excluded.response_bytes, updated_at = CURRENT_TIMESTAMP; +-- name: BackfillProxyRequestStatusRollupMinutesRange :exec +INSERT INTO proxy_request_status_rollup_minutes ( + bucket_unix_millis, status_code, requests, success, client_error, server_error, + internal_error, duration_ms_sum, request_bytes, response_bytes +) +SELECT + CAST((unixepoch(occurred_at) / 60) * 60 * 1000 AS INTEGER) AS bucket_unix_millis, + status_code, + COUNT(*) AS requests, + CAST(COALESCE(SUM(CASE WHEN status_code >= 200 AND status_code < 400 THEN 1 ELSE 0 END), 0) AS INTEGER) AS success, + CAST(COALESCE(SUM(CASE WHEN status_code >= 400 AND status_code < 500 THEN 1 ELSE 0 END), 0) AS INTEGER) AS client_error, + CAST(COALESCE(SUM(CASE WHEN status_code >= 500 THEN 1 ELSE 0 END), 0) AS INTEGER) AS server_error, + CAST(COALESCE(SUM(CASE WHEN error_kind != '' THEN 1 ELSE 0 END), 0) AS INTEGER) AS internal_error, + CAST(COALESCE(SUM(duration_ms), 0) AS INTEGER) AS duration_ms_sum, + CAST(COALESCE(SUM(request_bytes), 0) AS INTEGER) AS request_bytes, + CAST(COALESCE(SUM(response_bytes), 0) AS INTEGER) AS response_bytes +FROM proxy_request_events +WHERE id > sqlc.arg(from_id) + AND id <= sqlc.arg(through_id) +GROUP BY bucket_unix_millis, status_code +ON CONFLICT(bucket_unix_millis, status_code) DO UPDATE SET + requests = proxy_request_status_rollup_minutes.requests + excluded.requests, + success = proxy_request_status_rollup_minutes.success + excluded.success, + client_error = proxy_request_status_rollup_minutes.client_error + excluded.client_error, + server_error = proxy_request_status_rollup_minutes.server_error + excluded.server_error, + internal_error = proxy_request_status_rollup_minutes.internal_error + excluded.internal_error, + duration_ms_sum = proxy_request_status_rollup_minutes.duration_ms_sum + excluded.duration_ms_sum, + request_bytes = proxy_request_status_rollup_minutes.request_bytes + excluded.request_bytes, + response_bytes = proxy_request_status_rollup_minutes.response_bytes + excluded.response_bytes, + updated_at = CURRENT_TIMESTAMP; + -- name: BackfillAgentStatRollupMinutesRange :exec INSERT INTO agent_stat_rollup_minutes ( bucket_unix_millis, samples, req_success, req_client_error, req_server_error, req_internal_error, diff --git a/sql/schema.sql b/sql/schema.sql index c682866..6d41489 100644 --- a/sql/schema.sql +++ b/sql/schema.sql @@ -38,6 +38,9 @@ CREATE TABLE IF NOT EXISTS proxy_request_events ( status_code INTEGER NOT NULL, duration_ms INTEGER NOT NULL, error_kind TEXT NOT NULL DEFAULT '', + method TEXT NOT NULL DEFAULT '', + host TEXT NOT NULL DEFAULT '', + path_prefix TEXT NOT NULL DEFAULT '', listener_id INTEGER, route_id INTEGER, route_target_id INTEGER, @@ -95,6 +98,22 @@ CREATE TABLE IF NOT EXISTS proxy_request_tuple_rollup_minutes ( PRIMARY KEY (bucket_unix_millis, listener_id, route_id, route_target_id, agent_id, error_kind, status_class) ); +CREATE TABLE IF NOT EXISTS proxy_request_status_rollup_minutes ( + bucket_unix_millis INTEGER NOT NULL, + status_code INTEGER NOT NULL, + requests INTEGER NOT NULL DEFAULT 0, + success INTEGER NOT NULL DEFAULT 0, + client_error INTEGER NOT NULL DEFAULT 0, + server_error INTEGER NOT NULL DEFAULT 0, + internal_error INTEGER NOT NULL DEFAULT 0, + duration_ms_sum INTEGER NOT NULL DEFAULT 0, + request_bytes INTEGER NOT NULL DEFAULT 0, + response_bytes INTEGER NOT NULL DEFAULT 0, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (bucket_unix_millis, status_code) +); + CREATE TABLE IF NOT EXISTS agent_stat_rollup_minutes ( bucket_unix_millis INTEGER PRIMARY KEY, samples INTEGER NOT NULL DEFAULT 0, @@ -503,6 +522,10 @@ ON proxy_request_events (route_target_id); CREATE INDEX IF NOT EXISTS idx_proxy_request_events_agent_id ON proxy_request_events (agent_id); +CREATE INDEX IF NOT EXISTS idx_proxy_request_events_recent_problem +ON proxy_request_events (occurred_at DESC) +WHERE status_code >= 400 OR error_kind != ''; + CREATE INDEX IF NOT EXISTS idx_public_routes_listener_priority ON public_routes (listener_id, priority, id); diff --git a/tests/integration/observability_test.go b/tests/integration/observability_test.go index eeb06c7..359d77b 100644 --- a/tests/integration/observability_test.go +++ b/tests/integration/observability_test.go @@ -219,7 +219,7 @@ func TestProxyRequestEventRecordedCountsOnly(t *testing.T) { } columns := proxyRequestEventColumns(t, database) - expected := []string{"agent_id", "cache_bytes", "cache_rule_id", "cache_status", "duration_ms", "error_kind", "id", "listener_id", "occurred_at", "request_bytes", "response_bytes", "route_id", "route_target_id", "status_code", "waf_action", "waf_rule_id"} + expected := []string{"agent_id", "cache_bytes", "cache_rule_id", "cache_status", "duration_ms", "error_kind", "host", "id", "listener_id", "method", "occurred_at", "path_prefix", "request_bytes", "response_bytes", "route_id", "route_target_id", "status_code", "waf_action", "waf_rule_id"} if !equalStringSlices(columns, expected) { t.Fatalf("proxy_request_events columns changed: got %v, want %v", columns, expected) } diff --git a/web/management/src/App.vue b/web/management/src/App.vue index a21cb49..082a785 100644 --- a/web/management/src/App.vue +++ b/web/management/src/App.vue @@ -40,6 +40,7 @@ const error = ref(null); const tabs = [ { path: "/overview", label: "Overview" }, + { path: "/diagnostics", label: "Diagnostics" }, { path: "/traffic", label: "Traffic" }, { path: "/proxy", label: "Proxy" }, { path: "/agent", label: "Agents" }, diff --git a/web/management/src/gen/proto/p2pstream/v1/management_connect.ts b/web/management/src/gen/proto/p2pstream/v1/management_connect.ts index 59bbbc3..760ebc9 100644 --- a/web/management/src/gen/proto/p2pstream/v1/management_connect.ts +++ b/web/management/src/gen/proto/p2pstream/v1/management_connect.ts @@ -3,7 +3,7 @@ /* eslint-disable */ // @ts-nocheck -import { AgentStatsRequest, AgentStatsResponse, CreateAgentRequest, CreateAgentResponse, CreateEnvironmentRequest, CreateEnvironmentResponse, CreateManagementAccessTokenRequest, CreateManagementAccessTokenResponse, CreatePublicCacheRuleRequest, CreatePublicCacheRuleResponse, CreatePublicListenerRequest, CreatePublicListenerResponse, CreatePublicRateLimitRuleRequest, CreatePublicRateLimitRuleResponse, CreatePublicResponseTemplateRequest, CreatePublicResponseTemplateResponse, CreatePublicRouteRequest, CreatePublicRouteResponse, CreatePublicTlsCertificateRequest, CreatePublicTlsCertificateResponse, CreatePublicTlsDnsCredentialRequest, CreatePublicTlsDnsCredentialResponse, CreatePublicTrafficShaperRuleRequest, CreatePublicTrafficShaperRuleResponse, CreatePublicWafCaptchaProviderRequest, CreatePublicWafCaptchaProviderResponse, CreatePublicWafRuleRequest, CreatePublicWafRuleResponse, DeleteAgentRequest, DeleteAgentResponse, DeleteEnvironmentRequest, DeleteEnvironmentResponse, DeleteManagementAccessTokenRequest, DeleteManagementAccessTokenResponse, DeletePublicCacheRuleRequest, DeletePublicCacheRuleResponse, DeletePublicListenerRequest, DeletePublicListenerResponse, DeletePublicRateLimitRuleRequest, DeletePublicRateLimitRuleResponse, DeletePublicResponseTemplateRequest, DeletePublicResponseTemplateResponse, DeletePublicRouteRequest, DeletePublicRouteResponse, DeletePublicTlsCertificateRequest, DeletePublicTlsCertificateResponse, DeletePublicTlsDnsCredentialRequest, DeletePublicTlsDnsCredentialResponse, DeletePublicTrafficShaperRuleRequest, DeletePublicTrafficShaperRuleResponse, DeletePublicWafCaptchaProviderRequest, DeletePublicWafCaptchaProviderResponse, DeletePublicWafRuleRequest, DeletePublicWafRuleResponse, DisablePublicListenerRequest, DisablePublicListenerResponse, DiscoverEnvironmentCertificateRequest, DiscoverEnvironmentCertificateResponse, EnablePublicListenerRequest, EnablePublicListenerResponse, GetCurrentUserRequest, GetCurrentUserResponse, GetDashboardRequest, GetDashboardResponse, GetPublicProxyConfigRequest, GetPublicProxyConfigResponse, GetSetupStateRequest, GetSetupStateResponse, GetStatusRequest, GetStatusResponse, GetTrafficTraceSettingsRequest, GetTrafficTraceSettingsResponse, ListEnvironmentsRequest, ListEnvironmentsResponse, ListManagementAccessTokensRequest, ListManagementAccessTokensResponse, ListPublicRouteTargetHealthTracesRequest, ListPublicRouteTargetHealthTracesResponse, LoginRequest, LoginResponse, LogoutRequest, LogoutResponse, PurgePublicCacheRequest, PurgePublicCacheResponse, RenewPublicTlsCertificateRequest, RenewPublicTlsCertificateResponse, RotateAgentTokenRequest, RotateAgentTokenResponse, SetTrafficTraceSettingsRequest, SetTrafficTraceSettingsResponse, SetupAdminRequest, SetupAdminResponse, StartProxyRequest, StartProxyResponse, StartPublicListenerRequest, StartPublicListenerResponse, StopProxyRequest, StopProxyResponse, StopPublicListenerRequest, StopPublicListenerResponse, StreamTrafficTraceEventsRequest, StreamTrafficTraceEventsResponse, TestEnvironmentRequest, TestEnvironmentResponse, TrustEnvironmentCertificateRequest, TrustEnvironmentCertificateResponse, UpdateAgentRequest, UpdateAgentResponse, UpdateEnvironmentRequest, UpdateEnvironmentResponse, UpdatePublicCacheRuleRequest, UpdatePublicCacheRuleResponse, UpdatePublicCacheSettingsRequest, UpdatePublicCacheSettingsResponse, UpdatePublicListenerRequest, UpdatePublicListenerResponse, UpdatePublicRateLimitRuleRequest, UpdatePublicRateLimitRuleResponse, UpdatePublicResponseTemplateRequest, UpdatePublicResponseTemplateResponse, UpdatePublicRouteRequest, UpdatePublicRouteResponse, UpdatePublicTlsCertificateRequest, UpdatePublicTlsCertificateResponse, UpdatePublicTlsDnsCredentialRequest, UpdatePublicTlsDnsCredentialResponse, UpdatePublicTrafficShaperRuleRequest, UpdatePublicTrafficShaperRuleResponse, UpdatePublicWafCaptchaProviderRequest, UpdatePublicWafCaptchaProviderResponse, UpdatePublicWafRuleRequest, UpdatePublicWafRuleResponse } from "./management_pb"; +import { AgentStatsRequest, AgentStatsResponse, CreateAgentRequest, CreateAgentResponse, CreateEnvironmentRequest, CreateEnvironmentResponse, CreateManagementAccessTokenRequest, CreateManagementAccessTokenResponse, CreatePublicCacheRuleRequest, CreatePublicCacheRuleResponse, CreatePublicListenerRequest, CreatePublicListenerResponse, CreatePublicRateLimitRuleRequest, CreatePublicRateLimitRuleResponse, CreatePublicResponseTemplateRequest, CreatePublicResponseTemplateResponse, CreatePublicRouteRequest, CreatePublicRouteResponse, CreatePublicTlsCertificateRequest, CreatePublicTlsCertificateResponse, CreatePublicTlsDnsCredentialRequest, CreatePublicTlsDnsCredentialResponse, CreatePublicTrafficShaperRuleRequest, CreatePublicTrafficShaperRuleResponse, CreatePublicWafCaptchaProviderRequest, CreatePublicWafCaptchaProviderResponse, CreatePublicWafRuleRequest, CreatePublicWafRuleResponse, DeleteAgentRequest, DeleteAgentResponse, DeleteEnvironmentRequest, DeleteEnvironmentResponse, DeleteManagementAccessTokenRequest, DeleteManagementAccessTokenResponse, DeletePublicCacheRuleRequest, DeletePublicCacheRuleResponse, DeletePublicListenerRequest, DeletePublicListenerResponse, DeletePublicRateLimitRuleRequest, DeletePublicRateLimitRuleResponse, DeletePublicResponseTemplateRequest, DeletePublicResponseTemplateResponse, DeletePublicRouteRequest, DeletePublicRouteResponse, DeletePublicTlsCertificateRequest, DeletePublicTlsCertificateResponse, DeletePublicTlsDnsCredentialRequest, DeletePublicTlsDnsCredentialResponse, DeletePublicTrafficShaperRuleRequest, DeletePublicTrafficShaperRuleResponse, DeletePublicWafCaptchaProviderRequest, DeletePublicWafCaptchaProviderResponse, DeletePublicWafRuleRequest, DeletePublicWafRuleResponse, DisablePublicListenerRequest, DisablePublicListenerResponse, DiscoverEnvironmentCertificateRequest, DiscoverEnvironmentCertificateResponse, EnablePublicListenerRequest, EnablePublicListenerResponse, GetCurrentUserRequest, GetCurrentUserResponse, GetDashboardDiagnosticsRequest, GetDashboardDiagnosticsResponse, GetDashboardRequest, GetDashboardResponse, GetPublicProxyConfigRequest, GetPublicProxyConfigResponse, GetSetupStateRequest, GetSetupStateResponse, GetStatusRequest, GetStatusResponse, GetTrafficTraceSettingsRequest, GetTrafficTraceSettingsResponse, ListEnvironmentsRequest, ListEnvironmentsResponse, ListManagementAccessTokensRequest, ListManagementAccessTokensResponse, ListPublicRouteTargetHealthTracesRequest, ListPublicRouteTargetHealthTracesResponse, LoginRequest, LoginResponse, LogoutRequest, LogoutResponse, PurgePublicCacheRequest, PurgePublicCacheResponse, RenewPublicTlsCertificateRequest, RenewPublicTlsCertificateResponse, RotateAgentTokenRequest, RotateAgentTokenResponse, SetTrafficTraceSettingsRequest, SetTrafficTraceSettingsResponse, SetupAdminRequest, SetupAdminResponse, StartProxyRequest, StartProxyResponse, StartPublicListenerRequest, StartPublicListenerResponse, StopProxyRequest, StopProxyResponse, StopPublicListenerRequest, StopPublicListenerResponse, StreamTrafficTraceEventsRequest, StreamTrafficTraceEventsResponse, TestEnvironmentRequest, TestEnvironmentResponse, TrustEnvironmentCertificateRequest, TrustEnvironmentCertificateResponse, UpdateAgentRequest, UpdateAgentResponse, UpdateEnvironmentRequest, UpdateEnvironmentResponse, UpdatePublicCacheRuleRequest, UpdatePublicCacheRuleResponse, UpdatePublicCacheSettingsRequest, UpdatePublicCacheSettingsResponse, UpdatePublicListenerRequest, UpdatePublicListenerResponse, UpdatePublicRateLimitRuleRequest, UpdatePublicRateLimitRuleResponse, UpdatePublicResponseTemplateRequest, UpdatePublicResponseTemplateResponse, UpdatePublicRouteRequest, UpdatePublicRouteResponse, UpdatePublicTlsCertificateRequest, UpdatePublicTlsCertificateResponse, UpdatePublicTlsDnsCredentialRequest, UpdatePublicTlsDnsCredentialResponse, UpdatePublicTrafficShaperRuleRequest, UpdatePublicTrafficShaperRuleResponse, UpdatePublicWafCaptchaProviderRequest, UpdatePublicWafCaptchaProviderResponse, UpdatePublicWafRuleRequest, UpdatePublicWafRuleResponse } from "./management_pb"; import { MethodKind } from "@bufbuild/protobuf"; /** @@ -39,6 +39,15 @@ export const AgentManagementService = { O: GetDashboardResponse, kind: MethodKind.Unary, }, + /** + * @generated from rpc p2pstream.v1.AgentManagementService.GetDashboardDiagnostics + */ + getDashboardDiagnostics: { + name: "GetDashboardDiagnostics", + I: GetDashboardDiagnosticsRequest, + O: GetDashboardDiagnosticsResponse, + kind: MethodKind.Unary, + }, /** * @generated from rpc p2pstream.v1.AgentManagementService.GetTrafficTraceSettings */ diff --git a/web/management/src/gen/proto/p2pstream/v1/management_pb.ts b/web/management/src/gen/proto/p2pstream/v1/management_pb.ts index ebfcbbc..b2a39ee 100644 --- a/web/management/src/gen/proto/p2pstream/v1/management_pb.ts +++ b/web/management/src/gen/proto/p2pstream/v1/management_pb.ts @@ -10,7 +10,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file proto/p2pstream/v1/management.proto. */ export const file_proto_p2pstream_v1_management: GenFile = /*@__PURE__*/ - fileDesc("CiNwcm90by9wMnBzdHJlYW0vdjEvbWFuYWdlbWVudC5wcm90bxIMcDJwc3RyZWFtLnYxIpkCChFBZ2VudFN0YXRzUmVxdWVzdBIVCg1tZW1vcnlfc3lzX21iGAEgASgDEhUKDW51bV9nb3JvdXRpbmUYAiABKAMSEwoLcmVxX3N1Y2Nlc3MYAyABKAMSGAoQcmVxX2NsaWVudF9lcnJvchgEIAEoAxIYChByZXFfc2VydmVyX2Vycm9yGAUgASgDEhoKEnJlcV9pbnRlcm5hbF9lcnJvchgGIAEoAxIWCg5ieXRlc19yZWNlaXZlZBgHIAEoBBISCgpieXRlc19zZW50GAggASgEEhcKD2FjdGl2ZV9yZXF1ZXN0cxgJIAEoBRIXCg9hZ2VudF9wdWJsaWNfaWQYCiABKAkSEwoLY3B1X3BlcmNlbnQYCyABKAEiFAoSQWdlbnRTdGF0c1Jlc3BvbnNlIkoKBFVzZXISCgoCaWQYASABKAMSEAoIdXNlcm5hbWUYAiABKAkSJAoEcm9sZRgDIAEoDjIWLnAycHN0cmVhbS52MS5Vc2VyUm9sZSISChBHZXRTdGF0dXNSZXF1ZXN0IqICChJBZ2VudFN0YXRzU25hcHNob3QSFQoNbWVtb3J5X3N5c19tYhgBIAEoAxIVCg1udW1fZ29yb3V0aW5lGAIgASgDEhMKC3JlcV9zdWNjZXNzGAMgASgDEhgKEHJlcV9jbGllbnRfZXJyb3IYBCABKAMSGAoQcmVxX3NlcnZlcl9lcnJvchgFIAEoAxIaChJyZXFfaW50ZXJuYWxfZXJyb3IYBiABKAMSFgoOYnl0ZXNfcmVjZWl2ZWQYByABKAQSEgoKYnl0ZXNfc2VudBgIIAEoBBIXCg9hY3RpdmVfcmVxdWVzdHMYCSABKAUSHwoXcmVwb3J0ZWRfYXRfdW5peF9taWxsaXMYCiABKAMSEwoLY3B1X3BlcmNlbnQYCyABKAEi2gEKEUdldFN0YXR1c1Jlc3BvbnNlEhUKDXByb3h5X3J1bm5pbmcYASABKAgSGAoQcHJveHlfbGFzdF9lcnJvchgCIAEoCRIXCg9hZ2VudF9jb25uZWN0ZWQYAyABKAgSPAoSbGF0ZXN0X2FnZW50X3N0YXRzGAUgASgLMiAucDJwc3RyZWFtLnYxLkFnZW50U3RhdHNTbmFwc2hvdBIoCgVwcm94eRgGIAEoCzIZLnAycHN0cmVhbS52MS5Qcm94eVN0YXR1c0oECAQQBVINdGFyZ2V0X29yaWdpbiLBAQoLUHJveHlTdGF0dXMSJwoFc3RhdGUYASABKA4yGC5wMnBzdHJlYW0udjEuUHJveHlTdGF0ZRISCgpsYXN0X2Vycm9yGAIgASgJEh4KFnN0YXJ0ZWRfYXRfdW5peF9taWxsaXMYAyABKAMSHgoWc3RvcHBlZF9hdF91bml4X21pbGxpcxgEIAEoAxI1CglsaXN0ZW5lcnMYBSADKAsyIi5wMnBzdHJlYW0udjEuUHVibGljTGlzdGVuZXJTdGF0dXMiKwoMUHVibGljSGVhZGVyEgwKBG5hbWUYASABKAkSDQoFdmFsdWUYAiABKAkilQEKH1B1YmxpY1JvdXRlVGFyZ2V0VXBzdHJlYW1IZWFkZXISCgoCaWQYASABKAMSEQoJdGFyZ2V0X2lkGAIgASgDEgwKBG5hbWUYAyABKAkSDQoFdmFsdWUYBCABKAkSEQoJc2Vuc2l0aXZlGAUgASgIEhEKCXZhbHVlX3NldBgGIAEoCBIQCghwb3NpdGlvbhgHIAEoAyJnChpQdWJsaWNSb3V0ZVRhcmdldEJhc2ljQXV0aBIPCgdlbmFibGVkGAEgASgIEhAKCHVzZXJuYW1lGAIgASgJEhAKCHBhc3N3b3JkGAMgASgJEhQKDHBhc3N3b3JkX3NldBgEIAEoCCKTAwocUHVibGljUm91dGVUYXJnZXRIZWFsdGhDaGVjaxIPCgdlbmFibGVkGAEgASgIEg4KBm1ldGhvZBgCIAEoCRIMCgRwYXRoGAMgASgJEhcKD2ludGVydmFsX21pbGxpcxgEIAEoAxIWCg50aW1lb3V0X21pbGxpcxgFIAEoAxIZChFoZWFsdGh5X3RocmVzaG9sZBgGIAEoAxIbChN1bmhlYWx0aHlfdGhyZXNob2xkGAcgASgDEhsKE2V4cGVjdGVkX3N0YXR1c19taW4YCCABKAMSGwoTZXhwZWN0ZWRfc3RhdHVzX21heBgJIAEoAxI7CgZzdGF0dXMYCiABKA4yKy5wMnBzdHJlYW0udjEuUHVibGljUm91dGVUYXJnZXRIZWFsdGhTdGF0dXMSIwobbGFzdF9jaGVja2VkX2F0X3VuaXhfbWlsbGlzGAsgASgDEhIKCmxhc3RfZXJyb3IYDCABKAkSKwojcGFzc2l2ZV91bmhlYWx0aHlfdW50aWxfdW5peF9taWxsaXMYDSABKAMigAIKHFB1YmxpY1JvdXRlVGFyZ2V0QWdlbnRIZWFsdGgSOwoGc3RhdHVzGAEgASgOMisucDJwc3RyZWFtLnYxLlB1YmxpY1JvdXRlVGFyZ2V0SGVhbHRoU3RhdHVzEhEKCWNvbm5lY3RlZBgCIAEoCBIRCglhdmFpbGFibGUYAyABKAgSIwobbGFzdF9jaGVja2VkX2F0X3VuaXhfbWlsbGlzGAQgASgDEhIKCmxhc3RfZXJyb3IYBSABKAkSKwojcGFzc2l2ZV91bmhlYWx0aHlfdW50aWxfdW5peF9taWxsaXMYBiABKAMSFwoPYWN0aXZlX3JlcXVlc3RzGAcgASgDIpoDCgVBZ2VudBIKCgJpZBgBIAEoAxIRCglwdWJsaWNfaWQYAiABKAkSDAoEbmFtZRgDIAEoCRIPCgdlbmFibGVkGAQgASgIEhEKCWNvbm5lY3RlZBgFIAEoCBIXCg9hY3RpdmVfcmVxdWVzdHMYBiABKAMSHgoWY3JlYXRlZF9hdF91bml4X21pbGxpcxgHIAEoAxIeChZ1cGRhdGVkX2F0X3VuaXhfbWlsbGlzGAggASgDEiUKHWxhc3RfY29ubmVjdGVkX2F0X3VuaXhfbWlsbGlzGAkgASgDEigKIGxhc3RfZGlzY29ubmVjdGVkX2F0X3VuaXhfbWlsbGlzGAogASgDEjYKDGxhdGVzdF9zdGF0cxgLIAEoCzIgLnAycHN0cmVhbS52MS5BZ2VudFN0YXRzU25hcHNob3QSLwoGbGFiZWxzGAwgAygLMh8ucDJwc3RyZWFtLnYxLkFnZW50LkxhYmVsc0VudHJ5Gi0KC0xhYmVsc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEikwEKE1B1YmxpY0FnZW50U2VsZWN0b3ISSAoMbWF0Y2hfbGFiZWxzGAEgAygLMjIucDJwc3RyZWFtLnYxLlB1YmxpY0FnZW50U2VsZWN0b3IuTWF0Y2hMYWJlbHNFbnRyeRoyChBNYXRjaExhYmVsc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEijQIKF1B1YmxpY1JvdXRlVGFyZ2V0SGVhbHRoEjsKBnN0YXR1cxgBIAEoDjIrLnAycHN0cmVhbS52MS5QdWJsaWNSb3V0ZVRhcmdldEhlYWx0aFN0YXR1cxIRCglhdmFpbGFibGUYAiABKAgSEQoJY29ubmVjdGVkGAMgASgIEhAKCGFnZW50X2lkGAQgASgDEiMKG2xhc3RfY2hlY2tlZF9hdF91bml4X21pbGxpcxgFIAEoAxISCgpsYXN0X2Vycm9yGAYgASgJEisKI3Bhc3NpdmVfdW5oZWFsdGh5X3VudGlsX3VuaXhfbWlsbGlzGAcgASgDEhcKD2FjdGl2ZV9yZXF1ZXN0cxgIIAEoAyLVBwoRUHVibGljUm91dGVUYXJnZXQSCgoCaWQYASABKAMSEAoIcm91dGVfaWQYAiABKAMSDAoEbmFtZRgDIAEoCRIQCghwb3NpdGlvbhgEIAEoAxIWCg5wcmlvcml0eV9ncm91cBgFIAEoAxIOCgZ3ZWlnaHQYBiABKAMSDwoHZW5hYmxlZBgHIAEoCBI4Cgt0YXJnZXRfdHlwZRgIIAEoDjIjLnAycHN0cmVhbS52MS5QdWJsaWNSb3V0ZVRhcmdldFR5cGUSCwoDdXJsGAkgASgJEjsKCXRyYW5zcG9ydBgKIAEoDjIoLnAycHN0cmVhbS52MS5QdWJsaWNSb3V0ZVRhcmdldFRyYW5zcG9ydBI5Cg5hZ2VudF9zZWxlY3RvchgLIAEoCzIhLnAycHN0cmVhbS52MS5QdWJsaWNBZ2VudFNlbGVjdG9yEkoKFGFnZW50X2xvYWRfYmFsYW5jaW5nGAwgASgOMiwucDJwc3RyZWFtLnYxLlB1YmxpY1JvdXRlVGFyZ2V0TG9hZEJhbGFuY2luZxIXCg90bHNfc2tpcF92ZXJpZnkYDSABKAgSLwondXBzdHJlYW1fcmVzcG9uc2VfaGVhZGVyX3RpbWVvdXRfbWlsbGlzGA4gASgDEk8KGHVwc3RyZWFtX3JlcXVlc3RfaGVhZGVycxgPIAMoCzItLnAycHN0cmVhbS52MS5QdWJsaWNSb3V0ZVRhcmdldFVwc3RyZWFtSGVhZGVyEkUKE3Vwc3RyZWFtX2Jhc2ljX2F1dGgYECABKAsyKC5wMnBzdHJlYW0udjEuUHVibGljUm91dGVUYXJnZXRCYXNpY0F1dGgSQAoMaGVhbHRoX2NoZWNrGBEgASgLMioucDJwc3RyZWFtLnYxLlB1YmxpY1JvdXRlVGFyZ2V0SGVhbHRoQ2hlY2sSGgoSc3RhdGljX3N0YXR1c19jb2RlGBIgASgDEjsKF3N0YXRpY19yZXNwb25zZV9oZWFkZXJzGBMgAygLMhoucDJwc3RyZWFtLnYxLlB1YmxpY0hlYWRlchIcChRzdGF0aWNfcmVzcG9uc2VfYm9keRgUIAEoCRJHChlzdGF0aWNfcmVzcG9uc2VfYm9keV9tb2RlGBUgASgOMiQucDJwc3RyZWFtLnYxLlB1YmxpY1Jlc3BvbnNlQm9keU1vZGUSIwobc3RhdGljX3Jlc3BvbnNlX3RlbXBsYXRlX2lkGBYgASgDEjUKBmhlYWx0aBgXIAEoCzIlLnAycHN0cmVhbS52MS5QdWJsaWNSb3V0ZVRhcmdldEhlYWx0aCLxAQoOUHVibGljTGlzdGVuZXISCgoCaWQYASABKAMSDAoEbmFtZRgCIAEoCRIUCgxiaW5kX2FkZHJlc3MYAyABKAkSDAoEcG9ydBgEIAEoAxI2Cghwcm90b2NvbBgFIAEoDjIkLnAycHN0cmVhbS52MS5QdWJsaWNMaXN0ZW5lclByb3RvY29sEg8KB2VuYWJsZWQYBiABKAgSHgoWY3JlYXRlZF9hdF91bml4X21pbGxpcxgIIAEoAxIeChZ1cGRhdGVkX2F0X3VuaXhfbWlsbGlzGAkgASgDSgQIBxAIUhJkZWZhdWx0X2JhY2tlbmRfaWQiqAUKC1B1YmxpY1JvdXRlEgoKAmlkGAEgASgDEhMKC2xpc3RlbmVyX2lkGAIgASgDEhAKCHByaW9yaXR5GAMgASgDEhQKDGhvc3RfcGF0dGVybhgEIAEoCRITCgtwYXRoX3ByZWZpeBgFIAEoCRIPCgdlbmFibGVkGAcgASgIEh4KFmNyZWF0ZWRfYXRfdW5peF9taWxsaXMYCCABKAMSHgoWdXBkYXRlZF9hdF91bml4X21pbGxpcxgJIAEoAxIvCgZhY3Rpb24YCiABKA4yHy5wMnBzdHJlYW0udjEuUHVibGljUm91dGVBY3Rpb24SSQoUcmVkaXJlY3RfdGFyZ2V0X21vZGUYCyABKA4yKy5wMnBzdHJlYW0udjEuUHVibGljUm91dGVSZWRpcmVjdFRhcmdldE1vZGUSFwoPcmVkaXJlY3RfdGFyZ2V0GAwgASgJEhwKFHJlZGlyZWN0X3N0YXR1c19jb2RlGA0gASgDEiUKHXJlZGlyZWN0X3ByZXNlcnZlX3BhdGhfc3VmZml4GA4gASgIEh8KF3JlZGlyZWN0X3ByZXNlcnZlX3F1ZXJ5GA8gASgIEksKFXRhcmdldF9sb2FkX2JhbGFuY2luZxgTIAEoDjIsLnAycHN0cmVhbS52MS5QdWJsaWNSb3V0ZVRhcmdldExvYWRCYWxhbmNpbmcSEgoKaXNfZGVmYXVsdBgUIAEoCBIwCgd0YXJnZXRzGBUgAygLMh8ucDJwc3RyZWFtLnYxLlB1YmxpY1JvdXRlVGFyZ2V0SgQIBhAHSgQIEBARSgQIERASSgQIEhATUgpiYWNrZW5kX2lkUg5sb2FkX2JhbGFuY2luZ1ITYmFja2VuZF9hc3NpZ25tZW50c1ITZmFsbGJhY2tfYmFja2VuZF9pZCKABQoUUHVibGljVGxzQ2VydGlmaWNhdGUSCgoCaWQYASABKAMSEwoLbGlzdGVuZXJfaWQYAiABKAMSGAoQaG9zdG5hbWVfcGF0dGVybhgDIAEoCRIRCgljZXJ0X3BhdGgYBCABKAkSEAoIa2V5X3BhdGgYBSABKAkSDwoHZW5hYmxlZBgGIAEoCBIeChZjcmVhdGVkX2F0X3VuaXhfbWlsbGlzGAcgASgDEh4KFnVwZGF0ZWRfYXRfdW5peF9taWxsaXMYCCABKAMSOAoGc291cmNlGAkgASgOMigucDJwc3RyZWFtLnYxLlB1YmxpY1Rsc0NlcnRpZmljYXRlU291cmNlEkIKE2FjbWVfY2hhbGxlbmdlX3R5cGUYCiABKA4yJS5wMnBzdHJlYW0udjEuUHVibGljQWNtZUNoYWxsZW5nZVR5cGUSKwoHYWNtZV9jYRgLIAEoDjIaLnAycHN0cmVhbS52MS5QdWJsaWNBY21lQ2ESEgoKYWNtZV9lbWFpbBgMIAEoCRIZChFkbnNfY3JlZGVudGlhbF9pZBgNIAEoAxI4CgZzdGF0dXMYDiABKA4yKC5wMnBzdHJlYW0udjEuUHVibGljVGxzQ2VydGlmaWNhdGVTdGF0dXMSEgoKbGFzdF9lcnJvchgPIAEoCRIdChVpc3N1ZWRfYXRfdW5peF9taWxsaXMYECABKAMSHgoWZXhwaXJlc19hdF91bml4X21pbGxpcxgRIAEoAxIjChtuZXh0X3JlbmV3YWxfYXRfdW5peF9taWxsaXMYEiABKAMSKwojbGFzdF9yZW5ld2FsX2F0dGVtcHRfYXRfdW5peF9taWxsaXMYEyABKAMi6QEKFlB1YmxpY1Rsc0Ruc0NyZWRlbnRpYWwSCgoCaWQYASABKAMSDAoEbmFtZRgCIAEoCRIxCghwcm92aWRlchgDIAEoDjIfLnAycHN0cmVhbS52MS5QdWJsaWNEbnNQcm92aWRlchIaChJjbG91ZGZsYXJlX3pvbmVfaWQYBCABKAkSFQoNYXBpX3Rva2VuX3NldBgFIAEoCBIPCgdlbmFibGVkGAYgASgIEh4KFmNyZWF0ZWRfYXRfdW5peF9taWxsaXMYByABKAMSHgoWdXBkYXRlZF9hdF91bml4X21pbGxpcxgIIAEoAyJeChZQdWJsaWNSYXRlTGltaXRLZXlQYXJ0EjYKBnNvdXJjZRgBIAEoDjImLnAycHN0cmVhbS52MS5QdWJsaWNSYXRlTGltaXRLZXlTb3VyY2USDAoEbmFtZRgCIAEoCSJoChVQdWJsaWNQb2xpY3lNYXRjaFJ1bGUSFgoOY2VsX2V4cHJlc3Npb24YASABKAkSNwoHYnVpbGRlchgCIAEoCzImLnAycHN0cmVhbS52MS5QdWJsaWNQb2xpY3lNYXRjaEJ1aWxkZXIiTgoYUHVibGljUG9saWN5TWF0Y2hCdWlsZGVyEjIKBHJvb3QYASABKAsyJC5wMnBzdHJlYW0udjEuUHVibGljUG9saWN5TWF0Y2hHcm91cCLfAQoWUHVibGljUG9saWN5TWF0Y2hHcm91cBJACghvcGVyYXRvchgBIAEoDjIuLnAycHN0cmVhbS52MS5QdWJsaWNQb2xpY3lNYXRjaEJvb2xlYW5PcGVyYXRvchI8Cgpjb25kaXRpb25zGAIgAygLMigucDJwc3RyZWFtLnYxLlB1YmxpY1BvbGljeU1hdGNoQ29uZGl0aW9uEjQKBmdyb3VwcxgDIAMoCzIkLnAycHN0cmVhbS52MS5QdWJsaWNQb2xpY3lNYXRjaEdyb3VwEg8KB25lZ2F0ZWQYBCABKAgixAEKGlB1YmxpY1BvbGljeU1hdGNoQ29uZGl0aW9uEjMKBWZpZWxkGAEgASgOMiQucDJwc3RyZWFtLnYxLlB1YmxpY1BvbGljeU1hdGNoRmllbGQSDAoEbmFtZRgCIAEoCRJCCghvcGVyYXRvchgDIAEoDjIwLnAycHN0cmVhbS52MS5QdWJsaWNQb2xpY3lNYXRjaENvbmRpdGlvbk9wZXJhdG9yEg4KBnZhbHVlcxgEIAMoCRIPCgduZWdhdGVkGAUgASgIIjwKHVB1YmxpY1JhdGVMaW1pdFJlc3BvbnNlSGVhZGVyEgwKBG5hbWUYASABKAkSDQoFdmFsdWUYAiABKAkigQUKE1B1YmxpY1JhdGVMaW1pdFJ1bGUSCgoCaWQYASABKAMSDAoEbmFtZRgCIAEoCRIQCghwcmlvcml0eRgDIAEoAxIPCgdlbmFibGVkGAQgASgIEjkKCWFsZ29yaXRobRgFIAEoDjImLnAycHN0cmVhbS52MS5QdWJsaWNSYXRlTGltaXRBbGdvcml0aG0SDQoFbGltaXQYBiABKAMSFQoNd2luZG93X21pbGxpcxgHIAEoAxINCgVidXJzdBgIIAEoAxI3CglrZXlfcGFydHMYCiADKAsyJC5wMnBzdHJlYW0udjEuUHVibGljUmF0ZUxpbWl0S2V5UGFydBIcChRyZXNwb25zZV9zdGF0dXNfY29kZRgLIAEoAxIVCg1yZXNwb25zZV9ib2R5GAwgASgJEh0KFXJlc3BvbnNlX2NvbnRlbnRfdHlwZRgNIAEoCRJFChByZXNwb25zZV9oZWFkZXJzGA4gAygLMisucDJwc3RyZWFtLnYxLlB1YmxpY1JhdGVMaW1pdFJlc3BvbnNlSGVhZGVyEh4KFmNyZWF0ZWRfYXRfdW5peF9taWxsaXMYDyABKAMSHgoWdXBkYXRlZF9hdF91bml4X21pbGxpcxgQIAEoAxJAChJyZXNwb25zZV9ib2R5X21vZGUYESABKA4yJC5wMnBzdHJlYW0udjEuUHVibGljUmVzcG9uc2VCb2R5TW9kZRIhChlyZXNwb25zZV9ib2R5X3RlbXBsYXRlX2lkGBIgASgDEjcKCm1hdGNoX3J1bGUYEyABKAsyIy5wMnBzdHJlYW0udjEuUHVibGljUG9saWN5TWF0Y2hSdWxlSgQICRAKUgVtYXRjaCLvAwoXUHVibGljVHJhZmZpY1NoYXBlclJ1bGUSCgoCaWQYASABKAMSDAoEbmFtZRgCIAEoCRIQCghwcmlvcml0eRgDIAEoAxIPCgdlbmFibGVkGAQgASgIEkIKDGJ1ZGdldF9zY29wZRgFIAEoDjIsLnAycHN0cmVhbS52MS5QdWJsaWNUcmFmZmljU2hhcGVyQnVkZ2V0U2NvcGUSHwoXdXBsb2FkX2J5dGVzX3Blcl9zZWNvbmQYBiABKAMSIQoZZG93bmxvYWRfYnl0ZXNfcGVyX3NlY29uZBgHIAEoAxITCgtidXJzdF9ieXRlcxgIIAEoAxIcChRyZXF1ZXN0X2V4ZW1wdF9ieXRlcxgJIAEoAxIdChVyZXNwb25zZV9leGVtcHRfYnl0ZXMYCiABKAMSNwoJa2V5X3BhcnRzGAwgAygLMiQucDJwc3RyZWFtLnYxLlB1YmxpY1JhdGVMaW1pdEtleVBhcnQSHgoWY3JlYXRlZF9hdF91bml4X21pbGxpcxgNIAEoAxIeChZ1cGRhdGVkX2F0X3VuaXhfbWlsbGlzGA4gASgDEjcKCm1hdGNoX3J1bGUYDyABKAsyIy5wMnBzdHJlYW0udjEuUHVibGljUG9saWN5TWF0Y2hSdWxlSgQICxAMUgVtYXRjaCKGAgoYUHVibGljV2FmQ2FwdGNoYVByb3ZpZGVyEgoKAmlkGAEgASgDEgwKBG5hbWUYAiABKAkSQQoNcHJvdmlkZXJfdHlwZRgDIAEoDjIqLnAycHN0cmVhbS52MS5QdWJsaWNXYWZDYXB0Y2hhUHJvdmlkZXJUeXBlEhAKCHNpdGVfa2V5GAQgASgJEhIKCnNlY3JldF9rZXkYBSABKAkSFgoOc2VjcmV0X2tleV9zZXQYBiABKAgSDwoHZW5hYmxlZBgHIAEoCBIeChZjcmVhdGVkX2F0X3VuaXhfbWlsbGlzGAggASgDEh4KFnVwZGF0ZWRfYXRfdW5peF9taWxsaXMYCSABKAMizgIKFlB1YmxpY1dhZlRyaWdnZXJDb25maWcSHQoVcmVxdWVzdF93aW5kb3dfbWlsbGlzGAEgASgDEhwKFG1pbmltdW1fcmVxdWVzdF9yYXRlGAIgASgDEiAKGHRyYWZmaWNfc3Bpa2VfbXVsdGlwbGllchgDIAEoARIdChVwcm94eV9hY3RpdmVfcmVxdWVzdHMYBCABKAMSJAoccm91dGVfdGFyZ2V0X2FjdGl2ZV9yZXF1ZXN0cxgFIAEoAxIdChVhZ2VudF9hY3RpdmVfcmVxdWVzdHMYBiABKAMSGgoSc2VydmVyX2NwdV9wZXJjZW50GAcgASgBEhkKEWFnZW50X2NwdV9wZXJjZW50GAggASgBEh0KFW1pbmltdW1fYWN0aXZlX21pbGxpcxgJIAEoAxIbChNxdWlldF9wZXJpb2RfbWlsbGlzGAogASgDIu0BChpQdWJsaWNXYWZXYWl0aW5nUm9vbUNvbmZpZxIdChVtYXhfYWRtaXR0ZWRfc2Vzc2lvbnMYASABKAMSIQoZYWRtaXNzaW9uX3JhdGVfcGVyX3NlY29uZBgCIAEoAxIkChxhZG1pc3Npb25fc2Vzc2lvbl90dGxfbWlsbGlzGAMgASgDEiIKGnF1ZXVlX3BvbGxfaW50ZXJ2YWxfbWlsbGlzGAQgASgDEhwKFHF1ZXVlX3RpbWVvdXRfbWlsbGlzGAUgASgDEhIKCnBhZ2VfdGl0bGUYBiABKAkSEQoJcGFnZV9ib2R5GAcgASgJIpwHCg1QdWJsaWNXYWZSdWxlEgoKAmlkGAEgASgDEgwKBG5hbWUYAiABKAkSEAoIcHJpb3JpdHkYAyABKAMSDwoHZW5hYmxlZBgEIAEoCBIxCgZhY3Rpb24YBSABKA4yIS5wMnBzdHJlYW0udjEuUHVibGljV2FmUnVsZUFjdGlvbhI+Cg9hY3RpdmF0aW9uX21vZGUYBiABKA4yJS5wMnBzdHJlYW0udjEuUHVibGljV2FmQWN0aXZhdGlvbk1vZGUSNwoJa2V5X3BhcnRzGAggAygLMiQucDJwc3RyZWFtLnYxLlB1YmxpY1JhdGVMaW1pdEtleVBhcnQSGwoTY2FwdGNoYV9wcm92aWRlcl9pZBgJIAEoAxIfChdjYXB0Y2hhX3Bhc3NfdHRsX21pbGxpcxgKIAEoAxI+Cgx3YWl0aW5nX3Jvb20YCyABKAsyKC5wMnBzdHJlYW0udjEuUHVibGljV2FmV2FpdGluZ1Jvb21Db25maWcSNgoIdHJpZ2dlcnMYDCABKAsyJC5wMnBzdHJlYW0udjEuUHVibGljV2FmVHJpZ2dlckNvbmZpZxIiChpibG9ja19yZXNwb25zZV9zdGF0dXNfY29kZRgNIAEoAxIbChNibG9ja19yZXNwb25zZV9ib2R5GA4gASgJEiMKG2Jsb2NrX3Jlc3BvbnNlX2NvbnRlbnRfdHlwZRgPIAEoCRJLChZibG9ja19yZXNwb25zZV9oZWFkZXJzGBAgAygLMisucDJwc3RyZWFtLnYxLlB1YmxpY1JhdGVMaW1pdFJlc3BvbnNlSGVhZGVyEh4KFmNyZWF0ZWRfYXRfdW5peF9taWxsaXMYESABKAMSHgoWdXBkYXRlZF9hdF91bml4X21pbGxpcxgSIAEoAxJGChhibG9ja19yZXNwb25zZV9ib2R5X21vZGUYEyABKA4yJC5wMnBzdHJlYW0udjEuUHVibGljUmVzcG9uc2VCb2R5TW9kZRIiChpibG9ja19yZXNwb25zZV90ZW1wbGF0ZV9pZBgUIAEoAxIgChhjYXB0Y2hhX3BhZ2VfdGVtcGxhdGVfaWQYFSABKAMSJQodd2FpdGluZ19yb29tX3BhZ2VfdGVtcGxhdGVfaWQYFiABKAMSNwoKbWF0Y2hfcnVsZRgXIAEoCzIjLnAycHN0cmVhbS52MS5QdWJsaWNQb2xpY3lNYXRjaFJ1bGVKBAgHEAhSBW1hdGNoIuMBChZQdWJsaWNSZXNwb25zZVRlbXBsYXRlEgoKAmlkGAEgASgDEgwKBG5hbWUYAiABKAkSNgoEa2luZBgDIAEoDjIoLnAycHN0cmVhbS52MS5QdWJsaWNSZXNwb25zZVRlbXBsYXRlS2luZBITCgtkZXNjcmlwdGlvbhgEIAEoCRIUCgxjb250ZW50X3R5cGUYBSABKAkSDAoEYm9keRgGIAEoCRIeChZjcmVhdGVkX2F0X3VuaXhfbWlsbGlzGAcgASgDEh4KFnVwZGF0ZWRfYXRfdW5peF9taWxsaXMYCCABKAMi8wEKE1B1YmxpY0NhY2hlU2V0dGluZ3MSDwoHZW5hYmxlZBgBIAEoCBIWCg5tYXhfZGlza19ieXRlcxgCIAEoAxIYChBtYXhfbWVtb3J5X2J5dGVzGAMgASgDEiMKG21lbW9yeV9ob3Rfb2JqZWN0X21heF9ieXRlcxgEIAEoAxITCgttYXhfZW50cmllcxgFIAEoAxIfChdjbGVhbnVwX2ludGVydmFsX21pbGxpcxgGIAEoAxIeChZjcmVhdGVkX2F0X3VuaXhfbWlsbGlzGAcgASgDEh4KFnVwZGF0ZWRfYXRfdW5peF9taWxsaXMYCCABKAMi3wQKD1B1YmxpY0NhY2hlUnVsZRIKCgJpZBgBIAEoAxIMCgRuYW1lGAIgASgJEhAKCHByaW9yaXR5GAMgASgDEg8KB2VuYWJsZWQYBCABKAgSEQoJcm91dGVfaWRzGAYgAygDEi0KBXNjb3BlGAggASgOMh4ucDJwc3RyZWFtLnYxLlB1YmxpY0NhY2hlU2NvcGUSMgoIdHRsX21vZGUYCSABKA4yIC5wMnBzdHJlYW0udjEuUHVibGljQ2FjaGVUdGxNb2RlEhIKCnR0bF9taWxsaXMYCiABKAMSNgoKcXVlcnlfbW9kZRgLIAEoDjIiLnAycHN0cmVhbS52MS5QdWJsaWNDYWNoZVF1ZXJ5TW9kZRIUCgxxdWVyeV9wYXJhbXMYDCADKAkSFAoMdmFyeV9oZWFkZXJzGA0gAygJEhoKEmNhY2hlX3N0YXR1c19jb2RlcxgOIAMoAxIYChBtYXhfb2JqZWN0X2J5dGVzGA8gASgDEh8KF2FkZF9jYWNoZV9zdGF0dXNfaGVhZGVyGBAgASgIEh4KFmNyZWF0ZWRfYXRfdW5peF9taWxsaXMYESABKAMSHgoWdXBkYXRlZF9hdF91bml4X21pbGxpcxgSIAEoAxIdChVhbGxvd19jb29raWVfcmVxdWVzdHMYEyABKAgSNwoKbWF0Y2hfcnVsZRgUIAEoCzIjLnAycHN0cmVhbS52MS5QdWJsaWNQb2xpY3lNYXRjaFJ1bGUSEgoKdGFyZ2V0X2lkcxgVIAMoA0oECAUQBkoECAcQCFIFbWF0Y2hSC2JhY2tlbmRfaWRzIuIBChRQdWJsaWNMaXN0ZW5lclN0YXR1cxITCgtsaXN0ZW5lcl9pZBgBIAEoAxInCgVzdGF0ZRgCIAEoDjIYLnAycHN0cmVhbS52MS5Qcm94eVN0YXRlEhIKCmxhc3RfZXJyb3IYAyABKAkSHgoWc3RhcnRlZF9hdF91bml4X21pbGxpcxgEIAEoAxIeChZzdG9wcGVkX2F0X3VuaXhfbWlsbGlzGAUgASgDEhUKDWJvdW5kX2FkZHJlc3MYBiABKAkSDwoHcnVubmluZxgHIAEoCBIQCghkaXNhYmxlZBgIIAEoCCIdChtHZXRQdWJsaWNQcm94eUNvbmZpZ1JlcXVlc3Qi6AYKHEdldFB1YmxpY1Byb3h5Q29uZmlnUmVzcG9uc2USLwoJbGlzdGVuZXJzGAIgAygLMhwucDJwc3RyZWFtLnYxLlB1YmxpY0xpc3RlbmVyEikKBnJvdXRlcxgDIAMoCzIZLnAycHN0cmVhbS52MS5QdWJsaWNSb3V0ZRI8ChB0bHNfY2VydGlmaWNhdGVzGAQgAygLMiIucDJwc3RyZWFtLnYxLlB1YmxpY1Rsc0NlcnRpZmljYXRlEigKBXByb3h5GAUgASgLMhkucDJwc3RyZWFtLnYxLlByb3h5U3RhdHVzEiMKBmFnZW50cxgGIAMoCzITLnAycHN0cmVhbS52MS5BZ2VudBI7ChByYXRlX2xpbWl0X3J1bGVzGAggAygLMiEucDJwc3RyZWFtLnYxLlB1YmxpY1JhdGVMaW1pdFJ1bGUSQwoUdHJhZmZpY19zaGFwZXJfcnVsZXMYCSADKAsyJS5wMnBzdHJlYW0udjEuUHVibGljVHJhZmZpY1NoYXBlclJ1bGUSQQoTdGxzX2Ruc19jcmVkZW50aWFscxgKIAMoCzIkLnAycHN0cmVhbS52MS5QdWJsaWNUbHNEbnNDcmVkZW50aWFsEkUKFXdhZl9jYXB0Y2hhX3Byb3ZpZGVycxgMIAMoCzImLnAycHN0cmVhbS52MS5QdWJsaWNXYWZDYXB0Y2hhUHJvdmlkZXISLgoJd2FmX3J1bGVzGA0gAygLMhsucDJwc3RyZWFtLnYxLlB1YmxpY1dhZlJ1bGUSOQoOY2FjaGVfc2V0dGluZ3MYDiABKAsyIS5wMnBzdHJlYW0udjEuUHVibGljQ2FjaGVTZXR0aW5ncxIyCgtjYWNoZV9ydWxlcxgPIAMoCzIdLnAycHN0cmVhbS52MS5QdWJsaWNDYWNoZVJ1bGUSQAoScmVzcG9uc2VfdGVtcGxhdGVzGBAgAygLMiQucDJwc3RyZWFtLnYxLlB1YmxpY1Jlc3BvbnNlVGVtcGxhdGUSNgoNcm91dGVfdGFyZ2V0cxgRIAMoCzIfLnAycHN0cmVhbS52MS5QdWJsaWNSb3V0ZVRhcmdldEoECAEQAkoECAcQCEoECAsQDFIIYmFja2VuZHNSDmJhY2tlbmRfYWdlbnRzUg5yb3V0ZV9iYWNrZW5kcyL4CAocUHVibGljUm91dGVUYXJnZXRIZWFsdGhUcmFjZRIQCghzZXF1ZW5jZRgBIAEoBBIXCg9yb3V0ZV90YXJnZXRfaWQYAiABKAMSGQoRcm91dGVfdGFyZ2V0X25hbWUYAyABKAkSOwoJdHJhbnNwb3J0GAQgASgOMigucDJwc3RyZWFtLnYxLlB1YmxpY1JvdXRlVGFyZ2V0VHJhbnNwb3J0EkAKBnNvdXJjZRgFIAEoDjIwLnAycHN0cmVhbS52MS5QdWJsaWNSb3V0ZVRhcmdldEhlYWx0aFRyYWNlU291cmNlEkIKB291dGNvbWUYBiABKA4yMS5wMnBzdHJlYW0udjEuUHVibGljUm91dGVUYXJnZXRIZWFsdGhUcmFjZU91dGNvbWUSEAoIYWdlbnRfaWQYByABKAMSFwoPYWdlbnRfcHVibGljX2lkGAggASgJEhIKCmFnZW50X25hbWUYCSABKAkSHgoWc3RhcnRlZF9hdF91bml4X21pbGxpcxgKIAEoAxIfChdmaW5pc2hlZF9hdF91bml4X21pbGxpcxgLIAEoAxIXCg9kdXJhdGlvbl9taWxsaXMYDCABKAMSDgoGbWV0aG9kGA0gASgJEgsKA3VybBgOIAEoCRITCgtzdGF0dXNfY29kZRgPIAEoAxIbChNleHBlY3RlZF9zdGF0dXNfbWluGBAgASgDEhsKE2V4cGVjdGVkX3N0YXR1c19tYXgYESABKAMSFgoOdGltZW91dF9taWxsaXMYEiABKAMSFwoPdGxzX3NraXBfdmVyaWZ5GBMgASgIEkIKDXN0YXR1c19iZWZvcmUYFCABKA4yKy5wMnBzdHJlYW0udjEuUHVibGljUm91dGVUYXJnZXRIZWFsdGhTdGF0dXMSQQoMc3RhdHVzX2FmdGVyGBUgASgOMisucDJwc3RyZWFtLnYxLlB1YmxpY1JvdXRlVGFyZ2V0SGVhbHRoU3RhdHVzEhgKEGF2YWlsYWJsZV9iZWZvcmUYFiABKAgSFwoPYXZhaWxhYmxlX2FmdGVyGBcgASgIEh0KFWhlYWx0aHlfc3RyZWFrX2JlZm9yZRgYIAEoAxIcChRoZWFsdGh5X3N0cmVha19hZnRlchgZIAEoAxIfChd1bmhlYWx0aHlfc3RyZWFrX2JlZm9yZRgaIAEoAxIeChZ1bmhlYWx0aHlfc3RyZWFrX2FmdGVyGBsgASgDEisKI3Bhc3NpdmVfdW5oZWFsdGh5X3VudGlsX3VuaXhfbWlsbGlzGBwgASgDEhIKCmVycm9yX2tpbmQYHSABKAkSDQoFZXJyb3IYHiABKAkSWQoQZGVidWdfYXR0cmlidXRlcxgfIAMoCzI/LnAycHN0cmVhbS52MS5QdWJsaWNSb3V0ZVRhcmdldEhlYWx0aFRyYWNlLkRlYnVnQXR0cmlidXRlc0VudHJ5GjYKFERlYnVnQXR0cmlidXRlc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEiewooTGlzdFB1YmxpY1JvdXRlVGFyZ2V0SGVhbHRoVHJhY2VzUmVxdWVzdBIXCg9yb3V0ZV90YXJnZXRfaWQYASABKAMSEAoIYWdlbnRfaWQYAiABKAMSDQoFbGltaXQYAyABKAMSFQoNZmFpbHVyZXNfb25seRgEIAEoCCKgAQopTGlzdFB1YmxpY1JvdXRlVGFyZ2V0SGVhbHRoVHJhY2VzUmVzcG9uc2USOgoGdHJhY2VzGAEgAygLMioucDJwc3RyZWFtLnYxLlB1YmxpY1JvdXRlVGFyZ2V0SGVhbHRoVHJhY2USFgoOcmV0YWluZWRfY291bnQYAiABKAMSHwoXbWF4X3JldGFpbmVkX3Blcl90YXJnZXQYAyABKAMipAEKI0NyZWF0ZVB1YmxpY1Jlc3BvbnNlVGVtcGxhdGVSZXF1ZXN0EgwKBG5hbWUYASABKAkSNgoEa2luZBgCIAEoDjIoLnAycHN0cmVhbS52MS5QdWJsaWNSZXNwb25zZVRlbXBsYXRlS2luZBITCgtkZXNjcmlwdGlvbhgDIAEoCRIUCgxjb250ZW50X3R5cGUYBCABKAkSDAoEYm9keRgFIAEoCSJeCiRDcmVhdGVQdWJsaWNSZXNwb25zZVRlbXBsYXRlUmVzcG9uc2USNgoIdGVtcGxhdGUYASABKAsyJC5wMnBzdHJlYW0udjEuUHVibGljUmVzcG9uc2VUZW1wbGF0ZSKwAQojVXBkYXRlUHVibGljUmVzcG9uc2VUZW1wbGF0ZVJlcXVlc3QSCgoCaWQYASABKAMSDAoEbmFtZRgCIAEoCRI2CgRraW5kGAMgASgOMigucDJwc3RyZWFtLnYxLlB1YmxpY1Jlc3BvbnNlVGVtcGxhdGVLaW5kEhMKC2Rlc2NyaXB0aW9uGAQgASgJEhQKDGNvbnRlbnRfdHlwZRgFIAEoCRIMCgRib2R5GAYgASgJIl4KJFVwZGF0ZVB1YmxpY1Jlc3BvbnNlVGVtcGxhdGVSZXNwb25zZRI2Cgh0ZW1wbGF0ZRgBIAEoCzIkLnAycHN0cmVhbS52MS5QdWJsaWNSZXNwb25zZVRlbXBsYXRlIjEKI0RlbGV0ZVB1YmxpY1Jlc3BvbnNlVGVtcGxhdGVSZXF1ZXN0EgoKAmlkGAEgASgDIiYKJERlbGV0ZVB1YmxpY1Jlc3BvbnNlVGVtcGxhdGVSZXNwb25zZSKxAQoSQ3JlYXRlQWdlbnRSZXF1ZXN0EgwKBG5hbWUYAiABKAkSDwoHZW5hYmxlZBgDIAEoCBI8CgZsYWJlbHMYBCADKAsyLC5wMnBzdHJlYW0udjEuQ3JlYXRlQWdlbnRSZXF1ZXN0LkxhYmVsc0VudHJ5Gi0KC0xhYmVsc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAFKBAgBEAJSCXB1YmxpY19pZCJIChNDcmVhdGVBZ2VudFJlc3BvbnNlEiIKBWFnZW50GAEgASgLMhMucDJwc3RyZWFtLnYxLkFnZW50Eg0KBXRva2VuGAIgASgJIr0BChJVcGRhdGVBZ2VudFJlcXVlc3QSCgoCaWQYASABKAMSDAoEbmFtZRgDIAEoCRIPCgdlbmFibGVkGAQgASgIEjwKBmxhYmVscxgFIAMoCzIsLnAycHN0cmVhbS52MS5VcGRhdGVBZ2VudFJlcXVlc3QuTGFiZWxzRW50cnkaLQoLTGFiZWxzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4AUoECAIQA1IJcHVibGljX2lkIjkKE1VwZGF0ZUFnZW50UmVzcG9uc2USIgoFYWdlbnQYASABKAsyEy5wMnBzdHJlYW0udjEuQWdlbnQiIAoSRGVsZXRlQWdlbnRSZXF1ZXN0EgoKAmlkGAEgASgDIhUKE0RlbGV0ZUFnZW50UmVzcG9uc2UiJQoXUm90YXRlQWdlbnRUb2tlblJlcXVlc3QSCgoCaWQYASABKAMiTQoYUm90YXRlQWdlbnRUb2tlblJlc3BvbnNlEiIKBWFnZW50GAEgASgLMhMucDJwc3RyZWFtLnYxLkFnZW50Eg0KBXRva2VuGAIgASgJIsQBChVNYW5hZ2VtZW50QWNjZXNzVG9rZW4SCgoCaWQYASABKAMSDAoEbmFtZRgCIAEoCRIPCgdlbmFibGVkGAMgASgIEh4KFmV4cGlyZXNfYXRfdW5peF9taWxsaXMYBCABKAMSIAoYbGFzdF91c2VkX2F0X3VuaXhfbWlsbGlzGAUgASgDEh4KFmNyZWF0ZWRfYXRfdW5peF9taWxsaXMYBiABKAMSHgoWdXBkYXRlZF9hdF91bml4X21pbGxpcxgHIAEoAyJjCiJDcmVhdGVNYW5hZ2VtZW50QWNjZXNzVG9rZW5SZXF1ZXN0EgwKBG5hbWUYASABKAkSDwoHZW5hYmxlZBgCIAEoCBIeChZleHBpcmVzX2F0X3VuaXhfbWlsbGlzGAMgASgDIm8KI0NyZWF0ZU1hbmFnZW1lbnRBY2Nlc3NUb2tlblJlc3BvbnNlEjkKDGFjY2Vzc190b2tlbhgBIAEoCzIjLnAycHN0cmVhbS52MS5NYW5hZ2VtZW50QWNjZXNzVG9rZW4SDQoFdG9rZW4YAiABKAkiIwohTGlzdE1hbmFnZW1lbnRBY2Nlc3NUb2tlbnNSZXF1ZXN0ImAKIkxpc3RNYW5hZ2VtZW50QWNjZXNzVG9rZW5zUmVzcG9uc2USOgoNYWNjZXNzX3Rva2VucxgBIAMoCzIjLnAycHN0cmVhbS52MS5NYW5hZ2VtZW50QWNjZXNzVG9rZW4iMAoiRGVsZXRlTWFuYWdlbWVudEFjY2Vzc1Rva2VuUmVxdWVzdBIKCgJpZBgBIAEoAyIlCiNEZWxldGVNYW5hZ2VtZW50QWNjZXNzVG9rZW5SZXNwb25zZSLKAQoWRW52aXJvbm1lbnRDZXJ0aWZpY2F0ZRILCgNwZW0YASABKAkSGgoSc2hhMjU2X2ZpbmdlcnByaW50GAIgASgJEg8KB3N1YmplY3QYAyABKAkSDgoGaXNzdWVyGAQgASgJEhEKCWRuc19uYW1lcxgFIAMoCRIUCgxpcF9hZGRyZXNzZXMYBiADKAkSHgoWbm90X2JlZm9yZV91bml4X21pbGxpcxgHIAEoAxIdChVub3RfYWZ0ZXJfdW5peF9taWxsaXMYCCABKAMiyQQKC0Vudmlyb25tZW50EgoKAmlkGAEgASgDEgwKBG5hbWUYAiABKAkSFgoObWFuYWdlbWVudF91cmwYAyABKAkSNQoJdHJhbnNwb3J0GAQgASgOMiIucDJwc3RyZWFtLnYxLkVudmlyb25tZW50VHJhbnNwb3J0EhAKCGFnZW50X2lkGAUgASgDEhIKCmFnZW50X25hbWUYBiABKAkSFwoPYWdlbnRfY29ubmVjdGVkGAcgASgIEg8KB2VuYWJsZWQYCCABKAgSHwoXYWNjZXNzX3Rva2VuX2NvbmZpZ3VyZWQYCSABKAgSOAoLdHJ1c3Rfc3RhdGUYCiABKA4yIy5wMnBzdHJlYW0udjEuRW52aXJvbm1lbnRUcnVzdFN0YXRlEkEKE3RydXN0ZWRfY2VydGlmaWNhdGUYCyABKAsyJC5wMnBzdHJlYW0udjEuRW52aXJvbm1lbnRDZXJ0aWZpY2F0ZRJCChRvYnNlcnZlZF9jZXJ0aWZpY2F0ZRgMIAEoCzIkLnAycHN0cmVhbS52MS5FbnZpcm9ubWVudENlcnRpZmljYXRlEhIKCmxhc3RfZXJyb3IYDSABKAkSIwobbGFzdF9jaGVja2VkX2F0X3VuaXhfbWlsbGlzGA4gASgDEh4KFmNyZWF0ZWRfYXRfdW5peF9taWxsaXMYDyABKAMSHgoWdXBkYXRlZF9hdF91bml4X21pbGxpcxgQIAEoAxImCh5yZXNwb25zZV9oZWFkZXJfdGltZW91dF9taWxsaXMYESABKAMiGQoXTGlzdEVudmlyb25tZW50c1JlcXVlc3QiSwoYTGlzdEVudmlyb25tZW50c1Jlc3BvbnNlEi8KDGVudmlyb25tZW50cxgBIAMoCzIZLnAycHN0cmVhbS52MS5FbnZpcm9ubWVudCLYAQoYQ3JlYXRlRW52aXJvbm1lbnRSZXF1ZXN0EgwKBG5hbWUYASABKAkSFgoObWFuYWdlbWVudF91cmwYAiABKAkSNQoJdHJhbnNwb3J0GAMgASgOMiIucDJwc3RyZWFtLnYxLkVudmlyb25tZW50VHJhbnNwb3J0EhAKCGFnZW50X2lkGAQgASgDEhQKDGFjY2Vzc190b2tlbhgFIAEoCRImCh5yZXNwb25zZV9oZWFkZXJfdGltZW91dF9taWxsaXMYBiABKAMSDwoHZW5hYmxlZBgHIAEoCCJLChlDcmVhdGVFbnZpcm9ubWVudFJlc3BvbnNlEi4KC2Vudmlyb25tZW50GAEgASgLMhkucDJwc3RyZWFtLnYxLkVudmlyb25tZW50IuQBChhVcGRhdGVFbnZpcm9ubWVudFJlcXVlc3QSCgoCaWQYASABKAMSDAoEbmFtZRgCIAEoCRIWCg5tYW5hZ2VtZW50X3VybBgDIAEoCRI1Cgl0cmFuc3BvcnQYBCABKA4yIi5wMnBzdHJlYW0udjEuRW52aXJvbm1lbnRUcmFuc3BvcnQSEAoIYWdlbnRfaWQYBSABKAMSFAoMYWNjZXNzX3Rva2VuGAYgASgJEiYKHnJlc3BvbnNlX2hlYWRlcl90aW1lb3V0X21pbGxpcxgHIAEoAxIPCgdlbmFibGVkGAggASgIIksKGVVwZGF0ZUVudmlyb25tZW50UmVzcG9uc2USLgoLZW52aXJvbm1lbnQYASABKAsyGS5wMnBzdHJlYW0udjEuRW52aXJvbm1lbnQiJgoYRGVsZXRlRW52aXJvbm1lbnRSZXF1ZXN0EgoKAmlkGAEgASgDIhsKGURlbGV0ZUVudmlyb25tZW50UmVzcG9uc2UiMwolRGlzY292ZXJFbnZpcm9ubWVudENlcnRpZmljYXRlUmVxdWVzdBIKCgJpZBgBIAEoAyKTAQomRGlzY292ZXJFbnZpcm9ubWVudENlcnRpZmljYXRlUmVzcG9uc2USLgoLZW52aXJvbm1lbnQYASABKAsyGS5wMnBzdHJlYW0udjEuRW52aXJvbm1lbnQSOQoLY2VydGlmaWNhdGUYAiABKAsyJC5wMnBzdHJlYW0udjEuRW52aXJvbm1lbnRDZXJ0aWZpY2F0ZSJMCiJUcnVzdEVudmlyb25tZW50Q2VydGlmaWNhdGVSZXF1ZXN0EgoKAmlkGAEgASgDEhoKEnNoYTI1Nl9maW5nZXJwcmludBgCIAEoCSJVCiNUcnVzdEVudmlyb25tZW50Q2VydGlmaWNhdGVSZXNwb25zZRIuCgtlbnZpcm9ubWVudBgBIAEoCzIZLnAycHN0cmVhbS52MS5FbnZpcm9ubWVudCIkChZUZXN0RW52aXJvbm1lbnRSZXF1ZXN0EgoKAmlkGAEgASgDInoKF1Rlc3RFbnZpcm9ubWVudFJlc3BvbnNlEi4KC2Vudmlyb25tZW50GAEgASgLMhkucDJwc3RyZWFtLnYxLkVudmlyb25tZW50Ei8KBnN0YXR1cxgCIAEoCzIfLnAycHN0cmVhbS52MS5HZXRTdGF0dXNSZXNwb25zZSKyAQobQ3JlYXRlUHVibGljTGlzdGVuZXJSZXF1ZXN0EgwKBG5hbWUYASABKAkSFAoMYmluZF9hZGRyZXNzGAIgASgJEgwKBHBvcnQYAyABKAMSNgoIcHJvdG9jb2wYBCABKA4yJC5wMnBzdHJlYW0udjEuUHVibGljTGlzdGVuZXJQcm90b2NvbBIPCgdlbmFibGVkGAUgASgISgQIBhAHUhJkZWZhdWx0X2JhY2tlbmRfaWQirAEKHENyZWF0ZVB1YmxpY0xpc3RlbmVyUmVzcG9uc2USLgoIbGlzdGVuZXIYASABKAsyHC5wMnBzdHJlYW0udjEuUHVibGljTGlzdGVuZXISMgoGc3RhdHVzGAIgASgLMiIucDJwc3RyZWFtLnYxLlB1YmxpY0xpc3RlbmVyU3RhdHVzEigKBXByb3h5GAMgASgLMhkucDJwc3RyZWFtLnYxLlByb3h5U3RhdHVzIr4BChtVcGRhdGVQdWJsaWNMaXN0ZW5lclJlcXVlc3QSCgoCaWQYASABKAMSDAoEbmFtZRgCIAEoCRIUCgxiaW5kX2FkZHJlc3MYAyABKAkSDAoEcG9ydBgEIAEoAxI2Cghwcm90b2NvbBgFIAEoDjIkLnAycHN0cmVhbS52MS5QdWJsaWNMaXN0ZW5lclByb3RvY29sEg8KB2VuYWJsZWQYBiABKAhKBAgHEAhSEmRlZmF1bHRfYmFja2VuZF9pZCKsAQocVXBkYXRlUHVibGljTGlzdGVuZXJSZXNwb25zZRIuCghsaXN0ZW5lchgBIAEoCzIcLnAycHN0cmVhbS52MS5QdWJsaWNMaXN0ZW5lchIyCgZzdGF0dXMYAiABKAsyIi5wMnBzdHJlYW0udjEuUHVibGljTGlzdGVuZXJTdGF0dXMSKAoFcHJveHkYAyABKAsyGS5wMnBzdHJlYW0udjEuUHJveHlTdGF0dXMiKQobRGVsZXRlUHVibGljTGlzdGVuZXJSZXF1ZXN0EgoKAmlkGAEgASgDIh4KHERlbGV0ZVB1YmxpY0xpc3RlbmVyUmVzcG9uc2UiKQobRW5hYmxlUHVibGljTGlzdGVuZXJSZXF1ZXN0EgoKAmlkGAEgASgDIqwBChxFbmFibGVQdWJsaWNMaXN0ZW5lclJlc3BvbnNlEi4KCGxpc3RlbmVyGAEgASgLMhwucDJwc3RyZWFtLnYxLlB1YmxpY0xpc3RlbmVyEjIKBnN0YXR1cxgCIAEoCzIiLnAycHN0cmVhbS52MS5QdWJsaWNMaXN0ZW5lclN0YXR1cxIoCgVwcm94eRgDIAEoCzIZLnAycHN0cmVhbS52MS5Qcm94eVN0YXR1cyIqChxEaXNhYmxlUHVibGljTGlzdGVuZXJSZXF1ZXN0EgoKAmlkGAEgASgDIq0BCh1EaXNhYmxlUHVibGljTGlzdGVuZXJSZXNwb25zZRIuCghsaXN0ZW5lchgBIAEoCzIcLnAycHN0cmVhbS52MS5QdWJsaWNMaXN0ZW5lchIyCgZzdGF0dXMYAiABKAsyIi5wMnBzdHJlYW0udjEuUHVibGljTGlzdGVuZXJTdGF0dXMSKAoFcHJveHkYAyABKAsyGS5wMnBzdHJlYW0udjEuUHJveHlTdGF0dXMiKAoaU3RhcnRQdWJsaWNMaXN0ZW5lclJlcXVlc3QSCgoCaWQYASABKAMiewobU3RhcnRQdWJsaWNMaXN0ZW5lclJlc3BvbnNlEjIKBnN0YXR1cxgBIAEoCzIiLnAycHN0cmVhbS52MS5QdWJsaWNMaXN0ZW5lclN0YXR1cxIoCgVwcm94eRgCIAEoCzIZLnAycHN0cmVhbS52MS5Qcm94eVN0YXR1cyInChlTdG9wUHVibGljTGlzdGVuZXJSZXF1ZXN0EgoKAmlkGAEgASgDInoKGlN0b3BQdWJsaWNMaXN0ZW5lclJlc3BvbnNlEjIKBnN0YXR1cxgBIAEoCzIiLnAycHN0cmVhbS52MS5QdWJsaWNMaXN0ZW5lclN0YXR1cxIoCgVwcm94eRgCIAEoCzIZLnAycHN0cmVhbS52MS5Qcm94eVN0YXR1cyLpBAoYQ3JlYXRlUHVibGljUm91dGVSZXF1ZXN0EhMKC2xpc3RlbmVyX2lkGAEgASgDEhAKCHByaW9yaXR5GAIgASgDEhQKDGhvc3RfcGF0dGVybhgDIAEoCRITCgtwYXRoX3ByZWZpeBgEIAEoCRIPCgdlbmFibGVkGAYgASgIEi8KBmFjdGlvbhgKIAEoDjIfLnAycHN0cmVhbS52MS5QdWJsaWNSb3V0ZUFjdGlvbhJJChRyZWRpcmVjdF90YXJnZXRfbW9kZRgLIAEoDjIrLnAycHN0cmVhbS52MS5QdWJsaWNSb3V0ZVJlZGlyZWN0VGFyZ2V0TW9kZRIXCg9yZWRpcmVjdF90YXJnZXQYDCABKAkSHAoUcmVkaXJlY3Rfc3RhdHVzX2NvZGUYDSABKAMSJQodcmVkaXJlY3RfcHJlc2VydmVfcGF0aF9zdWZmaXgYDiABKAgSHwoXcmVkaXJlY3RfcHJlc2VydmVfcXVlcnkYDyABKAgSSwoVdGFyZ2V0X2xvYWRfYmFsYW5jaW5nGBMgASgOMiwucDJwc3RyZWFtLnYxLlB1YmxpY1JvdXRlVGFyZ2V0TG9hZEJhbGFuY2luZxISCgppc19kZWZhdWx0GBQgASgIEjAKB3RhcmdldHMYFSADKAsyHy5wMnBzdHJlYW0udjEuUHVibGljUm91dGVUYXJnZXRKBAgFEAZKBAgQEBFKBAgREBJKBAgSEBNSCmJhY2tlbmRfaWRSDmxvYWRfYmFsYW5jaW5nUhNiYWNrZW5kX2Fzc2lnbm1lbnRzUhNmYWxsYmFja19iYWNrZW5kX2lkIkUKGUNyZWF0ZVB1YmxpY1JvdXRlUmVzcG9uc2USKAoFcm91dGUYASABKAsyGS5wMnBzdHJlYW0udjEuUHVibGljUm91dGUi9QQKGFVwZGF0ZVB1YmxpY1JvdXRlUmVxdWVzdBIKCgJpZBgBIAEoAxITCgtsaXN0ZW5lcl9pZBgCIAEoAxIQCghwcmlvcml0eRgDIAEoAxIUCgxob3N0X3BhdHRlcm4YBCABKAkSEwoLcGF0aF9wcmVmaXgYBSABKAkSDwoHZW5hYmxlZBgHIAEoCBIvCgZhY3Rpb24YCiABKA4yHy5wMnBzdHJlYW0udjEuUHVibGljUm91dGVBY3Rpb24SSQoUcmVkaXJlY3RfdGFyZ2V0X21vZGUYCyABKA4yKy5wMnBzdHJlYW0udjEuUHVibGljUm91dGVSZWRpcmVjdFRhcmdldE1vZGUSFwoPcmVkaXJlY3RfdGFyZ2V0GAwgASgJEhwKFHJlZGlyZWN0X3N0YXR1c19jb2RlGA0gASgDEiUKHXJlZGlyZWN0X3ByZXNlcnZlX3BhdGhfc3VmZml4GA4gASgIEh8KF3JlZGlyZWN0X3ByZXNlcnZlX3F1ZXJ5GA8gASgIEksKFXRhcmdldF9sb2FkX2JhbGFuY2luZxgTIAEoDjIsLnAycHN0cmVhbS52MS5QdWJsaWNSb3V0ZVRhcmdldExvYWRCYWxhbmNpbmcSEgoKaXNfZGVmYXVsdBgUIAEoCBIwCgd0YXJnZXRzGBUgAygLMh8ucDJwc3RyZWFtLnYxLlB1YmxpY1JvdXRlVGFyZ2V0SgQIBhAHSgQIEBARSgQIERASSgQIEhATUgpiYWNrZW5kX2lkUg5sb2FkX2JhbGFuY2luZ1ITYmFja2VuZF9hc3NpZ25tZW50c1ITZmFsbGJhY2tfYmFja2VuZF9pZCJFChlVcGRhdGVQdWJsaWNSb3V0ZVJlc3BvbnNlEigKBXJvdXRlGAEgASgLMhkucDJwc3RyZWFtLnYxLlB1YmxpY1JvdXRlIiYKGERlbGV0ZVB1YmxpY1JvdXRlUmVxdWVzdBIKCgJpZBgBIAEoAyIbChlEZWxldGVQdWJsaWNSb3V0ZVJlc3BvbnNlIqYBCiNDcmVhdGVQdWJsaWNUbHNEbnNDcmVkZW50aWFsUmVxdWVzdBIMCgRuYW1lGAEgASgJEjEKCHByb3ZpZGVyGAIgASgOMh8ucDJwc3RyZWFtLnYxLlB1YmxpY0Ruc1Byb3ZpZGVyEhoKEmNsb3VkZmxhcmVfem9uZV9pZBgDIAEoCRIRCglhcGlfdG9rZW4YBCABKAkSDwoHZW5hYmxlZBgFIAEoCCJgCiRDcmVhdGVQdWJsaWNUbHNEbnNDcmVkZW50aWFsUmVzcG9uc2USOAoKY3JlZGVudGlhbBgBIAEoCzIkLnAycHN0cmVhbS52MS5QdWJsaWNUbHNEbnNDcmVkZW50aWFsIskBCiNVcGRhdGVQdWJsaWNUbHNEbnNDcmVkZW50aWFsUmVxdWVzdBIKCgJpZBgBIAEoAxIMCgRuYW1lGAIgASgJEjEKCHByb3ZpZGVyGAMgASgOMh8ucDJwc3RyZWFtLnYxLlB1YmxpY0Ruc1Byb3ZpZGVyEhoKEmNsb3VkZmxhcmVfem9uZV9pZBgEIAEoCRIRCglhcGlfdG9rZW4YBSABKAkSFQoNYXBpX3Rva2VuX3NldBgGIAEoCBIPCgdlbmFibGVkGAcgASgIImAKJFVwZGF0ZVB1YmxpY1Rsc0Ruc0NyZWRlbnRpYWxSZXNwb25zZRI4CgpjcmVkZW50aWFsGAEgASgLMiQucDJwc3RyZWFtLnYxLlB1YmxpY1Rsc0Ruc0NyZWRlbnRpYWwiMQojRGVsZXRlUHVibGljVGxzRG5zQ3JlZGVudGlhbFJlcXVlc3QSCgoCaWQYASABKAMiJgokRGVsZXRlUHVibGljVGxzRG5zQ3JlZGVudGlhbFJlc3BvbnNlIsYDCiFDcmVhdGVQdWJsaWNUbHNDZXJ0aWZpY2F0ZVJlcXVlc3QSEwoLbGlzdGVuZXJfaWQYASABKAMSGAoQaG9zdG5hbWVfcGF0dGVybhgCIAEoCRIRCgljZXJ0X3BhdGgYAyABKAkSEAoIa2V5X3BhdGgYBCABKAkSDwoHZW5hYmxlZBgFIAEoCBIQCghjZXJ0X3BlbRgGIAEoDBIPCgdrZXlfcGVtGAcgASgMEjgKBnNvdXJjZRgIIAEoDjIoLnAycHN0cmVhbS52MS5QdWJsaWNUbHNDZXJ0aWZpY2F0ZVNvdXJjZRJCChNhY21lX2NoYWxsZW5nZV90eXBlGAkgASgOMiUucDJwc3RyZWFtLnYxLlB1YmxpY0FjbWVDaGFsbGVuZ2VUeXBlEisKB2FjbWVfY2EYCiABKA4yGi5wMnBzdHJlYW0udjEuUHVibGljQWNtZUNhEhIKCmFjbWVfZW1haWwYCyABKAkSGQoRZG5zX2NyZWRlbnRpYWxfaWQYDCABKAMSHAoUZ2VuZXJhdGVfc2VsZl9zaWduZWQYDSABKAgSIQoZc2VsZl9zaWduZWRfdmFsaWRpdHlfZGF5cxgOIAEoAyJhCiJDcmVhdGVQdWJsaWNUbHNDZXJ0aWZpY2F0ZVJlc3BvbnNlEjsKD3Rsc19jZXJ0aWZpY2F0ZRgBIAEoCzIiLnAycHN0cmVhbS52MS5QdWJsaWNUbHNDZXJ0aWZpY2F0ZSLSAwohVXBkYXRlUHVibGljVGxzQ2VydGlmaWNhdGVSZXF1ZXN0EgoKAmlkGAEgASgDEhMKC2xpc3RlbmVyX2lkGAIgASgDEhgKEGhvc3RuYW1lX3BhdHRlcm4YAyABKAkSEQoJY2VydF9wYXRoGAQgASgJEhAKCGtleV9wYXRoGAUgASgJEg8KB2VuYWJsZWQYBiABKAgSEAoIY2VydF9wZW0YByABKAwSDwoHa2V5X3BlbRgIIAEoDBI4CgZzb3VyY2UYCSABKA4yKC5wMnBzdHJlYW0udjEuUHVibGljVGxzQ2VydGlmaWNhdGVTb3VyY2USQgoTYWNtZV9jaGFsbGVuZ2VfdHlwZRgKIAEoDjIlLnAycHN0cmVhbS52MS5QdWJsaWNBY21lQ2hhbGxlbmdlVHlwZRIrCgdhY21lX2NhGAsgASgOMhoucDJwc3RyZWFtLnYxLlB1YmxpY0FjbWVDYRISCgphY21lX2VtYWlsGAwgASgJEhkKEWRuc19jcmVkZW50aWFsX2lkGA0gASgDEhwKFGdlbmVyYXRlX3NlbGZfc2lnbmVkGA4gASgIEiEKGXNlbGZfc2lnbmVkX3ZhbGlkaXR5X2RheXMYDyABKAMiYQoiVXBkYXRlUHVibGljVGxzQ2VydGlmaWNhdGVSZXNwb25zZRI7Cg90bHNfY2VydGlmaWNhdGUYASABKAsyIi5wMnBzdHJlYW0udjEuUHVibGljVGxzQ2VydGlmaWNhdGUiLwohRGVsZXRlUHVibGljVGxzQ2VydGlmaWNhdGVSZXF1ZXN0EgoKAmlkGAEgASgDIiQKIkRlbGV0ZVB1YmxpY1Rsc0NlcnRpZmljYXRlUmVzcG9uc2UiLgogUmVuZXdQdWJsaWNUbHNDZXJ0aWZpY2F0ZVJlcXVlc3QSCgoCaWQYASABKAMiYAohUmVuZXdQdWJsaWNUbHNDZXJ0aWZpY2F0ZVJlc3BvbnNlEjsKD3Rsc19jZXJ0aWZpY2F0ZRgBIAEoCzIiLnAycHN0cmVhbS52MS5QdWJsaWNUbHNDZXJ0aWZpY2F0ZSLCBAogQ3JlYXRlUHVibGljUmF0ZUxpbWl0UnVsZVJlcXVlc3QSDAoEbmFtZRgBIAEoCRIQCghwcmlvcml0eRgCIAEoAxIPCgdlbmFibGVkGAMgASgIEjkKCWFsZ29yaXRobRgEIAEoDjImLnAycHN0cmVhbS52MS5QdWJsaWNSYXRlTGltaXRBbGdvcml0aG0SDQoFbGltaXQYBSABKAMSFQoNd2luZG93X21pbGxpcxgGIAEoAxINCgVidXJzdBgHIAEoAxI3CglrZXlfcGFydHMYCSADKAsyJC5wMnBzdHJlYW0udjEuUHVibGljUmF0ZUxpbWl0S2V5UGFydBIcChRyZXNwb25zZV9zdGF0dXNfY29kZRgKIAEoAxIVCg1yZXNwb25zZV9ib2R5GAsgASgJEh0KFXJlc3BvbnNlX2NvbnRlbnRfdHlwZRgMIAEoCRJFChByZXNwb25zZV9oZWFkZXJzGA0gAygLMisucDJwc3RyZWFtLnYxLlB1YmxpY1JhdGVMaW1pdFJlc3BvbnNlSGVhZGVyEkAKEnJlc3BvbnNlX2JvZHlfbW9kZRgOIAEoDjIkLnAycHN0cmVhbS52MS5QdWJsaWNSZXNwb25zZUJvZHlNb2RlEiEKGXJlc3BvbnNlX2JvZHlfdGVtcGxhdGVfaWQYDyABKAMSNwoKbWF0Y2hfcnVsZRgQIAEoCzIjLnAycHN0cmVhbS52MS5QdWJsaWNQb2xpY3lNYXRjaFJ1bGVKBAgIEAlSBW1hdGNoIlQKIUNyZWF0ZVB1YmxpY1JhdGVMaW1pdFJ1bGVSZXNwb25zZRIvCgRydWxlGAEgASgLMiEucDJwc3RyZWFtLnYxLlB1YmxpY1JhdGVMaW1pdFJ1bGUizgQKIFVwZGF0ZVB1YmxpY1JhdGVMaW1pdFJ1bGVSZXF1ZXN0EgoKAmlkGAEgASgDEgwKBG5hbWUYAiABKAkSEAoIcHJpb3JpdHkYAyABKAMSDwoHZW5hYmxlZBgEIAEoCBI5CglhbGdvcml0aG0YBSABKA4yJi5wMnBzdHJlYW0udjEuUHVibGljUmF0ZUxpbWl0QWxnb3JpdGhtEg0KBWxpbWl0GAYgASgDEhUKDXdpbmRvd19taWxsaXMYByABKAMSDQoFYnVyc3QYCCABKAMSNwoJa2V5X3BhcnRzGAogAygLMiQucDJwc3RyZWFtLnYxLlB1YmxpY1JhdGVMaW1pdEtleVBhcnQSHAoUcmVzcG9uc2Vfc3RhdHVzX2NvZGUYCyABKAMSFQoNcmVzcG9uc2VfYm9keRgMIAEoCRIdChVyZXNwb25zZV9jb250ZW50X3R5cGUYDSABKAkSRQoQcmVzcG9uc2VfaGVhZGVycxgOIAMoCzIrLnAycHN0cmVhbS52MS5QdWJsaWNSYXRlTGltaXRSZXNwb25zZUhlYWRlchJAChJyZXNwb25zZV9ib2R5X21vZGUYDyABKA4yJC5wMnBzdHJlYW0udjEuUHVibGljUmVzcG9uc2VCb2R5TW9kZRIhChlyZXNwb25zZV9ib2R5X3RlbXBsYXRlX2lkGBAgASgDEjcKCm1hdGNoX3J1bGUYESABKAsyIy5wMnBzdHJlYW0udjEuUHVibGljUG9saWN5TWF0Y2hSdWxlSgQICRAKUgVtYXRjaCJUCiFVcGRhdGVQdWJsaWNSYXRlTGltaXRSdWxlUmVzcG9uc2USLwoEcnVsZRgBIAEoCzIhLnAycHN0cmVhbS52MS5QdWJsaWNSYXRlTGltaXRSdWxlIi4KIERlbGV0ZVB1YmxpY1JhdGVMaW1pdFJ1bGVSZXF1ZXN0EgoKAmlkGAEgASgDIiMKIURlbGV0ZVB1YmxpY1JhdGVMaW1pdFJ1bGVSZXNwb25zZSKwAwokQ3JlYXRlUHVibGljVHJhZmZpY1NoYXBlclJ1bGVSZXF1ZXN0EgwKBG5hbWUYASABKAkSEAoIcHJpb3JpdHkYAiABKAMSDwoHZW5hYmxlZBgDIAEoCBJCCgxidWRnZXRfc2NvcGUYBCABKA4yLC5wMnBzdHJlYW0udjEuUHVibGljVHJhZmZpY1NoYXBlckJ1ZGdldFNjb3BlEh8KF3VwbG9hZF9ieXRlc19wZXJfc2Vjb25kGAUgASgDEiEKGWRvd25sb2FkX2J5dGVzX3Blcl9zZWNvbmQYBiABKAMSEwoLYnVyc3RfYnl0ZXMYByABKAMSHAoUcmVxdWVzdF9leGVtcHRfYnl0ZXMYCCABKAMSHQoVcmVzcG9uc2VfZXhlbXB0X2J5dGVzGAkgASgDEjcKCWtleV9wYXJ0cxgLIAMoCzIkLnAycHN0cmVhbS52MS5QdWJsaWNSYXRlTGltaXRLZXlQYXJ0EjcKCm1hdGNoX3J1bGUYDCABKAsyIy5wMnBzdHJlYW0udjEuUHVibGljUG9saWN5TWF0Y2hSdWxlSgQIChALUgVtYXRjaCJcCiVDcmVhdGVQdWJsaWNUcmFmZmljU2hhcGVyUnVsZVJlc3BvbnNlEjMKBHJ1bGUYASABKAsyJS5wMnBzdHJlYW0udjEuUHVibGljVHJhZmZpY1NoYXBlclJ1bGUivAMKJFVwZGF0ZVB1YmxpY1RyYWZmaWNTaGFwZXJSdWxlUmVxdWVzdBIKCgJpZBgBIAEoAxIMCgRuYW1lGAIgASgJEhAKCHByaW9yaXR5GAMgASgDEg8KB2VuYWJsZWQYBCABKAgSQgoMYnVkZ2V0X3Njb3BlGAUgASgOMiwucDJwc3RyZWFtLnYxLlB1YmxpY1RyYWZmaWNTaGFwZXJCdWRnZXRTY29wZRIfChd1cGxvYWRfYnl0ZXNfcGVyX3NlY29uZBgGIAEoAxIhChlkb3dubG9hZF9ieXRlc19wZXJfc2Vjb25kGAcgASgDEhMKC2J1cnN0X2J5dGVzGAggASgDEhwKFHJlcXVlc3RfZXhlbXB0X2J5dGVzGAkgASgDEh0KFXJlc3BvbnNlX2V4ZW1wdF9ieXRlcxgKIAEoAxI3CglrZXlfcGFydHMYDCADKAsyJC5wMnBzdHJlYW0udjEuUHVibGljUmF0ZUxpbWl0S2V5UGFydBI3CgptYXRjaF9ydWxlGA0gASgLMiMucDJwc3RyZWFtLnYxLlB1YmxpY1BvbGljeU1hdGNoUnVsZUoECAsQDFIFbWF0Y2giXAolVXBkYXRlUHVibGljVHJhZmZpY1NoYXBlclJ1bGVSZXNwb25zZRIzCgRydWxlGAEgASgLMiUucDJwc3RyZWFtLnYxLlB1YmxpY1RyYWZmaWNTaGFwZXJSdWxlIjIKJERlbGV0ZVB1YmxpY1RyYWZmaWNTaGFwZXJSdWxlUmVxdWVzdBIKCgJpZBgBIAEoAyInCiVEZWxldGVQdWJsaWNUcmFmZmljU2hhcGVyUnVsZVJlc3BvbnNlIq8BCiVDcmVhdGVQdWJsaWNXYWZDYXB0Y2hhUHJvdmlkZXJSZXF1ZXN0EgwKBG5hbWUYASABKAkSQQoNcHJvdmlkZXJfdHlwZRgCIAEoDjIqLnAycHN0cmVhbS52MS5QdWJsaWNXYWZDYXB0Y2hhUHJvdmlkZXJUeXBlEhAKCHNpdGVfa2V5GAMgASgJEhIKCnNlY3JldF9rZXkYBCABKAkSDwoHZW5hYmxlZBgFIAEoCCJiCiZDcmVhdGVQdWJsaWNXYWZDYXB0Y2hhUHJvdmlkZXJSZXNwb25zZRI4Cghwcm92aWRlchgBIAEoCzImLnAycHN0cmVhbS52MS5QdWJsaWNXYWZDYXB0Y2hhUHJvdmlkZXIi0wEKJVVwZGF0ZVB1YmxpY1dhZkNhcHRjaGFQcm92aWRlclJlcXVlc3QSCgoCaWQYASABKAMSDAoEbmFtZRgCIAEoCRJBCg1wcm92aWRlcl90eXBlGAMgASgOMioucDJwc3RyZWFtLnYxLlB1YmxpY1dhZkNhcHRjaGFQcm92aWRlclR5cGUSEAoIc2l0ZV9rZXkYBCABKAkSEgoKc2VjcmV0X2tleRgFIAEoCRIWCg5zZWNyZXRfa2V5X3NldBgGIAEoCBIPCgdlbmFibGVkGAcgASgIImIKJlVwZGF0ZVB1YmxpY1dhZkNhcHRjaGFQcm92aWRlclJlc3BvbnNlEjgKCHByb3ZpZGVyGAEgASgLMiYucDJwc3RyZWFtLnYxLlB1YmxpY1dhZkNhcHRjaGFQcm92aWRlciIzCiVEZWxldGVQdWJsaWNXYWZDYXB0Y2hhUHJvdmlkZXJSZXF1ZXN0EgoKAmlkGAEgASgDIigKJkRlbGV0ZVB1YmxpY1dhZkNhcHRjaGFQcm92aWRlclJlc3BvbnNlIt0GChpDcmVhdGVQdWJsaWNXYWZSdWxlUmVxdWVzdBIMCgRuYW1lGAEgASgJEhAKCHByaW9yaXR5GAIgASgDEg8KB2VuYWJsZWQYAyABKAgSMQoGYWN0aW9uGAQgASgOMiEucDJwc3RyZWFtLnYxLlB1YmxpY1dhZlJ1bGVBY3Rpb24SPgoPYWN0aXZhdGlvbl9tb2RlGAUgASgOMiUucDJwc3RyZWFtLnYxLlB1YmxpY1dhZkFjdGl2YXRpb25Nb2RlEjcKCWtleV9wYXJ0cxgHIAMoCzIkLnAycHN0cmVhbS52MS5QdWJsaWNSYXRlTGltaXRLZXlQYXJ0EhsKE2NhcHRjaGFfcHJvdmlkZXJfaWQYCCABKAMSHwoXY2FwdGNoYV9wYXNzX3R0bF9taWxsaXMYCSABKAMSPgoMd2FpdGluZ19yb29tGAogASgLMigucDJwc3RyZWFtLnYxLlB1YmxpY1dhZldhaXRpbmdSb29tQ29uZmlnEjYKCHRyaWdnZXJzGAsgASgLMiQucDJwc3RyZWFtLnYxLlB1YmxpY1dhZlRyaWdnZXJDb25maWcSIgoaYmxvY2tfcmVzcG9uc2Vfc3RhdHVzX2NvZGUYDCABKAMSGwoTYmxvY2tfcmVzcG9uc2VfYm9keRgNIAEoCRIjChtibG9ja19yZXNwb25zZV9jb250ZW50X3R5cGUYDiABKAkSSwoWYmxvY2tfcmVzcG9uc2VfaGVhZGVycxgPIAMoCzIrLnAycHN0cmVhbS52MS5QdWJsaWNSYXRlTGltaXRSZXNwb25zZUhlYWRlchJGChhibG9ja19yZXNwb25zZV9ib2R5X21vZGUYECABKA4yJC5wMnBzdHJlYW0udjEuUHVibGljUmVzcG9uc2VCb2R5TW9kZRIiChpibG9ja19yZXNwb25zZV90ZW1wbGF0ZV9pZBgRIAEoAxIgChhjYXB0Y2hhX3BhZ2VfdGVtcGxhdGVfaWQYEiABKAMSJQodd2FpdGluZ19yb29tX3BhZ2VfdGVtcGxhdGVfaWQYEyABKAMSNwoKbWF0Y2hfcnVsZRgUIAEoCzIjLnAycHN0cmVhbS52MS5QdWJsaWNQb2xpY3lNYXRjaFJ1bGVKBAgGEAdSBW1hdGNoIkgKG0NyZWF0ZVB1YmxpY1dhZlJ1bGVSZXNwb25zZRIpCgRydWxlGAEgASgLMhsucDJwc3RyZWFtLnYxLlB1YmxpY1dhZlJ1bGUi6QYKGlVwZGF0ZVB1YmxpY1dhZlJ1bGVSZXF1ZXN0EgoKAmlkGAEgASgDEgwKBG5hbWUYAiABKAkSEAoIcHJpb3JpdHkYAyABKAMSDwoHZW5hYmxlZBgEIAEoCBIxCgZhY3Rpb24YBSABKA4yIS5wMnBzdHJlYW0udjEuUHVibGljV2FmUnVsZUFjdGlvbhI+Cg9hY3RpdmF0aW9uX21vZGUYBiABKA4yJS5wMnBzdHJlYW0udjEuUHVibGljV2FmQWN0aXZhdGlvbk1vZGUSNwoJa2V5X3BhcnRzGAggAygLMiQucDJwc3RyZWFtLnYxLlB1YmxpY1JhdGVMaW1pdEtleVBhcnQSGwoTY2FwdGNoYV9wcm92aWRlcl9pZBgJIAEoAxIfChdjYXB0Y2hhX3Bhc3NfdHRsX21pbGxpcxgKIAEoAxI+Cgx3YWl0aW5nX3Jvb20YCyABKAsyKC5wMnBzdHJlYW0udjEuUHVibGljV2FmV2FpdGluZ1Jvb21Db25maWcSNgoIdHJpZ2dlcnMYDCABKAsyJC5wMnBzdHJlYW0udjEuUHVibGljV2FmVHJpZ2dlckNvbmZpZxIiChpibG9ja19yZXNwb25zZV9zdGF0dXNfY29kZRgNIAEoAxIbChNibG9ja19yZXNwb25zZV9ib2R5GA4gASgJEiMKG2Jsb2NrX3Jlc3BvbnNlX2NvbnRlbnRfdHlwZRgPIAEoCRJLChZibG9ja19yZXNwb25zZV9oZWFkZXJzGBAgAygLMisucDJwc3RyZWFtLnYxLlB1YmxpY1JhdGVMaW1pdFJlc3BvbnNlSGVhZGVyEkYKGGJsb2NrX3Jlc3BvbnNlX2JvZHlfbW9kZRgRIAEoDjIkLnAycHN0cmVhbS52MS5QdWJsaWNSZXNwb25zZUJvZHlNb2RlEiIKGmJsb2NrX3Jlc3BvbnNlX3RlbXBsYXRlX2lkGBIgASgDEiAKGGNhcHRjaGFfcGFnZV90ZW1wbGF0ZV9pZBgTIAEoAxIlCh13YWl0aW5nX3Jvb21fcGFnZV90ZW1wbGF0ZV9pZBgUIAEoAxI3CgptYXRjaF9ydWxlGBUgASgLMiMucDJwc3RyZWFtLnYxLlB1YmxpY1BvbGljeU1hdGNoUnVsZUoECAcQCFIFbWF0Y2giSAobVXBkYXRlUHVibGljV2FmUnVsZVJlc3BvbnNlEikKBHJ1bGUYASABKAsyGy5wMnBzdHJlYW0udjEuUHVibGljV2FmUnVsZSIoChpEZWxldGVQdWJsaWNXYWZSdWxlUmVxdWVzdBIKCgJpZBgBIAEoAyIdChtEZWxldGVQdWJsaWNXYWZSdWxlUmVzcG9uc2UizAQKHENyZWF0ZVB1YmxpY0NhY2hlUnVsZVJlcXVlc3QSDAoEbmFtZRgBIAEoCRIQCghwcmlvcml0eRgCIAEoAxIPCgdlbmFibGVkGAMgASgIEhEKCXJvdXRlX2lkcxgFIAMoAxItCgVzY29wZRgHIAEoDjIeLnAycHN0cmVhbS52MS5QdWJsaWNDYWNoZVNjb3BlEjIKCHR0bF9tb2RlGAggASgOMiAucDJwc3RyZWFtLnYxLlB1YmxpY0NhY2hlVHRsTW9kZRISCgp0dGxfbWlsbGlzGAkgASgDEjYKCnF1ZXJ5X21vZGUYCiABKA4yIi5wMnBzdHJlYW0udjEuUHVibGljQ2FjaGVRdWVyeU1vZGUSFAoMcXVlcnlfcGFyYW1zGAsgAygJEhQKDHZhcnlfaGVhZGVycxgMIAMoCRIaChJjYWNoZV9zdGF0dXNfY29kZXMYDSADKAMSGAoQbWF4X29iamVjdF9ieXRlcxgOIAEoAxIfChdhZGRfY2FjaGVfc3RhdHVzX2hlYWRlchgPIAEoCBIdChVhbGxvd19jb29raWVfcmVxdWVzdHMYECABKAgSNwoKbWF0Y2hfcnVsZRgRIAEoCzIjLnAycHN0cmVhbS52MS5QdWJsaWNQb2xpY3lNYXRjaFJ1bGUSKgoiYWxsb3dfY29va2llX3JlcXVlc3RzX2Fja25vd2xlZGdlZBgSIAEoCBISCgp0YXJnZXRfaWRzGBMgAygDSgQIBBAFSgQIBhAHUgVtYXRjaFILYmFja2VuZF9pZHMiTAodQ3JlYXRlUHVibGljQ2FjaGVSdWxlUmVzcG9uc2USKwoEcnVsZRgBIAEoCzIdLnAycHN0cmVhbS52MS5QdWJsaWNDYWNoZVJ1bGUi2AQKHFVwZGF0ZVB1YmxpY0NhY2hlUnVsZVJlcXVlc3QSCgoCaWQYASABKAMSDAoEbmFtZRgCIAEoCRIQCghwcmlvcml0eRgDIAEoAxIPCgdlbmFibGVkGAQgASgIEhEKCXJvdXRlX2lkcxgGIAMoAxItCgVzY29wZRgIIAEoDjIeLnAycHN0cmVhbS52MS5QdWJsaWNDYWNoZVNjb3BlEjIKCHR0bF9tb2RlGAkgASgOMiAucDJwc3RyZWFtLnYxLlB1YmxpY0NhY2hlVHRsTW9kZRISCgp0dGxfbWlsbGlzGAogASgDEjYKCnF1ZXJ5X21vZGUYCyABKA4yIi5wMnBzdHJlYW0udjEuUHVibGljQ2FjaGVRdWVyeU1vZGUSFAoMcXVlcnlfcGFyYW1zGAwgAygJEhQKDHZhcnlfaGVhZGVycxgNIAMoCRIaChJjYWNoZV9zdGF0dXNfY29kZXMYDiADKAMSGAoQbWF4X29iamVjdF9ieXRlcxgPIAEoAxIfChdhZGRfY2FjaGVfc3RhdHVzX2hlYWRlchgQIAEoCBIdChVhbGxvd19jb29raWVfcmVxdWVzdHMYESABKAgSNwoKbWF0Y2hfcnVsZRgSIAEoCzIjLnAycHN0cmVhbS52MS5QdWJsaWNQb2xpY3lNYXRjaFJ1bGUSKgoiYWxsb3dfY29va2llX3JlcXVlc3RzX2Fja25vd2xlZGdlZBgTIAEoCBISCgp0YXJnZXRfaWRzGBQgAygDSgQIBRAGSgQIBxAIUgVtYXRjaFILYmFja2VuZF9pZHMiTAodVXBkYXRlUHVibGljQ2FjaGVSdWxlUmVzcG9uc2USKwoEcnVsZRgBIAEoCzIdLnAycHN0cmVhbS52MS5QdWJsaWNDYWNoZVJ1bGUiKgocRGVsZXRlUHVibGljQ2FjaGVSdWxlUmVxdWVzdBIKCgJpZBgBIAEoAyIfCh1EZWxldGVQdWJsaWNDYWNoZVJ1bGVSZXNwb25zZSLAAQogVXBkYXRlUHVibGljQ2FjaGVTZXR0aW5nc1JlcXVlc3QSDwoHZW5hYmxlZBgBIAEoCBIWCg5tYXhfZGlza19ieXRlcxgCIAEoAxIYChBtYXhfbWVtb3J5X2J5dGVzGAMgASgDEiMKG21lbW9yeV9ob3Rfb2JqZWN0X21heF9ieXRlcxgEIAEoAxITCgttYXhfZW50cmllcxgFIAEoAxIfChdjbGVhbnVwX2ludGVydmFsX21pbGxpcxgGIAEoAyJYCiFVcGRhdGVQdWJsaWNDYWNoZVNldHRpbmdzUmVzcG9uc2USMwoIc2V0dGluZ3MYASABKAsyIS5wMnBzdHJlYW0udjEuUHVibGljQ2FjaGVTZXR0aW5ncyJaChdQdXJnZVB1YmxpY0NhY2hlUmVxdWVzdBILCgNhbGwYASABKAgSDwoHcnVsZV9pZBgCIAEoAxIMCgRob3N0GAMgASgJEhMKC3BhdGhfcHJlZml4GAQgASgJIkgKGFB1cmdlUHVibGljQ2FjaGVSZXNwb25zZRIWCg5wdXJnZWRfZW50cmllcxgBIAEoAxIUCgxwdXJnZWRfYnl0ZXMYAiABKAMiFQoTR2V0RGFzaGJvYXJkUmVxdWVzdCKOCAoWRGFzaGJvYXJkV2luZG93U3VtbWFyeRINCgVsYWJlbBgBIAEoCRIZChFzaW5jZV91bml4X21pbGxpcxgCIAEoAxIWCg5wcm94eV9yZXF1ZXN0cxgDIAEoAxIVCg1wcm94eV9zdWNjZXNzGAQgASgDEhoKEnByb3h5X2NsaWVudF9lcnJvchgFIAEoAxIaChJwcm94eV9zZXJ2ZXJfZXJyb3IYBiABKAMSHAoUcHJveHlfaW50ZXJuYWxfZXJyb3IYByABKAMSHQoVcHJveHlfYXZnX2R1cmF0aW9uX21zGAggASgDEhUKDWFnZW50X3NhbXBsZXMYCSABKAMSGQoRYWdlbnRfcmVxX3N1Y2Nlc3MYCiABKAMSHgoWYWdlbnRfcmVxX2NsaWVudF9lcnJvchgLIAEoAxIeChZhZ2VudF9yZXFfc2VydmVyX2Vycm9yGAwgASgDEiAKGGFnZW50X3JlcV9pbnRlcm5hbF9lcnJvchgNIAEoAxIcChRhZ2VudF9ieXRlc19yZWNlaXZlZBgOIAEoBBIYChBhZ2VudF9ieXRlc19zZW50GA8gASgEEhsKE2FnZW50X2F2Z19tZW1vcnlfbWIYECABKAMSGwoTYWdlbnRfbWF4X21lbW9yeV9tYhgRIAEoAxIcChRhZ2VudF9hdmdfZ29yb3V0aW5lcxgSIAEoAxIcChRhZ2VudF9tYXhfZ29yb3V0aW5lcxgTIAEoAxIbChNwcm94eV9yZXF1ZXN0X2J5dGVzGBQgASgEEhwKFHByb3h5X3Jlc3BvbnNlX2J5dGVzGBUgASgEEhkKEXByb3h5X3RvdGFsX2J5dGVzGBYgASgEEh8KF3Byb3h5X2F2Z19yZXF1ZXN0X2J5dGVzGBcgASgEEiAKGHByb3h5X2F2Z19yZXNwb25zZV9ieXRlcxgYIAEoBBIdChVwcm94eV9tYXhfZHVyYXRpb25fbXMYGSABKAMSGwoTcHJveHlfc2xvd19yZXF1ZXN0cxgaIAEoAxIdChVhZ2VudF9hdmdfY3B1X3BlcmNlbnQYGyABKAESHQoVYWdlbnRfbWF4X2NwdV9wZXJjZW50GBwgASgBEhgKEHByb3h5X2NhY2hlX2hpdHMYHSABKAMSGgoScHJveHlfY2FjaGVfbWlzc2VzGB4gASgDEhwKFHByb3h5X2NhY2hlX2J5cGFzc2VzGB8gASgDEhoKEnByb3h5X2NhY2hlX3N0b3JlZBggIAEoAxIgChhwcm94eV9jYWNoZV9zdG9yZV9mYWlsZWQYISABKAMSHQoVcHJveHlfY2FjaGVfaGl0X2J5dGVzGCIgASgEEiAKGHByb3h5X2NhY2hlX3N0b3JlZF9ieXRlcxgjIAEoBCKkAgoeRGFzaGJvYXJkUHJveHlEaW1lbnNpb25TdW1tYXJ5EjgKCWRpbWVuc2lvbhgBIAEoDjIlLnAycHN0cmVhbS52MS5EYXNoYm9hcmRQcm94eURpbWVuc2lvbhIKCgJpZBgCIAEoAxINCgVsYWJlbBgDIAEoCRIQCghyZXF1ZXN0cxgEIAEoAxIPCgdzdWNjZXNzGAUgASgDEhQKDGNsaWVudF9lcnJvchgGIAEoAxIUCgxzZXJ2ZXJfZXJyb3IYByABKAMSFgoOaW50ZXJuYWxfZXJyb3IYCCABKAMSFwoPYXZnX2R1cmF0aW9uX21zGAkgASgDEhUKDXJlcXVlc3RfYnl0ZXMYCiABKAQSFgoOcmVzcG9uc2VfYnl0ZXMYCyABKAQi4wEKFkRhc2hib2FyZFRyYWZmaWNCdWNrZXQSGgoSYnVja2V0X3VuaXhfbWlsbGlzGAEgASgDEhAKCHJlcXVlc3RzGAIgASgDEg8KB3N1Y2Nlc3MYAyABKAMSFAoMY2xpZW50X2Vycm9yGAQgASgDEhQKDHNlcnZlcl9lcnJvchgFIAEoAxIWCg5pbnRlcm5hbF9lcnJvchgGIAEoAxIVCg1yZXF1ZXN0X2J5dGVzGAcgASgEEhYKDnJlc3BvbnNlX2J5dGVzGAggASgEEhcKD2F2Z19kdXJhdGlvbl9tcxgJIAEoAyKdAgoSTWFuYWdlbWVudFNlY3VyaXR5EhMKC3Rsc19lbmFibGVkGAEgASgIEhAKCGF1dG9fdGxzGAIgASgIEh0KFWluc2VjdXJlX2h0dHBfYWxsb3dlZBgDIAEoCBIcChRhZ2VudF9odHRwc19yZXF1aXJlZBgEIAEoCBIpCiFhZ2VudF9jbGllbnRfY2VydGlmaWNhdGVfcmVxdWlyZWQYBSABKAgSHgoWZGVmYXVsdF9tYW5hZ2VtZW50X3VybBgGIAEoCRIZChFtYW5hZ2VtZW50X2NhX3BlbRgHIAEoCRIcChRtYW5hZ2VtZW50X2NhX3NoYTI1NhgIIAEoCRIfChdkZXRlY3RlZF9hZHZlcnRpc2VfaG9zdBgJIAEoCSLAAQoWQWdlbnRDb25uZWN0aW9uU3VtbWFyeRIRCgljb25uZWN0ZWQYASABKAgSGQoRdG90YWxfY29ubmVjdGlvbnMYAiABKAMSJwofYWN0aXZlX2Nvbm5lY3RlZF9hdF91bml4X21pbGxpcxgDIAEoAxIlCh1sYXN0X2Nvbm5lY3RlZF9hdF91bml4X21pbGxpcxgEIAEoAxIoCiBsYXN0X2Rpc2Nvbm5lY3RlZF9hdF91bml4X21pbGxpcxgFIAEoAyKhBAoSQWdlbnRVcHRpbWVTdW1tYXJ5EhAKCGFnZW50X2lkGAEgASgDEhcKD2FnZW50X3B1YmxpY19pZBgCIAEoCRISCgphZ2VudF9uYW1lGAMgASgJEg8KB2VuYWJsZWQYBCABKAgSEQoJY29ubmVjdGVkGAUgASgIEigKIGN1cnJlbnRfY29ubmVjdGVkX2F0X3VuaXhfbWlsbGlzGAYgASgDEh0KFWN1cnJlbnRfdXB0aW1lX21pbGxpcxgHIAEoAxIpCiFjdXJyZW50X29mZmxpbmVfc2luY2VfdW5peF9taWxsaXMYCCABKAMSHwoXY3VycmVudF9kb3dudGltZV9taWxsaXMYCSABKAMSFQoNdXB0aW1lX21pbGxpcxgKIAEoAxIXCg9kb3dudGltZV9taWxsaXMYCyABKAMSFgoOdXB0aW1lX3BlcmNlbnQYDCABKAESGAoQY29ubmVjdGlvbl9jb3VudBgNIAEoAxIYChBkaXNjb25uZWN0X2NvdW50GA4gASgDEiIKGm9ic2VydmVkX3NpbmNlX3VuaXhfbWlsbGlzGA8gASgDEiIKGm9ic2VydmVkX3VudGlsX3VuaXhfbWlsbGlzGBAgASgDEiUKHWxhc3RfY29ubmVjdGVkX2F0X3VuaXhfbWlsbGlzGBEgASgDEigKIGxhc3RfZGlzY29ubmVjdGVkX2F0X3VuaXhfbWlsbGlzGBIgASgDItMBChZBZ2VudENvbm5lY3Rpb25TZXNzaW9uEgoKAmlkGAEgASgDEhAKCGFnZW50X2lkGAIgASgDEhcKD2FnZW50X3B1YmxpY19pZBgDIAEoCRISCgphZ2VudF9uYW1lGAQgASgJEiAKGGNvbm5lY3RlZF9hdF91bml4X21pbGxpcxgFIAEoAxIjChtkaXNjb25uZWN0ZWRfYXRfdW5peF9taWxsaXMYBiABKAMSFwoPZHVyYXRpb25fbWlsbGlzGAcgASgDEg4KBmFjdGl2ZRgIIAEoCCK0BwoUR2V0RGFzaGJvYXJkUmVzcG9uc2USLwoGc3RhdHVzGAEgASgLMh8ucDJwc3RyZWFtLnYxLkdldFN0YXR1c1Jlc3BvbnNlEjUKB3dpbmRvd3MYAiADKAsyJC5wMnBzdHJlYW0udjEuRGFzaGJvYXJkV2luZG93U3VtbWFyeRI/ChFhZ2VudF9jb25uZWN0aW9ucxgDIAEoCzIkLnAycHN0cmVhbS52MS5BZ2VudENvbm5lY3Rpb25TdW1tYXJ5EhYKDnJldGVudGlvbl9kYXlzGAQgASgDEiAKGGdlbmVyYXRlZF9hdF91bml4X21pbGxpcxgFIAEoAxJDCg10b3BfbGlzdGVuZXJzGAYgAygLMiwucDJwc3RyZWFtLnYxLkRhc2hib2FyZFByb3h5RGltZW5zaW9uU3VtbWFyeRJACgp0b3Bfcm91dGVzGAggAygLMiwucDJwc3RyZWFtLnYxLkRhc2hib2FyZFByb3h5RGltZW5zaW9uU3VtbWFyeRJACgp0b3BfYWdlbnRzGAkgAygLMiwucDJwc3RyZWFtLnYxLkRhc2hib2FyZFByb3h5RGltZW5zaW9uU3VtbWFyeRJFCg90b3BfZXJyb3Jfa2luZHMYCiADKAsyLC5wMnBzdHJlYW0udjEuRGFzaGJvYXJkUHJveHlEaW1lbnNpb25TdW1tYXJ5EkQKDnN0YXR1c19jbGFzc2VzGAsgAygLMiwucDJwc3RyZWFtLnYxLkRhc2hib2FyZFByb3h5RGltZW5zaW9uU3VtbWFyeRI9Cg90cmFmZmljX2J1Y2tldHMYDCADKAsyJC5wMnBzdHJlYW0udjEuRGFzaGJvYXJkVHJhZmZpY0J1Y2tldBI9ChNtYW5hZ2VtZW50X3NlY3VyaXR5GA0gASgLMiAucDJwc3RyZWFtLnYxLk1hbmFnZW1lbnRTZWN1cml0eRJAChZhZ2VudF91cHRpbWVfc3VtbWFyaWVzGA4gAygLMiAucDJwc3RyZWFtLnYxLkFnZW50VXB0aW1lU3VtbWFyeRJGChhyZWNlbnRfYWdlbnRfY29ubmVjdGlvbnMYDyADKAsyJC5wMnBzdHJlYW0udjEuQWdlbnRDb25uZWN0aW9uU2Vzc2lvbhJHChF0b3Bfcm91dGVfdGFyZ2V0cxgQIAMoCzIsLnAycHN0cmVhbS52MS5EYXNoYm9hcmRQcm94eURpbWVuc2lvblN1bW1hcnlKBAgHEAhSDHRvcF9iYWNrZW5kcyLBAQoUVHJhZmZpY1RyYWNlU2V0dGluZ3MSDwoHZW5hYmxlZBgBIAEoCBIuCgVsZXZlbBgCIAEoDjIfLnAycHN0cmVhbS52MS5UcmFmZmljVHJhY2VMZXZlbBIeChZ1cGRhdGVkX2F0X3VuaXhfbWlsbGlzGAMgASgDEhYKDmVtaXR0ZWRfZXZlbnRzGAQgASgEEhYKDmRyb3BwZWRfZXZlbnRzGAUgASgEEhgKEHN1YnNjcmliZXJfY291bnQYBiABKAMiIAoeR2V0VHJhZmZpY1RyYWNlU2V0dGluZ3NSZXF1ZXN0IlcKH0dldFRyYWZmaWNUcmFjZVNldHRpbmdzUmVzcG9uc2USNAoIc2V0dGluZ3MYASABKAsyIi5wMnBzdHJlYW0udjEuVHJhZmZpY1RyYWNlU2V0dGluZ3MiYQoeU2V0VHJhZmZpY1RyYWNlU2V0dGluZ3NSZXF1ZXN0Eg8KB2VuYWJsZWQYASABKAgSLgoFbGV2ZWwYAiABKA4yHy5wMnBzdHJlYW0udjEuVHJhZmZpY1RyYWNlTGV2ZWwiVwofU2V0VHJhZmZpY1RyYWNlU2V0dGluZ3NSZXNwb25zZRI0CghzZXR0aW5ncxgBIAEoCzIiLnAycHN0cmVhbS52MS5UcmFmZmljVHJhY2VTZXR0aW5ncyJQCh9TdHJlYW1UcmFmZmljVHJhY2VFdmVudHNSZXF1ZXN0EhUKDXJlcGxheV9yZWNlbnQYASABKAgSFgoOYWZ0ZXJfc2VxdWVuY2UYAiABKAQipg8KEVRyYWZmaWNUcmFjZUV2ZW50EhAKCHNlcXVlbmNlGAEgASgEEhIKCnJlcXVlc3RfaWQYAiABKAkSLgoFc3RhZ2UYAyABKA4yHy5wMnBzdHJlYW0udjEuVHJhZmZpY1RyYWNlU3RhZ2USHwoXb2NjdXJyZWRfYXRfdW5peF9taWxsaXMYBCABKAMSDgoGbWV0aG9kGAUgASgJEgwKBGhvc3QYBiABKAkSDAoEcGF0aBgHIAEoCRINCgVxdWVyeRgIIAEoCRITCgtsaXN0ZW5lcl9pZBgJIAEoAxIVCg1saXN0ZW5lcl9uYW1lGAogASgJEhAKCHJvdXRlX2lkGAsgASgDEhMKC3JvdXRlX2xhYmVsGAwgASgJEhUKDWRlZmF1bHRfcm91dGUYDSABKAgSFQoNdGFyZ2V0X29yaWdpbhgSIAEoCRIQCghhZ2VudF9pZBgTIAEoAxIXCg9hZ2VudF9wdWJsaWNfaWQYFCABKAkSEgoKYWdlbnRfbmFtZRgVIAEoCRITCgtzdGF0dXNfY29kZRgWIAEoAxITCgtkdXJhdGlvbl9tcxgXIAEoAxISCgplcnJvcl9raW5kGBggASgJEkwKD3JlcXVlc3RfaGVhZGVycxgZIAMoCzIzLnAycHN0cmVhbS52MS5UcmFmZmljVHJhY2VFdmVudC5SZXF1ZXN0SGVhZGVyc0VudHJ5Ek4KEHJlc3BvbnNlX2hlYWRlcnMYGiADKAsyNC5wMnBzdHJlYW0udjEuVHJhZmZpY1RyYWNlRXZlbnQuUmVzcG9uc2VIZWFkZXJzRW50cnkSFQoNcmVxdWVzdF9ieXRlcxgbIAEoBBIWCg5yZXNwb25zZV9ieXRlcxgcIAEoBBJOChBkZWJ1Z19hdHRyaWJ1dGVzGB0gAygLMjQucDJwc3RyZWFtLnYxLlRyYWZmaWNUcmFjZUV2ZW50LkRlYnVnQXR0cmlidXRlc0VudHJ5EhoKEnJhdGVfbGltaXRfcnVsZV9pZBgeIAEoAxIcChRyYXRlX2xpbWl0X3J1bGVfbmFtZRgfIAEoCRJEChRyYXRlX2xpbWl0X2FsZ29yaXRobRggIAEoDjImLnAycHN0cmVhbS52MS5QdWJsaWNSYXRlTGltaXRBbGdvcml0aG0SHgoWdHJhZmZpY19zaGFwZXJfcnVsZV9pZBghIAEoAxIgChh0cmFmZmljX3NoYXBlcl9ydWxlX25hbWUYIiABKAkSUQobdHJhZmZpY19zaGFwZXJfYnVkZ2V0X3Njb3BlGCMgASgOMiwucDJwc3RyZWFtLnYxLlB1YmxpY1RyYWZmaWNTaGFwZXJCdWRnZXRTY29wZRIuCiZ0cmFmZmljX3NoYXBlcl91cGxvYWRfYnl0ZXNfcGVyX3NlY29uZBgkIAEoAxIwCih0cmFmZmljX3NoYXBlcl9kb3dubG9hZF9ieXRlc19wZXJfc2Vjb25kGCUgASgDEisKI3RyYWZmaWNfc2hhcGVyX3JlcXVlc3RfZXhlbXB0X2J5dGVzGCYgASgDEiwKJHRyYWZmaWNfc2hhcGVyX3Jlc3BvbnNlX2V4ZW1wdF9ieXRlcxgnIAEoAxITCgt3YWZfcnVsZV9pZBgoIAEoAxIVCg13YWZfcnVsZV9uYW1lGCkgASgJEjUKCndhZl9hY3Rpb24YKiABKA4yIS5wMnBzdHJlYW0udjEuUHVibGljV2FmUnVsZUFjdGlvbhJCChN3YWZfYWN0aXZhdGlvbl9tb2RlGCsgASgOMiUucDJwc3RyZWFtLnYxLlB1YmxpY1dhZkFjdGl2YXRpb25Nb2RlEhwKFHdhZl9hdXRvbWF0aWNfYWN0aXZlGCwgASgIEhoKEndhZl9jaGFsbGVuZ2Vfa2luZBgtIAEoCRIVCg1jYWNoZV9ydWxlX2lkGC4gASgDEhcKD2NhY2hlX3J1bGVfbmFtZRgvIAEoCRIUCgxjYWNoZV9zdGF0dXMYMCABKAkSGAoQY2FjaGVfa2V5X2RpZ2VzdBgxIAEoCRIXCg9yb3V0ZV90YXJnZXRfaWQYMiABKAMSGQoRcm91dGVfdGFyZ2V0X25hbWUYMyABKAkSPgoRcm91dGVfdGFyZ2V0X3R5cGUYNCABKA4yIy5wMnBzdHJlYW0udjEuUHVibGljUm91dGVUYXJnZXRUeXBlEkgKFnJvdXRlX3RhcmdldF90cmFuc3BvcnQYNSABKA4yKC5wMnBzdHJlYW0udjEuUHVibGljUm91dGVUYXJnZXRUcmFuc3BvcnQaNQoTUmVxdWVzdEhlYWRlcnNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBGjYKFFJlc3BvbnNlSGVhZGVyc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEaNgoURGVidWdBdHRyaWJ1dGVzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4AUoECA4QD0oECA8QEEoECBAQEUoECBEQElIKYmFja2VuZF9pZFIMYmFja2VuZF9uYW1lUgxiYWNrZW5kX3R5cGVSDGZvcndhcmRfbW9kZSKrAQogU3RyZWFtVHJhZmZpY1RyYWNlRXZlbnRzUmVzcG9uc2USNAoIc2V0dGluZ3MYASABKAsyIi5wMnBzdHJlYW0udjEuVHJhZmZpY1RyYWNlU2V0dGluZ3MSLgoFZXZlbnQYAiABKAsyHy5wMnBzdHJlYW0udjEuVHJhZmZpY1RyYWNlRXZlbnQSIQoZc3Vic2NyaWJlcl9kcm9wcGVkX2V2ZW50cxgDIAEoBCIWChRHZXRTZXR1cFN0YXRlUmVxdWVzdCKQAQoVR2V0U2V0dXBTdGF0ZVJlc3BvbnNlEhYKDnNldHVwX3JlcXVpcmVkGAEgASgIEhcKD3NldHVwX2F2YWlsYWJsZRgCIAEoCBIkChxzZXR1cF9leHBpcmVzX2F0X3VuaXhfbWlsbGlzGAMgASgDEiAKGHNldHVwX3VuYXZhaWxhYmxlX3JlYXNvbhgEIAEoCSJMChFTZXR1cEFkbWluUmVxdWVzdBIQCgh1c2VybmFtZRgBIAEoCRIQCghwYXNzd29yZBgCIAEoCRITCgtzZXR1cF90b2tlbhgDIAEoCSI2ChJTZXR1cEFkbWluUmVzcG9uc2USIAoEdXNlchgBIAEoCzISLnAycHN0cmVhbS52MS5Vc2VyIjIKDExvZ2luUmVxdWVzdBIQCgh1c2VybmFtZRgBIAEoCRIQCghwYXNzd29yZBgCIAEoCSIxCg1Mb2dpblJlc3BvbnNlEiAKBHVzZXIYASABKAsyEi5wMnBzdHJlYW0udjEuVXNlciIPCg1Mb2dvdXRSZXF1ZXN0IhAKDkxvZ291dFJlc3BvbnNlIhcKFUdldEN1cnJlbnRVc2VyUmVxdWVzdCI6ChZHZXRDdXJyZW50VXNlclJlc3BvbnNlEiAKBHVzZXIYASABKAsyEi5wMnBzdHJlYW0udjEuVXNlciITChFTdGFydFByb3h5UmVxdWVzdCI+ChJTdGFydFByb3h5UmVzcG9uc2USKAoFcHJveHkYASABKAsyGS5wMnBzdHJlYW0udjEuUHJveHlTdGF0dXMiEgoQU3RvcFByb3h5UmVxdWVzdCI9ChFTdG9wUHJveHlSZXNwb25zZRIoCgVwcm94eRgBIAEoCzIZLnAycHN0cmVhbS52MS5Qcm94eVN0YXR1cyo6CghVc2VyUm9sZRIZChVVU0VSX1JPTEVfVU5TUEVDSUZJRUQQABITCg9VU0VSX1JPTEVfQURNSU4QASqmAQoKUHJveHlTdGF0ZRIbChdQUk9YWV9TVEFURV9VTlNQRUNJRklFRBAAEhcKE1BST1hZX1NUQVRFX1NUT1BQRUQQARIYChRQUk9YWV9TVEFURV9TVEFSVElORxACEhcKE1BST1hZX1NUQVRFX1JVTk5JTkcQAxIYChRQUk9YWV9TVEFURV9TVE9QUElORxAEEhUKEVBST1hZX1NUQVRFX0VSUk9SEAUqiQEKFlB1YmxpY0xpc3RlbmVyUHJvdG9jb2wSKAokUFVCTElDX0xJU1RFTkVSX1BST1RPQ09MX1VOU1BFQ0lGSUVEEAASIQodUFVCTElDX0xJU1RFTkVSX1BST1RPQ09MX0hUVFAQARIiCh5QVUJMSUNfTElTVEVORVJfUFJPVE9DT0xfSFRUUFMQAiqRAQoWUHVibGljUmVzcG9uc2VCb2R5TW9kZRIpCiVQVUJMSUNfUkVTUE9OU0VfQk9EWV9NT0RFX1VOU1BFQ0lGSUVEEAASJAogUFVCTElDX1JFU1BPTlNFX0JPRFlfTU9ERV9JTkxJTkUQARImCiJQVUJMSUNfUkVTUE9OU0VfQk9EWV9NT0RFX1RFTVBMQVRFEAIq6AEKGlB1YmxpY1Jlc3BvbnNlVGVtcGxhdGVLaW5kEi0KKVBVQkxJQ19SRVNQT05TRV9URU1QTEFURV9LSU5EX1VOU1BFQ0lGSUVEEAASLgoqUFVCTElDX1JFU1BPTlNFX1RFTVBMQVRFX0tJTkRfR0VORVJJQ19CT0RZEAESMgouUFVCTElDX1JFU1BPTlNFX1RFTVBMQVRFX0tJTkRfV0FGX0NBUFRDSEFfUEFHRRACEjcKM1BVQkxJQ19SRVNQT05TRV9URU1QTEFURV9LSU5EX1dBRl9XQUlUSU5HX1JPT01fUEFHRRADKooBChVQdWJsaWNSb3V0ZVRhcmdldFR5cGUSKAokUFVCTElDX1JPVVRFX1RBUkdFVF9UWVBFX1VOU1BFQ0lGSUVEEAASIgoeUFVCTElDX1JPVVRFX1RBUkdFVF9UWVBFX1BST1hZEAESIwofUFVCTElDX1JPVVRFX1RBUkdFVF9UWVBFX1NUQVRJQxACKp4BChpQdWJsaWNSb3V0ZVRhcmdldFRyYW5zcG9ydBItCilQVUJMSUNfUk9VVEVfVEFSR0VUX1RSQU5TUE9SVF9VTlNQRUNJRklFRBAAEigKJFBVQkxJQ19ST1VURV9UQVJHRVRfVFJBTlNQT1JUX0RJUkVDVBABEicKI1BVQkxJQ19ST1VURV9UQVJHRVRfVFJBTlNQT1JUX0FHRU5UEAIqsQMKHlB1YmxpY1JvdXRlVGFyZ2V0TG9hZEJhbGFuY2luZxIyCi5QVUJMSUNfUk9VVEVfVEFSR0VUX0xPQURfQkFMQU5DSU5HX1VOU1BFQ0lGSUVEEAASMgouUFVCTElDX1JPVVRFX1RBUkdFVF9MT0FEX0JBTEFOQ0lOR19ST1VORF9ST0JJThABEjsKN1BVQkxJQ19ST1VURV9UQVJHRVRfTE9BRF9CQUxBTkNJTkdfV0VJR0hURURfUk9VTkRfUk9CSU4QAhItCilQVUJMSUNfUk9VVEVfVEFSR0VUX0xPQURfQkFMQU5DSU5HX1JBTkRPTRADEjYKMlBVQkxJQ19ST1VURV9UQVJHRVRfTE9BRF9CQUxBTkNJTkdfV0VJR0hURURfUkFORE9NEAQSPAo4UFVCTElDX1JPVVRFX1RBUkdFVF9MT0FEX0JBTEFOQ0lOR19MRUFTVF9BQ1RJVkVfUkVRVUVTVFMQBRJFCkFQVUJMSUNfUk9VVEVfVEFSR0VUX0xPQURfQkFMQU5DSU5HX1dFSUdIVEVEX0xFQVNUX0FDVElWRV9SRVFVRVNUUxAGKsUCCh1QdWJsaWNSb3V0ZVRhcmdldEhlYWx0aFN0YXR1cxIxCi1QVUJMSUNfUk9VVEVfVEFSR0VUX0hFQUxUSF9TVEFUVVNfVU5TUEVDSUZJRUQQABItCilQVUJMSUNfUk9VVEVfVEFSR0VUX0hFQUxUSF9TVEFUVVNfVU5LTk9XThABEi0KKVBVQkxJQ19ST1VURV9UQVJHRVRfSEVBTFRIX1NUQVRVU19IRUFMVEhZEAISLworUFVCTElDX1JPVVRFX1RBUkdFVF9IRUFMVEhfU1RBVFVTX1VOSEVBTFRIWRADEi4KKlBVQkxJQ19ST1VURV9UQVJHRVRfSEVBTFRIX1NUQVRVU19ESVNBQkxFRBAEEjIKLlBVQkxJQ19ST1VURV9UQVJHRVRfSEVBTFRIX1NUQVRVU19ESVNDT05ORUNURUQQBSqUAgoiUHVibGljUm91dGVUYXJnZXRIZWFsdGhUcmFjZVNvdXJjZRI3CjNQVUJMSUNfUk9VVEVfVEFSR0VUX0hFQUxUSF9UUkFDRV9TT1VSQ0VfVU5TUEVDSUZJRUQQABI4CjRQVUJMSUNfUk9VVEVfVEFSR0VUX0hFQUxUSF9UUkFDRV9TT1VSQ0VfQUNUSVZFX0NIRUNLEAESOwo3UFVCTElDX1JPVVRFX1RBUkdFVF9IRUFMVEhfVFJBQ0VfU09VUkNFX1BBU1NJVkVfRkFJTFVSRRACEj4KOlBVQkxJQ19ST1VURV9UQVJHRVRfSEVBTFRIX1RSQUNFX1NPVVJDRV9BR0VOVF9DT05ORUNUSVZJVFkQAyqBAgojUHVibGljUm91dGVUYXJnZXRIZWFsdGhUcmFjZU91dGNvbWUSOAo0UFVCTElDX1JPVVRFX1RBUkdFVF9IRUFMVEhfVFJBQ0VfT1VUQ09NRV9VTlNQRUNJRklFRBAAEjQKMFBVQkxJQ19ST1VURV9UQVJHRVRfSEVBTFRIX1RSQUNFX09VVENPTUVfU1VDQ0VTUxABEjQKMFBVQkxJQ19ST1VURV9UQVJHRVRfSEVBTFRIX1RSQUNFX09VVENPTUVfRkFJTFVSRRACEjQKMFBVQkxJQ19ST1VURV9UQVJHRVRfSEVBTFRIX1RSQUNFX09VVENPTUVfU0tJUFBFRBADKnsKEVB1YmxpY1JvdXRlQWN0aW9uEiMKH1BVQkxJQ19ST1VURV9BQ1RJT05fVU5TUEVDSUZJRUQQABIfChtQVUJMSUNfUk9VVEVfQUNUSU9OX0ZPUldBUkQQARIgChxQVUJMSUNfUk9VVEVfQUNUSU9OX1JFRElSRUNUEAIq/QEKHVB1YmxpY1JvdXRlUmVkaXJlY3RUYXJnZXRNb2RlEjEKLVBVQkxJQ19ST1VURV9SRURJUkVDVF9UQVJHRVRfTU9ERV9VTlNQRUNJRklFRBAAEjQKMFBVQkxJQ19ST1VURV9SRURJUkVDVF9UQVJHRVRfTU9ERV9TQU1FX0hPU1RfUEFUSBABEj8KO1BVQkxJQ19ST1VURV9SRURJUkVDVF9UQVJHRVRfTU9ERV9FWFRFUk5BTF9PUklHSU5fS0VFUF9QQVRIEAISMgouUFVCTElDX1JPVVRFX1JFRElSRUNUX1RBUkdFVF9NT0RFX0FCU09MVVRFX1VSTBADKoECChhQdWJsaWNSYXRlTGltaXRBbGdvcml0aG0SKwonUFVCTElDX1JBVEVfTElNSVRfQUxHT1JJVEhNX1VOU1BFQ0lGSUVEEAASLAooUFVCTElDX1JBVEVfTElNSVRfQUxHT1JJVEhNX0ZJWEVEX1dJTkRPVxABEi4KKlBVQkxJQ19SQVRFX0xJTUlUX0FMR09SSVRITV9TTElESU5HX1dJTkRPVxACEiwKKFBVQkxJQ19SQVRFX0xJTUlUX0FMR09SSVRITV9UT0tFTl9CVUNLRVQQAxIsCihQVUJMSUNfUkFURV9MSU1JVF9BTEdPUklUSE1fTEVBS1lfQlVDS0VUEAQqlgMKGFB1YmxpY1JhdGVMaW1pdEtleVNvdXJjZRIsCihQVUJMSUNfUkFURV9MSU1JVF9LRVlfU09VUkNFX1VOU1BFQ0lGSUVEEAASKgomUFVCTElDX1JBVEVfTElNSVRfS0VZX1NPVVJDRV9SRU1PVEVfSVAQARIlCiFQVUJMSUNfUkFURV9MSU1JVF9LRVlfU09VUkNFX0hPU1QQAhInCiNQVUJMSUNfUkFURV9MSU1JVF9LRVlfU09VUkNFX01FVEhPRBADEiUKIVBVQkxJQ19SQVRFX0xJTUlUX0tFWV9TT1VSQ0VfUEFUSBAEEikKJVBVQkxJQ19SQVRFX0xJTUlUX0tFWV9TT1VSQ0VfUFJPVE9DT0wQBRInCiNQVUJMSUNfUkFURV9MSU1JVF9LRVlfU09VUkNFX0hFQURFUhAGEicKI1BVQkxJQ19SQVRFX0xJTUlUX0tFWV9TT1VSQ0VfQ09PS0lFEAcSLAooUFVCTElDX1JBVEVfTElNSVRfS0VZX1NPVVJDRV9RVUVSWV9QQVJBTRAIKrQBCiBQdWJsaWNQb2xpY3lNYXRjaEJvb2xlYW5PcGVyYXRvchI0CjBQVUJMSUNfUE9MSUNZX01BVENIX0JPT0xFQU5fT1BFUkFUT1JfVU5TUEVDSUZJRUQQABIsCihQVUJMSUNfUE9MSUNZX01BVENIX0JPT0xFQU5fT1BFUkFUT1JfQUxMEAESLAooUFVCTElDX1BPTElDWV9NQVRDSF9CT09MRUFOX09QRVJBVE9SX0FOWRACKvkCChZQdWJsaWNQb2xpY3lNYXRjaEZpZWxkEikKJVBVQkxJQ19QT0xJQ1lfTUFUQ0hfRklFTERfVU5TUEVDSUZJRUQQABIkCiBQVUJMSUNfUE9MSUNZX01BVENIX0ZJRUxEX01FVEhPRBABEiYKIlBVQkxJQ19QT0xJQ1lfTUFUQ0hfRklFTERfUFJPVE9DT0wQAhIiCh5QVUJMSUNfUE9MSUNZX01BVENIX0ZJRUxEX0hPU1QQAxIiCh5QVUJMSUNfUE9MSUNZX01BVENIX0ZJRUxEX1BBVEgQBBInCiNQVUJMSUNfUE9MSUNZX01BVENIX0ZJRUxEX1JFTU9URV9JUBAFEiQKIFBVQkxJQ19QT0xJQ1lfTUFUQ0hfRklFTERfSEVBREVSEAYSJAogUFVCTElDX1BPTElDWV9NQVRDSF9GSUVMRF9DT09LSUUQBxIpCiVQVUJMSUNfUE9MSUNZX01BVENIX0ZJRUxEX1FVRVJZX1BBUkFNEAgqqwQKIlB1YmxpY1BvbGljeU1hdGNoQ29uZGl0aW9uT3BlcmF0b3ISNgoyUFVCTElDX1BPTElDWV9NQVRDSF9DT05ESVRJT05fT1BFUkFUT1JfVU5TUEVDSUZJRUQQABIyCi5QVUJMSUNfUE9MSUNZX01BVENIX0NPTkRJVElPTl9PUEVSQVRPUl9QUkVTRU5UEAESMQotUFVCTElDX1BPTElDWV9NQVRDSF9DT05ESVRJT05fT1BFUkFUT1JfRVFVQUxTEAISMQotUFVCTElDX1BPTElDWV9NQVRDSF9DT05ESVRJT05fT1BFUkFUT1JfUFJFRklYEAMSMQotUFVCTElDX1BPTElDWV9NQVRDSF9DT05ESVRJT05fT1BFUkFUT1JfU1VGRklYEAQSMwovUFVCTElDX1BPTElDWV9NQVRDSF9DT05ESVRJT05fT1BFUkFUT1JfQ09OVEFJTlMQBRIyCi5QVUJMSUNfUE9MSUNZX01BVENIX0NPTkRJVElPTl9PUEVSQVRPUl9NQVRDSEVTEAYSLQopUFVCTElDX1BPTElDWV9NQVRDSF9DT05ESVRJT05fT1BFUkFUT1JfSU4QBxIvCitQVUJMSUNfUE9MSUNZX01BVENIX0NPTkRJVElPTl9PUEVSQVRPUl9DSURSEAgSNwozUFVCTElDX1BPTElDWV9NQVRDSF9DT05ESVRJT05fT1BFUkFUT1JfSE9TVF9QQVRURVJOEAkquAEKHlB1YmxpY1RyYWZmaWNTaGFwZXJCdWRnZXRTY29wZRIyCi5QVUJMSUNfVFJBRkZJQ19TSEFQRVJfQlVER0VUX1NDT1BFX1VOU1BFQ0lGSUVEEAASLgoqUFVCTElDX1RSQUZGSUNfU0hBUEVSX0JVREdFVF9TQ09QRV9QRVJfS0VZEAESMgouUFVCTElDX1RSQUZGSUNfU0hBUEVSX0JVREdFVF9TQ09QRV9QRVJfUkVRVUVTVBACKuIBChxQdWJsaWNXYWZDYXB0Y2hhUHJvdmlkZXJUeXBlEjAKLFBVQkxJQ19XQUZfQ0FQVENIQV9QUk9WSURFUl9UWVBFX1VOU1BFQ0lGSUVEEAASLgoqUFVCTElDX1dBRl9DQVBUQ0hBX1BST1ZJREVSX1RZUEVfVFVSTlNUSUxFEAESLQopUFVCTElDX1dBRl9DQVBUQ0hBX1BST1ZJREVSX1RZUEVfSENBUFRDSEEQAhIxCi1QVUJMSUNfV0FGX0NBUFRDSEFfUFJPVklERVJfVFlQRV9SRUNBUFRDSEFfVjIQAyqsAQoTUHVibGljV2FmUnVsZUFjdGlvbhImCiJQVUJMSUNfV0FGX1JVTEVfQUNUSU9OX1VOU1BFQ0lGSUVEEAASIAocUFVCTElDX1dBRl9SVUxFX0FDVElPTl9CTE9DSxABEiIKHlBVQkxJQ19XQUZfUlVMRV9BQ1RJT05fQ0FQVENIQRACEicKI1BVQkxJQ19XQUZfUlVMRV9BQ1RJT05fV0FJVElOR19ST09NEAMqlgEKF1B1YmxpY1dhZkFjdGl2YXRpb25Nb2RlEioKJlBVQkxJQ19XQUZfQUNUSVZBVElPTl9NT0RFX1VOU1BFQ0lGSUVEEAASJQohUFVCTElDX1dBRl9BQ1RJVkFUSU9OX01PREVfQUxXQVlTEAESKAokUFVCTElDX1dBRl9BQ1RJVkFUSU9OX01PREVfQVVUT01BVElDEAIqfgoSUHVibGljQ2FjaGVUdGxNb2RlEiUKIVBVQkxJQ19DQUNIRV9UVExfTU9ERV9VTlNQRUNJRklFRBAAEh8KG1BVQkxJQ19DQUNIRV9UVExfTU9ERV9GSVhFRBABEiAKHFBVQkxJQ19DQUNIRV9UVExfTU9ERV9PUklHSU4QAirSAQoUUHVibGljQ2FjaGVRdWVyeU1vZGUSJwojUFVCTElDX0NBQ0hFX1FVRVJZX01PREVfVU5TUEVDSUZJRUQQABIgChxQVUJMSUNfQ0FDSEVfUVVFUllfTU9ERV9GVUxMEAESIgoeUFVCTElDX0NBQ0hFX1FVRVJZX01PREVfSUdOT1JFEAISJQohUFVCTElDX0NBQ0hFX1FVRVJZX01PREVfQUxMT1dMSVNUEAMSJAogUFVCTElDX0NBQ0hFX1FVRVJZX01PREVfREVOWUxJU1QQBCp9ChBQdWJsaWNDYWNoZVNjb3BlEiIKHlBVQkxJQ19DQUNIRV9TQ09QRV9VTlNQRUNJRklFRBAAEicKI1BVQkxJQ19DQUNIRV9TQ09QRV9TRUxFQ1RFRF9CQUNLRU5EEAESHAoYUFVCTElDX0NBQ0hFX1NDT1BFX1JPVVRFEAIqnQEKGlB1YmxpY1Rsc0NlcnRpZmljYXRlU291cmNlEi0KKVBVQkxJQ19UTFNfQ0VSVElGSUNBVEVfU09VUkNFX1VOU1BFQ0lGSUVEEAASKAokUFVCTElDX1RMU19DRVJUSUZJQ0FURV9TT1VSQ0VfTUFOVUFMEAESJgoiUFVCTElDX1RMU19DRVJUSUZJQ0FURV9TT1VSQ0VfQUNNRRACKsABChdQdWJsaWNBY21lQ2hhbGxlbmdlVHlwZRIqCiZQVUJMSUNfQUNNRV9DSEFMTEVOR0VfVFlQRV9VTlNQRUNJRklFRBAAEiYKIlBVQkxJQ19BQ01FX0NIQUxMRU5HRV9UWVBFX0hUVFBfMDEQARIqCiZQVUJMSUNfQUNNRV9DSEFMTEVOR0VfVFlQRV9UTFNfQUxQTl8wMRACEiUKIVBVQkxJQ19BQ01FX0NIQUxMRU5HRV9UWVBFX0ROU18wMRADKoMBCgxQdWJsaWNBY21lQ2ESHgoaUFVCTElDX0FDTUVfQ0FfVU5TUEVDSUZJRUQQABIqCiZQVUJMSUNfQUNNRV9DQV9MRVRTX0VOQ1JZUFRfUFJPRFVDVElPThABEicKI1BVQkxJQ19BQ01FX0NBX0xFVFNfRU5DUllQVF9TVEFHSU5HEAIqXAoRUHVibGljRG5zUHJvdmlkZXISIwofUFVCTElDX0ROU19QUk9WSURFUl9VTlNQRUNJRklFRBAAEiIKHlBVQkxJQ19ETlNfUFJPVklERVJfQ0xPVURGTEFSRRABKvQBChpQdWJsaWNUbHNDZXJ0aWZpY2F0ZVN0YXR1cxItCilQVUJMSUNfVExTX0NFUlRJRklDQVRFX1NUQVRVU19VTlNQRUNJRklFRBAAEikKJVBVQkxJQ19UTFNfQ0VSVElGSUNBVEVfU1RBVFVTX1BFTkRJTkcQARInCiNQVUJMSUNfVExTX0NFUlRJRklDQVRFX1NUQVRVU19SRUFEWRACEioKJlBVQkxJQ19UTFNfQ0VSVElGSUNBVEVfU1RBVFVTX1JFTkVXSU5HEAMSJwojUFVCTElDX1RMU19DRVJUSUZJQ0FURV9TVEFUVVNfRVJST1IQBCq5AQoRVHJhZmZpY1RyYWNlTGV2ZWwSIwofVFJBRkZJQ19UUkFDRV9MRVZFTF9VTlNQRUNJRklFRBAAEh0KGVRSQUZGSUNfVFJBQ0VfTEVWRUxfQkFTSUMQARIgChxUUkFGRklDX1RSQUNFX0xFVkVMX0RFVEFJTEVEEAISHwobVFJBRkZJQ19UUkFDRV9MRVZFTF9IRUFERVJTEAMSHQoZVFJBRkZJQ19UUkFDRV9MRVZFTF9ERUJVRxAEKtUGChFUcmFmZmljVHJhY2VTdGFnZRIjCh9UUkFGRklDX1RSQUNFX1NUQUdFX1VOU1BFQ0lGSUVEEAASIAocVFJBRkZJQ19UUkFDRV9TVEFHRV9SRUNFSVZFRBABEiYKIlRSQUZGSUNfVFJBQ0VfU1RBR0VfUk9VVEVfUkVTT0xWRUQQAhIoCiRUUkFGRklDX1RSQUNFX1NUQUdFX0JBQ0tFTkRfU0VMRUNURUQQAxImCiJUUkFGRklDX1RSQUNFX1NUQUdFX0FHRU5UX1NFTEVDVEVEEAQSKAokVFJBRkZJQ19UUkFDRV9TVEFHRV9VUFNUUkVBTV9TVEFSVEVEEAUSKgomVFJBRkZJQ19UUkFDRV9TVEFHRV9VUFNUUkVBTV9SRVNQT05ERUQQBhIlCiFUUkFGRklDX1RSQUNFX1NUQUdFX1JFU1BPTlNFX1NFTlQQBxIeChpUUkFGRklDX1RSQUNFX1NUQUdFX0ZBSUxFRBAIEiQKIFRSQUZGSUNfVFJBQ0VfU1RBR0VfUkFURV9MSU1JVEVEEAkSLworVFJBRkZJQ19UUkFDRV9TVEFHRV9UUkFGRklDX1NIQVBFUl9TRUxFQ1RFRBAKEiUKIVRSQUZGSUNfVFJBQ0VfU1RBR0VfV0FGX0VWQUxVQVRFRBALEiMKH1RSQUZGSUNfVFJBQ0VfU1RBR0VfV0FGX0JMT0NLRUQQDBIuCipUUkFGRklDX1RSQUNFX1NUQUdFX1dBRl9DQVBUQ0hBX0NIQUxMRU5HRUQQDRIsCihUUkFGRklDX1RSQUNFX1NUQUdFX1dBRl9DQVBUQ0hBX1ZFUklGSUVEEA4SKAokVFJBRkZJQ19UUkFDRV9TVEFHRV9XQUZfV0FJVElOR19ST09NEA8SJAogVFJBRkZJQ19UUkFDRV9TVEFHRV9DQUNIRV9MT09LVVAQEBIhCh1UUkFGRklDX1RSQUNFX1NUQUdFX0NBQ0hFX0hJVBAREiIKHlRSQUZGSUNfVFJBQ0VfU1RBR0VfQ0FDSEVfTUlTUxASEiQKIFRSQUZGSUNfVFJBQ0VfU1RBR0VfQ0FDSEVfQllQQVNTEBMSJAogVFJBRkZJQ19UUkFDRV9TVEFHRV9DQUNIRV9TVE9SRUQQFCqAAQoURW52aXJvbm1lbnRUcmFuc3BvcnQSJQohRU5WSVJPTk1FTlRfVFJBTlNQT1JUX1VOU1BFQ0lGSUVEEAASIAocRU5WSVJPTk1FTlRfVFJBTlNQT1JUX0RJUkVDVBABEh8KG0VOVklST05NRU5UX1RSQU5TUE9SVF9BR0VOVBACKtYBChVFbnZpcm9ubWVudFRydXN0U3RhdGUSJwojRU5WSVJPTk1FTlRfVFJVU1RfU1RBVEVfVU5TUEVDSUZJRUQQABIlCiFFTlZJUk9OTUVOVF9UUlVTVF9TVEFURV9VTlRSVVNURUQQARIjCh9FTlZJUk9OTUVOVF9UUlVTVF9TVEFURV9UUlVTVEVEEAISIwofRU5WSVJPTk1FTlRfVFJVU1RfU1RBVEVfQ0hBTkdFRBADEiMKH0VOVklST05NRU5UX1RSVVNUX1NUQVRFX0VYUElSRUQQBCrfAgoXRGFzaGJvYXJkUHJveHlEaW1lbnNpb24SKQolREFTSEJPQVJEX1BST1hZX0RJTUVOU0lPTl9VTlNQRUNJRklFRBAAEiYKIkRBU0hCT0FSRF9QUk9YWV9ESU1FTlNJT05fTElTVEVORVIQARIlCiFEQVNIQk9BUkRfUFJPWFlfRElNRU5TSU9OX0JBQ0tFTkQQAhIjCh9EQVNIQk9BUkRfUFJPWFlfRElNRU5TSU9OX1JPVVRFEAMSIwofREFTSEJPQVJEX1BST1hZX0RJTUVOU0lPTl9BR0VOVBAEEigKJERBU0hCT0FSRF9QUk9YWV9ESU1FTlNJT05fRVJST1JfS0lORBAFEioKJkRBU0hCT0FSRF9QUk9YWV9ESU1FTlNJT05fU1RBVFVTX0NMQVNTEAYSKgomREFTSEJPQVJEX1BST1hZX0RJTUVOU0lPTl9ST1VURV9UQVJHRVQQBzLQOwoWQWdlbnRNYW5hZ2VtZW50U2VydmljZRJSCgtSZXBvcnRTdGF0cxIfLnAycHN0cmVhbS52MS5BZ2VudFN0YXRzUmVxdWVzdBogLnAycHN0cmVhbS52MS5BZ2VudFN0YXRzUmVzcG9uc2UiABJOCglHZXRTdGF0dXMSHi5wMnBzdHJlYW0udjEuR2V0U3RhdHVzUmVxdWVzdBofLnAycHN0cmVhbS52MS5HZXRTdGF0dXNSZXNwb25zZSIAElcKDEdldERhc2hib2FyZBIhLnAycHN0cmVhbS52MS5HZXREYXNoYm9hcmRSZXF1ZXN0GiIucDJwc3RyZWFtLnYxLkdldERhc2hib2FyZFJlc3BvbnNlIgASeAoXR2V0VHJhZmZpY1RyYWNlU2V0dGluZ3MSLC5wMnBzdHJlYW0udjEuR2V0VHJhZmZpY1RyYWNlU2V0dGluZ3NSZXF1ZXN0Gi0ucDJwc3RyZWFtLnYxLkdldFRyYWZmaWNUcmFjZVNldHRpbmdzUmVzcG9uc2UiABJ4ChdTZXRUcmFmZmljVHJhY2VTZXR0aW5ncxIsLnAycHN0cmVhbS52MS5TZXRUcmFmZmljVHJhY2VTZXR0aW5nc1JlcXVlc3QaLS5wMnBzdHJlYW0udjEuU2V0VHJhZmZpY1RyYWNlU2V0dGluZ3NSZXNwb25zZSIAEn0KGFN0cmVhbVRyYWZmaWNUcmFjZUV2ZW50cxItLnAycHN0cmVhbS52MS5TdHJlYW1UcmFmZmljVHJhY2VFdmVudHNSZXF1ZXN0Gi4ucDJwc3RyZWFtLnYxLlN0cmVhbVRyYWZmaWNUcmFjZUV2ZW50c1Jlc3BvbnNlIgAwARJaCg1HZXRTZXR1cFN0YXRlEiIucDJwc3RyZWFtLnYxLkdldFNldHVwU3RhdGVSZXF1ZXN0GiMucDJwc3RyZWFtLnYxLkdldFNldHVwU3RhdGVSZXNwb25zZSIAElEKClNldHVwQWRtaW4SHy5wMnBzdHJlYW0udjEuU2V0dXBBZG1pblJlcXVlc3QaIC5wMnBzdHJlYW0udjEuU2V0dXBBZG1pblJlc3BvbnNlIgASQgoFTG9naW4SGi5wMnBzdHJlYW0udjEuTG9naW5SZXF1ZXN0GhsucDJwc3RyZWFtLnYxLkxvZ2luUmVzcG9uc2UiABJFCgZMb2dvdXQSGy5wMnBzdHJlYW0udjEuTG9nb3V0UmVxdWVzdBocLnAycHN0cmVhbS52MS5Mb2dvdXRSZXNwb25zZSIAEl0KDkdldEN1cnJlbnRVc2VyEiMucDJwc3RyZWFtLnYxLkdldEN1cnJlbnRVc2VyUmVxdWVzdBokLnAycHN0cmVhbS52MS5HZXRDdXJyZW50VXNlclJlc3BvbnNlIgASUQoKU3RhcnRQcm94eRIfLnAycHN0cmVhbS52MS5TdGFydFByb3h5UmVxdWVzdBogLnAycHN0cmVhbS52MS5TdGFydFByb3h5UmVzcG9uc2UiABJOCglTdG9wUHJveHkSHi5wMnBzdHJlYW0udjEuU3RvcFByb3h5UmVxdWVzdBofLnAycHN0cmVhbS52MS5TdG9wUHJveHlSZXNwb25zZSIAEm8KFEdldFB1YmxpY1Byb3h5Q29uZmlnEikucDJwc3RyZWFtLnYxLkdldFB1YmxpY1Byb3h5Q29uZmlnUmVxdWVzdBoqLnAycHN0cmVhbS52MS5HZXRQdWJsaWNQcm94eUNvbmZpZ1Jlc3BvbnNlIgAShwEKHENyZWF0ZVB1YmxpY1Jlc3BvbnNlVGVtcGxhdGUSMS5wMnBzdHJlYW0udjEuQ3JlYXRlUHVibGljUmVzcG9uc2VUZW1wbGF0ZVJlcXVlc3QaMi5wMnBzdHJlYW0udjEuQ3JlYXRlUHVibGljUmVzcG9uc2VUZW1wbGF0ZVJlc3BvbnNlIgAShwEKHFVwZGF0ZVB1YmxpY1Jlc3BvbnNlVGVtcGxhdGUSMS5wMnBzdHJlYW0udjEuVXBkYXRlUHVibGljUmVzcG9uc2VUZW1wbGF0ZVJlcXVlc3QaMi5wMnBzdHJlYW0udjEuVXBkYXRlUHVibGljUmVzcG9uc2VUZW1wbGF0ZVJlc3BvbnNlIgAShwEKHERlbGV0ZVB1YmxpY1Jlc3BvbnNlVGVtcGxhdGUSMS5wMnBzdHJlYW0udjEuRGVsZXRlUHVibGljUmVzcG9uc2VUZW1wbGF0ZVJlcXVlc3QaMi5wMnBzdHJlYW0udjEuRGVsZXRlUHVibGljUmVzcG9uc2VUZW1wbGF0ZVJlc3BvbnNlIgASlgEKIUxpc3RQdWJsaWNSb3V0ZVRhcmdldEhlYWx0aFRyYWNlcxI2LnAycHN0cmVhbS52MS5MaXN0UHVibGljUm91dGVUYXJnZXRIZWFsdGhUcmFjZXNSZXF1ZXN0GjcucDJwc3RyZWFtLnYxLkxpc3RQdWJsaWNSb3V0ZVRhcmdldEhlYWx0aFRyYWNlc1Jlc3BvbnNlIgASVAoLQ3JlYXRlQWdlbnQSIC5wMnBzdHJlYW0udjEuQ3JlYXRlQWdlbnRSZXF1ZXN0GiEucDJwc3RyZWFtLnYxLkNyZWF0ZUFnZW50UmVzcG9uc2UiABJUCgtVcGRhdGVBZ2VudBIgLnAycHN0cmVhbS52MS5VcGRhdGVBZ2VudFJlcXVlc3QaIS5wMnBzdHJlYW0udjEuVXBkYXRlQWdlbnRSZXNwb25zZSIAElQKC0RlbGV0ZUFnZW50EiAucDJwc3RyZWFtLnYxLkRlbGV0ZUFnZW50UmVxdWVzdBohLnAycHN0cmVhbS52MS5EZWxldGVBZ2VudFJlc3BvbnNlIgASYwoQUm90YXRlQWdlbnRUb2tlbhIlLnAycHN0cmVhbS52MS5Sb3RhdGVBZ2VudFRva2VuUmVxdWVzdBomLnAycHN0cmVhbS52MS5Sb3RhdGVBZ2VudFRva2VuUmVzcG9uc2UiABKEAQobQ3JlYXRlTWFuYWdlbWVudEFjY2Vzc1Rva2VuEjAucDJwc3RyZWFtLnYxLkNyZWF0ZU1hbmFnZW1lbnRBY2Nlc3NUb2tlblJlcXVlc3QaMS5wMnBzdHJlYW0udjEuQ3JlYXRlTWFuYWdlbWVudEFjY2Vzc1Rva2VuUmVzcG9uc2UiABKBAQoaTGlzdE1hbmFnZW1lbnRBY2Nlc3NUb2tlbnMSLy5wMnBzdHJlYW0udjEuTGlzdE1hbmFnZW1lbnRBY2Nlc3NUb2tlbnNSZXF1ZXN0GjAucDJwc3RyZWFtLnYxLkxpc3RNYW5hZ2VtZW50QWNjZXNzVG9rZW5zUmVzcG9uc2UiABKEAQobRGVsZXRlTWFuYWdlbWVudEFjY2Vzc1Rva2VuEjAucDJwc3RyZWFtLnYxLkRlbGV0ZU1hbmFnZW1lbnRBY2Nlc3NUb2tlblJlcXVlc3QaMS5wMnBzdHJlYW0udjEuRGVsZXRlTWFuYWdlbWVudEFjY2Vzc1Rva2VuUmVzcG9uc2UiABJjChBMaXN0RW52aXJvbm1lbnRzEiUucDJwc3RyZWFtLnYxLkxpc3RFbnZpcm9ubWVudHNSZXF1ZXN0GiYucDJwc3RyZWFtLnYxLkxpc3RFbnZpcm9ubWVudHNSZXNwb25zZSIAEmYKEUNyZWF0ZUVudmlyb25tZW50EiYucDJwc3RyZWFtLnYxLkNyZWF0ZUVudmlyb25tZW50UmVxdWVzdBonLnAycHN0cmVhbS52MS5DcmVhdGVFbnZpcm9ubWVudFJlc3BvbnNlIgASZgoRVXBkYXRlRW52aXJvbm1lbnQSJi5wMnBzdHJlYW0udjEuVXBkYXRlRW52aXJvbm1lbnRSZXF1ZXN0GicucDJwc3RyZWFtLnYxLlVwZGF0ZUVudmlyb25tZW50UmVzcG9uc2UiABJmChFEZWxldGVFbnZpcm9ubWVudBImLnAycHN0cmVhbS52MS5EZWxldGVFbnZpcm9ubWVudFJlcXVlc3QaJy5wMnBzdHJlYW0udjEuRGVsZXRlRW52aXJvbm1lbnRSZXNwb25zZSIAEo0BCh5EaXNjb3ZlckVudmlyb25tZW50Q2VydGlmaWNhdGUSMy5wMnBzdHJlYW0udjEuRGlzY292ZXJFbnZpcm9ubWVudENlcnRpZmljYXRlUmVxdWVzdBo0LnAycHN0cmVhbS52MS5EaXNjb3ZlckVudmlyb25tZW50Q2VydGlmaWNhdGVSZXNwb25zZSIAEoQBChtUcnVzdEVudmlyb25tZW50Q2VydGlmaWNhdGUSMC5wMnBzdHJlYW0udjEuVHJ1c3RFbnZpcm9ubWVudENlcnRpZmljYXRlUmVxdWVzdBoxLnAycHN0cmVhbS52MS5UcnVzdEVudmlyb25tZW50Q2VydGlmaWNhdGVSZXNwb25zZSIAEmAKD1Rlc3RFbnZpcm9ubWVudBIkLnAycHN0cmVhbS52MS5UZXN0RW52aXJvbm1lbnRSZXF1ZXN0GiUucDJwc3RyZWFtLnYxLlRlc3RFbnZpcm9ubWVudFJlc3BvbnNlIgASbwoUQ3JlYXRlUHVibGljTGlzdGVuZXISKS5wMnBzdHJlYW0udjEuQ3JlYXRlUHVibGljTGlzdGVuZXJSZXF1ZXN0GioucDJwc3RyZWFtLnYxLkNyZWF0ZVB1YmxpY0xpc3RlbmVyUmVzcG9uc2UiABJvChRVcGRhdGVQdWJsaWNMaXN0ZW5lchIpLnAycHN0cmVhbS52MS5VcGRhdGVQdWJsaWNMaXN0ZW5lclJlcXVlc3QaKi5wMnBzdHJlYW0udjEuVXBkYXRlUHVibGljTGlzdGVuZXJSZXNwb25zZSIAEm8KFERlbGV0ZVB1YmxpY0xpc3RlbmVyEikucDJwc3RyZWFtLnYxLkRlbGV0ZVB1YmxpY0xpc3RlbmVyUmVxdWVzdBoqLnAycHN0cmVhbS52MS5EZWxldGVQdWJsaWNMaXN0ZW5lclJlc3BvbnNlIgASbwoURW5hYmxlUHVibGljTGlzdGVuZXISKS5wMnBzdHJlYW0udjEuRW5hYmxlUHVibGljTGlzdGVuZXJSZXF1ZXN0GioucDJwc3RyZWFtLnYxLkVuYWJsZVB1YmxpY0xpc3RlbmVyUmVzcG9uc2UiABJyChVEaXNhYmxlUHVibGljTGlzdGVuZXISKi5wMnBzdHJlYW0udjEuRGlzYWJsZVB1YmxpY0xpc3RlbmVyUmVxdWVzdBorLnAycHN0cmVhbS52MS5EaXNhYmxlUHVibGljTGlzdGVuZXJSZXNwb25zZSIAEmwKE1N0YXJ0UHVibGljTGlzdGVuZXISKC5wMnBzdHJlYW0udjEuU3RhcnRQdWJsaWNMaXN0ZW5lclJlcXVlc3QaKS5wMnBzdHJlYW0udjEuU3RhcnRQdWJsaWNMaXN0ZW5lclJlc3BvbnNlIgASaQoSU3RvcFB1YmxpY0xpc3RlbmVyEicucDJwc3RyZWFtLnYxLlN0b3BQdWJsaWNMaXN0ZW5lclJlcXVlc3QaKC5wMnBzdHJlYW0udjEuU3RvcFB1YmxpY0xpc3RlbmVyUmVzcG9uc2UiABJmChFDcmVhdGVQdWJsaWNSb3V0ZRImLnAycHN0cmVhbS52MS5DcmVhdGVQdWJsaWNSb3V0ZVJlcXVlc3QaJy5wMnBzdHJlYW0udjEuQ3JlYXRlUHVibGljUm91dGVSZXNwb25zZSIAEmYKEVVwZGF0ZVB1YmxpY1JvdXRlEiYucDJwc3RyZWFtLnYxLlVwZGF0ZVB1YmxpY1JvdXRlUmVxdWVzdBonLnAycHN0cmVhbS52MS5VcGRhdGVQdWJsaWNSb3V0ZVJlc3BvbnNlIgASZgoRRGVsZXRlUHVibGljUm91dGUSJi5wMnBzdHJlYW0udjEuRGVsZXRlUHVibGljUm91dGVSZXF1ZXN0GicucDJwc3RyZWFtLnYxLkRlbGV0ZVB1YmxpY1JvdXRlUmVzcG9uc2UiABKHAQocQ3JlYXRlUHVibGljVGxzRG5zQ3JlZGVudGlhbBIxLnAycHN0cmVhbS52MS5DcmVhdGVQdWJsaWNUbHNEbnNDcmVkZW50aWFsUmVxdWVzdBoyLnAycHN0cmVhbS52MS5DcmVhdGVQdWJsaWNUbHNEbnNDcmVkZW50aWFsUmVzcG9uc2UiABKHAQocVXBkYXRlUHVibGljVGxzRG5zQ3JlZGVudGlhbBIxLnAycHN0cmVhbS52MS5VcGRhdGVQdWJsaWNUbHNEbnNDcmVkZW50aWFsUmVxdWVzdBoyLnAycHN0cmVhbS52MS5VcGRhdGVQdWJsaWNUbHNEbnNDcmVkZW50aWFsUmVzcG9uc2UiABKHAQocRGVsZXRlUHVibGljVGxzRG5zQ3JlZGVudGlhbBIxLnAycHN0cmVhbS52MS5EZWxldGVQdWJsaWNUbHNEbnNDcmVkZW50aWFsUmVxdWVzdBoyLnAycHN0cmVhbS52MS5EZWxldGVQdWJsaWNUbHNEbnNDcmVkZW50aWFsUmVzcG9uc2UiABKBAQoaQ3JlYXRlUHVibGljVGxzQ2VydGlmaWNhdGUSLy5wMnBzdHJlYW0udjEuQ3JlYXRlUHVibGljVGxzQ2VydGlmaWNhdGVSZXF1ZXN0GjAucDJwc3RyZWFtLnYxLkNyZWF0ZVB1YmxpY1Rsc0NlcnRpZmljYXRlUmVzcG9uc2UiABKBAQoaVXBkYXRlUHVibGljVGxzQ2VydGlmaWNhdGUSLy5wMnBzdHJlYW0udjEuVXBkYXRlUHVibGljVGxzQ2VydGlmaWNhdGVSZXF1ZXN0GjAucDJwc3RyZWFtLnYxLlVwZGF0ZVB1YmxpY1Rsc0NlcnRpZmljYXRlUmVzcG9uc2UiABKBAQoaRGVsZXRlUHVibGljVGxzQ2VydGlmaWNhdGUSLy5wMnBzdHJlYW0udjEuRGVsZXRlUHVibGljVGxzQ2VydGlmaWNhdGVSZXF1ZXN0GjAucDJwc3RyZWFtLnYxLkRlbGV0ZVB1YmxpY1Rsc0NlcnRpZmljYXRlUmVzcG9uc2UiABJ+ChlSZW5ld1B1YmxpY1Rsc0NlcnRpZmljYXRlEi4ucDJwc3RyZWFtLnYxLlJlbmV3UHVibGljVGxzQ2VydGlmaWNhdGVSZXF1ZXN0Gi8ucDJwc3RyZWFtLnYxLlJlbmV3UHVibGljVGxzQ2VydGlmaWNhdGVSZXNwb25zZSIAEn4KGUNyZWF0ZVB1YmxpY1JhdGVMaW1pdFJ1bGUSLi5wMnBzdHJlYW0udjEuQ3JlYXRlUHVibGljUmF0ZUxpbWl0UnVsZVJlcXVlc3QaLy5wMnBzdHJlYW0udjEuQ3JlYXRlUHVibGljUmF0ZUxpbWl0UnVsZVJlc3BvbnNlIgASfgoZVXBkYXRlUHVibGljUmF0ZUxpbWl0UnVsZRIuLnAycHN0cmVhbS52MS5VcGRhdGVQdWJsaWNSYXRlTGltaXRSdWxlUmVxdWVzdBovLnAycHN0cmVhbS52MS5VcGRhdGVQdWJsaWNSYXRlTGltaXRSdWxlUmVzcG9uc2UiABJ+ChlEZWxldGVQdWJsaWNSYXRlTGltaXRSdWxlEi4ucDJwc3RyZWFtLnYxLkRlbGV0ZVB1YmxpY1JhdGVMaW1pdFJ1bGVSZXF1ZXN0Gi8ucDJwc3RyZWFtLnYxLkRlbGV0ZVB1YmxpY1JhdGVMaW1pdFJ1bGVSZXNwb25zZSIAEooBCh1DcmVhdGVQdWJsaWNUcmFmZmljU2hhcGVyUnVsZRIyLnAycHN0cmVhbS52MS5DcmVhdGVQdWJsaWNUcmFmZmljU2hhcGVyUnVsZVJlcXVlc3QaMy5wMnBzdHJlYW0udjEuQ3JlYXRlUHVibGljVHJhZmZpY1NoYXBlclJ1bGVSZXNwb25zZSIAEooBCh1VcGRhdGVQdWJsaWNUcmFmZmljU2hhcGVyUnVsZRIyLnAycHN0cmVhbS52MS5VcGRhdGVQdWJsaWNUcmFmZmljU2hhcGVyUnVsZVJlcXVlc3QaMy5wMnBzdHJlYW0udjEuVXBkYXRlUHVibGljVHJhZmZpY1NoYXBlclJ1bGVSZXNwb25zZSIAEooBCh1EZWxldGVQdWJsaWNUcmFmZmljU2hhcGVyUnVsZRIyLnAycHN0cmVhbS52MS5EZWxldGVQdWJsaWNUcmFmZmljU2hhcGVyUnVsZVJlcXVlc3QaMy5wMnBzdHJlYW0udjEuRGVsZXRlUHVibGljVHJhZmZpY1NoYXBlclJ1bGVSZXNwb25zZSIAEo0BCh5DcmVhdGVQdWJsaWNXYWZDYXB0Y2hhUHJvdmlkZXISMy5wMnBzdHJlYW0udjEuQ3JlYXRlUHVibGljV2FmQ2FwdGNoYVByb3ZpZGVyUmVxdWVzdBo0LnAycHN0cmVhbS52MS5DcmVhdGVQdWJsaWNXYWZDYXB0Y2hhUHJvdmlkZXJSZXNwb25zZSIAEo0BCh5VcGRhdGVQdWJsaWNXYWZDYXB0Y2hhUHJvdmlkZXISMy5wMnBzdHJlYW0udjEuVXBkYXRlUHVibGljV2FmQ2FwdGNoYVByb3ZpZGVyUmVxdWVzdBo0LnAycHN0cmVhbS52MS5VcGRhdGVQdWJsaWNXYWZDYXB0Y2hhUHJvdmlkZXJSZXNwb25zZSIAEo0BCh5EZWxldGVQdWJsaWNXYWZDYXB0Y2hhUHJvdmlkZXISMy5wMnBzdHJlYW0udjEuRGVsZXRlUHVibGljV2FmQ2FwdGNoYVByb3ZpZGVyUmVxdWVzdBo0LnAycHN0cmVhbS52MS5EZWxldGVQdWJsaWNXYWZDYXB0Y2hhUHJvdmlkZXJSZXNwb25zZSIAEmwKE0NyZWF0ZVB1YmxpY1dhZlJ1bGUSKC5wMnBzdHJlYW0udjEuQ3JlYXRlUHVibGljV2FmUnVsZVJlcXVlc3QaKS5wMnBzdHJlYW0udjEuQ3JlYXRlUHVibGljV2FmUnVsZVJlc3BvbnNlIgASbAoTVXBkYXRlUHVibGljV2FmUnVsZRIoLnAycHN0cmVhbS52MS5VcGRhdGVQdWJsaWNXYWZSdWxlUmVxdWVzdBopLnAycHN0cmVhbS52MS5VcGRhdGVQdWJsaWNXYWZSdWxlUmVzcG9uc2UiABJsChNEZWxldGVQdWJsaWNXYWZSdWxlEigucDJwc3RyZWFtLnYxLkRlbGV0ZVB1YmxpY1dhZlJ1bGVSZXF1ZXN0GikucDJwc3RyZWFtLnYxLkRlbGV0ZVB1YmxpY1dhZlJ1bGVSZXNwb25zZSIAEnIKFUNyZWF0ZVB1YmxpY0NhY2hlUnVsZRIqLnAycHN0cmVhbS52MS5DcmVhdGVQdWJsaWNDYWNoZVJ1bGVSZXF1ZXN0GisucDJwc3RyZWFtLnYxLkNyZWF0ZVB1YmxpY0NhY2hlUnVsZVJlc3BvbnNlIgAScgoVVXBkYXRlUHVibGljQ2FjaGVSdWxlEioucDJwc3RyZWFtLnYxLlVwZGF0ZVB1YmxpY0NhY2hlUnVsZVJlcXVlc3QaKy5wMnBzdHJlYW0udjEuVXBkYXRlUHVibGljQ2FjaGVSdWxlUmVzcG9uc2UiABJyChVEZWxldGVQdWJsaWNDYWNoZVJ1bGUSKi5wMnBzdHJlYW0udjEuRGVsZXRlUHVibGljQ2FjaGVSdWxlUmVxdWVzdBorLnAycHN0cmVhbS52MS5EZWxldGVQdWJsaWNDYWNoZVJ1bGVSZXNwb25zZSIAEn4KGVVwZGF0ZVB1YmxpY0NhY2hlU2V0dGluZ3MSLi5wMnBzdHJlYW0udjEuVXBkYXRlUHVibGljQ2FjaGVTZXR0aW5nc1JlcXVlc3QaLy5wMnBzdHJlYW0udjEuVXBkYXRlUHVibGljQ2FjaGVTZXR0aW5nc1Jlc3BvbnNlIgASYwoQUHVyZ2VQdWJsaWNDYWNoZRIlLnAycHN0cmVhbS52MS5QdXJnZVB1YmxpY0NhY2hlUmVxdWVzdBomLnAycHN0cmVhbS52MS5QdXJnZVB1YmxpY0NhY2hlUmVzcG9uc2UiAEKiAQoQY29tLnAycHN0cmVhbS52MUIPTWFuYWdlbWVudFByb3RvUAFaLHAycHN0cmVhbS9nZW4vcHJvdG8vcDJwc3RyZWFtL3YxO3AycHN0cmVhbXYxogIDUFhYqgIMUDJwc3RyZWFtLlYxygIMUDJwc3RyZWFtXFYx4gIYUDJwc3RyZWFtXFYxXEdQQk1ldGFkYXRh6gINUDJwc3RyZWFtOjpWMWIGcHJvdG8z"); + fileDesc("CiNwcm90by9wMnBzdHJlYW0vdjEvbWFuYWdlbWVudC5wcm90bxIMcDJwc3RyZWFtLnYxIpkCChFBZ2VudFN0YXRzUmVxdWVzdBIVCg1tZW1vcnlfc3lzX21iGAEgASgDEhUKDW51bV9nb3JvdXRpbmUYAiABKAMSEwoLcmVxX3N1Y2Nlc3MYAyABKAMSGAoQcmVxX2NsaWVudF9lcnJvchgEIAEoAxIYChByZXFfc2VydmVyX2Vycm9yGAUgASgDEhoKEnJlcV9pbnRlcm5hbF9lcnJvchgGIAEoAxIWCg5ieXRlc19yZWNlaXZlZBgHIAEoBBISCgpieXRlc19zZW50GAggASgEEhcKD2FjdGl2ZV9yZXF1ZXN0cxgJIAEoBRIXCg9hZ2VudF9wdWJsaWNfaWQYCiABKAkSEwoLY3B1X3BlcmNlbnQYCyABKAEiFAoSQWdlbnRTdGF0c1Jlc3BvbnNlIkoKBFVzZXISCgoCaWQYASABKAMSEAoIdXNlcm5hbWUYAiABKAkSJAoEcm9sZRgDIAEoDjIWLnAycHN0cmVhbS52MS5Vc2VyUm9sZSISChBHZXRTdGF0dXNSZXF1ZXN0IqICChJBZ2VudFN0YXRzU25hcHNob3QSFQoNbWVtb3J5X3N5c19tYhgBIAEoAxIVCg1udW1fZ29yb3V0aW5lGAIgASgDEhMKC3JlcV9zdWNjZXNzGAMgASgDEhgKEHJlcV9jbGllbnRfZXJyb3IYBCABKAMSGAoQcmVxX3NlcnZlcl9lcnJvchgFIAEoAxIaChJyZXFfaW50ZXJuYWxfZXJyb3IYBiABKAMSFgoOYnl0ZXNfcmVjZWl2ZWQYByABKAQSEgoKYnl0ZXNfc2VudBgIIAEoBBIXCg9hY3RpdmVfcmVxdWVzdHMYCSABKAUSHwoXcmVwb3J0ZWRfYXRfdW5peF9taWxsaXMYCiABKAMSEwoLY3B1X3BlcmNlbnQYCyABKAEi2gEKEUdldFN0YXR1c1Jlc3BvbnNlEhUKDXByb3h5X3J1bm5pbmcYASABKAgSGAoQcHJveHlfbGFzdF9lcnJvchgCIAEoCRIXCg9hZ2VudF9jb25uZWN0ZWQYAyABKAgSPAoSbGF0ZXN0X2FnZW50X3N0YXRzGAUgASgLMiAucDJwc3RyZWFtLnYxLkFnZW50U3RhdHNTbmFwc2hvdBIoCgVwcm94eRgGIAEoCzIZLnAycHN0cmVhbS52MS5Qcm94eVN0YXR1c0oECAQQBVINdGFyZ2V0X29yaWdpbiLBAQoLUHJveHlTdGF0dXMSJwoFc3RhdGUYASABKA4yGC5wMnBzdHJlYW0udjEuUHJveHlTdGF0ZRISCgpsYXN0X2Vycm9yGAIgASgJEh4KFnN0YXJ0ZWRfYXRfdW5peF9taWxsaXMYAyABKAMSHgoWc3RvcHBlZF9hdF91bml4X21pbGxpcxgEIAEoAxI1CglsaXN0ZW5lcnMYBSADKAsyIi5wMnBzdHJlYW0udjEuUHVibGljTGlzdGVuZXJTdGF0dXMiKwoMUHVibGljSGVhZGVyEgwKBG5hbWUYASABKAkSDQoFdmFsdWUYAiABKAkilQEKH1B1YmxpY1JvdXRlVGFyZ2V0VXBzdHJlYW1IZWFkZXISCgoCaWQYASABKAMSEQoJdGFyZ2V0X2lkGAIgASgDEgwKBG5hbWUYAyABKAkSDQoFdmFsdWUYBCABKAkSEQoJc2Vuc2l0aXZlGAUgASgIEhEKCXZhbHVlX3NldBgGIAEoCBIQCghwb3NpdGlvbhgHIAEoAyJnChpQdWJsaWNSb3V0ZVRhcmdldEJhc2ljQXV0aBIPCgdlbmFibGVkGAEgASgIEhAKCHVzZXJuYW1lGAIgASgJEhAKCHBhc3N3b3JkGAMgASgJEhQKDHBhc3N3b3JkX3NldBgEIAEoCCKTAwocUHVibGljUm91dGVUYXJnZXRIZWFsdGhDaGVjaxIPCgdlbmFibGVkGAEgASgIEg4KBm1ldGhvZBgCIAEoCRIMCgRwYXRoGAMgASgJEhcKD2ludGVydmFsX21pbGxpcxgEIAEoAxIWCg50aW1lb3V0X21pbGxpcxgFIAEoAxIZChFoZWFsdGh5X3RocmVzaG9sZBgGIAEoAxIbChN1bmhlYWx0aHlfdGhyZXNob2xkGAcgASgDEhsKE2V4cGVjdGVkX3N0YXR1c19taW4YCCABKAMSGwoTZXhwZWN0ZWRfc3RhdHVzX21heBgJIAEoAxI7CgZzdGF0dXMYCiABKA4yKy5wMnBzdHJlYW0udjEuUHVibGljUm91dGVUYXJnZXRIZWFsdGhTdGF0dXMSIwobbGFzdF9jaGVja2VkX2F0X3VuaXhfbWlsbGlzGAsgASgDEhIKCmxhc3RfZXJyb3IYDCABKAkSKwojcGFzc2l2ZV91bmhlYWx0aHlfdW50aWxfdW5peF9taWxsaXMYDSABKAMigAIKHFB1YmxpY1JvdXRlVGFyZ2V0QWdlbnRIZWFsdGgSOwoGc3RhdHVzGAEgASgOMisucDJwc3RyZWFtLnYxLlB1YmxpY1JvdXRlVGFyZ2V0SGVhbHRoU3RhdHVzEhEKCWNvbm5lY3RlZBgCIAEoCBIRCglhdmFpbGFibGUYAyABKAgSIwobbGFzdF9jaGVja2VkX2F0X3VuaXhfbWlsbGlzGAQgASgDEhIKCmxhc3RfZXJyb3IYBSABKAkSKwojcGFzc2l2ZV91bmhlYWx0aHlfdW50aWxfdW5peF9taWxsaXMYBiABKAMSFwoPYWN0aXZlX3JlcXVlc3RzGAcgASgDIpoDCgVBZ2VudBIKCgJpZBgBIAEoAxIRCglwdWJsaWNfaWQYAiABKAkSDAoEbmFtZRgDIAEoCRIPCgdlbmFibGVkGAQgASgIEhEKCWNvbm5lY3RlZBgFIAEoCBIXCg9hY3RpdmVfcmVxdWVzdHMYBiABKAMSHgoWY3JlYXRlZF9hdF91bml4X21pbGxpcxgHIAEoAxIeChZ1cGRhdGVkX2F0X3VuaXhfbWlsbGlzGAggASgDEiUKHWxhc3RfY29ubmVjdGVkX2F0X3VuaXhfbWlsbGlzGAkgASgDEigKIGxhc3RfZGlzY29ubmVjdGVkX2F0X3VuaXhfbWlsbGlzGAogASgDEjYKDGxhdGVzdF9zdGF0cxgLIAEoCzIgLnAycHN0cmVhbS52MS5BZ2VudFN0YXRzU25hcHNob3QSLwoGbGFiZWxzGAwgAygLMh8ucDJwc3RyZWFtLnYxLkFnZW50LkxhYmVsc0VudHJ5Gi0KC0xhYmVsc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEikwEKE1B1YmxpY0FnZW50U2VsZWN0b3ISSAoMbWF0Y2hfbGFiZWxzGAEgAygLMjIucDJwc3RyZWFtLnYxLlB1YmxpY0FnZW50U2VsZWN0b3IuTWF0Y2hMYWJlbHNFbnRyeRoyChBNYXRjaExhYmVsc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEijQIKF1B1YmxpY1JvdXRlVGFyZ2V0SGVhbHRoEjsKBnN0YXR1cxgBIAEoDjIrLnAycHN0cmVhbS52MS5QdWJsaWNSb3V0ZVRhcmdldEhlYWx0aFN0YXR1cxIRCglhdmFpbGFibGUYAiABKAgSEQoJY29ubmVjdGVkGAMgASgIEhAKCGFnZW50X2lkGAQgASgDEiMKG2xhc3RfY2hlY2tlZF9hdF91bml4X21pbGxpcxgFIAEoAxISCgpsYXN0X2Vycm9yGAYgASgJEisKI3Bhc3NpdmVfdW5oZWFsdGh5X3VudGlsX3VuaXhfbWlsbGlzGAcgASgDEhcKD2FjdGl2ZV9yZXF1ZXN0cxgIIAEoAyLVBwoRUHVibGljUm91dGVUYXJnZXQSCgoCaWQYASABKAMSEAoIcm91dGVfaWQYAiABKAMSDAoEbmFtZRgDIAEoCRIQCghwb3NpdGlvbhgEIAEoAxIWCg5wcmlvcml0eV9ncm91cBgFIAEoAxIOCgZ3ZWlnaHQYBiABKAMSDwoHZW5hYmxlZBgHIAEoCBI4Cgt0YXJnZXRfdHlwZRgIIAEoDjIjLnAycHN0cmVhbS52MS5QdWJsaWNSb3V0ZVRhcmdldFR5cGUSCwoDdXJsGAkgASgJEjsKCXRyYW5zcG9ydBgKIAEoDjIoLnAycHN0cmVhbS52MS5QdWJsaWNSb3V0ZVRhcmdldFRyYW5zcG9ydBI5Cg5hZ2VudF9zZWxlY3RvchgLIAEoCzIhLnAycHN0cmVhbS52MS5QdWJsaWNBZ2VudFNlbGVjdG9yEkoKFGFnZW50X2xvYWRfYmFsYW5jaW5nGAwgASgOMiwucDJwc3RyZWFtLnYxLlB1YmxpY1JvdXRlVGFyZ2V0TG9hZEJhbGFuY2luZxIXCg90bHNfc2tpcF92ZXJpZnkYDSABKAgSLwondXBzdHJlYW1fcmVzcG9uc2VfaGVhZGVyX3RpbWVvdXRfbWlsbGlzGA4gASgDEk8KGHVwc3RyZWFtX3JlcXVlc3RfaGVhZGVycxgPIAMoCzItLnAycHN0cmVhbS52MS5QdWJsaWNSb3V0ZVRhcmdldFVwc3RyZWFtSGVhZGVyEkUKE3Vwc3RyZWFtX2Jhc2ljX2F1dGgYECABKAsyKC5wMnBzdHJlYW0udjEuUHVibGljUm91dGVUYXJnZXRCYXNpY0F1dGgSQAoMaGVhbHRoX2NoZWNrGBEgASgLMioucDJwc3RyZWFtLnYxLlB1YmxpY1JvdXRlVGFyZ2V0SGVhbHRoQ2hlY2sSGgoSc3RhdGljX3N0YXR1c19jb2RlGBIgASgDEjsKF3N0YXRpY19yZXNwb25zZV9oZWFkZXJzGBMgAygLMhoucDJwc3RyZWFtLnYxLlB1YmxpY0hlYWRlchIcChRzdGF0aWNfcmVzcG9uc2VfYm9keRgUIAEoCRJHChlzdGF0aWNfcmVzcG9uc2VfYm9keV9tb2RlGBUgASgOMiQucDJwc3RyZWFtLnYxLlB1YmxpY1Jlc3BvbnNlQm9keU1vZGUSIwobc3RhdGljX3Jlc3BvbnNlX3RlbXBsYXRlX2lkGBYgASgDEjUKBmhlYWx0aBgXIAEoCzIlLnAycHN0cmVhbS52MS5QdWJsaWNSb3V0ZVRhcmdldEhlYWx0aCLxAQoOUHVibGljTGlzdGVuZXISCgoCaWQYASABKAMSDAoEbmFtZRgCIAEoCRIUCgxiaW5kX2FkZHJlc3MYAyABKAkSDAoEcG9ydBgEIAEoAxI2Cghwcm90b2NvbBgFIAEoDjIkLnAycHN0cmVhbS52MS5QdWJsaWNMaXN0ZW5lclByb3RvY29sEg8KB2VuYWJsZWQYBiABKAgSHgoWY3JlYXRlZF9hdF91bml4X21pbGxpcxgIIAEoAxIeChZ1cGRhdGVkX2F0X3VuaXhfbWlsbGlzGAkgASgDSgQIBxAIUhJkZWZhdWx0X2JhY2tlbmRfaWQiqAUKC1B1YmxpY1JvdXRlEgoKAmlkGAEgASgDEhMKC2xpc3RlbmVyX2lkGAIgASgDEhAKCHByaW9yaXR5GAMgASgDEhQKDGhvc3RfcGF0dGVybhgEIAEoCRITCgtwYXRoX3ByZWZpeBgFIAEoCRIPCgdlbmFibGVkGAcgASgIEh4KFmNyZWF0ZWRfYXRfdW5peF9taWxsaXMYCCABKAMSHgoWdXBkYXRlZF9hdF91bml4X21pbGxpcxgJIAEoAxIvCgZhY3Rpb24YCiABKA4yHy5wMnBzdHJlYW0udjEuUHVibGljUm91dGVBY3Rpb24SSQoUcmVkaXJlY3RfdGFyZ2V0X21vZGUYCyABKA4yKy5wMnBzdHJlYW0udjEuUHVibGljUm91dGVSZWRpcmVjdFRhcmdldE1vZGUSFwoPcmVkaXJlY3RfdGFyZ2V0GAwgASgJEhwKFHJlZGlyZWN0X3N0YXR1c19jb2RlGA0gASgDEiUKHXJlZGlyZWN0X3ByZXNlcnZlX3BhdGhfc3VmZml4GA4gASgIEh8KF3JlZGlyZWN0X3ByZXNlcnZlX3F1ZXJ5GA8gASgIEksKFXRhcmdldF9sb2FkX2JhbGFuY2luZxgTIAEoDjIsLnAycHN0cmVhbS52MS5QdWJsaWNSb3V0ZVRhcmdldExvYWRCYWxhbmNpbmcSEgoKaXNfZGVmYXVsdBgUIAEoCBIwCgd0YXJnZXRzGBUgAygLMh8ucDJwc3RyZWFtLnYxLlB1YmxpY1JvdXRlVGFyZ2V0SgQIBhAHSgQIEBARSgQIERASSgQIEhATUgpiYWNrZW5kX2lkUg5sb2FkX2JhbGFuY2luZ1ITYmFja2VuZF9hc3NpZ25tZW50c1ITZmFsbGJhY2tfYmFja2VuZF9pZCKABQoUUHVibGljVGxzQ2VydGlmaWNhdGUSCgoCaWQYASABKAMSEwoLbGlzdGVuZXJfaWQYAiABKAMSGAoQaG9zdG5hbWVfcGF0dGVybhgDIAEoCRIRCgljZXJ0X3BhdGgYBCABKAkSEAoIa2V5X3BhdGgYBSABKAkSDwoHZW5hYmxlZBgGIAEoCBIeChZjcmVhdGVkX2F0X3VuaXhfbWlsbGlzGAcgASgDEh4KFnVwZGF0ZWRfYXRfdW5peF9taWxsaXMYCCABKAMSOAoGc291cmNlGAkgASgOMigucDJwc3RyZWFtLnYxLlB1YmxpY1Rsc0NlcnRpZmljYXRlU291cmNlEkIKE2FjbWVfY2hhbGxlbmdlX3R5cGUYCiABKA4yJS5wMnBzdHJlYW0udjEuUHVibGljQWNtZUNoYWxsZW5nZVR5cGUSKwoHYWNtZV9jYRgLIAEoDjIaLnAycHN0cmVhbS52MS5QdWJsaWNBY21lQ2ESEgoKYWNtZV9lbWFpbBgMIAEoCRIZChFkbnNfY3JlZGVudGlhbF9pZBgNIAEoAxI4CgZzdGF0dXMYDiABKA4yKC5wMnBzdHJlYW0udjEuUHVibGljVGxzQ2VydGlmaWNhdGVTdGF0dXMSEgoKbGFzdF9lcnJvchgPIAEoCRIdChVpc3N1ZWRfYXRfdW5peF9taWxsaXMYECABKAMSHgoWZXhwaXJlc19hdF91bml4X21pbGxpcxgRIAEoAxIjChtuZXh0X3JlbmV3YWxfYXRfdW5peF9taWxsaXMYEiABKAMSKwojbGFzdF9yZW5ld2FsX2F0dGVtcHRfYXRfdW5peF9taWxsaXMYEyABKAMi6QEKFlB1YmxpY1Rsc0Ruc0NyZWRlbnRpYWwSCgoCaWQYASABKAMSDAoEbmFtZRgCIAEoCRIxCghwcm92aWRlchgDIAEoDjIfLnAycHN0cmVhbS52MS5QdWJsaWNEbnNQcm92aWRlchIaChJjbG91ZGZsYXJlX3pvbmVfaWQYBCABKAkSFQoNYXBpX3Rva2VuX3NldBgFIAEoCBIPCgdlbmFibGVkGAYgASgIEh4KFmNyZWF0ZWRfYXRfdW5peF9taWxsaXMYByABKAMSHgoWdXBkYXRlZF9hdF91bml4X21pbGxpcxgIIAEoAyJeChZQdWJsaWNSYXRlTGltaXRLZXlQYXJ0EjYKBnNvdXJjZRgBIAEoDjImLnAycHN0cmVhbS52MS5QdWJsaWNSYXRlTGltaXRLZXlTb3VyY2USDAoEbmFtZRgCIAEoCSJoChVQdWJsaWNQb2xpY3lNYXRjaFJ1bGUSFgoOY2VsX2V4cHJlc3Npb24YASABKAkSNwoHYnVpbGRlchgCIAEoCzImLnAycHN0cmVhbS52MS5QdWJsaWNQb2xpY3lNYXRjaEJ1aWxkZXIiTgoYUHVibGljUG9saWN5TWF0Y2hCdWlsZGVyEjIKBHJvb3QYASABKAsyJC5wMnBzdHJlYW0udjEuUHVibGljUG9saWN5TWF0Y2hHcm91cCLfAQoWUHVibGljUG9saWN5TWF0Y2hHcm91cBJACghvcGVyYXRvchgBIAEoDjIuLnAycHN0cmVhbS52MS5QdWJsaWNQb2xpY3lNYXRjaEJvb2xlYW5PcGVyYXRvchI8Cgpjb25kaXRpb25zGAIgAygLMigucDJwc3RyZWFtLnYxLlB1YmxpY1BvbGljeU1hdGNoQ29uZGl0aW9uEjQKBmdyb3VwcxgDIAMoCzIkLnAycHN0cmVhbS52MS5QdWJsaWNQb2xpY3lNYXRjaEdyb3VwEg8KB25lZ2F0ZWQYBCABKAgixAEKGlB1YmxpY1BvbGljeU1hdGNoQ29uZGl0aW9uEjMKBWZpZWxkGAEgASgOMiQucDJwc3RyZWFtLnYxLlB1YmxpY1BvbGljeU1hdGNoRmllbGQSDAoEbmFtZRgCIAEoCRJCCghvcGVyYXRvchgDIAEoDjIwLnAycHN0cmVhbS52MS5QdWJsaWNQb2xpY3lNYXRjaENvbmRpdGlvbk9wZXJhdG9yEg4KBnZhbHVlcxgEIAMoCRIPCgduZWdhdGVkGAUgASgIIjwKHVB1YmxpY1JhdGVMaW1pdFJlc3BvbnNlSGVhZGVyEgwKBG5hbWUYASABKAkSDQoFdmFsdWUYAiABKAkigQUKE1B1YmxpY1JhdGVMaW1pdFJ1bGUSCgoCaWQYASABKAMSDAoEbmFtZRgCIAEoCRIQCghwcmlvcml0eRgDIAEoAxIPCgdlbmFibGVkGAQgASgIEjkKCWFsZ29yaXRobRgFIAEoDjImLnAycHN0cmVhbS52MS5QdWJsaWNSYXRlTGltaXRBbGdvcml0aG0SDQoFbGltaXQYBiABKAMSFQoNd2luZG93X21pbGxpcxgHIAEoAxINCgVidXJzdBgIIAEoAxI3CglrZXlfcGFydHMYCiADKAsyJC5wMnBzdHJlYW0udjEuUHVibGljUmF0ZUxpbWl0S2V5UGFydBIcChRyZXNwb25zZV9zdGF0dXNfY29kZRgLIAEoAxIVCg1yZXNwb25zZV9ib2R5GAwgASgJEh0KFXJlc3BvbnNlX2NvbnRlbnRfdHlwZRgNIAEoCRJFChByZXNwb25zZV9oZWFkZXJzGA4gAygLMisucDJwc3RyZWFtLnYxLlB1YmxpY1JhdGVMaW1pdFJlc3BvbnNlSGVhZGVyEh4KFmNyZWF0ZWRfYXRfdW5peF9taWxsaXMYDyABKAMSHgoWdXBkYXRlZF9hdF91bml4X21pbGxpcxgQIAEoAxJAChJyZXNwb25zZV9ib2R5X21vZGUYESABKA4yJC5wMnBzdHJlYW0udjEuUHVibGljUmVzcG9uc2VCb2R5TW9kZRIhChlyZXNwb25zZV9ib2R5X3RlbXBsYXRlX2lkGBIgASgDEjcKCm1hdGNoX3J1bGUYEyABKAsyIy5wMnBzdHJlYW0udjEuUHVibGljUG9saWN5TWF0Y2hSdWxlSgQICRAKUgVtYXRjaCLvAwoXUHVibGljVHJhZmZpY1NoYXBlclJ1bGUSCgoCaWQYASABKAMSDAoEbmFtZRgCIAEoCRIQCghwcmlvcml0eRgDIAEoAxIPCgdlbmFibGVkGAQgASgIEkIKDGJ1ZGdldF9zY29wZRgFIAEoDjIsLnAycHN0cmVhbS52MS5QdWJsaWNUcmFmZmljU2hhcGVyQnVkZ2V0U2NvcGUSHwoXdXBsb2FkX2J5dGVzX3Blcl9zZWNvbmQYBiABKAMSIQoZZG93bmxvYWRfYnl0ZXNfcGVyX3NlY29uZBgHIAEoAxITCgtidXJzdF9ieXRlcxgIIAEoAxIcChRyZXF1ZXN0X2V4ZW1wdF9ieXRlcxgJIAEoAxIdChVyZXNwb25zZV9leGVtcHRfYnl0ZXMYCiABKAMSNwoJa2V5X3BhcnRzGAwgAygLMiQucDJwc3RyZWFtLnYxLlB1YmxpY1JhdGVMaW1pdEtleVBhcnQSHgoWY3JlYXRlZF9hdF91bml4X21pbGxpcxgNIAEoAxIeChZ1cGRhdGVkX2F0X3VuaXhfbWlsbGlzGA4gASgDEjcKCm1hdGNoX3J1bGUYDyABKAsyIy5wMnBzdHJlYW0udjEuUHVibGljUG9saWN5TWF0Y2hSdWxlSgQICxAMUgVtYXRjaCKGAgoYUHVibGljV2FmQ2FwdGNoYVByb3ZpZGVyEgoKAmlkGAEgASgDEgwKBG5hbWUYAiABKAkSQQoNcHJvdmlkZXJfdHlwZRgDIAEoDjIqLnAycHN0cmVhbS52MS5QdWJsaWNXYWZDYXB0Y2hhUHJvdmlkZXJUeXBlEhAKCHNpdGVfa2V5GAQgASgJEhIKCnNlY3JldF9rZXkYBSABKAkSFgoOc2VjcmV0X2tleV9zZXQYBiABKAgSDwoHZW5hYmxlZBgHIAEoCBIeChZjcmVhdGVkX2F0X3VuaXhfbWlsbGlzGAggASgDEh4KFnVwZGF0ZWRfYXRfdW5peF9taWxsaXMYCSABKAMizgIKFlB1YmxpY1dhZlRyaWdnZXJDb25maWcSHQoVcmVxdWVzdF93aW5kb3dfbWlsbGlzGAEgASgDEhwKFG1pbmltdW1fcmVxdWVzdF9yYXRlGAIgASgDEiAKGHRyYWZmaWNfc3Bpa2VfbXVsdGlwbGllchgDIAEoARIdChVwcm94eV9hY3RpdmVfcmVxdWVzdHMYBCABKAMSJAoccm91dGVfdGFyZ2V0X2FjdGl2ZV9yZXF1ZXN0cxgFIAEoAxIdChVhZ2VudF9hY3RpdmVfcmVxdWVzdHMYBiABKAMSGgoSc2VydmVyX2NwdV9wZXJjZW50GAcgASgBEhkKEWFnZW50X2NwdV9wZXJjZW50GAggASgBEh0KFW1pbmltdW1fYWN0aXZlX21pbGxpcxgJIAEoAxIbChNxdWlldF9wZXJpb2RfbWlsbGlzGAogASgDIu0BChpQdWJsaWNXYWZXYWl0aW5nUm9vbUNvbmZpZxIdChVtYXhfYWRtaXR0ZWRfc2Vzc2lvbnMYASABKAMSIQoZYWRtaXNzaW9uX3JhdGVfcGVyX3NlY29uZBgCIAEoAxIkChxhZG1pc3Npb25fc2Vzc2lvbl90dGxfbWlsbGlzGAMgASgDEiIKGnF1ZXVlX3BvbGxfaW50ZXJ2YWxfbWlsbGlzGAQgASgDEhwKFHF1ZXVlX3RpbWVvdXRfbWlsbGlzGAUgASgDEhIKCnBhZ2VfdGl0bGUYBiABKAkSEQoJcGFnZV9ib2R5GAcgASgJIpwHCg1QdWJsaWNXYWZSdWxlEgoKAmlkGAEgASgDEgwKBG5hbWUYAiABKAkSEAoIcHJpb3JpdHkYAyABKAMSDwoHZW5hYmxlZBgEIAEoCBIxCgZhY3Rpb24YBSABKA4yIS5wMnBzdHJlYW0udjEuUHVibGljV2FmUnVsZUFjdGlvbhI+Cg9hY3RpdmF0aW9uX21vZGUYBiABKA4yJS5wMnBzdHJlYW0udjEuUHVibGljV2FmQWN0aXZhdGlvbk1vZGUSNwoJa2V5X3BhcnRzGAggAygLMiQucDJwc3RyZWFtLnYxLlB1YmxpY1JhdGVMaW1pdEtleVBhcnQSGwoTY2FwdGNoYV9wcm92aWRlcl9pZBgJIAEoAxIfChdjYXB0Y2hhX3Bhc3NfdHRsX21pbGxpcxgKIAEoAxI+Cgx3YWl0aW5nX3Jvb20YCyABKAsyKC5wMnBzdHJlYW0udjEuUHVibGljV2FmV2FpdGluZ1Jvb21Db25maWcSNgoIdHJpZ2dlcnMYDCABKAsyJC5wMnBzdHJlYW0udjEuUHVibGljV2FmVHJpZ2dlckNvbmZpZxIiChpibG9ja19yZXNwb25zZV9zdGF0dXNfY29kZRgNIAEoAxIbChNibG9ja19yZXNwb25zZV9ib2R5GA4gASgJEiMKG2Jsb2NrX3Jlc3BvbnNlX2NvbnRlbnRfdHlwZRgPIAEoCRJLChZibG9ja19yZXNwb25zZV9oZWFkZXJzGBAgAygLMisucDJwc3RyZWFtLnYxLlB1YmxpY1JhdGVMaW1pdFJlc3BvbnNlSGVhZGVyEh4KFmNyZWF0ZWRfYXRfdW5peF9taWxsaXMYESABKAMSHgoWdXBkYXRlZF9hdF91bml4X21pbGxpcxgSIAEoAxJGChhibG9ja19yZXNwb25zZV9ib2R5X21vZGUYEyABKA4yJC5wMnBzdHJlYW0udjEuUHVibGljUmVzcG9uc2VCb2R5TW9kZRIiChpibG9ja19yZXNwb25zZV90ZW1wbGF0ZV9pZBgUIAEoAxIgChhjYXB0Y2hhX3BhZ2VfdGVtcGxhdGVfaWQYFSABKAMSJQodd2FpdGluZ19yb29tX3BhZ2VfdGVtcGxhdGVfaWQYFiABKAMSNwoKbWF0Y2hfcnVsZRgXIAEoCzIjLnAycHN0cmVhbS52MS5QdWJsaWNQb2xpY3lNYXRjaFJ1bGVKBAgHEAhSBW1hdGNoIuMBChZQdWJsaWNSZXNwb25zZVRlbXBsYXRlEgoKAmlkGAEgASgDEgwKBG5hbWUYAiABKAkSNgoEa2luZBgDIAEoDjIoLnAycHN0cmVhbS52MS5QdWJsaWNSZXNwb25zZVRlbXBsYXRlS2luZBITCgtkZXNjcmlwdGlvbhgEIAEoCRIUCgxjb250ZW50X3R5cGUYBSABKAkSDAoEYm9keRgGIAEoCRIeChZjcmVhdGVkX2F0X3VuaXhfbWlsbGlzGAcgASgDEh4KFnVwZGF0ZWRfYXRfdW5peF9taWxsaXMYCCABKAMi8wEKE1B1YmxpY0NhY2hlU2V0dGluZ3MSDwoHZW5hYmxlZBgBIAEoCBIWCg5tYXhfZGlza19ieXRlcxgCIAEoAxIYChBtYXhfbWVtb3J5X2J5dGVzGAMgASgDEiMKG21lbW9yeV9ob3Rfb2JqZWN0X21heF9ieXRlcxgEIAEoAxITCgttYXhfZW50cmllcxgFIAEoAxIfChdjbGVhbnVwX2ludGVydmFsX21pbGxpcxgGIAEoAxIeChZjcmVhdGVkX2F0X3VuaXhfbWlsbGlzGAcgASgDEh4KFnVwZGF0ZWRfYXRfdW5peF9taWxsaXMYCCABKAMi3wQKD1B1YmxpY0NhY2hlUnVsZRIKCgJpZBgBIAEoAxIMCgRuYW1lGAIgASgJEhAKCHByaW9yaXR5GAMgASgDEg8KB2VuYWJsZWQYBCABKAgSEQoJcm91dGVfaWRzGAYgAygDEi0KBXNjb3BlGAggASgOMh4ucDJwc3RyZWFtLnYxLlB1YmxpY0NhY2hlU2NvcGUSMgoIdHRsX21vZGUYCSABKA4yIC5wMnBzdHJlYW0udjEuUHVibGljQ2FjaGVUdGxNb2RlEhIKCnR0bF9taWxsaXMYCiABKAMSNgoKcXVlcnlfbW9kZRgLIAEoDjIiLnAycHN0cmVhbS52MS5QdWJsaWNDYWNoZVF1ZXJ5TW9kZRIUCgxxdWVyeV9wYXJhbXMYDCADKAkSFAoMdmFyeV9oZWFkZXJzGA0gAygJEhoKEmNhY2hlX3N0YXR1c19jb2RlcxgOIAMoAxIYChBtYXhfb2JqZWN0X2J5dGVzGA8gASgDEh8KF2FkZF9jYWNoZV9zdGF0dXNfaGVhZGVyGBAgASgIEh4KFmNyZWF0ZWRfYXRfdW5peF9taWxsaXMYESABKAMSHgoWdXBkYXRlZF9hdF91bml4X21pbGxpcxgSIAEoAxIdChVhbGxvd19jb29raWVfcmVxdWVzdHMYEyABKAgSNwoKbWF0Y2hfcnVsZRgUIAEoCzIjLnAycHN0cmVhbS52MS5QdWJsaWNQb2xpY3lNYXRjaFJ1bGUSEgoKdGFyZ2V0X2lkcxgVIAMoA0oECAUQBkoECAcQCFIFbWF0Y2hSC2JhY2tlbmRfaWRzIuIBChRQdWJsaWNMaXN0ZW5lclN0YXR1cxITCgtsaXN0ZW5lcl9pZBgBIAEoAxInCgVzdGF0ZRgCIAEoDjIYLnAycHN0cmVhbS52MS5Qcm94eVN0YXRlEhIKCmxhc3RfZXJyb3IYAyABKAkSHgoWc3RhcnRlZF9hdF91bml4X21pbGxpcxgEIAEoAxIeChZzdG9wcGVkX2F0X3VuaXhfbWlsbGlzGAUgASgDEhUKDWJvdW5kX2FkZHJlc3MYBiABKAkSDwoHcnVubmluZxgHIAEoCBIQCghkaXNhYmxlZBgIIAEoCCIdChtHZXRQdWJsaWNQcm94eUNvbmZpZ1JlcXVlc3Qi6AYKHEdldFB1YmxpY1Byb3h5Q29uZmlnUmVzcG9uc2USLwoJbGlzdGVuZXJzGAIgAygLMhwucDJwc3RyZWFtLnYxLlB1YmxpY0xpc3RlbmVyEikKBnJvdXRlcxgDIAMoCzIZLnAycHN0cmVhbS52MS5QdWJsaWNSb3V0ZRI8ChB0bHNfY2VydGlmaWNhdGVzGAQgAygLMiIucDJwc3RyZWFtLnYxLlB1YmxpY1Rsc0NlcnRpZmljYXRlEigKBXByb3h5GAUgASgLMhkucDJwc3RyZWFtLnYxLlByb3h5U3RhdHVzEiMKBmFnZW50cxgGIAMoCzITLnAycHN0cmVhbS52MS5BZ2VudBI7ChByYXRlX2xpbWl0X3J1bGVzGAggAygLMiEucDJwc3RyZWFtLnYxLlB1YmxpY1JhdGVMaW1pdFJ1bGUSQwoUdHJhZmZpY19zaGFwZXJfcnVsZXMYCSADKAsyJS5wMnBzdHJlYW0udjEuUHVibGljVHJhZmZpY1NoYXBlclJ1bGUSQQoTdGxzX2Ruc19jcmVkZW50aWFscxgKIAMoCzIkLnAycHN0cmVhbS52MS5QdWJsaWNUbHNEbnNDcmVkZW50aWFsEkUKFXdhZl9jYXB0Y2hhX3Byb3ZpZGVycxgMIAMoCzImLnAycHN0cmVhbS52MS5QdWJsaWNXYWZDYXB0Y2hhUHJvdmlkZXISLgoJd2FmX3J1bGVzGA0gAygLMhsucDJwc3RyZWFtLnYxLlB1YmxpY1dhZlJ1bGUSOQoOY2FjaGVfc2V0dGluZ3MYDiABKAsyIS5wMnBzdHJlYW0udjEuUHVibGljQ2FjaGVTZXR0aW5ncxIyCgtjYWNoZV9ydWxlcxgPIAMoCzIdLnAycHN0cmVhbS52MS5QdWJsaWNDYWNoZVJ1bGUSQAoScmVzcG9uc2VfdGVtcGxhdGVzGBAgAygLMiQucDJwc3RyZWFtLnYxLlB1YmxpY1Jlc3BvbnNlVGVtcGxhdGUSNgoNcm91dGVfdGFyZ2V0cxgRIAMoCzIfLnAycHN0cmVhbS52MS5QdWJsaWNSb3V0ZVRhcmdldEoECAEQAkoECAcQCEoECAsQDFIIYmFja2VuZHNSDmJhY2tlbmRfYWdlbnRzUg5yb3V0ZV9iYWNrZW5kcyL4CAocUHVibGljUm91dGVUYXJnZXRIZWFsdGhUcmFjZRIQCghzZXF1ZW5jZRgBIAEoBBIXCg9yb3V0ZV90YXJnZXRfaWQYAiABKAMSGQoRcm91dGVfdGFyZ2V0X25hbWUYAyABKAkSOwoJdHJhbnNwb3J0GAQgASgOMigucDJwc3RyZWFtLnYxLlB1YmxpY1JvdXRlVGFyZ2V0VHJhbnNwb3J0EkAKBnNvdXJjZRgFIAEoDjIwLnAycHN0cmVhbS52MS5QdWJsaWNSb3V0ZVRhcmdldEhlYWx0aFRyYWNlU291cmNlEkIKB291dGNvbWUYBiABKA4yMS5wMnBzdHJlYW0udjEuUHVibGljUm91dGVUYXJnZXRIZWFsdGhUcmFjZU91dGNvbWUSEAoIYWdlbnRfaWQYByABKAMSFwoPYWdlbnRfcHVibGljX2lkGAggASgJEhIKCmFnZW50X25hbWUYCSABKAkSHgoWc3RhcnRlZF9hdF91bml4X21pbGxpcxgKIAEoAxIfChdmaW5pc2hlZF9hdF91bml4X21pbGxpcxgLIAEoAxIXCg9kdXJhdGlvbl9taWxsaXMYDCABKAMSDgoGbWV0aG9kGA0gASgJEgsKA3VybBgOIAEoCRITCgtzdGF0dXNfY29kZRgPIAEoAxIbChNleHBlY3RlZF9zdGF0dXNfbWluGBAgASgDEhsKE2V4cGVjdGVkX3N0YXR1c19tYXgYESABKAMSFgoOdGltZW91dF9taWxsaXMYEiABKAMSFwoPdGxzX3NraXBfdmVyaWZ5GBMgASgIEkIKDXN0YXR1c19iZWZvcmUYFCABKA4yKy5wMnBzdHJlYW0udjEuUHVibGljUm91dGVUYXJnZXRIZWFsdGhTdGF0dXMSQQoMc3RhdHVzX2FmdGVyGBUgASgOMisucDJwc3RyZWFtLnYxLlB1YmxpY1JvdXRlVGFyZ2V0SGVhbHRoU3RhdHVzEhgKEGF2YWlsYWJsZV9iZWZvcmUYFiABKAgSFwoPYXZhaWxhYmxlX2FmdGVyGBcgASgIEh0KFWhlYWx0aHlfc3RyZWFrX2JlZm9yZRgYIAEoAxIcChRoZWFsdGh5X3N0cmVha19hZnRlchgZIAEoAxIfChd1bmhlYWx0aHlfc3RyZWFrX2JlZm9yZRgaIAEoAxIeChZ1bmhlYWx0aHlfc3RyZWFrX2FmdGVyGBsgASgDEisKI3Bhc3NpdmVfdW5oZWFsdGh5X3VudGlsX3VuaXhfbWlsbGlzGBwgASgDEhIKCmVycm9yX2tpbmQYHSABKAkSDQoFZXJyb3IYHiABKAkSWQoQZGVidWdfYXR0cmlidXRlcxgfIAMoCzI/LnAycHN0cmVhbS52MS5QdWJsaWNSb3V0ZVRhcmdldEhlYWx0aFRyYWNlLkRlYnVnQXR0cmlidXRlc0VudHJ5GjYKFERlYnVnQXR0cmlidXRlc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEiewooTGlzdFB1YmxpY1JvdXRlVGFyZ2V0SGVhbHRoVHJhY2VzUmVxdWVzdBIXCg9yb3V0ZV90YXJnZXRfaWQYASABKAMSEAoIYWdlbnRfaWQYAiABKAMSDQoFbGltaXQYAyABKAMSFQoNZmFpbHVyZXNfb25seRgEIAEoCCKgAQopTGlzdFB1YmxpY1JvdXRlVGFyZ2V0SGVhbHRoVHJhY2VzUmVzcG9uc2USOgoGdHJhY2VzGAEgAygLMioucDJwc3RyZWFtLnYxLlB1YmxpY1JvdXRlVGFyZ2V0SGVhbHRoVHJhY2USFgoOcmV0YWluZWRfY291bnQYAiABKAMSHwoXbWF4X3JldGFpbmVkX3Blcl90YXJnZXQYAyABKAMipAEKI0NyZWF0ZVB1YmxpY1Jlc3BvbnNlVGVtcGxhdGVSZXF1ZXN0EgwKBG5hbWUYASABKAkSNgoEa2luZBgCIAEoDjIoLnAycHN0cmVhbS52MS5QdWJsaWNSZXNwb25zZVRlbXBsYXRlS2luZBITCgtkZXNjcmlwdGlvbhgDIAEoCRIUCgxjb250ZW50X3R5cGUYBCABKAkSDAoEYm9keRgFIAEoCSJeCiRDcmVhdGVQdWJsaWNSZXNwb25zZVRlbXBsYXRlUmVzcG9uc2USNgoIdGVtcGxhdGUYASABKAsyJC5wMnBzdHJlYW0udjEuUHVibGljUmVzcG9uc2VUZW1wbGF0ZSKwAQojVXBkYXRlUHVibGljUmVzcG9uc2VUZW1wbGF0ZVJlcXVlc3QSCgoCaWQYASABKAMSDAoEbmFtZRgCIAEoCRI2CgRraW5kGAMgASgOMigucDJwc3RyZWFtLnYxLlB1YmxpY1Jlc3BvbnNlVGVtcGxhdGVLaW5kEhMKC2Rlc2NyaXB0aW9uGAQgASgJEhQKDGNvbnRlbnRfdHlwZRgFIAEoCRIMCgRib2R5GAYgASgJIl4KJFVwZGF0ZVB1YmxpY1Jlc3BvbnNlVGVtcGxhdGVSZXNwb25zZRI2Cgh0ZW1wbGF0ZRgBIAEoCzIkLnAycHN0cmVhbS52MS5QdWJsaWNSZXNwb25zZVRlbXBsYXRlIjEKI0RlbGV0ZVB1YmxpY1Jlc3BvbnNlVGVtcGxhdGVSZXF1ZXN0EgoKAmlkGAEgASgDIiYKJERlbGV0ZVB1YmxpY1Jlc3BvbnNlVGVtcGxhdGVSZXNwb25zZSKxAQoSQ3JlYXRlQWdlbnRSZXF1ZXN0EgwKBG5hbWUYAiABKAkSDwoHZW5hYmxlZBgDIAEoCBI8CgZsYWJlbHMYBCADKAsyLC5wMnBzdHJlYW0udjEuQ3JlYXRlQWdlbnRSZXF1ZXN0LkxhYmVsc0VudHJ5Gi0KC0xhYmVsc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAFKBAgBEAJSCXB1YmxpY19pZCJIChNDcmVhdGVBZ2VudFJlc3BvbnNlEiIKBWFnZW50GAEgASgLMhMucDJwc3RyZWFtLnYxLkFnZW50Eg0KBXRva2VuGAIgASgJIr0BChJVcGRhdGVBZ2VudFJlcXVlc3QSCgoCaWQYASABKAMSDAoEbmFtZRgDIAEoCRIPCgdlbmFibGVkGAQgASgIEjwKBmxhYmVscxgFIAMoCzIsLnAycHN0cmVhbS52MS5VcGRhdGVBZ2VudFJlcXVlc3QuTGFiZWxzRW50cnkaLQoLTGFiZWxzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4AUoECAIQA1IJcHVibGljX2lkIjkKE1VwZGF0ZUFnZW50UmVzcG9uc2USIgoFYWdlbnQYASABKAsyEy5wMnBzdHJlYW0udjEuQWdlbnQiIAoSRGVsZXRlQWdlbnRSZXF1ZXN0EgoKAmlkGAEgASgDIhUKE0RlbGV0ZUFnZW50UmVzcG9uc2UiJQoXUm90YXRlQWdlbnRUb2tlblJlcXVlc3QSCgoCaWQYASABKAMiTQoYUm90YXRlQWdlbnRUb2tlblJlc3BvbnNlEiIKBWFnZW50GAEgASgLMhMucDJwc3RyZWFtLnYxLkFnZW50Eg0KBXRva2VuGAIgASgJIsQBChVNYW5hZ2VtZW50QWNjZXNzVG9rZW4SCgoCaWQYASABKAMSDAoEbmFtZRgCIAEoCRIPCgdlbmFibGVkGAMgASgIEh4KFmV4cGlyZXNfYXRfdW5peF9taWxsaXMYBCABKAMSIAoYbGFzdF91c2VkX2F0X3VuaXhfbWlsbGlzGAUgASgDEh4KFmNyZWF0ZWRfYXRfdW5peF9taWxsaXMYBiABKAMSHgoWdXBkYXRlZF9hdF91bml4X21pbGxpcxgHIAEoAyJjCiJDcmVhdGVNYW5hZ2VtZW50QWNjZXNzVG9rZW5SZXF1ZXN0EgwKBG5hbWUYASABKAkSDwoHZW5hYmxlZBgCIAEoCBIeChZleHBpcmVzX2F0X3VuaXhfbWlsbGlzGAMgASgDIm8KI0NyZWF0ZU1hbmFnZW1lbnRBY2Nlc3NUb2tlblJlc3BvbnNlEjkKDGFjY2Vzc190b2tlbhgBIAEoCzIjLnAycHN0cmVhbS52MS5NYW5hZ2VtZW50QWNjZXNzVG9rZW4SDQoFdG9rZW4YAiABKAkiIwohTGlzdE1hbmFnZW1lbnRBY2Nlc3NUb2tlbnNSZXF1ZXN0ImAKIkxpc3RNYW5hZ2VtZW50QWNjZXNzVG9rZW5zUmVzcG9uc2USOgoNYWNjZXNzX3Rva2VucxgBIAMoCzIjLnAycHN0cmVhbS52MS5NYW5hZ2VtZW50QWNjZXNzVG9rZW4iMAoiRGVsZXRlTWFuYWdlbWVudEFjY2Vzc1Rva2VuUmVxdWVzdBIKCgJpZBgBIAEoAyIlCiNEZWxldGVNYW5hZ2VtZW50QWNjZXNzVG9rZW5SZXNwb25zZSLKAQoWRW52aXJvbm1lbnRDZXJ0aWZpY2F0ZRILCgNwZW0YASABKAkSGgoSc2hhMjU2X2ZpbmdlcnByaW50GAIgASgJEg8KB3N1YmplY3QYAyABKAkSDgoGaXNzdWVyGAQgASgJEhEKCWRuc19uYW1lcxgFIAMoCRIUCgxpcF9hZGRyZXNzZXMYBiADKAkSHgoWbm90X2JlZm9yZV91bml4X21pbGxpcxgHIAEoAxIdChVub3RfYWZ0ZXJfdW5peF9taWxsaXMYCCABKAMiyQQKC0Vudmlyb25tZW50EgoKAmlkGAEgASgDEgwKBG5hbWUYAiABKAkSFgoObWFuYWdlbWVudF91cmwYAyABKAkSNQoJdHJhbnNwb3J0GAQgASgOMiIucDJwc3RyZWFtLnYxLkVudmlyb25tZW50VHJhbnNwb3J0EhAKCGFnZW50X2lkGAUgASgDEhIKCmFnZW50X25hbWUYBiABKAkSFwoPYWdlbnRfY29ubmVjdGVkGAcgASgIEg8KB2VuYWJsZWQYCCABKAgSHwoXYWNjZXNzX3Rva2VuX2NvbmZpZ3VyZWQYCSABKAgSOAoLdHJ1c3Rfc3RhdGUYCiABKA4yIy5wMnBzdHJlYW0udjEuRW52aXJvbm1lbnRUcnVzdFN0YXRlEkEKE3RydXN0ZWRfY2VydGlmaWNhdGUYCyABKAsyJC5wMnBzdHJlYW0udjEuRW52aXJvbm1lbnRDZXJ0aWZpY2F0ZRJCChRvYnNlcnZlZF9jZXJ0aWZpY2F0ZRgMIAEoCzIkLnAycHN0cmVhbS52MS5FbnZpcm9ubWVudENlcnRpZmljYXRlEhIKCmxhc3RfZXJyb3IYDSABKAkSIwobbGFzdF9jaGVja2VkX2F0X3VuaXhfbWlsbGlzGA4gASgDEh4KFmNyZWF0ZWRfYXRfdW5peF9taWxsaXMYDyABKAMSHgoWdXBkYXRlZF9hdF91bml4X21pbGxpcxgQIAEoAxImCh5yZXNwb25zZV9oZWFkZXJfdGltZW91dF9taWxsaXMYESABKAMiGQoXTGlzdEVudmlyb25tZW50c1JlcXVlc3QiSwoYTGlzdEVudmlyb25tZW50c1Jlc3BvbnNlEi8KDGVudmlyb25tZW50cxgBIAMoCzIZLnAycHN0cmVhbS52MS5FbnZpcm9ubWVudCLYAQoYQ3JlYXRlRW52aXJvbm1lbnRSZXF1ZXN0EgwKBG5hbWUYASABKAkSFgoObWFuYWdlbWVudF91cmwYAiABKAkSNQoJdHJhbnNwb3J0GAMgASgOMiIucDJwc3RyZWFtLnYxLkVudmlyb25tZW50VHJhbnNwb3J0EhAKCGFnZW50X2lkGAQgASgDEhQKDGFjY2Vzc190b2tlbhgFIAEoCRImCh5yZXNwb25zZV9oZWFkZXJfdGltZW91dF9taWxsaXMYBiABKAMSDwoHZW5hYmxlZBgHIAEoCCJLChlDcmVhdGVFbnZpcm9ubWVudFJlc3BvbnNlEi4KC2Vudmlyb25tZW50GAEgASgLMhkucDJwc3RyZWFtLnYxLkVudmlyb25tZW50IuQBChhVcGRhdGVFbnZpcm9ubWVudFJlcXVlc3QSCgoCaWQYASABKAMSDAoEbmFtZRgCIAEoCRIWCg5tYW5hZ2VtZW50X3VybBgDIAEoCRI1Cgl0cmFuc3BvcnQYBCABKA4yIi5wMnBzdHJlYW0udjEuRW52aXJvbm1lbnRUcmFuc3BvcnQSEAoIYWdlbnRfaWQYBSABKAMSFAoMYWNjZXNzX3Rva2VuGAYgASgJEiYKHnJlc3BvbnNlX2hlYWRlcl90aW1lb3V0X21pbGxpcxgHIAEoAxIPCgdlbmFibGVkGAggASgIIksKGVVwZGF0ZUVudmlyb25tZW50UmVzcG9uc2USLgoLZW52aXJvbm1lbnQYASABKAsyGS5wMnBzdHJlYW0udjEuRW52aXJvbm1lbnQiJgoYRGVsZXRlRW52aXJvbm1lbnRSZXF1ZXN0EgoKAmlkGAEgASgDIhsKGURlbGV0ZUVudmlyb25tZW50UmVzcG9uc2UiMwolRGlzY292ZXJFbnZpcm9ubWVudENlcnRpZmljYXRlUmVxdWVzdBIKCgJpZBgBIAEoAyKTAQomRGlzY292ZXJFbnZpcm9ubWVudENlcnRpZmljYXRlUmVzcG9uc2USLgoLZW52aXJvbm1lbnQYASABKAsyGS5wMnBzdHJlYW0udjEuRW52aXJvbm1lbnQSOQoLY2VydGlmaWNhdGUYAiABKAsyJC5wMnBzdHJlYW0udjEuRW52aXJvbm1lbnRDZXJ0aWZpY2F0ZSJMCiJUcnVzdEVudmlyb25tZW50Q2VydGlmaWNhdGVSZXF1ZXN0EgoKAmlkGAEgASgDEhoKEnNoYTI1Nl9maW5nZXJwcmludBgCIAEoCSJVCiNUcnVzdEVudmlyb25tZW50Q2VydGlmaWNhdGVSZXNwb25zZRIuCgtlbnZpcm9ubWVudBgBIAEoCzIZLnAycHN0cmVhbS52MS5FbnZpcm9ubWVudCIkChZUZXN0RW52aXJvbm1lbnRSZXF1ZXN0EgoKAmlkGAEgASgDInoKF1Rlc3RFbnZpcm9ubWVudFJlc3BvbnNlEi4KC2Vudmlyb25tZW50GAEgASgLMhkucDJwc3RyZWFtLnYxLkVudmlyb25tZW50Ei8KBnN0YXR1cxgCIAEoCzIfLnAycHN0cmVhbS52MS5HZXRTdGF0dXNSZXNwb25zZSKyAQobQ3JlYXRlUHVibGljTGlzdGVuZXJSZXF1ZXN0EgwKBG5hbWUYASABKAkSFAoMYmluZF9hZGRyZXNzGAIgASgJEgwKBHBvcnQYAyABKAMSNgoIcHJvdG9jb2wYBCABKA4yJC5wMnBzdHJlYW0udjEuUHVibGljTGlzdGVuZXJQcm90b2NvbBIPCgdlbmFibGVkGAUgASgISgQIBhAHUhJkZWZhdWx0X2JhY2tlbmRfaWQirAEKHENyZWF0ZVB1YmxpY0xpc3RlbmVyUmVzcG9uc2USLgoIbGlzdGVuZXIYASABKAsyHC5wMnBzdHJlYW0udjEuUHVibGljTGlzdGVuZXISMgoGc3RhdHVzGAIgASgLMiIucDJwc3RyZWFtLnYxLlB1YmxpY0xpc3RlbmVyU3RhdHVzEigKBXByb3h5GAMgASgLMhkucDJwc3RyZWFtLnYxLlByb3h5U3RhdHVzIr4BChtVcGRhdGVQdWJsaWNMaXN0ZW5lclJlcXVlc3QSCgoCaWQYASABKAMSDAoEbmFtZRgCIAEoCRIUCgxiaW5kX2FkZHJlc3MYAyABKAkSDAoEcG9ydBgEIAEoAxI2Cghwcm90b2NvbBgFIAEoDjIkLnAycHN0cmVhbS52MS5QdWJsaWNMaXN0ZW5lclByb3RvY29sEg8KB2VuYWJsZWQYBiABKAhKBAgHEAhSEmRlZmF1bHRfYmFja2VuZF9pZCKsAQocVXBkYXRlUHVibGljTGlzdGVuZXJSZXNwb25zZRIuCghsaXN0ZW5lchgBIAEoCzIcLnAycHN0cmVhbS52MS5QdWJsaWNMaXN0ZW5lchIyCgZzdGF0dXMYAiABKAsyIi5wMnBzdHJlYW0udjEuUHVibGljTGlzdGVuZXJTdGF0dXMSKAoFcHJveHkYAyABKAsyGS5wMnBzdHJlYW0udjEuUHJveHlTdGF0dXMiKQobRGVsZXRlUHVibGljTGlzdGVuZXJSZXF1ZXN0EgoKAmlkGAEgASgDIh4KHERlbGV0ZVB1YmxpY0xpc3RlbmVyUmVzcG9uc2UiKQobRW5hYmxlUHVibGljTGlzdGVuZXJSZXF1ZXN0EgoKAmlkGAEgASgDIqwBChxFbmFibGVQdWJsaWNMaXN0ZW5lclJlc3BvbnNlEi4KCGxpc3RlbmVyGAEgASgLMhwucDJwc3RyZWFtLnYxLlB1YmxpY0xpc3RlbmVyEjIKBnN0YXR1cxgCIAEoCzIiLnAycHN0cmVhbS52MS5QdWJsaWNMaXN0ZW5lclN0YXR1cxIoCgVwcm94eRgDIAEoCzIZLnAycHN0cmVhbS52MS5Qcm94eVN0YXR1cyIqChxEaXNhYmxlUHVibGljTGlzdGVuZXJSZXF1ZXN0EgoKAmlkGAEgASgDIq0BCh1EaXNhYmxlUHVibGljTGlzdGVuZXJSZXNwb25zZRIuCghsaXN0ZW5lchgBIAEoCzIcLnAycHN0cmVhbS52MS5QdWJsaWNMaXN0ZW5lchIyCgZzdGF0dXMYAiABKAsyIi5wMnBzdHJlYW0udjEuUHVibGljTGlzdGVuZXJTdGF0dXMSKAoFcHJveHkYAyABKAsyGS5wMnBzdHJlYW0udjEuUHJveHlTdGF0dXMiKAoaU3RhcnRQdWJsaWNMaXN0ZW5lclJlcXVlc3QSCgoCaWQYASABKAMiewobU3RhcnRQdWJsaWNMaXN0ZW5lclJlc3BvbnNlEjIKBnN0YXR1cxgBIAEoCzIiLnAycHN0cmVhbS52MS5QdWJsaWNMaXN0ZW5lclN0YXR1cxIoCgVwcm94eRgCIAEoCzIZLnAycHN0cmVhbS52MS5Qcm94eVN0YXR1cyInChlTdG9wUHVibGljTGlzdGVuZXJSZXF1ZXN0EgoKAmlkGAEgASgDInoKGlN0b3BQdWJsaWNMaXN0ZW5lclJlc3BvbnNlEjIKBnN0YXR1cxgBIAEoCzIiLnAycHN0cmVhbS52MS5QdWJsaWNMaXN0ZW5lclN0YXR1cxIoCgVwcm94eRgCIAEoCzIZLnAycHN0cmVhbS52MS5Qcm94eVN0YXR1cyLpBAoYQ3JlYXRlUHVibGljUm91dGVSZXF1ZXN0EhMKC2xpc3RlbmVyX2lkGAEgASgDEhAKCHByaW9yaXR5GAIgASgDEhQKDGhvc3RfcGF0dGVybhgDIAEoCRITCgtwYXRoX3ByZWZpeBgEIAEoCRIPCgdlbmFibGVkGAYgASgIEi8KBmFjdGlvbhgKIAEoDjIfLnAycHN0cmVhbS52MS5QdWJsaWNSb3V0ZUFjdGlvbhJJChRyZWRpcmVjdF90YXJnZXRfbW9kZRgLIAEoDjIrLnAycHN0cmVhbS52MS5QdWJsaWNSb3V0ZVJlZGlyZWN0VGFyZ2V0TW9kZRIXCg9yZWRpcmVjdF90YXJnZXQYDCABKAkSHAoUcmVkaXJlY3Rfc3RhdHVzX2NvZGUYDSABKAMSJQodcmVkaXJlY3RfcHJlc2VydmVfcGF0aF9zdWZmaXgYDiABKAgSHwoXcmVkaXJlY3RfcHJlc2VydmVfcXVlcnkYDyABKAgSSwoVdGFyZ2V0X2xvYWRfYmFsYW5jaW5nGBMgASgOMiwucDJwc3RyZWFtLnYxLlB1YmxpY1JvdXRlVGFyZ2V0TG9hZEJhbGFuY2luZxISCgppc19kZWZhdWx0GBQgASgIEjAKB3RhcmdldHMYFSADKAsyHy5wMnBzdHJlYW0udjEuUHVibGljUm91dGVUYXJnZXRKBAgFEAZKBAgQEBFKBAgREBJKBAgSEBNSCmJhY2tlbmRfaWRSDmxvYWRfYmFsYW5jaW5nUhNiYWNrZW5kX2Fzc2lnbm1lbnRzUhNmYWxsYmFja19iYWNrZW5kX2lkIkUKGUNyZWF0ZVB1YmxpY1JvdXRlUmVzcG9uc2USKAoFcm91dGUYASABKAsyGS5wMnBzdHJlYW0udjEuUHVibGljUm91dGUi9QQKGFVwZGF0ZVB1YmxpY1JvdXRlUmVxdWVzdBIKCgJpZBgBIAEoAxITCgtsaXN0ZW5lcl9pZBgCIAEoAxIQCghwcmlvcml0eRgDIAEoAxIUCgxob3N0X3BhdHRlcm4YBCABKAkSEwoLcGF0aF9wcmVmaXgYBSABKAkSDwoHZW5hYmxlZBgHIAEoCBIvCgZhY3Rpb24YCiABKA4yHy5wMnBzdHJlYW0udjEuUHVibGljUm91dGVBY3Rpb24SSQoUcmVkaXJlY3RfdGFyZ2V0X21vZGUYCyABKA4yKy5wMnBzdHJlYW0udjEuUHVibGljUm91dGVSZWRpcmVjdFRhcmdldE1vZGUSFwoPcmVkaXJlY3RfdGFyZ2V0GAwgASgJEhwKFHJlZGlyZWN0X3N0YXR1c19jb2RlGA0gASgDEiUKHXJlZGlyZWN0X3ByZXNlcnZlX3BhdGhfc3VmZml4GA4gASgIEh8KF3JlZGlyZWN0X3ByZXNlcnZlX3F1ZXJ5GA8gASgIEksKFXRhcmdldF9sb2FkX2JhbGFuY2luZxgTIAEoDjIsLnAycHN0cmVhbS52MS5QdWJsaWNSb3V0ZVRhcmdldExvYWRCYWxhbmNpbmcSEgoKaXNfZGVmYXVsdBgUIAEoCBIwCgd0YXJnZXRzGBUgAygLMh8ucDJwc3RyZWFtLnYxLlB1YmxpY1JvdXRlVGFyZ2V0SgQIBhAHSgQIEBARSgQIERASSgQIEhATUgpiYWNrZW5kX2lkUg5sb2FkX2JhbGFuY2luZ1ITYmFja2VuZF9hc3NpZ25tZW50c1ITZmFsbGJhY2tfYmFja2VuZF9pZCJFChlVcGRhdGVQdWJsaWNSb3V0ZVJlc3BvbnNlEigKBXJvdXRlGAEgASgLMhkucDJwc3RyZWFtLnYxLlB1YmxpY1JvdXRlIiYKGERlbGV0ZVB1YmxpY1JvdXRlUmVxdWVzdBIKCgJpZBgBIAEoAyIbChlEZWxldGVQdWJsaWNSb3V0ZVJlc3BvbnNlIqYBCiNDcmVhdGVQdWJsaWNUbHNEbnNDcmVkZW50aWFsUmVxdWVzdBIMCgRuYW1lGAEgASgJEjEKCHByb3ZpZGVyGAIgASgOMh8ucDJwc3RyZWFtLnYxLlB1YmxpY0Ruc1Byb3ZpZGVyEhoKEmNsb3VkZmxhcmVfem9uZV9pZBgDIAEoCRIRCglhcGlfdG9rZW4YBCABKAkSDwoHZW5hYmxlZBgFIAEoCCJgCiRDcmVhdGVQdWJsaWNUbHNEbnNDcmVkZW50aWFsUmVzcG9uc2USOAoKY3JlZGVudGlhbBgBIAEoCzIkLnAycHN0cmVhbS52MS5QdWJsaWNUbHNEbnNDcmVkZW50aWFsIskBCiNVcGRhdGVQdWJsaWNUbHNEbnNDcmVkZW50aWFsUmVxdWVzdBIKCgJpZBgBIAEoAxIMCgRuYW1lGAIgASgJEjEKCHByb3ZpZGVyGAMgASgOMh8ucDJwc3RyZWFtLnYxLlB1YmxpY0Ruc1Byb3ZpZGVyEhoKEmNsb3VkZmxhcmVfem9uZV9pZBgEIAEoCRIRCglhcGlfdG9rZW4YBSABKAkSFQoNYXBpX3Rva2VuX3NldBgGIAEoCBIPCgdlbmFibGVkGAcgASgIImAKJFVwZGF0ZVB1YmxpY1Rsc0Ruc0NyZWRlbnRpYWxSZXNwb25zZRI4CgpjcmVkZW50aWFsGAEgASgLMiQucDJwc3RyZWFtLnYxLlB1YmxpY1Rsc0Ruc0NyZWRlbnRpYWwiMQojRGVsZXRlUHVibGljVGxzRG5zQ3JlZGVudGlhbFJlcXVlc3QSCgoCaWQYASABKAMiJgokRGVsZXRlUHVibGljVGxzRG5zQ3JlZGVudGlhbFJlc3BvbnNlIsYDCiFDcmVhdGVQdWJsaWNUbHNDZXJ0aWZpY2F0ZVJlcXVlc3QSEwoLbGlzdGVuZXJfaWQYASABKAMSGAoQaG9zdG5hbWVfcGF0dGVybhgCIAEoCRIRCgljZXJ0X3BhdGgYAyABKAkSEAoIa2V5X3BhdGgYBCABKAkSDwoHZW5hYmxlZBgFIAEoCBIQCghjZXJ0X3BlbRgGIAEoDBIPCgdrZXlfcGVtGAcgASgMEjgKBnNvdXJjZRgIIAEoDjIoLnAycHN0cmVhbS52MS5QdWJsaWNUbHNDZXJ0aWZpY2F0ZVNvdXJjZRJCChNhY21lX2NoYWxsZW5nZV90eXBlGAkgASgOMiUucDJwc3RyZWFtLnYxLlB1YmxpY0FjbWVDaGFsbGVuZ2VUeXBlEisKB2FjbWVfY2EYCiABKA4yGi5wMnBzdHJlYW0udjEuUHVibGljQWNtZUNhEhIKCmFjbWVfZW1haWwYCyABKAkSGQoRZG5zX2NyZWRlbnRpYWxfaWQYDCABKAMSHAoUZ2VuZXJhdGVfc2VsZl9zaWduZWQYDSABKAgSIQoZc2VsZl9zaWduZWRfdmFsaWRpdHlfZGF5cxgOIAEoAyJhCiJDcmVhdGVQdWJsaWNUbHNDZXJ0aWZpY2F0ZVJlc3BvbnNlEjsKD3Rsc19jZXJ0aWZpY2F0ZRgBIAEoCzIiLnAycHN0cmVhbS52MS5QdWJsaWNUbHNDZXJ0aWZpY2F0ZSLSAwohVXBkYXRlUHVibGljVGxzQ2VydGlmaWNhdGVSZXF1ZXN0EgoKAmlkGAEgASgDEhMKC2xpc3RlbmVyX2lkGAIgASgDEhgKEGhvc3RuYW1lX3BhdHRlcm4YAyABKAkSEQoJY2VydF9wYXRoGAQgASgJEhAKCGtleV9wYXRoGAUgASgJEg8KB2VuYWJsZWQYBiABKAgSEAoIY2VydF9wZW0YByABKAwSDwoHa2V5X3BlbRgIIAEoDBI4CgZzb3VyY2UYCSABKA4yKC5wMnBzdHJlYW0udjEuUHVibGljVGxzQ2VydGlmaWNhdGVTb3VyY2USQgoTYWNtZV9jaGFsbGVuZ2VfdHlwZRgKIAEoDjIlLnAycHN0cmVhbS52MS5QdWJsaWNBY21lQ2hhbGxlbmdlVHlwZRIrCgdhY21lX2NhGAsgASgOMhoucDJwc3RyZWFtLnYxLlB1YmxpY0FjbWVDYRISCgphY21lX2VtYWlsGAwgASgJEhkKEWRuc19jcmVkZW50aWFsX2lkGA0gASgDEhwKFGdlbmVyYXRlX3NlbGZfc2lnbmVkGA4gASgIEiEKGXNlbGZfc2lnbmVkX3ZhbGlkaXR5X2RheXMYDyABKAMiYQoiVXBkYXRlUHVibGljVGxzQ2VydGlmaWNhdGVSZXNwb25zZRI7Cg90bHNfY2VydGlmaWNhdGUYASABKAsyIi5wMnBzdHJlYW0udjEuUHVibGljVGxzQ2VydGlmaWNhdGUiLwohRGVsZXRlUHVibGljVGxzQ2VydGlmaWNhdGVSZXF1ZXN0EgoKAmlkGAEgASgDIiQKIkRlbGV0ZVB1YmxpY1Rsc0NlcnRpZmljYXRlUmVzcG9uc2UiLgogUmVuZXdQdWJsaWNUbHNDZXJ0aWZpY2F0ZVJlcXVlc3QSCgoCaWQYASABKAMiYAohUmVuZXdQdWJsaWNUbHNDZXJ0aWZpY2F0ZVJlc3BvbnNlEjsKD3Rsc19jZXJ0aWZpY2F0ZRgBIAEoCzIiLnAycHN0cmVhbS52MS5QdWJsaWNUbHNDZXJ0aWZpY2F0ZSLCBAogQ3JlYXRlUHVibGljUmF0ZUxpbWl0UnVsZVJlcXVlc3QSDAoEbmFtZRgBIAEoCRIQCghwcmlvcml0eRgCIAEoAxIPCgdlbmFibGVkGAMgASgIEjkKCWFsZ29yaXRobRgEIAEoDjImLnAycHN0cmVhbS52MS5QdWJsaWNSYXRlTGltaXRBbGdvcml0aG0SDQoFbGltaXQYBSABKAMSFQoNd2luZG93X21pbGxpcxgGIAEoAxINCgVidXJzdBgHIAEoAxI3CglrZXlfcGFydHMYCSADKAsyJC5wMnBzdHJlYW0udjEuUHVibGljUmF0ZUxpbWl0S2V5UGFydBIcChRyZXNwb25zZV9zdGF0dXNfY29kZRgKIAEoAxIVCg1yZXNwb25zZV9ib2R5GAsgASgJEh0KFXJlc3BvbnNlX2NvbnRlbnRfdHlwZRgMIAEoCRJFChByZXNwb25zZV9oZWFkZXJzGA0gAygLMisucDJwc3RyZWFtLnYxLlB1YmxpY1JhdGVMaW1pdFJlc3BvbnNlSGVhZGVyEkAKEnJlc3BvbnNlX2JvZHlfbW9kZRgOIAEoDjIkLnAycHN0cmVhbS52MS5QdWJsaWNSZXNwb25zZUJvZHlNb2RlEiEKGXJlc3BvbnNlX2JvZHlfdGVtcGxhdGVfaWQYDyABKAMSNwoKbWF0Y2hfcnVsZRgQIAEoCzIjLnAycHN0cmVhbS52MS5QdWJsaWNQb2xpY3lNYXRjaFJ1bGVKBAgIEAlSBW1hdGNoIlQKIUNyZWF0ZVB1YmxpY1JhdGVMaW1pdFJ1bGVSZXNwb25zZRIvCgRydWxlGAEgASgLMiEucDJwc3RyZWFtLnYxLlB1YmxpY1JhdGVMaW1pdFJ1bGUizgQKIFVwZGF0ZVB1YmxpY1JhdGVMaW1pdFJ1bGVSZXF1ZXN0EgoKAmlkGAEgASgDEgwKBG5hbWUYAiABKAkSEAoIcHJpb3JpdHkYAyABKAMSDwoHZW5hYmxlZBgEIAEoCBI5CglhbGdvcml0aG0YBSABKA4yJi5wMnBzdHJlYW0udjEuUHVibGljUmF0ZUxpbWl0QWxnb3JpdGhtEg0KBWxpbWl0GAYgASgDEhUKDXdpbmRvd19taWxsaXMYByABKAMSDQoFYnVyc3QYCCABKAMSNwoJa2V5X3BhcnRzGAogAygLMiQucDJwc3RyZWFtLnYxLlB1YmxpY1JhdGVMaW1pdEtleVBhcnQSHAoUcmVzcG9uc2Vfc3RhdHVzX2NvZGUYCyABKAMSFQoNcmVzcG9uc2VfYm9keRgMIAEoCRIdChVyZXNwb25zZV9jb250ZW50X3R5cGUYDSABKAkSRQoQcmVzcG9uc2VfaGVhZGVycxgOIAMoCzIrLnAycHN0cmVhbS52MS5QdWJsaWNSYXRlTGltaXRSZXNwb25zZUhlYWRlchJAChJyZXNwb25zZV9ib2R5X21vZGUYDyABKA4yJC5wMnBzdHJlYW0udjEuUHVibGljUmVzcG9uc2VCb2R5TW9kZRIhChlyZXNwb25zZV9ib2R5X3RlbXBsYXRlX2lkGBAgASgDEjcKCm1hdGNoX3J1bGUYESABKAsyIy5wMnBzdHJlYW0udjEuUHVibGljUG9saWN5TWF0Y2hSdWxlSgQICRAKUgVtYXRjaCJUCiFVcGRhdGVQdWJsaWNSYXRlTGltaXRSdWxlUmVzcG9uc2USLwoEcnVsZRgBIAEoCzIhLnAycHN0cmVhbS52MS5QdWJsaWNSYXRlTGltaXRSdWxlIi4KIERlbGV0ZVB1YmxpY1JhdGVMaW1pdFJ1bGVSZXF1ZXN0EgoKAmlkGAEgASgDIiMKIURlbGV0ZVB1YmxpY1JhdGVMaW1pdFJ1bGVSZXNwb25zZSKwAwokQ3JlYXRlUHVibGljVHJhZmZpY1NoYXBlclJ1bGVSZXF1ZXN0EgwKBG5hbWUYASABKAkSEAoIcHJpb3JpdHkYAiABKAMSDwoHZW5hYmxlZBgDIAEoCBJCCgxidWRnZXRfc2NvcGUYBCABKA4yLC5wMnBzdHJlYW0udjEuUHVibGljVHJhZmZpY1NoYXBlckJ1ZGdldFNjb3BlEh8KF3VwbG9hZF9ieXRlc19wZXJfc2Vjb25kGAUgASgDEiEKGWRvd25sb2FkX2J5dGVzX3Blcl9zZWNvbmQYBiABKAMSEwoLYnVyc3RfYnl0ZXMYByABKAMSHAoUcmVxdWVzdF9leGVtcHRfYnl0ZXMYCCABKAMSHQoVcmVzcG9uc2VfZXhlbXB0X2J5dGVzGAkgASgDEjcKCWtleV9wYXJ0cxgLIAMoCzIkLnAycHN0cmVhbS52MS5QdWJsaWNSYXRlTGltaXRLZXlQYXJ0EjcKCm1hdGNoX3J1bGUYDCABKAsyIy5wMnBzdHJlYW0udjEuUHVibGljUG9saWN5TWF0Y2hSdWxlSgQIChALUgVtYXRjaCJcCiVDcmVhdGVQdWJsaWNUcmFmZmljU2hhcGVyUnVsZVJlc3BvbnNlEjMKBHJ1bGUYASABKAsyJS5wMnBzdHJlYW0udjEuUHVibGljVHJhZmZpY1NoYXBlclJ1bGUivAMKJFVwZGF0ZVB1YmxpY1RyYWZmaWNTaGFwZXJSdWxlUmVxdWVzdBIKCgJpZBgBIAEoAxIMCgRuYW1lGAIgASgJEhAKCHByaW9yaXR5GAMgASgDEg8KB2VuYWJsZWQYBCABKAgSQgoMYnVkZ2V0X3Njb3BlGAUgASgOMiwucDJwc3RyZWFtLnYxLlB1YmxpY1RyYWZmaWNTaGFwZXJCdWRnZXRTY29wZRIfChd1cGxvYWRfYnl0ZXNfcGVyX3NlY29uZBgGIAEoAxIhChlkb3dubG9hZF9ieXRlc19wZXJfc2Vjb25kGAcgASgDEhMKC2J1cnN0X2J5dGVzGAggASgDEhwKFHJlcXVlc3RfZXhlbXB0X2J5dGVzGAkgASgDEh0KFXJlc3BvbnNlX2V4ZW1wdF9ieXRlcxgKIAEoAxI3CglrZXlfcGFydHMYDCADKAsyJC5wMnBzdHJlYW0udjEuUHVibGljUmF0ZUxpbWl0S2V5UGFydBI3CgptYXRjaF9ydWxlGA0gASgLMiMucDJwc3RyZWFtLnYxLlB1YmxpY1BvbGljeU1hdGNoUnVsZUoECAsQDFIFbWF0Y2giXAolVXBkYXRlUHVibGljVHJhZmZpY1NoYXBlclJ1bGVSZXNwb25zZRIzCgRydWxlGAEgASgLMiUucDJwc3RyZWFtLnYxLlB1YmxpY1RyYWZmaWNTaGFwZXJSdWxlIjIKJERlbGV0ZVB1YmxpY1RyYWZmaWNTaGFwZXJSdWxlUmVxdWVzdBIKCgJpZBgBIAEoAyInCiVEZWxldGVQdWJsaWNUcmFmZmljU2hhcGVyUnVsZVJlc3BvbnNlIq8BCiVDcmVhdGVQdWJsaWNXYWZDYXB0Y2hhUHJvdmlkZXJSZXF1ZXN0EgwKBG5hbWUYASABKAkSQQoNcHJvdmlkZXJfdHlwZRgCIAEoDjIqLnAycHN0cmVhbS52MS5QdWJsaWNXYWZDYXB0Y2hhUHJvdmlkZXJUeXBlEhAKCHNpdGVfa2V5GAMgASgJEhIKCnNlY3JldF9rZXkYBCABKAkSDwoHZW5hYmxlZBgFIAEoCCJiCiZDcmVhdGVQdWJsaWNXYWZDYXB0Y2hhUHJvdmlkZXJSZXNwb25zZRI4Cghwcm92aWRlchgBIAEoCzImLnAycHN0cmVhbS52MS5QdWJsaWNXYWZDYXB0Y2hhUHJvdmlkZXIi0wEKJVVwZGF0ZVB1YmxpY1dhZkNhcHRjaGFQcm92aWRlclJlcXVlc3QSCgoCaWQYASABKAMSDAoEbmFtZRgCIAEoCRJBCg1wcm92aWRlcl90eXBlGAMgASgOMioucDJwc3RyZWFtLnYxLlB1YmxpY1dhZkNhcHRjaGFQcm92aWRlclR5cGUSEAoIc2l0ZV9rZXkYBCABKAkSEgoKc2VjcmV0X2tleRgFIAEoCRIWCg5zZWNyZXRfa2V5X3NldBgGIAEoCBIPCgdlbmFibGVkGAcgASgIImIKJlVwZGF0ZVB1YmxpY1dhZkNhcHRjaGFQcm92aWRlclJlc3BvbnNlEjgKCHByb3ZpZGVyGAEgASgLMiYucDJwc3RyZWFtLnYxLlB1YmxpY1dhZkNhcHRjaGFQcm92aWRlciIzCiVEZWxldGVQdWJsaWNXYWZDYXB0Y2hhUHJvdmlkZXJSZXF1ZXN0EgoKAmlkGAEgASgDIigKJkRlbGV0ZVB1YmxpY1dhZkNhcHRjaGFQcm92aWRlclJlc3BvbnNlIt0GChpDcmVhdGVQdWJsaWNXYWZSdWxlUmVxdWVzdBIMCgRuYW1lGAEgASgJEhAKCHByaW9yaXR5GAIgASgDEg8KB2VuYWJsZWQYAyABKAgSMQoGYWN0aW9uGAQgASgOMiEucDJwc3RyZWFtLnYxLlB1YmxpY1dhZlJ1bGVBY3Rpb24SPgoPYWN0aXZhdGlvbl9tb2RlGAUgASgOMiUucDJwc3RyZWFtLnYxLlB1YmxpY1dhZkFjdGl2YXRpb25Nb2RlEjcKCWtleV9wYXJ0cxgHIAMoCzIkLnAycHN0cmVhbS52MS5QdWJsaWNSYXRlTGltaXRLZXlQYXJ0EhsKE2NhcHRjaGFfcHJvdmlkZXJfaWQYCCABKAMSHwoXY2FwdGNoYV9wYXNzX3R0bF9taWxsaXMYCSABKAMSPgoMd2FpdGluZ19yb29tGAogASgLMigucDJwc3RyZWFtLnYxLlB1YmxpY1dhZldhaXRpbmdSb29tQ29uZmlnEjYKCHRyaWdnZXJzGAsgASgLMiQucDJwc3RyZWFtLnYxLlB1YmxpY1dhZlRyaWdnZXJDb25maWcSIgoaYmxvY2tfcmVzcG9uc2Vfc3RhdHVzX2NvZGUYDCABKAMSGwoTYmxvY2tfcmVzcG9uc2VfYm9keRgNIAEoCRIjChtibG9ja19yZXNwb25zZV9jb250ZW50X3R5cGUYDiABKAkSSwoWYmxvY2tfcmVzcG9uc2VfaGVhZGVycxgPIAMoCzIrLnAycHN0cmVhbS52MS5QdWJsaWNSYXRlTGltaXRSZXNwb25zZUhlYWRlchJGChhibG9ja19yZXNwb25zZV9ib2R5X21vZGUYECABKA4yJC5wMnBzdHJlYW0udjEuUHVibGljUmVzcG9uc2VCb2R5TW9kZRIiChpibG9ja19yZXNwb25zZV90ZW1wbGF0ZV9pZBgRIAEoAxIgChhjYXB0Y2hhX3BhZ2VfdGVtcGxhdGVfaWQYEiABKAMSJQodd2FpdGluZ19yb29tX3BhZ2VfdGVtcGxhdGVfaWQYEyABKAMSNwoKbWF0Y2hfcnVsZRgUIAEoCzIjLnAycHN0cmVhbS52MS5QdWJsaWNQb2xpY3lNYXRjaFJ1bGVKBAgGEAdSBW1hdGNoIkgKG0NyZWF0ZVB1YmxpY1dhZlJ1bGVSZXNwb25zZRIpCgRydWxlGAEgASgLMhsucDJwc3RyZWFtLnYxLlB1YmxpY1dhZlJ1bGUi6QYKGlVwZGF0ZVB1YmxpY1dhZlJ1bGVSZXF1ZXN0EgoKAmlkGAEgASgDEgwKBG5hbWUYAiABKAkSEAoIcHJpb3JpdHkYAyABKAMSDwoHZW5hYmxlZBgEIAEoCBIxCgZhY3Rpb24YBSABKA4yIS5wMnBzdHJlYW0udjEuUHVibGljV2FmUnVsZUFjdGlvbhI+Cg9hY3RpdmF0aW9uX21vZGUYBiABKA4yJS5wMnBzdHJlYW0udjEuUHVibGljV2FmQWN0aXZhdGlvbk1vZGUSNwoJa2V5X3BhcnRzGAggAygLMiQucDJwc3RyZWFtLnYxLlB1YmxpY1JhdGVMaW1pdEtleVBhcnQSGwoTY2FwdGNoYV9wcm92aWRlcl9pZBgJIAEoAxIfChdjYXB0Y2hhX3Bhc3NfdHRsX21pbGxpcxgKIAEoAxI+Cgx3YWl0aW5nX3Jvb20YCyABKAsyKC5wMnBzdHJlYW0udjEuUHVibGljV2FmV2FpdGluZ1Jvb21Db25maWcSNgoIdHJpZ2dlcnMYDCABKAsyJC5wMnBzdHJlYW0udjEuUHVibGljV2FmVHJpZ2dlckNvbmZpZxIiChpibG9ja19yZXNwb25zZV9zdGF0dXNfY29kZRgNIAEoAxIbChNibG9ja19yZXNwb25zZV9ib2R5GA4gASgJEiMKG2Jsb2NrX3Jlc3BvbnNlX2NvbnRlbnRfdHlwZRgPIAEoCRJLChZibG9ja19yZXNwb25zZV9oZWFkZXJzGBAgAygLMisucDJwc3RyZWFtLnYxLlB1YmxpY1JhdGVMaW1pdFJlc3BvbnNlSGVhZGVyEkYKGGJsb2NrX3Jlc3BvbnNlX2JvZHlfbW9kZRgRIAEoDjIkLnAycHN0cmVhbS52MS5QdWJsaWNSZXNwb25zZUJvZHlNb2RlEiIKGmJsb2NrX3Jlc3BvbnNlX3RlbXBsYXRlX2lkGBIgASgDEiAKGGNhcHRjaGFfcGFnZV90ZW1wbGF0ZV9pZBgTIAEoAxIlCh13YWl0aW5nX3Jvb21fcGFnZV90ZW1wbGF0ZV9pZBgUIAEoAxI3CgptYXRjaF9ydWxlGBUgASgLMiMucDJwc3RyZWFtLnYxLlB1YmxpY1BvbGljeU1hdGNoUnVsZUoECAcQCFIFbWF0Y2giSAobVXBkYXRlUHVibGljV2FmUnVsZVJlc3BvbnNlEikKBHJ1bGUYASABKAsyGy5wMnBzdHJlYW0udjEuUHVibGljV2FmUnVsZSIoChpEZWxldGVQdWJsaWNXYWZSdWxlUmVxdWVzdBIKCgJpZBgBIAEoAyIdChtEZWxldGVQdWJsaWNXYWZSdWxlUmVzcG9uc2UizAQKHENyZWF0ZVB1YmxpY0NhY2hlUnVsZVJlcXVlc3QSDAoEbmFtZRgBIAEoCRIQCghwcmlvcml0eRgCIAEoAxIPCgdlbmFibGVkGAMgASgIEhEKCXJvdXRlX2lkcxgFIAMoAxItCgVzY29wZRgHIAEoDjIeLnAycHN0cmVhbS52MS5QdWJsaWNDYWNoZVNjb3BlEjIKCHR0bF9tb2RlGAggASgOMiAucDJwc3RyZWFtLnYxLlB1YmxpY0NhY2hlVHRsTW9kZRISCgp0dGxfbWlsbGlzGAkgASgDEjYKCnF1ZXJ5X21vZGUYCiABKA4yIi5wMnBzdHJlYW0udjEuUHVibGljQ2FjaGVRdWVyeU1vZGUSFAoMcXVlcnlfcGFyYW1zGAsgAygJEhQKDHZhcnlfaGVhZGVycxgMIAMoCRIaChJjYWNoZV9zdGF0dXNfY29kZXMYDSADKAMSGAoQbWF4X29iamVjdF9ieXRlcxgOIAEoAxIfChdhZGRfY2FjaGVfc3RhdHVzX2hlYWRlchgPIAEoCBIdChVhbGxvd19jb29raWVfcmVxdWVzdHMYECABKAgSNwoKbWF0Y2hfcnVsZRgRIAEoCzIjLnAycHN0cmVhbS52MS5QdWJsaWNQb2xpY3lNYXRjaFJ1bGUSKgoiYWxsb3dfY29va2llX3JlcXVlc3RzX2Fja25vd2xlZGdlZBgSIAEoCBISCgp0YXJnZXRfaWRzGBMgAygDSgQIBBAFSgQIBhAHUgVtYXRjaFILYmFja2VuZF9pZHMiTAodQ3JlYXRlUHVibGljQ2FjaGVSdWxlUmVzcG9uc2USKwoEcnVsZRgBIAEoCzIdLnAycHN0cmVhbS52MS5QdWJsaWNDYWNoZVJ1bGUi2AQKHFVwZGF0ZVB1YmxpY0NhY2hlUnVsZVJlcXVlc3QSCgoCaWQYASABKAMSDAoEbmFtZRgCIAEoCRIQCghwcmlvcml0eRgDIAEoAxIPCgdlbmFibGVkGAQgASgIEhEKCXJvdXRlX2lkcxgGIAMoAxItCgVzY29wZRgIIAEoDjIeLnAycHN0cmVhbS52MS5QdWJsaWNDYWNoZVNjb3BlEjIKCHR0bF9tb2RlGAkgASgOMiAucDJwc3RyZWFtLnYxLlB1YmxpY0NhY2hlVHRsTW9kZRISCgp0dGxfbWlsbGlzGAogASgDEjYKCnF1ZXJ5X21vZGUYCyABKA4yIi5wMnBzdHJlYW0udjEuUHVibGljQ2FjaGVRdWVyeU1vZGUSFAoMcXVlcnlfcGFyYW1zGAwgAygJEhQKDHZhcnlfaGVhZGVycxgNIAMoCRIaChJjYWNoZV9zdGF0dXNfY29kZXMYDiADKAMSGAoQbWF4X29iamVjdF9ieXRlcxgPIAEoAxIfChdhZGRfY2FjaGVfc3RhdHVzX2hlYWRlchgQIAEoCBIdChVhbGxvd19jb29raWVfcmVxdWVzdHMYESABKAgSNwoKbWF0Y2hfcnVsZRgSIAEoCzIjLnAycHN0cmVhbS52MS5QdWJsaWNQb2xpY3lNYXRjaFJ1bGUSKgoiYWxsb3dfY29va2llX3JlcXVlc3RzX2Fja25vd2xlZGdlZBgTIAEoCBISCgp0YXJnZXRfaWRzGBQgAygDSgQIBRAGSgQIBxAIUgVtYXRjaFILYmFja2VuZF9pZHMiTAodVXBkYXRlUHVibGljQ2FjaGVSdWxlUmVzcG9uc2USKwoEcnVsZRgBIAEoCzIdLnAycHN0cmVhbS52MS5QdWJsaWNDYWNoZVJ1bGUiKgocRGVsZXRlUHVibGljQ2FjaGVSdWxlUmVxdWVzdBIKCgJpZBgBIAEoAyIfCh1EZWxldGVQdWJsaWNDYWNoZVJ1bGVSZXNwb25zZSLAAQogVXBkYXRlUHVibGljQ2FjaGVTZXR0aW5nc1JlcXVlc3QSDwoHZW5hYmxlZBgBIAEoCBIWCg5tYXhfZGlza19ieXRlcxgCIAEoAxIYChBtYXhfbWVtb3J5X2J5dGVzGAMgASgDEiMKG21lbW9yeV9ob3Rfb2JqZWN0X21heF9ieXRlcxgEIAEoAxITCgttYXhfZW50cmllcxgFIAEoAxIfChdjbGVhbnVwX2ludGVydmFsX21pbGxpcxgGIAEoAyJYCiFVcGRhdGVQdWJsaWNDYWNoZVNldHRpbmdzUmVzcG9uc2USMwoIc2V0dGluZ3MYASABKAsyIS5wMnBzdHJlYW0udjEuUHVibGljQ2FjaGVTZXR0aW5ncyJaChdQdXJnZVB1YmxpY0NhY2hlUmVxdWVzdBILCgNhbGwYASABKAgSDwoHcnVsZV9pZBgCIAEoAxIMCgRob3N0GAMgASgJEhMKC3BhdGhfcHJlZml4GAQgASgJIkgKGFB1cmdlUHVibGljQ2FjaGVSZXNwb25zZRIWCg5wdXJnZWRfZW50cmllcxgBIAEoAxIUCgxwdXJnZWRfYnl0ZXMYAiABKAMiFQoTR2V0RGFzaGJvYXJkUmVxdWVzdCKOCAoWRGFzaGJvYXJkV2luZG93U3VtbWFyeRINCgVsYWJlbBgBIAEoCRIZChFzaW5jZV91bml4X21pbGxpcxgCIAEoAxIWCg5wcm94eV9yZXF1ZXN0cxgDIAEoAxIVCg1wcm94eV9zdWNjZXNzGAQgASgDEhoKEnByb3h5X2NsaWVudF9lcnJvchgFIAEoAxIaChJwcm94eV9zZXJ2ZXJfZXJyb3IYBiABKAMSHAoUcHJveHlfaW50ZXJuYWxfZXJyb3IYByABKAMSHQoVcHJveHlfYXZnX2R1cmF0aW9uX21zGAggASgDEhUKDWFnZW50X3NhbXBsZXMYCSABKAMSGQoRYWdlbnRfcmVxX3N1Y2Nlc3MYCiABKAMSHgoWYWdlbnRfcmVxX2NsaWVudF9lcnJvchgLIAEoAxIeChZhZ2VudF9yZXFfc2VydmVyX2Vycm9yGAwgASgDEiAKGGFnZW50X3JlcV9pbnRlcm5hbF9lcnJvchgNIAEoAxIcChRhZ2VudF9ieXRlc19yZWNlaXZlZBgOIAEoBBIYChBhZ2VudF9ieXRlc19zZW50GA8gASgEEhsKE2FnZW50X2F2Z19tZW1vcnlfbWIYECABKAMSGwoTYWdlbnRfbWF4X21lbW9yeV9tYhgRIAEoAxIcChRhZ2VudF9hdmdfZ29yb3V0aW5lcxgSIAEoAxIcChRhZ2VudF9tYXhfZ29yb3V0aW5lcxgTIAEoAxIbChNwcm94eV9yZXF1ZXN0X2J5dGVzGBQgASgEEhwKFHByb3h5X3Jlc3BvbnNlX2J5dGVzGBUgASgEEhkKEXByb3h5X3RvdGFsX2J5dGVzGBYgASgEEh8KF3Byb3h5X2F2Z19yZXF1ZXN0X2J5dGVzGBcgASgEEiAKGHByb3h5X2F2Z19yZXNwb25zZV9ieXRlcxgYIAEoBBIdChVwcm94eV9tYXhfZHVyYXRpb25fbXMYGSABKAMSGwoTcHJveHlfc2xvd19yZXF1ZXN0cxgaIAEoAxIdChVhZ2VudF9hdmdfY3B1X3BlcmNlbnQYGyABKAESHQoVYWdlbnRfbWF4X2NwdV9wZXJjZW50GBwgASgBEhgKEHByb3h5X2NhY2hlX2hpdHMYHSABKAMSGgoScHJveHlfY2FjaGVfbWlzc2VzGB4gASgDEhwKFHByb3h5X2NhY2hlX2J5cGFzc2VzGB8gASgDEhoKEnByb3h5X2NhY2hlX3N0b3JlZBggIAEoAxIgChhwcm94eV9jYWNoZV9zdG9yZV9mYWlsZWQYISABKAMSHQoVcHJveHlfY2FjaGVfaGl0X2J5dGVzGCIgASgEEiAKGHByb3h5X2NhY2hlX3N0b3JlZF9ieXRlcxgjIAEoBCKkAgoeRGFzaGJvYXJkUHJveHlEaW1lbnNpb25TdW1tYXJ5EjgKCWRpbWVuc2lvbhgBIAEoDjIlLnAycHN0cmVhbS52MS5EYXNoYm9hcmRQcm94eURpbWVuc2lvbhIKCgJpZBgCIAEoAxINCgVsYWJlbBgDIAEoCRIQCghyZXF1ZXN0cxgEIAEoAxIPCgdzdWNjZXNzGAUgASgDEhQKDGNsaWVudF9lcnJvchgGIAEoAxIUCgxzZXJ2ZXJfZXJyb3IYByABKAMSFgoOaW50ZXJuYWxfZXJyb3IYCCABKAMSFwoPYXZnX2R1cmF0aW9uX21zGAkgASgDEhUKDXJlcXVlc3RfYnl0ZXMYCiABKAQSFgoOcmVzcG9uc2VfYnl0ZXMYCyABKAQi4wEKFkRhc2hib2FyZFRyYWZmaWNCdWNrZXQSGgoSYnVja2V0X3VuaXhfbWlsbGlzGAEgASgDEhAKCHJlcXVlc3RzGAIgASgDEg8KB3N1Y2Nlc3MYAyABKAMSFAoMY2xpZW50X2Vycm9yGAQgASgDEhQKDHNlcnZlcl9lcnJvchgFIAEoAxIWCg5pbnRlcm5hbF9lcnJvchgGIAEoAxIVCg1yZXF1ZXN0X2J5dGVzGAcgASgEEhYKDnJlc3BvbnNlX2J5dGVzGAggASgEEhcKD2F2Z19kdXJhdGlvbl9tcxgJIAEoAyKdAgoSTWFuYWdlbWVudFNlY3VyaXR5EhMKC3Rsc19lbmFibGVkGAEgASgIEhAKCGF1dG9fdGxzGAIgASgIEh0KFWluc2VjdXJlX2h0dHBfYWxsb3dlZBgDIAEoCBIcChRhZ2VudF9odHRwc19yZXF1aXJlZBgEIAEoCBIpCiFhZ2VudF9jbGllbnRfY2VydGlmaWNhdGVfcmVxdWlyZWQYBSABKAgSHgoWZGVmYXVsdF9tYW5hZ2VtZW50X3VybBgGIAEoCRIZChFtYW5hZ2VtZW50X2NhX3BlbRgHIAEoCRIcChRtYW5hZ2VtZW50X2NhX3NoYTI1NhgIIAEoCRIfChdkZXRlY3RlZF9hZHZlcnRpc2VfaG9zdBgJIAEoCSLAAQoWQWdlbnRDb25uZWN0aW9uU3VtbWFyeRIRCgljb25uZWN0ZWQYASABKAgSGQoRdG90YWxfY29ubmVjdGlvbnMYAiABKAMSJwofYWN0aXZlX2Nvbm5lY3RlZF9hdF91bml4X21pbGxpcxgDIAEoAxIlCh1sYXN0X2Nvbm5lY3RlZF9hdF91bml4X21pbGxpcxgEIAEoAxIoCiBsYXN0X2Rpc2Nvbm5lY3RlZF9hdF91bml4X21pbGxpcxgFIAEoAyKhBAoSQWdlbnRVcHRpbWVTdW1tYXJ5EhAKCGFnZW50X2lkGAEgASgDEhcKD2FnZW50X3B1YmxpY19pZBgCIAEoCRISCgphZ2VudF9uYW1lGAMgASgJEg8KB2VuYWJsZWQYBCABKAgSEQoJY29ubmVjdGVkGAUgASgIEigKIGN1cnJlbnRfY29ubmVjdGVkX2F0X3VuaXhfbWlsbGlzGAYgASgDEh0KFWN1cnJlbnRfdXB0aW1lX21pbGxpcxgHIAEoAxIpCiFjdXJyZW50X29mZmxpbmVfc2luY2VfdW5peF9taWxsaXMYCCABKAMSHwoXY3VycmVudF9kb3dudGltZV9taWxsaXMYCSABKAMSFQoNdXB0aW1lX21pbGxpcxgKIAEoAxIXCg9kb3dudGltZV9taWxsaXMYCyABKAMSFgoOdXB0aW1lX3BlcmNlbnQYDCABKAESGAoQY29ubmVjdGlvbl9jb3VudBgNIAEoAxIYChBkaXNjb25uZWN0X2NvdW50GA4gASgDEiIKGm9ic2VydmVkX3NpbmNlX3VuaXhfbWlsbGlzGA8gASgDEiIKGm9ic2VydmVkX3VudGlsX3VuaXhfbWlsbGlzGBAgASgDEiUKHWxhc3RfY29ubmVjdGVkX2F0X3VuaXhfbWlsbGlzGBEgASgDEigKIGxhc3RfZGlzY29ubmVjdGVkX2F0X3VuaXhfbWlsbGlzGBIgASgDItMBChZBZ2VudENvbm5lY3Rpb25TZXNzaW9uEgoKAmlkGAEgASgDEhAKCGFnZW50X2lkGAIgASgDEhcKD2FnZW50X3B1YmxpY19pZBgDIAEoCRISCgphZ2VudF9uYW1lGAQgASgJEiAKGGNvbm5lY3RlZF9hdF91bml4X21pbGxpcxgFIAEoAxIjChtkaXNjb25uZWN0ZWRfYXRfdW5peF9taWxsaXMYBiABKAMSFwoPZHVyYXRpb25fbWlsbGlzGAcgASgDEg4KBmFjdGl2ZRgIIAEoCCK0BwoUR2V0RGFzaGJvYXJkUmVzcG9uc2USLwoGc3RhdHVzGAEgASgLMh8ucDJwc3RyZWFtLnYxLkdldFN0YXR1c1Jlc3BvbnNlEjUKB3dpbmRvd3MYAiADKAsyJC5wMnBzdHJlYW0udjEuRGFzaGJvYXJkV2luZG93U3VtbWFyeRI/ChFhZ2VudF9jb25uZWN0aW9ucxgDIAEoCzIkLnAycHN0cmVhbS52MS5BZ2VudENvbm5lY3Rpb25TdW1tYXJ5EhYKDnJldGVudGlvbl9kYXlzGAQgASgDEiAKGGdlbmVyYXRlZF9hdF91bml4X21pbGxpcxgFIAEoAxJDCg10b3BfbGlzdGVuZXJzGAYgAygLMiwucDJwc3RyZWFtLnYxLkRhc2hib2FyZFByb3h5RGltZW5zaW9uU3VtbWFyeRJACgp0b3Bfcm91dGVzGAggAygLMiwucDJwc3RyZWFtLnYxLkRhc2hib2FyZFByb3h5RGltZW5zaW9uU3VtbWFyeRJACgp0b3BfYWdlbnRzGAkgAygLMiwucDJwc3RyZWFtLnYxLkRhc2hib2FyZFByb3h5RGltZW5zaW9uU3VtbWFyeRJFCg90b3BfZXJyb3Jfa2luZHMYCiADKAsyLC5wMnBzdHJlYW0udjEuRGFzaGJvYXJkUHJveHlEaW1lbnNpb25TdW1tYXJ5EkQKDnN0YXR1c19jbGFzc2VzGAsgAygLMiwucDJwc3RyZWFtLnYxLkRhc2hib2FyZFByb3h5RGltZW5zaW9uU3VtbWFyeRI9Cg90cmFmZmljX2J1Y2tldHMYDCADKAsyJC5wMnBzdHJlYW0udjEuRGFzaGJvYXJkVHJhZmZpY0J1Y2tldBI9ChNtYW5hZ2VtZW50X3NlY3VyaXR5GA0gASgLMiAucDJwc3RyZWFtLnYxLk1hbmFnZW1lbnRTZWN1cml0eRJAChZhZ2VudF91cHRpbWVfc3VtbWFyaWVzGA4gAygLMiAucDJwc3RyZWFtLnYxLkFnZW50VXB0aW1lU3VtbWFyeRJGChhyZWNlbnRfYWdlbnRfY29ubmVjdGlvbnMYDyADKAsyJC5wMnBzdHJlYW0udjEuQWdlbnRDb25uZWN0aW9uU2Vzc2lvbhJHChF0b3Bfcm91dGVfdGFyZ2V0cxgQIAMoCzIsLnAycHN0cmVhbS52MS5EYXNoYm9hcmRQcm94eURpbWVuc2lvblN1bW1hcnlKBAgHEAhSDHRvcF9iYWNrZW5kcyJMCh5HZXREYXNoYm9hcmREaWFnbm9zdGljc1JlcXVlc3QSFAoMd2luZG93X2xhYmVsGAEgASgJEhQKDHNhbXBsZV9saW1pdBgCIAEoAyL7AQoiRGFzaGJvYXJkRGlhZ25vc3RpY3NPdXRjb21lU3VtbWFyeRINCgVsYWJlbBgBIAEoCRIZChFzaW5jZV91bml4X21pbGxpcxgCIAEoAxIQCghyZXF1ZXN0cxgDIAEoAxIPCgdzdWNjZXNzGAQgASgDEhQKDGNsaWVudF9lcnJvchgFIAEoAxIUCgxzZXJ2ZXJfZXJyb3IYBiABKAMSEwoLbm9uX3N1Y2Nlc3MYByABKAMSFQoNcHJveHlfZmFpbHVyZRgIIAEoAxIXCg9hdmdfZHVyYXRpb25fbXMYCSABKAMSFwoPbWF4X2R1cmF0aW9uX21zGAogASgDIt8BChpEYXNoYm9hcmRTdGF0dXNDb2RlU3VtbWFyeRITCgtzdGF0dXNfY29kZRgBIAEoAxIQCghyZXF1ZXN0cxgCIAEoAxIPCgdzdWNjZXNzGAMgASgDEhQKDGNsaWVudF9lcnJvchgEIAEoAxIUCgxzZXJ2ZXJfZXJyb3IYBSABKAMSFQoNcHJveHlfZmFpbHVyZRgGIAEoAxIXCg9hdmdfZHVyYXRpb25fbXMYByABKAMSFQoNcmVxdWVzdF9ieXRlcxgIIAEoBBIWCg5yZXNwb25zZV9ieXRlcxgJIAEoBCK7AgoaRGFzaGJvYXJkRGlhZ25vc3RpY3NTYW1wbGUSHwoXb2NjdXJyZWRfYXRfdW5peF9taWxsaXMYASABKAMSDgoGbWV0aG9kGAIgASgJEgwKBGhvc3QYAyABKAkSEwoLcGF0aF9wcmVmaXgYBCABKAkSEwoLc3RhdHVzX2NvZGUYBSABKAMSEgoKZXJyb3Jfa2luZBgGIAEoCRIWCg5saXN0ZW5lcl9sYWJlbBgHIAEoCRITCgtyb3V0ZV9sYWJlbBgIIAEoCRIaChJyb3V0ZV90YXJnZXRfbGFiZWwYCSABKAkSEwoLYWdlbnRfbGFiZWwYCiABKAkSEwoLZHVyYXRpb25fbXMYCyABKAMSFQoNcmVxdWVzdF9ieXRlcxgMIAEoBBIWCg5yZXNwb25zZV9ieXRlcxgNIAEoBCKXBQofR2V0RGFzaGJvYXJkRGlhZ25vc3RpY3NSZXNwb25zZRINCgVsYWJlbBgBIAEoCRIZChFzaW5jZV91bml4X21pbGxpcxgCIAEoAxIgChhnZW5lcmF0ZWRfYXRfdW5peF9taWxsaXMYAyABKAMSQQoHb3V0Y29tZRgEIAEoCzIwLnAycHN0cmVhbS52MS5EYXNoYm9hcmREaWFnbm9zdGljc091dGNvbWVTdW1tYXJ5Ej4KDHN0YXR1c19jb2RlcxgFIAMoCzIoLnAycHN0cmVhbS52MS5EYXNoYm9hcmRTdGF0dXNDb2RlU3VtbWFyeRJBCgtlcnJvcl9raW5kcxgGIAMoCzIsLnAycHN0cmVhbS52MS5EYXNoYm9hcmRQcm94eURpbWVuc2lvblN1bW1hcnkSRwoRcHJvYmxlbV9saXN0ZW5lcnMYByADKAsyLC5wMnBzdHJlYW0udjEuRGFzaGJvYXJkUHJveHlEaW1lbnNpb25TdW1tYXJ5EkQKDnByb2JsZW1fcm91dGVzGAggAygLMiwucDJwc3RyZWFtLnYxLkRhc2hib2FyZFByb3h5RGltZW5zaW9uU3VtbWFyeRJLChVwcm9ibGVtX3JvdXRlX3RhcmdldHMYCSADKAsyLC5wMnBzdHJlYW0udjEuRGFzaGJvYXJkUHJveHlEaW1lbnNpb25TdW1tYXJ5EkQKDnByb2JsZW1fYWdlbnRzGAogAygLMiwucDJwc3RyZWFtLnYxLkRhc2hib2FyZFByb3h5RGltZW5zaW9uU3VtbWFyeRJACg5yZWNlbnRfc2FtcGxlcxgLIAMoCzIoLnAycHN0cmVhbS52MS5EYXNoYm9hcmREaWFnbm9zdGljc1NhbXBsZSLBAQoUVHJhZmZpY1RyYWNlU2V0dGluZ3MSDwoHZW5hYmxlZBgBIAEoCBIuCgVsZXZlbBgCIAEoDjIfLnAycHN0cmVhbS52MS5UcmFmZmljVHJhY2VMZXZlbBIeChZ1cGRhdGVkX2F0X3VuaXhfbWlsbGlzGAMgASgDEhYKDmVtaXR0ZWRfZXZlbnRzGAQgASgEEhYKDmRyb3BwZWRfZXZlbnRzGAUgASgEEhgKEHN1YnNjcmliZXJfY291bnQYBiABKAMiIAoeR2V0VHJhZmZpY1RyYWNlU2V0dGluZ3NSZXF1ZXN0IlcKH0dldFRyYWZmaWNUcmFjZVNldHRpbmdzUmVzcG9uc2USNAoIc2V0dGluZ3MYASABKAsyIi5wMnBzdHJlYW0udjEuVHJhZmZpY1RyYWNlU2V0dGluZ3MiYQoeU2V0VHJhZmZpY1RyYWNlU2V0dGluZ3NSZXF1ZXN0Eg8KB2VuYWJsZWQYASABKAgSLgoFbGV2ZWwYAiABKA4yHy5wMnBzdHJlYW0udjEuVHJhZmZpY1RyYWNlTGV2ZWwiVwofU2V0VHJhZmZpY1RyYWNlU2V0dGluZ3NSZXNwb25zZRI0CghzZXR0aW5ncxgBIAEoCzIiLnAycHN0cmVhbS52MS5UcmFmZmljVHJhY2VTZXR0aW5ncyJQCh9TdHJlYW1UcmFmZmljVHJhY2VFdmVudHNSZXF1ZXN0EhUKDXJlcGxheV9yZWNlbnQYASABKAgSFgoOYWZ0ZXJfc2VxdWVuY2UYAiABKAQipg8KEVRyYWZmaWNUcmFjZUV2ZW50EhAKCHNlcXVlbmNlGAEgASgEEhIKCnJlcXVlc3RfaWQYAiABKAkSLgoFc3RhZ2UYAyABKA4yHy5wMnBzdHJlYW0udjEuVHJhZmZpY1RyYWNlU3RhZ2USHwoXb2NjdXJyZWRfYXRfdW5peF9taWxsaXMYBCABKAMSDgoGbWV0aG9kGAUgASgJEgwKBGhvc3QYBiABKAkSDAoEcGF0aBgHIAEoCRINCgVxdWVyeRgIIAEoCRITCgtsaXN0ZW5lcl9pZBgJIAEoAxIVCg1saXN0ZW5lcl9uYW1lGAogASgJEhAKCHJvdXRlX2lkGAsgASgDEhMKC3JvdXRlX2xhYmVsGAwgASgJEhUKDWRlZmF1bHRfcm91dGUYDSABKAgSFQoNdGFyZ2V0X29yaWdpbhgSIAEoCRIQCghhZ2VudF9pZBgTIAEoAxIXCg9hZ2VudF9wdWJsaWNfaWQYFCABKAkSEgoKYWdlbnRfbmFtZRgVIAEoCRITCgtzdGF0dXNfY29kZRgWIAEoAxITCgtkdXJhdGlvbl9tcxgXIAEoAxISCgplcnJvcl9raW5kGBggASgJEkwKD3JlcXVlc3RfaGVhZGVycxgZIAMoCzIzLnAycHN0cmVhbS52MS5UcmFmZmljVHJhY2VFdmVudC5SZXF1ZXN0SGVhZGVyc0VudHJ5Ek4KEHJlc3BvbnNlX2hlYWRlcnMYGiADKAsyNC5wMnBzdHJlYW0udjEuVHJhZmZpY1RyYWNlRXZlbnQuUmVzcG9uc2VIZWFkZXJzRW50cnkSFQoNcmVxdWVzdF9ieXRlcxgbIAEoBBIWCg5yZXNwb25zZV9ieXRlcxgcIAEoBBJOChBkZWJ1Z19hdHRyaWJ1dGVzGB0gAygLMjQucDJwc3RyZWFtLnYxLlRyYWZmaWNUcmFjZUV2ZW50LkRlYnVnQXR0cmlidXRlc0VudHJ5EhoKEnJhdGVfbGltaXRfcnVsZV9pZBgeIAEoAxIcChRyYXRlX2xpbWl0X3J1bGVfbmFtZRgfIAEoCRJEChRyYXRlX2xpbWl0X2FsZ29yaXRobRggIAEoDjImLnAycHN0cmVhbS52MS5QdWJsaWNSYXRlTGltaXRBbGdvcml0aG0SHgoWdHJhZmZpY19zaGFwZXJfcnVsZV9pZBghIAEoAxIgChh0cmFmZmljX3NoYXBlcl9ydWxlX25hbWUYIiABKAkSUQobdHJhZmZpY19zaGFwZXJfYnVkZ2V0X3Njb3BlGCMgASgOMiwucDJwc3RyZWFtLnYxLlB1YmxpY1RyYWZmaWNTaGFwZXJCdWRnZXRTY29wZRIuCiZ0cmFmZmljX3NoYXBlcl91cGxvYWRfYnl0ZXNfcGVyX3NlY29uZBgkIAEoAxIwCih0cmFmZmljX3NoYXBlcl9kb3dubG9hZF9ieXRlc19wZXJfc2Vjb25kGCUgASgDEisKI3RyYWZmaWNfc2hhcGVyX3JlcXVlc3RfZXhlbXB0X2J5dGVzGCYgASgDEiwKJHRyYWZmaWNfc2hhcGVyX3Jlc3BvbnNlX2V4ZW1wdF9ieXRlcxgnIAEoAxITCgt3YWZfcnVsZV9pZBgoIAEoAxIVCg13YWZfcnVsZV9uYW1lGCkgASgJEjUKCndhZl9hY3Rpb24YKiABKA4yIS5wMnBzdHJlYW0udjEuUHVibGljV2FmUnVsZUFjdGlvbhJCChN3YWZfYWN0aXZhdGlvbl9tb2RlGCsgASgOMiUucDJwc3RyZWFtLnYxLlB1YmxpY1dhZkFjdGl2YXRpb25Nb2RlEhwKFHdhZl9hdXRvbWF0aWNfYWN0aXZlGCwgASgIEhoKEndhZl9jaGFsbGVuZ2Vfa2luZBgtIAEoCRIVCg1jYWNoZV9ydWxlX2lkGC4gASgDEhcKD2NhY2hlX3J1bGVfbmFtZRgvIAEoCRIUCgxjYWNoZV9zdGF0dXMYMCABKAkSGAoQY2FjaGVfa2V5X2RpZ2VzdBgxIAEoCRIXCg9yb3V0ZV90YXJnZXRfaWQYMiABKAMSGQoRcm91dGVfdGFyZ2V0X25hbWUYMyABKAkSPgoRcm91dGVfdGFyZ2V0X3R5cGUYNCABKA4yIy5wMnBzdHJlYW0udjEuUHVibGljUm91dGVUYXJnZXRUeXBlEkgKFnJvdXRlX3RhcmdldF90cmFuc3BvcnQYNSABKA4yKC5wMnBzdHJlYW0udjEuUHVibGljUm91dGVUYXJnZXRUcmFuc3BvcnQaNQoTUmVxdWVzdEhlYWRlcnNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBGjYKFFJlc3BvbnNlSGVhZGVyc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEaNgoURGVidWdBdHRyaWJ1dGVzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4AUoECA4QD0oECA8QEEoECBAQEUoECBEQElIKYmFja2VuZF9pZFIMYmFja2VuZF9uYW1lUgxiYWNrZW5kX3R5cGVSDGZvcndhcmRfbW9kZSKrAQogU3RyZWFtVHJhZmZpY1RyYWNlRXZlbnRzUmVzcG9uc2USNAoIc2V0dGluZ3MYASABKAsyIi5wMnBzdHJlYW0udjEuVHJhZmZpY1RyYWNlU2V0dGluZ3MSLgoFZXZlbnQYAiABKAsyHy5wMnBzdHJlYW0udjEuVHJhZmZpY1RyYWNlRXZlbnQSIQoZc3Vic2NyaWJlcl9kcm9wcGVkX2V2ZW50cxgDIAEoBCIWChRHZXRTZXR1cFN0YXRlUmVxdWVzdCKQAQoVR2V0U2V0dXBTdGF0ZVJlc3BvbnNlEhYKDnNldHVwX3JlcXVpcmVkGAEgASgIEhcKD3NldHVwX2F2YWlsYWJsZRgCIAEoCBIkChxzZXR1cF9leHBpcmVzX2F0X3VuaXhfbWlsbGlzGAMgASgDEiAKGHNldHVwX3VuYXZhaWxhYmxlX3JlYXNvbhgEIAEoCSJMChFTZXR1cEFkbWluUmVxdWVzdBIQCgh1c2VybmFtZRgBIAEoCRIQCghwYXNzd29yZBgCIAEoCRITCgtzZXR1cF90b2tlbhgDIAEoCSI2ChJTZXR1cEFkbWluUmVzcG9uc2USIAoEdXNlchgBIAEoCzISLnAycHN0cmVhbS52MS5Vc2VyIjIKDExvZ2luUmVxdWVzdBIQCgh1c2VybmFtZRgBIAEoCRIQCghwYXNzd29yZBgCIAEoCSIxCg1Mb2dpblJlc3BvbnNlEiAKBHVzZXIYASABKAsyEi5wMnBzdHJlYW0udjEuVXNlciIPCg1Mb2dvdXRSZXF1ZXN0IhAKDkxvZ291dFJlc3BvbnNlIhcKFUdldEN1cnJlbnRVc2VyUmVxdWVzdCI6ChZHZXRDdXJyZW50VXNlclJlc3BvbnNlEiAKBHVzZXIYASABKAsyEi5wMnBzdHJlYW0udjEuVXNlciITChFTdGFydFByb3h5UmVxdWVzdCI+ChJTdGFydFByb3h5UmVzcG9uc2USKAoFcHJveHkYASABKAsyGS5wMnBzdHJlYW0udjEuUHJveHlTdGF0dXMiEgoQU3RvcFByb3h5UmVxdWVzdCI9ChFTdG9wUHJveHlSZXNwb25zZRIoCgVwcm94eRgBIAEoCzIZLnAycHN0cmVhbS52MS5Qcm94eVN0YXR1cyo6CghVc2VyUm9sZRIZChVVU0VSX1JPTEVfVU5TUEVDSUZJRUQQABITCg9VU0VSX1JPTEVfQURNSU4QASqmAQoKUHJveHlTdGF0ZRIbChdQUk9YWV9TVEFURV9VTlNQRUNJRklFRBAAEhcKE1BST1hZX1NUQVRFX1NUT1BQRUQQARIYChRQUk9YWV9TVEFURV9TVEFSVElORxACEhcKE1BST1hZX1NUQVRFX1JVTk5JTkcQAxIYChRQUk9YWV9TVEFURV9TVE9QUElORxAEEhUKEVBST1hZX1NUQVRFX0VSUk9SEAUqiQEKFlB1YmxpY0xpc3RlbmVyUHJvdG9jb2wSKAokUFVCTElDX0xJU1RFTkVSX1BST1RPQ09MX1VOU1BFQ0lGSUVEEAASIQodUFVCTElDX0xJU1RFTkVSX1BST1RPQ09MX0hUVFAQARIiCh5QVUJMSUNfTElTVEVORVJfUFJPVE9DT0xfSFRUUFMQAiqRAQoWUHVibGljUmVzcG9uc2VCb2R5TW9kZRIpCiVQVUJMSUNfUkVTUE9OU0VfQk9EWV9NT0RFX1VOU1BFQ0lGSUVEEAASJAogUFVCTElDX1JFU1BPTlNFX0JPRFlfTU9ERV9JTkxJTkUQARImCiJQVUJMSUNfUkVTUE9OU0VfQk9EWV9NT0RFX1RFTVBMQVRFEAIq6AEKGlB1YmxpY1Jlc3BvbnNlVGVtcGxhdGVLaW5kEi0KKVBVQkxJQ19SRVNQT05TRV9URU1QTEFURV9LSU5EX1VOU1BFQ0lGSUVEEAASLgoqUFVCTElDX1JFU1BPTlNFX1RFTVBMQVRFX0tJTkRfR0VORVJJQ19CT0RZEAESMgouUFVCTElDX1JFU1BPTlNFX1RFTVBMQVRFX0tJTkRfV0FGX0NBUFRDSEFfUEFHRRACEjcKM1BVQkxJQ19SRVNQT05TRV9URU1QTEFURV9LSU5EX1dBRl9XQUlUSU5HX1JPT01fUEFHRRADKooBChVQdWJsaWNSb3V0ZVRhcmdldFR5cGUSKAokUFVCTElDX1JPVVRFX1RBUkdFVF9UWVBFX1VOU1BFQ0lGSUVEEAASIgoeUFVCTElDX1JPVVRFX1RBUkdFVF9UWVBFX1BST1hZEAESIwofUFVCTElDX1JPVVRFX1RBUkdFVF9UWVBFX1NUQVRJQxACKp4BChpQdWJsaWNSb3V0ZVRhcmdldFRyYW5zcG9ydBItCilQVUJMSUNfUk9VVEVfVEFSR0VUX1RSQU5TUE9SVF9VTlNQRUNJRklFRBAAEigKJFBVQkxJQ19ST1VURV9UQVJHRVRfVFJBTlNQT1JUX0RJUkVDVBABEicKI1BVQkxJQ19ST1VURV9UQVJHRVRfVFJBTlNQT1JUX0FHRU5UEAIqsQMKHlB1YmxpY1JvdXRlVGFyZ2V0TG9hZEJhbGFuY2luZxIyCi5QVUJMSUNfUk9VVEVfVEFSR0VUX0xPQURfQkFMQU5DSU5HX1VOU1BFQ0lGSUVEEAASMgouUFVCTElDX1JPVVRFX1RBUkdFVF9MT0FEX0JBTEFOQ0lOR19ST1VORF9ST0JJThABEjsKN1BVQkxJQ19ST1VURV9UQVJHRVRfTE9BRF9CQUxBTkNJTkdfV0VJR0hURURfUk9VTkRfUk9CSU4QAhItCilQVUJMSUNfUk9VVEVfVEFSR0VUX0xPQURfQkFMQU5DSU5HX1JBTkRPTRADEjYKMlBVQkxJQ19ST1VURV9UQVJHRVRfTE9BRF9CQUxBTkNJTkdfV0VJR0hURURfUkFORE9NEAQSPAo4UFVCTElDX1JPVVRFX1RBUkdFVF9MT0FEX0JBTEFOQ0lOR19MRUFTVF9BQ1RJVkVfUkVRVUVTVFMQBRJFCkFQVUJMSUNfUk9VVEVfVEFSR0VUX0xPQURfQkFMQU5DSU5HX1dFSUdIVEVEX0xFQVNUX0FDVElWRV9SRVFVRVNUUxAGKsUCCh1QdWJsaWNSb3V0ZVRhcmdldEhlYWx0aFN0YXR1cxIxCi1QVUJMSUNfUk9VVEVfVEFSR0VUX0hFQUxUSF9TVEFUVVNfVU5TUEVDSUZJRUQQABItCilQVUJMSUNfUk9VVEVfVEFSR0VUX0hFQUxUSF9TVEFUVVNfVU5LTk9XThABEi0KKVBVQkxJQ19ST1VURV9UQVJHRVRfSEVBTFRIX1NUQVRVU19IRUFMVEhZEAISLworUFVCTElDX1JPVVRFX1RBUkdFVF9IRUFMVEhfU1RBVFVTX1VOSEVBTFRIWRADEi4KKlBVQkxJQ19ST1VURV9UQVJHRVRfSEVBTFRIX1NUQVRVU19ESVNBQkxFRBAEEjIKLlBVQkxJQ19ST1VURV9UQVJHRVRfSEVBTFRIX1NUQVRVU19ESVNDT05ORUNURUQQBSqUAgoiUHVibGljUm91dGVUYXJnZXRIZWFsdGhUcmFjZVNvdXJjZRI3CjNQVUJMSUNfUk9VVEVfVEFSR0VUX0hFQUxUSF9UUkFDRV9TT1VSQ0VfVU5TUEVDSUZJRUQQABI4CjRQVUJMSUNfUk9VVEVfVEFSR0VUX0hFQUxUSF9UUkFDRV9TT1VSQ0VfQUNUSVZFX0NIRUNLEAESOwo3UFVCTElDX1JPVVRFX1RBUkdFVF9IRUFMVEhfVFJBQ0VfU09VUkNFX1BBU1NJVkVfRkFJTFVSRRACEj4KOlBVQkxJQ19ST1VURV9UQVJHRVRfSEVBTFRIX1RSQUNFX1NPVVJDRV9BR0VOVF9DT05ORUNUSVZJVFkQAyqBAgojUHVibGljUm91dGVUYXJnZXRIZWFsdGhUcmFjZU91dGNvbWUSOAo0UFVCTElDX1JPVVRFX1RBUkdFVF9IRUFMVEhfVFJBQ0VfT1VUQ09NRV9VTlNQRUNJRklFRBAAEjQKMFBVQkxJQ19ST1VURV9UQVJHRVRfSEVBTFRIX1RSQUNFX09VVENPTUVfU1VDQ0VTUxABEjQKMFBVQkxJQ19ST1VURV9UQVJHRVRfSEVBTFRIX1RSQUNFX09VVENPTUVfRkFJTFVSRRACEjQKMFBVQkxJQ19ST1VURV9UQVJHRVRfSEVBTFRIX1RSQUNFX09VVENPTUVfU0tJUFBFRBADKnsKEVB1YmxpY1JvdXRlQWN0aW9uEiMKH1BVQkxJQ19ST1VURV9BQ1RJT05fVU5TUEVDSUZJRUQQABIfChtQVUJMSUNfUk9VVEVfQUNUSU9OX0ZPUldBUkQQARIgChxQVUJMSUNfUk9VVEVfQUNUSU9OX1JFRElSRUNUEAIq/QEKHVB1YmxpY1JvdXRlUmVkaXJlY3RUYXJnZXRNb2RlEjEKLVBVQkxJQ19ST1VURV9SRURJUkVDVF9UQVJHRVRfTU9ERV9VTlNQRUNJRklFRBAAEjQKMFBVQkxJQ19ST1VURV9SRURJUkVDVF9UQVJHRVRfTU9ERV9TQU1FX0hPU1RfUEFUSBABEj8KO1BVQkxJQ19ST1VURV9SRURJUkVDVF9UQVJHRVRfTU9ERV9FWFRFUk5BTF9PUklHSU5fS0VFUF9QQVRIEAISMgouUFVCTElDX1JPVVRFX1JFRElSRUNUX1RBUkdFVF9NT0RFX0FCU09MVVRFX1VSTBADKoECChhQdWJsaWNSYXRlTGltaXRBbGdvcml0aG0SKwonUFVCTElDX1JBVEVfTElNSVRfQUxHT1JJVEhNX1VOU1BFQ0lGSUVEEAASLAooUFVCTElDX1JBVEVfTElNSVRfQUxHT1JJVEhNX0ZJWEVEX1dJTkRPVxABEi4KKlBVQkxJQ19SQVRFX0xJTUlUX0FMR09SSVRITV9TTElESU5HX1dJTkRPVxACEiwKKFBVQkxJQ19SQVRFX0xJTUlUX0FMR09SSVRITV9UT0tFTl9CVUNLRVQQAxIsCihQVUJMSUNfUkFURV9MSU1JVF9BTEdPUklUSE1fTEVBS1lfQlVDS0VUEAQqlgMKGFB1YmxpY1JhdGVMaW1pdEtleVNvdXJjZRIsCihQVUJMSUNfUkFURV9MSU1JVF9LRVlfU09VUkNFX1VOU1BFQ0lGSUVEEAASKgomUFVCTElDX1JBVEVfTElNSVRfS0VZX1NPVVJDRV9SRU1PVEVfSVAQARIlCiFQVUJMSUNfUkFURV9MSU1JVF9LRVlfU09VUkNFX0hPU1QQAhInCiNQVUJMSUNfUkFURV9MSU1JVF9LRVlfU09VUkNFX01FVEhPRBADEiUKIVBVQkxJQ19SQVRFX0xJTUlUX0tFWV9TT1VSQ0VfUEFUSBAEEikKJVBVQkxJQ19SQVRFX0xJTUlUX0tFWV9TT1VSQ0VfUFJPVE9DT0wQBRInCiNQVUJMSUNfUkFURV9MSU1JVF9LRVlfU09VUkNFX0hFQURFUhAGEicKI1BVQkxJQ19SQVRFX0xJTUlUX0tFWV9TT1VSQ0VfQ09PS0lFEAcSLAooUFVCTElDX1JBVEVfTElNSVRfS0VZX1NPVVJDRV9RVUVSWV9QQVJBTRAIKrQBCiBQdWJsaWNQb2xpY3lNYXRjaEJvb2xlYW5PcGVyYXRvchI0CjBQVUJMSUNfUE9MSUNZX01BVENIX0JPT0xFQU5fT1BFUkFUT1JfVU5TUEVDSUZJRUQQABIsCihQVUJMSUNfUE9MSUNZX01BVENIX0JPT0xFQU5fT1BFUkFUT1JfQUxMEAESLAooUFVCTElDX1BPTElDWV9NQVRDSF9CT09MRUFOX09QRVJBVE9SX0FOWRACKvkCChZQdWJsaWNQb2xpY3lNYXRjaEZpZWxkEikKJVBVQkxJQ19QT0xJQ1lfTUFUQ0hfRklFTERfVU5TUEVDSUZJRUQQABIkCiBQVUJMSUNfUE9MSUNZX01BVENIX0ZJRUxEX01FVEhPRBABEiYKIlBVQkxJQ19QT0xJQ1lfTUFUQ0hfRklFTERfUFJPVE9DT0wQAhIiCh5QVUJMSUNfUE9MSUNZX01BVENIX0ZJRUxEX0hPU1QQAxIiCh5QVUJMSUNfUE9MSUNZX01BVENIX0ZJRUxEX1BBVEgQBBInCiNQVUJMSUNfUE9MSUNZX01BVENIX0ZJRUxEX1JFTU9URV9JUBAFEiQKIFBVQkxJQ19QT0xJQ1lfTUFUQ0hfRklFTERfSEVBREVSEAYSJAogUFVCTElDX1BPTElDWV9NQVRDSF9GSUVMRF9DT09LSUUQBxIpCiVQVUJMSUNfUE9MSUNZX01BVENIX0ZJRUxEX1FVRVJZX1BBUkFNEAgqqwQKIlB1YmxpY1BvbGljeU1hdGNoQ29uZGl0aW9uT3BlcmF0b3ISNgoyUFVCTElDX1BPTElDWV9NQVRDSF9DT05ESVRJT05fT1BFUkFUT1JfVU5TUEVDSUZJRUQQABIyCi5QVUJMSUNfUE9MSUNZX01BVENIX0NPTkRJVElPTl9PUEVSQVRPUl9QUkVTRU5UEAESMQotUFVCTElDX1BPTElDWV9NQVRDSF9DT05ESVRJT05fT1BFUkFUT1JfRVFVQUxTEAISMQotUFVCTElDX1BPTElDWV9NQVRDSF9DT05ESVRJT05fT1BFUkFUT1JfUFJFRklYEAMSMQotUFVCTElDX1BPTElDWV9NQVRDSF9DT05ESVRJT05fT1BFUkFUT1JfU1VGRklYEAQSMwovUFVCTElDX1BPTElDWV9NQVRDSF9DT05ESVRJT05fT1BFUkFUT1JfQ09OVEFJTlMQBRIyCi5QVUJMSUNfUE9MSUNZX01BVENIX0NPTkRJVElPTl9PUEVSQVRPUl9NQVRDSEVTEAYSLQopUFVCTElDX1BPTElDWV9NQVRDSF9DT05ESVRJT05fT1BFUkFUT1JfSU4QBxIvCitQVUJMSUNfUE9MSUNZX01BVENIX0NPTkRJVElPTl9PUEVSQVRPUl9DSURSEAgSNwozUFVCTElDX1BPTElDWV9NQVRDSF9DT05ESVRJT05fT1BFUkFUT1JfSE9TVF9QQVRURVJOEAkquAEKHlB1YmxpY1RyYWZmaWNTaGFwZXJCdWRnZXRTY29wZRIyCi5QVUJMSUNfVFJBRkZJQ19TSEFQRVJfQlVER0VUX1NDT1BFX1VOU1BFQ0lGSUVEEAASLgoqUFVCTElDX1RSQUZGSUNfU0hBUEVSX0JVREdFVF9TQ09QRV9QRVJfS0VZEAESMgouUFVCTElDX1RSQUZGSUNfU0hBUEVSX0JVREdFVF9TQ09QRV9QRVJfUkVRVUVTVBACKuIBChxQdWJsaWNXYWZDYXB0Y2hhUHJvdmlkZXJUeXBlEjAKLFBVQkxJQ19XQUZfQ0FQVENIQV9QUk9WSURFUl9UWVBFX1VOU1BFQ0lGSUVEEAASLgoqUFVCTElDX1dBRl9DQVBUQ0hBX1BST1ZJREVSX1RZUEVfVFVSTlNUSUxFEAESLQopUFVCTElDX1dBRl9DQVBUQ0hBX1BST1ZJREVSX1RZUEVfSENBUFRDSEEQAhIxCi1QVUJMSUNfV0FGX0NBUFRDSEFfUFJPVklERVJfVFlQRV9SRUNBUFRDSEFfVjIQAyqsAQoTUHVibGljV2FmUnVsZUFjdGlvbhImCiJQVUJMSUNfV0FGX1JVTEVfQUNUSU9OX1VOU1BFQ0lGSUVEEAASIAocUFVCTElDX1dBRl9SVUxFX0FDVElPTl9CTE9DSxABEiIKHlBVQkxJQ19XQUZfUlVMRV9BQ1RJT05fQ0FQVENIQRACEicKI1BVQkxJQ19XQUZfUlVMRV9BQ1RJT05fV0FJVElOR19ST09NEAMqlgEKF1B1YmxpY1dhZkFjdGl2YXRpb25Nb2RlEioKJlBVQkxJQ19XQUZfQUNUSVZBVElPTl9NT0RFX1VOU1BFQ0lGSUVEEAASJQohUFVCTElDX1dBRl9BQ1RJVkFUSU9OX01PREVfQUxXQVlTEAESKAokUFVCTElDX1dBRl9BQ1RJVkFUSU9OX01PREVfQVVUT01BVElDEAIqfgoSUHVibGljQ2FjaGVUdGxNb2RlEiUKIVBVQkxJQ19DQUNIRV9UVExfTU9ERV9VTlNQRUNJRklFRBAAEh8KG1BVQkxJQ19DQUNIRV9UVExfTU9ERV9GSVhFRBABEiAKHFBVQkxJQ19DQUNIRV9UVExfTU9ERV9PUklHSU4QAirSAQoUUHVibGljQ2FjaGVRdWVyeU1vZGUSJwojUFVCTElDX0NBQ0hFX1FVRVJZX01PREVfVU5TUEVDSUZJRUQQABIgChxQVUJMSUNfQ0FDSEVfUVVFUllfTU9ERV9GVUxMEAESIgoeUFVCTElDX0NBQ0hFX1FVRVJZX01PREVfSUdOT1JFEAISJQohUFVCTElDX0NBQ0hFX1FVRVJZX01PREVfQUxMT1dMSVNUEAMSJAogUFVCTElDX0NBQ0hFX1FVRVJZX01PREVfREVOWUxJU1QQBCp9ChBQdWJsaWNDYWNoZVNjb3BlEiIKHlBVQkxJQ19DQUNIRV9TQ09QRV9VTlNQRUNJRklFRBAAEicKI1BVQkxJQ19DQUNIRV9TQ09QRV9TRUxFQ1RFRF9CQUNLRU5EEAESHAoYUFVCTElDX0NBQ0hFX1NDT1BFX1JPVVRFEAIqnQEKGlB1YmxpY1Rsc0NlcnRpZmljYXRlU291cmNlEi0KKVBVQkxJQ19UTFNfQ0VSVElGSUNBVEVfU09VUkNFX1VOU1BFQ0lGSUVEEAASKAokUFVCTElDX1RMU19DRVJUSUZJQ0FURV9TT1VSQ0VfTUFOVUFMEAESJgoiUFVCTElDX1RMU19DRVJUSUZJQ0FURV9TT1VSQ0VfQUNNRRACKsABChdQdWJsaWNBY21lQ2hhbGxlbmdlVHlwZRIqCiZQVUJMSUNfQUNNRV9DSEFMTEVOR0VfVFlQRV9VTlNQRUNJRklFRBAAEiYKIlBVQkxJQ19BQ01FX0NIQUxMRU5HRV9UWVBFX0hUVFBfMDEQARIqCiZQVUJMSUNfQUNNRV9DSEFMTEVOR0VfVFlQRV9UTFNfQUxQTl8wMRACEiUKIVBVQkxJQ19BQ01FX0NIQUxMRU5HRV9UWVBFX0ROU18wMRADKoMBCgxQdWJsaWNBY21lQ2ESHgoaUFVCTElDX0FDTUVfQ0FfVU5TUEVDSUZJRUQQABIqCiZQVUJMSUNfQUNNRV9DQV9MRVRTX0VOQ1JZUFRfUFJPRFVDVElPThABEicKI1BVQkxJQ19BQ01FX0NBX0xFVFNfRU5DUllQVF9TVEFHSU5HEAIqXAoRUHVibGljRG5zUHJvdmlkZXISIwofUFVCTElDX0ROU19QUk9WSURFUl9VTlNQRUNJRklFRBAAEiIKHlBVQkxJQ19ETlNfUFJPVklERVJfQ0xPVURGTEFSRRABKvQBChpQdWJsaWNUbHNDZXJ0aWZpY2F0ZVN0YXR1cxItCilQVUJMSUNfVExTX0NFUlRJRklDQVRFX1NUQVRVU19VTlNQRUNJRklFRBAAEikKJVBVQkxJQ19UTFNfQ0VSVElGSUNBVEVfU1RBVFVTX1BFTkRJTkcQARInCiNQVUJMSUNfVExTX0NFUlRJRklDQVRFX1NUQVRVU19SRUFEWRACEioKJlBVQkxJQ19UTFNfQ0VSVElGSUNBVEVfU1RBVFVTX1JFTkVXSU5HEAMSJwojUFVCTElDX1RMU19DRVJUSUZJQ0FURV9TVEFUVVNfRVJST1IQBCq5AQoRVHJhZmZpY1RyYWNlTGV2ZWwSIwofVFJBRkZJQ19UUkFDRV9MRVZFTF9VTlNQRUNJRklFRBAAEh0KGVRSQUZGSUNfVFJBQ0VfTEVWRUxfQkFTSUMQARIgChxUUkFGRklDX1RSQUNFX0xFVkVMX0RFVEFJTEVEEAISHwobVFJBRkZJQ19UUkFDRV9MRVZFTF9IRUFERVJTEAMSHQoZVFJBRkZJQ19UUkFDRV9MRVZFTF9ERUJVRxAEKtUGChFUcmFmZmljVHJhY2VTdGFnZRIjCh9UUkFGRklDX1RSQUNFX1NUQUdFX1VOU1BFQ0lGSUVEEAASIAocVFJBRkZJQ19UUkFDRV9TVEFHRV9SRUNFSVZFRBABEiYKIlRSQUZGSUNfVFJBQ0VfU1RBR0VfUk9VVEVfUkVTT0xWRUQQAhIoCiRUUkFGRklDX1RSQUNFX1NUQUdFX0JBQ0tFTkRfU0VMRUNURUQQAxImCiJUUkFGRklDX1RSQUNFX1NUQUdFX0FHRU5UX1NFTEVDVEVEEAQSKAokVFJBRkZJQ19UUkFDRV9TVEFHRV9VUFNUUkVBTV9TVEFSVEVEEAUSKgomVFJBRkZJQ19UUkFDRV9TVEFHRV9VUFNUUkVBTV9SRVNQT05ERUQQBhIlCiFUUkFGRklDX1RSQUNFX1NUQUdFX1JFU1BPTlNFX1NFTlQQBxIeChpUUkFGRklDX1RSQUNFX1NUQUdFX0ZBSUxFRBAIEiQKIFRSQUZGSUNfVFJBQ0VfU1RBR0VfUkFURV9MSU1JVEVEEAkSLworVFJBRkZJQ19UUkFDRV9TVEFHRV9UUkFGRklDX1NIQVBFUl9TRUxFQ1RFRBAKEiUKIVRSQUZGSUNfVFJBQ0VfU1RBR0VfV0FGX0VWQUxVQVRFRBALEiMKH1RSQUZGSUNfVFJBQ0VfU1RBR0VfV0FGX0JMT0NLRUQQDBIuCipUUkFGRklDX1RSQUNFX1NUQUdFX1dBRl9DQVBUQ0hBX0NIQUxMRU5HRUQQDRIsCihUUkFGRklDX1RSQUNFX1NUQUdFX1dBRl9DQVBUQ0hBX1ZFUklGSUVEEA4SKAokVFJBRkZJQ19UUkFDRV9TVEFHRV9XQUZfV0FJVElOR19ST09NEA8SJAogVFJBRkZJQ19UUkFDRV9TVEFHRV9DQUNIRV9MT09LVVAQEBIhCh1UUkFGRklDX1RSQUNFX1NUQUdFX0NBQ0hFX0hJVBAREiIKHlRSQUZGSUNfVFJBQ0VfU1RBR0VfQ0FDSEVfTUlTUxASEiQKIFRSQUZGSUNfVFJBQ0VfU1RBR0VfQ0FDSEVfQllQQVNTEBMSJAogVFJBRkZJQ19UUkFDRV9TVEFHRV9DQUNIRV9TVE9SRUQQFCqAAQoURW52aXJvbm1lbnRUcmFuc3BvcnQSJQohRU5WSVJPTk1FTlRfVFJBTlNQT1JUX1VOU1BFQ0lGSUVEEAASIAocRU5WSVJPTk1FTlRfVFJBTlNQT1JUX0RJUkVDVBABEh8KG0VOVklST05NRU5UX1RSQU5TUE9SVF9BR0VOVBACKtYBChVFbnZpcm9ubWVudFRydXN0U3RhdGUSJwojRU5WSVJPTk1FTlRfVFJVU1RfU1RBVEVfVU5TUEVDSUZJRUQQABIlCiFFTlZJUk9OTUVOVF9UUlVTVF9TVEFURV9VTlRSVVNURUQQARIjCh9FTlZJUk9OTUVOVF9UUlVTVF9TVEFURV9UUlVTVEVEEAISIwofRU5WSVJPTk1FTlRfVFJVU1RfU1RBVEVfQ0hBTkdFRBADEiMKH0VOVklST05NRU5UX1RSVVNUX1NUQVRFX0VYUElSRUQQBCrfAgoXRGFzaGJvYXJkUHJveHlEaW1lbnNpb24SKQolREFTSEJPQVJEX1BST1hZX0RJTUVOU0lPTl9VTlNQRUNJRklFRBAAEiYKIkRBU0hCT0FSRF9QUk9YWV9ESU1FTlNJT05fTElTVEVORVIQARIlCiFEQVNIQk9BUkRfUFJPWFlfRElNRU5TSU9OX0JBQ0tFTkQQAhIjCh9EQVNIQk9BUkRfUFJPWFlfRElNRU5TSU9OX1JPVVRFEAMSIwofREFTSEJPQVJEX1BST1hZX0RJTUVOU0lPTl9BR0VOVBAEEigKJERBU0hCT0FSRF9QUk9YWV9ESU1FTlNJT05fRVJST1JfS0lORBAFEioKJkRBU0hCT0FSRF9QUk9YWV9ESU1FTlNJT05fU1RBVFVTX0NMQVNTEAYSKgomREFTSEJPQVJEX1BST1hZX0RJTUVOU0lPTl9ST1VURV9UQVJHRVQQBzLKPAoWQWdlbnRNYW5hZ2VtZW50U2VydmljZRJSCgtSZXBvcnRTdGF0cxIfLnAycHN0cmVhbS52MS5BZ2VudFN0YXRzUmVxdWVzdBogLnAycHN0cmVhbS52MS5BZ2VudFN0YXRzUmVzcG9uc2UiABJOCglHZXRTdGF0dXMSHi5wMnBzdHJlYW0udjEuR2V0U3RhdHVzUmVxdWVzdBofLnAycHN0cmVhbS52MS5HZXRTdGF0dXNSZXNwb25zZSIAElcKDEdldERhc2hib2FyZBIhLnAycHN0cmVhbS52MS5HZXREYXNoYm9hcmRSZXF1ZXN0GiIucDJwc3RyZWFtLnYxLkdldERhc2hib2FyZFJlc3BvbnNlIgASeAoXR2V0RGFzaGJvYXJkRGlhZ25vc3RpY3MSLC5wMnBzdHJlYW0udjEuR2V0RGFzaGJvYXJkRGlhZ25vc3RpY3NSZXF1ZXN0Gi0ucDJwc3RyZWFtLnYxLkdldERhc2hib2FyZERpYWdub3N0aWNzUmVzcG9uc2UiABJ4ChdHZXRUcmFmZmljVHJhY2VTZXR0aW5ncxIsLnAycHN0cmVhbS52MS5HZXRUcmFmZmljVHJhY2VTZXR0aW5nc1JlcXVlc3QaLS5wMnBzdHJlYW0udjEuR2V0VHJhZmZpY1RyYWNlU2V0dGluZ3NSZXNwb25zZSIAEngKF1NldFRyYWZmaWNUcmFjZVNldHRpbmdzEiwucDJwc3RyZWFtLnYxLlNldFRyYWZmaWNUcmFjZVNldHRpbmdzUmVxdWVzdBotLnAycHN0cmVhbS52MS5TZXRUcmFmZmljVHJhY2VTZXR0aW5nc1Jlc3BvbnNlIgASfQoYU3RyZWFtVHJhZmZpY1RyYWNlRXZlbnRzEi0ucDJwc3RyZWFtLnYxLlN0cmVhbVRyYWZmaWNUcmFjZUV2ZW50c1JlcXVlc3QaLi5wMnBzdHJlYW0udjEuU3RyZWFtVHJhZmZpY1RyYWNlRXZlbnRzUmVzcG9uc2UiADABEloKDUdldFNldHVwU3RhdGUSIi5wMnBzdHJlYW0udjEuR2V0U2V0dXBTdGF0ZVJlcXVlc3QaIy5wMnBzdHJlYW0udjEuR2V0U2V0dXBTdGF0ZVJlc3BvbnNlIgASUQoKU2V0dXBBZG1pbhIfLnAycHN0cmVhbS52MS5TZXR1cEFkbWluUmVxdWVzdBogLnAycHN0cmVhbS52MS5TZXR1cEFkbWluUmVzcG9uc2UiABJCCgVMb2dpbhIaLnAycHN0cmVhbS52MS5Mb2dpblJlcXVlc3QaGy5wMnBzdHJlYW0udjEuTG9naW5SZXNwb25zZSIAEkUKBkxvZ291dBIbLnAycHN0cmVhbS52MS5Mb2dvdXRSZXF1ZXN0GhwucDJwc3RyZWFtLnYxLkxvZ291dFJlc3BvbnNlIgASXQoOR2V0Q3VycmVudFVzZXISIy5wMnBzdHJlYW0udjEuR2V0Q3VycmVudFVzZXJSZXF1ZXN0GiQucDJwc3RyZWFtLnYxLkdldEN1cnJlbnRVc2VyUmVzcG9uc2UiABJRCgpTdGFydFByb3h5Eh8ucDJwc3RyZWFtLnYxLlN0YXJ0UHJveHlSZXF1ZXN0GiAucDJwc3RyZWFtLnYxLlN0YXJ0UHJveHlSZXNwb25zZSIAEk4KCVN0b3BQcm94eRIeLnAycHN0cmVhbS52MS5TdG9wUHJveHlSZXF1ZXN0Gh8ucDJwc3RyZWFtLnYxLlN0b3BQcm94eVJlc3BvbnNlIgASbwoUR2V0UHVibGljUHJveHlDb25maWcSKS5wMnBzdHJlYW0udjEuR2V0UHVibGljUHJveHlDb25maWdSZXF1ZXN0GioucDJwc3RyZWFtLnYxLkdldFB1YmxpY1Byb3h5Q29uZmlnUmVzcG9uc2UiABKHAQocQ3JlYXRlUHVibGljUmVzcG9uc2VUZW1wbGF0ZRIxLnAycHN0cmVhbS52MS5DcmVhdGVQdWJsaWNSZXNwb25zZVRlbXBsYXRlUmVxdWVzdBoyLnAycHN0cmVhbS52MS5DcmVhdGVQdWJsaWNSZXNwb25zZVRlbXBsYXRlUmVzcG9uc2UiABKHAQocVXBkYXRlUHVibGljUmVzcG9uc2VUZW1wbGF0ZRIxLnAycHN0cmVhbS52MS5VcGRhdGVQdWJsaWNSZXNwb25zZVRlbXBsYXRlUmVxdWVzdBoyLnAycHN0cmVhbS52MS5VcGRhdGVQdWJsaWNSZXNwb25zZVRlbXBsYXRlUmVzcG9uc2UiABKHAQocRGVsZXRlUHVibGljUmVzcG9uc2VUZW1wbGF0ZRIxLnAycHN0cmVhbS52MS5EZWxldGVQdWJsaWNSZXNwb25zZVRlbXBsYXRlUmVxdWVzdBoyLnAycHN0cmVhbS52MS5EZWxldGVQdWJsaWNSZXNwb25zZVRlbXBsYXRlUmVzcG9uc2UiABKWAQohTGlzdFB1YmxpY1JvdXRlVGFyZ2V0SGVhbHRoVHJhY2VzEjYucDJwc3RyZWFtLnYxLkxpc3RQdWJsaWNSb3V0ZVRhcmdldEhlYWx0aFRyYWNlc1JlcXVlc3QaNy5wMnBzdHJlYW0udjEuTGlzdFB1YmxpY1JvdXRlVGFyZ2V0SGVhbHRoVHJhY2VzUmVzcG9uc2UiABJUCgtDcmVhdGVBZ2VudBIgLnAycHN0cmVhbS52MS5DcmVhdGVBZ2VudFJlcXVlc3QaIS5wMnBzdHJlYW0udjEuQ3JlYXRlQWdlbnRSZXNwb25zZSIAElQKC1VwZGF0ZUFnZW50EiAucDJwc3RyZWFtLnYxLlVwZGF0ZUFnZW50UmVxdWVzdBohLnAycHN0cmVhbS52MS5VcGRhdGVBZ2VudFJlc3BvbnNlIgASVAoLRGVsZXRlQWdlbnQSIC5wMnBzdHJlYW0udjEuRGVsZXRlQWdlbnRSZXF1ZXN0GiEucDJwc3RyZWFtLnYxLkRlbGV0ZUFnZW50UmVzcG9uc2UiABJjChBSb3RhdGVBZ2VudFRva2VuEiUucDJwc3RyZWFtLnYxLlJvdGF0ZUFnZW50VG9rZW5SZXF1ZXN0GiYucDJwc3RyZWFtLnYxLlJvdGF0ZUFnZW50VG9rZW5SZXNwb25zZSIAEoQBChtDcmVhdGVNYW5hZ2VtZW50QWNjZXNzVG9rZW4SMC5wMnBzdHJlYW0udjEuQ3JlYXRlTWFuYWdlbWVudEFjY2Vzc1Rva2VuUmVxdWVzdBoxLnAycHN0cmVhbS52MS5DcmVhdGVNYW5hZ2VtZW50QWNjZXNzVG9rZW5SZXNwb25zZSIAEoEBChpMaXN0TWFuYWdlbWVudEFjY2Vzc1Rva2VucxIvLnAycHN0cmVhbS52MS5MaXN0TWFuYWdlbWVudEFjY2Vzc1Rva2Vuc1JlcXVlc3QaMC5wMnBzdHJlYW0udjEuTGlzdE1hbmFnZW1lbnRBY2Nlc3NUb2tlbnNSZXNwb25zZSIAEoQBChtEZWxldGVNYW5hZ2VtZW50QWNjZXNzVG9rZW4SMC5wMnBzdHJlYW0udjEuRGVsZXRlTWFuYWdlbWVudEFjY2Vzc1Rva2VuUmVxdWVzdBoxLnAycHN0cmVhbS52MS5EZWxldGVNYW5hZ2VtZW50QWNjZXNzVG9rZW5SZXNwb25zZSIAEmMKEExpc3RFbnZpcm9ubWVudHMSJS5wMnBzdHJlYW0udjEuTGlzdEVudmlyb25tZW50c1JlcXVlc3QaJi5wMnBzdHJlYW0udjEuTGlzdEVudmlyb25tZW50c1Jlc3BvbnNlIgASZgoRQ3JlYXRlRW52aXJvbm1lbnQSJi5wMnBzdHJlYW0udjEuQ3JlYXRlRW52aXJvbm1lbnRSZXF1ZXN0GicucDJwc3RyZWFtLnYxLkNyZWF0ZUVudmlyb25tZW50UmVzcG9uc2UiABJmChFVcGRhdGVFbnZpcm9ubWVudBImLnAycHN0cmVhbS52MS5VcGRhdGVFbnZpcm9ubWVudFJlcXVlc3QaJy5wMnBzdHJlYW0udjEuVXBkYXRlRW52aXJvbm1lbnRSZXNwb25zZSIAEmYKEURlbGV0ZUVudmlyb25tZW50EiYucDJwc3RyZWFtLnYxLkRlbGV0ZUVudmlyb25tZW50UmVxdWVzdBonLnAycHN0cmVhbS52MS5EZWxldGVFbnZpcm9ubWVudFJlc3BvbnNlIgASjQEKHkRpc2NvdmVyRW52aXJvbm1lbnRDZXJ0aWZpY2F0ZRIzLnAycHN0cmVhbS52MS5EaXNjb3ZlckVudmlyb25tZW50Q2VydGlmaWNhdGVSZXF1ZXN0GjQucDJwc3RyZWFtLnYxLkRpc2NvdmVyRW52aXJvbm1lbnRDZXJ0aWZpY2F0ZVJlc3BvbnNlIgAShAEKG1RydXN0RW52aXJvbm1lbnRDZXJ0aWZpY2F0ZRIwLnAycHN0cmVhbS52MS5UcnVzdEVudmlyb25tZW50Q2VydGlmaWNhdGVSZXF1ZXN0GjEucDJwc3RyZWFtLnYxLlRydXN0RW52aXJvbm1lbnRDZXJ0aWZpY2F0ZVJlc3BvbnNlIgASYAoPVGVzdEVudmlyb25tZW50EiQucDJwc3RyZWFtLnYxLlRlc3RFbnZpcm9ubWVudFJlcXVlc3QaJS5wMnBzdHJlYW0udjEuVGVzdEVudmlyb25tZW50UmVzcG9uc2UiABJvChRDcmVhdGVQdWJsaWNMaXN0ZW5lchIpLnAycHN0cmVhbS52MS5DcmVhdGVQdWJsaWNMaXN0ZW5lclJlcXVlc3QaKi5wMnBzdHJlYW0udjEuQ3JlYXRlUHVibGljTGlzdGVuZXJSZXNwb25zZSIAEm8KFFVwZGF0ZVB1YmxpY0xpc3RlbmVyEikucDJwc3RyZWFtLnYxLlVwZGF0ZVB1YmxpY0xpc3RlbmVyUmVxdWVzdBoqLnAycHN0cmVhbS52MS5VcGRhdGVQdWJsaWNMaXN0ZW5lclJlc3BvbnNlIgASbwoURGVsZXRlUHVibGljTGlzdGVuZXISKS5wMnBzdHJlYW0udjEuRGVsZXRlUHVibGljTGlzdGVuZXJSZXF1ZXN0GioucDJwc3RyZWFtLnYxLkRlbGV0ZVB1YmxpY0xpc3RlbmVyUmVzcG9uc2UiABJvChRFbmFibGVQdWJsaWNMaXN0ZW5lchIpLnAycHN0cmVhbS52MS5FbmFibGVQdWJsaWNMaXN0ZW5lclJlcXVlc3QaKi5wMnBzdHJlYW0udjEuRW5hYmxlUHVibGljTGlzdGVuZXJSZXNwb25zZSIAEnIKFURpc2FibGVQdWJsaWNMaXN0ZW5lchIqLnAycHN0cmVhbS52MS5EaXNhYmxlUHVibGljTGlzdGVuZXJSZXF1ZXN0GisucDJwc3RyZWFtLnYxLkRpc2FibGVQdWJsaWNMaXN0ZW5lclJlc3BvbnNlIgASbAoTU3RhcnRQdWJsaWNMaXN0ZW5lchIoLnAycHN0cmVhbS52MS5TdGFydFB1YmxpY0xpc3RlbmVyUmVxdWVzdBopLnAycHN0cmVhbS52MS5TdGFydFB1YmxpY0xpc3RlbmVyUmVzcG9uc2UiABJpChJTdG9wUHVibGljTGlzdGVuZXISJy5wMnBzdHJlYW0udjEuU3RvcFB1YmxpY0xpc3RlbmVyUmVxdWVzdBooLnAycHN0cmVhbS52MS5TdG9wUHVibGljTGlzdGVuZXJSZXNwb25zZSIAEmYKEUNyZWF0ZVB1YmxpY1JvdXRlEiYucDJwc3RyZWFtLnYxLkNyZWF0ZVB1YmxpY1JvdXRlUmVxdWVzdBonLnAycHN0cmVhbS52MS5DcmVhdGVQdWJsaWNSb3V0ZVJlc3BvbnNlIgASZgoRVXBkYXRlUHVibGljUm91dGUSJi5wMnBzdHJlYW0udjEuVXBkYXRlUHVibGljUm91dGVSZXF1ZXN0GicucDJwc3RyZWFtLnYxLlVwZGF0ZVB1YmxpY1JvdXRlUmVzcG9uc2UiABJmChFEZWxldGVQdWJsaWNSb3V0ZRImLnAycHN0cmVhbS52MS5EZWxldGVQdWJsaWNSb3V0ZVJlcXVlc3QaJy5wMnBzdHJlYW0udjEuRGVsZXRlUHVibGljUm91dGVSZXNwb25zZSIAEocBChxDcmVhdGVQdWJsaWNUbHNEbnNDcmVkZW50aWFsEjEucDJwc3RyZWFtLnYxLkNyZWF0ZVB1YmxpY1Rsc0Ruc0NyZWRlbnRpYWxSZXF1ZXN0GjIucDJwc3RyZWFtLnYxLkNyZWF0ZVB1YmxpY1Rsc0Ruc0NyZWRlbnRpYWxSZXNwb25zZSIAEocBChxVcGRhdGVQdWJsaWNUbHNEbnNDcmVkZW50aWFsEjEucDJwc3RyZWFtLnYxLlVwZGF0ZVB1YmxpY1Rsc0Ruc0NyZWRlbnRpYWxSZXF1ZXN0GjIucDJwc3RyZWFtLnYxLlVwZGF0ZVB1YmxpY1Rsc0Ruc0NyZWRlbnRpYWxSZXNwb25zZSIAEocBChxEZWxldGVQdWJsaWNUbHNEbnNDcmVkZW50aWFsEjEucDJwc3RyZWFtLnYxLkRlbGV0ZVB1YmxpY1Rsc0Ruc0NyZWRlbnRpYWxSZXF1ZXN0GjIucDJwc3RyZWFtLnYxLkRlbGV0ZVB1YmxpY1Rsc0Ruc0NyZWRlbnRpYWxSZXNwb25zZSIAEoEBChpDcmVhdGVQdWJsaWNUbHNDZXJ0aWZpY2F0ZRIvLnAycHN0cmVhbS52MS5DcmVhdGVQdWJsaWNUbHNDZXJ0aWZpY2F0ZVJlcXVlc3QaMC5wMnBzdHJlYW0udjEuQ3JlYXRlUHVibGljVGxzQ2VydGlmaWNhdGVSZXNwb25zZSIAEoEBChpVcGRhdGVQdWJsaWNUbHNDZXJ0aWZpY2F0ZRIvLnAycHN0cmVhbS52MS5VcGRhdGVQdWJsaWNUbHNDZXJ0aWZpY2F0ZVJlcXVlc3QaMC5wMnBzdHJlYW0udjEuVXBkYXRlUHVibGljVGxzQ2VydGlmaWNhdGVSZXNwb25zZSIAEoEBChpEZWxldGVQdWJsaWNUbHNDZXJ0aWZpY2F0ZRIvLnAycHN0cmVhbS52MS5EZWxldGVQdWJsaWNUbHNDZXJ0aWZpY2F0ZVJlcXVlc3QaMC5wMnBzdHJlYW0udjEuRGVsZXRlUHVibGljVGxzQ2VydGlmaWNhdGVSZXNwb25zZSIAEn4KGVJlbmV3UHVibGljVGxzQ2VydGlmaWNhdGUSLi5wMnBzdHJlYW0udjEuUmVuZXdQdWJsaWNUbHNDZXJ0aWZpY2F0ZVJlcXVlc3QaLy5wMnBzdHJlYW0udjEuUmVuZXdQdWJsaWNUbHNDZXJ0aWZpY2F0ZVJlc3BvbnNlIgASfgoZQ3JlYXRlUHVibGljUmF0ZUxpbWl0UnVsZRIuLnAycHN0cmVhbS52MS5DcmVhdGVQdWJsaWNSYXRlTGltaXRSdWxlUmVxdWVzdBovLnAycHN0cmVhbS52MS5DcmVhdGVQdWJsaWNSYXRlTGltaXRSdWxlUmVzcG9uc2UiABJ+ChlVcGRhdGVQdWJsaWNSYXRlTGltaXRSdWxlEi4ucDJwc3RyZWFtLnYxLlVwZGF0ZVB1YmxpY1JhdGVMaW1pdFJ1bGVSZXF1ZXN0Gi8ucDJwc3RyZWFtLnYxLlVwZGF0ZVB1YmxpY1JhdGVMaW1pdFJ1bGVSZXNwb25zZSIAEn4KGURlbGV0ZVB1YmxpY1JhdGVMaW1pdFJ1bGUSLi5wMnBzdHJlYW0udjEuRGVsZXRlUHVibGljUmF0ZUxpbWl0UnVsZVJlcXVlc3QaLy5wMnBzdHJlYW0udjEuRGVsZXRlUHVibGljUmF0ZUxpbWl0UnVsZVJlc3BvbnNlIgASigEKHUNyZWF0ZVB1YmxpY1RyYWZmaWNTaGFwZXJSdWxlEjIucDJwc3RyZWFtLnYxLkNyZWF0ZVB1YmxpY1RyYWZmaWNTaGFwZXJSdWxlUmVxdWVzdBozLnAycHN0cmVhbS52MS5DcmVhdGVQdWJsaWNUcmFmZmljU2hhcGVyUnVsZVJlc3BvbnNlIgASigEKHVVwZGF0ZVB1YmxpY1RyYWZmaWNTaGFwZXJSdWxlEjIucDJwc3RyZWFtLnYxLlVwZGF0ZVB1YmxpY1RyYWZmaWNTaGFwZXJSdWxlUmVxdWVzdBozLnAycHN0cmVhbS52MS5VcGRhdGVQdWJsaWNUcmFmZmljU2hhcGVyUnVsZVJlc3BvbnNlIgASigEKHURlbGV0ZVB1YmxpY1RyYWZmaWNTaGFwZXJSdWxlEjIucDJwc3RyZWFtLnYxLkRlbGV0ZVB1YmxpY1RyYWZmaWNTaGFwZXJSdWxlUmVxdWVzdBozLnAycHN0cmVhbS52MS5EZWxldGVQdWJsaWNUcmFmZmljU2hhcGVyUnVsZVJlc3BvbnNlIgASjQEKHkNyZWF0ZVB1YmxpY1dhZkNhcHRjaGFQcm92aWRlchIzLnAycHN0cmVhbS52MS5DcmVhdGVQdWJsaWNXYWZDYXB0Y2hhUHJvdmlkZXJSZXF1ZXN0GjQucDJwc3RyZWFtLnYxLkNyZWF0ZVB1YmxpY1dhZkNhcHRjaGFQcm92aWRlclJlc3BvbnNlIgASjQEKHlVwZGF0ZVB1YmxpY1dhZkNhcHRjaGFQcm92aWRlchIzLnAycHN0cmVhbS52MS5VcGRhdGVQdWJsaWNXYWZDYXB0Y2hhUHJvdmlkZXJSZXF1ZXN0GjQucDJwc3RyZWFtLnYxLlVwZGF0ZVB1YmxpY1dhZkNhcHRjaGFQcm92aWRlclJlc3BvbnNlIgASjQEKHkRlbGV0ZVB1YmxpY1dhZkNhcHRjaGFQcm92aWRlchIzLnAycHN0cmVhbS52MS5EZWxldGVQdWJsaWNXYWZDYXB0Y2hhUHJvdmlkZXJSZXF1ZXN0GjQucDJwc3RyZWFtLnYxLkRlbGV0ZVB1YmxpY1dhZkNhcHRjaGFQcm92aWRlclJlc3BvbnNlIgASbAoTQ3JlYXRlUHVibGljV2FmUnVsZRIoLnAycHN0cmVhbS52MS5DcmVhdGVQdWJsaWNXYWZSdWxlUmVxdWVzdBopLnAycHN0cmVhbS52MS5DcmVhdGVQdWJsaWNXYWZSdWxlUmVzcG9uc2UiABJsChNVcGRhdGVQdWJsaWNXYWZSdWxlEigucDJwc3RyZWFtLnYxLlVwZGF0ZVB1YmxpY1dhZlJ1bGVSZXF1ZXN0GikucDJwc3RyZWFtLnYxLlVwZGF0ZVB1YmxpY1dhZlJ1bGVSZXNwb25zZSIAEmwKE0RlbGV0ZVB1YmxpY1dhZlJ1bGUSKC5wMnBzdHJlYW0udjEuRGVsZXRlUHVibGljV2FmUnVsZVJlcXVlc3QaKS5wMnBzdHJlYW0udjEuRGVsZXRlUHVibGljV2FmUnVsZVJlc3BvbnNlIgAScgoVQ3JlYXRlUHVibGljQ2FjaGVSdWxlEioucDJwc3RyZWFtLnYxLkNyZWF0ZVB1YmxpY0NhY2hlUnVsZVJlcXVlc3QaKy5wMnBzdHJlYW0udjEuQ3JlYXRlUHVibGljQ2FjaGVSdWxlUmVzcG9uc2UiABJyChVVcGRhdGVQdWJsaWNDYWNoZVJ1bGUSKi5wMnBzdHJlYW0udjEuVXBkYXRlUHVibGljQ2FjaGVSdWxlUmVxdWVzdBorLnAycHN0cmVhbS52MS5VcGRhdGVQdWJsaWNDYWNoZVJ1bGVSZXNwb25zZSIAEnIKFURlbGV0ZVB1YmxpY0NhY2hlUnVsZRIqLnAycHN0cmVhbS52MS5EZWxldGVQdWJsaWNDYWNoZVJ1bGVSZXF1ZXN0GisucDJwc3RyZWFtLnYxLkRlbGV0ZVB1YmxpY0NhY2hlUnVsZVJlc3BvbnNlIgASfgoZVXBkYXRlUHVibGljQ2FjaGVTZXR0aW5ncxIuLnAycHN0cmVhbS52MS5VcGRhdGVQdWJsaWNDYWNoZVNldHRpbmdzUmVxdWVzdBovLnAycHN0cmVhbS52MS5VcGRhdGVQdWJsaWNDYWNoZVNldHRpbmdzUmVzcG9uc2UiABJjChBQdXJnZVB1YmxpY0NhY2hlEiUucDJwc3RyZWFtLnYxLlB1cmdlUHVibGljQ2FjaGVSZXF1ZXN0GiYucDJwc3RyZWFtLnYxLlB1cmdlUHVibGljQ2FjaGVSZXNwb25zZSIAQqIBChBjb20ucDJwc3RyZWFtLnYxQg9NYW5hZ2VtZW50UHJvdG9QAVoscDJwc3RyZWFtL2dlbi9wcm90by9wMnBzdHJlYW0vdjE7cDJwc3RyZWFtdjGiAgNQWFiqAgxQMnBzdHJlYW0uVjHKAgxQMnBzdHJlYW1cVjHiAhhQMnBzdHJlYW1cVjFcR1BCTWV0YWRhdGHqAg1QMnBzdHJlYW06OlYxYgZwcm90bzM"); /** * @generated from message p2pstream.v1.AgentStatsRequest @@ -6096,6 +6096,291 @@ export type GetDashboardResponse = Message<"p2pstream.v1.GetDashboardResponse"> export const GetDashboardResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_proto_p2pstream_v1_management, 154); +/** + * @generated from message p2pstream.v1.GetDashboardDiagnosticsRequest + */ +export type GetDashboardDiagnosticsRequest = Message<"p2pstream.v1.GetDashboardDiagnosticsRequest"> & { + /** + * @generated from field: string window_label = 1; + */ + windowLabel: string; + + /** + * @generated from field: int64 sample_limit = 2; + */ + sampleLimit: bigint; +}; + +/** + * Describes the message p2pstream.v1.GetDashboardDiagnosticsRequest. + * Use `create(GetDashboardDiagnosticsRequestSchema)` to create a new message. + */ +export const GetDashboardDiagnosticsRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_proto_p2pstream_v1_management, 155); + +/** + * @generated from message p2pstream.v1.DashboardDiagnosticsOutcomeSummary + */ +export type DashboardDiagnosticsOutcomeSummary = Message<"p2pstream.v1.DashboardDiagnosticsOutcomeSummary"> & { + /** + * @generated from field: string label = 1; + */ + label: string; + + /** + * @generated from field: int64 since_unix_millis = 2; + */ + sinceUnixMillis: bigint; + + /** + * @generated from field: int64 requests = 3; + */ + requests: bigint; + + /** + * @generated from field: int64 success = 4; + */ + success: bigint; + + /** + * @generated from field: int64 client_error = 5; + */ + clientError: bigint; + + /** + * @generated from field: int64 server_error = 6; + */ + serverError: bigint; + + /** + * @generated from field: int64 non_success = 7; + */ + nonSuccess: bigint; + + /** + * @generated from field: int64 proxy_failure = 8; + */ + proxyFailure: bigint; + + /** + * @generated from field: int64 avg_duration_ms = 9; + */ + avgDurationMs: bigint; + + /** + * @generated from field: int64 max_duration_ms = 10; + */ + maxDurationMs: bigint; +}; + +/** + * Describes the message p2pstream.v1.DashboardDiagnosticsOutcomeSummary. + * Use `create(DashboardDiagnosticsOutcomeSummarySchema)` to create a new message. + */ +export const DashboardDiagnosticsOutcomeSummarySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_proto_p2pstream_v1_management, 156); + +/** + * @generated from message p2pstream.v1.DashboardStatusCodeSummary + */ +export type DashboardStatusCodeSummary = Message<"p2pstream.v1.DashboardStatusCodeSummary"> & { + /** + * @generated from field: int64 status_code = 1; + */ + statusCode: bigint; + + /** + * @generated from field: int64 requests = 2; + */ + requests: bigint; + + /** + * @generated from field: int64 success = 3; + */ + success: bigint; + + /** + * @generated from field: int64 client_error = 4; + */ + clientError: bigint; + + /** + * @generated from field: int64 server_error = 5; + */ + serverError: bigint; + + /** + * @generated from field: int64 proxy_failure = 6; + */ + proxyFailure: bigint; + + /** + * @generated from field: int64 avg_duration_ms = 7; + */ + avgDurationMs: bigint; + + /** + * @generated from field: uint64 request_bytes = 8; + */ + requestBytes: bigint; + + /** + * @generated from field: uint64 response_bytes = 9; + */ + responseBytes: bigint; +}; + +/** + * Describes the message p2pstream.v1.DashboardStatusCodeSummary. + * Use `create(DashboardStatusCodeSummarySchema)` to create a new message. + */ +export const DashboardStatusCodeSummarySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_proto_p2pstream_v1_management, 157); + +/** + * @generated from message p2pstream.v1.DashboardDiagnosticsSample + */ +export type DashboardDiagnosticsSample = Message<"p2pstream.v1.DashboardDiagnosticsSample"> & { + /** + * @generated from field: int64 occurred_at_unix_millis = 1; + */ + occurredAtUnixMillis: bigint; + + /** + * @generated from field: string method = 2; + */ + method: string; + + /** + * @generated from field: string host = 3; + */ + host: string; + + /** + * @generated from field: string path_prefix = 4; + */ + pathPrefix: string; + + /** + * @generated from field: int64 status_code = 5; + */ + statusCode: bigint; + + /** + * @generated from field: string error_kind = 6; + */ + errorKind: string; + + /** + * @generated from field: string listener_label = 7; + */ + listenerLabel: string; + + /** + * @generated from field: string route_label = 8; + */ + routeLabel: string; + + /** + * @generated from field: string route_target_label = 9; + */ + routeTargetLabel: string; + + /** + * @generated from field: string agent_label = 10; + */ + agentLabel: string; + + /** + * @generated from field: int64 duration_ms = 11; + */ + durationMs: bigint; + + /** + * @generated from field: uint64 request_bytes = 12; + */ + requestBytes: bigint; + + /** + * @generated from field: uint64 response_bytes = 13; + */ + responseBytes: bigint; +}; + +/** + * Describes the message p2pstream.v1.DashboardDiagnosticsSample. + * Use `create(DashboardDiagnosticsSampleSchema)` to create a new message. + */ +export const DashboardDiagnosticsSampleSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_proto_p2pstream_v1_management, 158); + +/** + * @generated from message p2pstream.v1.GetDashboardDiagnosticsResponse + */ +export type GetDashboardDiagnosticsResponse = Message<"p2pstream.v1.GetDashboardDiagnosticsResponse"> & { + /** + * @generated from field: string label = 1; + */ + label: string; + + /** + * @generated from field: int64 since_unix_millis = 2; + */ + sinceUnixMillis: bigint; + + /** + * @generated from field: int64 generated_at_unix_millis = 3; + */ + generatedAtUnixMillis: bigint; + + /** + * @generated from field: p2pstream.v1.DashboardDiagnosticsOutcomeSummary outcome = 4; + */ + outcome?: DashboardDiagnosticsOutcomeSummary | undefined; + + /** + * @generated from field: repeated p2pstream.v1.DashboardStatusCodeSummary status_codes = 5; + */ + statusCodes: DashboardStatusCodeSummary[]; + + /** + * @generated from field: repeated p2pstream.v1.DashboardProxyDimensionSummary error_kinds = 6; + */ + errorKinds: DashboardProxyDimensionSummary[]; + + /** + * @generated from field: repeated p2pstream.v1.DashboardProxyDimensionSummary problem_listeners = 7; + */ + problemListeners: DashboardProxyDimensionSummary[]; + + /** + * @generated from field: repeated p2pstream.v1.DashboardProxyDimensionSummary problem_routes = 8; + */ + problemRoutes: DashboardProxyDimensionSummary[]; + + /** + * @generated from field: repeated p2pstream.v1.DashboardProxyDimensionSummary problem_route_targets = 9; + */ + problemRouteTargets: DashboardProxyDimensionSummary[]; + + /** + * @generated from field: repeated p2pstream.v1.DashboardProxyDimensionSummary problem_agents = 10; + */ + problemAgents: DashboardProxyDimensionSummary[]; + + /** + * @generated from field: repeated p2pstream.v1.DashboardDiagnosticsSample recent_samples = 11; + */ + recentSamples: DashboardDiagnosticsSample[]; +}; + +/** + * Describes the message p2pstream.v1.GetDashboardDiagnosticsResponse. + * Use `create(GetDashboardDiagnosticsResponseSchema)` to create a new message. + */ +export const GetDashboardDiagnosticsResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_proto_p2pstream_v1_management, 159); + /** * @generated from message p2pstream.v1.TrafficTraceSettings */ @@ -6136,7 +6421,7 @@ export type TrafficTraceSettings = Message<"p2pstream.v1.TrafficTraceSettings"> * Use `create(TrafficTraceSettingsSchema)` to create a new message. */ export const TrafficTraceSettingsSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_proto_p2pstream_v1_management, 155); + messageDesc(file_proto_p2pstream_v1_management, 160); /** * @generated from message p2pstream.v1.GetTrafficTraceSettingsRequest @@ -6149,7 +6434,7 @@ export type GetTrafficTraceSettingsRequest = Message<"p2pstream.v1.GetTrafficTra * Use `create(GetTrafficTraceSettingsRequestSchema)` to create a new message. */ export const GetTrafficTraceSettingsRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_proto_p2pstream_v1_management, 156); + messageDesc(file_proto_p2pstream_v1_management, 161); /** * @generated from message p2pstream.v1.GetTrafficTraceSettingsResponse @@ -6166,7 +6451,7 @@ export type GetTrafficTraceSettingsResponse = Message<"p2pstream.v1.GetTrafficTr * Use `create(GetTrafficTraceSettingsResponseSchema)` to create a new message. */ export const GetTrafficTraceSettingsResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_proto_p2pstream_v1_management, 157); + messageDesc(file_proto_p2pstream_v1_management, 162); /** * @generated from message p2pstream.v1.SetTrafficTraceSettingsRequest @@ -6188,7 +6473,7 @@ export type SetTrafficTraceSettingsRequest = Message<"p2pstream.v1.SetTrafficTra * Use `create(SetTrafficTraceSettingsRequestSchema)` to create a new message. */ export const SetTrafficTraceSettingsRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_proto_p2pstream_v1_management, 158); + messageDesc(file_proto_p2pstream_v1_management, 163); /** * @generated from message p2pstream.v1.SetTrafficTraceSettingsResponse @@ -6205,7 +6490,7 @@ export type SetTrafficTraceSettingsResponse = Message<"p2pstream.v1.SetTrafficTr * Use `create(SetTrafficTraceSettingsResponseSchema)` to create a new message. */ export const SetTrafficTraceSettingsResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_proto_p2pstream_v1_management, 159); + messageDesc(file_proto_p2pstream_v1_management, 164); /** * @generated from message p2pstream.v1.StreamTrafficTraceEventsRequest @@ -6227,7 +6512,7 @@ export type StreamTrafficTraceEventsRequest = Message<"p2pstream.v1.StreamTraffi * Use `create(StreamTrafficTraceEventsRequestSchema)` to create a new message. */ export const StreamTrafficTraceEventsRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_proto_p2pstream_v1_management, 160); + messageDesc(file_proto_p2pstream_v1_management, 165); /** * @generated from message p2pstream.v1.TrafficTraceEvent @@ -6484,7 +6769,7 @@ export type TrafficTraceEvent = Message<"p2pstream.v1.TrafficTraceEvent"> & { * Use `create(TrafficTraceEventSchema)` to create a new message. */ export const TrafficTraceEventSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_proto_p2pstream_v1_management, 161); + messageDesc(file_proto_p2pstream_v1_management, 166); /** * @generated from message p2pstream.v1.StreamTrafficTraceEventsResponse @@ -6511,7 +6796,7 @@ export type StreamTrafficTraceEventsResponse = Message<"p2pstream.v1.StreamTraff * Use `create(StreamTrafficTraceEventsResponseSchema)` to create a new message. */ export const StreamTrafficTraceEventsResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_proto_p2pstream_v1_management, 162); + messageDesc(file_proto_p2pstream_v1_management, 167); /** * @generated from message p2pstream.v1.GetSetupStateRequest @@ -6524,7 +6809,7 @@ export type GetSetupStateRequest = Message<"p2pstream.v1.GetSetupStateRequest"> * Use `create(GetSetupStateRequestSchema)` to create a new message. */ export const GetSetupStateRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_proto_p2pstream_v1_management, 163); + messageDesc(file_proto_p2pstream_v1_management, 168); /** * @generated from message p2pstream.v1.GetSetupStateResponse @@ -6556,7 +6841,7 @@ export type GetSetupStateResponse = Message<"p2pstream.v1.GetSetupStateResponse" * Use `create(GetSetupStateResponseSchema)` to create a new message. */ export const GetSetupStateResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_proto_p2pstream_v1_management, 164); + messageDesc(file_proto_p2pstream_v1_management, 169); /** * @generated from message p2pstream.v1.SetupAdminRequest @@ -6583,7 +6868,7 @@ export type SetupAdminRequest = Message<"p2pstream.v1.SetupAdminRequest"> & { * Use `create(SetupAdminRequestSchema)` to create a new message. */ export const SetupAdminRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_proto_p2pstream_v1_management, 165); + messageDesc(file_proto_p2pstream_v1_management, 170); /** * @generated from message p2pstream.v1.SetupAdminResponse @@ -6600,7 +6885,7 @@ export type SetupAdminResponse = Message<"p2pstream.v1.SetupAdminResponse"> & { * Use `create(SetupAdminResponseSchema)` to create a new message. */ export const SetupAdminResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_proto_p2pstream_v1_management, 166); + messageDesc(file_proto_p2pstream_v1_management, 171); /** * @generated from message p2pstream.v1.LoginRequest @@ -6622,7 +6907,7 @@ export type LoginRequest = Message<"p2pstream.v1.LoginRequest"> & { * Use `create(LoginRequestSchema)` to create a new message. */ export const LoginRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_proto_p2pstream_v1_management, 167); + messageDesc(file_proto_p2pstream_v1_management, 172); /** * @generated from message p2pstream.v1.LoginResponse @@ -6639,7 +6924,7 @@ export type LoginResponse = Message<"p2pstream.v1.LoginResponse"> & { * Use `create(LoginResponseSchema)` to create a new message. */ export const LoginResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_proto_p2pstream_v1_management, 168); + messageDesc(file_proto_p2pstream_v1_management, 173); /** * @generated from message p2pstream.v1.LogoutRequest @@ -6652,7 +6937,7 @@ export type LogoutRequest = Message<"p2pstream.v1.LogoutRequest"> & { * Use `create(LogoutRequestSchema)` to create a new message. */ export const LogoutRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_proto_p2pstream_v1_management, 169); + messageDesc(file_proto_p2pstream_v1_management, 174); /** * @generated from message p2pstream.v1.LogoutResponse @@ -6665,7 +6950,7 @@ export type LogoutResponse = Message<"p2pstream.v1.LogoutResponse"> & { * Use `create(LogoutResponseSchema)` to create a new message. */ export const LogoutResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_proto_p2pstream_v1_management, 170); + messageDesc(file_proto_p2pstream_v1_management, 175); /** * @generated from message p2pstream.v1.GetCurrentUserRequest @@ -6678,7 +6963,7 @@ export type GetCurrentUserRequest = Message<"p2pstream.v1.GetCurrentUserRequest" * Use `create(GetCurrentUserRequestSchema)` to create a new message. */ export const GetCurrentUserRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_proto_p2pstream_v1_management, 171); + messageDesc(file_proto_p2pstream_v1_management, 176); /** * @generated from message p2pstream.v1.GetCurrentUserResponse @@ -6695,7 +6980,7 @@ export type GetCurrentUserResponse = Message<"p2pstream.v1.GetCurrentUserRespons * Use `create(GetCurrentUserResponseSchema)` to create a new message. */ export const GetCurrentUserResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_proto_p2pstream_v1_management, 172); + messageDesc(file_proto_p2pstream_v1_management, 177); /** * @generated from message p2pstream.v1.StartProxyRequest @@ -6708,7 +6993,7 @@ export type StartProxyRequest = Message<"p2pstream.v1.StartProxyRequest"> & { * Use `create(StartProxyRequestSchema)` to create a new message. */ export const StartProxyRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_proto_p2pstream_v1_management, 173); + messageDesc(file_proto_p2pstream_v1_management, 178); /** * @generated from message p2pstream.v1.StartProxyResponse @@ -6725,7 +7010,7 @@ export type StartProxyResponse = Message<"p2pstream.v1.StartProxyResponse"> & { * Use `create(StartProxyResponseSchema)` to create a new message. */ export const StartProxyResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_proto_p2pstream_v1_management, 174); + messageDesc(file_proto_p2pstream_v1_management, 179); /** * @generated from message p2pstream.v1.StopProxyRequest @@ -6738,7 +7023,7 @@ export type StopProxyRequest = Message<"p2pstream.v1.StopProxyRequest"> & { * Use `create(StopProxyRequestSchema)` to create a new message. */ export const StopProxyRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_proto_p2pstream_v1_management, 175); + messageDesc(file_proto_p2pstream_v1_management, 180); /** * @generated from message p2pstream.v1.StopProxyResponse @@ -6755,7 +7040,7 @@ export type StopProxyResponse = Message<"p2pstream.v1.StopProxyResponse"> & { * Use `create(StopProxyResponseSchema)` to create a new message. */ export const StopProxyResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_proto_p2pstream_v1_management, 176); + messageDesc(file_proto_p2pstream_v1_management, 181); /** * @generated from enum p2pstream.v1.UserRole @@ -8030,6 +8315,14 @@ export const AgentManagementService: GenService<{ input: typeof GetDashboardRequestSchema; output: typeof GetDashboardResponseSchema; }, + /** + * @generated from rpc p2pstream.v1.AgentManagementService.GetDashboardDiagnostics + */ + getDashboardDiagnostics: { + methodKind: "unary"; + input: typeof GetDashboardDiagnosticsRequestSchema; + output: typeof GetDashboardDiagnosticsResponseSchema; + }, /** * @generated from rpc p2pstream.v1.AgentManagementService.GetTrafficTraceSettings */ diff --git a/web/management/src/lib/dashboardStats.test.ts b/web/management/src/lib/dashboardStats.test.ts index 7bfc689..4ccc176 100644 --- a/web/management/src/lib/dashboardStats.test.ts +++ b/web/management/src/lib/dashboardStats.test.ts @@ -21,10 +21,14 @@ import { formatNumber, formatPercent, fleetUptimePercent, + formatPathPrefix, + nonSuccessRate, + nonSuccessRequests, + proxyFailureRequests, recentDisconnectCount, + statusTone, statusClassCounts, successRate, - errorRate, windowByLabel, } from "@/lib/dashboardStats"; import { DashboardProxyDimension } from "@/gen/proto/p2pstream/v1/management_pb"; @@ -37,19 +41,33 @@ describe("dashboardStats", () => { expect(windowByLabel(dashboard, "24h")).toBeNull(); }); - test("computes success and error rates", () => { + test("computes success, non-success, and proxy failure metrics", () => { const window = windowSummary({ proxyRequests: 10n, proxySuccess: 7n, proxyClientError: 2n, proxyServerError: 1n, + proxyInternalError: 4n, }); expect(successRate(window)).toBe(0.7); - expect(errorRate(window)).toBe(0.3); + expect(nonSuccessRequests(window)).toBe(3n); + expect(proxyFailureRequests(window)).toBe(4n); + expect(nonSuccessRate(window)).toBe(0.3); expect(successRate(windowSummary({ proxyRequests: 0n }))).toBe(0); }); + test("maps status tones and formats path prefixes", () => { + expect(statusTone(200n)).toBe("success"); + expect(statusTone(301n)).toBe("redirect"); + expect(statusTone(404n)).toBe("client-error"); + expect(statusTone(502n)).toBe("server-error"); + expect(statusTone(102n)).toBe("neutral"); + expect(formatPathPrefix("/api/users/...")).toBe("/api/users/..."); + expect(formatPathPrefix("")).toBe("-"); + expect(formatPathPrefix(undefined)).toBe("-"); + }); + test("computes cache hit rate from lookups and excludes bypasses", () => { const window = windowSummary({ proxyCacheHits: 7n, @@ -124,7 +142,7 @@ describe("dashboardStats", () => { expect(filled).toHaveLength(12); expect(filled[10]?.requests).toBe(3n); - expect(filled[10]?.errors).toBe(1n); + expect(filled[10]?.nonSuccess).toBe(1n); expect(filled[10]?.totalBytes).toBe(300n); expect(filled[0]?.requests).toBe(0n); }); diff --git a/web/management/src/lib/dashboardStats.ts b/web/management/src/lib/dashboardStats.ts index 668a038..6031a63 100644 --- a/web/management/src/lib/dashboardStats.ts +++ b/web/management/src/lib/dashboardStats.ts @@ -20,7 +20,7 @@ export type DashboardTrafficBucketView = { requestBytes: bigint; responseBytes: bigint; avgDurationMs: bigint; - errors: bigint; + nonSuccess: bigint; totalBytes: bigint; }; @@ -51,10 +51,32 @@ export function successRate(window: DashboardWindowSummary | null | undefined): return ratio(window.proxySuccess, window.proxyRequests); } -export function errorRate(window: DashboardWindowSummary | null | undefined): number { +export function nonSuccessRequests(window: DashboardWindowSummary | null | undefined): bigint { + return (window?.proxyClientError ?? 0n) + (window?.proxyServerError ?? 0n); +} + +export function proxyFailureRequests(window: DashboardWindowSummary | null | undefined): bigint { + return window?.proxyInternalError ?? 0n; +} + +export function nonSuccessRate(window: DashboardWindowSummary | null | undefined): number { if (!window || window.proxyRequests === 0n) return 0; - const errors = window.proxyClientError + window.proxyServerError + window.proxyInternalError; - return ratio(errors, window.proxyRequests); + return ratio(nonSuccessRequests(window), window.proxyRequests); +} + +export function statusTone(statusCode: bigint | number | null | undefined): "success" | "redirect" | "client-error" | "server-error" | "neutral" { + if (statusCode === null || statusCode === undefined) return "neutral"; + const status = toSafeNumber(statusCode); + if (status >= 200 && status < 300) return "success"; + if (status >= 300 && status < 400) return "redirect"; + if (status >= 400 && status < 500) return "client-error"; + if (status >= 500) return "server-error"; + return "neutral"; +} + +export function formatPathPrefix(pathPrefix: string | null | undefined): string { + const value = pathPrefix?.trim() ?? ""; + return value || "-"; } export function cacheLookupRequests(window: DashboardWindowSummary | null | undefined): bigint { @@ -181,7 +203,7 @@ export function filledTrafficBuckets( requestBytes, responseBytes, avgDurationMs: existing?.avgDurationMs ?? 0n, - errors: clientError + serverError + internalError, + nonSuccess: clientError + serverError, totalBytes: requestBytes + responseBytes, }); } diff --git a/web/management/src/router.ts b/web/management/src/router.ts index bdccca6..756e92e 100644 --- a/web/management/src/router.ts +++ b/web/management/src/router.ts @@ -1,5 +1,6 @@ import { createRouter, createWebHashHistory } from 'vue-router'; import Overview from './views/Overview.vue'; +import Diagnostics from './views/Diagnostics.vue'; import Traffic from './views/Traffic.vue'; import AgentHealth from './views/AgentHealth.vue'; import Settings from './views/Settings.vue'; @@ -13,6 +14,7 @@ import TlsConfig from './views/TlsConfig.vue'; const routes = [ { path: '/', redirect: '/overview' }, { path: '/overview', name: 'overview', component: Overview }, + { path: '/diagnostics', name: 'diagnostics', component: Diagnostics }, { path: '/traffic', name: 'traffic', component: Traffic }, { path: '/agent', name: 'agent', component: AgentHealth }, { diff --git a/web/management/src/views/Diagnostics.vue b/web/management/src/views/Diagnostics.vue new file mode 100644 index 0000000..6f032b9 --- /dev/null +++ b/web/management/src/views/Diagnostics.vue @@ -0,0 +1,694 @@ + + + + + diff --git a/web/management/src/views/Overview.vue b/web/management/src/views/Overview.vue index a214389..f7999b5 100644 --- a/web/management/src/views/Overview.vue +++ b/web/management/src/views/Overview.vue @@ -15,7 +15,6 @@ import { cacheActivityRequests, cacheHitRate, cacheLookupRequests, - errorRate, filledTrafficBuckets, formatByteRate, formatBytes, @@ -23,6 +22,8 @@ import { formatNumber, formatPercent, fleetUptimePercent, + nonSuccessRequests, + proxyFailureRequests, requestsPerSecond, statusClassCounts, successRate, @@ -151,8 +152,8 @@ function agentsMetricSubline(): string { return `${formatPercent(fleetUptime.value)} uptime / ${active}`; } -function rowErrors(row: DashboardProxyDimensionSummary): bigint { - return row.clientError + row.serverError + row.internalError; +function rowNonSuccess(row: DashboardProxyDimensionSummary): bigint { + return row.clientError + row.serverError; } function rowSuccess(row: DashboardProxyDimensionSummary): string { @@ -174,14 +175,14 @@ function bucketHeight(bucket: DashboardTrafficBucketView): string { } function bucketErrorHeight(bucket: DashboardTrafficBucketView): string { - if (bucket.requests === 0n || bucket.errors === 0n) return "0%"; - return `${Math.max(12, Math.round((Number(bucket.errors) / Number(bucket.requests)) * 100)).toString()}%`; + if (bucket.requests === 0n || bucket.nonSuccess === 0n) return "0%"; + return `${Math.max(12, Math.round((Number(bucket.nonSuccess) / Number(bucket.requests)) * 100)).toString()}%`; } function bucketTitle(bucket: DashboardTrafficBucket | DashboardTrafficBucketView): string { const start = new Date(Number(bucket.bucketUnixMillis)); - const errors = "errors" in bucket ? bucket.errors : bucket.clientError + bucket.serverError + bucket.internalError; - return `${start.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}: ${formatNumber(bucket.requests)} requests, ${formatNumber(errors)} errors, down ${formatBytes(bucket.responseBytes)}, up ${formatBytes(bucket.requestBytes)}`; + const nonSuccess = "nonSuccess" in bucket ? bucket.nonSuccess : bucket.clientError + bucket.serverError; + return `${start.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}: ${formatNumber(bucket.requests)} requests, ${formatNumber(nonSuccess)} non-success, down ${formatBytes(bucket.responseBytes)}, up ${formatBytes(bucket.requestBytes)}`; } @@ -226,7 +227,7 @@ function bucketTitle(bucket: DashboardTrafficBucket | DashboardTrafficBucketView

Success

{{ formatPercent(successRate(selectedWindow)) }}
-

{{ formatPercent(errorRate(selectedWindow)) }} errors

+

{{ formatNumber(nonSuccessRequests(selectedWindow)) }} non-success / {{ formatNumber(proxyFailureRequests(selectedWindow)) }} proxy failures

@@ -325,9 +326,12 @@ function bucketTitle(bucket: DashboardTrafficBucket | DashboardTrafficBucketView

Problem Signals

Selected window plus last-hour error kinds.

- - {{ formatNumber(selectedWindow?.proxySlowRequests) }} slow - +
+ View diagnostics + + {{ formatNumber(selectedWindow?.proxySlowRequests) }} slow + +
@@ -346,7 +350,7 @@ function bucketTitle(bucket: DashboardTrafficBucket | DashboardTrafficBucketView {{ errorKindLabel(error.label) }} {{ formatNumber(error.requests) }}
-
No proxy errors in the last hour.
+
No proxy failures in the last hour.
@@ -379,7 +383,7 @@ function bucketTitle(bucket: DashboardTrafficBucket | DashboardTrafficBucketView Name Requests Success - Errors + Non-success Avg latency Down Up @@ -390,7 +394,7 @@ function bucketTitle(bucket: DashboardTrafficBucket | DashboardTrafficBucketView {{ row.label }} {{ formatNumber(row.requests) }} {{ rowSuccess(row) }} - {{ formatNumber(rowErrors(row)) }} + {{ formatNumber(rowNonSuccess(row)) }} {{ formatDuration(row.avgDurationMs) }} {{ formatBytes(row.responseBytes) }} {{ formatBytes(row.requestBytes) }} @@ -583,6 +587,31 @@ function bucketTitle(bucket: DashboardTrafficBucket | DashboardTrafficBucketView gap: 1rem; } +.panel-actions { + display: inline-flex; + flex-wrap: wrap; + justify-content: end; + gap: 0.45rem; +} + +.diagnostics-link { + display: inline-flex; + min-height: 1.55rem; + align-items: center; + border: 1px solid #333; + border-radius: 6px; + background: #fff; + color: #000; + font-size: 0.72rem; + font-weight: 700; + padding: 0 0.55rem; + white-space: nowrap; +} + +.diagnostics-link:hover { + background: #d4d4d8; +} + .panel-heading h4, .empty-panel h4 { color: #fff;