diff --git a/db/ConfigBuilder.cpp b/db/ConfigBuilder.cpp index 4a9f2b7..47d6f38 100644 --- a/db/ConfigBuilder.cpp +++ b/db/ConfigBuilder.cpp @@ -51,15 +51,18 @@ namespace NekoGui { } } + + // Common - std::shared_ptr BuildConfig(const std::shared_ptr &ent, bool forTest, bool forExport) { + std::shared_ptr BuildConfig(const std::shared_ptr &ent, bool forTest, bool forExport, int chainID) { auto result = std::make_shared(); auto status = std::make_shared(); status->ent = ent; status->result = result; status->forTest = forTest; status->forExport = forExport; + status->chainID = chainID; auto customBean = dynamic_cast(ent->bean.get()); if (customBean != nullptr && customBean->core == "internal-full") { @@ -74,6 +77,80 @@ namespace NekoGui { return result; } + std::shared_ptr BuildTestConfig(QList> profiles) { + auto results = std::make_shared(); + + auto idx = 1; + QJsonArray outboundArray = { + QJsonObject{ + {"type", "direct"}, + {"tag", "direct"} + }, + QJsonObject{ + {"type", "block"}, + {"tag", "block"} + }, + QJsonObject{ + {"type", "dns"}, + {"tag", "dns-out"} + } + }; + QJsonArray directDomainArray; + for (const auto &item: profiles) { + auto res = BuildConfig(item, true, false, idx++); + if (!res->error.isEmpty()) { + results->error = res->error; + return results; + } + if (item->CustomBean() != nullptr && item->CustomBean()->core == "internal-full") { + res->coreConfig["inbounds"] = QJsonArray(); + results->fullConfigs[item->id] = QJsonObject2QString(res->coreConfig, true); + continue; + } + + // not full config, process it + if (results->coreConfig.isEmpty()) { + results->coreConfig = res->coreConfig; + } + // add the direct dns domains + for (const auto &rule: res->coreConfig["dns"].toObject()["rules"].toArray()) { + if (rule.toObject().contains("domain")) { + for (const auto &domain: rule.toObject()["domain"].toArray()) { + directDomainArray.append(domain); + } + } + } + // now we add the outbounds of the current config to the final one + auto outbounds = res->coreConfig["outbounds"].toArray(); + if (outbounds.isEmpty()) { + results->error = QString("outbounds is empty for %1").arg(item->bean->name); + return results; + } + auto tag = outbounds[0].toObject()["tag"].toString(); + results->outboundTags << tag; + results->tag2entID.insert(tag, item->id); + for (const auto &outboundRef: outbounds) { + auto outbound = outboundRef.toObject(); + if (outbound["tag"] == "direct" || outbound["tag"] == "block" || outbound["tag"] == "dns-out") continue; + outboundArray.append(outbound); + } + } + + results->coreConfig["outbounds"] = outboundArray; + auto dnsObj = results->coreConfig["dns"].toObject(); + auto dnsRulesObj = QJsonArray(); + if (!directDomainArray.empty()) { + dnsRulesObj += QJsonObject{ + {"domain", directDomainArray}, + {"server", "dns-direct"} + }; + } + dnsObj["rules"] = dnsRulesObj; + results->coreConfig["dns"] = dnsObj; + + return results; + } + QString BuildChain(int chainId, const std::shared_ptr &status) { auto group = profileManager->GetGroup(status->ent->gid); if (group == nullptr) { @@ -128,7 +205,7 @@ namespace NekoGui { } // BuildChain - QString chainTagOut = BuildChainInternal(0, ents, status); + QString chainTagOut = BuildChainInternal(chainId, ents, status); // Chain ent traffic stat if (ents.length() > 1) { @@ -154,21 +231,16 @@ namespace NekoGui { // profile2 (in) (global) tag g-(id) // profile1 tag (chainTag)-(id) // profile0 (out) tag (chainTag)-(id) / single: chainTag=g-(id) - auto tagOut = chainTag + "-" + Int2String(ent->id); - - // needGlobal: can only contain one? - bool needGlobal = false; + auto tagOut = chainTag + "-" + Int2String(ent->id) + "-" + Int2String(index); // first profile set as global auto isFirstProfile = index == ents.length() - 1; if (isFirstProfile) { - needGlobal = true; - tagOut = "g-" + Int2String(ent->id); + tagOut = "g-" + Int2String(ent->id) + "-" + Int2String(index); } // last profile set as "proxy" if (chainId == 0 && index == 0) { - needGlobal = false; tagOut = "proxy"; } @@ -177,13 +249,6 @@ namespace NekoGui { status->result->ignoreConnTag << tagOut; } - if (needGlobal) { - if (status->globalProfiles.contains(ent->id)) { - continue; - } - status->globalProfiles += ent->id; - } - if (index > 0) { // chain rules: past if (pastExternalStat == 0) { @@ -433,7 +498,7 @@ namespace NekoGui { } // Outbounds - auto tagProxy = BuildChain(0, status); + auto tagProxy = BuildChain(status->chainID, status); if (!status->result->error.isEmpty()) return; // direct & block & dns-out @@ -465,7 +530,7 @@ namespace NekoGui { if (dataStore->spmode_vpn) { routeObj["auto_detect_interface"] = true; } - routeObj["final"] = dataStore->routing->def_outbound; + if (!status->forTest) routeObj["final"] = dataStore->routing->def_outbound; auto routeChain = NekoGui::profileManager->GetRouteChain(NekoGui::dataStore->routing->current_route_id); if (routeChain == nullptr) { diff --git a/db/ConfigBuilder.hpp b/db/ConfigBuilder.hpp index ebcb518..2f04052 100644 --- a/db/ConfigBuilder.hpp +++ b/db/ConfigBuilder.hpp @@ -16,16 +16,23 @@ namespace NekoGui { std::list> extRs; }; + class BuildTestConfigResult { + public: + QString error; + QMap fullConfigs; + QMap tag2entID; + QJsonObject coreConfig; + QStringList outboundTags; + }; + class BuildConfigStatus { public: std::shared_ptr result; std::shared_ptr ent; + int chainID = 0; bool forTest; bool forExport; - // priv - QList globalProfiles; - // xxList is V2Ray format string list QStringList domainListDNSDirect; @@ -37,7 +44,9 @@ namespace NekoGui { QJsonArray outbounds; }; - std::shared_ptr BuildConfig(const std::shared_ptr &ent, bool forTest, bool forExport); + std::shared_ptr BuildTestConfig(QList> profiles); + + std::shared_ptr BuildConfig(const std::shared_ptr &ent, bool forTest, bool forExport, int chainID = 0); void BuildConfigSingBox(const std::shared_ptr &status); diff --git a/go/cmd/nekobox_core/grpc_box.go b/go/cmd/nekobox_core/grpc_box.go index fe0ca5d..3309af0 100644 --- a/go/cmd/nekobox_core/grpc_box.go +++ b/go/cmd/nekobox_core/grpc_box.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/Mahdi-zarei/sing-box-extra/boxbox" "github.com/sagernet/sing-box/common/settings" "github.com/sagernet/sing/common/metadata" "strings" @@ -13,12 +14,9 @@ import ( "grpc_server/gen" "github.com/Mahdi-zarei/sing-box-extra/boxapi" - "github.com/Mahdi-zarei/sing-box-extra/boxbox" "github.com/Mahdi-zarei/sing-box-extra/boxmain" "github.com/matsuridayo/libneko/neko_common" "github.com/matsuridayo/libneko/neko_log" - "github.com/matsuridayo/libneko/speedtest" - "log" "github.com/sagernet/sing-box/option" @@ -86,53 +84,66 @@ func (s *server) Stop(ctx context.Context, in *gen.EmptyReq) (out *gen.ErrorResp return } -func (s *server) Test(ctx context.Context, in *gen.TestReq) (out *gen.TestResp, _ error) { +func (s *server) Test(ctx context.Context, in *gen.TestReq) (*gen.TestResp, error) { + var testInstance *boxbox.Box + var cancel context.CancelFunc var err error - out = &gen.TestResp{Ms: 0} - - defer func() { + var twice = true + if in.TestCurrent { + if instance == nil { + return &gen.TestResp{Results: []*gen.URLTestResp{{ + OutboundTag: "proxy", + LatencyMs: 0, + Error: "Instance is not running", + }}}, nil + } + testInstance = instance + twice = false + } else { + testInstance, cancel, err = boxmain.Create([]byte(in.Config), true) if err != nil { - out.Error = err.Error() + return nil, err } - }() - - if in.Mode == gen.TestMode_UrlTest { - var i *boxbox.Box - var cancel context.CancelFunc - if in.Config != nil { - // Test instance - i, cancel, err = boxmain.Create([]byte(in.Config.CoreConfig), true) - if i != nil { - defer i.Close() - defer cancel() - } - if err != nil { - return - } - } else { - // Test running instance - i = instance - if i == nil { - return - } - } - // Latency - out.Ms, err = speedtest.UrlTest(boxapi.CreateProxyHttpClient(i), in.Url, in.Timeout) - } else if in.Mode == gen.TestMode_TcpPing { - out.Ms, err = speedtest.TcpPing(in.Address, in.Timeout) - } else if in.Mode == gen.TestMode_FullTest { - i, cancel, err := boxmain.Create([]byte(in.Config.CoreConfig), true) - if i != nil { - defer i.Close() - defer cancel() - } - if err != nil { - return - } - return grpc_server.DoFullTest(ctx, in, i) + defer cancel() + defer testInstance.Close() } - return + outboundTags := in.OutboundTags + if in.UseDefaultOutbound || in.TestCurrent { + outbound, err := testInstance.Router().DefaultOutbound("tcp") + if err != nil { + return nil, err + } + outboundTags = []string{outbound.Tag()} + } + + var maxConcurrency = in.MaxConcurrency + if maxConcurrency >= 500 || maxConcurrency == 0 { + maxConcurrency = MaxConcurrentTests + } + results := BatchURLTest(testCtx, testInstance, outboundTags, in.Url, int(maxConcurrency), twice) + + res := make([]*gen.URLTestResp, 0) + for idx, data := range results { + errStr := "" + if data.Error != nil { + errStr = data.Error.Error() + } + res = append(res, &gen.URLTestResp{ + OutboundTag: outboundTags[idx], + LatencyMs: int32(data.Duration.Milliseconds()), + Error: errStr, + }) + } + + return &gen.TestResp{Results: res}, nil +} + +func (s *server) StopTest(ctx context.Context, in *gen.EmptyReq) (*gen.EmptyResp, error) { + cancelTests() + testCtx, cancelTests = context.WithCancel(context.Background()) + + return &gen.EmptyResp{}, nil } func (s *server) QueryStats(ctx context.Context, in *gen.QueryStatsReq) (out *gen.QueryStatsResp, _ error) { diff --git a/go/cmd/nekobox_core/main.go b/go/cmd/nekobox_core/main.go index a7f46eb..5ffcb78 100644 --- a/go/cmd/nekobox_core/main.go +++ b/go/cmd/nekobox_core/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" _ "unsafe" @@ -14,6 +15,7 @@ func main() { fmt.Println("sing-box:", boxbox.Version) fmt.Println() + testCtx, cancelTests = context.WithCancel(context.Background()) grpc_server.RunCore(setupCore, &server{}) return } diff --git a/go/cmd/nekobox_core/test_utils.go b/go/cmd/nekobox_core/test_utils.go new file mode 100644 index 0000000..52ab3fd --- /dev/null +++ b/go/cmd/nekobox_core/test_utils.go @@ -0,0 +1,99 @@ +package main + +import ( + "context" + "errors" + "github.com/Mahdi-zarei/sing-box-extra/boxbox" + "github.com/sagernet/sing/common/metadata" + "net" + "net/http" + "sync" + "time" +) + +var testCtx context.Context +var cancelTests context.CancelFunc + +const URLTestTimeout = 3 * time.Second +const MaxConcurrentTests = 100 + +type URLTestResult struct { + Duration time.Duration + Error error +} + +func BatchURLTest(ctx context.Context, i *boxbox.Box, outboundTags []string, url string, maxConcurrency int, twice bool) []*URLTestResult { + router := i.Router() + resMap := make(map[string]*URLTestResult) + resAccess := sync.Mutex{} + limiter := make(chan struct{}, maxConcurrency) + + wg := &sync.WaitGroup{} + wg.Add(len(outboundTags)) + for _, tag := range outboundTags { + select { + case <-ctx.Done(): + wg.Done() + resAccess.Lock() + resMap[tag] = &URLTestResult{ + Duration: 0, + Error: errors.New("test aborted"), + } + resAccess.Unlock() + default: + time.Sleep(2 * time.Millisecond) // don't spawn goroutines too quickly + limiter <- struct{}{} + go func(t string) { + defer wg.Done() + outbound, found := router.Outbound(t) + if !found { + panic("no outbound with tag " + t + " found") + } + client := &http.Client{ + Transport: &http.Transport{ + DialContext: func(_ context.Context, network string, addr string) (net.Conn, error) { + return outbound.DialContext(ctx, "tcp", metadata.ParseSocksaddr(addr)) + }, + }, + Timeout: URLTestTimeout, + } + // to properly measure muxed configs, let's do the test twice + duration, err := urlTest(ctx, client, url) + if err == nil && twice { + duration, err = urlTest(ctx, client, url) + } + resAccess.Lock() + resMap[t] = &URLTestResult{ + Duration: duration, + Error: err, + } + resAccess.Unlock() + <-limiter + }(tag) + } + } + + wg.Wait() + res := make([]*URLTestResult, 0, len(outboundTags)) + for _, tag := range outboundTags { + res = append(res, resMap[tag]) + } + + return res +} + +func urlTest(ctx context.Context, client *http.Client, url string) (time.Duration, error) { + ctx, cancel := context.WithTimeout(ctx, URLTestTimeout) + defer cancel() + begin := time.Now() + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return 0, err + } + resp, err := client.Do(req) + if err != nil { + return 0, err + } + _ = resp.Body.Close() + return time.Since(begin), nil +} diff --git a/go/grpc_server/fulltest.go b/go/grpc_server/fulltest.go deleted file mode 100644 index 9bf70ed..0000000 --- a/go/grpc_server/fulltest.go +++ /dev/null @@ -1,180 +0,0 @@ -package grpc_server - -import ( - "context" - "encoding/hex" - "fmt" - "grpc_server/gen" - "io" - "log" - "math" - "net" - "net/http" - "strings" - "time" - - "github.com/matsuridayo/libneko/neko_common" - "github.com/matsuridayo/libneko/speedtest" -) - -const ( - KiB = 1024 - MiB = 1024 * KiB -) - -func getBetweenStr(str, start, end string) string { - n := strings.Index(str, start) - if n == -1 { - n = 0 - } - str = string([]byte(str)[n:]) - m := strings.Index(str, end) - if m == -1 { - m = len(str) - } - str = string([]byte(str)[:m]) - return str[len(start):] -} - -func DoFullTest(ctx context.Context, in *gen.TestReq, instance interface{}) (out *gen.TestResp, _ error) { - out = &gen.TestResp{} - httpClient := neko_common.CreateProxyHttpClient(instance) - - // Latency - var latency string - if in.FullLatency { - t, _ := speedtest.UrlTest(httpClient, in.Url, in.Timeout) - out.Ms = t - if t > 0 { - latency = fmt.Sprint(t, "ms") - } else { - latency = "Error" - } - } - - // UDP Latency - var udpLatency string - if in.FullUdpLatency { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) - result := make(chan string) - - go func() { - var startTime = time.Now() - pc, err := neko_common.DialContext(ctx, instance, "udp", "8.8.8.8:53") - if err == nil { - defer pc.Close() - dnsPacket, _ := hex.DecodeString("0000010000010000000000000377777706676f6f676c6503636f6d0000010001") - _, err = pc.Write(dnsPacket) - if err == nil { - var buf [1400]byte - _, err = pc.Read(buf[:]) - } - } - if err == nil { - var endTime = time.Now() - result <- fmt.Sprint(endTime.Sub(startTime).Abs().Milliseconds(), "ms") - } else { - log.Println("UDP Latency test error:", err) - result <- "Error" - } - close(result) - }() - - select { - case <-ctx.Done(): - udpLatency = "Timeout" - case r := <-result: - udpLatency = r - } - cancel() - } - - // 入口 IP - var in_ip string - if in.FullInOut { - _in_ip, err := net.ResolveIPAddr("ip", in.InAddress) - if err == nil { - in_ip = _in_ip.String() - } else { - in_ip = err.Error() - } - } - - // 出口 IP - var out_ip string - if in.FullInOut { - resp, err := httpClient.Get("https://www.cloudflare.com/cdn-cgi/trace") - if err == nil { - b, _ := io.ReadAll(resp.Body) - out_ip = getBetweenStr(string(b), "ip=", "\n") - resp.Body.Close() - } else { - out_ip = "Error" - } - } - - // 下载 - var speed string - if in.FullSpeed { - if in.FullSpeedTimeout <= 0 { - in.FullSpeedTimeout = 30 - } - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(in.FullSpeedTimeout)) - result := make(chan string) - var bodyClose io.Closer - - go func() { - req, _ := http.NewRequestWithContext(ctx, "GET", in.FullSpeedUrl, nil) - resp, err := httpClient.Do(req) - if err == nil && resp != nil && resp.Body != nil { - bodyClose = resp.Body - defer resp.Body.Close() - - timeStart := time.Now() - n, _ := io.Copy(io.Discard, resp.Body) - timeEnd := time.Now() - - duration := math.Max(timeEnd.Sub(timeStart).Seconds(), 0.000001) - resultSpeed := (float64(n) / duration) / MiB - result <- fmt.Sprintf("%.2fMiB/s", resultSpeed) - } else { - result <- "Error" - } - close(result) - }() - - select { - case <-ctx.Done(): - speed = "Timeout" - case s := <-result: - speed = s - } - - cancel() - if bodyClose != nil { - bodyClose.Close() - } - } - - fr := make([]string, 0) - if latency != "" { - fr = append(fr, fmt.Sprintf("Latency: %s", latency)) - } - if udpLatency != "" { - fr = append(fr, fmt.Sprintf("UDPLatency: %s", udpLatency)) - } - if speed != "" { - fr = append(fr, fmt.Sprintf("Speed: %s", speed)) - } - if in_ip != "" { - fr = append(fr, fmt.Sprintf("In: %s", in_ip)) - } - if out_ip != "" { - fr = append(fr, fmt.Sprintf("Out: %s", out_ip)) - } - - out.FullReport = strings.Join(fr, " / ") - - return -} diff --git a/go/grpc_server/gen/libcore.pb.go b/go/grpc_server/gen/libcore.pb.go index b22cc1d..0a6c00b 100644 --- a/go/grpc_server/gen/libcore.pb.go +++ b/go/grpc_server/gen/libcore.pb.go @@ -20,55 +20,6 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -type TestMode int32 - -const ( - TestMode_TcpPing TestMode = 0 - TestMode_UrlTest TestMode = 1 - TestMode_FullTest TestMode = 2 -) - -// Enum value maps for TestMode. -var ( - TestMode_name = map[int32]string{ - 0: "TcpPing", - 1: "UrlTest", - 2: "FullTest", - } - TestMode_value = map[string]int32{ - "TcpPing": 0, - "UrlTest": 1, - "FullTest": 2, - } -) - -func (x TestMode) Enum() *TestMode { - p := new(TestMode) - *p = x - return p -} - -func (x TestMode) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (TestMode) Descriptor() protoreflect.EnumDescriptor { - return file_libcore_proto_enumTypes[0].Descriptor() -} - -func (TestMode) Type() protoreflect.EnumType { - return &file_libcore_proto_enumTypes[0] -} - -func (x TestMode) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use TestMode.Descriptor instead. -func (TestMode) EnumDescriptor() ([]byte, []int) { - return file_libcore_proto_rawDescGZIP(), []int{0} -} - type UpdateAction int32 const ( @@ -99,11 +50,11 @@ func (x UpdateAction) String() string { } func (UpdateAction) Descriptor() protoreflect.EnumDescriptor { - return file_libcore_proto_enumTypes[1].Descriptor() + return file_libcore_proto_enumTypes[0].Descriptor() } func (UpdateAction) Type() protoreflect.EnumType { - return &file_libcore_proto_enumTypes[1] + return &file_libcore_proto_enumTypes[0] } func (x UpdateAction) Number() protoreflect.EnumNumber { @@ -112,7 +63,7 @@ func (x UpdateAction) Number() protoreflect.EnumNumber { // Deprecated: Use UpdateAction.Descriptor instead. func (UpdateAction) EnumDescriptor() ([]byte, []int) { - return file_libcore_proto_rawDescGZIP(), []int{1} + return file_libcore_proto_rawDescGZIP(), []int{0} } type EmptyReq struct { @@ -309,35 +260,86 @@ func (x *LoadConfigReq) GetDisableStats() bool { return false } +type URLTestResp struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + OutboundTag string `protobuf:"bytes,1,opt,name=outbound_tag,json=outboundTag,proto3" json:"outbound_tag,omitempty"` + LatencyMs int32 `protobuf:"varint,2,opt,name=latency_ms,json=latencyMs,proto3" json:"latency_ms,omitempty"` + Error string `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"` +} + +func (x *URLTestResp) Reset() { + *x = URLTestResp{} + if protoimpl.UnsafeEnabled { + mi := &file_libcore_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *URLTestResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*URLTestResp) ProtoMessage() {} + +func (x *URLTestResp) ProtoReflect() protoreflect.Message { + mi := &file_libcore_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use URLTestResp.ProtoReflect.Descriptor instead. +func (*URLTestResp) Descriptor() ([]byte, []int) { + return file_libcore_proto_rawDescGZIP(), []int{4} +} + +func (x *URLTestResp) GetOutboundTag() string { + if x != nil { + return x.OutboundTag + } + return "" +} + +func (x *URLTestResp) GetLatencyMs() int32 { + if x != nil { + return x.LatencyMs + } + return 0 +} + +func (x *URLTestResp) GetError() string { + if x != nil { + return x.Error + } + return "" +} + type TestReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Mode TestMode `protobuf:"varint,1,opt,name=mode,proto3,enum=libcore.TestMode" json:"mode,omitempty"` - Timeout int32 `protobuf:"varint,6,opt,name=timeout,proto3" json:"timeout,omitempty"` - // TcpPing - Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` - // UrlTest - Config *LoadConfigReq `protobuf:"bytes,3,opt,name=config,proto3" json:"config,omitempty"` - Inbound string `protobuf:"bytes,4,opt,name=inbound,proto3" json:"inbound,omitempty"` - Url string `protobuf:"bytes,5,opt,name=url,proto3" json:"url,omitempty"` - // FullTest - InAddress string `protobuf:"bytes,7,opt,name=in_address,json=inAddress,proto3" json:"in_address,omitempty"` - FullLatency bool `protobuf:"varint,8,opt,name=full_latency,json=fullLatency,proto3" json:"full_latency,omitempty"` - FullSpeed bool `protobuf:"varint,9,opt,name=full_speed,json=fullSpeed,proto3" json:"full_speed,omitempty"` - FullSpeedUrl string `protobuf:"bytes,13,opt,name=full_speed_url,json=fullSpeedUrl,proto3" json:"full_speed_url,omitempty"` - FullSpeedTimeout int32 `protobuf:"varint,14,opt,name=full_speed_timeout,json=fullSpeedTimeout,proto3" json:"full_speed_timeout,omitempty"` - FullInOut bool `protobuf:"varint,10,opt,name=full_in_out,json=fullInOut,proto3" json:"full_in_out,omitempty"` - FullUdpLatency bool `protobuf:"varint,12,opt,name=full_udp_latency,json=fullUdpLatency,proto3" json:"full_udp_latency,omitempty"` - // Deprecated: Marked as deprecated in libcore.proto. - FullNat bool `protobuf:"varint,11,opt,name=full_nat,json=fullNat,proto3" json:"full_nat,omitempty"` + Config string `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` + OutboundTags []string `protobuf:"bytes,2,rep,name=outbound_tags,json=outboundTags,proto3" json:"outbound_tags,omitempty"` + UseDefaultOutbound bool `protobuf:"varint,3,opt,name=use_default_outbound,json=useDefaultOutbound,proto3" json:"use_default_outbound,omitempty"` + Url string `protobuf:"bytes,4,opt,name=url,proto3" json:"url,omitempty"` + TestCurrent bool `protobuf:"varint,5,opt,name=test_current,json=testCurrent,proto3" json:"test_current,omitempty"` + MaxConcurrency int32 `protobuf:"varint,6,opt,name=max_concurrency,json=maxConcurrency,proto3" json:"max_concurrency,omitempty"` } func (x *TestReq) Reset() { *x = TestReq{} if protoimpl.UnsafeEnabled { - mi := &file_libcore_proto_msgTypes[4] + mi := &file_libcore_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -350,7 +352,7 @@ func (x *TestReq) String() string { func (*TestReq) ProtoMessage() {} func (x *TestReq) ProtoReflect() protoreflect.Message { - mi := &file_libcore_proto_msgTypes[4] + mi := &file_libcore_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -363,42 +365,28 @@ func (x *TestReq) ProtoReflect() protoreflect.Message { // Deprecated: Use TestReq.ProtoReflect.Descriptor instead. func (*TestReq) Descriptor() ([]byte, []int) { - return file_libcore_proto_rawDescGZIP(), []int{4} + return file_libcore_proto_rawDescGZIP(), []int{5} } -func (x *TestReq) GetMode() TestMode { +func (x *TestReq) GetConfig() string { if x != nil { - return x.Mode - } - return TestMode_TcpPing -} - -func (x *TestReq) GetTimeout() int32 { - if x != nil { - return x.Timeout - } - return 0 -} - -func (x *TestReq) GetAddress() string { - if x != nil { - return x.Address + return x.Config } return "" } -func (x *TestReq) GetConfig() *LoadConfigReq { +func (x *TestReq) GetOutboundTags() []string { if x != nil { - return x.Config + return x.OutboundTags } return nil } -func (x *TestReq) GetInbound() string { +func (x *TestReq) GetUseDefaultOutbound() bool { if x != nil { - return x.Inbound + return x.UseDefaultOutbound } - return "" + return false } func (x *TestReq) GetUrl() string { @@ -408,77 +396,32 @@ func (x *TestReq) GetUrl() string { return "" } -func (x *TestReq) GetInAddress() string { +func (x *TestReq) GetTestCurrent() bool { if x != nil { - return x.InAddress - } - return "" -} - -func (x *TestReq) GetFullLatency() bool { - if x != nil { - return x.FullLatency + return x.TestCurrent } return false } -func (x *TestReq) GetFullSpeed() bool { +func (x *TestReq) GetMaxConcurrency() int32 { if x != nil { - return x.FullSpeed - } - return false -} - -func (x *TestReq) GetFullSpeedUrl() string { - if x != nil { - return x.FullSpeedUrl - } - return "" -} - -func (x *TestReq) GetFullSpeedTimeout() int32 { - if x != nil { - return x.FullSpeedTimeout + return x.MaxConcurrency } return 0 } -func (x *TestReq) GetFullInOut() bool { - if x != nil { - return x.FullInOut - } - return false -} - -func (x *TestReq) GetFullUdpLatency() bool { - if x != nil { - return x.FullUdpLatency - } - return false -} - -// Deprecated: Marked as deprecated in libcore.proto. -func (x *TestReq) GetFullNat() bool { - if x != nil { - return x.FullNat - } - return false -} - type TestResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Error string `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"` - Ms int32 `protobuf:"varint,2,opt,name=ms,proto3" json:"ms,omitempty"` - FullReport string `protobuf:"bytes,3,opt,name=full_report,json=fullReport,proto3" json:"full_report,omitempty"` + Results []*URLTestResp `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"` } func (x *TestResp) Reset() { *x = TestResp{} if protoimpl.UnsafeEnabled { - mi := &file_libcore_proto_msgTypes[5] + mi := &file_libcore_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -491,7 +434,7 @@ func (x *TestResp) String() string { func (*TestResp) ProtoMessage() {} func (x *TestResp) ProtoReflect() protoreflect.Message { - mi := &file_libcore_proto_msgTypes[5] + mi := &file_libcore_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -504,28 +447,14 @@ func (x *TestResp) ProtoReflect() protoreflect.Message { // Deprecated: Use TestResp.ProtoReflect.Descriptor instead. func (*TestResp) Descriptor() ([]byte, []int) { - return file_libcore_proto_rawDescGZIP(), []int{5} + return file_libcore_proto_rawDescGZIP(), []int{6} } -func (x *TestResp) GetError() string { +func (x *TestResp) GetResults() []*URLTestResp { if x != nil { - return x.Error + return x.Results } - return "" -} - -func (x *TestResp) GetMs() int32 { - if x != nil { - return x.Ms - } - return 0 -} - -func (x *TestResp) GetFullReport() string { - if x != nil { - return x.FullReport - } - return "" + return nil } type QueryStatsReq struct { @@ -540,7 +469,7 @@ type QueryStatsReq struct { func (x *QueryStatsReq) Reset() { *x = QueryStatsReq{} if protoimpl.UnsafeEnabled { - mi := &file_libcore_proto_msgTypes[6] + mi := &file_libcore_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -553,7 +482,7 @@ func (x *QueryStatsReq) String() string { func (*QueryStatsReq) ProtoMessage() {} func (x *QueryStatsReq) ProtoReflect() protoreflect.Message { - mi := &file_libcore_proto_msgTypes[6] + mi := &file_libcore_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -566,7 +495,7 @@ func (x *QueryStatsReq) ProtoReflect() protoreflect.Message { // Deprecated: Use QueryStatsReq.ProtoReflect.Descriptor instead. func (*QueryStatsReq) Descriptor() ([]byte, []int) { - return file_libcore_proto_rawDescGZIP(), []int{6} + return file_libcore_proto_rawDescGZIP(), []int{7} } func (x *QueryStatsReq) GetTag() string { @@ -594,7 +523,7 @@ type QueryStatsResp struct { func (x *QueryStatsResp) Reset() { *x = QueryStatsResp{} if protoimpl.UnsafeEnabled { - mi := &file_libcore_proto_msgTypes[7] + mi := &file_libcore_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -607,7 +536,7 @@ func (x *QueryStatsResp) String() string { func (*QueryStatsResp) ProtoMessage() {} func (x *QueryStatsResp) ProtoReflect() protoreflect.Message { - mi := &file_libcore_proto_msgTypes[7] + mi := &file_libcore_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -620,7 +549,7 @@ func (x *QueryStatsResp) ProtoReflect() protoreflect.Message { // Deprecated: Use QueryStatsResp.ProtoReflect.Descriptor instead. func (*QueryStatsResp) Descriptor() ([]byte, []int) { - return file_libcore_proto_rawDescGZIP(), []int{7} + return file_libcore_proto_rawDescGZIP(), []int{8} } func (x *QueryStatsResp) GetTraffic() int64 { @@ -642,7 +571,7 @@ type UpdateReq struct { func (x *UpdateReq) Reset() { *x = UpdateReq{} if protoimpl.UnsafeEnabled { - mi := &file_libcore_proto_msgTypes[8] + mi := &file_libcore_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -655,7 +584,7 @@ func (x *UpdateReq) String() string { func (*UpdateReq) ProtoMessage() {} func (x *UpdateReq) ProtoReflect() protoreflect.Message { - mi := &file_libcore_proto_msgTypes[8] + mi := &file_libcore_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -668,7 +597,7 @@ func (x *UpdateReq) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateReq.ProtoReflect.Descriptor instead. func (*UpdateReq) Descriptor() ([]byte, []int) { - return file_libcore_proto_rawDescGZIP(), []int{8} + return file_libcore_proto_rawDescGZIP(), []int{9} } func (x *UpdateReq) GetAction() UpdateAction { @@ -701,7 +630,7 @@ type UpdateResp struct { func (x *UpdateResp) Reset() { *x = UpdateResp{} if protoimpl.UnsafeEnabled { - mi := &file_libcore_proto_msgTypes[9] + mi := &file_libcore_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -714,7 +643,7 @@ func (x *UpdateResp) String() string { func (*UpdateResp) ProtoMessage() {} func (x *UpdateResp) ProtoReflect() protoreflect.Message { - mi := &file_libcore_proto_msgTypes[9] + mi := &file_libcore_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -727,7 +656,7 @@ func (x *UpdateResp) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateResp.ProtoReflect.Descriptor instead. func (*UpdateResp) Descriptor() ([]byte, []int) { - return file_libcore_proto_rawDescGZIP(), []int{9} + return file_libcore_proto_rawDescGZIP(), []int{10} } func (x *UpdateResp) GetError() string { @@ -783,7 +712,7 @@ type ListConnectionsResp struct { func (x *ListConnectionsResp) Reset() { *x = ListConnectionsResp{} if protoimpl.UnsafeEnabled { - mi := &file_libcore_proto_msgTypes[10] + mi := &file_libcore_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -796,7 +725,7 @@ func (x *ListConnectionsResp) String() string { func (*ListConnectionsResp) ProtoMessage() {} func (x *ListConnectionsResp) ProtoReflect() protoreflect.Message { - mi := &file_libcore_proto_msgTypes[10] + mi := &file_libcore_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -809,7 +738,7 @@ func (x *ListConnectionsResp) ProtoReflect() protoreflect.Message { // Deprecated: Use ListConnectionsResp.ProtoReflect.Descriptor instead. func (*ListConnectionsResp) Descriptor() ([]byte, []int) { - return file_libcore_proto_rawDescGZIP(), []int{10} + return file_libcore_proto_rawDescGZIP(), []int{11} } func (x *ListConnectionsResp) GetNekorayConnectionsJson() string { @@ -830,7 +759,7 @@ type GetGeoIPListResponse struct { func (x *GetGeoIPListResponse) Reset() { *x = GetGeoIPListResponse{} if protoimpl.UnsafeEnabled { - mi := &file_libcore_proto_msgTypes[11] + mi := &file_libcore_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -843,7 +772,7 @@ func (x *GetGeoIPListResponse) String() string { func (*GetGeoIPListResponse) ProtoMessage() {} func (x *GetGeoIPListResponse) ProtoReflect() protoreflect.Message { - mi := &file_libcore_proto_msgTypes[11] + mi := &file_libcore_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -856,7 +785,7 @@ func (x *GetGeoIPListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetGeoIPListResponse.ProtoReflect.Descriptor instead. func (*GetGeoIPListResponse) Descriptor() ([]byte, []int) { - return file_libcore_proto_rawDescGZIP(), []int{11} + return file_libcore_proto_rawDescGZIP(), []int{12} } func (x *GetGeoIPListResponse) GetItems() []string { @@ -877,7 +806,7 @@ type GetGeoSiteListResponse struct { func (x *GetGeoSiteListResponse) Reset() { *x = GetGeoSiteListResponse{} if protoimpl.UnsafeEnabled { - mi := &file_libcore_proto_msgTypes[12] + mi := &file_libcore_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -890,7 +819,7 @@ func (x *GetGeoSiteListResponse) String() string { func (*GetGeoSiteListResponse) ProtoMessage() {} func (x *GetGeoSiteListResponse) ProtoReflect() protoreflect.Message { - mi := &file_libcore_proto_msgTypes[12] + mi := &file_libcore_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -903,7 +832,7 @@ func (x *GetGeoSiteListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetGeoSiteListResponse.ProtoReflect.Descriptor instead. func (*GetGeoSiteListResponse) Descriptor() ([]byte, []int) { - return file_libcore_proto_rawDescGZIP(), []int{12} + return file_libcore_proto_rawDescGZIP(), []int{13} } func (x *GetGeoSiteListResponse) GetItems() []string { @@ -924,7 +853,7 @@ type CompileGeoIPToSrsRequest struct { func (x *CompileGeoIPToSrsRequest) Reset() { *x = CompileGeoIPToSrsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_libcore_proto_msgTypes[13] + mi := &file_libcore_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -937,7 +866,7 @@ func (x *CompileGeoIPToSrsRequest) String() string { func (*CompileGeoIPToSrsRequest) ProtoMessage() {} func (x *CompileGeoIPToSrsRequest) ProtoReflect() protoreflect.Message { - mi := &file_libcore_proto_msgTypes[13] + mi := &file_libcore_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -950,7 +879,7 @@ func (x *CompileGeoIPToSrsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CompileGeoIPToSrsRequest.ProtoReflect.Descriptor instead. func (*CompileGeoIPToSrsRequest) Descriptor() ([]byte, []int) { - return file_libcore_proto_rawDescGZIP(), []int{13} + return file_libcore_proto_rawDescGZIP(), []int{14} } func (x *CompileGeoIPToSrsRequest) GetItem() string { @@ -971,7 +900,7 @@ type CompileGeoSiteToSrsRequest struct { func (x *CompileGeoSiteToSrsRequest) Reset() { *x = CompileGeoSiteToSrsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_libcore_proto_msgTypes[14] + mi := &file_libcore_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -984,7 +913,7 @@ func (x *CompileGeoSiteToSrsRequest) String() string { func (*CompileGeoSiteToSrsRequest) ProtoMessage() {} func (x *CompileGeoSiteToSrsRequest) ProtoReflect() protoreflect.Message { - mi := &file_libcore_proto_msgTypes[14] + mi := &file_libcore_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -997,7 +926,7 @@ func (x *CompileGeoSiteToSrsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CompileGeoSiteToSrsRequest.ProtoReflect.Descriptor instead. func (*CompileGeoSiteToSrsRequest) Descriptor() ([]byte, []int) { - return file_libcore_proto_rawDescGZIP(), []int{14} + return file_libcore_proto_rawDescGZIP(), []int{15} } func (x *CompileGeoSiteToSrsRequest) GetItem() string { @@ -1019,7 +948,7 @@ type SetSystemProxyRequest struct { func (x *SetSystemProxyRequest) Reset() { *x = SetSystemProxyRequest{} if protoimpl.UnsafeEnabled { - mi := &file_libcore_proto_msgTypes[15] + mi := &file_libcore_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1032,7 +961,7 @@ func (x *SetSystemProxyRequest) String() string { func (*SetSystemProxyRequest) ProtoMessage() {} func (x *SetSystemProxyRequest) ProtoReflect() protoreflect.Message { - mi := &file_libcore_proto_msgTypes[15] + mi := &file_libcore_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1045,7 +974,7 @@ func (x *SetSystemProxyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetSystemProxyRequest.ProtoReflect.Descriptor instead. func (*SetSystemProxyRequest) Descriptor() ([]byte, []int) { - return file_libcore_proto_rawDescGZIP(), []int{15} + return file_libcore_proto_rawDescGZIP(), []int{16} } func (x *SetSystemProxyRequest) GetEnable() bool { @@ -1082,145 +1011,134 @@ var file_libcore_proto_rawDesc = []byte{ 0x73, 0x74, 0x61, 0x74, 0x73, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x74, - 0x61, 0x74, 0x73, 0x22, 0xde, 0x03, 0x0a, 0x07, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x12, - 0x25, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, - 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x6f, 0x64, 0x65, - 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, - 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, - 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2e, 0x0a, 0x06, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x69, 0x62, - 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, - 0x65, 0x71, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x69, 0x6e, - 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x69, 0x6e, 0x62, - 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x6e, 0x5f, 0x61, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x69, 0x6e, 0x41, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x6c, 0x61, - 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x66, 0x75, 0x6c, - 0x6c, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x66, 0x75, 0x6c, 0x6c, - 0x5f, 0x73, 0x70, 0x65, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x66, 0x75, - 0x6c, 0x6c, 0x53, 0x70, 0x65, 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x66, 0x75, 0x6c, 0x6c, 0x5f, - 0x73, 0x70, 0x65, 0x65, 0x64, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x70, 0x65, 0x65, 0x64, 0x55, 0x72, 0x6c, 0x12, 0x2c, 0x0a, - 0x12, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x73, 0x70, 0x65, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, - 0x6f, 0x75, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x66, 0x75, 0x6c, 0x6c, 0x53, - 0x70, 0x65, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x66, - 0x75, 0x6c, 0x6c, 0x5f, 0x69, 0x6e, 0x5f, 0x6f, 0x75, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x09, 0x66, 0x75, 0x6c, 0x6c, 0x49, 0x6e, 0x4f, 0x75, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x66, - 0x75, 0x6c, 0x6c, 0x5f, 0x75, 0x64, 0x70, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, - 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x66, 0x75, 0x6c, 0x6c, 0x55, 0x64, 0x70, 0x4c, 0x61, - 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x1d, 0x0a, 0x08, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x6e, 0x61, - 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x07, 0x66, 0x75, 0x6c, - 0x6c, 0x4e, 0x61, 0x74, 0x22, 0x51, 0x0a, 0x08, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x02, 0x6d, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x72, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x66, 0x75, 0x6c, - 0x6c, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x39, 0x0a, 0x0d, 0x51, 0x75, 0x65, 0x72, 0x79, - 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, - 0x72, 0x65, 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x69, 0x72, 0x65, - 0x63, 0x74, 0x22, 0x2a, 0x0a, 0x0e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x22, 0x66, - 0x0a, 0x09, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x12, 0x2d, 0x0a, 0x06, 0x61, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6c, 0x69, - 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x68, - 0x65, 0x63, 0x6b, 0x5f, 0x70, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x50, 0x72, 0x65, 0x52, - 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x22, 0xd0, 0x01, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x61, - 0x73, 0x73, 0x65, 0x74, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, - 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x55, 0x72, 0x6c, 0x12, - 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x55, 0x72, 0x6c, - 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x6e, 0x6f, 0x74, 0x65, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4e, - 0x6f, 0x74, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x69, 0x73, 0x5f, 0x70, 0x72, 0x65, 0x5f, 0x72, 0x65, - 0x6c, 0x65, 0x61, 0x73, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x73, 0x50, - 0x72, 0x65, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x22, 0x4f, 0x0a, 0x13, 0x4c, 0x69, 0x73, - 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x12, 0x38, 0x0a, 0x18, 0x6e, 0x65, 0x6b, 0x6f, 0x72, 0x61, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x16, 0x6e, 0x65, 0x6b, 0x6f, 0x72, 0x61, 0x79, 0x43, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4a, 0x73, 0x6f, 0x6e, 0x22, 0x2c, 0x0a, 0x14, 0x47, 0x65, + 0x61, 0x74, 0x73, 0x22, 0x65, 0x0a, 0x0b, 0x55, 0x52, 0x4c, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, + 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, + 0x6e, 0x64, 0x54, 0x61, 0x67, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, + 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x6c, 0x61, 0x74, 0x65, 0x6e, + 0x63, 0x79, 0x4d, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0xd6, 0x01, 0x0a, 0x07, 0x54, + 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x23, + 0x0a, 0x0d, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, + 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, + 0x61, 0x67, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x75, 0x73, 0x65, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, + 0x6c, 0x74, 0x5f, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x12, 0x75, 0x73, 0x65, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x4f, 0x75, 0x74, + 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x65, 0x73, 0x74, 0x5f, + 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x74, + 0x65, 0x73, 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x6d, 0x61, + 0x78, 0x5f, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x0e, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, + 0x6e, 0x63, 0x79, 0x22, 0x3a, 0x0a, 0x08, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, + 0x2e, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x14, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x55, 0x52, 0x4c, 0x54, 0x65, + 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, + 0x39, 0x0a, 0x0d, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, + 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, + 0x61, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x22, 0x2a, 0x0a, 0x0e, 0x51, 0x75, + 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x12, 0x18, 0x0a, 0x07, + 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x74, + 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x22, 0x66, 0x0a, 0x09, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x52, 0x65, 0x71, 0x12, 0x2d, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x70, 0x72, 0x65, 0x5f, + 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x63, + 0x68, 0x65, 0x63, 0x6b, 0x50, 0x72, 0x65, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x22, 0xd0, + 0x01, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x14, 0x0a, + 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x73, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x73, + 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, + 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x6f, 0x77, 0x6e, + 0x6c, 0x6f, 0x61, 0x64, 0x55, 0x72, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x6c, 0x65, 0x61, + 0x73, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, + 0x6c, 0x65, 0x61, 0x73, 0x65, 0x55, 0x72, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x6c, 0x65, + 0x61, 0x73, 0x65, 0x5f, 0x6e, 0x6f, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4e, 0x6f, 0x74, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x69, + 0x73, 0x5f, 0x70, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x73, 0x50, 0x72, 0x65, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, + 0x65, 0x22, 0x4f, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x12, 0x38, 0x0a, 0x18, 0x6e, 0x65, 0x6b, 0x6f, + 0x72, 0x61, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, + 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x6e, 0x65, 0x6b, 0x6f, + 0x72, 0x61, 0x79, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4a, 0x73, + 0x6f, 0x6e, 0x22, 0x2c, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x74, + 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, + 0x22, 0x2e, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x74, + 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, + 0x22, 0x2e, 0x0a, 0x18, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, 0x49, 0x50, + 0x54, 0x6f, 0x53, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, + 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, + 0x22, 0x30, 0x0a, 0x1a, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, 0x53, 0x69, + 0x74, 0x65, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, + 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x74, + 0x65, 0x6d, 0x22, 0x49, 0x0a, 0x15, 0x53, 0x65, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x50, + 0x72, 0x6f, 0x78, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x65, + 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x65, 0x6e, 0x61, + 0x62, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x2a, 0x27, 0x0a, + 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, 0x0a, + 0x05, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x6f, 0x77, 0x6e, + 0x6c, 0x6f, 0x61, 0x64, 0x10, 0x01, 0x32, 0xb1, 0x06, 0x0a, 0x0e, 0x4c, 0x69, 0x62, 0x63, 0x6f, + 0x72, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x45, 0x78, 0x69, + 0x74, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x06, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x12, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x13, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, + 0x72, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, + 0x35, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x16, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, + 0x72, 0x65, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, + 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, + 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x2f, 0x0a, 0x04, 0x53, 0x74, 0x6f, 0x70, 0x12, 0x11, + 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, + 0x71, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, + 0x72, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x2d, 0x0a, 0x04, 0x54, 0x65, 0x73, 0x74, 0x12, + 0x10, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, + 0x71, 0x1a, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x65, 0x73, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x08, 0x53, 0x74, 0x6f, 0x70, 0x54, 0x65, + 0x73, 0x74, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x12, 0x3f, 0x0a, 0x0a, 0x51, 0x75, 0x65, + 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x16, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, + 0x65, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, + 0x17, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, + 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x0f, 0x4c, 0x69, + 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x11, 0x2e, + 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, + 0x1a, 0x1c, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, + 0x12, 0x40, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x4c, 0x69, 0x73, 0x74, + 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x52, 0x65, 0x71, 0x1a, 0x1d, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x2e, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x47, - 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x2e, 0x0a, 0x18, 0x43, 0x6f, 0x6d, 0x70, - 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x30, 0x0a, 0x1a, 0x43, 0x6f, 0x6d, 0x70, - 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x49, 0x0a, 0x15, 0x53, 0x65, - 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x06, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x2a, 0x32, 0x0a, 0x08, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x6f, 0x64, - 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x54, 0x63, 0x70, 0x50, 0x69, 0x6e, 0x67, 0x10, 0x00, 0x12, 0x0b, - 0x0a, 0x07, 0x55, 0x72, 0x6c, 0x54, 0x65, 0x73, 0x74, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x46, - 0x75, 0x6c, 0x6c, 0x54, 0x65, 0x73, 0x74, 0x10, 0x02, 0x2a, 0x27, 0x0a, 0x0c, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x68, 0x65, - 0x63, 0x6b, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, - 0x10, 0x01, 0x32, 0xfe, 0x05, 0x0a, 0x0e, 0x4c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x45, 0x78, 0x69, 0x74, 0x12, 0x11, 0x2e, - 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, + 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, + 0x4c, 0x69, 0x73, 0x74, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x1f, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, + 0x65, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x11, 0x43, 0x6f, 0x6d, 0x70, + 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x12, 0x21, 0x2e, + 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47, + 0x65, 0x6f, 0x49, 0x50, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x12, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x52, 0x65, 0x71, 0x1a, 0x13, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x05, 0x53, - 0x74, 0x61, 0x72, 0x74, 0x12, 0x16, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, - 0x6f, 0x61, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x6c, - 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, - 0x22, 0x00, 0x12, 0x2f, 0x0a, 0x04, 0x53, 0x74, 0x6f, 0x70, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, - 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, - 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, - 0x70, 0x22, 0x00, 0x12, 0x2d, 0x0a, 0x04, 0x54, 0x65, 0x73, 0x74, 0x12, 0x10, 0x2e, 0x6c, 0x69, - 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e, - 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, - 0x12, 0x16, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, - 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, - 0x72, 0x65, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, - 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x1c, 0x2e, 0x6c, 0x69, 0x62, 0x63, - 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x0c, 0x47, 0x65, 0x74, - 0x47, 0x65, 0x6f, 0x49, 0x50, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, - 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x1d, 0x2e, 0x6c, - 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x4c, - 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0e, 0x47, - 0x65, 0x74, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x11, 0x2e, - 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, - 0x1a, 0x1f, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x47, 0x65, - 0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x4a, 0x0a, 0x11, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, 0x49, - 0x50, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x12, 0x21, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, - 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x54, 0x6f, 0x53, - 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, - 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x12, 0x4e, 0x0a, - 0x13, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x54, - 0x6f, 0x53, 0x72, 0x73, 0x12, 0x23, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x43, - 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x54, 0x6f, 0x53, - 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, - 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x12, 0x44, 0x0a, - 0x0e, 0x53, 0x65, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x12, - 0x1e, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x53, 0x65, 0x74, 0x53, 0x79, 0x73, - 0x74, 0x65, 0x6d, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, - 0x65, 0x73, 0x70, 0x42, 0x11, 0x5a, 0x0f, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x73, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x2f, 0x67, 0x65, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x52, 0x65, 0x73, 0x70, 0x12, 0x4e, 0x0a, 0x13, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47, + 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x12, 0x23, 0x2e, 0x6c, 0x69, + 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, + 0x53, 0x69, 0x74, 0x65, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x52, 0x65, 0x73, 0x70, 0x12, 0x44, 0x0a, 0x0e, 0x53, 0x65, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, + 0x6d, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x12, 0x1e, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, + 0x2e, 0x53, 0x65, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x42, 0x11, 0x5a, 0x0f, 0x67, 0x72, + 0x70, 0x63, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x67, 0x65, 0x6e, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1235,15 +1153,15 @@ func file_libcore_proto_rawDescGZIP() []byte { return file_libcore_proto_rawDescData } -var file_libcore_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_libcore_proto_msgTypes = make([]protoimpl.MessageInfo, 16) +var file_libcore_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_libcore_proto_msgTypes = make([]protoimpl.MessageInfo, 17) var file_libcore_proto_goTypes = []interface{}{ - (TestMode)(0), // 0: libcore.TestMode - (UpdateAction)(0), // 1: libcore.UpdateAction - (*EmptyReq)(nil), // 2: libcore.EmptyReq - (*EmptyResp)(nil), // 3: libcore.EmptyResp - (*ErrorResp)(nil), // 4: libcore.ErrorResp - (*LoadConfigReq)(nil), // 5: libcore.LoadConfigReq + (UpdateAction)(0), // 0: libcore.UpdateAction + (*EmptyReq)(nil), // 1: libcore.EmptyReq + (*EmptyResp)(nil), // 2: libcore.EmptyResp + (*ErrorResp)(nil), // 3: libcore.ErrorResp + (*LoadConfigReq)(nil), // 4: libcore.LoadConfigReq + (*URLTestResp)(nil), // 5: libcore.URLTestResp (*TestReq)(nil), // 6: libcore.TestReq (*TestResp)(nil), // 7: libcore.TestResp (*QueryStatsReq)(nil), // 8: libcore.QueryStatsReq @@ -1258,38 +1176,39 @@ var file_libcore_proto_goTypes = []interface{}{ (*SetSystemProxyRequest)(nil), // 17: libcore.SetSystemProxyRequest } var file_libcore_proto_depIdxs = []int32{ - 0, // 0: libcore.TestReq.mode:type_name -> libcore.TestMode - 5, // 1: libcore.TestReq.config:type_name -> libcore.LoadConfigReq - 1, // 2: libcore.UpdateReq.action:type_name -> libcore.UpdateAction - 2, // 3: libcore.LibcoreService.Exit:input_type -> libcore.EmptyReq - 10, // 4: libcore.LibcoreService.Update:input_type -> libcore.UpdateReq - 5, // 5: libcore.LibcoreService.Start:input_type -> libcore.LoadConfigReq - 2, // 6: libcore.LibcoreService.Stop:input_type -> libcore.EmptyReq - 6, // 7: libcore.LibcoreService.Test:input_type -> libcore.TestReq + 5, // 0: libcore.TestResp.results:type_name -> libcore.URLTestResp + 0, // 1: libcore.UpdateReq.action:type_name -> libcore.UpdateAction + 1, // 2: libcore.LibcoreService.Exit:input_type -> libcore.EmptyReq + 10, // 3: libcore.LibcoreService.Update:input_type -> libcore.UpdateReq + 4, // 4: libcore.LibcoreService.Start:input_type -> libcore.LoadConfigReq + 1, // 5: libcore.LibcoreService.Stop:input_type -> libcore.EmptyReq + 6, // 6: libcore.LibcoreService.Test:input_type -> libcore.TestReq + 1, // 7: libcore.LibcoreService.StopTest:input_type -> libcore.EmptyReq 8, // 8: libcore.LibcoreService.QueryStats:input_type -> libcore.QueryStatsReq - 2, // 9: libcore.LibcoreService.ListConnections:input_type -> libcore.EmptyReq - 2, // 10: libcore.LibcoreService.GetGeoIPList:input_type -> libcore.EmptyReq - 2, // 11: libcore.LibcoreService.GetGeoSiteList:input_type -> libcore.EmptyReq + 1, // 9: libcore.LibcoreService.ListConnections:input_type -> libcore.EmptyReq + 1, // 10: libcore.LibcoreService.GetGeoIPList:input_type -> libcore.EmptyReq + 1, // 11: libcore.LibcoreService.GetGeoSiteList:input_type -> libcore.EmptyReq 15, // 12: libcore.LibcoreService.CompileGeoIPToSrs:input_type -> libcore.CompileGeoIPToSrsRequest 16, // 13: libcore.LibcoreService.CompileGeoSiteToSrs:input_type -> libcore.CompileGeoSiteToSrsRequest 17, // 14: libcore.LibcoreService.SetSystemProxy:input_type -> libcore.SetSystemProxyRequest - 3, // 15: libcore.LibcoreService.Exit:output_type -> libcore.EmptyResp + 2, // 15: libcore.LibcoreService.Exit:output_type -> libcore.EmptyResp 11, // 16: libcore.LibcoreService.Update:output_type -> libcore.UpdateResp - 4, // 17: libcore.LibcoreService.Start:output_type -> libcore.ErrorResp - 4, // 18: libcore.LibcoreService.Stop:output_type -> libcore.ErrorResp + 3, // 17: libcore.LibcoreService.Start:output_type -> libcore.ErrorResp + 3, // 18: libcore.LibcoreService.Stop:output_type -> libcore.ErrorResp 7, // 19: libcore.LibcoreService.Test:output_type -> libcore.TestResp - 9, // 20: libcore.LibcoreService.QueryStats:output_type -> libcore.QueryStatsResp - 12, // 21: libcore.LibcoreService.ListConnections:output_type -> libcore.ListConnectionsResp - 13, // 22: libcore.LibcoreService.GetGeoIPList:output_type -> libcore.GetGeoIPListResponse - 14, // 23: libcore.LibcoreService.GetGeoSiteList:output_type -> libcore.GetGeoSiteListResponse - 3, // 24: libcore.LibcoreService.CompileGeoIPToSrs:output_type -> libcore.EmptyResp - 3, // 25: libcore.LibcoreService.CompileGeoSiteToSrs:output_type -> libcore.EmptyResp - 3, // 26: libcore.LibcoreService.SetSystemProxy:output_type -> libcore.EmptyResp - 15, // [15:27] is the sub-list for method output_type - 3, // [3:15] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 2, // 20: libcore.LibcoreService.StopTest:output_type -> libcore.EmptyResp + 9, // 21: libcore.LibcoreService.QueryStats:output_type -> libcore.QueryStatsResp + 12, // 22: libcore.LibcoreService.ListConnections:output_type -> libcore.ListConnectionsResp + 13, // 23: libcore.LibcoreService.GetGeoIPList:output_type -> libcore.GetGeoIPListResponse + 14, // 24: libcore.LibcoreService.GetGeoSiteList:output_type -> libcore.GetGeoSiteListResponse + 2, // 25: libcore.LibcoreService.CompileGeoIPToSrs:output_type -> libcore.EmptyResp + 2, // 26: libcore.LibcoreService.CompileGeoSiteToSrs:output_type -> libcore.EmptyResp + 2, // 27: libcore.LibcoreService.SetSystemProxy:output_type -> libcore.EmptyResp + 15, // [15:28] is the sub-list for method output_type + 2, // [2:15] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name } func init() { file_libcore_proto_init() } @@ -1347,7 +1266,7 @@ func file_libcore_proto_init() { } } file_libcore_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TestReq); i { + switch v := v.(*URLTestResp); i { case 0: return &v.state case 1: @@ -1359,7 +1278,7 @@ func file_libcore_proto_init() { } } file_libcore_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TestResp); i { + switch v := v.(*TestReq); i { case 0: return &v.state case 1: @@ -1371,7 +1290,7 @@ func file_libcore_proto_init() { } } file_libcore_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*QueryStatsReq); i { + switch v := v.(*TestResp); i { case 0: return &v.state case 1: @@ -1383,7 +1302,7 @@ func file_libcore_proto_init() { } } file_libcore_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*QueryStatsResp); i { + switch v := v.(*QueryStatsReq); i { case 0: return &v.state case 1: @@ -1395,7 +1314,7 @@ func file_libcore_proto_init() { } } file_libcore_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateReq); i { + switch v := v.(*QueryStatsResp); i { case 0: return &v.state case 1: @@ -1407,7 +1326,7 @@ func file_libcore_proto_init() { } } file_libcore_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateResp); i { + switch v := v.(*UpdateReq); i { case 0: return &v.state case 1: @@ -1419,7 +1338,7 @@ func file_libcore_proto_init() { } } file_libcore_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListConnectionsResp); i { + switch v := v.(*UpdateResp); i { case 0: return &v.state case 1: @@ -1431,7 +1350,7 @@ func file_libcore_proto_init() { } } file_libcore_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetGeoIPListResponse); i { + switch v := v.(*ListConnectionsResp); i { case 0: return &v.state case 1: @@ -1443,7 +1362,7 @@ func file_libcore_proto_init() { } } file_libcore_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetGeoSiteListResponse); i { + switch v := v.(*GetGeoIPListResponse); i { case 0: return &v.state case 1: @@ -1455,7 +1374,7 @@ func file_libcore_proto_init() { } } file_libcore_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CompileGeoIPToSrsRequest); i { + switch v := v.(*GetGeoSiteListResponse); i { case 0: return &v.state case 1: @@ -1467,7 +1386,7 @@ func file_libcore_proto_init() { } } file_libcore_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CompileGeoSiteToSrsRequest); i { + switch v := v.(*CompileGeoIPToSrsRequest); i { case 0: return &v.state case 1: @@ -1479,6 +1398,18 @@ func file_libcore_proto_init() { } } file_libcore_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CompileGeoSiteToSrsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_libcore_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SetSystemProxyRequest); i { case 0: return &v.state @@ -1496,8 +1427,8 @@ func file_libcore_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_libcore_proto_rawDesc, - NumEnums: 2, - NumMessages: 16, + NumEnums: 1, + NumMessages: 17, NumExtensions: 0, NumServices: 1, }, diff --git a/go/grpc_server/gen/libcore.proto b/go/grpc_server/gen/libcore.proto index 28610b3..129ec7a 100644 --- a/go/grpc_server/gen/libcore.proto +++ b/go/grpc_server/gen/libcore.proto @@ -10,6 +10,7 @@ service LibcoreService { rpc Start(LoadConfigReq) returns (ErrorResp) {} rpc Stop(EmptyReq) returns (ErrorResp) {} rpc Test(TestReq) returns (TestResp) {} + rpc StopTest(EmptyReq) returns (EmptyResp); rpc QueryStats(QueryStatsReq) returns (QueryStatsResp) {} rpc ListConnections(EmptyReq) returns (ListConnectionsResp) {} // @@ -36,37 +37,23 @@ message LoadConfigReq { bool disable_stats = 4; } -enum TestMode { - TcpPing = 0; - UrlTest = 1; - FullTest = 2; +message URLTestResp { + string outbound_tag = 1; + int32 latency_ms = 2; + string error = 3; } message TestReq { - TestMode mode = 1; - int32 timeout = 6; - // TcpPing - string address = 2; - // UrlTest - LoadConfigReq config = 3; - string inbound = 4; - string url = 5; - // FullTest - string in_address = 7; - bool full_latency = 8; - bool full_speed = 9; - string full_speed_url = 13; - int32 full_speed_timeout = 14; - bool full_in_out = 10; - bool full_udp_latency = 12; - // - bool full_nat = 11 [deprecated = true]; + string config = 1; + repeated string outbound_tags = 2; + bool use_default_outbound = 3; + string url = 4; + bool test_current = 5; + int32 max_concurrency = 6; } message TestResp { - string error = 1; - int32 ms = 2; - string full_report = 3; + repeated URLTestResp results = 1; } message QueryStatsReq{ diff --git a/go/grpc_server/gen/libcore_grpc.pb.go b/go/grpc_server/gen/libcore_grpc.pb.go index f90d401..d80cd85 100644 --- a/go/grpc_server/gen/libcore_grpc.pb.go +++ b/go/grpc_server/gen/libcore_grpc.pb.go @@ -24,6 +24,7 @@ const ( LibcoreService_Start_FullMethodName = "/libcore.LibcoreService/Start" LibcoreService_Stop_FullMethodName = "/libcore.LibcoreService/Stop" LibcoreService_Test_FullMethodName = "/libcore.LibcoreService/Test" + LibcoreService_StopTest_FullMethodName = "/libcore.LibcoreService/StopTest" LibcoreService_QueryStats_FullMethodName = "/libcore.LibcoreService/QueryStats" LibcoreService_ListConnections_FullMethodName = "/libcore.LibcoreService/ListConnections" LibcoreService_GetGeoIPList_FullMethodName = "/libcore.LibcoreService/GetGeoIPList" @@ -42,6 +43,7 @@ type LibcoreServiceClient interface { Start(ctx context.Context, in *LoadConfigReq, opts ...grpc.CallOption) (*ErrorResp, error) Stop(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*ErrorResp, error) Test(ctx context.Context, in *TestReq, opts ...grpc.CallOption) (*TestResp, error) + StopTest(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*EmptyResp, error) QueryStats(ctx context.Context, in *QueryStatsReq, opts ...grpc.CallOption) (*QueryStatsResp, error) ListConnections(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*ListConnectionsResp, error) GetGeoIPList(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*GetGeoIPListResponse, error) @@ -104,6 +106,15 @@ func (c *libcoreServiceClient) Test(ctx context.Context, in *TestReq, opts ...gr return out, nil } +func (c *libcoreServiceClient) StopTest(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*EmptyResp, error) { + out := new(EmptyResp) + err := c.cc.Invoke(ctx, LibcoreService_StopTest_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *libcoreServiceClient) QueryStats(ctx context.Context, in *QueryStatsReq, opts ...grpc.CallOption) (*QueryStatsResp, error) { out := new(QueryStatsResp) err := c.cc.Invoke(ctx, LibcoreService_QueryStats_FullMethodName, in, out, opts...) @@ -176,6 +187,7 @@ type LibcoreServiceServer interface { Start(context.Context, *LoadConfigReq) (*ErrorResp, error) Stop(context.Context, *EmptyReq) (*ErrorResp, error) Test(context.Context, *TestReq) (*TestResp, error) + StopTest(context.Context, *EmptyReq) (*EmptyResp, error) QueryStats(context.Context, *QueryStatsReq) (*QueryStatsResp, error) ListConnections(context.Context, *EmptyReq) (*ListConnectionsResp, error) GetGeoIPList(context.Context, *EmptyReq) (*GetGeoIPListResponse, error) @@ -205,6 +217,9 @@ func (UnimplementedLibcoreServiceServer) Stop(context.Context, *EmptyReq) (*Erro func (UnimplementedLibcoreServiceServer) Test(context.Context, *TestReq) (*TestResp, error) { return nil, status.Errorf(codes.Unimplemented, "method Test not implemented") } +func (UnimplementedLibcoreServiceServer) StopTest(context.Context, *EmptyReq) (*EmptyResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method StopTest not implemented") +} func (UnimplementedLibcoreServiceServer) QueryStats(context.Context, *QueryStatsReq) (*QueryStatsResp, error) { return nil, status.Errorf(codes.Unimplemented, "method QueryStats not implemented") } @@ -329,6 +344,24 @@ func _LibcoreService_Test_Handler(srv interface{}, ctx context.Context, dec func return interceptor(ctx, in, info, handler) } +func _LibcoreService_StopTest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(EmptyReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LibcoreServiceServer).StopTest(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: LibcoreService_StopTest_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LibcoreServiceServer).StopTest(ctx, req.(*EmptyReq)) + } + return interceptor(ctx, in, info, handler) +} + func _LibcoreService_QueryStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(QueryStatsReq) if err := dec(in); err != nil { @@ -482,6 +515,10 @@ var LibcoreService_ServiceDesc = grpc.ServiceDesc{ MethodName: "Test", Handler: _LibcoreService_Test_Handler, }, + { + MethodName: "StopTest", + Handler: _LibcoreService_StopTest_Handler, + }, { MethodName: "QueryStats", Handler: _LibcoreService_QueryStats_Handler, diff --git a/go/grpc_server/grpc.go b/go/grpc_server/grpc.go index 210f780..6f2137c 100644 --- a/go/grpc_server/grpc.go +++ b/go/grpc_server/grpc.go @@ -91,6 +91,8 @@ func RunCore(setupCore func(), server gen.LibcoreServiceServer) { s := grpc.NewServer( grpc.StreamInterceptor(grpc_auth.StreamServerInterceptor(auther.Authenticate)), grpc.UnaryInterceptor(grpc_auth.UnaryServerInterceptor(auther.Authenticate)), + grpc.MaxRecvMsgSize(1024*1024*1024), // 1 gigaByte + grpc.MaxSendMsgSize(1024*1024*1024), // 1 gigaByte ) gen.RegisterLibcoreServiceServer(s, server) diff --git a/rpc/gRPC.cpp b/rpc/gRPC.cpp index 50834fd..f0d1860 100644 --- a/rpc/gRPC.cpp +++ b/rpc/gRPC.cpp @@ -66,9 +66,7 @@ namespace QtGrpc { QNetworkRequest request(callUrl); // request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false); // request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); -#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) request.setAttribute(QNetworkRequest::Http2DirectAttribute, true); -#endif request.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String{"application/grpc"}); request.setRawHeader("Cache-Control", "no-store"); request.setRawHeader(GrpcAcceptEncodingHeader, QByteArray{"identity,deflate,gzip"}); @@ -268,6 +266,19 @@ namespace NekoGui_rpc { } } + void Client::StopTests(bool *rpcOK) { + const libcore::EmptyReq req; + libcore::EmptyResp resp; + + auto status = make_grpc_channel()->Call("StopTest", req, &resp); + + if (status == QNetworkReply::NoError) { + *rpcOK = true; + } else { + NOT_OK + } + } + libcore::UpdateResp Client::Update(bool *rpcOK, const libcore::UpdateReq &request) { libcore::UpdateResp reply; auto status = default_grpc_channel->Call("Update", request, &reply); diff --git a/rpc/gRPC.h b/rpc/gRPC.h index fe6650e..9ed2265 100644 --- a/rpc/gRPC.h +++ b/rpc/gRPC.h @@ -28,6 +28,8 @@ namespace NekoGui_rpc { libcore::TestResp Test(bool *rpcOK, const libcore::TestReq &request); + void StopTests(bool *rpcOK); + libcore::UpdateResp Update(bool *rpcOK, const libcore::UpdateReq &request); QStringList GetGeoList(bool *rpcOK, GeoRuleSetType mode); diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index b7c2b30..f79ba7b 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -1,7 +1,6 @@ #include "./ui_mainwindow.h" #include "mainwindow.h" -#include "fmt/Preset.hpp" #include "db/ProfileFilter.hpp" #include "db/ConfigBuilder.hpp" #include "sub/GroupUpdater.hpp" @@ -68,7 +67,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi } themeManager->ApplyTheme(NekoGui::dataStore->theme); ui->setupUi(this); - speedTestThreadPool->setMaxThreadCount(NekoGui::dataStore->test_concurrent); + speedTestThreadPool->setMaxThreadCount(10); // constant value // connect(ui->menu_start, &QAction::triggered, this, [=]() { neko_start(); }); connect(ui->menu_stop, &QAction::triggered, this, [=]() { neko_stop(); }); @@ -323,37 +322,28 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi neko_set_spmode_vpn(false); }); connect(ui->menu_qr, &QAction::triggered, this, [=]() { display_qr_link(false); }); - connect(ui->menu_tcp_ping, &QAction::triggered, this, [=]() { speedtest_current_group(0); }); - connect(ui->menu_url_test, &QAction::triggered, this, [=]() { speedtest_current_group(1); }); - connect(ui->menu_full_test, &QAction::triggered, this, [=]() { speedtest_current_group(2); }); - connect(ui->menu_stop_testing, &QAction::triggered, this, [=]() { speedtest_current_group(114514); }); + + connect(ui->menu_server, &QMenu::aboutToShow, this, [=](){ + if (!speedtestRunning.tryLock()) { + ui->menu_server->addAction(ui->menu_stop_testing); + return; + } else { + speedtestRunning.unlock(); + ui->menu_server->removeAction(ui->menu_stop_testing); + } + }); + connect(ui->actionUrl_Test_Selected, &QAction::triggered, this, [=]() { + speedtest_current_group(get_now_selected_list()); + }); + connect(ui->actionUrl_Test_Group, &QAction::triggered, this, [=]() { + speedtest_current_group(NekoGui::profileManager->CurrentGroup()->ProfilesWithOrder()); + }); + connect(ui->menu_stop_testing, &QAction::triggered, this, [=]() { stopSpeedTests(); }); // auto set_selected_or_group = [=](int mode) { // 0=group 1=select 2=unknown(menu is hide) ui->menu_server->setProperty("selected_or_group", mode); }; - auto move_tests_to_menu = [=](bool menuCurrent_Select) { - return [=] { - if (menuCurrent_Select) { - ui->menuCurrent_Select->insertAction(ui->actionfake_4, ui->menu_tcp_ping); - ui->menuCurrent_Select->insertAction(ui->actionfake_4, ui->menu_url_test); - ui->menuCurrent_Select->insertAction(ui->actionfake_4, ui->menu_full_test); - ui->menuCurrent_Select->insertAction(ui->actionfake_4, ui->menu_stop_testing); - ui->menuCurrent_Select->insertAction(ui->actionfake_4, ui->menu_clear_test_result); - ui->menuCurrent_Select->insertAction(ui->actionfake_4, ui->menu_resolve_domain); - } else { - ui->menuCurrent_Group->insertAction(ui->actionfake_5, ui->menu_tcp_ping); - ui->menuCurrent_Group->insertAction(ui->actionfake_5, ui->menu_url_test); - ui->menuCurrent_Group->insertAction(ui->actionfake_5, ui->menu_full_test); - ui->menuCurrent_Group->insertAction(ui->actionfake_5, ui->menu_stop_testing); - ui->menuCurrent_Group->insertAction(ui->actionfake_5, ui->menu_clear_test_result); - ui->menuCurrent_Group->insertAction(ui->actionfake_5, ui->menu_resolve_domain); - } - set_selected_or_group(menuCurrent_Select ? 1 : 0); - }; - }; - connect(ui->menuCurrent_Select, &QMenu::aboutToShow, this, move_tests_to_menu(true)); - connect(ui->menuCurrent_Group, &QMenu::aboutToShow, this, move_tests_to_menu(false)); connect(ui->menu_server, &QMenu::aboutToHide, this, [=] { setTimeout([=] { set_selected_or_group(2); }, this, 200); }); @@ -911,11 +901,10 @@ void MainWindow::refresh_proxy_list_impl(const int &id, GroupSortAction groupSor ui->proxyListTable->setRowCount(0); // 添加行 int row = -1; - for (const auto &[id, profile]: NekoGui::profileManager->profiles) { - if (NekoGui::dataStore->current_group != profile->gid) continue; + for (const auto ent: NekoGui::profileManager->GetGroup(NekoGui::dataStore->current_group)->ProfilesWithOrder()) { row++; ui->proxyListTable->insertRow(row); - ui->proxyListTable->row2Id += id; + ui->proxyListTable->row2Id += ent->id; } } diff --git a/ui/mainwindow.h b/ui/mainwindow.h index 2ec92cf..b58d591 100644 --- a/ui/mainwindow.h +++ b/ui/mainwindow.h @@ -190,9 +190,11 @@ private: static void setup_grpc(); - void speedtest_current_group(int mode); + void speedtest_current_group(const QList>& profiles); - void RunSpeedTest(const std::shared_ptr& ent, int mode, const QStringList& full_test_flags); + void stopSpeedTests(); + + void RunSpeedTest(const QString& config, bool useDefault, const QStringList& outboundTags, const QMap& tag2entID, int entID = -1); void url_test_current(); diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index eee0993..83ec46e 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -38,10 +38,10 @@ - QToolButton::InstantPopup + QToolButton::ToolButtonPopupMode::InstantPopup - Qt::ToolButtonTextUnderIcon + Qt::ToolButtonStyle::ToolButtonTextUnderIcon @@ -60,10 +60,10 @@ - QToolButton::InstantPopup + QToolButton::ToolButtonPopupMode::InstantPopup - Qt::ToolButtonTextUnderIcon + Qt::ToolButtonStyle::ToolButtonTextUnderIcon @@ -82,10 +82,10 @@ - QToolButton::InstantPopup + QToolButton::ToolButtonPopupMode::InstantPopup - Qt::ToolButtonTextUnderIcon + Qt::ToolButtonStyle::ToolButtonTextUnderIcon @@ -104,10 +104,10 @@ - QToolButton::InstantPopup + QToolButton::ToolButtonPopupMode::InstantPopup - Qt::ToolButtonTextUnderIcon + Qt::ToolButtonStyle::ToolButtonTextUnderIcon @@ -138,7 +138,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -160,7 +160,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -192,19 +192,19 @@ - Qt::CustomContextMenu + Qt::ContextMenuPolicy::CustomContextMenu - QAbstractItemView::NoEditTriggers + QAbstractItemView::EditTrigger::NoEditTriggers true - QAbstractItemView::SelectRows + QAbstractItemView::SelectionBehavior::SelectRows - QAbstractItemView::ScrollPerPixel + QAbstractItemView::ScrollMode::ScrollPerPixel false @@ -274,7 +274,7 @@ - Qt::CustomContextMenu + Qt::ContextMenuPolicy::CustomContextMenu false @@ -300,10 +300,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QSizePolicy::Maximum + QSizePolicy::Policy::Maximum @@ -337,7 +337,7 @@ 0 0 800 - 25 + 33 @@ -404,29 +404,6 @@ - - - Current Group - - - - - - - - - - - - - - - - - Current Select - - - @@ -440,12 +417,8 @@ - - - - - - + + @@ -779,6 +752,16 @@ Stop Testing + + + Url Test Selected + + + + + Url Test Group + + diff --git a/ui/mainwindow_grpc.cpp b/ui/mainwindow_grpc.cpp index 3dccd2d..b746b3a 100644 --- a/ui/mainwindow_grpc.cpp +++ b/ui/mainwindow_grpc.cpp @@ -8,12 +8,10 @@ #include "ui/widget/MessageBoxTimer.h" #include -#include #include #include #include #include -#include // ext core @@ -49,7 +47,7 @@ void MainWindow::setup_grpc() { runOnNewThread([=] { NekoGui_traffic::trafficLooper->Loop(); }); } -void MainWindow::RunSpeedTest(const std::shared_ptr& ent, int mode, const QStringList& full_test_flags) { +void MainWindow::RunSpeedTest(const QString& config, bool useDefault, const QStringList& outboundTags, const QMap& tag2entID, int entID) { if (stopSpeedtest.load()) { MW_show_log("Profile test aborted"); return; @@ -59,46 +57,13 @@ void MainWindow::RunSpeedTest(const std::shared_ptr& ent, QSemaphore extSem; libcore::TestReq req; - req.set_mode((libcore::TestMode) mode); - req.set_timeout(3000); - req.set_url(NekoGui::dataStore->test_latency_url.toStdString()); - if (mode == libcore::TestMode::UrlTest || mode == libcore::FullTest) { - auto c = BuildConfig(ent, true, false); - if (!c->error.isEmpty()) { - ent->full_test_report = c->error; - ent->Save(); - auto profileId = ent->id; - runOnUiThread([this, profileId] { - refresh_proxy_list(profileId); - }); - } - // - if (!c->extRs.empty()) { - runOnUiThread( - [&] { - extCs = CreateExtCFromExtR(c->extRs, true); - QThread::msleep(500); - extSem.release(); - }, - DS_cores); - extSem.acquire(); - } - // - auto config = new libcore::LoadConfigReq; - config->set_core_config(QJsonObject2QString(c->coreConfig, true).toStdString()); - req.set_allocated_config(config); - req.set_in_address(ent->bean->serverAddress.toStdString()); - - req.set_full_latency(full_test_flags.contains("1")); - req.set_full_udp_latency(full_test_flags.contains("2")); - req.set_full_speed(full_test_flags.contains("3")); - req.set_full_in_out(full_test_flags.contains("4")); - - req.set_full_speed_url(NekoGui::dataStore->test_download_url.toStdString()); - req.set_full_speed_timeout(NekoGui::dataStore->test_download_timeout); - } else if (mode == libcore::TcpPing) { - req.set_address(ent->bean->DisplayAddress().toStdString()); + for (const auto &item: outboundTags) { + req.add_outbound_tags(item.toStdString()); } + req.set_config(config.toStdString()); + req.set_url(NekoGui::dataStore->test_latency_url.toStdString()); + req.set_use_default_outbound(useDefault); + req.set_max_concurrency(NekoGui::dataStore->test_concurrent); bool rpcOK; auto result = defaultClient->Test(&rpcOK, req); @@ -117,29 +82,33 @@ void MainWindow::RunSpeedTest(const std::shared_ptr& ent, // if (!rpcOK) return; - if (result.error().empty()) { - ent->latency = result.ms(); - if (ent->latency == 0) ent->latency = 1; // nekoray use 0 to represents not tested - } else { - ent->latency = -1; - } - ent->full_test_report = result.full_report().c_str(); // higher priority - ent->Save(); + for (const auto &res: result.results()) { + if (!tag2entID.empty()) { + entID = tag2entID.count(QString(res.outbound_tag().c_str())) == 0 ? -1 : tag2entID[QString(res.outbound_tag().c_str())]; + } + if (entID == -1) { + MW_show_log("Something is very wrong, the subject ent cannot be found!"); + continue; + } - if (!result.error().empty()) { - MW_show_log(tr("[%1] test error: %2").arg(ent->bean->DisplayTypeAndName(), result.error().c_str())); - } + auto ent = NekoGui::profileManager->GetProfile(entID); + if (ent == nullptr) { + MW_show_log("Profile manager data is corrupted, try again."); + continue; + } - auto profileId = ent->id; - runOnUiThread([this, profileId] { - refresh_proxy_list(profileId); - }); + if (res.error().empty()) { + ent->latency = res.latency_ms(); + } else { + ent->latency = 0; + MW_show_log(tr("[%1] test error: %2").arg(ent->bean->DisplayTypeAndName(), res.error().c_str())); + } + ent->Save(); + } } -void MainWindow::speedtest_current_group(int mode) { - // menu_stop_testing mode == 114514, TODO use proper constants - if (mode == 114514) { - stopSpeedtest.store(true); +void MainWindow::speedtest_current_group(const QList>& profiles) { + if (profiles.isEmpty()) { return; } if (!speedtestRunning.tryLock()) { @@ -147,95 +116,72 @@ void MainWindow::speedtest_current_group(int mode) { return; } - speedTestThreadPool->setMaxThreadCount(NekoGui::dataStore->test_concurrent); + runOnNewThread([this, profiles]() { + auto buildObject = NekoGui::BuildTestConfig(profiles); - auto profiles = get_selected_or_group(); - if (profiles.isEmpty()) { - speedtestRunning.unlock(); - return; - } - - QStringList full_test_flags; - if (mode == libcore::FullTest) { - auto w = new QDialog(this); - auto layout = new QVBoxLayout(w); - w->setWindowTitle(tr("Test Options")); - // - auto l1 = new QCheckBox(tr("Latency")); - auto l2 = new QCheckBox(tr("UDP latency")); - auto l3 = new QCheckBox(tr("Download speed")); - auto l4 = new QCheckBox(tr("In and Out IP")); - // - auto box = new QDialogButtonBox; - box->setOrientation(Qt::Horizontal); - box->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); - connect(box, &QDialogButtonBox::accepted, w, &QDialog::accept); - connect(box, &QDialogButtonBox::rejected, w, &QDialog::reject); - // - layout->addWidget(l1); - layout->addWidget(l2); - layout->addWidget(l3); - layout->addWidget(l4); - layout->addWidget(box); - if (w->exec() != QDialog::Accepted) { - w->deleteLater(); - speedtestRunning.unlock(); - return; - } - // - if (l1->isChecked()) full_test_flags << "1"; - if (l2->isChecked()) full_test_flags << "2"; - if (l3->isChecked()) full_test_flags << "3"; - if (l4->isChecked()) full_test_flags << "4"; - // - w->deleteLater(); - if (full_test_flags.isEmpty()) { - speedtestRunning.unlock(); - return; - } - } - - runOnNewThread([profiles, full_test_flags, mode, this]() { std::atomic counter(0); stopSpeedtest.store(false); - for (const auto &item: profiles) { - auto func = [&item, full_test_flags, mode, this, &counter, profiles]() { - MainWindow::RunSpeedTest(item, mode, full_test_flags); + auto testCount = buildObject->fullConfigs.size() + (!buildObject->outboundTags.empty()); + for (const auto &entID: buildObject->fullConfigs.keys()) { + auto configStr = buildObject->fullConfigs[entID]; + auto func = [this, &counter, testCount, configStr, entID]() { + MainWindow::RunSpeedTest(configStr, true, {}, {}, entID); counter++; - if (counter.load() == profiles.size()) { + if (counter.load() == testCount) { speedtestRunning.unlock(); } }; + speedTestThreadPool->start(func); + } + if (!buildObject->outboundTags.empty()) { + auto func = [this, &buildObject, &counter, testCount]() { + MainWindow::RunSpeedTest(QJsonObject2QString(buildObject->coreConfig, false), false, buildObject->outboundTags, buildObject->tag2entID); + counter++; + if (counter.load() == testCount) { + speedtestRunning.unlock(); + } + }; speedTestThreadPool->start(func); } speedtestRunning.lock(); speedtestRunning.unlock(); - MW_show_log("Speedtest finished!"); + runOnUiThread([=]{ + refresh_proxy_list(); + MW_show_log("Speedtest finished!"); + }); }); } +void MainWindow::stopSpeedTests() { + bool ok; + defaultClient->StopTests(&ok); + + if (!ok) { + MW_show_log("Failed to stop tests"); + } +} + void MainWindow::url_test_current() { last_test_time = QTime::currentTime(); ui->label_running->setText(tr("Testing")); runOnNewThread([=] { libcore::TestReq req; - req.set_mode(libcore::UrlTest); - req.set_timeout(3000); + req.set_test_current(true); req.set_url(NekoGui::dataStore->test_latency_url.toStdString()); bool rpcOK; auto result = defaultClient->Test(&rpcOK, req); if (!rpcOK) return; - auto latency = result.ms(); + auto latency = result.results()[0].latency_ms(); last_test_time = QTime::currentTime(); runOnUiThread([=] { - if (!result.error().empty()) { - MW_show_log(QString("UrlTest error: %1").arg(result.error().c_str())); + if (!result.results()[0].error().empty()) { + MW_show_log(QString("UrlTest error: %1").arg(result.results()[0].error().c_str())); } if (latency <= 0) { ui->label_running->setText(tr("Test Result") + ": " + tr("Unavailable"));