diff --git a/Dockerfile b/Dockerfile index 89d60229cbce..1fdfce29e046 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,7 @@ RUN --mount=type=cache,target=/go/pkg/mod \ GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o trufflehog . FROM alpine:3.18 -RUN apk add --no-cache bash git openssh-client ca-certificates rpm2cpio \ +RUN apk add --no-cache bash git openssh-client ca-certificates rpm2cpio binutils cpio \ && rm -rf /var/cache/apk/* && update-ca-certificates COPY --from=builder /build/trufflehog /usr/bin/trufflehog COPY entrypoint.sh /etc/entrypoint.sh diff --git a/Dockerfile.goreleaser b/Dockerfile.goreleaser index db663c467885..bd1b1aad00b4 100644 --- a/Dockerfile.goreleaser +++ b/Dockerfile.goreleaser @@ -1,6 +1,6 @@ FROM alpine:3.18 -RUN apk add --no-cache bash git openssh-client ca-certificates rpm2cpio \ +RUN apk add --no-cache bash git openssh-client ca-certificates rpm2cpio binutils cpio \ && rm -rf /var/cache/apk/* && update-ca-certificates WORKDIR /usr/bin/ COPY trufflehog . diff --git a/hack/snifftest/main.go b/hack/snifftest/main.go index 1a1062d373fb..217ecd079f27 100644 --- a/hack/snifftest/main.go +++ b/hack/snifftest/main.go @@ -204,7 +204,7 @@ func main() { }) logger.Info("scanning repo", "repo", r) - err = s.ScanRepo(ctx, repo, path, git.NewScanOptions(), chunksChan) + err = s.ScanRepo(ctx, repo, path, git.NewScanOptions(), sources.ChanReporter{Ch: chunksChan}) if err != nil { logFatal(err, "error scanning repo") } diff --git a/pkg/cache/memory/memory.go b/pkg/cache/memory/memory.go index 549dc2c06a28..b4e9c87c3cfd 100644 --- a/pkg/cache/memory/memory.go +++ b/pkg/cache/memory/memory.go @@ -94,7 +94,7 @@ func (c *Cache) Values() []string { return res } -// Contents returns all key-value pairs in the cache encodes as a string. +// Contents returns a comma-separated string containing all keys in the cache. func (c *Cache) Contents() string { items := c.c.Items() res := make([]string, 0, len(items)) diff --git a/pkg/detectors/appfollow/appfollow.go b/pkg/detectors/appfollow/appfollow.go index a9b6c6355ee5..7e3e37abf0d7 100644 --- a/pkg/detectors/appfollow/appfollow.go +++ b/pkg/detectors/appfollow/appfollow.go @@ -2,8 +2,6 @@ package appfollow import ( "context" - b64 "encoding/base64" - "fmt" "net/http" "regexp" "strings" @@ -22,7 +20,7 @@ var ( client = common.SaneHttpClient() // Make sure that your group is surrounded in boundary characters such as below to reduce false positives. - keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"appfollow"}) + `\b([0-9A-Za-z]{20})\b`) + keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"appfollow"}) + `\b(eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9\.[0-9A-Za-z]{74}\.[0-9A-Z-a-z\-_]{43})\b`) ) // Keywords are used for efficiently pre-filtering chunks. @@ -49,13 +47,11 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result } if verify { - data := fmt.Sprintf("%s:", resMatch) - sEnc := b64.StdEncoding.EncodeToString([]byte(data)) - req, err := http.NewRequestWithContext(ctx, "GET", "https://api.appfollow.io/test", nil) + req, err := http.NewRequestWithContext(ctx, "GET", "https://api.appfollow.io/api/v2/account/users", nil) if err != nil { continue } - req.Header.Add("Authorization", fmt.Sprintf("Basic %s", sEnc)) + req.Header.Add("X-AppFollow-API-Token", resMatch) res, err := client.Do(req) if err == nil { defer res.Body.Close() diff --git a/pkg/detectors/aws/aws.go b/pkg/detectors/aws/aws.go index 0047f761176d..9a5baf4c6ef0 100644 --- a/pkg/detectors/aws/aws.go +++ b/pkg/detectors/aws/aws.go @@ -22,6 +22,22 @@ type scanner struct { skipIDs map[string]struct{} } +// resourceTypes derived from: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-unique-ids +var resourceTypes = map[string]string{ + "ABIA": "AWS STS service bearer token", + "ACCA": "Context-specific credential", + "AGPA": "User group", + "AIDA": "IAM user", + "AIPA": "Amazon EC2 instance profile", + "AKIA": "Access key", + "ANPA": "Managed policy", + "ANVA": "Version in a managed policy", + "APKA": "Public key", + "AROA": "Role", + "ASCA": "Certificate", + "ASIA": "Temporary (AWS STS) access key IDs", +} + func New(opts ...func(*scanner)) *scanner { scanner := &scanner{ skipIDs: map[string]struct{}{}, @@ -53,7 +69,7 @@ var ( // Make sure that your group is surrounded in boundary characters such as below to reduce false positives. // Key types are from this list https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-unique-ids - idPat = regexp.MustCompile(`\b((?:AKIA|ABIA|ACCA|ASIA)[0-9A-Z]{16})\b`) + idPat = regexp.MustCompile(`\b((AKIA|ABIA|ACCA)[0-9A-Z]{16})\b`) secretPat = regexp.MustCompile(`[^A-Za-z0-9+\/]{0,1}([A-Za-z0-9+\/]{40})[^A-Za-z0-9+\/]{0,1}`) // Hashes, like those for git, do technically match the secret pattern. // But they are extremely unlikely to be generated as an actual AWS secret. @@ -93,7 +109,7 @@ func (s scanner) FromData(ctx context.Context, verify bool, data []byte) (result secretMatches := secretPat.FindAllStringSubmatch(dataStr, -1) for _, idMatch := range idMatches { - if len(idMatch) != 2 { + if len(idMatch) != 3 { continue } resIDMatch := strings.TrimSpace(idMatch[1]) @@ -115,12 +131,19 @@ func (s scanner) FromData(ctx context.Context, verify bool, data []byte) (result Raw: []byte(resIDMatch), Redacted: resIDMatch, RawV2: []byte(resIDMatch + resSecretMatch), + ExtraData: map[string]string{ + "resource_type": resourceTypes[idMatch[2]], + }, } if verify { verified, extraData, verificationErr := s.verifyMatch(ctx, resIDMatch, resSecretMatch, true) s1.Verified = verified - s1.ExtraData = extraData + //Append the extraData to the existing ExtraData map. + // This will overwrite with the new verified values. + for k, v := range extraData { + s1.ExtraData[k] = v + } s1.VerificationError = verificationErr } diff --git a/pkg/detectors/aws/aws_test.go b/pkg/detectors/aws/aws_test.go index 8c7799b78608..f94e007544b9 100644 --- a/pkg/detectors/aws/aws_test.go +++ b/pkg/detectors/aws/aws_test.go @@ -61,9 +61,11 @@ func TestAWS_FromChunk(t *testing.T) { Verified: true, Redacted: "AKIASP2TPHJSQH3FJRUX", ExtraData: map[string]string{ - "account": "171436882533", - "arn": "arn:aws:iam::171436882533:user/canarytokens.com@@4dxkh0pdeop3bzu9zx5wob793", - "user_id": "AIDASP2TPHJSUFRSTTZX4", + "resource_type": "Access key", + "rotation_guide": "https://howtorotate.com/docs/tutorials/aws/", + "account": "171436882533", + "arn": "arn:aws:iam::171436882533:user/canarytokens.com@@4dxkh0pdeop3bzu9zx5wob793", + "user_id": "AIDASP2TPHJSUFRSTTZX4", }, }, }, @@ -82,7 +84,7 @@ func TestAWS_FromChunk(t *testing.T) { DetectorType: detectorspb.DetectorType_AWS, Verified: false, Redacted: "AKIASP2TPHJSQH3FJRUX", - ExtraData: nil, + ExtraData: map[string]string{"resource_type": "Access key"}, }, }, wantErr: false, @@ -111,15 +113,20 @@ func TestAWS_FromChunk(t *testing.T) { DetectorType: detectorspb.DetectorType_AWS, Verified: false, Redacted: "AKIASP2TPHJSQH3FJXYZ", + ExtraData: map[string]string{ + "resource_type": "Access key", + }, }, { DetectorType: detectorspb.DetectorType_AWS, Verified: true, Redacted: "AKIASP2TPHJSQH3FJRUX", ExtraData: map[string]string{ - "account": "171436882533", - "arn": "arn:aws:iam::171436882533:user/canarytokens.com@@4dxkh0pdeop3bzu9zx5wob793", - "user_id": "AIDASP2TPHJSUFRSTTZX4", + "resource_type": "Access key", + "rotation_guide": "https://howtorotate.com/docs/tutorials/aws/", + "account": "171436882533", + "arn": "arn:aws:iam::171436882533:user/canarytokens.com@@4dxkh0pdeop3bzu9zx5wob793", + "user_id": "AIDASP2TPHJSUFRSTTZX4", }, }, }, @@ -150,9 +157,11 @@ func TestAWS_FromChunk(t *testing.T) { Verified: true, Redacted: "AKIASP2TPHJSQH3FJRUX", ExtraData: map[string]string{ - "account": "171436882533", - "arn": "arn:aws:iam::171436882533:user/canarytokens.com@@4dxkh0pdeop3bzu9zx5wob793", - "user_id": "AIDASP2TPHJSUFRSTTZX4", + "resource_type": "Access key", + "rotation_guide": "https://howtorotate.com/docs/tutorials/aws/", + "account": "171436882533", + "arn": "arn:aws:iam::171436882533:user/canarytokens.com@@4dxkh0pdeop3bzu9zx5wob793", + "user_id": "AIDASP2TPHJSUFRSTTZX4", }, }, { @@ -176,6 +185,9 @@ func TestAWS_FromChunk(t *testing.T) { DetectorType: detectorspb.DetectorType_AWS, Verified: false, Redacted: "AKIASP2TPHJSQH3FJRUX", + ExtraData: map[string]string{ + "resource_type": "Access key", + }, }, }, wantErr: false, @@ -207,6 +219,9 @@ func TestAWS_FromChunk(t *testing.T) { DetectorType: detectorspb.DetectorType_AWS, Verified: false, Redacted: "AKIASP2TPHJSQH3FJRUX", + ExtraData: map[string]string{ + "resource_type": "Access key", + }, }, }, wantErr: false, @@ -225,6 +240,9 @@ func TestAWS_FromChunk(t *testing.T) { DetectorType: detectorspb.DetectorType_AWS, Verified: false, Redacted: "AKIASP2TPHJSQH3FJRUX", + ExtraData: map[string]string{ + "resource_type": "Access key", + }, }, }, wantErr: false, @@ -243,6 +261,9 @@ func TestAWS_FromChunk(t *testing.T) { DetectorType: detectorspb.DetectorType_AWS, Verified: false, Redacted: "AKIASP2TPHJSQH3FJRUX", + ExtraData: map[string]string{ + "resource_type": "Access key", + }, }, }, wantErr: false, @@ -262,9 +283,11 @@ func TestAWS_FromChunk(t *testing.T) { Verified: true, Redacted: "AKIASP2TPHJSQH3FJRUX", ExtraData: map[string]string{ - "account": "171436882533", - "arn": "arn:aws:iam::171436882533:user/canarytokens.com@@4dxkh0pdeop3bzu9zx5wob793", - "user_id": "AIDASP2TPHJSUFRSTTZX4", + "resource_type": "Access key", + "rotation_guide": "https://howtorotate.com/docs/tutorials/aws/", + "account": "171436882533", + "arn": "arn:aws:iam::171436882533:user/canarytokens.com@@4dxkh0pdeop3bzu9zx5wob793", + "user_id": "AIDASP2TPHJSUFRSTTZX4", }, }, }, diff --git a/pkg/detectors/awssessionkeys/awssessionkey.go b/pkg/detectors/awssessionkeys/awssessionkey.go new file mode 100644 index 000000000000..3c53c37a132a --- /dev/null +++ b/pkg/detectors/awssessionkeys/awssessionkey.go @@ -0,0 +1,328 @@ +package awssessionkey + +import ( + "context" + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "net/http" + "regexp" + "strings" + "time" + + "github.com/trufflesecurity/trufflehog/v3/pkg/common" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" + "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" +) + +type scanner struct { + verificationClient *http.Client + skipIDs map[string]struct{} +} + +func New(opts ...func(*scanner)) *scanner { + scanner := &scanner{ + skipIDs: map[string]struct{}{}, + } + for _, opt := range opts { + + opt(scanner) + } + + return scanner +} + +func WithSkipIDs(skipIDs []string) func(*scanner) { + return func(s *scanner) { + ids := map[string]struct{}{} + for _, id := range skipIDs { + ids[id] = struct{}{} + } + + s.skipIDs = ids + } +} + +// Ensure the scanner satisfies the interface at compile time. +var _ detectors.Detector = (*scanner)(nil) + +var ( + defaultVerificationClient = common.SaneHttpClient() + + // Make sure that your group is surrounded in boundary characters such as below to reduce false positives. + // Key types are from this list https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-unique-ids + idPat = regexp.MustCompile(`\b((?:ASIA)[0-9A-Z]{16})\b`) + secretPat = regexp.MustCompile(`\b[^A-Za-z0-9+\/]{0,1}([A-Za-z0-9+\/]{40})[^A-Za-z0-9+\/]{0,1}\b`) + sessionPat = regexp.MustCompile(`\b[^A-Za-z0-9+\/]{0,1}([A-Za-z0-9+=\/]{41,1000})[^A-Za-z0-9+=\/]{0,1}\b`) + // Hashes, like those for git, do technically match the secret pattern. + // But they are extremely unlikely to be generated as an actual AWS secret. + // So when we find them, if they're not verified, we should ignore the result. + falsePositiveSecretCheck = regexp.MustCompile(`[a-f0-9]{40}`) +) + +// Keywords are used for efficiently pre-filtering chunks. +// Use identifiers in the secret preferably, or the provider name. +func (s scanner) Keywords() []string { + return []string{ + "ASIA", + } +} + +func GetHash(input string) string { + data := []byte(input) + hasher := sha256.New() + hasher.Write(data) + return hex.EncodeToString(hasher.Sum(nil)) +} + +func GetHMAC(key []byte, data []byte) []byte { + hasher := hmac.New(sha256.New, key) + hasher.Write(data) + return hasher.Sum(nil) +} + +func checkSessionToken(sessionToken string, secret string) bool { + if !strings.Contains(sessionToken, "YXdz") || strings.Contains(sessionToken, secret) { + // Handle error if the sessionToken is not a valid base64 string + return false + } + return true +} + +// FromData will find and optionally verify AWS secrets in a given set of bytes. +func (s scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) { + dataStr := string(data) + + idMatches := idPat.FindAllStringSubmatch(dataStr, -1) + secretMatches := secretPat.FindAllStringSubmatch(dataStr, -1) + sessionMatches := sessionPat.FindAllStringSubmatch(dataStr, -1) + + for _, idMatch := range idMatches { + if len(idMatch) != 2 { + continue + } + resIDMatch := strings.TrimSpace(idMatch[1]) + + if s.skipIDs != nil { + if _, ok := s.skipIDs[resIDMatch]; ok { + continue + } + } + + for _, secretMatch := range secretMatches { + if len(secretMatch) != 2 { + continue + } + resSecretMatch := strings.TrimSpace(secretMatch[1]) + + for _, sessionMatch := range sessionMatches { + if len(sessionMatch) != 2 { + continue + } + resSessionMatch := strings.TrimSpace(sessionMatch[1]) + if !checkSessionToken(resSessionMatch, resSecretMatch) { + continue + } + s1 := detectors.Result{ + DetectorType: detectorspb.DetectorType_AWSSessionKey, + Raw: []byte(resIDMatch), + Redacted: resIDMatch, + RawV2: []byte(resIDMatch + resSecretMatch + resSessionMatch), + } + + if verify { + verified, extraData, verificationErr := s.verifyMatch(ctx, resIDMatch, resSecretMatch, resSessionMatch, true) + s1.Verified = verified + s1.ExtraData = extraData + s1.VerificationError = verificationErr + } + + if !s1.Verified { + // Unverified results that contain common test words are probably not secrets + if detectors.IsKnownFalsePositive(resSecretMatch, detectors.DefaultFalsePositives, true) { + continue + } + // Unverified results that look like hashes are probably not secrets + if falsePositiveSecretCheck.MatchString(resSecretMatch) { + continue + } + } + + results = append(results, s1) + // If we've found a verified match with this ID, we don't need to look for any more. So move on to the next ID. + if s1.Verified { + break + } + } + } + } + return awsCustomCleanResults(results), nil +} + +func (s scanner) verifyMatch(ctx context.Context, resIDMatch, resSecretMatch string, resSessionMatch string, retryOn403 bool) (bool, map[string]string, error) { + + // REQUEST VALUES. + method := "GET" + service := "sts" + host := "sts.amazonaws.com" + region := "us-east-1" + endpoint := "https://sts.amazonaws.com" + now := time.Now().UTC() + datestamp := now.Format("20060102") + amzDate := now.Format("20060102T150405Z") + + req, err := http.NewRequestWithContext(ctx, method, endpoint, nil) + if err != nil { + return false, nil, err + } + req.Header.Set("Accept", "application/json") + + canonicalURI := "/" + canonicalHeaders := "host:" + host + "\n" + "x-amz-date:" + amzDate + "\n" + "x-amz-security-token:" + resSessionMatch + "\n" + signedHeaders := "host;x-amz-date;x-amz-security-token" + algorithm := "AWS4-HMAC-SHA256" + credentialScope := fmt.Sprintf("%s/%s/%s/aws4_request", datestamp, region, service) + + params := req.URL.Query() + params.Add("Action", "GetCallerIdentity") + params.Add("Version", "2011-06-15") + canonicalQuerystring := params.Encode() + payloadHash := GetHash("") // empty payload + canonicalRequest := method + "\n" + canonicalURI + "\n" + canonicalQuerystring + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + payloadHash + + stringToSign := algorithm + "\n" + amzDate + "\n" + credentialScope + "\n" + GetHash(canonicalRequest) + + hash := GetHMAC([]byte(fmt.Sprintf("AWS4%s", resSecretMatch)), []byte(datestamp)) + hash = GetHMAC(hash, []byte(region)) + hash = GetHMAC(hash, []byte(service)) + hash = GetHMAC(hash, []byte("aws4_request")) + + signature2 := GetHMAC(hash, []byte(stringToSign)) // Get Signature HMAC SHA256 + signature := hex.EncodeToString(signature2) + + authorizationHeader := fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s", + algorithm, resIDMatch, credentialScope, signedHeaders, signature) + + req.Header.Add("Authorization", authorizationHeader) + req.Header.Add("x-amz-date", amzDate) + req.Header.Add("x-amz-security-token", resSessionMatch) + + req.URL.RawQuery = params.Encode() + + client := s.verificationClient + if client == nil { + client = defaultVerificationClient + } + + extraData := map[string]string{ + "rotation_guide": "https://howtorotate.com/docs/tutorials/aws/", + } + + res, err := client.Do(req) + if err == nil { + defer res.Body.Close() + if res.StatusCode >= 200 && res.StatusCode < 300 { + identityInfo := identityRes{} + err := json.NewDecoder(res.Body).Decode(&identityInfo) + if err == nil { + extraData["account"] = identityInfo.GetCallerIdentityResponse.GetCallerIdentityResult.Account + extraData["user_id"] = identityInfo.GetCallerIdentityResponse.GetCallerIdentityResult.UserID + extraData["arn"] = identityInfo.GetCallerIdentityResponse.GetCallerIdentityResult.Arn + return true, extraData, nil + } else { + return false, nil, err + } + } else if res.StatusCode == 403 { + // Experimentation has indicated that if you make two GetCallerIdentity requests within five seconds that + // share a key ID but are signed with different secrets the second one will be rejected with a 403 that + // carries a SignatureDoesNotMatch code in its body. This happens even if the second ID-secret pair is + // valid. Since this is exactly our access pattern, we need to work around it. + // + // Fortunately, experimentation has also revealed a workaround: simply resubmit the second request. The + // response to the resubmission will be as expected. But there's a caveat: You can't have closed the body of + // the response to the original second request, or read to its end, or the resubmission will also yield a + // SignatureDoesNotMatch. For this reason, we have to re-request all 403s. We can't re-request only + // SignatureDoesNotMatch responses, because we can only tell whether a given 403 is a SignatureDoesNotMatch + // after decoding its response body, which requires reading the entire response body, which disables the + // workaround. + // + // We are clearly deep in the guts of AWS implementation details here, so this all might change with no + // notice. If you're here because something in this detector broke, you have my condolences. + if retryOn403 { + return s.verifyMatch(ctx, resIDMatch, resSecretMatch, resSessionMatch, false) + } + var body awsErrorResponseBody + err = json.NewDecoder(res.Body).Decode(&body) + if err == nil { + // All instances of the code I've seen in the wild are PascalCased but this check is + // case-insensitive out of an abundance of caution + if strings.EqualFold(body.Error.Code, "InvalidClientTokenId") { + return false, nil, nil + } else { + return false, nil, fmt.Errorf("request to %v returned status %d with an unexpected reason (%s: %s)", res.Request.URL, res.StatusCode, body.Error.Code, body.Error.Message) + } + } else { + return false, nil, fmt.Errorf("couldn't parse the sts response body (%v)", err) + } + } else { + return false, nil, fmt.Errorf("request to %v returned unexpected status %d", res.Request.URL, res.StatusCode) + } + } else { + return false, nil, err + } +} + +func awsCustomCleanResults(results []detectors.Result) []detectors.Result { + if len(results) == 0 { + return results + } + + // For every ID, we want at most one result, preferably verified. + idResults := map[string]detectors.Result{} + for _, result := range results { + // Always accept the verified result as the result for the given ID. + if result.Verified { + idResults[result.Redacted] = result + continue + } + + // Only include an unverified result if we don't already have a result for a given ID. + if _, exist := idResults[result.Redacted]; !exist { + idResults[result.Redacted] = result + } + } + + out := []detectors.Result{} + for _, r := range idResults { + out = append(out, r) + } + return out +} + +type awsError struct { + Code string `json:"Code"` + Message string `json:"Message"` +} + +type awsErrorResponseBody struct { + Error awsError `json:"Error"` +} + +type identityRes struct { + GetCallerIdentityResponse struct { + GetCallerIdentityResult struct { + Account string `json:"Account"` + Arn string `json:"Arn"` + UserID string `json:"UserId"` + } `json:"GetCallerIdentityResult"` + ResponseMetadata struct { + RequestID string `json:"RequestId"` + } `json:"ResponseMetadata"` + } `json:"GetCallerIdentityResponse"` +} + +func (s scanner) Type() detectorspb.DetectorType { + return detectorspb.DetectorType_AWSSessionKey +} diff --git a/pkg/detectors/azurebatch/azurebatch.go b/pkg/detectors/azurebatch/azurebatch.go new file mode 100644 index 000000000000..576fcd43af40 --- /dev/null +++ b/pkg/detectors/azurebatch/azurebatch.go @@ -0,0 +1,114 @@ +package azurebatch + +import ( + "context" + "crypto/hmac" + "crypto/sha256" + "encoding/base64" + "fmt" + "net/http" + "regexp" + "strings" + "time" + + "github.com/trufflesecurity/trufflehog/v3/pkg/common" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" + "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" +) + +type Scanner struct { + client *http.Client +} + +// Ensure the Scanner satisfies the interface at compile time. +var _ detectors.Detector = (*Scanner)(nil) + +var ( + defaultClient = common.SaneHttpClient() + // Make sure that your group is surrounded in boundary characters such as below to reduce false positives. + urlPat = regexp.MustCompile(`https://(.{1,50})\.(.{1,50})\.batch\.azure\.com`) + secretPat = regexp.MustCompile(`[A-Za-z0-9+/=]{88}`) +) + +// Keywords are used for efficiently pre-filtering chunks. +// Use identifiers in the secret preferably, or the provider name. +func (s Scanner) Keywords() []string { + return []string{".batch.azure.com"} +} + +// FromData will find and optionally verify Azurebatch secrets in a given set of bytes. +func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) { + dataStr := string(data) + + urlMatches := urlPat.FindAllStringSubmatch(dataStr, -1) + secretMatches := secretPat.FindAllStringSubmatch(dataStr, -1) + + for _, urlMatch := range urlMatches { + + for _, secretMatch := range secretMatches { + + endpoint := urlMatch[0] + accountName := urlMatch[1] + accountKey := secretMatch[0] + + s1 := detectors.Result{ + DetectorType: detectorspb.DetectorType_AzureBatch, + Raw: []byte(endpoint), + Redacted: endpoint, + RawV2: []byte(endpoint + accountKey), + } + + if verify { + client := s.client + if client == nil { + client = defaultClient + } + url := fmt.Sprintf("%s/applications?api-version=2020-09-01.12.0", endpoint) + date := time.Now().UTC().Format(http.TimeFormat) + stringToSign := fmt.Sprintf( + "GET\n\n\n\n\napplication/json\n%s\n\n\n\n\n\n%s\napi-version:%s", + date, + strings.ToLower(fmt.Sprintf("/%s/applications", accountName)), + "2020-09-01.12.0", + ) + key, _ := base64.StdEncoding.DecodeString(accountKey) + h := hmac.New(sha256.New, key) + h.Write([]byte(stringToSign)) + signature := base64.StdEncoding.EncodeToString(h.Sum(nil)) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + continue + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", fmt.Sprintf("SharedKey %s:%s", accountName, signature)) + req.Header.Set("Date", date) + resp, err := client.Do(req) + if err != nil { + continue + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusOK { + s1.Verified = true + } + + } + + // This function will check false positives for common test words, but also it will make sure the key appears 'random' enough to be a real key. + if !s1.Verified && detectors.IsKnownFalsePositive(accountKey, detectors.DefaultFalsePositives, true) { + continue + } + + results = append(results, s1) + if s1.Verified { + break + } + } + } + + return results, nil +} + +func (s Scanner) Type() detectorspb.DetectorType { + return detectorspb.DetectorType_AzureBatch +} diff --git a/pkg/detectors/prospectio/prospectio_test.go b/pkg/detectors/azurebatch/azurebatch_test.go similarity index 52% rename from pkg/detectors/prospectio/prospectio_test.go rename to pkg/detectors/azurebatch/azurebatch_test.go index 675b0e39339c..b7a356275d66 100644 --- a/pkg/detectors/prospectio/prospectio_test.go +++ b/pkg/detectors/azurebatch/azurebatch_test.go @@ -1,7 +1,7 @@ //go:build detectors // +build detectors -package prospectio +package azurebatch import ( "context" @@ -9,22 +9,25 @@ import ( "testing" "time" - "github.com/kylelemons/godebug/pretty" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" "github.com/trufflesecurity/trufflehog/v3/pkg/common" "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" ) -func TestProspectIO_FromChunk(t *testing.T) { +func TestAzurebatch_FromChunk(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() - testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3") + testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5") if err != nil { t.Fatalf("could not get test secrets from GCP: %s", err) } - secret := testSecrets.MustGetField("PROSPECTIO_TOKEN") - inactiveSecret := testSecrets.MustGetField("PROSPECTIO_INACTIVE") + url := testSecrets.MustGetField("AZUREBATCH_URL") + secret := testSecrets.MustGetField("AZUREBATCH_KEY") + inactiveSecret := testSecrets.MustGetField("AZUREBATCH_KEY_INACTIVE") type args struct { ctx context.Context @@ -32,43 +35,46 @@ func TestProspectIO_FromChunk(t *testing.T) { verify bool } tests := []struct { - name string - s Scanner - args args - want []detectors.Result - wantErr bool + name string + s Scanner + args args + want []detectors.Result + wantErr bool + wantVerificationErr bool }{ { name: "found, verified", s: Scanner{}, args: args{ ctx: context.Background(), - data: []byte(fmt.Sprintf("You can find a prospectio secret %s within", secret)), + data: []byte(fmt.Sprintf("You can find a azurebatch secret %s and %s within", url, secret)), verify: true, }, want: []detectors.Result{ { - DetectorType: detectorspb.DetectorType_ProspectIO, + DetectorType: detectorspb.DetectorType_AzureBatch, Verified: true, }, }, - wantErr: false, + wantErr: false, + wantVerificationErr: false, }, { name: "found, unverified", s: Scanner{}, args: args{ ctx: context.Background(), - data: []byte(fmt.Sprintf("You can find a prospectio secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation + data: []byte(fmt.Sprintf("You can find a azurebatch secret %s and %s within but not valid", url, inactiveSecret)), // the secret would satisfy the regex but not pass validation verify: true, }, want: []detectors.Result{ { - DetectorType: detectorspb.DetectorType_ProspectIO, + DetectorType: detectorspb.DetectorType_AzureBatch, Verified: false, }, }, - wantErr: false, + wantErr: false, + wantVerificationErr: false, }, { name: "not found", @@ -78,26 +84,30 @@ func TestProspectIO_FromChunk(t *testing.T) { data: []byte("You cannot find the secret within"), verify: true, }, - want: nil, - wantErr: false, + want: nil, + wantErr: false, + wantVerificationErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := Scanner{} - got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) + got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) if (err != nil) != tt.wantErr { - t.Errorf("ProspectIO.FromData() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("AzureBatch.FromData() error = %v, wantErr %v", err, tt.wantErr) return } for i := range got { if len(got[i].Raw) == 0 { t.Fatalf("no raw secret present: \n %+v", got[i]) } - got[i].Raw = nil + if (got[i].VerificationError != nil) != tt.wantVerificationErr { + t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError) + } } - if diff := pretty.Compare(got, tt.want); diff != "" { - t.Errorf("ProspectIO.FromData() %s diff: (-got +want)\n%s", tt.name, diff) + ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "RawV2", "Raw", "Redacted", "VerificationError") + + if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" { + t.Errorf("AzureBatch.FromData() %s diff: (-got +want)\n%s", tt.name, diff) } }) } diff --git a/pkg/detectors/azurecontainerregistry/azurecontainerregistry.go b/pkg/detectors/azurecontainerregistry/azurecontainerregistry.go new file mode 100644 index 000000000000..6698c338c2ea --- /dev/null +++ b/pkg/detectors/azurecontainerregistry/azurecontainerregistry.go @@ -0,0 +1,101 @@ +package azurecontainerregistry + +import ( + "context" + "encoding/base64" + "fmt" + "net/http" + "regexp" + + "github.com/trufflesecurity/trufflehog/v3/pkg/common" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" + "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" +) + +type Scanner struct { + client *http.Client +} + +// Ensure the Scanner satisfies the interface at compile time. +var _ detectors.Detector = (*Scanner)(nil) + +var ( + defaultClient = common.SaneHttpClient() + // Make sure that your group is surrounded in boundary characters such as below to reduce false positives. + url = regexp.MustCompile(`([a-zA-Z0-9-]{1,100})\.azurecr\.io`) + password = regexp.MustCompile(`\b[A-Za-z0-9+/=]{52}\b`) +) + +// Keywords are used for efficiently pre-filtering chunks. +// Use identifiers in the secret preferably, or the provider name. +func (s Scanner) Keywords() []string { + return []string{".azurecr.io"} +} + +// FromData will find and optionally verify Azurecontainerregistry secrets in a given set of bytes. +func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) { + dataStr := string(data) + + urlMatches := url.FindAllStringSubmatch(dataStr, -1) + passwordMatches := password.FindAllStringSubmatch(dataStr, -1) + + for _, urlMatch := range urlMatches { + for _, passwordMatch := range passwordMatches { + + endpoint := urlMatch[0] + username := urlMatch[1] + password := passwordMatch[0] + + s1 := detectors.Result{ + DetectorType: detectorspb.DetectorType_AzureContainerRegistry, + Raw: []byte(endpoint), + Redacted: endpoint, + RawV2: []byte(endpoint + password), + } + + if verify { + client := s.client + if client == nil { + client = defaultClient + } + + auth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", username, password))) + url := fmt.Sprintf("https://%s/v2/", endpoint) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + continue + } + + req.Header.Set("Authorization", fmt.Sprintf("Basic %s", auth)) + res, err := client.Do(req) + if err == nil { + defer res.Body.Close() + if res.StatusCode >= 200 && res.StatusCode < 300 { + s1.Verified = true + } else if res.StatusCode == 401 { + // The secret is determinately not verified (nothing to do) + } else { + s1.VerificationError = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode) + } + } else { + s1.VerificationError = err + } + } + + if !s1.Verified && detectors.IsKnownFalsePositive(password, detectors.DefaultFalsePositives, true) { + continue + } + + results = append(results, s1) + if s1.Verified { + break + } + } + } + + return results, nil +} + +func (s Scanner) Type() detectorspb.DetectorType { + return detectorspb.DetectorType_AzureContainerRegistry +} diff --git a/pkg/detectors/azurecontainerregistry/azurecontainerregistry_test.go b/pkg/detectors/azurecontainerregistry/azurecontainerregistry_test.go new file mode 100644 index 000000000000..ede6611f771e --- /dev/null +++ b/pkg/detectors/azurecontainerregistry/azurecontainerregistry_test.go @@ -0,0 +1,129 @@ +//go:build detectors +// +build detectors + +package azurecontainerregistry + +import ( + "context" + "fmt" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "testing" + "time" + + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" + + "github.com/trufflesecurity/trufflehog/v3/pkg/common" + "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" +) + +func TestAzureContainerRegistry_FromChunk(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5") + if err != nil { + t.Fatalf("could not get test secrets from GCP: %s", err) + } + azureHost := testSecrets.MustGetField("AZURE_CR_HOST") + password := testSecrets.MustGetField("AZURE_CR_PASSWORD") + passwordInactive := testSecrets.MustGetField("AZURE_CR_PASSWORD_INACTIVE") + + + type args struct { + ctx context.Context + data []byte + verify bool + } + tests := []struct { + name string + s Scanner + args args + want []detectors.Result + wantErr bool + wantVerificationErr bool + }{ + { + name: "found, verified", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a azurecontainerregistry secret %s and %s within", azureHost, password)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_AzureContainerRegistry, + Verified: true, + }, + }, + wantErr: false, + wantVerificationErr: false, + }, + { + name: "found, unverified", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a azurecontainerregistry secret %s and %s within but not valid", azureHost, passwordInactive)), // the secret would satisfy the regex but not pass validation + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_AzureContainerRegistry, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: false, + }, + { + name: "not found", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte("You cannot find the secret within"), + verify: true, + }, + want: nil, + wantErr: false, + wantVerificationErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) + if (err != nil) != tt.wantErr { + t.Errorf("AzureContainerRegistry.FromData() error = %v, wantErr %v", err, tt.wantErr) + return + } + for i := range got { + if len(got[i].Raw) == 0 { + t.Fatalf("no raw secret present: \n %+v", got[i]) + } + if (got[i].VerificationError != nil) != tt.wantVerificationErr { + t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError) + } + } + ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "RawV2", "Raw","Redacted", "VerificationError") + if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" { + t.Errorf("AzureContainerRegistry.FromData() %s diff: (-got +want)\n%s", tt.name, diff) + } + }) + } +} + +func BenchmarkFromData(benchmark *testing.B) { + ctx := context.Background() + s := Scanner{} + for name, data := range detectors.MustGetBenchmarkData() { + benchmark.Run(name, func(b *testing.B) { + b.ResetTimer() + for n := 0; n < b.N; n++ { + _, err := s.FromData(ctx, false, data) + if err != nil { + b.Fatal(err) + } + } + }) + } +} diff --git a/pkg/detectors/braintreepayments/braintreepayments.go b/pkg/detectors/braintreepayments/braintreepayments.go index b073a2aa8438..1600945305d0 100644 --- a/pkg/detectors/braintreepayments/braintreepayments.go +++ b/pkg/detectors/braintreepayments/braintreepayments.go @@ -2,6 +2,7 @@ package braintreepayments import ( "context" + "fmt" "io" "net/http" "regexp" @@ -12,14 +13,21 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" ) -type Scanner struct{} +type Scanner struct { + client *http.Client + useTestURL bool +} // Ensure the Scanner satisfies the interface at compile time var _ detectors.Detector = (*Scanner)(nil) -var ( - client = common.SaneHttpClient() +const ( + verifyURL = "https://payments.braintree-api.com/graphql" + verifyTestURL = "https://payments.sandbox.braintree-api.com/graphql" +) +var ( + defaultClient = common.SaneHttpClient() // Make sure that your group is surrounded in boundary characters such as below to reduce false positives keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"braintree"}) + `\b([0-9a-f]{32})\b`) idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"braintree"}) + `\b([0-9a-z]{16})\b`) @@ -48,7 +56,6 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result if len(idMatch) != 2 { continue } - resIdMatch := strings.TrimSpace(idMatch[1]) s1 := detectors.Result{ @@ -57,34 +64,16 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result } if verify { - payload := strings.NewReader(`{"query": "query { ping }"}`) - req, err := http.NewRequestWithContext(ctx, "POST", "https://payments.braintree-api.com/graphql", payload) - if err != nil { - continue - } - req.Header.Add("Content-Type", "application/json") - req.Header.Add("Braintree-Version", "2019-01-01") - req.SetBasicAuth(resIdMatch, resMatch) - res, err := client.Do(req) - if err == nil { - bodyBytes, err := io.ReadAll(res.Body) - if err != nil { - continue - } - - bodyString := string(bodyBytes) - validResponse := strings.Contains(bodyString, `"data":{`) - - defer res.Body.Close() - if res.StatusCode >= 200 && res.StatusCode < 300 && validResponse { - s1.Verified = true - } else { - // This function will check false positives for common test words, but also it will make sure the key appears 'random' enough to be a real key - if detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) { - continue - } - } - } + client := s.getClient() + url := s.getBraintreeURL() + isVerified, verificationErr := verifyBraintree(ctx, client, url, resIdMatch, resMatch) + s1.Verified = isVerified + s1.VerificationError = verificationErr + } + + // This function will check false positives for common test words, but also it will make sure the key appears 'random' enough to be a real key + if !s1.Verified && detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) { + continue } results = append(results, s1) @@ -94,6 +83,53 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result return results, nil } +func (s Scanner) getBraintreeURL() string { + if s.useTestURL { + return verifyTestURL + } + return verifyURL +} + +func (s Scanner) getClient() *http.Client { + if s.client != nil { + return s.client + } + return defaultClient +} + +func verifyBraintree(ctx context.Context, client *http.Client, url, pubKey, privKey string) (bool, error) { + payload := strings.NewReader(`{"query": "query { ping }"}`) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, payload) + if err != nil { + return false, err + } + req.Header.Add("Content-Type", "application/json") + req.Header.Add("Braintree-Version", "2019-01-01") + req.SetBasicAuth(pubKey, privKey) + res, err := client.Do(req) + if err != nil { + return false, err + } + + bodyBytes, err := io.ReadAll(res.Body) + if err != nil { + return false, err + } + defer res.Body.Close() + + bodyString := string(bodyBytes) + if !(res.StatusCode == http.StatusOK) { + return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode) + } + + validResponse := `"data":{` + if strings.Contains(bodyString, validResponse) { + return true, nil + } + + return false, nil +} + func (s Scanner) Type() detectorspb.DetectorType { return detectorspb.DetectorType_BraintreePayments } diff --git a/pkg/detectors/braintreepayments/braintreepayments_test.go b/pkg/detectors/braintreepayments/braintreepayments_test.go index 8a3daea09c55..0713f124b614 100644 --- a/pkg/detectors/braintreepayments/braintreepayments_test.go +++ b/pkg/detectors/braintreepayments/braintreepayments_test.go @@ -6,10 +6,11 @@ import ( "testing" "time" - "github.com/kylelemons/godebug/pretty" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/trufflesecurity/trufflehog/v3/pkg/common" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" - "github.com/trufflesecurity/trufflehog/v3/pkg/common" "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" ) @@ -30,15 +31,16 @@ func TestBraintreePayments_FromChunk(t *testing.T) { verify bool } tests := []struct { - name string - s Scanner - args args - want []detectors.Result - wantErr bool + name string + s Scanner + args args + want []detectors.Result + wantErr bool + wantVerificationErr bool }{ { name: "found, verified", - s: Scanner{}, + s: Scanner{useTestURL: true}, args: args{ ctx: context.Background(), data: []byte(fmt.Sprintf("You can find a braintree secret %s within braintree %s", secret, id)), @@ -52,9 +54,47 @@ func TestBraintreePayments_FromChunk(t *testing.T) { }, wantErr: false, }, + { + name: "found, verified but unexpected api surface", + s: Scanner{ + client: common.ConstantResponseHttpClient(404, ""), + }, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a braintree secret %s within braintree %s", secret, id)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_BraintreePayments, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: true, + }, + { + name: "found, would be verified if not for timeout", + s: Scanner{ + client: common.SaneHttpClientTimeOut(1 * time.Microsecond), + }, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a braintree secret %s within braintree %s", secret, id)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_BraintreePayments, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: true, + }, { name: "found, unverified", - s: Scanner{}, + s: Scanner{useTestURL: true}, args: args{ ctx: context.Background(), data: []byte(fmt.Sprintf("You can find a braintree secret %s within braintree %s but not valid", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation @@ -82,8 +122,7 @@ func TestBraintreePayments_FromChunk(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := Scanner{} - got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) + got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) if (err != nil) != tt.wantErr { t.Errorf("BraintreePayments.FromData() error = %v, wantErr %v", err, tt.wantErr) return @@ -92,9 +131,13 @@ func TestBraintreePayments_FromChunk(t *testing.T) { if len(got[i].Raw) == 0 { t.Fatalf("no raw secret present: \n %+v", got[i]) } - got[i].Raw = nil + if (got[i].VerificationError != nil) != tt.wantVerificationErr { + t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError) + } } - if diff := pretty.Compare(got, tt.want); diff != "" { + + ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "VerificationError") + if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" { t.Errorf("BraintreePayments.FromData() %s diff: (-got +want)\n%s", tt.name, diff) } }) diff --git a/pkg/detectors/clickuppersonaltoken/clickuppersonaltoken.go b/pkg/detectors/clickuppersonaltoken/clickuppersonaltoken.go index f3ce2df5c0f8..4ceed898d62f 100644 --- a/pkg/detectors/clickuppersonaltoken/clickuppersonaltoken.go +++ b/pkg/detectors/clickuppersonaltoken/clickuppersonaltoken.go @@ -20,7 +20,7 @@ var ( client = common.SaneHttpClient() // Make sure that your group is surrounded in boundary characters such as below to reduce false positives. - keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"clickup"}) + `\b(pk_[0-9]{8}_[0-9A-Z]{32})\b`) + keyPat = regexp.MustCompile(`\b(pk_[0-9]{7,8}_[0-9A-Z]{32})\b`) ) // Keywords are used for efficiently pre-filtering chunks. diff --git a/pkg/detectors/coda/coda.go b/pkg/detectors/coda/coda.go new file mode 100644 index 000000000000..bec893080e30 --- /dev/null +++ b/pkg/detectors/coda/coda.go @@ -0,0 +1,89 @@ +package coda + +import ( + "context" + "fmt" + "net/http" + "regexp" + "strings" + + "github.com/trufflesecurity/trufflehog/v3/pkg/common" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" + "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" +) + +type Scanner struct { + client *http.Client +} + +// Ensure the Scanner satisfies the interface at compile time. +var _ detectors.Detector = (*Scanner)(nil) + +var ( + defaultClient = common.SaneHttpClient() + // Make sure that your group is surrounded in boundary characters such as below to reduce false positives. + keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"coda"}) + `\b([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})\b`) +) + +// Keywords are used for efficiently pre-filtering chunks. +// Use identifiers in the secret preferably, or the provider name. +func (s Scanner) Keywords() []string { + return []string{"coda"} +} + +// FromData will find and optionally verify Coda secrets in a given set of bytes. +func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) { + dataStr := string(data) + + matches := keyPat.FindAllStringSubmatch(dataStr, -1) + + for _, match := range matches { + if len(match) != 2 { + continue + } + resMatch := strings.TrimSpace(match[1]) + + s1 := detectors.Result{ + DetectorType: detectorspb.DetectorType_Coda, + Raw: []byte(resMatch), + } + + if verify { + client := s.client + if client == nil { + client = defaultClient + } + req, err := http.NewRequestWithContext(ctx, "GET", "https://coda.io/apis/v1/whoami", nil) + if err != nil { + continue + } + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch)) + res, err := client.Do(req) + if err == nil { + defer res.Body.Close() + if res.StatusCode >= 200 && res.StatusCode < 300 { + s1.Verified = true + } else if res.StatusCode == 401 { + // The secret is determinately not verified (nothing to do) + } else { + s1.VerificationError = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode) + } + } else { + s1.VerificationError = err + } + } + + // This function will check false positives for common test words, but also it will make sure the key appears 'random' enough to be a real key. + if !s1.Verified && detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) { + continue + } + + results = append(results, s1) + } + + return results, nil +} + +func (s Scanner) Type() detectorspb.DetectorType { + return detectorspb.DetectorType_Coda +} diff --git a/pkg/detectors/coda/coda_test.go b/pkg/detectors/coda/coda_test.go new file mode 100644 index 000000000000..0503f9e6ba59 --- /dev/null +++ b/pkg/detectors/coda/coda_test.go @@ -0,0 +1,161 @@ +//go:build detectors +// +build detectors + +package coda + +import ( + "context" + "fmt" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "testing" + "time" + + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" + + "github.com/trufflesecurity/trufflehog/v3/pkg/common" + "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" +) + +func TestCoda_FromChunk(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5") + if err != nil { + t.Fatalf("could not get test secrets from GCP: %s", err) + } + secret := testSecrets.MustGetField("CODA") + inactiveSecret := testSecrets.MustGetField("CODA_INACTIVE") + + type args struct { + ctx context.Context + data []byte + verify bool + } + tests := []struct { + name string + s Scanner + args args + want []detectors.Result + wantErr bool + wantVerificationErr bool + }{ + { + name: "found, verified", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a coda secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_Coda, + Verified: true, + }, + }, + wantErr: false, + wantVerificationErr: false, + }, + { + name: "found, unverified", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a coda secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_Coda, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: false, + }, + { + name: "not found", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte("You cannot find the secret within"), + verify: true, + }, + want: nil, + wantErr: false, + wantVerificationErr: false, + }, + { + name: "found, would be verified if not for timeout", + s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a coda secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_Coda, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: true, + }, + { + name: "found, verified but unexpected api surface", + s: Scanner{client: common.ConstantResponseHttpClient(404, "")}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a coda secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_Coda, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) + if (err != nil) != tt.wantErr { + t.Errorf("Coda.FromData() error = %v, wantErr %v", err, tt.wantErr) + return + } + for i := range got { + if len(got[i].Raw) == 0 { + t.Fatalf("no raw secret present: \n %+v", got[i]) + } + if (got[i].VerificationError != nil) != tt.wantVerificationErr { + t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError) + } + } + ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "VerificationError") + if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" { + t.Errorf("Coda.FromData() %s diff: (-got +want)\n%s", tt.name, diff) + } + }) + } +} + +func BenchmarkFromData(benchmark *testing.B) { + ctx := context.Background() + s := Scanner{} + for name, data := range detectors.MustGetBenchmarkData() { + benchmark.Run(name, func(b *testing.B) { + b.ResetTimer() + for n := 0; n < b.N; n++ { + _, err := s.FromData(ctx, false, data) + if err != nil { + b.Fatal(err) + } + } + }) + } +} diff --git a/pkg/detectors/databrickstoken/databrickstoken.go b/pkg/detectors/databrickstoken/databrickstoken.go index 7ce0cfc99baa..72e48e6b07c6 100644 --- a/pkg/detectors/databrickstoken/databrickstoken.go +++ b/pkg/detectors/databrickstoken/databrickstoken.go @@ -12,17 +12,19 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" ) -type Scanner struct{} +type Scanner struct{ + client *http.Client +} // Ensure the Scanner satisfies the interface at compile time. var _ detectors.Detector = (*Scanner)(nil) var ( - client = common.SaneHttpClient() + defaultClient = common.SaneHttpClient() // Make sure that your group is surrounded in boundary characters such as below to reduce false positives. - domain = regexp.MustCompile(`\b(https:\/\/[a-z0-9-]+\.cloud\.databricks\.com)\b`) - keyPat = regexp.MustCompile(`\b(dapi[a-z0-9]{32})\b`) + domain = regexp.MustCompile(`\b([a-z0-9-]+(?:\.[a-z0-9-]+)*\.(cloud\.databricks\.com|gcp\.databricks\.com|azurewebsites\.net))\b`) + keyPat = regexp.MustCompile(`\b(dapi[0-9a-f]{32})(-\d)?\b`) ) // Keywords are used for efficiently pre-filtering chunks. @@ -39,15 +41,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result domainMatches := domain.FindAllStringSubmatch(dataStr, -1) for _, match := range matches { - if len(match) != 2 { - continue - } resMatch := strings.TrimSpace(match[1]) for _, domainmatch := range domainMatches { - if len(domainmatch) != 2 { - continue - } resDomainMatch := strings.TrimSpace(domainmatch[1]) s1 := detectors.Result{ @@ -57,7 +53,11 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result } if verify { - req, err := http.NewRequestWithContext(ctx, "GET", resDomainMatch + "/api/2.0/clusters/list", nil) + client := s.client + if client == nil { + client = defaultClient + } + req, err := http.NewRequestWithContext(ctx, "GET", "https://" + resDomainMatch + "/api/2.0/clusters/list", nil) if err != nil { continue } @@ -67,14 +67,18 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result defer res.Body.Close() if res.StatusCode >= 200 && res.StatusCode < 300 { s1.Verified = true + } else if res.StatusCode == 403 { + // nothing to do here } else { - // This function will check false positives for common test words, but also it will make sure the key appears 'random' enough to be a real key. - if detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) { - continue - } + s1.VerificationError = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode) } + } else { + s1.VerificationError = err } } + if !s1.Verified && detectors.IsKnownFalsePositive(string(s1.Raw), detectors.DefaultFalsePositives, true) { + continue + } results = append(results, s1) } diff --git a/pkg/detectors/databrickstoken/databrickstoken_test.go b/pkg/detectors/databrickstoken/databrickstoken_test.go index c25f21c8921c..92911f9d0e3e 100644 --- a/pkg/detectors/databrickstoken/databrickstoken_test.go +++ b/pkg/detectors/databrickstoken/databrickstoken_test.go @@ -6,17 +6,18 @@ package databrickstoken import ( "context" "fmt" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "testing" "time" - "github.com/kylelemons/godebug/pretty" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" "github.com/trufflesecurity/trufflehog/v3/pkg/common" - "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" ) -func TestDatabrickstoken_FromChunk(t *testing.T) { +func TestDatabricksToken_FromChunk(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2") @@ -25,6 +26,7 @@ func TestDatabrickstoken_FromChunk(t *testing.T) { } secret := testSecrets.MustGetField("DATABRICKSTOKEN") inactiveSecret := testSecrets.MustGetField("DATABRICKSTOKEN_INACTIVE") + domain := testSecrets.MustGetField("DATABRICKSTOKEN_DOMAIN") type args struct { ctx context.Context @@ -32,18 +34,19 @@ func TestDatabrickstoken_FromChunk(t *testing.T) { verify bool } tests := []struct { - name string - s Scanner - args args - want []detectors.Result - wantErr bool + name string + s Scanner + args args + want []detectors.Result + wantErr bool + wantVerificationErr bool }{ { name: "found, verified", s: Scanner{}, args: args{ ctx: context.Background(), - data: []byte(fmt.Sprintf("You can find a databrickstoken secret %s within", secret)), + data: []byte(fmt.Sprintf("You can find a databrickstoken secret %s within %s", secret, domain)), verify: true, }, want: []detectors.Result{ @@ -52,14 +55,15 @@ func TestDatabrickstoken_FromChunk(t *testing.T) { Verified: true, }, }, - wantErr: false, + wantErr: false, + wantVerificationErr: false, }, { name: "found, unverified", s: Scanner{}, args: args{ ctx: context.Background(), - data: []byte(fmt.Sprintf("You can find a databrickstoken secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation + data: []byte(fmt.Sprintf("You can find a databrickstoken secret %s within %s but not valid", inactiveSecret, domain)), // the secret would satisfy the regex but not pass validation verify: true, }, want: []detectors.Result{ @@ -68,7 +72,8 @@ func TestDatabrickstoken_FromChunk(t *testing.T) { Verified: false, }, }, - wantErr: false, + wantErr: false, + wantVerificationErr: false, }, { name: "not found", @@ -78,14 +83,48 @@ func TestDatabrickstoken_FromChunk(t *testing.T) { data: []byte("You cannot find the secret within"), verify: true, }, - want: nil, - wantErr: false, + want: nil, + wantErr: false, + wantVerificationErr: false, + }, + { + name: "found, would be verified if not for timeout", + s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a databrickstoken secret %s within %s", secret, domain)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_DatabricksToken, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: true, + }, + { + name: "found, verified but unexpected api surface", + s: Scanner{client: common.ConstantResponseHttpClient(404, "")}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a databrickstoken secret %s within %s", secret, domain)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_DatabricksToken, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := Scanner{} - got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) + got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) if (err != nil) != tt.wantErr { t.Errorf("Databrickstoken.FromData() error = %v, wantErr %v", err, tt.wantErr) return @@ -94,10 +133,13 @@ func TestDatabrickstoken_FromChunk(t *testing.T) { if len(got[i].Raw) == 0 { t.Fatalf("no raw secret present: \n %+v", got[i]) } - got[i].Raw = nil + if (got[i].VerificationError != nil) != tt.wantVerificationErr { + t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError) + } } - if diff := pretty.Compare(got, tt.want); diff != "" { - t.Errorf("Databrickstoken.FromData() %s diff: (-got +want)\n%s", tt.name, diff) + ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "VerificationError") + if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" { + t.Errorf("DatabricksToken.FromData() %s diff: (-got +want)\n%s", tt.name, diff) } }) } diff --git a/pkg/detectors/scrapersite/scrapersite.go b/pkg/detectors/eventbrite/eventbrite.go similarity index 53% rename from pkg/detectors/scrapersite/scrapersite.go rename to pkg/detectors/eventbrite/eventbrite.go index 1295c831a6be..821596371762 100644 --- a/pkg/detectors/scrapersite/scrapersite.go +++ b/pkg/detectors/eventbrite/eventbrite.go @@ -1,38 +1,37 @@ -package scrapersite +package eventbrite import ( "context" "fmt" - "io" "net/http" "regexp" "strings" - "time" "github.com/trufflesecurity/trufflehog/v3/pkg/common" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" ) -type Scanner struct{} +type Scanner struct { + client *http.Client +} // Ensure the Scanner satisfies the interface at compile time. var _ detectors.Detector = (*Scanner)(nil) var ( - client = common.SaneHttpClientTimeOut(10 * time.Second) - + defaultClient = common.SaneHttpClient() // Make sure that your group is surrounded in boundary characters such as below to reduce false positives. - keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"scrapersite"}) + `\b([a-zA-Z0-9]{45})\b`) + keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"eventbrite"}) + `\b([0-9A-Z]{20})\b`) ) // Keywords are used for efficiently pre-filtering chunks. // Use identifiers in the secret preferably, or the provider name. func (s Scanner) Keywords() []string { - return []string{"scrapersite"} + return []string{"eventbrite"} } -// FromData will find and optionally verify ScraperSite secrets in a given set of bytes. +// FromData will find and optionally verify Eventbrite secrets in a given set of bytes. func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) { dataStr := string(data) @@ -45,39 +44,39 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result resMatch := strings.TrimSpace(match[1]) s1 := detectors.Result{ - DetectorType: detectorspb.DetectorType_ScraperSite, + DetectorType: detectorspb.DetectorType_Eventbrite, Raw: []byte(resMatch), } if verify { - req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://scrapersite.com/api-v1?api_key=%s&url=https://google.com", resMatch), nil) + client := s.client + if client == nil { + client = defaultClient + } + req, err := http.NewRequestWithContext(ctx, "GET", "https://www.eventbriteapi.com/v3/users/me/?token="+resMatch, nil) if err != nil { continue } res, err := client.Do(req) if err == nil { - bodyBytes, err := io.ReadAll(res.Body) - if err != nil { - continue - } - bodyString := string(bodyBytes) - validResponse := strings.Contains(bodyString, `"status":true`) defer res.Body.Close() if res.StatusCode >= 200 && res.StatusCode < 300 { - if validResponse { - s1.Verified = true - } else { - s1.Verified = false - } + s1.Verified = true + } else if res.StatusCode == 401 { + // The secret is determinately not verified (nothing to do) } else { - // This function will check false positives for common test words, but also it will make sure the key appears 'random' enough to be a real key. - if detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) { - continue - } + s1.VerificationError = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode) } + } else { + s1.VerificationError = err } } + // This function will check false positives for common test words, but also it will make sure the key appears 'random' enough to be a real key. + if !s1.Verified && detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) { + continue + } + results = append(results, s1) } @@ -85,5 +84,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result } func (s Scanner) Type() detectorspb.DetectorType { - return detectorspb.DetectorType_ScraperSite + return detectorspb.DetectorType_Eventbrite } diff --git a/pkg/detectors/eventbrite/eventbrite_test.go b/pkg/detectors/eventbrite/eventbrite_test.go new file mode 100644 index 000000000000..d5eddbd5e665 --- /dev/null +++ b/pkg/detectors/eventbrite/eventbrite_test.go @@ -0,0 +1,161 @@ +//go:build detectors +// +build detectors + +package eventbrite + +import ( + "context" + "fmt" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "testing" + "time" + + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" + + "github.com/trufflesecurity/trufflehog/v3/pkg/common" + "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" +) + +func TestEventbrite_FromChunk(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5") + if err != nil { + t.Fatalf("could not get test secrets from GCP: %s", err) + } + secret := testSecrets.MustGetField("EVENTBRITE") + inactiveSecret := testSecrets.MustGetField("EVENTBRITE_INACTIVE") + + type args struct { + ctx context.Context + data []byte + verify bool + } + tests := []struct { + name string + s Scanner + args args + want []detectors.Result + wantErr bool + wantVerificationErr bool + }{ + { + name: "found, verified", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a eventbrite secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_Eventbrite, + Verified: true, + }, + }, + wantErr: false, + wantVerificationErr: false, + }, + { + name: "found, unverified", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a eventbrite secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_Eventbrite, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: false, + }, + { + name: "not found", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte("You cannot find the secret within"), + verify: true, + }, + want: nil, + wantErr: false, + wantVerificationErr: false, + }, + { + name: "found, would be verified if not for timeout", + s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a eventbrite secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_Eventbrite, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: true, + }, + { + name: "found, verified but unexpected api surface", + s: Scanner{client: common.ConstantResponseHttpClient(404, "")}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a eventbrite secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_Eventbrite, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) + if (err != nil) != tt.wantErr { + t.Errorf("Eventbrite.FromData() error = %v, wantErr %v", err, tt.wantErr) + return + } + for i := range got { + if len(got[i].Raw) == 0 { + t.Fatalf("no raw secret present: \n %+v", got[i]) + } + if (got[i].VerificationError != nil) != tt.wantVerificationErr { + t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError) + } + } + ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "VerificationError") + if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" { + t.Errorf("Eventbrite.FromData() %s diff: (-got +want)\n%s", tt.name, diff) + } + }) + } +} + +func BenchmarkFromData(benchmark *testing.B) { + ctx := context.Background() + s := Scanner{} + for name, data := range detectors.MustGetBenchmarkData() { + benchmark.Run(name, func(b *testing.B) { + b.ResetTimer() + for n := 0; n < b.N; n++ { + _, err := s.FromData(ctx, false, data) + if err != nil { + b.Fatal(err) + } + } + }) + } +} diff --git a/pkg/detectors/fakejson/fakejson.go b/pkg/detectors/fakejson/fakejson.go deleted file mode 100644 index d3cf365d3aed..000000000000 --- a/pkg/detectors/fakejson/fakejson.go +++ /dev/null @@ -1,89 +0,0 @@ -package fakejson - -import ( - "context" - "fmt" - "io" - "net/http" - "regexp" - "strings" - - "github.com/trufflesecurity/trufflehog/v3/pkg/common" - "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" - "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" -) - -type Scanner struct{} - -// Ensure the Scanner satisfies the interface at compile time. -var _ detectors.Detector = (*Scanner)(nil) - -var ( - client = common.SaneHttpClient() - - // Make sure that your group is surrounded in boundary characters such as below to reduce false positives. - keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"fakejson"}) + `\b([a-zA-Z0-9]{22})\b`) -) - -// Keywords are used for efficiently pre-filtering chunks. -// Use identifiers in the secret preferably, or the provider name. -func (s Scanner) Keywords() []string { - return []string{"fakejson"} -} - -// FromData will find and optionally verify FakeJSON secrets in a given set of bytes. -func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) { - dataStr := string(data) - - matches := keyPat.FindAllStringSubmatch(dataStr, -1) - - for _, match := range matches { - if len(match) != 2 { - continue - } - resMatch := strings.TrimSpace(match[1]) - - s1 := detectors.Result{ - DetectorType: detectorspb.DetectorType_FakeJSON, - Raw: []byte(resMatch), - } - - if verify { - payload := strings.NewReader(fmt.Sprintf(`{"token":"%s","data":{"user_name":"nameFirst","user_email":"internetEmail","_repeat":3}}`, resMatch)) - req, err := http.NewRequestWithContext(ctx, "POST", "https://app.fakejson.com/q", payload) - if err != nil { - continue - } - req.Header.Add("Content-Type", "application/json") - res, err := client.Do(req) - if err == nil { - bodyBytes, err := io.ReadAll(res.Body) - if err == nil { - bodyString := string(bodyBytes) - validResponse := strings.Contains(bodyString, `"user_email":`) - defer res.Body.Close() - if res.StatusCode >= 200 && res.StatusCode < 300 { - if validResponse { - s1.Verified = true - } else { - s1.Verified = false - } - } else { - // This function will check false positives for common test words, but also it will make sure the key appears 'random' enough to be a real key. - if detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) { - continue - } - } - } - } - } - - results = append(results, s1) - } - - return results, nil -} - -func (s Scanner) Type() detectorspb.DetectorType { - return detectorspb.DetectorType_FakeJSON -} diff --git a/pkg/detectors/falsepositives.go b/pkg/detectors/falsepositives.go index c12cf6b53ce0..d77a65b408f7 100644 --- a/pkg/detectors/falsepositives.go +++ b/pkg/detectors/falsepositives.go @@ -5,6 +5,7 @@ import ( "math" "strings" "unicode" + "unicode/utf8" ) var DefaultFalsePositives = []FalsePositive{"example", "xxxxxx", "aaaaaa", "abcde", "00000", "sample", "www"} @@ -21,9 +22,9 @@ var wordList []byte var programmingBookWords []byte type Wordlists struct { - wordList []string - badList []string - programmingBookWords []string + wordList map[string]struct{} + badList map[string]struct{} + programmingBookWords map[string]struct{} } var FalsePositiveWordlists = Wordlists{ @@ -36,36 +37,29 @@ var FalsePositiveWordlists = Wordlists{ // Currently that includes: No number, english word in key, or matches common example pattens. // Only the secret key material should be passed into this function func IsKnownFalsePositive(match string, falsePositives []FalsePositive, wordCheck bool) bool { - + if !utf8.ValidString(match) { + return true + } + lower := strings.ToLower(match) for _, fp := range falsePositives { - if strings.Contains(strings.ToLower(match), string(fp)) { + if strings.Contains(lower, string(fp)) { return true } } if wordCheck { // check against common substring badlist - if hasDictWord(FalsePositiveWordlists.badList, match) { + if _, ok := FalsePositiveWordlists.badList[lower]; ok { return true } // check for dictionary word substrings - if hasDictWord(FalsePositiveWordlists.wordList, match) { + if _, ok := FalsePositiveWordlists.wordList[lower]; ok { return true } // check for programming book token substrings - if hasDictWord(FalsePositiveWordlists.programmingBookWords, match) { - return true - } - } - return false -} - -func hasDictWord(wordList []string, token string) bool { - lower := strings.ToLower(token) - for _, word := range wordList { - if strings.Contains(lower, word) { + if _, ok := FalsePositiveWordlists.programmingBookWords[lower]; ok { return true } } @@ -82,11 +76,11 @@ func HasDigit(key string) bool { return false } -func bytesToCleanWordList(data []byte) []string { - words := []string{} +func bytesToCleanWordList(data []byte) map[string]struct{} { + words := make(map[string]struct{}) for _, word := range strings.Split(string(data), "\n") { if strings.TrimSpace(word) != "" { - words = append(words, strings.TrimSpace(strings.ToLower(word))) + words[strings.TrimSpace(strings.ToLower(word))] = struct{}{} } } return words diff --git a/pkg/detectors/falsepositives_test.go b/pkg/detectors/falsepositives_test.go index b84aa427d62a..987269f45feb 100644 --- a/pkg/detectors/falsepositives_test.go +++ b/pkg/detectors/falsepositives_test.go @@ -90,3 +90,10 @@ func TestStringShannonEntropy(t *testing.T) { }) } } + +func BenchmarkDefaultIsKnownFalsePositive(b *testing.B) { + for i := 0; i < b.N; i++ { + // Use a string that won't be found in any dictionary for the worst case check. + IsKnownFalsePositive("aoeuaoeuaoeuaoeuaoeuaoeu", DefaultFalsePositives, true) + } +} diff --git a/pkg/detectors/fullstory/fullstory.go b/pkg/detectors/fullstory/fullstory.go index 97d6f09492d7..641080516a06 100644 --- a/pkg/detectors/fullstory/fullstory.go +++ b/pkg/detectors/fullstory/fullstory.go @@ -16,6 +16,9 @@ type Scanner struct{} // Ensure the Scanner satisfies the interface at compile time. var _ detectors.Detector = (*Scanner)(nil) +var _ detectors.Versioner = (*Scanner)(nil) + +func (s Scanner) Version() int { return 1 } var ( client = common.SaneHttpClient() diff --git a/pkg/detectors/prospectio/prospectio.go b/pkg/detectors/fullstory_v2/fullstory_v2.go similarity index 77% rename from pkg/detectors/prospectio/prospectio.go rename to pkg/detectors/fullstory_v2/fullstory_v2.go index 384d531dbf86..87f0b8547d0f 100644 --- a/pkg/detectors/prospectio/prospectio.go +++ b/pkg/detectors/fullstory_v2/fullstory_v2.go @@ -1,7 +1,8 @@ -package prospectio +package fullstory_v2 import ( "context" + "fmt" "net/http" "regexp" "strings" @@ -15,21 +16,24 @@ type Scanner struct{} // Ensure the Scanner satisfies the interface at compile time. var _ detectors.Detector = (*Scanner)(nil) +var _ detectors.Versioner = (*Scanner)(nil) + +func (Scanner) Version() int { return 1 } var ( client = common.SaneHttpClient() // Make sure that your group is surrounded in boundary characters such as below to reduce false positives. - keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"prospect"}) + `\b([a-z0-9A-Z-]{50})\b`) + keyPat = regexp.MustCompile(`\b(na1\.[A-Za-z0-9\+\/]{100})\b`) ) // Keywords are used for efficiently pre-filtering chunks. // Use identifiers in the secret preferably, or the provider name. func (s Scanner) Keywords() []string { - return []string{"prospect"} + return []string{"fullstory"} } -// FromData will find and optionally verify ProspectIO secrets in a given set of bytes. +// FromData will find and optionally verify Fullstory secrets in a given set of bytes. func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) { dataStr := string(data) @@ -42,17 +46,16 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result resMatch := strings.TrimSpace(match[1]) s1 := detectors.Result{ - DetectorType: detectorspb.DetectorType_ProspectIO, + DetectorType: detectorspb.DetectorType_Fullstory, Raw: []byte(resMatch), } if verify { - req, err := http.NewRequestWithContext(ctx, "GET", "https://api.prospect.io/public/v1/prospects", nil) + req, err := http.NewRequestWithContext(ctx, "GET", "https://api.fullstory.com/v2/users", nil) if err != nil { continue } - req.Header.Add("Authorization", resMatch) - req.Header.Add("Content-Type", "application/vnd.api+json; charset=utf-8") + req.Header.Add("Authorization", fmt.Sprintf("Basic %s", resMatch)) res, err := client.Do(req) if err == nil { defer res.Body.Close() @@ -74,5 +77,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result } func (s Scanner) Type() detectorspb.DetectorType { - return detectorspb.DetectorType_ProspectIO + return detectorspb.DetectorType_Fullstory } diff --git a/pkg/detectors/fakejson/fakejson_test.go b/pkg/detectors/fullstory_v2/fullstory_v2_test.go similarity index 75% rename from pkg/detectors/fakejson/fakejson_test.go rename to pkg/detectors/fullstory_v2/fullstory_v2_test.go index 1507bbd61bbc..083ba2410e3d 100644 --- a/pkg/detectors/fakejson/fakejson_test.go +++ b/pkg/detectors/fullstory_v2/fullstory_v2_test.go @@ -1,7 +1,7 @@ //go:build detectors // +build detectors -package fakejson +package fullstory_v2 import ( "context" @@ -16,15 +16,15 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" ) -func TestFakeJSON_FromChunk(t *testing.T) { +func TestFullstory_FromChunk(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1") if err != nil { t.Fatalf("could not get test secrets from GCP: %s", err) } - secret := testSecrets.MustGetField("FAKEJSON") - inactiveSecret := testSecrets.MustGetField("FAKEJSON_INACTIVE") + secret := testSecrets.MustGetField("FULLSTORY_V2") + inactiveSecret := testSecrets.MustGetField("FULLSTORY_V2_INACTIVE") type args struct { ctx context.Context @@ -43,12 +43,12 @@ func TestFakeJSON_FromChunk(t *testing.T) { s: Scanner{}, args: args{ ctx: context.Background(), - data: []byte(fmt.Sprintf("You can find a fakejson secret %s within", secret)), + data: []byte(fmt.Sprintf("You can find a fullstory secret %s within", secret)), verify: true, }, want: []detectors.Result{ { - DetectorType: detectorspb.DetectorType_FakeJSON, + DetectorType: detectorspb.DetectorType_Fullstory, Verified: true, }, }, @@ -59,12 +59,12 @@ func TestFakeJSON_FromChunk(t *testing.T) { s: Scanner{}, args: args{ ctx: context.Background(), - data: []byte(fmt.Sprintf("You can find a fakejson secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation + data: []byte(fmt.Sprintf("You can find a fullstory secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation verify: true, }, want: []detectors.Result{ { - DetectorType: detectorspb.DetectorType_FakeJSON, + DetectorType: detectorspb.DetectorType_Fullstory, Verified: false, }, }, @@ -87,7 +87,7 @@ func TestFakeJSON_FromChunk(t *testing.T) { s := Scanner{} got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) if (err != nil) != tt.wantErr { - t.Errorf("FakeJSON.FromData() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("Fullstory.FromData() error = %v, wantErr %v", err, tt.wantErr) return } for i := range got { @@ -97,7 +97,7 @@ func TestFakeJSON_FromChunk(t *testing.T) { got[i].Raw = nil } if diff := pretty.Compare(got, tt.want); diff != "" { - t.Errorf("FakeJSON.FromData() %s diff: (-got +want)\n%s", tt.name, diff) + t.Errorf("Fullstory.FromData() %s diff: (-got +want)\n%s", tt.name, diff) } }) } diff --git a/pkg/detectors/grafana/grafana.go b/pkg/detectors/grafana/grafana.go new file mode 100644 index 000000000000..696ede60b1da --- /dev/null +++ b/pkg/detectors/grafana/grafana.go @@ -0,0 +1,89 @@ +package grafana + +import ( + "context" + "fmt" + "net/http" + "regexp" + "strings" + + "github.com/trufflesecurity/trufflehog/v3/pkg/common" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" + "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" +) + +type Scanner struct { + client *http.Client +} + +// Ensure the Scanner satisfies the interface at compile time. +var _ detectors.Detector = (*Scanner)(nil) + +var ( + defaultClient = common.SaneHttpClient() + // Make sure that your group is surrounded in boundary characters such as below to reduce false positives. + keyPat = regexp.MustCompile(`\b(glc_[A-Za-z0-9+\/]{50,150}\={0,2})`) +) + +// Keywords are used for efficiently pre-filtering chunks. +// Use identifiers in the secret preferably, or the provider name. +func (s Scanner) Keywords() []string { + return []string{"glc_"} +} + +// FromData will find and optionally verify Grafana secrets in a given set of bytes. +func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) { + dataStr := string(data) + + matches := keyPat.FindAllStringSubmatch(dataStr, -1) + + for _, match := range matches { + if len(match) != 2 { + continue + } + resMatch := strings.TrimSpace(match[1]) + + s1 := detectors.Result{ + DetectorType: detectorspb.DetectorType_Grafana, + Raw: []byte(resMatch), + } + + if verify { + client := s.client + if client == nil { + client = defaultClient + } + req, err := http.NewRequestWithContext(ctx, "GET", "https://grafana.com/api/v1/tokens?region=us", nil) + if err != nil { + continue + } + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch)) + res, err := client.Do(req) + if err == nil { + defer res.Body.Close() + if res.StatusCode >= 200 && res.StatusCode < 300 || res.StatusCode == 403 { + s1.Verified = true + } else if res.StatusCode == 401 { + // The secret is determinately not verified (nothing to do) + } else { + s1.VerificationError = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode) + } + } else { + s1.VerificationError = err + } + } + + // This function will check false positives for common test words, but also it will make sure the key appears 'random' enough to be a real key. + if !s1.Verified && detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) { + continue + } + + results = append(results, s1) + } + + return results, nil +} + +func (s Scanner) Type() detectorspb.DetectorType { + return detectorspb.DetectorType_Grafana +} diff --git a/pkg/detectors/grafana/grafana_test.go b/pkg/detectors/grafana/grafana_test.go new file mode 100644 index 000000000000..3079553ceef0 --- /dev/null +++ b/pkg/detectors/grafana/grafana_test.go @@ -0,0 +1,161 @@ +//go:build detectors +// +build detectors + +package grafana + +import ( + "context" + "fmt" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "testing" + "time" + + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" + + "github.com/trufflesecurity/trufflehog/v3/pkg/common" + "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" +) + +func TestGrafana_FromChunk(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5") + if err != nil { + t.Fatalf("could not get test secrets from GCP: %s", err) + } + secret := testSecrets.MustGetField("GRAFANA") + inactiveSecret := testSecrets.MustGetField("GRAFANA_INACTIVE") + + type args struct { + ctx context.Context + data []byte + verify bool + } + tests := []struct { + name string + s Scanner + args args + want []detectors.Result + wantErr bool + wantVerificationErr bool + }{ + { + name: "found, verified", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a grafana secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_Grafana, + Verified: true, + }, + }, + wantErr: false, + wantVerificationErr: false, + }, + { + name: "found, unverified", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a grafana secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_Grafana, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: false, + }, + { + name: "not found", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte("You cannot find the secret within"), + verify: true, + }, + want: nil, + wantErr: false, + wantVerificationErr: false, + }, + { + name: "found, would be verified if not for timeout", + s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a grafana secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_Grafana, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: true, + }, + { + name: "found, verified but unexpected api surface", + s: Scanner{client: common.ConstantResponseHttpClient(404, "")}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a grafana secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_Grafana, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) + if (err != nil) != tt.wantErr { + t.Errorf("Grafana.FromData() error = %v, wantErr %v", err, tt.wantErr) + return + } + for i := range got { + if len(got[i].Raw) == 0 { + t.Fatalf("no raw secret present: \n %+v", got[i]) + } + if (got[i].VerificationError != nil) != tt.wantVerificationErr { + t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError) + } + } + ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "VerificationError") + if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" { + t.Errorf("Grafana.FromData() %s diff: (-got +want)\n%s", tt.name, diff) + } + }) + } +} + +func BenchmarkFromData(benchmark *testing.B) { + ctx := context.Background() + s := Scanner{} + for name, data := range detectors.MustGetBenchmarkData() { + benchmark.Run(name, func(b *testing.B) { + b.ResetTimer() + for n := 0; n < b.N; n++ { + _, err := s.FromData(ctx, false, data) + if err != nil { + b.Fatal(err) + } + } + }) + } +} diff --git a/pkg/detectors/grafanaserviceaccount/grafanaserviceaccount.go b/pkg/detectors/grafanaserviceaccount/grafanaserviceaccount.go new file mode 100644 index 000000000000..0d002fb409ea --- /dev/null +++ b/pkg/detectors/grafanaserviceaccount/grafanaserviceaccount.go @@ -0,0 +1,99 @@ +package grafanaserviceaccount + +import ( + "context" + "fmt" + "net/http" + "regexp" + "strings" + + "github.com/trufflesecurity/trufflehog/v3/pkg/common" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" + "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" +) + +type Scanner struct { + client *http.Client +} + +// Ensure the Scanner satisfies the interface at compile time. +var _ detectors.Detector = (*Scanner)(nil) + +var ( + defaultClient = common.SaneHttpClient() + // Make sure that your group is surrounded in boundary characters such as below to reduce false positives. + keyPat = regexp.MustCompile(`\b(glsa_[0-9a-zA-Z_]{41})\b`) + domainPat = regexp.MustCompile(`\b([a-zA-Z0-9-]+\.grafana\.net)\b`) +) + +// Keywords are used for efficiently pre-filtering chunks. +// Use identifiers in the secret preferably, or the provider name. +func (s Scanner) Keywords() []string { + return []string{"glsa_"} +} + +// FromData will find and optionally verify Grafanaserviceaccount secrets in a given set of bytes. +func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) { + dataStr := string(data) + + keyMatches := keyPat.FindAllStringSubmatch(dataStr, -1) + domainMatches := domainPat.FindAllStringSubmatch(dataStr, -1) + + for _, match := range keyMatches { + if len(match) != 2 { + continue + } + key := strings.TrimSpace(match[1]) + + for _, domainMatch := range domainMatches { + if len(domainMatch) != 2 { + continue + } + domainRes := strings.TrimSpace(domainMatch[1]) + + s1 := detectors.Result{ + DetectorType: detectorspb.DetectorType_GrafanaServiceAccount, + Raw: []byte(key), + RawV2: []byte(fmt.Sprintf("%s:%s", domainRes, key)), + } + + if verify { + client := s.client + if client == nil { + client = defaultClient + } + req, err := http.NewRequestWithContext(ctx, "GET", "https://"+domainRes+"/api/access-control/user/permissions", nil) + if err != nil { + continue + } + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", key)) + res, err := client.Do(req) + if err == nil { + defer res.Body.Close() + if res.StatusCode >= 200 && res.StatusCode < 300 { + s1.Verified = true + } else if res.StatusCode == 401 { + // The secret is determinately not verified (nothing to do) + } else { + s1.VerificationError = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode) + } + } else { + s1.VerificationError = err + } + } + + // This function will check false positives for common test words, but also it will make sure the key appears 'random' enough to be a real key. + if !s1.Verified && detectors.IsKnownFalsePositive(key, detectors.DefaultFalsePositives, true) { + continue + } + + results = append(results, s1) + } + } + + return results, nil +} + +func (s Scanner) Type() detectorspb.DetectorType { + return detectorspb.DetectorType_GrafanaServiceAccount +} diff --git a/pkg/detectors/grafanaserviceaccount/grafanaserviceaccount_test.go b/pkg/detectors/grafanaserviceaccount/grafanaserviceaccount_test.go new file mode 100644 index 000000000000..48818f58056d --- /dev/null +++ b/pkg/detectors/grafanaserviceaccount/grafanaserviceaccount_test.go @@ -0,0 +1,162 @@ +//go:build detectors +// +build detectors + +package grafanaserviceaccount + +import ( + "context" + "fmt" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "testing" + "time" + + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" + + "github.com/trufflesecurity/trufflehog/v3/pkg/common" + "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" +) + +func TestGrafanaServiceAccount_FromChunk(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5") + if err != nil { + t.Fatalf("could not get test secrets from GCP: %s", err) + } + secret := testSecrets.MustGetField("GRAFANASERVICEACCOUNT") + domain := testSecrets.MustGetField("GRAFANA_DOMAIN") + inactiveSecret := testSecrets.MustGetField("GRAFANASERVICEACCOUNT_INACTIVE") + + type args struct { + ctx context.Context + data []byte + verify bool + } + tests := []struct { + name string + s Scanner + args args + want []detectors.Result + wantErr bool + wantVerificationErr bool + }{ + { + name: "found, verified", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a grafanaserviceaccount secret %s within %s", secret, domain)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_GrafanaServiceAccount, + Verified: true, + }, + }, + wantErr: false, + wantVerificationErr: false, + }, + { + name: "found, unverified", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a grafanaserviceaccount secret %s within %s but not valid", inactiveSecret, domain)), // the secret would satisfy the regex but not pass validation + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_GrafanaServiceAccount, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: false, + }, + { + name: "not found", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte("You cannot find the secret within"), + verify: true, + }, + want: nil, + wantErr: false, + wantVerificationErr: false, + }, + { + name: "found, would be verified if not for timeout", + s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a grafanaserviceaccount secret %s within %s", secret, domain)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_GrafanaServiceAccount, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: true, + }, + { + name: "found, verified but unexpected api surface", + s: Scanner{client: common.ConstantResponseHttpClient(404, "")}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a grafanaserviceaccount secret %s within domain %s", secret, domain)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_GrafanaServiceAccount, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) + if (err != nil) != tt.wantErr { + t.Errorf("GrafanaServiceAccount.FromData() error = %v, wantErr %v", err, tt.wantErr) + return + } + for i := range got { + if len(got[i].Raw) == 0 { + t.Fatalf("no raw secret present: \n %+v", got[i]) + } + if (got[i].VerificationError != nil) != tt.wantVerificationErr { + t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError) + } + } + ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "VerificationError") + if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" { + t.Errorf("GrafanaServiceAccount.FromData() %s diff: (-got +want)\n%s", tt.name, diff) + } + }) + } +} + +func BenchmarkFromData(benchmark *testing.B) { + ctx := context.Background() + s := Scanner{} + for name, data := range detectors.MustGetBenchmarkData() { + benchmark.Run(name, func(b *testing.B) { + b.ResetTimer() + for n := 0; n < b.N; n++ { + _, err := s.FromData(ctx, false, data) + if err != nil { + b.Fatal(err) + } + } + }) + } +} diff --git a/pkg/detectors/helpscout/helpscout.go b/pkg/detectors/helpscout/helpscout.go index becc8caa9683..ed139de33597 100644 --- a/pkg/detectors/helpscout/helpscout.go +++ b/pkg/detectors/helpscout/helpscout.go @@ -2,7 +2,6 @@ package helpscout import ( "context" - "fmt" "net/http" "regexp" "strings" @@ -21,7 +20,7 @@ var ( client = common.SaneHttpClient() // Make sure that your group is surrounded in boundary characters such as below to reduce false positives. - keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"helpscout"}) + `\b([A-Za-z0-9]{56})\b`) + keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"helpscout"}) + `\b([a-z0-9]{40})\b`) ) // Keywords are used for efficiently pre-filtering chunks. @@ -52,7 +51,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result if err != nil { continue } - req.Header.Add("Authorization", fmt.Sprintf("Basic %s", resMatch)) + req.SetBasicAuth(resMatch, "X") res, err := client.Do(req) if err == nil { defer res.Body.Close() diff --git a/pkg/detectors/jdbc/postgres.go b/pkg/detectors/jdbc/postgres.go index a79efb081cb5..66e86b60d12b 100644 --- a/pkg/detectors/jdbc/postgres.go +++ b/pkg/detectors/jdbc/postgres.go @@ -4,8 +4,9 @@ import ( "context" "errors" "fmt" - "github.com/lib/pq" "strings" + + "github.com/lib/pq" ) type postgresJDBC struct { @@ -57,18 +58,34 @@ func joinKeyValues(m map[string]string, sep string) string { } func parsePostgres(subname string) (jdbc, error) { - // expected form: //HOST/DB?key=value&key=value + // expected form: [subprotocol:]//[user:password@]HOST[/DB][?key=val[&key=val]] hostAndDB, paramString, _ := strings.Cut(subname, "?") if !strings.HasPrefix(hostAndDB, "//") { return nil, errors.New("expected host to start with //") } - hostAndDB = strings.TrimPrefix(hostAndDB, "//") - host, database, _ := strings.Cut(hostAndDB, "/") + userPassAndHostAndDB := strings.TrimPrefix(hostAndDB, "//") + userPass, hostAndDB, found := strings.Cut(userPassAndHostAndDB, "@") + var user, pass string + if found { + user, pass, _ = strings.Cut(userPass, ":") + } else { + hostAndDB = userPass + } + host, database, found := strings.Cut(hostAndDB, "/") + if !found { + return nil, errors.New("expected host and database to be separated by /") + } params := map[string]string{ "host": host, "dbname": database, } + if len(user) > 0 { + params["user"] = user + } + if len(pass) > 0 { + params["password"] = pass + } for _, param := range strings.Split(paramString, "&") { key, val, _ := strings.Cut(param, "=") params[key] = val diff --git a/pkg/detectors/jdbc/postgres_integration_test.go b/pkg/detectors/jdbc/postgres_integration_test.go index e667a0aa8a96..4c80ff88cdc3 100644 --- a/pkg/detectors/jdbc/postgres_integration_test.go +++ b/pkg/detectors/jdbc/postgres_integration_test.go @@ -7,6 +7,7 @@ import ( "bytes" "context" "errors" + "fmt" "os/exec" "testing" "time" @@ -33,10 +34,18 @@ func TestPostgres(t *testing.T) { input: "//localhost:5432/foo?sslmode=disable&password=" + postgresPass, want: result{pingOk: true, pingDeterminate: true}, }, + { + input: fmt.Sprintf("//postgres:%s@localhost:5432/foo?sslmode=disable", postgresPass), + want: result{pingOk: true, pingDeterminate: true}, + }, { input: "//localhost:5432/foo?sslmode=disable&user=" + postgresUser + "&password=" + postgresPass, want: result{pingOk: true, pingDeterminate: true}, }, + { + input: fmt.Sprintf("//%s:%s@localhost:5432/foo?sslmode=disable", postgresUser, postgresPass), + want: result{pingOk: true, pingDeterminate: true}, + }, { input: "//localhost/foo?sslmode=disable&port=5432&password=" + postgresPass, want: result{pingOk: true, pingDeterminate: true}, diff --git a/pkg/detectors/liveagent/liveagent.go b/pkg/detectors/liveagent/liveagent.go index 3571a44ec3ad..9dfc04df859d 100644 --- a/pkg/detectors/liveagent/liveagent.go +++ b/pkg/detectors/liveagent/liveagent.go @@ -2,6 +2,7 @@ package liveagent import ( "context" + "encoding/json" "net/http" "regexp" "strings" @@ -20,53 +21,80 @@ var ( client = common.SaneHttpClient() // Make sure that your group is surrounded in boundary characters such as below to reduce false positives. - keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"liveagent"}) + `\b([a-zA-Z0-9]{32})\b`) + domainPat = regexp.MustCompile(`\b(https?://[A-Za-z0-9-]+\.ladesk\.com)\b`) + keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"liveagent", "apikey"}) + `\b([a-zA-Z0-9]{32})\b`) ) // Keywords are used for efficiently pre-filtering chunks. // Use identifiers in the secret preferably, or the provider name. func (s Scanner) Keywords() []string { - return []string{"liveagent"} + return []string{"liveagent", "ladesk"} +} + +type response struct { + Message string `json:"message"` } // FromData will find and optionally verify LiveAgent secrets in a given set of bytes. func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) { dataStr := string(data) + domainMatches := domainPat.FindAllStringSubmatch(dataStr, -1) matches := keyPat.FindAllStringSubmatch(dataStr, -1) for _, match := range matches { if len(match) != 2 { continue } - resMatch := strings.TrimSpace(match[1]) - s1 := detectors.Result{ - DetectorType: detectorspb.DetectorType_LiveAgent, - Raw: []byte(resMatch), - } + resMatch := strings.TrimSpace(match[1]) - if verify { - req, err := http.NewRequestWithContext(ctx, "GET", "https://secretscanner.ladesk.com/api/v3/agents", nil) - if err != nil { + for _, domainMatch := range domainMatches { + if len(domainMatch) != 2 { continue } - req.Header.Add("apikey", resMatch) - res, err := client.Do(req) - if err == nil { - defer res.Body.Close() - if res.StatusCode >= 200 && res.StatusCode < 300 { - s1.Verified = true - } else { - // This function will check false positives for common test words, but also it will make sure the key appears 'random' enough to be a real key. - if detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) { - continue + domainRes := strings.TrimSpace(domainMatch[0]) + s1 := detectors.Result{ + DetectorType: detectorspb.DetectorType_LiveAgent, + Raw: []byte(resMatch), + ExtraData: map[string]string{ + "domain": domainRes, + }, + } + + if verify { + req, err := http.NewRequestWithContext(ctx, "GET", domainRes+"/api/v3/agents", nil) + if err != nil { + continue + } + req.Header.Add("apikey", resMatch) + res, err := client.Do(req) + if err == nil { + defer res.Body.Close() + if res.StatusCode >= 200 && res.StatusCode < 300 { + s1.Verified = true + } else if res.StatusCode == 403 { + var r response + if err := json.NewDecoder(res.Body).Decode(&r); err != nil { + s1.VerificationError = err + continue + } + + // If the message is "You do not have sufficient privileges", then the key is valid, but does not have access to the `/agents` endpoint. + if r.Message == "You do not have sufficient privileges" { + s1.Verified = true + } + } else { + // This function will check false positives for common test words, but also it will make sure the key appears 'random' enough to be a real key. + if detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) { + continue + } } } } - } - results = append(results, s1) + results = append(results, s1) + } } return results, nil diff --git a/pkg/detectors/liveagent/liveagent_test.go b/pkg/detectors/liveagent/liveagent_test.go index a711a520b15c..083387f18100 100644 --- a/pkg/detectors/liveagent/liveagent_test.go +++ b/pkg/detectors/liveagent/liveagent_test.go @@ -6,6 +6,7 @@ package liveagent import ( "context" "fmt" + "net/url" "testing" "time" @@ -23,8 +24,14 @@ func TestLiveAgent_FromChunk(t *testing.T) { if err != nil { t.Fatalf("could not get test secrets from GCP: %s", err) } + deskUrl := testSecrets.MustGetField("LIVEAGENT_URL") secret := testSecrets.MustGetField("LIVEAGENT_TOKEN") inactiveSecret := testSecrets.MustGetField("LIVEAGENT_INACTIVE") + u, err := url.Parse(deskUrl) + if err != nil { + t.Fatalf("could not parse LIVEAGENT_URL: %s", err) + } + wantUrl := u.Scheme + "://" + u.Hostname() type args struct { ctx context.Context @@ -43,13 +50,16 @@ func TestLiveAgent_FromChunk(t *testing.T) { s: Scanner{}, args: args{ ctx: context.Background(), - data: []byte(fmt.Sprintf("You can find a liveagent secret %s within", secret)), + data: []byte(fmt.Sprintf("You can find a liveagent secret %s within for %s", secret, deskUrl)), verify: true, }, want: []detectors.Result{ { DetectorType: detectorspb.DetectorType_LiveAgent, Verified: true, + ExtraData: map[string]string{ + "domain": wantUrl, + }, }, }, wantErr: false, @@ -59,13 +69,16 @@ func TestLiveAgent_FromChunk(t *testing.T) { s: Scanner{}, args: args{ ctx: context.Background(), - data: []byte(fmt.Sprintf("You can find a liveagent secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation + data: []byte(fmt.Sprintf("You can find a liveagent secret %s within but not valid for %s", inactiveSecret, deskUrl)), // the secret would satisfy the regex but not pass validation verify: true, }, want: []detectors.Result{ { DetectorType: detectorspb.DetectorType_LiveAgent, Verified: false, + ExtraData: map[string]string{ + "domain": wantUrl, + }, }, }, wantErr: false, diff --git a/pkg/detectors/logzio/logzio.go b/pkg/detectors/logzio/logzio.go new file mode 100644 index 000000000000..572fe52697cd --- /dev/null +++ b/pkg/detectors/logzio/logzio.go @@ -0,0 +1,90 @@ +package logzio + +import ( + "context" + "fmt" + "net/http" + "regexp" + "strings" + + "github.com/trufflesecurity/trufflehog/v3/pkg/common" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" + "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" +) + +type Scanner struct { + client *http.Client +} + +// Ensure the Scanner satisfies the interface at compile time. +var _ detectors.Detector = (*Scanner)(nil) + +var ( + defaultClient = common.SaneHttpClient() + // Make sure that your group is surrounded in boundary characters such as below to reduce false positives. + keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"logz"}) + `\b([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})\b`) +) + +// Keywords are used for efficiently pre-filtering chunks. +// Use identifiers in the secret preferably, or the provider name. +func (s Scanner) Keywords() []string { + return []string{"logz"} +} + +// FromData will find and optionally verify Logzio secrets in a given set of bytes. +func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) { + dataStr := string(data) + + matches := keyPat.FindAllStringSubmatch(dataStr, -1) + + for _, match := range matches { + if len(match) != 2 { + continue + } + resMatch := strings.TrimSpace(match[1]) + + s1 := detectors.Result{ + DetectorType: detectorspb.DetectorType_LogzIO, + Raw: []byte(resMatch), + } + + if verify { + client := s.client + if client == nil { + client = defaultClient + } + req, err := http.NewRequestWithContext(ctx, "GET", "https://api.logz.io/v2/whoami", nil) + if err != nil { + continue + } + req.Header.Add("X-API-TOKEN", resMatch) + req.Header.Add("Content-Type", "application/json; charset=utf-8") + res, err := client.Do(req) + if err == nil { + defer res.Body.Close() + if res.StatusCode >= 200 && res.StatusCode < 300 { + s1.Verified = true + } else if res.StatusCode == 401 { + // The secret is determinately not verified (nothing to do) + } else { + s1.VerificationError = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode) + } + } else { + s1.VerificationError = err + } + } + + // This function will check false positives for common test words, but also it will make sure the key appears 'random' enough to be a real key. + if !s1.Verified && detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) { + continue + } + + results = append(results, s1) + } + + return results, nil +} + +func (s Scanner) Type() detectorspb.DetectorType { + return detectorspb.DetectorType_LogzIO +} diff --git a/pkg/detectors/logzio/logzio_test.go b/pkg/detectors/logzio/logzio_test.go new file mode 100644 index 000000000000..d754d7f07d6b --- /dev/null +++ b/pkg/detectors/logzio/logzio_test.go @@ -0,0 +1,161 @@ +//go:build detectors +// +build detectors + +package logzio + +import ( + "context" + "fmt" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "testing" + "time" + + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" + + "github.com/trufflesecurity/trufflehog/v3/pkg/common" + "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" +) + +func TestLogzIO_FromChunk(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5") + if err != nil { + t.Fatalf("could not get test secrets from GCP: %s", err) + } + secret := testSecrets.MustGetField("LOGZIO") + inactiveSecret := testSecrets.MustGetField("LOGZIO_INACTIVE") + + type args struct { + ctx context.Context + data []byte + verify bool + } + tests := []struct { + name string + s Scanner + args args + want []detectors.Result + wantErr bool + wantVerificationErr bool + }{ + { + name: "found, verified", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a logzio secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_LogzIO, + Verified: true, + }, + }, + wantErr: false, + wantVerificationErr: false, + }, + { + name: "found, unverified", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a logzio secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_LogzIO, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: false, + }, + { + name: "not found", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte("You cannot find the secret within"), + verify: true, + }, + want: nil, + wantErr: false, + wantVerificationErr: false, + }, + { + name: "found, would be verified if not for timeout", + s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a logzio secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_LogzIO, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: true, + }, + { + name: "found, verified but unexpected api surface", + s: Scanner{client: common.ConstantResponseHttpClient(404, "")}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a logzio secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_LogzIO, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) + if (err != nil) != tt.wantErr { + t.Errorf("LogzIO.FromData() error = %v, wantErr %v", err, tt.wantErr) + return + } + for i := range got { + if len(got[i].Raw) == 0 { + t.Fatalf("no raw secret present: \n %+v", got[i]) + } + if (got[i].VerificationError != nil) != tt.wantVerificationErr { + t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError) + } + } + ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "VerificationError") + if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" { + t.Errorf("LogzIO.FromData() %s diff: (-got +want)\n%s", tt.name, diff) + } + }) + } +} + +func BenchmarkFromData(benchmark *testing.B) { + ctx := context.Background() + s := Scanner{} + for name, data := range detectors.MustGetBenchmarkData() { + benchmark.Run(name, func(b *testing.B) { + b.ResetTimer() + for n := 0; n < b.N; n++ { + _, err := s.FromData(ctx, false, data) + if err != nil { + b.Fatal(err) + } + } + }) + } +} diff --git a/pkg/detectors/ngrok/ngrok.go b/pkg/detectors/ngrok/ngrok.go new file mode 100644 index 000000000000..eb599f3e033b --- /dev/null +++ b/pkg/detectors/ngrok/ngrok.go @@ -0,0 +1,87 @@ +package ngrok + +import ( + "context" + "fmt" + "net/http" + "regexp" + "strings" + + "github.com/trufflesecurity/trufflehog/v3/pkg/common" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" + "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" +) + +type Scanner struct { + client *http.Client +} + + +var _ detectors.Detector = (*Scanner)(nil) + +var ( + defaultClient = common.SaneHttpClient() + + keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"ngrok"}) + `\b2[a-zA-Z0-9]{26}_\d[a-zA-Z0-9]{20}\b`) +) + +func (s Scanner) Keywords() []string { + return []string{"ngrok"} +} + +func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) { + dataStr := string(data) + + matches := keyPat.FindAllStringSubmatch(dataStr, -1) + + for _, match := range matches { + if len(match) != 2 { + continue + } + resMatch := strings.TrimSpace(match[1]) + + s1 := detectors.Result{ + DetectorType: detectorspb.DetectorType_Ngrok, + Raw: []byte(resMatch), + } + + if verify { + client := s.client + if client == nil { + client = defaultClient + } + req, err := http.NewRequestWithContext(ctx, "GET", "https://api.ngrok.com/agent_ingresses", nil) + if err != nil { + continue + } + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch)) + req.Header.Add("ngrok-version", "2") + res, err := client.Do(req) + if err == nil { + defer res.Body.Close() + if res.StatusCode >= 200 && res.StatusCode < 300 { + s1.Verified = true + } else if res.StatusCode == 401 { + // The secret is determinately not verified (nothing to do) + } else { + s1.VerificationError = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode) + } + } else { + s1.VerificationError = err + } + } + + // This function will check false positives for common test words, but also it will make sure the key appears 'random' enough to be a real key. + if !s1.Verified && detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) { + continue + } + + results = append(results, s1) + } + + return results, nil +} + +func (s Scanner) Type() detectorspb.DetectorType { + return detectorspb.DetectorType_Ngrok +} diff --git a/pkg/detectors/passbase/passbase_test.go b/pkg/detectors/ngrok/ngrok_test.go similarity index 53% rename from pkg/detectors/passbase/passbase_test.go rename to pkg/detectors/ngrok/ngrok_test.go index 8821bf7435fd..9e6faebb613f 100644 --- a/pkg/detectors/passbase/passbase_test.go +++ b/pkg/detectors/ngrok/ngrok_test.go @@ -1,7 +1,7 @@ //go:build detectors // +build detectors -package passbase +package ngrok import ( "context" @@ -9,22 +9,24 @@ import ( "testing" "time" - "github.com/kylelemons/godebug/pretty" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" "github.com/trufflesecurity/trufflehog/v3/pkg/common" "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" ) -func TestPassbase_FromChunk(t *testing.T) { +func TestNgrok_FromChunk(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() - testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2") + testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5") if err != nil { t.Fatalf("could not get test secrets from GCP: %s", err) } - secret := testSecrets.MustGetField("PASSBASE") - inactiveSecret := testSecrets.MustGetField("PASSBASE_INACTIVE") + secret := testSecrets.MustGetField("NGROK") + inactiveSecret := testSecrets.MustGetField("NGROK_INACTIVE") type args struct { ctx context.Context @@ -32,43 +34,46 @@ func TestPassbase_FromChunk(t *testing.T) { verify bool } tests := []struct { - name string - s Scanner - args args - want []detectors.Result - wantErr bool + name string + s Scanner + args args + want []detectors.Result + wantErr bool + wantVerificationErr bool }{ { name: "found, verified", s: Scanner{}, args: args{ ctx: context.Background(), - data: []byte(fmt.Sprintf("You can find a passbase secret %s within", secret)), + data: []byte(fmt.Sprintf("You can find a ngrok secret %s within", secret)), verify: true, }, want: []detectors.Result{ { - DetectorType: detectorspb.DetectorType_Passbase, + DetectorType: detectorspb.DetectorType_Ngrok, Verified: true, }, }, - wantErr: false, + wantErr: false, + wantVerificationErr: false, }, { name: "found, unverified", s: Scanner{}, args: args{ ctx: context.Background(), - data: []byte(fmt.Sprintf("You can find a passbase secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation + data: []byte(fmt.Sprintf("You can find a ngrok secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation verify: true, }, want: []detectors.Result{ { - DetectorType: detectorspb.DetectorType_Passbase, + DetectorType: detectorspb.DetectorType_Ngrok, Verified: false, }, }, - wantErr: false, + wantErr: false, + wantVerificationErr: true, }, { name: "not found", @@ -78,26 +83,29 @@ func TestPassbase_FromChunk(t *testing.T) { data: []byte("You cannot find the secret within"), verify: true, }, - want: nil, - wantErr: false, + want: nil, + wantErr: false, + wantVerificationErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := Scanner{} - got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) + got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) if (err != nil) != tt.wantErr { - t.Errorf("Passbase.FromData() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("Ngrok.FromData() error = %v, wantErr %v", err, tt.wantErr) return } for i := range got { if len(got[i].Raw) == 0 { t.Fatalf("no raw secret present: \n %+v", got[i]) } - got[i].Raw = nil + if (got[i].VerificationError != nil) != tt.wantVerificationErr { + t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError) + } } - if diff := pretty.Compare(got, tt.want); diff != "" { - t.Errorf("Passbase.FromData() %s diff: (-got +want)\n%s", tt.name, diff) + ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "VerificationError") + if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" { + t.Errorf("Ngrok.FromData() %s diff: (-got +want)\n%s", tt.name, diff) } }) } diff --git a/pkg/detectors/passbase/passbase.go b/pkg/detectors/overloop/overloop.go similarity index 57% rename from pkg/detectors/passbase/passbase.go rename to pkg/detectors/overloop/overloop.go index 2cf797ce5598..2b39fc38f87c 100644 --- a/pkg/detectors/passbase/passbase.go +++ b/pkg/detectors/overloop/overloop.go @@ -1,7 +1,8 @@ -package passbase +package overloop import ( "context" + "fmt" "net/http" "regexp" "strings" @@ -11,25 +12,26 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" ) -type Scanner struct{} +type Scanner struct { + client *http.Client +} // Ensure the Scanner satisfies the interface at compile time. var _ detectors.Detector = (*Scanner)(nil) var ( - client = common.SaneHttpClient() - + defaultClient = common.SaneHttpClient() // Make sure that your group is surrounded in boundary characters such as below to reduce false positives. - keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"passbase"}) + `\b([a-zA-Z0-9]{128})\b`) + keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"overloop"}) + `\b([a-zA-Z\_\-0-9]{50})\b`) ) // Keywords are used for efficiently pre-filtering chunks. // Use identifiers in the secret preferably, or the provider name. func (s Scanner) Keywords() []string { - return []string{"passbase"} + return []string{"overloop"} } -// FromData will find and optionally verify Passbase secrets in a given set of bytes. +// FromData will find and optionally verify Overloop secrets in a given set of bytes. func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) { dataStr := string(data) @@ -42,30 +44,40 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result resMatch := strings.TrimSpace(match[1]) s1 := detectors.Result{ - DetectorType: detectorspb.DetectorType_Passbase, + DetectorType: detectorspb.DetectorType_Overloop, Raw: []byte(resMatch), } if verify { - req, err := http.NewRequestWithContext(ctx, "GET", "https://api.passbase.com/verification/v1/settings", nil) + client := s.client + if client == nil { + client = defaultClient + } + req, err := http.NewRequestWithContext(ctx, "GET", "https://api.overloop.com/public/v1/users", nil) if err != nil { continue } - req.Header.Add("X-API-KEY", resMatch) + req.Header.Set("Authorization", resMatch) res, err := client.Do(req) if err == nil { defer res.Body.Close() if res.StatusCode >= 200 && res.StatusCode < 300 { s1.Verified = true + } else if res.StatusCode == 401 { + // The secret is determinately not verified (nothing to do) } else { - // This function will check false positives for common test words, but also it will make sure the key appears 'random' enough to be a real key. - if detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) { - continue - } + s1.VerificationError = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode) } + } else { + s1.VerificationError = err } } + // This function will check false positives for common test words, but also it will make sure the key appears 'random' enough to be a real key. + if !s1.Verified && detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) { + continue + } + results = append(results, s1) } @@ -73,5 +85,5 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result } func (s Scanner) Type() detectorspb.DetectorType { - return detectorspb.DetectorType_Passbase + return detectorspb.DetectorType_Overloop } diff --git a/pkg/detectors/overloop/overloop_test.go b/pkg/detectors/overloop/overloop_test.go new file mode 100644 index 000000000000..e4b2b38939a5 --- /dev/null +++ b/pkg/detectors/overloop/overloop_test.go @@ -0,0 +1,161 @@ +//go:build detectors +// +build detectors + +package overloop + +import ( + "context" + "fmt" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "testing" + "time" + + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" + + "github.com/trufflesecurity/trufflehog/v3/pkg/common" + "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" +) + +func TestOverloop_FromChunk(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5") + if err != nil { + t.Fatalf("could not get test secrets from GCP: %s", err) + } + secret := testSecrets.MustGetField("OVERLOOP") + inactiveSecret := testSecrets.MustGetField("OVERLOOP_INACTIVE") + + type args struct { + ctx context.Context + data []byte + verify bool + } + tests := []struct { + name string + s Scanner + args args + want []detectors.Result + wantErr bool + wantVerificationErr bool + }{ + { + name: "found, verified", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a overloop secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_Overloop, + Verified: true, + }, + }, + wantErr: false, + wantVerificationErr: false, + }, + { + name: "found, unverified", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a overloop secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_Overloop, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: false, + }, + { + name: "not found", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte("You cannot find the secret within"), + verify: true, + }, + want: nil, + wantErr: false, + wantVerificationErr: false, + }, + { + name: "found, would be verified if not for timeout", + s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a overloop secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_Overloop, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: true, + }, + { + name: "found, verified but unexpected api surface", + s: Scanner{client: common.ConstantResponseHttpClient(404, "")}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a overloop secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_Overloop, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) + if (err != nil) != tt.wantErr { + t.Errorf("Overloop.FromData() error = %v, wantErr %v", err, tt.wantErr) + return + } + for i := range got { + if len(got[i].Raw) == 0 { + t.Fatalf("no raw secret present: \n %+v", got[i]) + } + if (got[i].VerificationError != nil) != tt.wantVerificationErr { + t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError) + } + } + ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "VerificationError") + if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" { + t.Errorf("Overloop.FromData() %s diff: (-got +want)\n%s", tt.name, diff) + } + }) + } +} + +func BenchmarkFromData(benchmark *testing.B) { + ctx := context.Background() + s := Scanner{} + for name, data := range detectors.MustGetBenchmarkData() { + benchmark.Run(name, func(b *testing.B) { + b.ResetTimer() + for n := 0; n < b.N; n++ { + _, err := s.FromData(ctx, false, data) + if err != nil { + b.Fatal(err) + } + } + }) + } +} diff --git a/pkg/detectors/pubnubpublishkey/pubnubpublishkey.go b/pkg/detectors/pubnubpublishkey/pubnubpublishkey.go index d0376a65ea09..f83296d940a1 100644 --- a/pkg/detectors/pubnubpublishkey/pubnubpublishkey.go +++ b/pkg/detectors/pubnubpublishkey/pubnubpublishkey.go @@ -55,6 +55,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result s1 := detectors.Result{ DetectorType: detectorspb.DetectorType_PubNubPublishKey, Raw: []byte(resMatch), + RawV2: []byte(resMatch + "/" + ressubMatch), } if verify { diff --git a/pkg/detectors/pubnubpublishkey/pubnubpublishkey_test.go b/pkg/detectors/pubnubpublishkey/pubnubpublishkey_test.go index a91b38a5fd53..7978ae490ce6 100644 --- a/pkg/detectors/pubnubpublishkey/pubnubpublishkey_test.go +++ b/pkg/detectors/pubnubpublishkey/pubnubpublishkey_test.go @@ -6,11 +6,12 @@ package pubnubpublishkey import ( "context" "fmt" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" "testing" "time" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" "github.com/trufflesecurity/trufflehog/v3/pkg/common" @@ -134,7 +135,7 @@ func TestPubNubPublishKey_FromChunk(t *testing.T) { t.Fatalf(" wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError) } } - opts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "VerificationError") + opts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "VerificationError") if diff := cmp.Diff(got, tt.want, opts); diff != "" { t.Errorf("PubNubPublishKey.FromData() %s diff: (-got +want)\n%s", tt.name, diff) } diff --git a/pkg/detectors/redis/redis.go b/pkg/detectors/redis/redis.go index 138f5306967f..5aa4efdd8b0e 100644 --- a/pkg/detectors/redis/redis.go +++ b/pkg/detectors/redis/redis.go @@ -2,6 +2,7 @@ package redis import ( "context" + "fmt" "net/url" "regexp" "strings" @@ -18,7 +19,8 @@ type Scanner struct{} var _ detectors.Detector = (*Scanner)(nil) var ( - keyPat = regexp.MustCompile(`\bredis://[\S]{3,50}:([\S]{3,50})@[-.%\w\/:]+\b`) + keyPat = regexp.MustCompile(`\bredi[s]{1,2}://[\S]{3,50}:([\S]{3,50})@[-.%\w\/:]+\b`) + azureRedisPat = regexp.MustCompile(`\b([\w\d.-]{1,100}\.redis\.cache\.windows\.net:6380),password=([^,]{44}),ssl=True,abortConnect=False\b`) ) // Keywords are used for efficiently pre-filtering chunks. @@ -32,6 +34,51 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result dataStr := string(data) matches := keyPat.FindAllStringSubmatch(dataStr, -1) + azureMatches := azureRedisPat.FindAllStringSubmatch(dataStr, -1) + + for _, match := range azureMatches { + host := match[1] + password := match[2] + urlMatch := fmt.Sprintf("rediss://:%s@%s", password, host) + + // Skip findings where the password only has "*" characters, this is a redacted password + if strings.Trim(password, "*") == "" { + continue + } + + parsedURL, err := url.Parse(urlMatch) + if err != nil { + continue + } + if _, ok := parsedURL.User.Password(); !ok { + continue + } + + redact := strings.TrimSpace(strings.Replace(urlMatch, password, "*******", -1)) + + s := detectors.Result{ + DetectorType: detectorspb.DetectorType_Redis, + Raw: []byte(urlMatch), + Redacted: redact, + } + + if verify { + s.Verified = verifyRedis(ctx, parsedURL) + } + + if !s.Verified { + // Skip unverified findings where the password starts with a `$` - it's almost certainly a variable. + if strings.HasPrefix(password, "$") { + continue + } + } + + if !s.Verified && detectors.IsKnownFalsePositive(string(s.Raw), detectors.DefaultFalsePositives, false) { + continue + } + + results = append(results, s) + } for _, match := range matches { urlMatch := match[0] @@ -50,7 +97,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result continue } - redact := strings.TrimSpace(strings.Replace(urlMatch, password, "********", -1)) + redact := strings.TrimSpace(strings.Replace(urlMatch, password, "*******", -1)) s := detectors.Result{ DetectorType: detectorspb.DetectorType_Redis, diff --git a/pkg/detectors/redis/redis_test.go b/pkg/detectors/redis/redis_test.go index d83db2caf155..92b58dece5a1 100644 --- a/pkg/detectors/redis/redis_test.go +++ b/pkg/detectors/redis/redis_test.go @@ -61,9 +61,6 @@ func TestURI_FromChunk(t *testing.T) { t.Errorf("URI.FromData() error = %v, wantErr %v", err, tt.wantErr) return } - // if os.Getenv("FORCE_PASS_DIFF") == "true" { - // return - // } for i := range got { got[i].Raw = nil } diff --git a/pkg/detectors/requestfinance/requestfinance.go b/pkg/detectors/requestfinance/requestfinance.go new file mode 100644 index 000000000000..c55c64d2ec41 --- /dev/null +++ b/pkg/detectors/requestfinance/requestfinance.go @@ -0,0 +1,88 @@ +package requestfinance + +import ( + "context" + "fmt" + "net/http" + "regexp" + "strings" + + "github.com/trufflesecurity/trufflehog/v3/pkg/common" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" + "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" +) + +type Scanner struct { + client *http.Client +} + +// Ensure the Scanner satisfies the interface at compile time. +var _ detectors.Detector = (*Scanner)(nil) + +var ( + defaultClient = common.SaneHttpClient() + keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"requestfinance"}) + `\b([0-9A-Z]{7}-[0-9A-Z]{7}-[0-9A-Z]{7}-[0-9A-Z]{7})\b`) +) + +// Keywords are used for efficiently pre-filtering chunks. +// Use identifiers in the secret preferably, or the provider name. +func (s Scanner) Keywords() []string { + return []string{"requestfinance"} +} + +// FromData will find and optionally verify Requestfinance secrets in a given set of bytes. +func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) { + dataStr := string(data) + + matches := keyPat.FindAllStringSubmatch(dataStr, -1) + + for _, match := range matches { + if len(match) != 2 { + continue + } + resMatch := strings.TrimSpace(match[1]) + + s1 := detectors.Result{ + DetectorType: detectorspb.DetectorType_RequestFinance, + Raw: []byte(resMatch), + } + + if verify { + client := s.client + if client == nil { + client = defaultClient + } + req, err := http.NewRequestWithContext(ctx, "GET", "https://api.request.finance/invoices", nil) + if err != nil { + continue + } + req.Header.Add("Authorization", resMatch) + res, err := client.Do(req) + if err == nil { + defer res.Body.Close() + if res.StatusCode >= 200 && res.StatusCode < 300 { + s1.Verified = true + } else if res.StatusCode == 401 { + // The secret is determinately not verified (nothing to do) + } else { + s1.VerificationError = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode) + } + } else { + s1.VerificationError = err + } + } + + // This function will check false positives for common test words, but also it will make sure the key appears 'random' enough to be a real key. + if !s1.Verified && detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) { + continue + } + + results = append(results, s1) + } + + return results, nil +} + +func (s Scanner) Type() detectorspb.DetectorType { + return detectorspb.DetectorType_RequestFinance +} diff --git a/pkg/detectors/sentiment/sentiment_test.go b/pkg/detectors/requestfinance/requestfinance_test.go similarity index 52% rename from pkg/detectors/sentiment/sentiment_test.go rename to pkg/detectors/requestfinance/requestfinance_test.go index 1859fecdc63a..3c32fca4d2a4 100644 --- a/pkg/detectors/sentiment/sentiment_test.go +++ b/pkg/detectors/requestfinance/requestfinance_test.go @@ -1,7 +1,7 @@ //go:build detectors // +build detectors -package sentiment +package requestfinance import ( "context" @@ -9,24 +9,24 @@ import ( "testing" "time" - "github.com/kylelemons/godebug/pretty" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" "github.com/trufflesecurity/trufflehog/v3/pkg/common" "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" ) -func TestSentiment_FromChunk(t *testing.T) { +func TestRequestfinance_FromChunk(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() - testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2") + testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5") if err != nil { t.Fatalf("could not get test secrets from GCP: %s", err) } - token := testSecrets.MustGetField("SENTIMENT_TOKEN") - inactiveToken := testSecrets.MustGetField("SENTIMENT_TOKEN_INACTIVE") - key := testSecrets.MustGetField("SENTIMENT_KEY") - inactiveKey := testSecrets.MustGetField("SENTIMENT_KEY_INACTIVE") + secret := testSecrets.MustGetField("REQUESTFINANCE") + inactiveSecret := testSecrets.MustGetField("REQUESTFINANCE_INACTIVE") type args struct { ctx context.Context @@ -34,43 +34,46 @@ func TestSentiment_FromChunk(t *testing.T) { verify bool } tests := []struct { - name string - s Scanner - args args - want []detectors.Result - wantErr bool + name string + s Scanner + args args + want []detectors.Result + wantErr bool + wantVerificationErr bool }{ { name: "found, verified", s: Scanner{}, args: args{ ctx: context.Background(), - data: []byte(fmt.Sprintf("You can find a sentiment key %s with sentiment user token %s within", key, token)), + data: []byte(fmt.Sprintf("You can find a requestfinance secret %s within", secret)), verify: true, }, want: []detectors.Result{ { - DetectorType: detectorspb.DetectorType_Sentiment, + DetectorType: detectorspb.DetectorType_RequestFinance, Verified: true, }, }, - wantErr: false, + wantErr: false, + wantVerificationErr: false, }, { name: "found, unverified", s: Scanner{}, args: args{ ctx: context.Background(), - data: []byte(fmt.Sprintf("You can find a sentiment key %s with sentiment user token %s within but not valid", inactiveKey, inactiveToken)), // the secret would satisfy the regex but not pass validation + data: []byte(fmt.Sprintf("You can find a requestfinance secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation verify: true, }, want: []detectors.Result{ { - DetectorType: detectorspb.DetectorType_Sentiment, + DetectorType: detectorspb.DetectorType_RequestFinance, Verified: false, }, }, - wantErr: false, + wantErr: false, + wantVerificationErr: false, }, { name: "not found", @@ -80,26 +83,29 @@ func TestSentiment_FromChunk(t *testing.T) { data: []byte("You cannot find the secret within"), verify: true, }, - want: nil, - wantErr: false, + want: nil, + wantErr: false, + wantVerificationErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := Scanner{} - got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) + got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) if (err != nil) != tt.wantErr { - t.Errorf("Sentiment.FromData() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("Requestfinance.FromData() error = %v, wantErr %v", err, tt.wantErr) return } for i := range got { if len(got[i].Raw) == 0 { t.Fatalf("no raw secret present: \n %+v", got[i]) } - got[i].Raw = nil + if (got[i].VerificationError != nil) != tt.wantVerificationErr { + t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError) + } } - if diff := pretty.Compare(got, tt.want); diff != "" { - t.Errorf("Sentiment.FromData() %s diff: (-got +want)\n%s", tt.name, diff) + ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "VerificationError") + if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" { + t.Errorf("Requestfinance.FromData() %s diff: (-got +want)\n%s", tt.name, diff) } }) } diff --git a/pkg/detectors/scrapersite/scrapersite_test.go b/pkg/detectors/scrapersite/scrapersite_test.go deleted file mode 100644 index 187d85e06812..000000000000 --- a/pkg/detectors/scrapersite/scrapersite_test.go +++ /dev/null @@ -1,120 +0,0 @@ -//go:build detectors -// +build detectors - -package scrapersite - -import ( - "context" - "fmt" - "testing" - "time" - - "github.com/kylelemons/godebug/pretty" - "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" - - "github.com/trufflesecurity/trufflehog/v3/pkg/common" - "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" -) - -func TestScraperSite_FromChunk(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - defer cancel() - testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2") - if err != nil { - t.Fatalf("could not get test secrets from GCP: %s", err) - } - secret := testSecrets.MustGetField("SCRAPERSITE") - inactiveSecret := testSecrets.MustGetField("SCRAPERSITE_INACTIVE") - - type args struct { - ctx context.Context - data []byte - verify bool - } - tests := []struct { - name string - s Scanner - args args - want []detectors.Result - wantErr bool - }{ - { - name: "found, verified", - s: Scanner{}, - args: args{ - ctx: context.Background(), - data: []byte(fmt.Sprintf("You can find a scrapersite secret %s within", secret)), - verify: true, - }, - want: []detectors.Result{ - { - DetectorType: detectorspb.DetectorType_ScraperSite, - Verified: true, - }, - }, - wantErr: false, - }, - { - name: "found, unverified", - s: Scanner{}, - args: args{ - ctx: context.Background(), - data: []byte(fmt.Sprintf("You can find a scrapersite secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation - verify: true, - }, - want: []detectors.Result{ - { - DetectorType: detectorspb.DetectorType_ScraperSite, - Verified: false, - }, - }, - wantErr: false, - }, - { - name: "not found", - s: Scanner{}, - args: args{ - ctx: context.Background(), - data: []byte("You cannot find the secret within"), - verify: true, - }, - want: nil, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := Scanner{} - got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) - if (err != nil) != tt.wantErr { - t.Errorf("ScraperSite.FromData() error = %v, wantErr %v", err, tt.wantErr) - return - } - for i := range got { - if len(got[i].Raw) == 0 { - t.Fatalf("no raw secret present: \n %+v", got[i]) - } - got[i].Raw = nil - } - if diff := pretty.Compare(got, tt.want); diff != "" { - t.Errorf("ScraperSite.FromData() %s diff: (-got +want)\n%s", tt.name, diff) - } - }) - } -} - -func BenchmarkFromData(benchmark *testing.B) { - ctx := context.Background() - s := Scanner{} - for name, data := range detectors.MustGetBenchmarkData() { - benchmark.Run(name, func(b *testing.B) { - b.ResetTimer() - for n := 0; n < b.N; n++ { - _, err := s.FromData(ctx, false, data) - if err != nil { - b.Fatal(err) - } - } - }) - } -} diff --git a/pkg/detectors/sentiment/sentiment.go b/pkg/detectors/sentiment/sentiment.go deleted file mode 100644 index df3b67571a6c..000000000000 --- a/pkg/detectors/sentiment/sentiment.go +++ /dev/null @@ -1,92 +0,0 @@ -package sentiment - -import ( - "context" - "net/http" - "regexp" - "strings" - - "github.com/trufflesecurity/trufflehog/v3/pkg/common" - "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" - "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" -) - -type Scanner struct{} - -// Ensure the Scanner satisfies the interface at compile time. -var _ detectors.Detector = (*Scanner)(nil) - -var ( - client = common.SaneHttpClient() - - // Make sure that your group is surrounded in boundary characters such as below to reduce false positives. - tokenPat = regexp.MustCompile(detectors.PrefixRegex([]string{"sentiment"}) + `\b([a-zA-Z0-9]{20})\b`) - keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"sentiment"}) + `\b([0-9]{17})\b`) -) - -// Keywords are used for efficiently pre-filtering chunks. -// Use identifiers in the secret preferably, or the provider name. -func (s Scanner) Keywords() []string { - return []string{"sentiment"} -} - -// FromData will find and optionally verify Sentiment secrets in a given set of bytes. -func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) { - dataStr := string(data) - - tokenMatches := tokenPat.FindAllStringSubmatch(dataStr, -1) - keyMatches := keyPat.FindAllStringSubmatch(dataStr, -1) - - for _, match := range tokenMatches { - if len(match) != 2 { - continue - } - - tokenMatch := strings.TrimSpace(match[1]) - - for _, secret := range keyMatches { - if len(secret) != 2 { - continue - } - - keyMatch := strings.TrimSpace(secret[1]) - - s1 := detectors.Result{ - DetectorType: detectorspb.DetectorType_Sentiment, - Raw: []byte(tokenMatch), - } - - if verify { - req, err := http.NewRequestWithContext(ctx, "GET", "https://api.sentimentinvestor.com/v4/parsed?symbol=AAPL&token="+tokenMatch+"&key="+keyMatch, nil) - if err != nil { - continue - } - - res, err := client.Do(req) - if err == nil { - defer res.Body.Close() - if res.StatusCode >= 200 && res.StatusCode < 300 { - s1.Verified = true - } else { - // This function will check false positives for common test words, but also it will make sure the key appears 'random' enough to be a real key. - if detectors.IsKnownFalsePositive(tokenMatch, detectors.DefaultFalsePositives, true) { - continue - } - - if detectors.IsKnownFalsePositive(keyMatch, detectors.DefaultFalsePositives, true) { - continue - } - } - } - } - - results = append(results, s1) - } - } - - return results, nil -} - -func (s Scanner) Type() detectorspb.DetectorType { - return detectorspb.DetectorType_Sentiment -} diff --git a/pkg/detectors/zulipchat/zulipchat.go b/pkg/detectors/zulipchat/zulipchat.go index 4af23d06428b..1164836c869a 100644 --- a/pkg/detectors/zulipchat/zulipchat.go +++ b/pkg/detectors/zulipchat/zulipchat.go @@ -12,24 +12,26 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" ) -type Scanner struct{} +type Scanner struct{ + client *http.Client +} // Ensure the Scanner satisfies the interface at compile time. var _ detectors.Detector = (*Scanner)(nil) var ( - client = common.SaneHttpClient() + defaultClient = common.SaneHttpClient() // Make sure that your group is surrounded in boundary characters such as below to reduce false positives. - keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"zulipchat"}) + common.BuildRegex(common.AlphaNumPattern, "", 32)) - idPat = regexp.MustCompile(detectors.PrefixRegex([]string{"zulipchat"}) + common.EmailPattern) - domainPat = regexp.MustCompile(detectors.PrefixRegex([]string{"zulipchat", "domain"}) + common.SubDomainPattern) + keyPat = regexp.MustCompile(common.BuildRegex(common.AlphaNumPattern, "", 32)) + idPat = regexp.MustCompile(`\b([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})\b`) + domainPat = regexp.MustCompile(`\b([a-z0-9-]+\.zulip(?:(?:chat)?\.com|\.org))\b`) ) // Keywords are used for efficiently pre-filtering chunks. // Use identifiers in the secret preferably, or the provider name. func (s Scanner) Keywords() []string { - return []string{"zulipchat"} + return []string{"zulip"} } // FromData will find and optionally verify ZulipChat secrets in a given set of bytes. @@ -48,7 +50,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result for _, idMatch := range idMatches { // getting the last word of the string - resIdMatch := strings.TrimSpace(idMatch[0][strings.LastIndex(idMatch[0], " ")+1:]) + resIdMatch := strings.TrimSpace(idMatch[1]) for _, domainMatch := range domainMatches { if len(domainMatch) != 2 { @@ -60,10 +62,15 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result s1 := detectors.Result{ DetectorType: detectorspb.DetectorType_ZulipChat, Raw: []byte(resMatch), + RawV2: []byte(fmt.Sprintf("%s:%s:%s",resMatch, resIdMatch, resDomainMatch)), } if verify { - req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://%s.zulipchat.com/api/v1/users", resDomainMatch), nil) + client := s.client + if client == nil { + client = defaultClient + } + req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://%s/api/v1/users", resDomainMatch), nil) if err != nil { continue } @@ -75,14 +82,18 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result defer res.Body.Close() if res.StatusCode >= 200 && res.StatusCode < 300 { s1.Verified = true + } else if res.StatusCode == 401 { + // This secret is determinately not verified, nothing to do here } else { - // This function will check false positives for common test words, but also it will make sure the key appears 'random' enough to be a real key. - if detectors.IsKnownFalsePositive(resIdMatch, detectors.DefaultFalsePositives, true) { - continue - } + s1.VerificationError = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode) } + } else { + s1.VerificationError = err } } + if !s1.Verified && detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) { + continue + } results = append(results, s1) } diff --git a/pkg/detectors/zulipchat/zulipchat_test.go b/pkg/detectors/zulipchat/zulipchat_test.go index 167919d8d3e8..c16eb28499d3 100644 --- a/pkg/detectors/zulipchat/zulipchat_test.go +++ b/pkg/detectors/zulipchat/zulipchat_test.go @@ -6,10 +6,11 @@ package zulipchat import ( "context" "fmt" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "testing" "time" - "github.com/kylelemons/godebug/pretty" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" "github.com/trufflesecurity/trufflehog/v3/pkg/common" @@ -24,8 +25,8 @@ func TestZulipChat_FromChunk(t *testing.T) { t.Fatalf("could not get test secrets from GCP: %s", err) } secret := testSecrets.MustGetField("ZULIPCHAT") - id := testSecrets.MustGetField("ZULIPCHAT_ID") domain := testSecrets.MustGetField("ZULIPCHAT_DOMAINV2") + id := testSecrets.MustGetField("ZULIPCHAT_ID") inactiveSecret := testSecrets.MustGetField("ZULIPCHAT_INACTIVE") type args struct { @@ -34,11 +35,12 @@ func TestZulipChat_FromChunk(t *testing.T) { verify bool } tests := []struct { - name string - s Scanner - args args - want []detectors.Result - wantErr bool + name string + s Scanner + args args + want []detectors.Result + wantErr bool + wantVerificationErr bool }{ { name: "found, verified", @@ -54,7 +56,8 @@ func TestZulipChat_FromChunk(t *testing.T) { Verified: true, }, }, - wantErr: false, + wantErr: false, + wantVerificationErr: false, }, { name: "found, unverified", @@ -70,7 +73,8 @@ func TestZulipChat_FromChunk(t *testing.T) { Verified: false, }, }, - wantErr: false, + wantErr: false, + wantVerificationErr: false, }, { name: "not found", @@ -80,26 +84,63 @@ func TestZulipChat_FromChunk(t *testing.T) { data: []byte("You cannot find the secret within"), verify: true, }, - want: nil, - wantErr: false, + want: nil, + wantErr: false, + wantVerificationErr: false, + }, + { + name: "found, would be verified if not for timeout", + s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a zulipchat secret %s within zulipchat %s and zulipchat %s", secret, id, domain)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_ZulipChat, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: true, + }, + { + name: "found, verified but unexpected api surface", + s: Scanner{client: common.ConstantResponseHttpClient(404, "")}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a zulipchat secret %s within zulipchat %s and zulipchat %s", secret, id, domain)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_ZulipChat, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := Scanner{} - got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) + got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) if (err != nil) != tt.wantErr { - t.Errorf("ZulipChat.FromData() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("Zulipchat.FromData() error = %v, wantErr %v", err, tt.wantErr) return } for i := range got { if len(got[i].Raw) == 0 { t.Fatalf("no raw secret present: \n %+v", got[i]) } - got[i].Raw = nil + if (got[i].VerificationError != nil) != tt.wantVerificationErr { + t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError) + } } - if diff := pretty.Compare(got, tt.want); diff != "" { - t.Errorf("ZulipChat.FromData() %s diff: (-got +want)\n%s", tt.name, diff) + ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "VerificationError") + if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" { + t.Errorf("Zulipchat.FromData() %s diff: (-got +want)\n%s", tt.name, diff) } }) } diff --git a/pkg/engine/ahocorasickcore.go b/pkg/engine/ahocorasickcore.go index b7d2ee7a8a28..19fda9d90f35 100644 --- a/pkg/engine/ahocorasickcore.go +++ b/pkg/engine/ahocorasickcore.go @@ -9,12 +9,14 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" ) -// detectorKey is used to identify a detector in the keywordsToDetectors map. +// DetectorKey is used to identify a detector in the keywordsToDetectors map. // Multiple detectors can have the same detector type but different versions. // This allows us to identify a detector by its type and version. An // additional (optional) field is provided to disambiguate multiple custom -// detectors. -type detectorKey struct { +// detectors. This type is exported even though none of its fields are so +// that the AhoCorasickCore can populate passed-in maps keyed on this type +// without exposing any of its internals to consumers. +type DetectorKey struct { detectorType detectorspb.DetectorType version int customDetectorName string @@ -32,16 +34,16 @@ type AhoCorasickCore struct { // type and then again from detector type to detector. We could // go straight from keywords to detectors but doing it this way makes // some consuming code a little cleaner.) - keywordsToDetectors map[string][]detectorKey - detectorsByKey map[detectorKey]detectors.Detector + keywordsToDetectors map[string][]DetectorKey + detectorsByKey map[DetectorKey]detectors.Detector } // NewAhoCorasickCore allocates and initializes a new instance of AhoCorasickCore. It uses the // provided detector slice to create a map from keywords to detectors and build the Aho-Corasick // prefilter trie. func NewAhoCorasickCore(allDetectors []detectors.Detector) *AhoCorasickCore { - keywordsToDetectors := make(map[string][]detectorKey) - detectorsByKey := make(map[detectorKey]detectors.Detector, len(allDetectors)) + keywordsToDetectors := make(map[string][]DetectorKey) + detectorsByKey := make(map[DetectorKey]detectors.Detector, len(allDetectors)) var keywords []string for _, d := range allDetectors { key := createDetectorKey(d) @@ -60,29 +62,20 @@ func NewAhoCorasickCore(allDetectors []detectors.Detector) *AhoCorasickCore { } } -// MatchString performs a string match using the Aho-Corasick algorithm, returning an array of matches. -// Designed for internal use within the AhoCorasickCore component. -func (ac *AhoCorasickCore) MatchString(input string) []*ahocorasick.Match { - return ac.prefilter.MatchString(strings.ToLower(input)) -} - -// PopulateDetectorsByMatch populates the given detectorMap based on the Aho-Corasick match results. -// This method is designed to reuse the same map for performance optimization, -// reducing the need for repeated allocations within each detector worker in the engine. -func (ac *AhoCorasickCore) PopulateDetectorsByMatch(match *ahocorasick.Match, detectors map[detectorspb.DetectorType]detectors.Detector) bool { - matchedDetectorKeys, ok := ac.keywordsToDetectors[match.MatchString()] - if !ok { - return false - } - for _, key := range matchedDetectorKeys { - detectors[key.detectorType] = ac.detectorsByKey[key] +// PopulateMatchingDetectors populates the given detector slice with all the detectors matching the +// provided input. This method populates an existing map rather than allocating a new one because +// it will be called once per chunk and that many allocations has a noticeable performance cost. +func (ac *AhoCorasickCore) PopulateMatchingDetectors(chunkData string, detectors map[DetectorKey]detectors.Detector) { + for _, m := range ac.prefilter.MatchString(strings.ToLower(chunkData)) { + for _, k := range ac.keywordsToDetectors[m.MatchString()] { + detectors[k] = ac.detectorsByKey[k] + } } - return true } -// createDetectorKey creates a unique key for each detector. This key based on type and version, -// it ensures faster lookups and reduces redundancy in our main detector store. -func createDetectorKey(d detectors.Detector) detectorKey { +// createDetectorKey creates a unique key for each detector from its type, version, and, for +// custom regex detectors, its name. +func createDetectorKey(d detectors.Detector) DetectorKey { detectorType := d.Type() var version int if v, ok := d.(detectors.Versioner); ok { @@ -92,5 +85,5 @@ func createDetectorKey(d detectors.Detector) detectorKey { if r, ok := d.(*custom_detectors.CustomRegexWebhook); ok { customDetectorName = r.GetName() } - return detectorKey{detectorType: detectorType, version: version, customDetectorName: customDetectorName} + return DetectorKey{detectorType: detectorType, version: version, customDetectorName: customDetectorName} } diff --git a/pkg/engine/ahocorasickcore_test.go b/pkg/engine/ahocorasickcore_test.go index 0de2a416ef68..3bf56a754de5 100644 --- a/pkg/engine/ahocorasickcore_test.go +++ b/pkg/engine/ahocorasickcore_test.go @@ -21,7 +21,7 @@ func (d testDetectorV1) FromData(ctx context.Context, verify bool, data []byte) } func (d testDetectorV1) Keywords() []string { - return []string{"a"} + return []string{"a", "b"} } func (d testDetectorV1) Type() detectorspb.DetectorType { @@ -40,7 +40,7 @@ func (d testDetectorV2) FromData(ctx context.Context, verify bool, data []byte) } func (d testDetectorV2) Keywords() []string { - return []string{"b"} + return []string{"a"} } func (d testDetectorV2) Type() detectorspb.DetectorType { @@ -66,72 +66,51 @@ func TestAhoCorasickCore_MultipleCustomDetectorsMatchable(t *testing.T) { customDetector2, err := custom_detectors.NewWebhookCustomRegex(&custom_detectorspb.CustomRegex{ Name: "custom detector 2", - Keywords: []string{"b"}, + Keywords: []string{"a"}, Regex: map[string]string{"": ""}, }) assert.Nil(t, err) - testCases := []struct { - matchString string - detector detectors.Detector - }{ - { - matchString: "a", - detector: customDetector1, - }, - { - matchString: "b", - detector: customDetector2, - }, - } - - var allDetectors []detectors.Detector - for _, tt := range testCases { - allDetectors = append(allDetectors, tt.detector) - } + allDetectors := []detectors.Detector{customDetector1, customDetector2} ac := NewAhoCorasickCore(allDetectors) - for _, tt := range testCases { - matches := ac.MatchString(tt.matchString) - assert.Equal(t, 1, len(matches)) - - matchingDetectors := make(map[detectorspb.DetectorType]detectors.Detector) - ac.PopulateDetectorsByMatch(matches[0], matchingDetectors) - assert.Equal(t, 1, len(matchingDetectors)) - assert.Equal(t, tt.detector, matchingDetectors[detectorspb.DetectorType_CustomRegex]) + detectorsMap := make(map[DetectorKey]detectors.Detector, 2) + ac.PopulateMatchingDetectors("a", detectorsMap) + matchingDetectors := make([]detectors.Detector, 0, 2) + for _, d := range detectorsMap { + matchingDetectors = append(matchingDetectors, d) } + assert.ElementsMatch(t, allDetectors, matchingDetectors) } func TestAhoCorasickCore_MultipleDetectorVersionsMatchable(t *testing.T) { - testCases := []struct { - matchString string - detector detectors.Detector - }{ - { - matchString: "a", - detector: testDetectorV1{}, - }, - { - matchString: "b", - detector: testDetectorV2{}, - }, - } + v1 := testDetectorV1{} + v2 := testDetectorV2{} + allDetectors := []detectors.Detector{v1, v2} + + ac := NewAhoCorasickCore(allDetectors) - var allDetectors []detectors.Detector - for _, tt := range testCases { - allDetectors = append(allDetectors, tt.detector) + detectorsMap := make(map[DetectorKey]detectors.Detector, 2) + ac.PopulateMatchingDetectors("a", detectorsMap) + matchingDetectors := make([]detectors.Detector, 0, 2) + for _, d := range detectorsMap { + matchingDetectors = append(matchingDetectors, d) } + assert.ElementsMatch(t, allDetectors, matchingDetectors) +} - ac := NewAhoCorasickCore(allDetectors) +func TestAhoCorasickCore_NoDuplicateDetectorsMatched(t *testing.T) { + d := testDetectorV1{} + allDetectors := []detectors.Detector{d} - for _, tt := range testCases { - matches := ac.MatchString(tt.matchString) - assert.Equal(t, 1, len(matches)) + ac := NewAhoCorasickCore(allDetectors) - matchingDetectors := make(map[detectorspb.DetectorType]detectors.Detector) - ac.PopulateDetectorsByMatch(matches[0], matchingDetectors) - assert.Equal(t, 1, len(matchingDetectors)) - assert.Equal(t, tt.detector, matchingDetectors[TestDetectorType]) + detectorsMap := make(map[DetectorKey]detectors.Detector, 2) + ac.PopulateMatchingDetectors("a a b b", detectorsMap) + matchingDetectors := make([]detectors.Detector, 0, 2) + for _, d := range detectorsMap { + matchingDetectors = append(matchingDetectors, d) } + assert.ElementsMatch(t, allDetectors, matchingDetectors) } diff --git a/pkg/engine/defaults.go b/pkg/engine/defaults.go index b284087642b2..4ef289af853b 100644 --- a/pkg/engine/defaults.go +++ b/pkg/engine/defaults.go @@ -58,10 +58,13 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/avazapersonalaccesstoken" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/aviationstack" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/aws" + awssessionkey "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/awssessionkeys" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/axonaut" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/aylien" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ayrshare" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azurebatch" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azurecontainerregistry" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bannerbear" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/baremetrics" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/beamer" @@ -140,6 +143,7 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cloverly" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cloze" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/clustdoc" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/coda" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/codacy" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/codeclimate" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/codemagic" @@ -229,6 +233,7 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/etherscan" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ethplorer" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/etsyapikey" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/eventbrite" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/everhour" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/exchangerateapi" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/exchangeratesapi" @@ -236,7 +241,6 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/extractorapi" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/facebookoauth" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/faceplusplus" - "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fakejson" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fastforex" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fastlypersonaltoken" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/feedier" @@ -273,6 +277,7 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ftp" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fulcrum" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fullstory" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fullstory_v2" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fusebill" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fxmarket" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gcp" @@ -299,6 +304,8 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gocanvas" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gocardless" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/goodday" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/grafana" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/grafanaserviceaccount" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/graphcms" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/graphhopper" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/groovehq" @@ -382,6 +389,7 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/locationiq" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/loggly" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/loginradius" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/logzio" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/lokalisetoken" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/loyverse" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/lunchmoney" @@ -439,6 +447,7 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/nexmoapikey" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/nftport" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ngc" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ngrok" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/nicereply" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/nightfall" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/nimble" @@ -470,6 +479,7 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/openweather" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/opsgenie" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/optimizely" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/overloop" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/owlbot" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/packagecloud" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pagerdutyapikey" @@ -481,7 +491,6 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/parsers" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/parseur" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/partnerstack" - "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/passbase" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pastebin" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/paydirtapp" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/paymoapp" @@ -516,7 +525,6 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/privatekey" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/prodpad" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/prospectcrm" - "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/prospectio" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/protocolsio" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/proxycrawl" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pubnubpublishkey" @@ -543,6 +551,7 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/repairshopr" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/replicate" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/replyio" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/requestfinance" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/restpack" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/restpackhtmltopdfapi" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/restpackscreenshotapi" @@ -570,7 +579,6 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/scrapeowl" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/scraperapi" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/scraperbox" - "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/scrapersite" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/scrapestack" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/scrapfly" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/scrapingant" @@ -780,7 +788,10 @@ func DefaultDetectors() []detectors.Detector { &linearapi.Scanner{}, &alibaba.Scanner{}, aws.New(), + awssessionkey.New(), &azure.Scanner{}, + &azurecontainerregistry.Scanner{}, + &azurebatch.Scanner{}, &slack.Scanner{}, // has 4 secret types &gitlab.Scanner{}, &gitlabv2.Scanner{}, @@ -941,7 +952,6 @@ func DefaultDetectors() []detectors.Detector { &delighted.Scanner{}, &abbysale.Scanner{}, &feedier.Scanner{}, - &prospectio.Scanner{}, &nytimes.Scanner{}, &powrbot.Scanner{}, &magnetic.Scanner{}, @@ -1079,6 +1089,7 @@ func DefaultDetectors() []detectors.Detector { ipgeolocation.Scanner{}, tmetric.Scanner{}, fullstory.Scanner{}, + fullstory_v2.Scanner{}, noticeable.Scanner{}, currencyscoop.Scanner{}, scrapingbee.Scanner{}, @@ -1113,7 +1124,6 @@ func DefaultDetectors() []detectors.Detector { bitcoinaverage.Scanner{}, zipcodeapi.Scanner{}, gyazo.Scanner{}, - fakejson.Scanner{}, // sparkpost.Scanner{}, locationiq.Scanner{}, saucelabs.Scanner{}, @@ -1217,7 +1227,6 @@ func DefaultDetectors() []detectors.Detector { optimizely.Scanner{}, censys.Scanner{}, scraperbox.Scanner{}, - passbase.Scanner{}, ticketmaster.Scanner{}, iexcloud.Scanner{}, partnerstack.Scanner{}, @@ -1258,7 +1267,6 @@ func DefaultDetectors() []detectors.Detector { blitapp.Scanner{}, restpackhtmltopdfapi.Scanner{}, webscraping.Scanner{}, - // sentiment.Scanner{}, geoapify.Scanner{}, dfuse.Scanner{}, gitter.Scanner{}, @@ -1318,7 +1326,6 @@ func DefaultDetectors() []detectors.Detector { zenkitapi.Scanner{}, sherpadesk.Scanner{}, shotstack.Scanner{}, - scrapersite.Scanner{}, luno.Scanner{}, apacta.Scanner{}, fmfw.Scanner{}, @@ -1558,6 +1565,7 @@ func DefaultDetectors() []detectors.Detector { &sourcegraphcody.Scanner{}, voiceflow.Scanner{}, ip2location.Scanner{}, + grafanaserviceaccount.Scanner{}, vagrantcloudpersonaltoken.Scanner{}, openvpn.Scanner{}, &metabase.Scanner{}, @@ -1570,6 +1578,13 @@ func DefaultDetectors() []detectors.Detector { lemonsqueezy.Scanner{}, denodeploy.Scanner{}, budibase.Scanner{}, + requestfinance.Scanner{}, + coda.Scanner{}, + grafana.Scanner{}, + logzio.Scanner{}, + eventbrite.Scanner{}, + &overloop.Scanner{}, + ngrok.Scanner{}, replicate.Scanner{}, } diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 07a3a3731c28..1628d96b2c18 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -458,7 +458,7 @@ func (e *Engine) detectorWorker(ctx context.Context) { // Reuse the same map to avoid allocations. const avgDetectorsPerChunk = 2 - chunkSpecificDetectors := make(map[detectorspb.DetectorType]detectors.Detector, avgDetectorsPerChunk) + chunkSpecificDetectors := make(map[DetectorKey]detectors.Detector, avgDetectorsPerChunk) for originalChunk := range e.ChunksChan() { for chunk := range sources.Chunker(originalChunk) { atomic.AddUint64(&e.metrics.BytesScanned, uint64(len(chunk.Data))) @@ -469,11 +469,7 @@ func (e *Engine) detectorWorker(ctx context.Context) { continue } - for _, match := range e.ahoCorasickCore.MatchString(string(decoded.Chunk.Data)) { - if !e.ahoCorasickCore.PopulateDetectorsByMatch(match, chunkSpecificDetectors) { - continue - } - } + e.ahoCorasickCore.PopulateMatchingDetectors(string(decoded.Chunk.Data), chunkSpecificDetectors) for k, detector := range chunkSpecificDetectors { decoded.Chunk.Verify = e.verify diff --git a/pkg/handlers/archive.go b/pkg/handlers/archive.go index 6b4062556760..942e513288c8 100644 --- a/pkg/handlers/archive.go +++ b/pkg/handlers/archive.go @@ -414,9 +414,11 @@ func (a *Archive) handleNestedFileMIME(ctx logContext.Context, tempEnv tempEnv, // determineMimeType reads from the provided reader to detect the MIME type. // It returns the detected MIME type and a new reader that includes the read portion. func determineMimeType(reader io.Reader) (mimeType, io.Reader, error) { + // A buffer of 512 bytes is used since many file formats store their magic numbers within the first 512 bytes. + // If fewer bytes are read, MIME type detection may still succeed. buffer := make([]byte, 512) n, err := reader.Read(buffer) - if err != nil { + if err != nil && !errors.Is(err, io.EOF) { return "", nil, fmt.Errorf("unable to read file for MIME type detection: %w", err) } diff --git a/pkg/handlers/archive_test.go b/pkg/handlers/archive_test.go index 009446c62b89..8f2186f47ef0 100644 --- a/pkg/handlers/archive_test.go +++ b/pkg/handlers/archive_test.go @@ -11,8 +11,9 @@ import ( "testing" "time" - diskbufferreader "github.com/trufflesecurity/disk-buffer-reader" + "github.com/h2non/filetype" "github.com/stretchr/testify/assert" + diskbufferreader "github.com/trufflesecurity/disk-buffer-reader" logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context" "github.com/trufflesecurity/trufflehog/v3/pkg/sources" @@ -106,12 +107,12 @@ func TestArchiveHandler(t *testing.T) { } func TestHandleFile(t *testing.T) { - ch := make(chan *sources.Chunk, 2) + reporter := sources.ChanReporter{Ch: make(chan *sources.Chunk, 2)} // Context cancels the operation. canceledCtx, cancel := context.WithCancel(context.Background()) cancel() - assert.False(t, HandleFile(canceledCtx, strings.NewReader("file"), &sources.Chunk{}, ch)) + assert.False(t, HandleFile(canceledCtx, strings.NewReader("file"), &sources.Chunk{}, reporter)) // Only one chunk is sent on the channel. // TODO: Embed a zip without making an HTTP request. @@ -123,9 +124,9 @@ func TestHandleFile(t *testing.T) { reader, err := diskbufferreader.New(resp.Body) assert.NoError(t, err) - assert.Equal(t, 0, len(ch)) - assert.True(t, HandleFile(context.Background(), reader, &sources.Chunk{}, ch)) - assert.Equal(t, 1, len(ch)) + assert.Equal(t, 0, len(reporter.Ch)) + assert.True(t, HandleFile(context.Background(), reader, &sources.Chunk{}, reporter)) + assert.Equal(t, 1, len(reporter.Ch)) } func TestReadToMax(t *testing.T) { @@ -208,7 +209,7 @@ func TestExtractTarContent(t *testing.T) { chunkCh := make(chan *sources.Chunk) go func() { defer close(chunkCh) - ok := HandleFile(ctx, file, &sources.Chunk{}, chunkCh) + ok := HandleFile(ctx, file, &sources.Chunk{}, sources.ChanReporter{Ch: chunkCh}) assert.True(t, ok) }() @@ -261,7 +262,7 @@ func TestNestedDirArchive(t *testing.T) { go func() { defer close(sourceChan) - HandleFile(ctx, file, &sources.Chunk{}, sourceChan) + HandleFile(ctx, file, &sources.Chunk{}, sources.ChanReporter{Ch: sourceChan}) }() count := 0 @@ -271,3 +272,77 @@ func TestNestedDirArchive(t *testing.T) { } assert.Equal(t, want, count) } + +func TestDetermineMimeType(t *testing.T) { + filetype.AddMatcher(filetype.NewType("txt", "text/plain"), func(buf []byte) bool { + return strings.HasPrefix(string(buf), "text:") + }) + + pngBytes := []byte("\x89PNG\r\n\x1a\n") + jpegBytes := []byte{0xFF, 0xD8, 0xFF} + textBytes := []byte("text: This is a plain text") + rpmBytes := []byte("\xed\xab\xee\xdb") + + tests := []struct { + name string + input io.Reader + expected mimeType + shouldFail bool + }{ + { + name: "PNG file", + input: bytes.NewReader(pngBytes), + expected: mimeType("image/png"), + shouldFail: false, + }, + { + name: "JPEG file", + input: bytes.NewReader(jpegBytes), + expected: mimeType("image/jpeg"), + shouldFail: false, + }, + { + name: "Text file", + input: bytes.NewReader(textBytes), + expected: mimeType("text/plain"), + shouldFail: false, + }, + { + name: "RPM file", + input: bytes.NewReader(rpmBytes), + expected: rpmMimeType, + shouldFail: false, + }, + { + name: "Truncated JPEG file", + input: io.LimitReader(bytes.NewReader(jpegBytes), 2), + expected: mimeType("unknown"), + shouldFail: true, + }, + { + name: "Empty reader", + input: bytes.NewReader([]byte{}), + shouldFail: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + originalData, _ := io.ReadAll(io.TeeReader(tt.input, &bytes.Buffer{})) + tt.input = bytes.NewReader(originalData) // Reset the reader + + mime, reader, err := determineMimeType(tt.input) + if err != nil && !tt.shouldFail { + t.Fatalf("unexpected error: %v", err) + } + + if !tt.shouldFail { + assert.Equal(t, tt.expected, mime) + } + + // Ensure the reader still contains all the original data. + data, _ := io.ReadAll(reader) + assert.Equal(t, originalData, data) + }) + } +} diff --git a/pkg/handlers/handlers.go b/pkg/handlers/handlers.go index 131e77c963a4..d2792c0e69f6 100644 --- a/pkg/handlers/handlers.go +++ b/pkg/handlers/handlers.go @@ -34,10 +34,10 @@ type Handler interface { // HandleFile processes a given file by selecting an appropriate handler from DefaultHandlers. // It first checks if the handler implements SpecializedHandler for any special processing, // then falls back to regular file type handling. If successful, it reads the file in chunks, -// packages them in the provided chunk skeleton, and sends them to chunksChan. +// packages them in the provided chunk skeleton, and reports them to the chunk reporter. // The function returns true if processing was successful and false otherwise. // Context is used for cancellation, and the caller is responsible for canceling it if needed. -func HandleFile(ctx context.Context, file io.Reader, chunkSkel *sources.Chunk, chunksChan chan *sources.Chunk) bool { +func HandleFile(ctx context.Context, file io.Reader, chunkSkel *sources.Chunk, reporter sources.ChunkReporter) bool { aCtx := logContext.AddLogger(ctx) for _, h := range DefaultHandlers() { h.New() @@ -51,7 +51,7 @@ func HandleFile(ctx context.Context, file io.Reader, chunkSkel *sources.Chunk, c return false } - if success := processHandler(aCtx, h, reReader, chunkSkel, chunksChan); success { + if success := processHandler(aCtx, h, reReader, chunkSkel, reporter); success { return true } } @@ -59,14 +59,14 @@ func HandleFile(ctx context.Context, file io.Reader, chunkSkel *sources.Chunk, c return false } -func processHandler(ctx logContext.Context, h Handler, reReader *diskbufferreader.DiskBufferReader, chunkSkel *sources.Chunk, chunksChan chan *sources.Chunk) bool { +func processHandler(ctx logContext.Context, h Handler, reReader *diskbufferreader.DiskBufferReader, chunkSkel *sources.Chunk, reporter sources.ChunkReporter) bool { defer reReader.Close() defer reReader.Stop() if specialHandler, ok := h.(SpecializedHandler); ok { file, isSpecial, err := specialHandler.HandleSpecialized(ctx, reReader) if isSpecial { - return handleChunks(ctx, h.FromFile(ctx, file), chunkSkel, chunksChan) + return handleChunks(ctx, h.FromFile(ctx, file), chunkSkel, reporter) } if err != nil { ctx.Logger().Error(err, "error handling file") @@ -82,10 +82,10 @@ func processHandler(ctx logContext.Context, h Handler, reReader *diskbufferreade return false } - return handleChunks(ctx, h.FromFile(ctx, reReader), chunkSkel, chunksChan) + return handleChunks(ctx, h.FromFile(ctx, reReader), chunkSkel, reporter) } -func handleChunks(ctx context.Context, handlerChan chan []byte, chunkSkel *sources.Chunk, chunksChan chan *sources.Chunk) bool { +func handleChunks(ctx context.Context, handlerChan chan []byte, chunkSkel *sources.Chunk, reporter sources.ChunkReporter) bool { for { select { case data, open := <-handlerChan: @@ -94,9 +94,7 @@ func handleChunks(ctx context.Context, handlerChan chan []byte, chunkSkel *sourc } chunk := *chunkSkel chunk.Data = data - select { - case chunksChan <- &chunk: - case <-ctx.Done(): + if err := reporter.ChunkOk(logContext.AddLogger(ctx), chunk); err != nil { return false } case <-ctx.Done(): diff --git a/pkg/pb/detectorspb/detectors.pb.go b/pkg/pb/detectorspb/detectors.pb.go index 1fa585b0cc0e..d743b8e83d8c 100644 --- a/pkg/pb/detectorspb/detectors.pb.go +++ b/pkg/pb/detectorspb/detectors.pb.go @@ -316,114 +316,115 @@ const ( DetectorType_Nytimes DetectorType = 241 DetectorType_Polygon DetectorType = 242 DetectorType_Powrbot DetectorType = 243 - DetectorType_ProspectIO DetectorType = 244 - DetectorType_Skrappio DetectorType = 245 - DetectorType_Monday DetectorType = 246 - DetectorType_Smartsheets DetectorType = 247 - DetectorType_Wrike DetectorType = 248 - DetectorType_Float DetectorType = 249 - DetectorType_Imagekit DetectorType = 250 - DetectorType_Integromat DetectorType = 251 - DetectorType_Salesblink DetectorType = 252 - DetectorType_Bored DetectorType = 253 - DetectorType_Campayn DetectorType = 254 - DetectorType_Clinchpad DetectorType = 255 - DetectorType_CompanyHub DetectorType = 256 - DetectorType_Debounce DetectorType = 257 - DetectorType_Dyspatch DetectorType = 258 - DetectorType_Guardianapi DetectorType = 259 - DetectorType_Harvest DetectorType = 260 - DetectorType_Moosend DetectorType = 261 - DetectorType_OpenWeather DetectorType = 262 - DetectorType_Siteleaf DetectorType = 263 - DetectorType_Squarespace DetectorType = 264 - DetectorType_FlowFlu DetectorType = 265 - DetectorType_Nimble DetectorType = 266 - DetectorType_LessAnnoyingCRM DetectorType = 267 - DetectorType_Nethunt DetectorType = 268 - DetectorType_Apptivo DetectorType = 269 - DetectorType_CapsuleCRM DetectorType = 270 - DetectorType_Insightly DetectorType = 271 - DetectorType_Kylas DetectorType = 272 - DetectorType_OnepageCRM DetectorType = 273 - DetectorType_User DetectorType = 274 - DetectorType_ProspectCRM DetectorType = 275 - DetectorType_ReallySimpleSystems DetectorType = 276 - DetectorType_Airship DetectorType = 277 - DetectorType_Artsy DetectorType = 278 - DetectorType_Yandex DetectorType = 279 - DetectorType_Clockify DetectorType = 280 - DetectorType_Dnscheck DetectorType = 281 - DetectorType_EasyInsight DetectorType = 282 - DetectorType_Ethplorer DetectorType = 283 - DetectorType_Everhour DetectorType = 284 - DetectorType_Fulcrum DetectorType = 285 - DetectorType_GeoIpifi DetectorType = 286 - DetectorType_Jotform DetectorType = 287 - DetectorType_Refiner DetectorType = 288 - DetectorType_Timezoneapi DetectorType = 289 - DetectorType_TogglTrack DetectorType = 290 - DetectorType_Vpnapi DetectorType = 291 - DetectorType_Workstack DetectorType = 292 - DetectorType_Apollo DetectorType = 293 - DetectorType_Eversign DetectorType = 294 - DetectorType_Juro DetectorType = 295 - DetectorType_KarmaCRM DetectorType = 296 - DetectorType_Metrilo DetectorType = 297 - DetectorType_Pandadoc DetectorType = 298 - DetectorType_RevampCRM DetectorType = 299 - DetectorType_Salescookie DetectorType = 300 - DetectorType_Alconost DetectorType = 301 - DetectorType_Blogger DetectorType = 302 - DetectorType_Accuweather DetectorType = 303 - DetectorType_Opengraphr DetectorType = 304 - DetectorType_Rawg DetectorType = 305 - DetectorType_Riotgames DetectorType = 306 - DetectorType_RoninApp DetectorType = 307 - DetectorType_Stormglass DetectorType = 308 - DetectorType_Tomtom DetectorType = 309 - DetectorType_Twitch DetectorType = 310 - DetectorType_Documo DetectorType = 311 - DetectorType_Cloudways DetectorType = 312 - DetectorType_Veevavault DetectorType = 313 - DetectorType_KiteConnect DetectorType = 314 - DetectorType_ShopeeOpenPlatform DetectorType = 315 - DetectorType_TeamViewer DetectorType = 316 - DetectorType_Bulbul DetectorType = 317 - DetectorType_CentralStationCRM DetectorType = 318 - DetectorType_Teamgate DetectorType = 319 - DetectorType_Axonaut DetectorType = 320 - DetectorType_Tyntec DetectorType = 321 - DetectorType_Appcues DetectorType = 322 - DetectorType_Autoklose DetectorType = 323 - DetectorType_Cloudplan DetectorType = 324 - DetectorType_Dotmailer DetectorType = 325 - DetectorType_GetEmail DetectorType = 326 - DetectorType_GetEmails DetectorType = 327 - DetectorType_Kontent DetectorType = 328 - DetectorType_Leadfeeder DetectorType = 329 - DetectorType_Raven DetectorType = 330 - DetectorType_RocketReach DetectorType = 331 - DetectorType_Uplead DetectorType = 332 - DetectorType_Brandfetch DetectorType = 333 - DetectorType_Clearbit DetectorType = 334 - DetectorType_Crowdin DetectorType = 335 - DetectorType_Mapquest DetectorType = 336 - DetectorType_Noticeable DetectorType = 337 - DetectorType_Onbuka DetectorType = 338 - DetectorType_Todoist DetectorType = 339 - DetectorType_Storychief DetectorType = 340 - DetectorType_LinkedIn DetectorType = 341 - DetectorType_YouSign DetectorType = 342 - DetectorType_Docker DetectorType = 343 - DetectorType_Telesign DetectorType = 344 - DetectorType_Spoonacular DetectorType = 345 - DetectorType_Aerisweather DetectorType = 346 - DetectorType_Alphavantage DetectorType = 347 - DetectorType_Imgur DetectorType = 348 - DetectorType_Imagga DetectorType = 349 - DetectorType_SMSApi DetectorType = 350 - DetectorType_Distribusion DetectorType = 351 + // Deprecated: Do not use. + DetectorType_ProspectIO DetectorType = 244 + DetectorType_Skrappio DetectorType = 245 + DetectorType_Monday DetectorType = 246 + DetectorType_Smartsheets DetectorType = 247 + DetectorType_Wrike DetectorType = 248 + DetectorType_Float DetectorType = 249 + DetectorType_Imagekit DetectorType = 250 + DetectorType_Integromat DetectorType = 251 + DetectorType_Salesblink DetectorType = 252 + DetectorType_Bored DetectorType = 253 + DetectorType_Campayn DetectorType = 254 + DetectorType_Clinchpad DetectorType = 255 + DetectorType_CompanyHub DetectorType = 256 + DetectorType_Debounce DetectorType = 257 + DetectorType_Dyspatch DetectorType = 258 + DetectorType_Guardianapi DetectorType = 259 + DetectorType_Harvest DetectorType = 260 + DetectorType_Moosend DetectorType = 261 + DetectorType_OpenWeather DetectorType = 262 + DetectorType_Siteleaf DetectorType = 263 + DetectorType_Squarespace DetectorType = 264 + DetectorType_FlowFlu DetectorType = 265 + DetectorType_Nimble DetectorType = 266 + DetectorType_LessAnnoyingCRM DetectorType = 267 + DetectorType_Nethunt DetectorType = 268 + DetectorType_Apptivo DetectorType = 269 + DetectorType_CapsuleCRM DetectorType = 270 + DetectorType_Insightly DetectorType = 271 + DetectorType_Kylas DetectorType = 272 + DetectorType_OnepageCRM DetectorType = 273 + DetectorType_User DetectorType = 274 + DetectorType_ProspectCRM DetectorType = 275 + DetectorType_ReallySimpleSystems DetectorType = 276 + DetectorType_Airship DetectorType = 277 + DetectorType_Artsy DetectorType = 278 + DetectorType_Yandex DetectorType = 279 + DetectorType_Clockify DetectorType = 280 + DetectorType_Dnscheck DetectorType = 281 + DetectorType_EasyInsight DetectorType = 282 + DetectorType_Ethplorer DetectorType = 283 + DetectorType_Everhour DetectorType = 284 + DetectorType_Fulcrum DetectorType = 285 + DetectorType_GeoIpifi DetectorType = 286 + DetectorType_Jotform DetectorType = 287 + DetectorType_Refiner DetectorType = 288 + DetectorType_Timezoneapi DetectorType = 289 + DetectorType_TogglTrack DetectorType = 290 + DetectorType_Vpnapi DetectorType = 291 + DetectorType_Workstack DetectorType = 292 + DetectorType_Apollo DetectorType = 293 + DetectorType_Eversign DetectorType = 294 + DetectorType_Juro DetectorType = 295 + DetectorType_KarmaCRM DetectorType = 296 + DetectorType_Metrilo DetectorType = 297 + DetectorType_Pandadoc DetectorType = 298 + DetectorType_RevampCRM DetectorType = 299 + DetectorType_Salescookie DetectorType = 300 + DetectorType_Alconost DetectorType = 301 + DetectorType_Blogger DetectorType = 302 + DetectorType_Accuweather DetectorType = 303 + DetectorType_Opengraphr DetectorType = 304 + DetectorType_Rawg DetectorType = 305 + DetectorType_Riotgames DetectorType = 306 + DetectorType_RoninApp DetectorType = 307 + DetectorType_Stormglass DetectorType = 308 + DetectorType_Tomtom DetectorType = 309 + DetectorType_Twitch DetectorType = 310 + DetectorType_Documo DetectorType = 311 + DetectorType_Cloudways DetectorType = 312 + DetectorType_Veevavault DetectorType = 313 + DetectorType_KiteConnect DetectorType = 314 + DetectorType_ShopeeOpenPlatform DetectorType = 315 + DetectorType_TeamViewer DetectorType = 316 + DetectorType_Bulbul DetectorType = 317 + DetectorType_CentralStationCRM DetectorType = 318 + DetectorType_Teamgate DetectorType = 319 + DetectorType_Axonaut DetectorType = 320 + DetectorType_Tyntec DetectorType = 321 + DetectorType_Appcues DetectorType = 322 + DetectorType_Autoklose DetectorType = 323 + DetectorType_Cloudplan DetectorType = 324 + DetectorType_Dotmailer DetectorType = 325 + DetectorType_GetEmail DetectorType = 326 + DetectorType_GetEmails DetectorType = 327 + DetectorType_Kontent DetectorType = 328 + DetectorType_Leadfeeder DetectorType = 329 + DetectorType_Raven DetectorType = 330 + DetectorType_RocketReach DetectorType = 331 + DetectorType_Uplead DetectorType = 332 + DetectorType_Brandfetch DetectorType = 333 + DetectorType_Clearbit DetectorType = 334 + DetectorType_Crowdin DetectorType = 335 + DetectorType_Mapquest DetectorType = 336 + DetectorType_Noticeable DetectorType = 337 + DetectorType_Onbuka DetectorType = 338 + DetectorType_Todoist DetectorType = 339 + DetectorType_Storychief DetectorType = 340 + DetectorType_LinkedIn DetectorType = 341 + DetectorType_YouSign DetectorType = 342 + DetectorType_Docker DetectorType = 343 + DetectorType_Telesign DetectorType = 344 + DetectorType_Spoonacular DetectorType = 345 + DetectorType_Aerisweather DetectorType = 346 + DetectorType_Alphavantage DetectorType = 347 + DetectorType_Imgur DetectorType = 348 + DetectorType_Imagga DetectorType = 349 + DetectorType_SMSApi DetectorType = 350 + DetectorType_Distribusion DetectorType = 351 // Deprecated: Do not use. DetectorType_Blablabus DetectorType = 352 DetectorType_WordsApi DetectorType = 353 @@ -507,6 +508,7 @@ const ( DetectorType_BitcoinAverage DetectorType = 432 DetectorType_CommerceJS DetectorType = 433 DetectorType_DetectLanguage DetectorType = 434 + // Deprecated: Do not use. DetectorType_FakeJSON DetectorType = 435 DetectorType_Graphhopper DetectorType = 436 DetectorType_Lexigram DetectorType = 437 @@ -646,12 +648,13 @@ const ( DetectorType_Hive DetectorType = 571 DetectorType_Hiveage DetectorType = 572 DetectorType_Kickbox DetectorType = 573 - DetectorType_Passbase DetectorType = 574 - DetectorType_PostageApp DetectorType = 575 - DetectorType_PureStake DetectorType = 576 - DetectorType_Qubole DetectorType = 577 - DetectorType_CarbonInterface DetectorType = 578 - DetectorType_Intrinio DetectorType = 579 + // Deprecated: Do not use. + DetectorType_Passbase DetectorType = 574 + DetectorType_PostageApp DetectorType = 575 + DetectorType_PureStake DetectorType = 576 + DetectorType_Qubole DetectorType = 577 + DetectorType_CarbonInterface DetectorType = 578 + DetectorType_Intrinio DetectorType = 579 // Deprecated: Do not use. DetectorType_QuickMetrics DetectorType = 580 DetectorType_ScrapeStack DetectorType = 581 @@ -694,12 +697,13 @@ const ( DetectorType_Squareup DetectorType = 618 DetectorType_Dandelion DetectorType = 619 // Deprecated: Do not use. - DetectorType_DataFire DetectorType = 620 - DetectorType_DeepAI DetectorType = 621 - DetectorType_MeaningCloud DetectorType = 622 - DetectorType_NeutrinoApi DetectorType = 623 - DetectorType_Storecove DetectorType = 624 - DetectorType_Shipday DetectorType = 625 + DetectorType_DataFire DetectorType = 620 + DetectorType_DeepAI DetectorType = 621 + DetectorType_MeaningCloud DetectorType = 622 + DetectorType_NeutrinoApi DetectorType = 623 + DetectorType_Storecove DetectorType = 624 + DetectorType_Shipday DetectorType = 625 + // Deprecated: Do not use. DetectorType_Sentiment DetectorType = 626 DetectorType_StreamChatMessaging DetectorType = 627 DetectorType_TeamworkCRM DetectorType = 628 @@ -709,31 +713,32 @@ const ( DetectorType_Apacta DetectorType = 632 DetectorType_GetSandbox DetectorType = 633 // Deprecated: Do not use. - DetectorType_Happi DetectorType = 634 - DetectorType_Oanda DetectorType = 635 - DetectorType_FastForex DetectorType = 636 - DetectorType_APIMatic DetectorType = 637 - DetectorType_VersionEye DetectorType = 638 - DetectorType_EagleEyeNetworks DetectorType = 639 - DetectorType_ThousandEyes DetectorType = 640 - DetectorType_SelectPDF DetectorType = 641 - DetectorType_Flightstats DetectorType = 642 - DetectorType_ChecIO DetectorType = 643 - DetectorType_Manifest DetectorType = 644 - DetectorType_ApiScience DetectorType = 645 - DetectorType_AppSynergy DetectorType = 646 - DetectorType_Caflou DetectorType = 647 - DetectorType_Caspio DetectorType = 648 - DetectorType_ChecklyHQ DetectorType = 649 - DetectorType_CloudElements DetectorType = 650 - DetectorType_DronaHQ DetectorType = 651 - DetectorType_Enablex DetectorType = 652 - DetectorType_Fmfw DetectorType = 653 - DetectorType_GoodDay DetectorType = 654 - DetectorType_Luno DetectorType = 655 - DetectorType_Meistertask DetectorType = 656 - DetectorType_Mindmeister DetectorType = 657 - DetectorType_PeopleDataLabs DetectorType = 658 + DetectorType_Happi DetectorType = 634 + DetectorType_Oanda DetectorType = 635 + DetectorType_FastForex DetectorType = 636 + DetectorType_APIMatic DetectorType = 637 + DetectorType_VersionEye DetectorType = 638 + DetectorType_EagleEyeNetworks DetectorType = 639 + DetectorType_ThousandEyes DetectorType = 640 + DetectorType_SelectPDF DetectorType = 641 + DetectorType_Flightstats DetectorType = 642 + DetectorType_ChecIO DetectorType = 643 + DetectorType_Manifest DetectorType = 644 + DetectorType_ApiScience DetectorType = 645 + DetectorType_AppSynergy DetectorType = 646 + DetectorType_Caflou DetectorType = 647 + DetectorType_Caspio DetectorType = 648 + DetectorType_ChecklyHQ DetectorType = 649 + DetectorType_CloudElements DetectorType = 650 + DetectorType_DronaHQ DetectorType = 651 + DetectorType_Enablex DetectorType = 652 + DetectorType_Fmfw DetectorType = 653 + DetectorType_GoodDay DetectorType = 654 + DetectorType_Luno DetectorType = 655 + DetectorType_Meistertask DetectorType = 656 + DetectorType_Mindmeister DetectorType = 657 + DetectorType_PeopleDataLabs DetectorType = 658 + // Deprecated: Do not use. DetectorType_ScraperSite DetectorType = 659 DetectorType_Scrapfly DetectorType = 660 DetectorType_SimplyNoted DetectorType = 661 @@ -1034,7 +1039,17 @@ const ( DetectorType_DenoDeploy DetectorType = 954 DetectorType_Stripo DetectorType = 955 DetectorType_ReplyIO DetectorType = 956 - DetectorType_Replicate DetectorType = 957 + DetectorType_AzureBatch DetectorType = 957 + DetectorType_AzureContainerRegistry DetectorType = 958 + DetectorType_AWSSessionKey DetectorType = 959 + DetectorType_Coda DetectorType = 960 + DetectorType_LogzIO DetectorType = 961 + DetectorType_Eventbrite DetectorType = 962 + DetectorType_GrafanaServiceAccount DetectorType = 963 + DetectorType_RequestFinance DetectorType = 964 + DetectorType_Overloop DetectorType = 965 + DetectorType_Ngrok DetectorType = 966 + DetectorType_Replicate DetectorType = 967 ) // Enum value maps for DetectorType. @@ -1993,7 +2008,17 @@ var ( 954: "DenoDeploy", 955: "Stripo", 956: "ReplyIO", - 957: "Replicate", + 957: "AzureBatch", + 958: "AzureContainerRegistry", + 959: "AWSSessionKey", + 960: "Coda", + 961: "LogzIO", + 962: "Eventbrite", + 963: "GrafanaServiceAccount", + 964: "RequestFinance", + 965: "Overloop", + 966: "Ngrok", + 967: "Replicate", } DetectorType_value = map[string]int32{ "Alibaba": 0, @@ -2949,7 +2974,17 @@ var ( "DenoDeploy": 954, "Stripo": 955, "ReplyIO": 956, - "Replicate": 957, + "AzureBatch": 957, + "AzureContainerRegistry": 958, + "AWSSessionKey": 959, + "Coda": 960, + "LogzIO": 961, + "Eventbrite": 962, + "GrafanaServiceAccount": 963, + "RequestFinance": 964, + "Overloop": 965, + "Ngrok": 966, + "Replicate": 967, } ) @@ -3328,7 +3363,7 @@ var file_detectors_proto_rawDesc = []byte{ 0x44, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x4c, 0x41, 0x49, 0x4e, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x42, 0x41, 0x53, 0x45, 0x36, 0x34, 0x10, 0x02, 0x12, - 0x09, 0x0a, 0x05, 0x55, 0x54, 0x46, 0x31, 0x36, 0x10, 0x03, 0x2a, 0x9c, 0x78, 0x0a, 0x0c, 0x44, + 0x09, 0x0a, 0x05, 0x55, 0x54, 0x46, 0x31, 0x36, 0x10, 0x03, 0x2a, 0xe7, 0x79, 0x0a, 0x0c, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x6c, 0x69, 0x62, 0x61, 0x62, 0x61, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x4d, 0x51, 0x50, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x57, 0x53, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x41, @@ -3583,383 +3618,384 @@ var file_detectors_proto_rawDesc = []byte{ 0x08, 0x4d, 0x61, 0x67, 0x6e, 0x65, 0x74, 0x69, 0x63, 0x10, 0xf0, 0x01, 0x12, 0x0c, 0x0a, 0x07, 0x4e, 0x79, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x10, 0xf1, 0x01, 0x12, 0x0c, 0x0a, 0x07, 0x50, 0x6f, 0x6c, 0x79, 0x67, 0x6f, 0x6e, 0x10, 0xf2, 0x01, 0x12, 0x0c, 0x0a, 0x07, 0x50, 0x6f, 0x77, 0x72, - 0x62, 0x6f, 0x74, 0x10, 0xf3, 0x01, 0x12, 0x0f, 0x0a, 0x0a, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, - 0x63, 0x74, 0x49, 0x4f, 0x10, 0xf4, 0x01, 0x12, 0x0d, 0x0a, 0x08, 0x53, 0x6b, 0x72, 0x61, 0x70, - 0x70, 0x69, 0x6f, 0x10, 0xf5, 0x01, 0x12, 0x0b, 0x0a, 0x06, 0x4d, 0x6f, 0x6e, 0x64, 0x61, 0x79, - 0x10, 0xf6, 0x01, 0x12, 0x10, 0x0a, 0x0b, 0x53, 0x6d, 0x61, 0x72, 0x74, 0x73, 0x68, 0x65, 0x65, - 0x74, 0x73, 0x10, 0xf7, 0x01, 0x12, 0x0a, 0x0a, 0x05, 0x57, 0x72, 0x69, 0x6b, 0x65, 0x10, 0xf8, - 0x01, 0x12, 0x0a, 0x0a, 0x05, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x10, 0xf9, 0x01, 0x12, 0x0d, 0x0a, - 0x08, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x6b, 0x69, 0x74, 0x10, 0xfa, 0x01, 0x12, 0x0f, 0x0a, 0x0a, - 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x6f, 0x6d, 0x61, 0x74, 0x10, 0xfb, 0x01, 0x12, 0x0f, 0x0a, - 0x0a, 0x53, 0x61, 0x6c, 0x65, 0x73, 0x62, 0x6c, 0x69, 0x6e, 0x6b, 0x10, 0xfc, 0x01, 0x12, 0x0a, - 0x0a, 0x05, 0x42, 0x6f, 0x72, 0x65, 0x64, 0x10, 0xfd, 0x01, 0x12, 0x0c, 0x0a, 0x07, 0x43, 0x61, - 0x6d, 0x70, 0x61, 0x79, 0x6e, 0x10, 0xfe, 0x01, 0x12, 0x0e, 0x0a, 0x09, 0x43, 0x6c, 0x69, 0x6e, - 0x63, 0x68, 0x70, 0x61, 0x64, 0x10, 0xff, 0x01, 0x12, 0x0f, 0x0a, 0x0a, 0x43, 0x6f, 0x6d, 0x70, - 0x61, 0x6e, 0x79, 0x48, 0x75, 0x62, 0x10, 0x80, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x44, 0x65, 0x62, - 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x10, 0x81, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x44, 0x79, 0x73, 0x70, - 0x61, 0x74, 0x63, 0x68, 0x10, 0x82, 0x02, 0x12, 0x10, 0x0a, 0x0b, 0x47, 0x75, 0x61, 0x72, 0x64, - 0x69, 0x61, 0x6e, 0x61, 0x70, 0x69, 0x10, 0x83, 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x48, 0x61, 0x72, - 0x76, 0x65, 0x73, 0x74, 0x10, 0x84, 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x4d, 0x6f, 0x6f, 0x73, 0x65, - 0x6e, 0x64, 0x10, 0x85, 0x02, 0x12, 0x10, 0x0a, 0x0b, 0x4f, 0x70, 0x65, 0x6e, 0x57, 0x65, 0x61, - 0x74, 0x68, 0x65, 0x72, 0x10, 0x86, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x53, 0x69, 0x74, 0x65, 0x6c, - 0x65, 0x61, 0x66, 0x10, 0x87, 0x02, 0x12, 0x10, 0x0a, 0x0b, 0x53, 0x71, 0x75, 0x61, 0x72, 0x65, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x10, 0x88, 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x46, 0x6c, 0x6f, 0x77, - 0x46, 0x6c, 0x75, 0x10, 0x89, 0x02, 0x12, 0x0b, 0x0a, 0x06, 0x4e, 0x69, 0x6d, 0x62, 0x6c, 0x65, - 0x10, 0x8a, 0x02, 0x12, 0x14, 0x0a, 0x0f, 0x4c, 0x65, 0x73, 0x73, 0x41, 0x6e, 0x6e, 0x6f, 0x79, - 0x69, 0x6e, 0x67, 0x43, 0x52, 0x4d, 0x10, 0x8b, 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x4e, 0x65, 0x74, - 0x68, 0x75, 0x6e, 0x74, 0x10, 0x8c, 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x41, 0x70, 0x70, 0x74, 0x69, - 0x76, 0x6f, 0x10, 0x8d, 0x02, 0x12, 0x0f, 0x0a, 0x0a, 0x43, 0x61, 0x70, 0x73, 0x75, 0x6c, 0x65, - 0x43, 0x52, 0x4d, 0x10, 0x8e, 0x02, 0x12, 0x0e, 0x0a, 0x09, 0x49, 0x6e, 0x73, 0x69, 0x67, 0x68, - 0x74, 0x6c, 0x79, 0x10, 0x8f, 0x02, 0x12, 0x0a, 0x0a, 0x05, 0x4b, 0x79, 0x6c, 0x61, 0x73, 0x10, - 0x90, 0x02, 0x12, 0x0f, 0x0a, 0x0a, 0x4f, 0x6e, 0x65, 0x70, 0x61, 0x67, 0x65, 0x43, 0x52, 0x4d, - 0x10, 0x91, 0x02, 0x12, 0x09, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x10, 0x92, 0x02, 0x12, 0x10, - 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x43, 0x52, 0x4d, 0x10, 0x93, 0x02, - 0x12, 0x18, 0x0a, 0x13, 0x52, 0x65, 0x61, 0x6c, 0x6c, 0x79, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, - 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x10, 0x94, 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x41, 0x69, - 0x72, 0x73, 0x68, 0x69, 0x70, 0x10, 0x95, 0x02, 0x12, 0x0a, 0x0a, 0x05, 0x41, 0x72, 0x74, 0x73, - 0x79, 0x10, 0x96, 0x02, 0x12, 0x0b, 0x0a, 0x06, 0x59, 0x61, 0x6e, 0x64, 0x65, 0x78, 0x10, 0x97, - 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x43, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x66, 0x79, 0x10, 0x98, 0x02, - 0x12, 0x0d, 0x0a, 0x08, 0x44, 0x6e, 0x73, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x10, 0x99, 0x02, 0x12, - 0x10, 0x0a, 0x0b, 0x45, 0x61, 0x73, 0x79, 0x49, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x10, 0x9a, - 0x02, 0x12, 0x0e, 0x0a, 0x09, 0x45, 0x74, 0x68, 0x70, 0x6c, 0x6f, 0x72, 0x65, 0x72, 0x10, 0x9b, - 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x45, 0x76, 0x65, 0x72, 0x68, 0x6f, 0x75, 0x72, 0x10, 0x9c, 0x02, - 0x12, 0x0c, 0x0a, 0x07, 0x46, 0x75, 0x6c, 0x63, 0x72, 0x75, 0x6d, 0x10, 0x9d, 0x02, 0x12, 0x0d, - 0x0a, 0x08, 0x47, 0x65, 0x6f, 0x49, 0x70, 0x69, 0x66, 0x69, 0x10, 0x9e, 0x02, 0x12, 0x0c, 0x0a, - 0x07, 0x4a, 0x6f, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x10, 0x9f, 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x52, - 0x65, 0x66, 0x69, 0x6e, 0x65, 0x72, 0x10, 0xa0, 0x02, 0x12, 0x10, 0x0a, 0x0b, 0x54, 0x69, 0x6d, - 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x61, 0x70, 0x69, 0x10, 0xa1, 0x02, 0x12, 0x0f, 0x0a, 0x0a, 0x54, - 0x6f, 0x67, 0x67, 0x6c, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x10, 0xa2, 0x02, 0x12, 0x0b, 0x0a, 0x06, - 0x56, 0x70, 0x6e, 0x61, 0x70, 0x69, 0x10, 0xa3, 0x02, 0x12, 0x0e, 0x0a, 0x09, 0x57, 0x6f, 0x72, - 0x6b, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x10, 0xa4, 0x02, 0x12, 0x0b, 0x0a, 0x06, 0x41, 0x70, 0x6f, - 0x6c, 0x6c, 0x6f, 0x10, 0xa5, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x45, 0x76, 0x65, 0x72, 0x73, 0x69, - 0x67, 0x6e, 0x10, 0xa6, 0x02, 0x12, 0x09, 0x0a, 0x04, 0x4a, 0x75, 0x72, 0x6f, 0x10, 0xa7, 0x02, - 0x12, 0x0d, 0x0a, 0x08, 0x4b, 0x61, 0x72, 0x6d, 0x61, 0x43, 0x52, 0x4d, 0x10, 0xa8, 0x02, 0x12, - 0x0c, 0x0a, 0x07, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x6c, 0x6f, 0x10, 0xa9, 0x02, 0x12, 0x0d, 0x0a, - 0x08, 0x50, 0x61, 0x6e, 0x64, 0x61, 0x64, 0x6f, 0x63, 0x10, 0xaa, 0x02, 0x12, 0x0e, 0x0a, 0x09, - 0x52, 0x65, 0x76, 0x61, 0x6d, 0x70, 0x43, 0x52, 0x4d, 0x10, 0xab, 0x02, 0x12, 0x10, 0x0a, 0x0b, - 0x53, 0x61, 0x6c, 0x65, 0x73, 0x63, 0x6f, 0x6f, 0x6b, 0x69, 0x65, 0x10, 0xac, 0x02, 0x12, 0x0d, - 0x0a, 0x08, 0x41, 0x6c, 0x63, 0x6f, 0x6e, 0x6f, 0x73, 0x74, 0x10, 0xad, 0x02, 0x12, 0x0c, 0x0a, - 0x07, 0x42, 0x6c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x10, 0xae, 0x02, 0x12, 0x10, 0x0a, 0x0b, 0x41, - 0x63, 0x63, 0x75, 0x77, 0x65, 0x61, 0x74, 0x68, 0x65, 0x72, 0x10, 0xaf, 0x02, 0x12, 0x0f, 0x0a, - 0x0a, 0x4f, 0x70, 0x65, 0x6e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x72, 0x10, 0xb0, 0x02, 0x12, 0x09, - 0x0a, 0x04, 0x52, 0x61, 0x77, 0x67, 0x10, 0xb1, 0x02, 0x12, 0x0e, 0x0a, 0x09, 0x52, 0x69, 0x6f, - 0x74, 0x67, 0x61, 0x6d, 0x65, 0x73, 0x10, 0xb2, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x52, 0x6f, 0x6e, - 0x69, 0x6e, 0x41, 0x70, 0x70, 0x10, 0xb3, 0x02, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x74, 0x6f, 0x72, - 0x6d, 0x67, 0x6c, 0x61, 0x73, 0x73, 0x10, 0xb4, 0x02, 0x12, 0x0b, 0x0a, 0x06, 0x54, 0x6f, 0x6d, - 0x74, 0x6f, 0x6d, 0x10, 0xb5, 0x02, 0x12, 0x0b, 0x0a, 0x06, 0x54, 0x77, 0x69, 0x74, 0x63, 0x68, - 0x10, 0xb6, 0x02, 0x12, 0x0b, 0x0a, 0x06, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x6f, 0x10, 0xb7, 0x02, - 0x12, 0x0e, 0x0a, 0x09, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x77, 0x61, 0x79, 0x73, 0x10, 0xb8, 0x02, - 0x12, 0x0f, 0x0a, 0x0a, 0x56, 0x65, 0x65, 0x76, 0x61, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x10, 0xb9, - 0x02, 0x12, 0x10, 0x0a, 0x0b, 0x4b, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, - 0x10, 0xba, 0x02, 0x12, 0x17, 0x0a, 0x12, 0x53, 0x68, 0x6f, 0x70, 0x65, 0x65, 0x4f, 0x70, 0x65, - 0x6e, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x10, 0xbb, 0x02, 0x12, 0x0f, 0x0a, 0x0a, - 0x54, 0x65, 0x61, 0x6d, 0x56, 0x69, 0x65, 0x77, 0x65, 0x72, 0x10, 0xbc, 0x02, 0x12, 0x0b, 0x0a, - 0x06, 0x42, 0x75, 0x6c, 0x62, 0x75, 0x6c, 0x10, 0xbd, 0x02, 0x12, 0x16, 0x0a, 0x11, 0x43, 0x65, - 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x52, 0x4d, 0x10, - 0xbe, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x54, 0x65, 0x61, 0x6d, 0x67, 0x61, 0x74, 0x65, 0x10, 0xbf, - 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x41, 0x78, 0x6f, 0x6e, 0x61, 0x75, 0x74, 0x10, 0xc0, 0x02, 0x12, - 0x0b, 0x0a, 0x06, 0x54, 0x79, 0x6e, 0x74, 0x65, 0x63, 0x10, 0xc1, 0x02, 0x12, 0x0c, 0x0a, 0x07, - 0x41, 0x70, 0x70, 0x63, 0x75, 0x65, 0x73, 0x10, 0xc2, 0x02, 0x12, 0x0e, 0x0a, 0x09, 0x41, 0x75, - 0x74, 0x6f, 0x6b, 0x6c, 0x6f, 0x73, 0x65, 0x10, 0xc3, 0x02, 0x12, 0x0e, 0x0a, 0x09, 0x43, 0x6c, - 0x6f, 0x75, 0x64, 0x70, 0x6c, 0x61, 0x6e, 0x10, 0xc4, 0x02, 0x12, 0x0e, 0x0a, 0x09, 0x44, 0x6f, - 0x74, 0x6d, 0x61, 0x69, 0x6c, 0x65, 0x72, 0x10, 0xc5, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x47, 0x65, - 0x74, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x10, 0xc6, 0x02, 0x12, 0x0e, 0x0a, 0x09, 0x47, 0x65, 0x74, - 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x73, 0x10, 0xc7, 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x4b, 0x6f, 0x6e, - 0x74, 0x65, 0x6e, 0x74, 0x10, 0xc8, 0x02, 0x12, 0x0f, 0x0a, 0x0a, 0x4c, 0x65, 0x61, 0x64, 0x66, - 0x65, 0x65, 0x64, 0x65, 0x72, 0x10, 0xc9, 0x02, 0x12, 0x0a, 0x0a, 0x05, 0x52, 0x61, 0x76, 0x65, - 0x6e, 0x10, 0xca, 0x02, 0x12, 0x10, 0x0a, 0x0b, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, - 0x61, 0x63, 0x68, 0x10, 0xcb, 0x02, 0x12, 0x0b, 0x0a, 0x06, 0x55, 0x70, 0x6c, 0x65, 0x61, 0x64, - 0x10, 0xcc, 0x02, 0x12, 0x0f, 0x0a, 0x0a, 0x42, 0x72, 0x61, 0x6e, 0x64, 0x66, 0x65, 0x74, 0x63, - 0x68, 0x10, 0xcd, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x62, 0x69, 0x74, - 0x10, 0xce, 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x43, 0x72, 0x6f, 0x77, 0x64, 0x69, 0x6e, 0x10, 0xcf, - 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x4d, 0x61, 0x70, 0x71, 0x75, 0x65, 0x73, 0x74, 0x10, 0xd0, 0x02, - 0x12, 0x0f, 0x0a, 0x0a, 0x4e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x61, 0x62, 0x6c, 0x65, 0x10, 0xd1, - 0x02, 0x12, 0x0b, 0x0a, 0x06, 0x4f, 0x6e, 0x62, 0x75, 0x6b, 0x61, 0x10, 0xd2, 0x02, 0x12, 0x0c, - 0x0a, 0x07, 0x54, 0x6f, 0x64, 0x6f, 0x69, 0x73, 0x74, 0x10, 0xd3, 0x02, 0x12, 0x0f, 0x0a, 0x0a, - 0x53, 0x74, 0x6f, 0x72, 0x79, 0x63, 0x68, 0x69, 0x65, 0x66, 0x10, 0xd4, 0x02, 0x12, 0x0d, 0x0a, - 0x08, 0x4c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x49, 0x6e, 0x10, 0xd5, 0x02, 0x12, 0x0c, 0x0a, 0x07, - 0x59, 0x6f, 0x75, 0x53, 0x69, 0x67, 0x6e, 0x10, 0xd6, 0x02, 0x12, 0x0b, 0x0a, 0x06, 0x44, 0x6f, - 0x63, 0x6b, 0x65, 0x72, 0x10, 0xd7, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x54, 0x65, 0x6c, 0x65, 0x73, - 0x69, 0x67, 0x6e, 0x10, 0xd8, 0x02, 0x12, 0x10, 0x0a, 0x0b, 0x53, 0x70, 0x6f, 0x6f, 0x6e, 0x61, - 0x63, 0x75, 0x6c, 0x61, 0x72, 0x10, 0xd9, 0x02, 0x12, 0x11, 0x0a, 0x0c, 0x41, 0x65, 0x72, 0x69, - 0x73, 0x77, 0x65, 0x61, 0x74, 0x68, 0x65, 0x72, 0x10, 0xda, 0x02, 0x12, 0x11, 0x0a, 0x0c, 0x41, - 0x6c, 0x70, 0x68, 0x61, 0x76, 0x61, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x10, 0xdb, 0x02, 0x12, 0x0a, - 0x0a, 0x05, 0x49, 0x6d, 0x67, 0x75, 0x72, 0x10, 0xdc, 0x02, 0x12, 0x0b, 0x0a, 0x06, 0x49, 0x6d, - 0x61, 0x67, 0x67, 0x61, 0x10, 0xdd, 0x02, 0x12, 0x0b, 0x0a, 0x06, 0x53, 0x4d, 0x53, 0x41, 0x70, - 0x69, 0x10, 0xde, 0x02, 0x12, 0x11, 0x0a, 0x0c, 0x44, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, - 0x73, 0x69, 0x6f, 0x6e, 0x10, 0xdf, 0x02, 0x12, 0x12, 0x0a, 0x09, 0x42, 0x6c, 0x61, 0x62, 0x6c, - 0x61, 0x62, 0x75, 0x73, 0x10, 0xe0, 0x02, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x0d, 0x0a, 0x08, 0x57, - 0x6f, 0x72, 0x64, 0x73, 0x41, 0x70, 0x69, 0x10, 0xe1, 0x02, 0x12, 0x12, 0x0a, 0x0d, 0x43, 0x75, - 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x10, 0xe2, 0x02, 0x12, 0x0d, - 0x0a, 0x08, 0x48, 0x74, 0x6d, 0x6c, 0x32, 0x50, 0x64, 0x66, 0x10, 0xe3, 0x02, 0x12, 0x12, 0x0a, - 0x0d, 0x49, 0x50, 0x47, 0x65, 0x6f, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0xe4, - 0x02, 0x12, 0x0b, 0x0a, 0x06, 0x4f, 0x77, 0x6c, 0x62, 0x6f, 0x74, 0x10, 0xe5, 0x02, 0x12, 0x11, - 0x0a, 0x0c, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x6d, 0x65, 0x72, 0x73, 0x69, 0x76, 0x65, 0x10, 0xe6, - 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x44, 0x79, 0x6e, 0x61, 0x6c, 0x69, 0x73, 0x74, 0x10, 0xe7, 0x02, - 0x12, 0x14, 0x0a, 0x0f, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x61, 0x74, 0x65, - 0x41, 0x50, 0x49, 0x10, 0xe8, 0x02, 0x12, 0x0f, 0x0a, 0x0a, 0x48, 0x6f, 0x6c, 0x69, 0x64, 0x61, - 0x79, 0x41, 0x50, 0x49, 0x10, 0xe9, 0x02, 0x12, 0x0a, 0x0a, 0x05, 0x49, 0x70, 0x61, 0x70, 0x69, - 0x10, 0xea, 0x02, 0x12, 0x10, 0x0a, 0x0b, 0x4d, 0x61, 0x72, 0x6b, 0x65, 0x74, 0x73, 0x74, 0x61, - 0x63, 0x6b, 0x10, 0xeb, 0x02, 0x12, 0x10, 0x0a, 0x0b, 0x4e, 0x75, 0x74, 0x72, 0x69, 0x74, 0x69, - 0x6f, 0x6e, 0x69, 0x78, 0x10, 0xec, 0x02, 0x12, 0x0a, 0x0a, 0x05, 0x53, 0x77, 0x65, 0x6c, 0x6c, - 0x10, 0xed, 0x02, 0x12, 0x19, 0x0a, 0x14, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x75, 0x70, 0x50, 0x65, - 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0xee, 0x02, 0x12, 0x0a, - 0x0a, 0x05, 0x4e, 0x69, 0x74, 0x72, 0x6f, 0x10, 0xef, 0x02, 0x12, 0x08, 0x0a, 0x03, 0x52, 0x65, - 0x76, 0x10, 0xf0, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x52, 0x75, 0x6e, 0x52, 0x75, 0x6e, 0x49, 0x74, - 0x10, 0xf1, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x54, 0x79, 0x70, 0x65, 0x66, 0x6f, 0x72, 0x6d, 0x10, - 0xf2, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x4d, 0x69, 0x78, 0x70, 0x61, 0x6e, 0x65, 0x6c, 0x10, 0xf3, - 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x54, 0x72, 0x61, 0x64, 0x69, 0x65, 0x72, 0x10, 0xf4, 0x02, 0x12, - 0x0d, 0x0a, 0x08, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x72, 0x10, 0xf5, 0x02, 0x12, 0x0d, - 0x0a, 0x08, 0x56, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x79, 0x10, 0xf6, 0x02, 0x12, 0x0b, 0x0a, - 0x06, 0x41, 0x6c, 0x65, 0x67, 0x72, 0x61, 0x10, 0xf7, 0x02, 0x12, 0x09, 0x0a, 0x04, 0x41, 0x75, - 0x64, 0x64, 0x10, 0xf8, 0x02, 0x12, 0x10, 0x0a, 0x0b, 0x42, 0x61, 0x72, 0x65, 0x6d, 0x65, 0x74, - 0x72, 0x69, 0x63, 0x73, 0x10, 0xf9, 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x43, 0x6f, 0x69, 0x6e, 0x6c, - 0x69, 0x62, 0x10, 0xfa, 0x02, 0x12, 0x15, 0x0a, 0x10, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x52, 0x61, 0x74, 0x65, 0x73, 0x41, 0x50, 0x49, 0x10, 0xfb, 0x02, 0x12, 0x12, 0x0a, 0x0d, - 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x63, 0x6f, 0x6f, 0x70, 0x10, 0xfc, 0x02, - 0x12, 0x0d, 0x0a, 0x08, 0x46, 0x58, 0x4d, 0x61, 0x72, 0x6b, 0x65, 0x74, 0x10, 0xfd, 0x02, 0x12, - 0x12, 0x0a, 0x0d, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6c, 0x6f, 0x75, 0x64, - 0x10, 0xfe, 0x02, 0x12, 0x0e, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f, 0x41, 0x50, 0x49, - 0x10, 0xff, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x41, 0x62, 0x73, 0x74, 0x72, 0x61, 0x63, 0x74, 0x10, - 0x80, 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x42, 0x69, 0x6c, 0x6c, 0x6f, 0x6d, 0x61, 0x74, 0x10, 0x81, - 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x44, 0x6f, 0x76, 0x69, 0x63, 0x6f, 0x10, 0x82, 0x03, 0x12, 0x0b, - 0x0a, 0x06, 0x42, 0x69, 0x74, 0x62, 0x61, 0x72, 0x10, 0x83, 0x03, 0x12, 0x0c, 0x0a, 0x07, 0x42, - 0x75, 0x67, 0x73, 0x6e, 0x61, 0x67, 0x10, 0x84, 0x03, 0x12, 0x0f, 0x0a, 0x0a, 0x41, 0x73, 0x73, - 0x65, 0x6d, 0x62, 0x6c, 0x79, 0x41, 0x49, 0x10, 0x85, 0x03, 0x12, 0x0f, 0x0a, 0x0a, 0x41, 0x64, - 0x61, 0x66, 0x72, 0x75, 0x69, 0x74, 0x49, 0x4f, 0x10, 0x86, 0x03, 0x12, 0x0a, 0x0a, 0x05, 0x41, - 0x70, 0x69, 0x66, 0x79, 0x10, 0x87, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x43, 0x6f, 0x69, 0x6e, 0x47, - 0x65, 0x63, 0x6b, 0x6f, 0x10, 0x88, 0x03, 0x12, 0x12, 0x0a, 0x0d, 0x43, 0x72, 0x79, 0x70, 0x74, - 0x6f, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x65, 0x10, 0x89, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x46, - 0x75, 0x6c, 0x6c, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x10, 0x8a, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x48, - 0x65, 0x6c, 0x6c, 0x6f, 0x53, 0x69, 0x67, 0x6e, 0x10, 0x8b, 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x4c, - 0x6f, 0x79, 0x76, 0x65, 0x72, 0x73, 0x65, 0x10, 0x8c, 0x03, 0x12, 0x0c, 0x0a, 0x07, 0x4e, 0x65, - 0x74, 0x43, 0x6f, 0x72, 0x65, 0x10, 0x8d, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x53, 0x61, 0x75, 0x63, - 0x65, 0x4c, 0x61, 0x62, 0x73, 0x10, 0x8e, 0x03, 0x12, 0x0f, 0x0a, 0x0a, 0x41, 0x6c, 0x69, 0x65, - 0x6e, 0x56, 0x61, 0x75, 0x6c, 0x74, 0x10, 0x8f, 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x41, 0x70, 0x69, - 0x66, 0x6c, 0x61, 0x73, 0x68, 0x10, 0x91, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x43, 0x6f, 0x69, 0x6e, - 0x6c, 0x61, 0x79, 0x65, 0x72, 0x10, 0x92, 0x03, 0x12, 0x10, 0x0a, 0x0b, 0x43, 0x75, 0x72, 0x72, - 0x65, 0x6e, 0x74, 0x73, 0x41, 0x50, 0x49, 0x10, 0x93, 0x03, 0x12, 0x0c, 0x0a, 0x07, 0x44, 0x61, - 0x74, 0x61, 0x47, 0x6f, 0x76, 0x10, 0x94, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x45, 0x6e, 0x69, 0x67, - 0x6d, 0x61, 0x10, 0x95, 0x03, 0x12, 0x1a, 0x0a, 0x15, 0x46, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x69, - 0x61, 0x6c, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x69, 0x6e, 0x67, 0x50, 0x72, 0x65, 0x70, 0x10, 0x96, - 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x47, 0x65, 0x6f, 0x63, 0x6f, 0x64, 0x69, 0x6f, 0x10, 0x97, 0x03, - 0x12, 0x0c, 0x0a, 0x07, 0x48, 0x65, 0x72, 0x65, 0x41, 0x50, 0x49, 0x10, 0x98, 0x03, 0x12, 0x0f, - 0x0a, 0x0a, 0x4d, 0x61, 0x63, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x10, 0x99, 0x03, 0x12, - 0x0c, 0x0a, 0x07, 0x4f, 0x4f, 0x50, 0x53, 0x70, 0x61, 0x6d, 0x10, 0x9a, 0x03, 0x12, 0x10, 0x0a, - 0x0b, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x73, 0x49, 0x4f, 0x10, 0x9b, 0x03, 0x12, - 0x0f, 0x0a, 0x0a, 0x53, 0x63, 0x72, 0x61, 0x70, 0x65, 0x72, 0x41, 0x50, 0x49, 0x10, 0x9c, 0x03, - 0x12, 0x13, 0x0a, 0x0e, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x54, 0x72, 0x61, 0x69, - 0x6c, 0x73, 0x10, 0x9d, 0x03, 0x12, 0x0f, 0x0a, 0x0a, 0x54, 0x6f, 0x6d, 0x6f, 0x72, 0x72, 0x6f, - 0x77, 0x49, 0x4f, 0x10, 0x9e, 0x03, 0x12, 0x13, 0x0a, 0x0e, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x43, - 0x6f, 0x69, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x10, 0x9f, 0x03, 0x12, 0x11, 0x0a, 0x0c, 0x46, - 0x61, 0x63, 0x65, 0x50, 0x6c, 0x75, 0x73, 0x50, 0x6c, 0x75, 0x73, 0x10, 0xa0, 0x03, 0x12, 0x0e, - 0x0a, 0x09, 0x56, 0x6f, 0x69, 0x63, 0x65, 0x67, 0x61, 0x69, 0x6e, 0x10, 0xa1, 0x03, 0x12, 0x0d, - 0x0a, 0x08, 0x44, 0x65, 0x65, 0x70, 0x67, 0x72, 0x61, 0x6d, 0x10, 0xa2, 0x03, 0x12, 0x13, 0x0a, - 0x0e, 0x56, 0x69, 0x73, 0x75, 0x61, 0x6c, 0x43, 0x72, 0x6f, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x10, - 0xa3, 0x03, 0x12, 0x0c, 0x0a, 0x07, 0x46, 0x69, 0x6e, 0x6e, 0x68, 0x75, 0x62, 0x10, 0xa4, 0x03, - 0x12, 0x0b, 0x0a, 0x06, 0x54, 0x69, 0x69, 0x6e, 0x67, 0x6f, 0x10, 0xa5, 0x03, 0x12, 0x10, 0x0a, - 0x0b, 0x52, 0x69, 0x6e, 0x67, 0x43, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x10, 0xa6, 0x03, 0x12, - 0x0b, 0x0a, 0x06, 0x46, 0x69, 0x6e, 0x61, 0x67, 0x65, 0x10, 0xa7, 0x03, 0x12, 0x0b, 0x0a, 0x06, - 0x45, 0x64, 0x61, 0x6d, 0x61, 0x6d, 0x10, 0xa8, 0x03, 0x12, 0x10, 0x0a, 0x0b, 0x48, 0x79, 0x70, - 0x65, 0x41, 0x75, 0x64, 0x69, 0x74, 0x6f, 0x72, 0x10, 0xa9, 0x03, 0x12, 0x0a, 0x0a, 0x05, 0x47, - 0x65, 0x6e, 0x67, 0x6f, 0x10, 0xaa, 0x03, 0x12, 0x0a, 0x0a, 0x05, 0x46, 0x72, 0x6f, 0x6e, 0x74, - 0x10, 0xab, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x46, 0x6c, 0x65, 0x65, 0x74, 0x62, 0x61, 0x73, 0x65, - 0x10, 0xac, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x42, 0x75, 0x62, 0x62, 0x6c, 0x65, 0x10, 0xad, 0x03, - 0x12, 0x0f, 0x0a, 0x0a, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x62, 0x65, 0x61, 0x72, 0x10, 0xae, - 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x41, 0x64, 0x7a, 0x75, 0x6e, 0x61, 0x10, 0xaf, 0x03, 0x12, 0x13, - 0x0a, 0x0e, 0x42, 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, 0x41, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, - 0x10, 0xb0, 0x03, 0x12, 0x0f, 0x0a, 0x0a, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x72, 0x63, 0x65, 0x4a, - 0x53, 0x10, 0xb1, 0x03, 0x12, 0x13, 0x0a, 0x0e, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x4c, 0x61, - 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x10, 0xb2, 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x46, 0x61, 0x6b, - 0x65, 0x4a, 0x53, 0x4f, 0x4e, 0x10, 0xb3, 0x03, 0x12, 0x10, 0x0a, 0x0b, 0x47, 0x72, 0x61, 0x70, - 0x68, 0x68, 0x6f, 0x70, 0x70, 0x65, 0x72, 0x10, 0xb4, 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x4c, 0x65, - 0x78, 0x69, 0x67, 0x72, 0x61, 0x6d, 0x10, 0xb5, 0x03, 0x12, 0x10, 0x0a, 0x0b, 0x4c, 0x69, 0x6e, - 0x6b, 0x50, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x10, 0xb6, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x4e, - 0x75, 0x6d, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x10, 0xb7, 0x03, 0x12, 0x0f, 0x0a, 0x0a, 0x50, - 0x72, 0x6f, 0x78, 0x79, 0x43, 0x72, 0x61, 0x77, 0x6c, 0x10, 0xb8, 0x03, 0x12, 0x0f, 0x0a, 0x0a, - 0x5a, 0x69, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x41, 0x50, 0x49, 0x10, 0xb9, 0x03, 0x12, 0x0e, 0x0a, - 0x09, 0x43, 0x6f, 0x6d, 0x65, 0x74, 0x63, 0x68, 0x61, 0x74, 0x10, 0xba, 0x03, 0x12, 0x0b, 0x0a, - 0x06, 0x4b, 0x65, 0x79, 0x67, 0x65, 0x6e, 0x10, 0xbb, 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x4d, 0x69, - 0x78, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x10, 0xbc, 0x03, 0x12, 0x0c, 0x0a, 0x07, 0x54, 0x61, 0x74, - 0x75, 0x6d, 0x49, 0x4f, 0x10, 0xbd, 0x03, 0x12, 0x0c, 0x0a, 0x07, 0x54, 0x6d, 0x65, 0x74, 0x72, - 0x69, 0x63, 0x10, 0xbe, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x4c, 0x61, 0x73, 0x74, 0x66, 0x6d, 0x10, - 0xbf, 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x42, 0x72, 0x6f, 0x77, 0x73, 0x68, 0x6f, 0x74, 0x10, 0xc0, - 0x03, 0x12, 0x0c, 0x0a, 0x07, 0x4a, 0x53, 0x4f, 0x4e, 0x62, 0x69, 0x6e, 0x10, 0xc1, 0x03, 0x12, - 0x0f, 0x0a, 0x0a, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x51, 0x10, 0xc2, 0x03, - 0x12, 0x12, 0x0a, 0x0d, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x73, 0x68, 0x6f, 0x74, 0x41, 0x50, - 0x49, 0x10, 0xc3, 0x03, 0x12, 0x11, 0x0a, 0x0c, 0x57, 0x65, 0x61, 0x74, 0x68, 0x65, 0x72, 0x53, - 0x74, 0x61, 0x63, 0x6b, 0x10, 0xc4, 0x03, 0x12, 0x0c, 0x0a, 0x07, 0x41, 0x6d, 0x61, 0x64, 0x65, - 0x75, 0x73, 0x10, 0xc5, 0x03, 0x12, 0x0f, 0x0a, 0x0a, 0x46, 0x6f, 0x75, 0x72, 0x53, 0x71, 0x75, - 0x61, 0x72, 0x65, 0x10, 0xc6, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x46, 0x6c, 0x69, 0x63, 0x6b, 0x72, - 0x10, 0xc7, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x48, 0x65, 0x6c, 0x70, - 0x10, 0xc8, 0x03, 0x12, 0x0a, 0x0a, 0x05, 0x41, 0x6d, 0x62, 0x65, 0x65, 0x10, 0xc9, 0x03, 0x12, - 0x0d, 0x0a, 0x08, 0x41, 0x70, 0x69, 0x32, 0x43, 0x61, 0x72, 0x74, 0x10, 0xca, 0x03, 0x12, 0x0f, - 0x0a, 0x0a, 0x48, 0x79, 0x70, 0x65, 0x72, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x10, 0xcb, 0x03, 0x12, - 0x0e, 0x0a, 0x09, 0x4b, 0x61, 0x6b, 0x61, 0x6f, 0x54, 0x61, 0x6c, 0x6b, 0x10, 0xcc, 0x03, 0x12, - 0x0c, 0x0a, 0x07, 0x52, 0x69, 0x74, 0x65, 0x4b, 0x69, 0x74, 0x10, 0xcd, 0x03, 0x12, 0x11, 0x0a, - 0x0c, 0x53, 0x68, 0x75, 0x74, 0x74, 0x65, 0x72, 0x73, 0x74, 0x6f, 0x63, 0x6b, 0x10, 0xce, 0x03, - 0x12, 0x0e, 0x0a, 0x09, 0x54, 0x65, 0x78, 0x74, 0x32, 0x44, 0x61, 0x74, 0x61, 0x10, 0xcf, 0x03, - 0x12, 0x13, 0x0a, 0x0e, 0x59, 0x6f, 0x75, 0x4e, 0x65, 0x65, 0x64, 0x41, 0x42, 0x75, 0x64, 0x67, - 0x65, 0x74, 0x10, 0xd0, 0x03, 0x12, 0x0c, 0x0a, 0x07, 0x43, 0x72, 0x69, 0x63, 0x6b, 0x65, 0x74, - 0x10, 0xd1, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x74, 0x61, 0x63, 0x6b, - 0x10, 0xd2, 0x03, 0x12, 0x0a, 0x0a, 0x05, 0x47, 0x79, 0x61, 0x7a, 0x6f, 0x10, 0xd3, 0x03, 0x12, - 0x0e, 0x0a, 0x09, 0x4d, 0x61, 0x76, 0x65, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x10, 0xd4, 0x03, 0x12, - 0x0b, 0x0a, 0x06, 0x53, 0x68, 0x65, 0x65, 0x74, 0x79, 0x10, 0xd5, 0x03, 0x12, 0x0f, 0x0a, 0x0a, - 0x53, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x6d, 0x6f, 0x6e, 0x6b, 0x10, 0xd6, 0x03, 0x12, 0x0e, 0x0a, - 0x09, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x64, 0x61, 0x74, 0x61, 0x10, 0xd7, 0x03, 0x12, 0x0d, 0x0a, - 0x08, 0x55, 0x6e, 0x73, 0x70, 0x6c, 0x61, 0x73, 0x68, 0x10, 0xd8, 0x03, 0x12, 0x0e, 0x0a, 0x09, - 0x41, 0x6c, 0x6c, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x10, 0xd9, 0x03, 0x12, 0x11, 0x0a, 0x0c, - 0x43, 0x61, 0x6c, 0x6f, 0x72, 0x69, 0x65, 0x4e, 0x69, 0x6e, 0x6a, 0x61, 0x10, 0xda, 0x03, 0x12, - 0x0e, 0x0a, 0x09, 0x57, 0x61, 0x6c, 0x6b, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x10, 0xdb, 0x03, 0x12, - 0x0b, 0x0a, 0x06, 0x53, 0x74, 0x72, 0x61, 0x76, 0x61, 0x10, 0xdc, 0x03, 0x12, 0x0b, 0x0a, 0x06, - 0x43, 0x69, 0x63, 0x65, 0x72, 0x6f, 0x10, 0xdd, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x49, 0x50, 0x51, - 0x75, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x10, 0xde, 0x03, 0x12, 0x11, 0x0a, 0x0c, 0x50, 0x61, 0x72, - 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x44, 0x6f, 0x74, 0x73, 0x10, 0xdf, 0x03, 0x12, 0x0c, 0x0a, 0x07, - 0x52, 0x6f, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x10, 0xe0, 0x03, 0x12, 0x0c, 0x0a, 0x07, 0x4d, 0x61, - 0x69, 0x6c, 0x73, 0x61, 0x63, 0x10, 0xe1, 0x03, 0x12, 0x0a, 0x0a, 0x05, 0x57, 0x68, 0x6f, 0x78, - 0x79, 0x10, 0xe2, 0x03, 0x12, 0x11, 0x0a, 0x0c, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x57, 0x65, 0x61, - 0x74, 0x68, 0x65, 0x72, 0x10, 0xe3, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x41, 0x70, 0x69, 0x46, 0x6f, - 0x6e, 0x69, 0x63, 0x61, 0x10, 0xe4, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x41, 0x79, 0x6c, 0x69, 0x65, - 0x6e, 0x10, 0xe5, 0x03, 0x12, 0x0c, 0x0a, 0x07, 0x47, 0x65, 0x6f, 0x63, 0x6f, 0x64, 0x65, 0x10, - 0xe6, 0x03, 0x12, 0x0f, 0x0a, 0x0a, 0x49, 0x63, 0x6f, 0x6e, 0x46, 0x69, 0x6e, 0x64, 0x65, 0x72, - 0x10, 0xe7, 0x03, 0x12, 0x0a, 0x0a, 0x05, 0x49, 0x70, 0x69, 0x66, 0x79, 0x10, 0xe8, 0x03, 0x12, - 0x12, 0x0a, 0x0d, 0x4c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x4c, 0x61, 0x79, 0x65, 0x72, - 0x10, 0xe9, 0x03, 0x12, 0x08, 0x0a, 0x03, 0x4c, 0x6f, 0x62, 0x10, 0xea, 0x03, 0x12, 0x0e, 0x0a, - 0x09, 0x4f, 0x6e, 0x57, 0x61, 0x74, 0x65, 0x72, 0x49, 0x4f, 0x10, 0xeb, 0x03, 0x12, 0x0d, 0x0a, - 0x08, 0x50, 0x61, 0x73, 0x74, 0x65, 0x62, 0x69, 0x6e, 0x10, 0xec, 0x03, 0x12, 0x0d, 0x0a, 0x08, - 0x50, 0x64, 0x66, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x10, 0xed, 0x03, 0x12, 0x0c, 0x0a, 0x07, 0x50, - 0x69, 0x78, 0x61, 0x62, 0x61, 0x79, 0x10, 0xee, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x52, 0x65, 0x61, - 0x64, 0x4d, 0x65, 0x10, 0xef, 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x56, 0x61, 0x74, 0x4c, 0x61, 0x79, - 0x65, 0x72, 0x10, 0xf0, 0x03, 0x12, 0x0f, 0x0a, 0x0a, 0x56, 0x69, 0x72, 0x75, 0x73, 0x54, 0x6f, - 0x74, 0x61, 0x6c, 0x10, 0xf1, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x41, 0x69, 0x72, 0x56, 0x69, 0x73, - 0x75, 0x61, 0x6c, 0x10, 0xf2, 0x03, 0x12, 0x13, 0x0a, 0x0e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, - 0x63, 0x79, 0x66, 0x72, 0x65, 0x61, 0x6b, 0x73, 0x10, 0xf3, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x44, - 0x75, 0x66, 0x66, 0x65, 0x6c, 0x10, 0xf4, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x46, 0x6c, 0x61, 0x74, - 0x49, 0x4f, 0x10, 0xf5, 0x03, 0x12, 0x08, 0x0a, 0x03, 0x4d, 0x33, 0x6f, 0x10, 0xf6, 0x03, 0x12, - 0x0b, 0x0a, 0x06, 0x4d, 0x65, 0x73, 0x69, 0x62, 0x6f, 0x10, 0xf7, 0x03, 0x12, 0x0b, 0x0a, 0x06, - 0x4f, 0x70, 0x65, 0x6e, 0x75, 0x76, 0x10, 0xf8, 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x53, 0x6e, 0x69, - 0x70, 0x63, 0x61, 0x72, 0x74, 0x10, 0xf9, 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x42, 0x65, 0x73, 0x74, - 0x74, 0x69, 0x6d, 0x65, 0x10, 0xfa, 0x03, 0x12, 0x10, 0x0a, 0x0b, 0x48, 0x61, 0x70, 0x70, 0x79, - 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x10, 0xfb, 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x48, 0x75, 0x6d, - 0x61, 0x6e, 0x69, 0x74, 0x79, 0x10, 0xfc, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x49, 0x6d, 0x70, 0x61, - 0x6c, 0x61, 0x10, 0xfd, 0x03, 0x12, 0x10, 0x0a, 0x0b, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x72, 0x61, - 0x64, 0x69, 0x75, 0x73, 0x10, 0xfe, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x6f, 0x50, - 0x69, 0x6c, 0x6f, 0x74, 0x10, 0xff, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x42, 0x69, 0x74, 0x6d, 0x65, - 0x78, 0x10, 0x80, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x44, 0x6f, 0x63, - 0x10, 0x81, 0x04, 0x12, 0x0c, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x72, 0x69, 0x10, 0x82, - 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x50, 0x64, 0x66, 0x53, 0x68, 0x69, 0x66, 0x74, 0x10, 0x83, 0x04, - 0x12, 0x0d, 0x0a, 0x08, 0x50, 0x6f, 0x6c, 0x6f, 0x6e, 0x69, 0x65, 0x78, 0x10, 0x84, 0x04, 0x12, - 0x19, 0x0a, 0x14, 0x52, 0x65, 0x73, 0x74, 0x70, 0x61, 0x63, 0x6b, 0x48, 0x74, 0x6d, 0x6c, 0x54, - 0x6f, 0x50, 0x64, 0x66, 0x41, 0x50, 0x49, 0x10, 0x85, 0x04, 0x12, 0x1a, 0x0a, 0x15, 0x52, 0x65, - 0x73, 0x74, 0x70, 0x61, 0x63, 0x6b, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x73, 0x68, 0x6f, 0x74, - 0x41, 0x50, 0x49, 0x10, 0x86, 0x04, 0x12, 0x16, 0x0a, 0x11, 0x53, 0x68, 0x75, 0x74, 0x74, 0x65, - 0x72, 0x73, 0x74, 0x6f, 0x63, 0x6b, 0x4f, 0x41, 0x75, 0x74, 0x68, 0x10, 0x87, 0x04, 0x12, 0x10, - 0x0a, 0x0b, 0x53, 0x6b, 0x79, 0x42, 0x69, 0x6f, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x10, 0x88, 0x04, - 0x12, 0x0e, 0x0a, 0x09, 0x41, 0x62, 0x75, 0x73, 0x65, 0x49, 0x50, 0x44, 0x42, 0x10, 0x89, 0x04, - 0x12, 0x10, 0x0a, 0x0b, 0x41, 0x6c, 0x65, 0x74, 0x68, 0x65, 0x69, 0x61, 0x41, 0x70, 0x69, 0x10, - 0x8a, 0x04, 0x12, 0x0c, 0x0a, 0x07, 0x42, 0x6c, 0x69, 0x74, 0x41, 0x70, 0x70, 0x10, 0x8b, 0x04, - 0x12, 0x0b, 0x0a, 0x06, 0x43, 0x65, 0x6e, 0x73, 0x79, 0x73, 0x10, 0x8c, 0x04, 0x12, 0x0d, 0x0a, - 0x08, 0x43, 0x6c, 0x6f, 0x76, 0x65, 0x72, 0x6c, 0x79, 0x10, 0x8d, 0x04, 0x12, 0x11, 0x0a, 0x0c, - 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x10, 0x8e, 0x04, 0x12, - 0x0b, 0x0a, 0x06, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x4f, 0x10, 0x8f, 0x04, 0x12, 0x0e, 0x0a, 0x09, - 0x46, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x41, 0x70, 0x69, 0x10, 0x90, 0x04, 0x12, 0x0d, 0x0a, 0x08, - 0x47, 0x65, 0x6f, 0x61, 0x70, 0x69, 0x66, 0x79, 0x10, 0x91, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x49, - 0x50, 0x69, 0x6e, 0x66, 0x6f, 0x44, 0x42, 0x10, 0x92, 0x04, 0x12, 0x0f, 0x0a, 0x0a, 0x4d, 0x65, - 0x64, 0x69, 0x61, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x10, 0x93, 0x04, 0x12, 0x13, 0x0a, 0x0e, 0x4e, - 0x61, 0x73, 0x64, 0x61, 0x71, 0x44, 0x61, 0x74, 0x61, 0x4c, 0x69, 0x6e, 0x6b, 0x10, 0x94, 0x04, - 0x12, 0x11, 0x0a, 0x0c, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x61, 0x67, 0x65, 0x44, 0x61, 0x74, 0x61, - 0x10, 0x95, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x50, 0x61, 0x79, 0x6d, 0x6f, 0x6e, 0x67, 0x6f, 0x10, - 0x96, 0x04, 0x12, 0x12, 0x0a, 0x0d, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, - 0x61, 0x63, 0x6b, 0x10, 0x97, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x52, 0x65, 0x62, 0x72, 0x61, 0x6e, - 0x64, 0x6c, 0x79, 0x10, 0x98, 0x04, 0x12, 0x14, 0x0a, 0x0f, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, - 0x73, 0x68, 0x6f, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x10, 0x99, 0x04, 0x12, 0x0b, 0x0a, 0x06, - 0x53, 0x74, 0x79, 0x74, 0x63, 0x68, 0x10, 0x9a, 0x04, 0x12, 0x0c, 0x0a, 0x07, 0x55, 0x6e, 0x70, - 0x6c, 0x75, 0x67, 0x67, 0x10, 0x9b, 0x04, 0x12, 0x10, 0x0a, 0x0b, 0x55, 0x50, 0x43, 0x44, 0x61, - 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x10, 0x9c, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x55, 0x73, 0x65, - 0x72, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x10, 0x9d, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x47, 0x65, 0x6f, - 0x63, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x10, 0x9e, 0x04, 0x12, 0x10, 0x0a, 0x0b, 0x4e, 0x65, 0x77, - 0x73, 0x63, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x10, 0x9f, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x4e, - 0x69, 0x63, 0x65, 0x72, 0x65, 0x70, 0x6c, 0x79, 0x10, 0xa0, 0x04, 0x12, 0x11, 0x0a, 0x0c, 0x50, - 0x61, 0x72, 0x74, 0x6e, 0x65, 0x72, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x10, 0xa1, 0x04, 0x12, 0x0d, - 0x0a, 0x08, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x34, 0x6d, 0x65, 0x10, 0xa2, 0x04, 0x12, 0x0e, 0x0a, - 0x09, 0x53, 0x63, 0x72, 0x61, 0x70, 0x65, 0x6f, 0x77, 0x6c, 0x10, 0xa3, 0x04, 0x12, 0x10, 0x0a, - 0x0b, 0x53, 0x63, 0x72, 0x61, 0x70, 0x69, 0x6e, 0x67, 0x44, 0x6f, 0x67, 0x10, 0xa4, 0x04, 0x12, - 0x0b, 0x0a, 0x06, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6b, 0x10, 0xa5, 0x04, 0x12, 0x0e, 0x0a, 0x09, - 0x56, 0x65, 0x72, 0x69, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x10, 0xa6, 0x04, 0x12, 0x10, 0x0a, 0x0b, - 0x57, 0x65, 0x62, 0x73, 0x63, 0x72, 0x61, 0x70, 0x69, 0x6e, 0x67, 0x10, 0xa7, 0x04, 0x12, 0x0e, - 0x0a, 0x09, 0x5a, 0x65, 0x6e, 0x73, 0x63, 0x72, 0x61, 0x70, 0x65, 0x10, 0xa8, 0x04, 0x12, 0x0c, - 0x0a, 0x07, 0x5a, 0x65, 0x6e, 0x73, 0x65, 0x72, 0x70, 0x10, 0xa9, 0x04, 0x12, 0x0c, 0x0a, 0x07, - 0x43, 0x6f, 0x69, 0x6e, 0x41, 0x70, 0x69, 0x10, 0xaa, 0x04, 0x12, 0x0b, 0x0a, 0x06, 0x47, 0x69, - 0x74, 0x74, 0x65, 0x72, 0x10, 0xab, 0x04, 0x12, 0x09, 0x0a, 0x04, 0x48, 0x6f, 0x73, 0x74, 0x10, - 0xac, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x49, 0x65, 0x78, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x10, 0xad, - 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x74, 0x70, 0x61, 0x63, 0x6b, 0x10, 0xae, 0x04, - 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x63, 0x72, 0x61, 0x70, 0x65, 0x72, 0x42, 0x6f, 0x78, 0x10, 0xaf, - 0x04, 0x12, 0x10, 0x0a, 0x0b, 0x53, 0x63, 0x72, 0x61, 0x70, 0x69, 0x6e, 0x67, 0x41, 0x6e, 0x74, - 0x10, 0xb0, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x53, 0x65, 0x72, 0x70, 0x53, 0x74, 0x61, 0x63, 0x6b, - 0x10, 0xb1, 0x04, 0x12, 0x12, 0x0a, 0x0d, 0x53, 0x6d, 0x61, 0x72, 0x74, 0x79, 0x53, 0x74, 0x72, - 0x65, 0x65, 0x74, 0x73, 0x10, 0xb2, 0x04, 0x12, 0x11, 0x0a, 0x0c, 0x54, 0x69, 0x63, 0x6b, 0x65, - 0x74, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x10, 0xb3, 0x04, 0x12, 0x12, 0x0a, 0x0d, 0x41, 0x76, - 0x69, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x10, 0xb4, 0x04, 0x12, 0x0d, - 0x0a, 0x08, 0x42, 0x6f, 0x6d, 0x62, 0x42, 0x6f, 0x6d, 0x62, 0x10, 0xb5, 0x04, 0x12, 0x10, 0x0a, - 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x64, 0x69, 0x74, 0x69, 0x65, 0x73, 0x10, 0xb6, 0x04, 0x12, - 0x0a, 0x0a, 0x05, 0x44, 0x66, 0x75, 0x73, 0x65, 0x10, 0xb7, 0x04, 0x12, 0x0b, 0x0a, 0x06, 0x45, - 0x64, 0x65, 0x6e, 0x41, 0x49, 0x10, 0xb8, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x47, 0x6c, 0x61, 0x73, - 0x73, 0x6e, 0x6f, 0x64, 0x65, 0x10, 0xb9, 0x04, 0x12, 0x09, 0x0a, 0x04, 0x47, 0x75, 0x72, 0x75, - 0x10, 0xba, 0x04, 0x12, 0x09, 0x0a, 0x04, 0x48, 0x69, 0x76, 0x65, 0x10, 0xbb, 0x04, 0x12, 0x0c, - 0x0a, 0x07, 0x48, 0x69, 0x76, 0x65, 0x61, 0x67, 0x65, 0x10, 0xbc, 0x04, 0x12, 0x0c, 0x0a, 0x07, - 0x4b, 0x69, 0x63, 0x6b, 0x62, 0x6f, 0x78, 0x10, 0xbd, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x50, 0x61, - 0x73, 0x73, 0x62, 0x61, 0x73, 0x65, 0x10, 0xbe, 0x04, 0x12, 0x0f, 0x0a, 0x0a, 0x50, 0x6f, 0x73, - 0x74, 0x61, 0x67, 0x65, 0x41, 0x70, 0x70, 0x10, 0xbf, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x50, 0x75, - 0x72, 0x65, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x10, 0xc0, 0x04, 0x12, 0x0b, 0x0a, 0x06, 0x51, 0x75, - 0x62, 0x6f, 0x6c, 0x65, 0x10, 0xc1, 0x04, 0x12, 0x14, 0x0a, 0x0f, 0x43, 0x61, 0x72, 0x62, 0x6f, - 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x10, 0xc2, 0x04, 0x12, 0x0d, 0x0a, - 0x08, 0x49, 0x6e, 0x74, 0x72, 0x69, 0x6e, 0x69, 0x6f, 0x10, 0xc3, 0x04, 0x12, 0x15, 0x0a, 0x0c, - 0x51, 0x75, 0x69, 0x63, 0x6b, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x10, 0xc4, 0x04, 0x1a, - 0x02, 0x08, 0x01, 0x12, 0x10, 0x0a, 0x0b, 0x53, 0x63, 0x72, 0x61, 0x70, 0x65, 0x53, 0x74, 0x61, - 0x63, 0x6b, 0x10, 0xc5, 0x04, 0x12, 0x19, 0x0a, 0x14, 0x54, 0x65, 0x63, 0x68, 0x6e, 0x69, 0x63, - 0x61, 0x6c, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x41, 0x70, 0x69, 0x10, 0xc6, 0x04, - 0x12, 0x0c, 0x0a, 0x07, 0x55, 0x72, 0x6c, 0x73, 0x63, 0x61, 0x6e, 0x10, 0xc7, 0x04, 0x12, 0x0e, - 0x0a, 0x09, 0x42, 0x61, 0x73, 0x65, 0x41, 0x70, 0x69, 0x49, 0x4f, 0x10, 0xc8, 0x04, 0x12, 0x0c, - 0x0a, 0x07, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x4f, 0x10, 0xc9, 0x04, 0x12, 0x08, 0x0a, 0x03, - 0x54, 0x4c, 0x79, 0x10, 0xca, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x63, - 0x75, 0x74, 0x10, 0xcb, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x41, 0x70, 0x70, 0x66, 0x6f, 0x6c, 0x6c, - 0x6f, 0x77, 0x10, 0xcc, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x54, 0x68, 0x69, 0x6e, 0x6b, 0x69, 0x66, - 0x69, 0x63, 0x10, 0xcd, 0x04, 0x12, 0x0b, 0x0a, 0x06, 0x46, 0x65, 0x65, 0x64, 0x6c, 0x79, 0x10, - 0xce, 0x04, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x74, 0x69, 0x74, 0x63, 0x68, 0x64, 0x61, 0x74, 0x61, - 0x10, 0xcf, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x46, 0x65, 0x74, 0x63, 0x68, 0x72, 0x73, 0x73, 0x10, - 0xd0, 0x04, 0x12, 0x11, 0x0a, 0x0c, 0x53, 0x69, 0x67, 0x6e, 0x75, 0x70, 0x67, 0x65, 0x6e, 0x69, - 0x75, 0x73, 0x10, 0xd1, 0x04, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, - 0x72, 0x69, 0x74, 0x10, 0xd2, 0x04, 0x12, 0x0f, 0x0a, 0x0a, 0x4f, 0x70, 0x74, 0x69, 0x6d, 0x69, - 0x7a, 0x65, 0x6c, 0x79, 0x10, 0xd3, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x4f, 0x63, 0x72, 0x53, 0x70, - 0x61, 0x63, 0x65, 0x10, 0xd4, 0x04, 0x12, 0x0f, 0x0a, 0x0a, 0x57, 0x65, 0x61, 0x74, 0x68, 0x65, - 0x72, 0x42, 0x69, 0x74, 0x10, 0xd5, 0x04, 0x12, 0x0c, 0x0a, 0x07, 0x42, 0x75, 0x64, 0x64, 0x79, - 0x4e, 0x53, 0x10, 0xd6, 0x04, 0x12, 0x0b, 0x0a, 0x06, 0x5a, 0x69, 0x70, 0x41, 0x50, 0x49, 0x10, - 0xd7, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x5a, 0x69, 0x70, 0x42, 0x6f, 0x6f, 0x6b, 0x73, 0x10, 0xd8, - 0x04, 0x12, 0x0c, 0x0a, 0x07, 0x4f, 0x6e, 0x65, 0x64, 0x65, 0x73, 0x6b, 0x10, 0xd9, 0x04, 0x12, - 0x0c, 0x0a, 0x07, 0x42, 0x75, 0x67, 0x68, 0x65, 0x72, 0x64, 0x10, 0xda, 0x04, 0x12, 0x0f, 0x0a, - 0x0a, 0x42, 0x6c, 0x61, 0x7a, 0x65, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x10, 0xdb, 0x04, 0x12, 0x0d, - 0x0a, 0x08, 0x41, 0x75, 0x74, 0x6f, 0x64, 0x65, 0x73, 0x6b, 0x10, 0xdc, 0x04, 0x12, 0x08, 0x0a, - 0x03, 0x54, 0x72, 0x75, 0x10, 0xdd, 0x04, 0x12, 0x0c, 0x0a, 0x07, 0x55, 0x6e, 0x69, 0x66, 0x79, - 0x49, 0x44, 0x10, 0xde, 0x04, 0x12, 0x0c, 0x0a, 0x07, 0x54, 0x72, 0x69, 0x6d, 0x62, 0x6c, 0x65, - 0x10, 0xdf, 0x04, 0x12, 0x0b, 0x0a, 0x06, 0x53, 0x6d, 0x6f, 0x6f, 0x63, 0x68, 0x10, 0xe0, 0x04, - 0x12, 0x0e, 0x0a, 0x09, 0x53, 0x65, 0x6d, 0x61, 0x70, 0x68, 0x6f, 0x72, 0x65, 0x10, 0xe1, 0x04, - 0x12, 0x0b, 0x0a, 0x06, 0x54, 0x65, 0x6c, 0x6e, 0x79, 0x78, 0x10, 0xe2, 0x04, 0x12, 0x0f, 0x0a, - 0x0a, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x77, 0x69, 0x72, 0x65, 0x10, 0xe3, 0x04, 0x12, 0x0e, - 0x0a, 0x09, 0x54, 0x65, 0x78, 0x74, 0x6d, 0x61, 0x67, 0x69, 0x63, 0x10, 0xe4, 0x04, 0x12, 0x0e, - 0x0a, 0x09, 0x53, 0x65, 0x72, 0x70, 0x68, 0x6f, 0x75, 0x73, 0x65, 0x10, 0xe5, 0x04, 0x12, 0x0b, - 0x0a, 0x06, 0x50, 0x6c, 0x61, 0x6e, 0x79, 0x6f, 0x10, 0xe6, 0x04, 0x12, 0x0f, 0x0a, 0x0a, 0x53, - 0x69, 0x6d, 0x70, 0x6c, 0x79, 0x62, 0x6f, 0x6f, 0x6b, 0x10, 0xe7, 0x04, 0x12, 0x09, 0x0a, 0x04, - 0x56, 0x79, 0x74, 0x65, 0x10, 0xe8, 0x04, 0x12, 0x0a, 0x0a, 0x05, 0x4e, 0x79, 0x6c, 0x61, 0x73, - 0x10, 0xe9, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x53, 0x71, 0x75, 0x61, 0x72, 0x65, 0x75, 0x70, 0x10, - 0xea, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x44, 0x61, 0x6e, 0x64, 0x65, 0x6c, 0x69, 0x6f, 0x6e, 0x10, - 0xeb, 0x04, 0x12, 0x11, 0x0a, 0x08, 0x44, 0x61, 0x74, 0x61, 0x46, 0x69, 0x72, 0x65, 0x10, 0xec, - 0x04, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x0b, 0x0a, 0x06, 0x44, 0x65, 0x65, 0x70, 0x41, 0x49, 0x10, - 0xed, 0x04, 0x12, 0x11, 0x0a, 0x0c, 0x4d, 0x65, 0x61, 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x6c, 0x6f, - 0x75, 0x64, 0x10, 0xee, 0x04, 0x12, 0x10, 0x0a, 0x0b, 0x4e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, - 0x6f, 0x41, 0x70, 0x69, 0x10, 0xef, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x53, 0x74, 0x6f, 0x72, 0x65, - 0x63, 0x6f, 0x76, 0x65, 0x10, 0xf0, 0x04, 0x12, 0x0c, 0x0a, 0x07, 0x53, 0x68, 0x69, 0x70, 0x64, - 0x61, 0x79, 0x10, 0xf1, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x53, 0x65, 0x6e, 0x74, 0x69, 0x6d, 0x65, - 0x6e, 0x74, 0x10, 0xf2, 0x04, 0x12, 0x18, 0x0a, 0x13, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, + 0x62, 0x6f, 0x74, 0x10, 0xf3, 0x01, 0x12, 0x13, 0x0a, 0x0a, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, + 0x63, 0x74, 0x49, 0x4f, 0x10, 0xf4, 0x01, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x0d, 0x0a, 0x08, 0x53, + 0x6b, 0x72, 0x61, 0x70, 0x70, 0x69, 0x6f, 0x10, 0xf5, 0x01, 0x12, 0x0b, 0x0a, 0x06, 0x4d, 0x6f, + 0x6e, 0x64, 0x61, 0x79, 0x10, 0xf6, 0x01, 0x12, 0x10, 0x0a, 0x0b, 0x53, 0x6d, 0x61, 0x72, 0x74, + 0x73, 0x68, 0x65, 0x65, 0x74, 0x73, 0x10, 0xf7, 0x01, 0x12, 0x0a, 0x0a, 0x05, 0x57, 0x72, 0x69, + 0x6b, 0x65, 0x10, 0xf8, 0x01, 0x12, 0x0a, 0x0a, 0x05, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x10, 0xf9, + 0x01, 0x12, 0x0d, 0x0a, 0x08, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x6b, 0x69, 0x74, 0x10, 0xfa, 0x01, + 0x12, 0x0f, 0x0a, 0x0a, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x6f, 0x6d, 0x61, 0x74, 0x10, 0xfb, + 0x01, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x61, 0x6c, 0x65, 0x73, 0x62, 0x6c, 0x69, 0x6e, 0x6b, 0x10, + 0xfc, 0x01, 0x12, 0x0a, 0x0a, 0x05, 0x42, 0x6f, 0x72, 0x65, 0x64, 0x10, 0xfd, 0x01, 0x12, 0x0c, + 0x0a, 0x07, 0x43, 0x61, 0x6d, 0x70, 0x61, 0x79, 0x6e, 0x10, 0xfe, 0x01, 0x12, 0x0e, 0x0a, 0x09, + 0x43, 0x6c, 0x69, 0x6e, 0x63, 0x68, 0x70, 0x61, 0x64, 0x10, 0xff, 0x01, 0x12, 0x0f, 0x0a, 0x0a, + 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x48, 0x75, 0x62, 0x10, 0x80, 0x02, 0x12, 0x0d, 0x0a, + 0x08, 0x44, 0x65, 0x62, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x10, 0x81, 0x02, 0x12, 0x0d, 0x0a, 0x08, + 0x44, 0x79, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x10, 0x82, 0x02, 0x12, 0x10, 0x0a, 0x0b, 0x47, + 0x75, 0x61, 0x72, 0x64, 0x69, 0x61, 0x6e, 0x61, 0x70, 0x69, 0x10, 0x83, 0x02, 0x12, 0x0c, 0x0a, + 0x07, 0x48, 0x61, 0x72, 0x76, 0x65, 0x73, 0x74, 0x10, 0x84, 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x4d, + 0x6f, 0x6f, 0x73, 0x65, 0x6e, 0x64, 0x10, 0x85, 0x02, 0x12, 0x10, 0x0a, 0x0b, 0x4f, 0x70, 0x65, + 0x6e, 0x57, 0x65, 0x61, 0x74, 0x68, 0x65, 0x72, 0x10, 0x86, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x53, + 0x69, 0x74, 0x65, 0x6c, 0x65, 0x61, 0x66, 0x10, 0x87, 0x02, 0x12, 0x10, 0x0a, 0x0b, 0x53, 0x71, + 0x75, 0x61, 0x72, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x10, 0x88, 0x02, 0x12, 0x0c, 0x0a, 0x07, + 0x46, 0x6c, 0x6f, 0x77, 0x46, 0x6c, 0x75, 0x10, 0x89, 0x02, 0x12, 0x0b, 0x0a, 0x06, 0x4e, 0x69, + 0x6d, 0x62, 0x6c, 0x65, 0x10, 0x8a, 0x02, 0x12, 0x14, 0x0a, 0x0f, 0x4c, 0x65, 0x73, 0x73, 0x41, + 0x6e, 0x6e, 0x6f, 0x79, 0x69, 0x6e, 0x67, 0x43, 0x52, 0x4d, 0x10, 0x8b, 0x02, 0x12, 0x0c, 0x0a, + 0x07, 0x4e, 0x65, 0x74, 0x68, 0x75, 0x6e, 0x74, 0x10, 0x8c, 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x41, + 0x70, 0x70, 0x74, 0x69, 0x76, 0x6f, 0x10, 0x8d, 0x02, 0x12, 0x0f, 0x0a, 0x0a, 0x43, 0x61, 0x70, + 0x73, 0x75, 0x6c, 0x65, 0x43, 0x52, 0x4d, 0x10, 0x8e, 0x02, 0x12, 0x0e, 0x0a, 0x09, 0x49, 0x6e, + 0x73, 0x69, 0x67, 0x68, 0x74, 0x6c, 0x79, 0x10, 0x8f, 0x02, 0x12, 0x0a, 0x0a, 0x05, 0x4b, 0x79, + 0x6c, 0x61, 0x73, 0x10, 0x90, 0x02, 0x12, 0x0f, 0x0a, 0x0a, 0x4f, 0x6e, 0x65, 0x70, 0x61, 0x67, + 0x65, 0x43, 0x52, 0x4d, 0x10, 0x91, 0x02, 0x12, 0x09, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x10, + 0x92, 0x02, 0x12, 0x10, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x43, 0x52, + 0x4d, 0x10, 0x93, 0x02, 0x12, 0x18, 0x0a, 0x13, 0x52, 0x65, 0x61, 0x6c, 0x6c, 0x79, 0x53, 0x69, + 0x6d, 0x70, 0x6c, 0x65, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x10, 0x94, 0x02, 0x12, 0x0c, + 0x0a, 0x07, 0x41, 0x69, 0x72, 0x73, 0x68, 0x69, 0x70, 0x10, 0x95, 0x02, 0x12, 0x0a, 0x0a, 0x05, + 0x41, 0x72, 0x74, 0x73, 0x79, 0x10, 0x96, 0x02, 0x12, 0x0b, 0x0a, 0x06, 0x59, 0x61, 0x6e, 0x64, + 0x65, 0x78, 0x10, 0x97, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x43, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x66, + 0x79, 0x10, 0x98, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x44, 0x6e, 0x73, 0x63, 0x68, 0x65, 0x63, 0x6b, + 0x10, 0x99, 0x02, 0x12, 0x10, 0x0a, 0x0b, 0x45, 0x61, 0x73, 0x79, 0x49, 0x6e, 0x73, 0x69, 0x67, + 0x68, 0x74, 0x10, 0x9a, 0x02, 0x12, 0x0e, 0x0a, 0x09, 0x45, 0x74, 0x68, 0x70, 0x6c, 0x6f, 0x72, + 0x65, 0x72, 0x10, 0x9b, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x45, 0x76, 0x65, 0x72, 0x68, 0x6f, 0x75, + 0x72, 0x10, 0x9c, 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x46, 0x75, 0x6c, 0x63, 0x72, 0x75, 0x6d, 0x10, + 0x9d, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x47, 0x65, 0x6f, 0x49, 0x70, 0x69, 0x66, 0x69, 0x10, 0x9e, + 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x4a, 0x6f, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x10, 0x9f, 0x02, 0x12, + 0x0c, 0x0a, 0x07, 0x52, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x72, 0x10, 0xa0, 0x02, 0x12, 0x10, 0x0a, + 0x0b, 0x54, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x61, 0x70, 0x69, 0x10, 0xa1, 0x02, 0x12, + 0x0f, 0x0a, 0x0a, 0x54, 0x6f, 0x67, 0x67, 0x6c, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x10, 0xa2, 0x02, + 0x12, 0x0b, 0x0a, 0x06, 0x56, 0x70, 0x6e, 0x61, 0x70, 0x69, 0x10, 0xa3, 0x02, 0x12, 0x0e, 0x0a, + 0x09, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x10, 0xa4, 0x02, 0x12, 0x0b, 0x0a, + 0x06, 0x41, 0x70, 0x6f, 0x6c, 0x6c, 0x6f, 0x10, 0xa5, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x45, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x67, 0x6e, 0x10, 0xa6, 0x02, 0x12, 0x09, 0x0a, 0x04, 0x4a, 0x75, 0x72, + 0x6f, 0x10, 0xa7, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x4b, 0x61, 0x72, 0x6d, 0x61, 0x43, 0x52, 0x4d, + 0x10, 0xa8, 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x6c, 0x6f, 0x10, 0xa9, + 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x50, 0x61, 0x6e, 0x64, 0x61, 0x64, 0x6f, 0x63, 0x10, 0xaa, 0x02, + 0x12, 0x0e, 0x0a, 0x09, 0x52, 0x65, 0x76, 0x61, 0x6d, 0x70, 0x43, 0x52, 0x4d, 0x10, 0xab, 0x02, + 0x12, 0x10, 0x0a, 0x0b, 0x53, 0x61, 0x6c, 0x65, 0x73, 0x63, 0x6f, 0x6f, 0x6b, 0x69, 0x65, 0x10, + 0xac, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x41, 0x6c, 0x63, 0x6f, 0x6e, 0x6f, 0x73, 0x74, 0x10, 0xad, + 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x42, 0x6c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x10, 0xae, 0x02, 0x12, + 0x10, 0x0a, 0x0b, 0x41, 0x63, 0x63, 0x75, 0x77, 0x65, 0x61, 0x74, 0x68, 0x65, 0x72, 0x10, 0xaf, + 0x02, 0x12, 0x0f, 0x0a, 0x0a, 0x4f, 0x70, 0x65, 0x6e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x72, 0x10, + 0xb0, 0x02, 0x12, 0x09, 0x0a, 0x04, 0x52, 0x61, 0x77, 0x67, 0x10, 0xb1, 0x02, 0x12, 0x0e, 0x0a, + 0x09, 0x52, 0x69, 0x6f, 0x74, 0x67, 0x61, 0x6d, 0x65, 0x73, 0x10, 0xb2, 0x02, 0x12, 0x0d, 0x0a, + 0x08, 0x52, 0x6f, 0x6e, 0x69, 0x6e, 0x41, 0x70, 0x70, 0x10, 0xb3, 0x02, 0x12, 0x0f, 0x0a, 0x0a, + 0x53, 0x74, 0x6f, 0x72, 0x6d, 0x67, 0x6c, 0x61, 0x73, 0x73, 0x10, 0xb4, 0x02, 0x12, 0x0b, 0x0a, + 0x06, 0x54, 0x6f, 0x6d, 0x74, 0x6f, 0x6d, 0x10, 0xb5, 0x02, 0x12, 0x0b, 0x0a, 0x06, 0x54, 0x77, + 0x69, 0x74, 0x63, 0x68, 0x10, 0xb6, 0x02, 0x12, 0x0b, 0x0a, 0x06, 0x44, 0x6f, 0x63, 0x75, 0x6d, + 0x6f, 0x10, 0xb7, 0x02, 0x12, 0x0e, 0x0a, 0x09, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x77, 0x61, 0x79, + 0x73, 0x10, 0xb8, 0x02, 0x12, 0x0f, 0x0a, 0x0a, 0x56, 0x65, 0x65, 0x76, 0x61, 0x76, 0x61, 0x75, + 0x6c, 0x74, 0x10, 0xb9, 0x02, 0x12, 0x10, 0x0a, 0x0b, 0x4b, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x10, 0xba, 0x02, 0x12, 0x17, 0x0a, 0x12, 0x53, 0x68, 0x6f, 0x70, 0x65, + 0x65, 0x4f, 0x70, 0x65, 0x6e, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x10, 0xbb, 0x02, + 0x12, 0x0f, 0x0a, 0x0a, 0x54, 0x65, 0x61, 0x6d, 0x56, 0x69, 0x65, 0x77, 0x65, 0x72, 0x10, 0xbc, + 0x02, 0x12, 0x0b, 0x0a, 0x06, 0x42, 0x75, 0x6c, 0x62, 0x75, 0x6c, 0x10, 0xbd, 0x02, 0x12, 0x16, + 0x0a, 0x11, 0x43, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x43, 0x52, 0x4d, 0x10, 0xbe, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x54, 0x65, 0x61, 0x6d, 0x67, 0x61, + 0x74, 0x65, 0x10, 0xbf, 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x41, 0x78, 0x6f, 0x6e, 0x61, 0x75, 0x74, + 0x10, 0xc0, 0x02, 0x12, 0x0b, 0x0a, 0x06, 0x54, 0x79, 0x6e, 0x74, 0x65, 0x63, 0x10, 0xc1, 0x02, + 0x12, 0x0c, 0x0a, 0x07, 0x41, 0x70, 0x70, 0x63, 0x75, 0x65, 0x73, 0x10, 0xc2, 0x02, 0x12, 0x0e, + 0x0a, 0x09, 0x41, 0x75, 0x74, 0x6f, 0x6b, 0x6c, 0x6f, 0x73, 0x65, 0x10, 0xc3, 0x02, 0x12, 0x0e, + 0x0a, 0x09, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x70, 0x6c, 0x61, 0x6e, 0x10, 0xc4, 0x02, 0x12, 0x0e, + 0x0a, 0x09, 0x44, 0x6f, 0x74, 0x6d, 0x61, 0x69, 0x6c, 0x65, 0x72, 0x10, 0xc5, 0x02, 0x12, 0x0d, + 0x0a, 0x08, 0x47, 0x65, 0x74, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x10, 0xc6, 0x02, 0x12, 0x0e, 0x0a, + 0x09, 0x47, 0x65, 0x74, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x73, 0x10, 0xc7, 0x02, 0x12, 0x0c, 0x0a, + 0x07, 0x4b, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x10, 0xc8, 0x02, 0x12, 0x0f, 0x0a, 0x0a, 0x4c, + 0x65, 0x61, 0x64, 0x66, 0x65, 0x65, 0x64, 0x65, 0x72, 0x10, 0xc9, 0x02, 0x12, 0x0a, 0x0a, 0x05, + 0x52, 0x61, 0x76, 0x65, 0x6e, 0x10, 0xca, 0x02, 0x12, 0x10, 0x0a, 0x0b, 0x52, 0x6f, 0x63, 0x6b, + 0x65, 0x74, 0x52, 0x65, 0x61, 0x63, 0x68, 0x10, 0xcb, 0x02, 0x12, 0x0b, 0x0a, 0x06, 0x55, 0x70, + 0x6c, 0x65, 0x61, 0x64, 0x10, 0xcc, 0x02, 0x12, 0x0f, 0x0a, 0x0a, 0x42, 0x72, 0x61, 0x6e, 0x64, + 0x66, 0x65, 0x74, 0x63, 0x68, 0x10, 0xcd, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x43, 0x6c, 0x65, 0x61, + 0x72, 0x62, 0x69, 0x74, 0x10, 0xce, 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x43, 0x72, 0x6f, 0x77, 0x64, + 0x69, 0x6e, 0x10, 0xcf, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x4d, 0x61, 0x70, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x10, 0xd0, 0x02, 0x12, 0x0f, 0x0a, 0x0a, 0x4e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x61, 0x62, + 0x6c, 0x65, 0x10, 0xd1, 0x02, 0x12, 0x0b, 0x0a, 0x06, 0x4f, 0x6e, 0x62, 0x75, 0x6b, 0x61, 0x10, + 0xd2, 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x54, 0x6f, 0x64, 0x6f, 0x69, 0x73, 0x74, 0x10, 0xd3, 0x02, + 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x74, 0x6f, 0x72, 0x79, 0x63, 0x68, 0x69, 0x65, 0x66, 0x10, 0xd4, + 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x4c, 0x69, 0x6e, 0x6b, 0x65, 0x64, 0x49, 0x6e, 0x10, 0xd5, 0x02, + 0x12, 0x0c, 0x0a, 0x07, 0x59, 0x6f, 0x75, 0x53, 0x69, 0x67, 0x6e, 0x10, 0xd6, 0x02, 0x12, 0x0b, + 0x0a, 0x06, 0x44, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x10, 0xd7, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x54, + 0x65, 0x6c, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x10, 0xd8, 0x02, 0x12, 0x10, 0x0a, 0x0b, 0x53, 0x70, + 0x6f, 0x6f, 0x6e, 0x61, 0x63, 0x75, 0x6c, 0x61, 0x72, 0x10, 0xd9, 0x02, 0x12, 0x11, 0x0a, 0x0c, + 0x41, 0x65, 0x72, 0x69, 0x73, 0x77, 0x65, 0x61, 0x74, 0x68, 0x65, 0x72, 0x10, 0xda, 0x02, 0x12, + 0x11, 0x0a, 0x0c, 0x41, 0x6c, 0x70, 0x68, 0x61, 0x76, 0x61, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x10, + 0xdb, 0x02, 0x12, 0x0a, 0x0a, 0x05, 0x49, 0x6d, 0x67, 0x75, 0x72, 0x10, 0xdc, 0x02, 0x12, 0x0b, + 0x0a, 0x06, 0x49, 0x6d, 0x61, 0x67, 0x67, 0x61, 0x10, 0xdd, 0x02, 0x12, 0x0b, 0x0a, 0x06, 0x53, + 0x4d, 0x53, 0x41, 0x70, 0x69, 0x10, 0xde, 0x02, 0x12, 0x11, 0x0a, 0x0c, 0x44, 0x69, 0x73, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x73, 0x69, 0x6f, 0x6e, 0x10, 0xdf, 0x02, 0x12, 0x12, 0x0a, 0x09, 0x42, + 0x6c, 0x61, 0x62, 0x6c, 0x61, 0x62, 0x75, 0x73, 0x10, 0xe0, 0x02, 0x1a, 0x02, 0x08, 0x01, 0x12, + 0x0d, 0x0a, 0x08, 0x57, 0x6f, 0x72, 0x64, 0x73, 0x41, 0x70, 0x69, 0x10, 0xe1, 0x02, 0x12, 0x12, + 0x0a, 0x0d, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x10, + 0xe2, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x48, 0x74, 0x6d, 0x6c, 0x32, 0x50, 0x64, 0x66, 0x10, 0xe3, + 0x02, 0x12, 0x12, 0x0a, 0x0d, 0x49, 0x50, 0x47, 0x65, 0x6f, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x10, 0xe4, 0x02, 0x12, 0x0b, 0x0a, 0x06, 0x4f, 0x77, 0x6c, 0x62, 0x6f, 0x74, 0x10, + 0xe5, 0x02, 0x12, 0x11, 0x0a, 0x0c, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x6d, 0x65, 0x72, 0x73, 0x69, + 0x76, 0x65, 0x10, 0xe6, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x44, 0x79, 0x6e, 0x61, 0x6c, 0x69, 0x73, + 0x74, 0x10, 0xe7, 0x02, 0x12, 0x14, 0x0a, 0x0f, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x52, 0x61, 0x74, 0x65, 0x41, 0x50, 0x49, 0x10, 0xe8, 0x02, 0x12, 0x0f, 0x0a, 0x0a, 0x48, 0x6f, + 0x6c, 0x69, 0x64, 0x61, 0x79, 0x41, 0x50, 0x49, 0x10, 0xe9, 0x02, 0x12, 0x0a, 0x0a, 0x05, 0x49, + 0x70, 0x61, 0x70, 0x69, 0x10, 0xea, 0x02, 0x12, 0x10, 0x0a, 0x0b, 0x4d, 0x61, 0x72, 0x6b, 0x65, + 0x74, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x10, 0xeb, 0x02, 0x12, 0x10, 0x0a, 0x0b, 0x4e, 0x75, 0x74, + 0x72, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x69, 0x78, 0x10, 0xec, 0x02, 0x12, 0x0a, 0x0a, 0x05, 0x53, + 0x77, 0x65, 0x6c, 0x6c, 0x10, 0xed, 0x02, 0x12, 0x19, 0x0a, 0x14, 0x43, 0x6c, 0x69, 0x63, 0x6b, + 0x75, 0x70, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, + 0xee, 0x02, 0x12, 0x0a, 0x0a, 0x05, 0x4e, 0x69, 0x74, 0x72, 0x6f, 0x10, 0xef, 0x02, 0x12, 0x08, + 0x0a, 0x03, 0x52, 0x65, 0x76, 0x10, 0xf0, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x52, 0x75, 0x6e, 0x52, + 0x75, 0x6e, 0x49, 0x74, 0x10, 0xf1, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x54, 0x79, 0x70, 0x65, 0x66, + 0x6f, 0x72, 0x6d, 0x10, 0xf2, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x4d, 0x69, 0x78, 0x70, 0x61, 0x6e, + 0x65, 0x6c, 0x10, 0xf3, 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x54, 0x72, 0x61, 0x64, 0x69, 0x65, 0x72, + 0x10, 0xf4, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x72, 0x10, + 0xf5, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x56, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x79, 0x10, 0xf6, + 0x02, 0x12, 0x0b, 0x0a, 0x06, 0x41, 0x6c, 0x65, 0x67, 0x72, 0x61, 0x10, 0xf7, 0x02, 0x12, 0x09, + 0x0a, 0x04, 0x41, 0x75, 0x64, 0x64, 0x10, 0xf8, 0x02, 0x12, 0x10, 0x0a, 0x0b, 0x42, 0x61, 0x72, + 0x65, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x10, 0xf9, 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x43, + 0x6f, 0x69, 0x6e, 0x6c, 0x69, 0x62, 0x10, 0xfa, 0x02, 0x12, 0x15, 0x0a, 0x10, 0x45, 0x78, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x61, 0x74, 0x65, 0x73, 0x41, 0x50, 0x49, 0x10, 0xfb, 0x02, + 0x12, 0x12, 0x0a, 0x0d, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x63, 0x6f, 0x6f, + 0x70, 0x10, 0xfc, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x46, 0x58, 0x4d, 0x61, 0x72, 0x6b, 0x65, 0x74, + 0x10, 0xfd, 0x02, 0x12, 0x12, 0x0a, 0x0d, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, + 0x6c, 0x6f, 0x75, 0x64, 0x10, 0xfe, 0x02, 0x12, 0x0e, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x47, 0x65, + 0x6f, 0x41, 0x50, 0x49, 0x10, 0xff, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x41, 0x62, 0x73, 0x74, 0x72, + 0x61, 0x63, 0x74, 0x10, 0x80, 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x42, 0x69, 0x6c, 0x6c, 0x6f, 0x6d, + 0x61, 0x74, 0x10, 0x81, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x44, 0x6f, 0x76, 0x69, 0x63, 0x6f, 0x10, + 0x82, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x42, 0x69, 0x74, 0x62, 0x61, 0x72, 0x10, 0x83, 0x03, 0x12, + 0x0c, 0x0a, 0x07, 0x42, 0x75, 0x67, 0x73, 0x6e, 0x61, 0x67, 0x10, 0x84, 0x03, 0x12, 0x0f, 0x0a, + 0x0a, 0x41, 0x73, 0x73, 0x65, 0x6d, 0x62, 0x6c, 0x79, 0x41, 0x49, 0x10, 0x85, 0x03, 0x12, 0x0f, + 0x0a, 0x0a, 0x41, 0x64, 0x61, 0x66, 0x72, 0x75, 0x69, 0x74, 0x49, 0x4f, 0x10, 0x86, 0x03, 0x12, + 0x0a, 0x0a, 0x05, 0x41, 0x70, 0x69, 0x66, 0x79, 0x10, 0x87, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x43, + 0x6f, 0x69, 0x6e, 0x47, 0x65, 0x63, 0x6b, 0x6f, 0x10, 0x88, 0x03, 0x12, 0x12, 0x0a, 0x0d, 0x43, + 0x72, 0x79, 0x70, 0x74, 0x6f, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x65, 0x10, 0x89, 0x03, 0x12, + 0x0e, 0x0a, 0x09, 0x46, 0x75, 0x6c, 0x6c, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x10, 0x8a, 0x03, 0x12, + 0x0e, 0x0a, 0x09, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x53, 0x69, 0x67, 0x6e, 0x10, 0x8b, 0x03, 0x12, + 0x0d, 0x0a, 0x08, 0x4c, 0x6f, 0x79, 0x76, 0x65, 0x72, 0x73, 0x65, 0x10, 0x8c, 0x03, 0x12, 0x0c, + 0x0a, 0x07, 0x4e, 0x65, 0x74, 0x43, 0x6f, 0x72, 0x65, 0x10, 0x8d, 0x03, 0x12, 0x0e, 0x0a, 0x09, + 0x53, 0x61, 0x75, 0x63, 0x65, 0x4c, 0x61, 0x62, 0x73, 0x10, 0x8e, 0x03, 0x12, 0x0f, 0x0a, 0x0a, + 0x41, 0x6c, 0x69, 0x65, 0x6e, 0x56, 0x61, 0x75, 0x6c, 0x74, 0x10, 0x8f, 0x03, 0x12, 0x0d, 0x0a, + 0x08, 0x41, 0x70, 0x69, 0x66, 0x6c, 0x61, 0x73, 0x68, 0x10, 0x91, 0x03, 0x12, 0x0e, 0x0a, 0x09, + 0x43, 0x6f, 0x69, 0x6e, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x10, 0x92, 0x03, 0x12, 0x10, 0x0a, 0x0b, + 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x41, 0x50, 0x49, 0x10, 0x93, 0x03, 0x12, 0x0c, + 0x0a, 0x07, 0x44, 0x61, 0x74, 0x61, 0x47, 0x6f, 0x76, 0x10, 0x94, 0x03, 0x12, 0x0b, 0x0a, 0x06, + 0x45, 0x6e, 0x69, 0x67, 0x6d, 0x61, 0x10, 0x95, 0x03, 0x12, 0x1a, 0x0a, 0x15, 0x46, 0x69, 0x6e, + 0x61, 0x6e, 0x63, 0x69, 0x61, 0x6c, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x69, 0x6e, 0x67, 0x50, 0x72, + 0x65, 0x70, 0x10, 0x96, 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x47, 0x65, 0x6f, 0x63, 0x6f, 0x64, 0x69, + 0x6f, 0x10, 0x97, 0x03, 0x12, 0x0c, 0x0a, 0x07, 0x48, 0x65, 0x72, 0x65, 0x41, 0x50, 0x49, 0x10, + 0x98, 0x03, 0x12, 0x0f, 0x0a, 0x0a, 0x4d, 0x61, 0x63, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x10, 0x99, 0x03, 0x12, 0x0c, 0x0a, 0x07, 0x4f, 0x4f, 0x50, 0x53, 0x70, 0x61, 0x6d, 0x10, 0x9a, + 0x03, 0x12, 0x10, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x73, 0x49, 0x4f, + 0x10, 0x9b, 0x03, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x63, 0x72, 0x61, 0x70, 0x65, 0x72, 0x41, 0x50, + 0x49, 0x10, 0x9c, 0x03, 0x12, 0x13, 0x0a, 0x0e, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, + 0x54, 0x72, 0x61, 0x69, 0x6c, 0x73, 0x10, 0x9d, 0x03, 0x12, 0x0f, 0x0a, 0x0a, 0x54, 0x6f, 0x6d, + 0x6f, 0x72, 0x72, 0x6f, 0x77, 0x49, 0x4f, 0x10, 0x9e, 0x03, 0x12, 0x13, 0x0a, 0x0e, 0x57, 0x6f, + 0x72, 0x6c, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x10, 0x9f, 0x03, 0x12, + 0x11, 0x0a, 0x0c, 0x46, 0x61, 0x63, 0x65, 0x50, 0x6c, 0x75, 0x73, 0x50, 0x6c, 0x75, 0x73, 0x10, + 0xa0, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x56, 0x6f, 0x69, 0x63, 0x65, 0x67, 0x61, 0x69, 0x6e, 0x10, + 0xa1, 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x44, 0x65, 0x65, 0x70, 0x67, 0x72, 0x61, 0x6d, 0x10, 0xa2, + 0x03, 0x12, 0x13, 0x0a, 0x0e, 0x56, 0x69, 0x73, 0x75, 0x61, 0x6c, 0x43, 0x72, 0x6f, 0x73, 0x73, + 0x69, 0x6e, 0x67, 0x10, 0xa3, 0x03, 0x12, 0x0c, 0x0a, 0x07, 0x46, 0x69, 0x6e, 0x6e, 0x68, 0x75, + 0x62, 0x10, 0xa4, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x54, 0x69, 0x69, 0x6e, 0x67, 0x6f, 0x10, 0xa5, + 0x03, 0x12, 0x10, 0x0a, 0x0b, 0x52, 0x69, 0x6e, 0x67, 0x43, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, + 0x10, 0xa6, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x46, 0x69, 0x6e, 0x61, 0x67, 0x65, 0x10, 0xa7, 0x03, + 0x12, 0x0b, 0x0a, 0x06, 0x45, 0x64, 0x61, 0x6d, 0x61, 0x6d, 0x10, 0xa8, 0x03, 0x12, 0x10, 0x0a, + 0x0b, 0x48, 0x79, 0x70, 0x65, 0x41, 0x75, 0x64, 0x69, 0x74, 0x6f, 0x72, 0x10, 0xa9, 0x03, 0x12, + 0x0a, 0x0a, 0x05, 0x47, 0x65, 0x6e, 0x67, 0x6f, 0x10, 0xaa, 0x03, 0x12, 0x0a, 0x0a, 0x05, 0x46, + 0x72, 0x6f, 0x6e, 0x74, 0x10, 0xab, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x46, 0x6c, 0x65, 0x65, 0x74, + 0x62, 0x61, 0x73, 0x65, 0x10, 0xac, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x42, 0x75, 0x62, 0x62, 0x6c, + 0x65, 0x10, 0xad, 0x03, 0x12, 0x0f, 0x0a, 0x0a, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x62, 0x65, + 0x61, 0x72, 0x10, 0xae, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x41, 0x64, 0x7a, 0x75, 0x6e, 0x61, 0x10, + 0xaf, 0x03, 0x12, 0x13, 0x0a, 0x0e, 0x42, 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, 0x41, 0x76, 0x65, + 0x72, 0x61, 0x67, 0x65, 0x10, 0xb0, 0x03, 0x12, 0x0f, 0x0a, 0x0a, 0x43, 0x6f, 0x6d, 0x6d, 0x65, + 0x72, 0x63, 0x65, 0x4a, 0x53, 0x10, 0xb1, 0x03, 0x12, 0x13, 0x0a, 0x0e, 0x44, 0x65, 0x74, 0x65, + 0x63, 0x74, 0x4c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x10, 0xb2, 0x03, 0x12, 0x11, 0x0a, + 0x08, 0x46, 0x61, 0x6b, 0x65, 0x4a, 0x53, 0x4f, 0x4e, 0x10, 0xb3, 0x03, 0x1a, 0x02, 0x08, 0x01, + 0x12, 0x10, 0x0a, 0x0b, 0x47, 0x72, 0x61, 0x70, 0x68, 0x68, 0x6f, 0x70, 0x70, 0x65, 0x72, 0x10, + 0xb4, 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x4c, 0x65, 0x78, 0x69, 0x67, 0x72, 0x61, 0x6d, 0x10, 0xb5, + 0x03, 0x12, 0x10, 0x0a, 0x0b, 0x4c, 0x69, 0x6e, 0x6b, 0x50, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, + 0x10, 0xb6, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x4e, 0x75, 0x6d, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, + 0x10, 0xb7, 0x03, 0x12, 0x0f, 0x0a, 0x0a, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x43, 0x72, 0x61, 0x77, + 0x6c, 0x10, 0xb8, 0x03, 0x12, 0x0f, 0x0a, 0x0a, 0x5a, 0x69, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x41, + 0x50, 0x49, 0x10, 0xb9, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x43, 0x6f, 0x6d, 0x65, 0x74, 0x63, 0x68, + 0x61, 0x74, 0x10, 0xba, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x4b, 0x65, 0x79, 0x67, 0x65, 0x6e, 0x10, + 0xbb, 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x4d, 0x69, 0x78, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x10, 0xbc, + 0x03, 0x12, 0x0c, 0x0a, 0x07, 0x54, 0x61, 0x74, 0x75, 0x6d, 0x49, 0x4f, 0x10, 0xbd, 0x03, 0x12, + 0x0c, 0x0a, 0x07, 0x54, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x10, 0xbe, 0x03, 0x12, 0x0b, 0x0a, + 0x06, 0x4c, 0x61, 0x73, 0x74, 0x66, 0x6d, 0x10, 0xbf, 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x42, 0x72, + 0x6f, 0x77, 0x73, 0x68, 0x6f, 0x74, 0x10, 0xc0, 0x03, 0x12, 0x0c, 0x0a, 0x07, 0x4a, 0x53, 0x4f, + 0x4e, 0x62, 0x69, 0x6e, 0x10, 0xc1, 0x03, 0x12, 0x0f, 0x0a, 0x0a, 0x4c, 0x6f, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x49, 0x51, 0x10, 0xc2, 0x03, 0x12, 0x12, 0x0a, 0x0d, 0x53, 0x63, 0x72, 0x65, + 0x65, 0x6e, 0x73, 0x68, 0x6f, 0x74, 0x41, 0x50, 0x49, 0x10, 0xc3, 0x03, 0x12, 0x11, 0x0a, 0x0c, + 0x57, 0x65, 0x61, 0x74, 0x68, 0x65, 0x72, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x10, 0xc4, 0x03, 0x12, + 0x0c, 0x0a, 0x07, 0x41, 0x6d, 0x61, 0x64, 0x65, 0x75, 0x73, 0x10, 0xc5, 0x03, 0x12, 0x0f, 0x0a, + 0x0a, 0x46, 0x6f, 0x75, 0x72, 0x53, 0x71, 0x75, 0x61, 0x72, 0x65, 0x10, 0xc6, 0x03, 0x12, 0x0b, + 0x0a, 0x06, 0x46, 0x6c, 0x69, 0x63, 0x6b, 0x72, 0x10, 0xc7, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x43, + 0x6c, 0x69, 0x63, 0x6b, 0x48, 0x65, 0x6c, 0x70, 0x10, 0xc8, 0x03, 0x12, 0x0a, 0x0a, 0x05, 0x41, + 0x6d, 0x62, 0x65, 0x65, 0x10, 0xc9, 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x41, 0x70, 0x69, 0x32, 0x43, + 0x61, 0x72, 0x74, 0x10, 0xca, 0x03, 0x12, 0x0f, 0x0a, 0x0a, 0x48, 0x79, 0x70, 0x65, 0x72, 0x74, + 0x72, 0x61, 0x63, 0x6b, 0x10, 0xcb, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x4b, 0x61, 0x6b, 0x61, 0x6f, + 0x54, 0x61, 0x6c, 0x6b, 0x10, 0xcc, 0x03, 0x12, 0x0c, 0x0a, 0x07, 0x52, 0x69, 0x74, 0x65, 0x4b, + 0x69, 0x74, 0x10, 0xcd, 0x03, 0x12, 0x11, 0x0a, 0x0c, 0x53, 0x68, 0x75, 0x74, 0x74, 0x65, 0x72, + 0x73, 0x74, 0x6f, 0x63, 0x6b, 0x10, 0xce, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x54, 0x65, 0x78, 0x74, + 0x32, 0x44, 0x61, 0x74, 0x61, 0x10, 0xcf, 0x03, 0x12, 0x13, 0x0a, 0x0e, 0x59, 0x6f, 0x75, 0x4e, + 0x65, 0x65, 0x64, 0x41, 0x42, 0x75, 0x64, 0x67, 0x65, 0x74, 0x10, 0xd0, 0x03, 0x12, 0x0c, 0x0a, + 0x07, 0x43, 0x72, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x10, 0xd1, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x46, + 0x69, 0x6c, 0x65, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x10, 0xd2, 0x03, 0x12, 0x0a, 0x0a, 0x05, 0x47, + 0x79, 0x61, 0x7a, 0x6f, 0x10, 0xd3, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x4d, 0x61, 0x76, 0x65, 0x6e, + 0x6c, 0x69, 0x6e, 0x6b, 0x10, 0xd4, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x53, 0x68, 0x65, 0x65, 0x74, + 0x79, 0x10, 0xd5, 0x03, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x6d, 0x6f, + 0x6e, 0x6b, 0x10, 0xd6, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x64, 0x61, + 0x74, 0x61, 0x10, 0xd7, 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x55, 0x6e, 0x73, 0x70, 0x6c, 0x61, 0x73, + 0x68, 0x10, 0xd8, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x41, 0x6c, 0x6c, 0x73, 0x70, 0x6f, 0x72, 0x74, + 0x73, 0x10, 0xd9, 0x03, 0x12, 0x11, 0x0a, 0x0c, 0x43, 0x61, 0x6c, 0x6f, 0x72, 0x69, 0x65, 0x4e, + 0x69, 0x6e, 0x6a, 0x61, 0x10, 0xda, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x57, 0x61, 0x6c, 0x6b, 0x53, + 0x63, 0x6f, 0x72, 0x65, 0x10, 0xdb, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x53, 0x74, 0x72, 0x61, 0x76, + 0x61, 0x10, 0xdc, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x43, 0x69, 0x63, 0x65, 0x72, 0x6f, 0x10, 0xdd, + 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x49, 0x50, 0x51, 0x75, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x10, 0xde, + 0x03, 0x12, 0x11, 0x0a, 0x0c, 0x50, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x44, 0x6f, 0x74, + 0x73, 0x10, 0xdf, 0x03, 0x12, 0x0c, 0x0a, 0x07, 0x52, 0x6f, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x10, + 0xe0, 0x03, 0x12, 0x0c, 0x0a, 0x07, 0x4d, 0x61, 0x69, 0x6c, 0x73, 0x61, 0x63, 0x10, 0xe1, 0x03, + 0x12, 0x0a, 0x0a, 0x05, 0x57, 0x68, 0x6f, 0x78, 0x79, 0x10, 0xe2, 0x03, 0x12, 0x11, 0x0a, 0x0c, + 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x57, 0x65, 0x61, 0x74, 0x68, 0x65, 0x72, 0x10, 0xe3, 0x03, 0x12, + 0x0e, 0x0a, 0x09, 0x41, 0x70, 0x69, 0x46, 0x6f, 0x6e, 0x69, 0x63, 0x61, 0x10, 0xe4, 0x03, 0x12, + 0x0b, 0x0a, 0x06, 0x41, 0x79, 0x6c, 0x69, 0x65, 0x6e, 0x10, 0xe5, 0x03, 0x12, 0x0c, 0x0a, 0x07, + 0x47, 0x65, 0x6f, 0x63, 0x6f, 0x64, 0x65, 0x10, 0xe6, 0x03, 0x12, 0x0f, 0x0a, 0x0a, 0x49, 0x63, + 0x6f, 0x6e, 0x46, 0x69, 0x6e, 0x64, 0x65, 0x72, 0x10, 0xe7, 0x03, 0x12, 0x0a, 0x0a, 0x05, 0x49, + 0x70, 0x69, 0x66, 0x79, 0x10, 0xe8, 0x03, 0x12, 0x12, 0x0a, 0x0d, 0x4c, 0x61, 0x6e, 0x67, 0x75, + 0x61, 0x67, 0x65, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x10, 0xe9, 0x03, 0x12, 0x08, 0x0a, 0x03, 0x4c, + 0x6f, 0x62, 0x10, 0xea, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x4f, 0x6e, 0x57, 0x61, 0x74, 0x65, 0x72, + 0x49, 0x4f, 0x10, 0xeb, 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x50, 0x61, 0x73, 0x74, 0x65, 0x62, 0x69, + 0x6e, 0x10, 0xec, 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x50, 0x64, 0x66, 0x4c, 0x61, 0x79, 0x65, 0x72, + 0x10, 0xed, 0x03, 0x12, 0x0c, 0x0a, 0x07, 0x50, 0x69, 0x78, 0x61, 0x62, 0x61, 0x79, 0x10, 0xee, + 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x52, 0x65, 0x61, 0x64, 0x4d, 0x65, 0x10, 0xef, 0x03, 0x12, 0x0d, + 0x0a, 0x08, 0x56, 0x61, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x10, 0xf0, 0x03, 0x12, 0x0f, 0x0a, + 0x0a, 0x56, 0x69, 0x72, 0x75, 0x73, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x10, 0xf1, 0x03, 0x12, 0x0e, + 0x0a, 0x09, 0x41, 0x69, 0x72, 0x56, 0x69, 0x73, 0x75, 0x61, 0x6c, 0x10, 0xf2, 0x03, 0x12, 0x13, + 0x0a, 0x0e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x66, 0x72, 0x65, 0x61, 0x6b, 0x73, + 0x10, 0xf3, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x44, 0x75, 0x66, 0x66, 0x65, 0x6c, 0x10, 0xf4, 0x03, + 0x12, 0x0b, 0x0a, 0x06, 0x46, 0x6c, 0x61, 0x74, 0x49, 0x4f, 0x10, 0xf5, 0x03, 0x12, 0x08, 0x0a, + 0x03, 0x4d, 0x33, 0x6f, 0x10, 0xf6, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x4d, 0x65, 0x73, 0x69, 0x62, + 0x6f, 0x10, 0xf7, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x4f, 0x70, 0x65, 0x6e, 0x75, 0x76, 0x10, 0xf8, + 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x53, 0x6e, 0x69, 0x70, 0x63, 0x61, 0x72, 0x74, 0x10, 0xf9, 0x03, + 0x12, 0x0d, 0x0a, 0x08, 0x42, 0x65, 0x73, 0x74, 0x74, 0x69, 0x6d, 0x65, 0x10, 0xfa, 0x03, 0x12, + 0x10, 0x0a, 0x0b, 0x48, 0x61, 0x70, 0x70, 0x79, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x10, 0xfb, + 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x48, 0x75, 0x6d, 0x61, 0x6e, 0x69, 0x74, 0x79, 0x10, 0xfc, 0x03, + 0x12, 0x0b, 0x0a, 0x06, 0x49, 0x6d, 0x70, 0x61, 0x6c, 0x61, 0x10, 0xfd, 0x03, 0x12, 0x10, 0x0a, + 0x0b, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x72, 0x61, 0x64, 0x69, 0x75, 0x73, 0x10, 0xfe, 0x03, 0x12, + 0x0e, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x6f, 0x50, 0x69, 0x6c, 0x6f, 0x74, 0x10, 0xff, 0x03, 0x12, + 0x0b, 0x0a, 0x06, 0x42, 0x69, 0x74, 0x6d, 0x65, 0x78, 0x10, 0x80, 0x04, 0x12, 0x0d, 0x0a, 0x08, + 0x43, 0x6c, 0x75, 0x73, 0x74, 0x44, 0x6f, 0x63, 0x10, 0x81, 0x04, 0x12, 0x0c, 0x0a, 0x07, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x72, 0x69, 0x10, 0x82, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x50, 0x64, 0x66, + 0x53, 0x68, 0x69, 0x66, 0x74, 0x10, 0x83, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x50, 0x6f, 0x6c, 0x6f, + 0x6e, 0x69, 0x65, 0x78, 0x10, 0x84, 0x04, 0x12, 0x19, 0x0a, 0x14, 0x52, 0x65, 0x73, 0x74, 0x70, + 0x61, 0x63, 0x6b, 0x48, 0x74, 0x6d, 0x6c, 0x54, 0x6f, 0x50, 0x64, 0x66, 0x41, 0x50, 0x49, 0x10, + 0x85, 0x04, 0x12, 0x1a, 0x0a, 0x15, 0x52, 0x65, 0x73, 0x74, 0x70, 0x61, 0x63, 0x6b, 0x53, 0x63, + 0x72, 0x65, 0x65, 0x6e, 0x73, 0x68, 0x6f, 0x74, 0x41, 0x50, 0x49, 0x10, 0x86, 0x04, 0x12, 0x16, + 0x0a, 0x11, 0x53, 0x68, 0x75, 0x74, 0x74, 0x65, 0x72, 0x73, 0x74, 0x6f, 0x63, 0x6b, 0x4f, 0x41, + 0x75, 0x74, 0x68, 0x10, 0x87, 0x04, 0x12, 0x10, 0x0a, 0x0b, 0x53, 0x6b, 0x79, 0x42, 0x69, 0x6f, + 0x6d, 0x65, 0x74, 0x72, 0x79, 0x10, 0x88, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x41, 0x62, 0x75, 0x73, + 0x65, 0x49, 0x50, 0x44, 0x42, 0x10, 0x89, 0x04, 0x12, 0x10, 0x0a, 0x0b, 0x41, 0x6c, 0x65, 0x74, + 0x68, 0x65, 0x69, 0x61, 0x41, 0x70, 0x69, 0x10, 0x8a, 0x04, 0x12, 0x0c, 0x0a, 0x07, 0x42, 0x6c, + 0x69, 0x74, 0x41, 0x70, 0x70, 0x10, 0x8b, 0x04, 0x12, 0x0b, 0x0a, 0x06, 0x43, 0x65, 0x6e, 0x73, + 0x79, 0x73, 0x10, 0x8c, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x43, 0x6c, 0x6f, 0x76, 0x65, 0x72, 0x6c, + 0x79, 0x10, 0x8d, 0x04, 0x12, 0x11, 0x0a, 0x0c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x4c, + 0x61, 0x79, 0x65, 0x72, 0x10, 0x8e, 0x04, 0x12, 0x0b, 0x0a, 0x06, 0x46, 0x69, 0x6c, 0x65, 0x49, + 0x4f, 0x10, 0x8f, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x46, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x41, 0x70, + 0x69, 0x10, 0x90, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x47, 0x65, 0x6f, 0x61, 0x70, 0x69, 0x66, 0x79, + 0x10, 0x91, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x49, 0x50, 0x69, 0x6e, 0x66, 0x6f, 0x44, 0x42, 0x10, + 0x92, 0x04, 0x12, 0x0f, 0x0a, 0x0a, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x53, 0x74, 0x61, 0x63, 0x6b, + 0x10, 0x93, 0x04, 0x12, 0x13, 0x0a, 0x0e, 0x4e, 0x61, 0x73, 0x64, 0x61, 0x71, 0x44, 0x61, 0x74, + 0x61, 0x4c, 0x69, 0x6e, 0x6b, 0x10, 0x94, 0x04, 0x12, 0x11, 0x0a, 0x0c, 0x4f, 0x70, 0x65, 0x6e, + 0x43, 0x61, 0x67, 0x65, 0x44, 0x61, 0x74, 0x61, 0x10, 0x95, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x50, + 0x61, 0x79, 0x6d, 0x6f, 0x6e, 0x67, 0x6f, 0x10, 0x96, 0x04, 0x12, 0x12, 0x0a, 0x0d, 0x50, 0x6f, + 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x10, 0x97, 0x04, 0x12, 0x0e, + 0x0a, 0x09, 0x52, 0x65, 0x62, 0x72, 0x61, 0x6e, 0x64, 0x6c, 0x79, 0x10, 0x98, 0x04, 0x12, 0x14, + 0x0a, 0x0f, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x73, 0x68, 0x6f, 0x74, 0x4c, 0x61, 0x79, 0x65, + 0x72, 0x10, 0x99, 0x04, 0x12, 0x0b, 0x0a, 0x06, 0x53, 0x74, 0x79, 0x74, 0x63, 0x68, 0x10, 0x9a, + 0x04, 0x12, 0x0c, 0x0a, 0x07, 0x55, 0x6e, 0x70, 0x6c, 0x75, 0x67, 0x67, 0x10, 0x9b, 0x04, 0x12, + 0x10, 0x0a, 0x0b, 0x55, 0x50, 0x43, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x10, 0x9c, + 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x10, 0x9d, + 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x47, 0x65, 0x6f, 0x63, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x10, 0x9e, + 0x04, 0x12, 0x10, 0x0a, 0x0b, 0x4e, 0x65, 0x77, 0x73, 0x63, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, + 0x10, 0x9f, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x4e, 0x69, 0x63, 0x65, 0x72, 0x65, 0x70, 0x6c, 0x79, + 0x10, 0xa0, 0x04, 0x12, 0x11, 0x0a, 0x0c, 0x50, 0x61, 0x72, 0x74, 0x6e, 0x65, 0x72, 0x73, 0x74, + 0x61, 0x63, 0x6b, 0x10, 0xa1, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x34, + 0x6d, 0x65, 0x10, 0xa2, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x53, 0x63, 0x72, 0x61, 0x70, 0x65, 0x6f, + 0x77, 0x6c, 0x10, 0xa3, 0x04, 0x12, 0x10, 0x0a, 0x0b, 0x53, 0x63, 0x72, 0x61, 0x70, 0x69, 0x6e, + 0x67, 0x44, 0x6f, 0x67, 0x10, 0xa4, 0x04, 0x12, 0x0b, 0x0a, 0x06, 0x53, 0x74, 0x72, 0x65, 0x61, + 0x6b, 0x10, 0xa5, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x56, 0x65, 0x72, 0x69, 0x70, 0x68, 0x6f, 0x6e, + 0x65, 0x10, 0xa6, 0x04, 0x12, 0x10, 0x0a, 0x0b, 0x57, 0x65, 0x62, 0x73, 0x63, 0x72, 0x61, 0x70, + 0x69, 0x6e, 0x67, 0x10, 0xa7, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x5a, 0x65, 0x6e, 0x73, 0x63, 0x72, + 0x61, 0x70, 0x65, 0x10, 0xa8, 0x04, 0x12, 0x0c, 0x0a, 0x07, 0x5a, 0x65, 0x6e, 0x73, 0x65, 0x72, + 0x70, 0x10, 0xa9, 0x04, 0x12, 0x0c, 0x0a, 0x07, 0x43, 0x6f, 0x69, 0x6e, 0x41, 0x70, 0x69, 0x10, + 0xaa, 0x04, 0x12, 0x0b, 0x0a, 0x06, 0x47, 0x69, 0x74, 0x74, 0x65, 0x72, 0x10, 0xab, 0x04, 0x12, + 0x09, 0x0a, 0x04, 0x48, 0x6f, 0x73, 0x74, 0x10, 0xac, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x49, 0x65, + 0x78, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x10, 0xad, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x52, 0x65, 0x73, + 0x74, 0x70, 0x61, 0x63, 0x6b, 0x10, 0xae, 0x04, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x63, 0x72, 0x61, + 0x70, 0x65, 0x72, 0x42, 0x6f, 0x78, 0x10, 0xaf, 0x04, 0x12, 0x10, 0x0a, 0x0b, 0x53, 0x63, 0x72, + 0x61, 0x70, 0x69, 0x6e, 0x67, 0x41, 0x6e, 0x74, 0x10, 0xb0, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x53, + 0x65, 0x72, 0x70, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x10, 0xb1, 0x04, 0x12, 0x12, 0x0a, 0x0d, 0x53, + 0x6d, 0x61, 0x72, 0x74, 0x79, 0x53, 0x74, 0x72, 0x65, 0x65, 0x74, 0x73, 0x10, 0xb2, 0x04, 0x12, + 0x11, 0x0a, 0x0c, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x10, + 0xb3, 0x04, 0x12, 0x12, 0x0a, 0x0d, 0x41, 0x76, 0x69, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, + 0x61, 0x63, 0x6b, 0x10, 0xb4, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x42, 0x6f, 0x6d, 0x62, 0x42, 0x6f, + 0x6d, 0x62, 0x10, 0xb5, 0x04, 0x12, 0x10, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x64, 0x69, + 0x74, 0x69, 0x65, 0x73, 0x10, 0xb6, 0x04, 0x12, 0x0a, 0x0a, 0x05, 0x44, 0x66, 0x75, 0x73, 0x65, + 0x10, 0xb7, 0x04, 0x12, 0x0b, 0x0a, 0x06, 0x45, 0x64, 0x65, 0x6e, 0x41, 0x49, 0x10, 0xb8, 0x04, + 0x12, 0x0e, 0x0a, 0x09, 0x47, 0x6c, 0x61, 0x73, 0x73, 0x6e, 0x6f, 0x64, 0x65, 0x10, 0xb9, 0x04, + 0x12, 0x09, 0x0a, 0x04, 0x47, 0x75, 0x72, 0x75, 0x10, 0xba, 0x04, 0x12, 0x09, 0x0a, 0x04, 0x48, + 0x69, 0x76, 0x65, 0x10, 0xbb, 0x04, 0x12, 0x0c, 0x0a, 0x07, 0x48, 0x69, 0x76, 0x65, 0x61, 0x67, + 0x65, 0x10, 0xbc, 0x04, 0x12, 0x0c, 0x0a, 0x07, 0x4b, 0x69, 0x63, 0x6b, 0x62, 0x6f, 0x78, 0x10, + 0xbd, 0x04, 0x12, 0x11, 0x0a, 0x08, 0x50, 0x61, 0x73, 0x73, 0x62, 0x61, 0x73, 0x65, 0x10, 0xbe, + 0x04, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x0f, 0x0a, 0x0a, 0x50, 0x6f, 0x73, 0x74, 0x61, 0x67, 0x65, + 0x41, 0x70, 0x70, 0x10, 0xbf, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x50, 0x75, 0x72, 0x65, 0x53, 0x74, + 0x61, 0x6b, 0x65, 0x10, 0xc0, 0x04, 0x12, 0x0b, 0x0a, 0x06, 0x51, 0x75, 0x62, 0x6f, 0x6c, 0x65, + 0x10, 0xc1, 0x04, 0x12, 0x14, 0x0a, 0x0f, 0x43, 0x61, 0x72, 0x62, 0x6f, 0x6e, 0x49, 0x6e, 0x74, + 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x10, 0xc2, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x49, 0x6e, 0x74, + 0x72, 0x69, 0x6e, 0x69, 0x6f, 0x10, 0xc3, 0x04, 0x12, 0x15, 0x0a, 0x0c, 0x51, 0x75, 0x69, 0x63, + 0x6b, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x10, 0xc4, 0x04, 0x1a, 0x02, 0x08, 0x01, 0x12, + 0x10, 0x0a, 0x0b, 0x53, 0x63, 0x72, 0x61, 0x70, 0x65, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x10, 0xc5, + 0x04, 0x12, 0x19, 0x0a, 0x14, 0x54, 0x65, 0x63, 0x68, 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x41, 0x6e, + 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x41, 0x70, 0x69, 0x10, 0xc6, 0x04, 0x12, 0x0c, 0x0a, 0x07, + 0x55, 0x72, 0x6c, 0x73, 0x63, 0x61, 0x6e, 0x10, 0xc7, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x42, 0x61, + 0x73, 0x65, 0x41, 0x70, 0x69, 0x49, 0x4f, 0x10, 0xc8, 0x04, 0x12, 0x0c, 0x0a, 0x07, 0x44, 0x61, + 0x69, 0x6c, 0x79, 0x43, 0x4f, 0x10, 0xc9, 0x04, 0x12, 0x08, 0x0a, 0x03, 0x54, 0x4c, 0x79, 0x10, + 0xca, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75, 0x74, 0x10, 0xcb, + 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x41, 0x70, 0x70, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x10, 0xcc, + 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x54, 0x68, 0x69, 0x6e, 0x6b, 0x69, 0x66, 0x69, 0x63, 0x10, 0xcd, + 0x04, 0x12, 0x0b, 0x0a, 0x06, 0x46, 0x65, 0x65, 0x64, 0x6c, 0x79, 0x10, 0xce, 0x04, 0x12, 0x0f, + 0x0a, 0x0a, 0x53, 0x74, 0x69, 0x74, 0x63, 0x68, 0x64, 0x61, 0x74, 0x61, 0x10, 0xcf, 0x04, 0x12, + 0x0d, 0x0a, 0x08, 0x46, 0x65, 0x74, 0x63, 0x68, 0x72, 0x73, 0x73, 0x10, 0xd0, 0x04, 0x12, 0x11, + 0x0a, 0x0c, 0x53, 0x69, 0x67, 0x6e, 0x75, 0x70, 0x67, 0x65, 0x6e, 0x69, 0x75, 0x73, 0x10, 0xd1, + 0x04, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x69, 0x74, 0x10, + 0xd2, 0x04, 0x12, 0x0f, 0x0a, 0x0a, 0x4f, 0x70, 0x74, 0x69, 0x6d, 0x69, 0x7a, 0x65, 0x6c, 0x79, + 0x10, 0xd3, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x4f, 0x63, 0x72, 0x53, 0x70, 0x61, 0x63, 0x65, 0x10, + 0xd4, 0x04, 0x12, 0x0f, 0x0a, 0x0a, 0x57, 0x65, 0x61, 0x74, 0x68, 0x65, 0x72, 0x42, 0x69, 0x74, + 0x10, 0xd5, 0x04, 0x12, 0x0c, 0x0a, 0x07, 0x42, 0x75, 0x64, 0x64, 0x79, 0x4e, 0x53, 0x10, 0xd6, + 0x04, 0x12, 0x0b, 0x0a, 0x06, 0x5a, 0x69, 0x70, 0x41, 0x50, 0x49, 0x10, 0xd7, 0x04, 0x12, 0x0d, + 0x0a, 0x08, 0x5a, 0x69, 0x70, 0x42, 0x6f, 0x6f, 0x6b, 0x73, 0x10, 0xd8, 0x04, 0x12, 0x0c, 0x0a, + 0x07, 0x4f, 0x6e, 0x65, 0x64, 0x65, 0x73, 0x6b, 0x10, 0xd9, 0x04, 0x12, 0x0c, 0x0a, 0x07, 0x42, + 0x75, 0x67, 0x68, 0x65, 0x72, 0x64, 0x10, 0xda, 0x04, 0x12, 0x0f, 0x0a, 0x0a, 0x42, 0x6c, 0x61, + 0x7a, 0x65, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x10, 0xdb, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x41, 0x75, + 0x74, 0x6f, 0x64, 0x65, 0x73, 0x6b, 0x10, 0xdc, 0x04, 0x12, 0x08, 0x0a, 0x03, 0x54, 0x72, 0x75, + 0x10, 0xdd, 0x04, 0x12, 0x0c, 0x0a, 0x07, 0x55, 0x6e, 0x69, 0x66, 0x79, 0x49, 0x44, 0x10, 0xde, + 0x04, 0x12, 0x0c, 0x0a, 0x07, 0x54, 0x72, 0x69, 0x6d, 0x62, 0x6c, 0x65, 0x10, 0xdf, 0x04, 0x12, + 0x0b, 0x0a, 0x06, 0x53, 0x6d, 0x6f, 0x6f, 0x63, 0x68, 0x10, 0xe0, 0x04, 0x12, 0x0e, 0x0a, 0x09, + 0x53, 0x65, 0x6d, 0x61, 0x70, 0x68, 0x6f, 0x72, 0x65, 0x10, 0xe1, 0x04, 0x12, 0x0b, 0x0a, 0x06, + 0x54, 0x65, 0x6c, 0x6e, 0x79, 0x78, 0x10, 0xe2, 0x04, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x69, 0x67, + 0x6e, 0x61, 0x6c, 0x77, 0x69, 0x72, 0x65, 0x10, 0xe3, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x54, 0x65, + 0x78, 0x74, 0x6d, 0x61, 0x67, 0x69, 0x63, 0x10, 0xe4, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x53, 0x65, + 0x72, 0x70, 0x68, 0x6f, 0x75, 0x73, 0x65, 0x10, 0xe5, 0x04, 0x12, 0x0b, 0x0a, 0x06, 0x50, 0x6c, + 0x61, 0x6e, 0x79, 0x6f, 0x10, 0xe6, 0x04, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x69, 0x6d, 0x70, 0x6c, + 0x79, 0x62, 0x6f, 0x6f, 0x6b, 0x10, 0xe7, 0x04, 0x12, 0x09, 0x0a, 0x04, 0x56, 0x79, 0x74, 0x65, + 0x10, 0xe8, 0x04, 0x12, 0x0a, 0x0a, 0x05, 0x4e, 0x79, 0x6c, 0x61, 0x73, 0x10, 0xe9, 0x04, 0x12, + 0x0d, 0x0a, 0x08, 0x53, 0x71, 0x75, 0x61, 0x72, 0x65, 0x75, 0x70, 0x10, 0xea, 0x04, 0x12, 0x0e, + 0x0a, 0x09, 0x44, 0x61, 0x6e, 0x64, 0x65, 0x6c, 0x69, 0x6f, 0x6e, 0x10, 0xeb, 0x04, 0x12, 0x11, + 0x0a, 0x08, 0x44, 0x61, 0x74, 0x61, 0x46, 0x69, 0x72, 0x65, 0x10, 0xec, 0x04, 0x1a, 0x02, 0x08, + 0x01, 0x12, 0x0b, 0x0a, 0x06, 0x44, 0x65, 0x65, 0x70, 0x41, 0x49, 0x10, 0xed, 0x04, 0x12, 0x11, + 0x0a, 0x0c, 0x4d, 0x65, 0x61, 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x10, 0xee, + 0x04, 0x12, 0x10, 0x0a, 0x0b, 0x4e, 0x65, 0x75, 0x74, 0x72, 0x69, 0x6e, 0x6f, 0x41, 0x70, 0x69, + 0x10, 0xef, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, + 0x10, 0xf0, 0x04, 0x12, 0x0c, 0x0a, 0x07, 0x53, 0x68, 0x69, 0x70, 0x64, 0x61, 0x79, 0x10, 0xf1, + 0x04, 0x12, 0x12, 0x0a, 0x09, 0x53, 0x65, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x10, 0xf2, + 0x04, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x18, 0x0a, 0x13, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x68, 0x61, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x69, 0x6e, 0x67, 0x10, 0xf3, 0x04, 0x12, 0x10, 0x0a, 0x0b, 0x54, 0x65, 0x61, 0x6d, 0x77, 0x6f, 0x72, 0x6b, 0x43, 0x52, 0x4d, 0x10, 0xf4, 0x04, 0x12, 0x11, 0x0a, 0x0c, 0x54, 0x65, 0x61, 0x6d, 0x77, 0x6f, 0x72, 0x6b, 0x44, 0x65, 0x73, @@ -3992,309 +4028,321 @@ var file_detectors_proto_rawDesc = []byte{ 0x4d, 0x65, 0x69, 0x73, 0x74, 0x65, 0x72, 0x74, 0x61, 0x73, 0x6b, 0x10, 0x90, 0x05, 0x12, 0x10, 0x0a, 0x0b, 0x4d, 0x69, 0x6e, 0x64, 0x6d, 0x65, 0x69, 0x73, 0x74, 0x65, 0x72, 0x10, 0x91, 0x05, 0x12, 0x13, 0x0a, 0x0e, 0x50, 0x65, 0x6f, 0x70, 0x6c, 0x65, 0x44, 0x61, 0x74, 0x61, 0x4c, 0x61, - 0x62, 0x73, 0x10, 0x92, 0x05, 0x12, 0x10, 0x0a, 0x0b, 0x53, 0x63, 0x72, 0x61, 0x70, 0x65, 0x72, - 0x53, 0x69, 0x74, 0x65, 0x10, 0x93, 0x05, 0x12, 0x0d, 0x0a, 0x08, 0x53, 0x63, 0x72, 0x61, 0x70, - 0x66, 0x6c, 0x79, 0x10, 0x94, 0x05, 0x12, 0x10, 0x0a, 0x0b, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x79, - 0x4e, 0x6f, 0x74, 0x65, 0x64, 0x10, 0x95, 0x05, 0x12, 0x12, 0x0a, 0x0d, 0x54, 0x72, 0x61, 0x76, - 0x65, 0x6c, 0x50, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x73, 0x10, 0x96, 0x05, 0x12, 0x0f, 0x0a, 0x0a, - 0x57, 0x65, 0x62, 0x53, 0x63, 0x72, 0x61, 0x70, 0x65, 0x72, 0x10, 0x97, 0x05, 0x12, 0x0c, 0x0a, - 0x07, 0x43, 0x6f, 0x6e, 0x76, 0x69, 0x65, 0x72, 0x10, 0x98, 0x05, 0x12, 0x0c, 0x0a, 0x07, 0x43, - 0x6f, 0x75, 0x72, 0x69, 0x65, 0x72, 0x10, 0x99, 0x05, 0x12, 0x0a, 0x0a, 0x05, 0x44, 0x69, 0x74, - 0x74, 0x6f, 0x10, 0x9a, 0x05, 0x12, 0x0a, 0x0a, 0x05, 0x46, 0x69, 0x6e, 0x64, 0x6c, 0x10, 0x9b, - 0x05, 0x12, 0x0d, 0x0a, 0x08, 0x4c, 0x65, 0x6e, 0x64, 0x66, 0x6c, 0x6f, 0x77, 0x10, 0x9c, 0x05, - 0x12, 0x0f, 0x0a, 0x0a, 0x4d, 0x6f, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0x9d, - 0x05, 0x12, 0x11, 0x0a, 0x0c, 0x4f, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x61, 0x73, 0x6f, 0x66, - 0x74, 0x10, 0x9e, 0x05, 0x12, 0x0a, 0x0a, 0x05, 0x50, 0x6f, 0x64, 0x69, 0x6f, 0x10, 0x9f, 0x05, - 0x12, 0x0c, 0x0a, 0x07, 0x52, 0x6f, 0x63, 0x6b, 0x73, 0x65, 0x74, 0x10, 0xa0, 0x05, 0x12, 0x0a, - 0x0a, 0x05, 0x52, 0x6f, 0x77, 0x6e, 0x64, 0x10, 0xa1, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x53, 0x68, - 0x6f, 0x74, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x10, 0xa2, 0x05, 0x12, 0x0d, 0x0a, 0x08, 0x53, 0x77, - 0x69, 0x66, 0x74, 0x79, 0x70, 0x65, 0x10, 0xa3, 0x05, 0x12, 0x0c, 0x0a, 0x07, 0x54, 0x77, 0x69, - 0x74, 0x74, 0x65, 0x72, 0x10, 0xa4, 0x05, 0x12, 0x0a, 0x0a, 0x05, 0x48, 0x6f, 0x6e, 0x65, 0x79, - 0x10, 0xa5, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x46, 0x72, 0x65, 0x73, 0x68, 0x64, 0x65, 0x73, 0x6b, - 0x10, 0xa6, 0x05, 0x12, 0x0b, 0x0a, 0x06, 0x55, 0x70, 0x77, 0x61, 0x76, 0x65, 0x10, 0xa7, 0x05, - 0x12, 0x0d, 0x0a, 0x08, 0x46, 0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x10, 0xa8, 0x05, 0x12, - 0x0f, 0x0a, 0x0a, 0x46, 0x72, 0x65, 0x73, 0x68, 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0x10, 0xa9, 0x05, - 0x12, 0x09, 0x0a, 0x04, 0x4d, 0x69, 0x74, 0x65, 0x10, 0xaa, 0x05, 0x12, 0x0b, 0x0a, 0x06, 0x44, - 0x65, 0x70, 0x75, 0x74, 0x79, 0x10, 0xab, 0x05, 0x12, 0x0c, 0x0a, 0x07, 0x42, 0x65, 0x65, 0x62, - 0x6f, 0x6c, 0x65, 0x10, 0xac, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x43, 0x61, 0x73, 0x68, 0x62, 0x6f, - 0x61, 0x72, 0x64, 0x10, 0xad, 0x05, 0x12, 0x0b, 0x0a, 0x06, 0x4b, 0x61, 0x6e, 0x62, 0x61, 0x6e, - 0x10, 0xae, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x6e, 0x61, 0x70, 0x73, - 0x10, 0xaf, 0x05, 0x12, 0x10, 0x0a, 0x0b, 0x4d, 0x79, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, - 0x6c, 0x73, 0x10, 0xb0, 0x05, 0x12, 0x11, 0x0a, 0x0c, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, - 0x4f, 0x63, 0x65, 0x61, 0x6e, 0x10, 0xb1, 0x05, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x68, 0x65, 0x72, - 0x70, 0x61, 0x64, 0x65, 0x73, 0x6b, 0x10, 0xb2, 0x05, 0x12, 0x0f, 0x0a, 0x0a, 0x4d, 0x72, 0x74, - 0x69, 0x63, 0x6b, 0x74, 0x6f, 0x63, 0x6b, 0x10, 0xb3, 0x05, 0x12, 0x0d, 0x0a, 0x08, 0x43, 0x68, - 0x61, 0x74, 0x66, 0x75, 0x6c, 0x65, 0x10, 0xb4, 0x05, 0x12, 0x11, 0x0a, 0x0c, 0x41, 0x65, 0x72, - 0x6f, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x10, 0xb5, 0x05, 0x12, 0x11, 0x0a, 0x0c, - 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x6f, 0x63, 0x74, 0x6f, 0x70, 0x75, 0x73, 0x10, 0xb6, 0x05, 0x12, - 0x0d, 0x0a, 0x08, 0x46, 0x75, 0x73, 0x65, 0x62, 0x69, 0x6c, 0x6c, 0x10, 0xb7, 0x05, 0x12, 0x0f, - 0x0a, 0x0a, 0x47, 0x65, 0x63, 0x6b, 0x6f, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x10, 0xb8, 0x05, 0x12, - 0x0e, 0x0a, 0x09, 0x47, 0x6f, 0x73, 0x71, 0x75, 0x61, 0x72, 0x65, 0x64, 0x10, 0xb9, 0x05, 0x12, - 0x0e, 0x0a, 0x09, 0x4d, 0x6f, 0x6f, 0x6e, 0x63, 0x6c, 0x65, 0x72, 0x6b, 0x10, 0xba, 0x05, 0x12, - 0x0d, 0x0a, 0x08, 0x50, 0x61, 0x79, 0x6d, 0x6f, 0x61, 0x70, 0x70, 0x10, 0xbb, 0x05, 0x12, 0x0b, - 0x0a, 0x06, 0x4d, 0x69, 0x78, 0x6d, 0x61, 0x78, 0x10, 0xbc, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x50, - 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x73, 0x74, 0x10, 0xbd, 0x05, 0x12, 0x10, 0x0a, 0x0b, 0x52, - 0x65, 0x70, 0x61, 0x69, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x72, 0x10, 0xbe, 0x05, 0x12, 0x0d, 0x0a, - 0x08, 0x47, 0x6f, 0x73, 0x68, 0x69, 0x70, 0x70, 0x6f, 0x10, 0xbf, 0x05, 0x12, 0x0b, 0x0a, 0x06, - 0x53, 0x69, 0x67, 0x6f, 0x70, 0x74, 0x10, 0xc0, 0x05, 0x12, 0x0d, 0x0a, 0x08, 0x53, 0x75, 0x67, - 0x65, 0x73, 0x74, 0x65, 0x72, 0x10, 0xc1, 0x05, 0x12, 0x0c, 0x0a, 0x07, 0x56, 0x69, 0x65, 0x77, - 0x6e, 0x65, 0x6f, 0x10, 0xc2, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x42, 0x6f, 0x6f, 0x73, 0x74, 0x4e, - 0x6f, 0x74, 0x65, 0x10, 0xc3, 0x05, 0x12, 0x10, 0x0a, 0x0b, 0x43, 0x61, 0x70, 0x74, 0x61, 0x69, - 0x6e, 0x44, 0x61, 0x74, 0x61, 0x10, 0xc4, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x43, 0x68, 0x65, 0x63, - 0x6b, 0x76, 0x69, 0x73, 0x74, 0x10, 0xc5, 0x05, 0x12, 0x0c, 0x0a, 0x07, 0x43, 0x6c, 0x69, 0x65, - 0x6e, 0x67, 0x6f, 0x10, 0xc6, 0x05, 0x12, 0x0a, 0x0a, 0x05, 0x43, 0x6c, 0x6f, 0x7a, 0x65, 0x10, - 0xc7, 0x05, 0x12, 0x0b, 0x0a, 0x06, 0x46, 0x6f, 0x72, 0x6d, 0x49, 0x4f, 0x10, 0xc8, 0x05, 0x12, - 0x0f, 0x0a, 0x0a, 0x46, 0x6f, 0x72, 0x6d, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x10, 0xc9, 0x05, - 0x12, 0x0d, 0x0a, 0x08, 0x47, 0x6f, 0x43, 0x61, 0x6e, 0x76, 0x61, 0x73, 0x10, 0xca, 0x05, 0x12, - 0x0c, 0x0a, 0x07, 0x4d, 0x61, 0x64, 0x4b, 0x75, 0x64, 0x75, 0x10, 0xcb, 0x05, 0x12, 0x0f, 0x0a, - 0x0a, 0x4e, 0x6f, 0x7a, 0x62, 0x65, 0x54, 0x65, 0x61, 0x6d, 0x73, 0x10, 0xcc, 0x05, 0x12, 0x0b, - 0x0a, 0x06, 0x50, 0x61, 0x70, 0x79, 0x72, 0x73, 0x10, 0xcd, 0x05, 0x12, 0x12, 0x0a, 0x0d, 0x53, - 0x75, 0x70, 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x65, 0x73, 0x41, 0x50, 0x49, 0x10, 0xce, 0x05, 0x12, - 0x0c, 0x0a, 0x07, 0x54, 0x61, 0x6c, 0x6c, 0x79, 0x66, 0x79, 0x10, 0xcf, 0x05, 0x12, 0x0e, 0x0a, - 0x09, 0x5a, 0x65, 0x6e, 0x6b, 0x69, 0x74, 0x41, 0x50, 0x49, 0x10, 0xd0, 0x05, 0x12, 0x0f, 0x0a, - 0x0a, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x10, 0xd1, 0x05, 0x12, 0x0f, - 0x0a, 0x0a, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x43, 0x61, 0x72, 0x65, 0x10, 0xd2, 0x05, 0x12, - 0x0d, 0x0a, 0x08, 0x42, 0x6f, 0x72, 0x67, 0x62, 0x61, 0x73, 0x65, 0x10, 0xd3, 0x05, 0x12, 0x0e, - 0x0a, 0x09, 0x50, 0x69, 0x70, 0x65, 0x64, 0x72, 0x65, 0x61, 0x6d, 0x10, 0xd4, 0x05, 0x12, 0x09, - 0x0a, 0x04, 0x53, 0x69, 0x72, 0x76, 0x10, 0xd5, 0x05, 0x12, 0x0c, 0x0a, 0x07, 0x44, 0x69, 0x66, - 0x66, 0x62, 0x6f, 0x74, 0x10, 0xd6, 0x05, 0x12, 0x10, 0x0a, 0x0b, 0x45, 0x69, 0x67, 0x68, 0x74, - 0x78, 0x45, 0x69, 0x67, 0x68, 0x74, 0x10, 0xd7, 0x05, 0x12, 0x0c, 0x0a, 0x07, 0x53, 0x65, 0x6e, - 0x64, 0x6f, 0x73, 0x6f, 0x10, 0xd8, 0x05, 0x12, 0x11, 0x0a, 0x0c, 0x50, 0x72, 0x69, 0x6e, 0x74, - 0x66, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0xd9, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x41, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x10, 0xda, 0x05, 0x12, 0x0f, 0x0a, 0x0a, 0x50, 0x61, - 0x6e, 0x64, 0x61, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x10, 0xdb, 0x05, 0x12, 0x0a, 0x0a, 0x05, 0x50, - 0x61, 0x79, 0x6d, 0x6f, 0x10, 0xdc, 0x05, 0x12, 0x1d, 0x0a, 0x18, 0x41, 0x76, 0x61, 0x7a, 0x61, - 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x10, 0xdd, 0x05, 0x12, 0x14, 0x0a, 0x0f, 0x50, 0x6c, 0x61, 0x6e, 0x76, 0x69, - 0x65, 0x77, 0x4c, 0x65, 0x61, 0x6e, 0x4b, 0x69, 0x74, 0x10, 0xde, 0x05, 0x12, 0x0e, 0x0a, 0x09, - 0x4c, 0x69, 0x76, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x6d, 0x10, 0xdf, 0x05, 0x12, 0x0b, 0x0a, 0x06, - 0x4b, 0x75, 0x43, 0x6f, 0x69, 0x6e, 0x10, 0xe0, 0x05, 0x12, 0x0c, 0x0a, 0x07, 0x4d, 0x65, 0x74, - 0x61, 0x41, 0x50, 0x49, 0x10, 0xe1, 0x05, 0x12, 0x0d, 0x0a, 0x08, 0x4e, 0x69, 0x63, 0x65, 0x48, - 0x61, 0x73, 0x68, 0x10, 0xe2, 0x05, 0x12, 0x0a, 0x0a, 0x05, 0x43, 0x65, 0x78, 0x49, 0x4f, 0x10, - 0xe3, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x4b, 0x6c, 0x69, 0x70, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x10, - 0xe4, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x44, 0x79, 0x6e, 0x61, 0x74, 0x72, 0x61, 0x63, 0x65, 0x10, - 0xe5, 0x05, 0x12, 0x11, 0x0a, 0x0c, 0x4d, 0x6f, 0x6c, 0x6c, 0x69, 0x65, 0x41, 0x50, 0x49, 0x4b, - 0x65, 0x79, 0x10, 0xe6, 0x05, 0x12, 0x16, 0x0a, 0x11, 0x4d, 0x6f, 0x6c, 0x6c, 0x69, 0x65, 0x41, - 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0xe7, 0x05, 0x12, 0x10, 0x0a, - 0x0b, 0x42, 0x61, 0x73, 0x69, 0x73, 0x54, 0x68, 0x65, 0x6f, 0x72, 0x79, 0x10, 0xe8, 0x05, 0x12, - 0x0d, 0x0a, 0x08, 0x4e, 0x6f, 0x72, 0x64, 0x69, 0x67, 0x65, 0x6e, 0x10, 0xe9, 0x05, 0x12, 0x1c, - 0x0a, 0x17, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x6d, 0x69, 0x74, 0x68, 0x45, 0x6e, 0x76, 0x69, 0x72, - 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x10, 0xea, 0x05, 0x12, 0x13, 0x0a, 0x0e, - 0x46, 0x6c, 0x61, 0x67, 0x73, 0x6d, 0x69, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0xeb, - 0x05, 0x12, 0x08, 0x0a, 0x03, 0x4d, 0x75, 0x78, 0x10, 0xec, 0x05, 0x12, 0x0b, 0x0a, 0x06, 0x43, - 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x10, 0xed, 0x05, 0x12, 0x0d, 0x0a, 0x08, 0x53, 0x65, 0x6e, 0x64, - 0x62, 0x69, 0x72, 0x64, 0x10, 0xee, 0x05, 0x12, 0x1c, 0x0a, 0x17, 0x53, 0x65, 0x6e, 0x64, 0x62, - 0x69, 0x72, 0x64, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, - 0x50, 0x49, 0x10, 0xef, 0x05, 0x12, 0x0b, 0x0a, 0x06, 0x4d, 0x69, 0x64, 0x69, 0x73, 0x65, 0x10, - 0xf0, 0x05, 0x12, 0x0d, 0x0a, 0x08, 0x4d, 0x6f, 0x63, 0x6b, 0x61, 0x72, 0x6f, 0x6f, 0x10, 0xf1, - 0x05, 0x12, 0x0b, 0x0a, 0x06, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x34, 0x10, 0xf2, 0x05, 0x12, 0x0b, - 0x0a, 0x06, 0x50, 0x69, 0x6e, 0x61, 0x74, 0x61, 0x10, 0xf3, 0x05, 0x12, 0x11, 0x0a, 0x0c, 0x42, - 0x72, 0x6f, 0x77, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x10, 0xf4, 0x05, 0x12, 0x18, - 0x0a, 0x13, 0x43, 0x72, 0x6f, 0x73, 0x73, 0x42, 0x72, 0x6f, 0x77, 0x73, 0x65, 0x72, 0x54, 0x65, - 0x73, 0x74, 0x69, 0x6e, 0x67, 0x10, 0xf5, 0x05, 0x12, 0x0d, 0x0a, 0x08, 0x4c, 0x6f, 0x61, 0x64, - 0x6d, 0x69, 0x6c, 0x6c, 0x10, 0xf6, 0x05, 0x12, 0x0f, 0x0a, 0x0a, 0x54, 0x65, 0x73, 0x74, 0x69, - 0x6e, 0x67, 0x42, 0x6f, 0x74, 0x10, 0xf7, 0x05, 0x12, 0x10, 0x0a, 0x0b, 0x4b, 0x6e, 0x61, 0x70, - 0x73, 0x61, 0x63, 0x6b, 0x50, 0x72, 0x6f, 0x10, 0xf8, 0x05, 0x12, 0x09, 0x0a, 0x04, 0x51, 0x61, - 0x73, 0x65, 0x10, 0xf9, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x44, 0x61, 0x72, 0x65, 0x62, 0x6f, 0x6f, - 0x73, 0x74, 0x10, 0xfa, 0x05, 0x12, 0x0d, 0x0a, 0x08, 0x47, 0x54, 0x4d, 0x65, 0x74, 0x72, 0x69, - 0x78, 0x10, 0xfb, 0x05, 0x12, 0x0d, 0x0a, 0x08, 0x48, 0x6f, 0x6c, 0x69, 0x73, 0x74, 0x69, 0x63, - 0x10, 0xfc, 0x05, 0x12, 0x0c, 0x0a, 0x07, 0x50, 0x61, 0x72, 0x73, 0x65, 0x72, 0x73, 0x10, 0xfd, - 0x05, 0x12, 0x12, 0x0a, 0x0d, 0x53, 0x63, 0x72, 0x75, 0x74, 0x69, 0x6e, 0x69, 0x7a, 0x65, 0x72, - 0x43, 0x69, 0x10, 0xfe, 0x05, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x6f, 0x6e, 0x61, 0x72, 0x43, 0x6c, - 0x6f, 0x75, 0x64, 0x10, 0xff, 0x05, 0x12, 0x10, 0x0a, 0x0b, 0x41, 0x50, 0x49, 0x54, 0x65, 0x6d, - 0x70, 0x6c, 0x61, 0x74, 0x65, 0x10, 0x80, 0x06, 0x12, 0x14, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6f, 0x6c, 0x73, 0x10, 0x81, 0x06, 0x12, 0x0f, - 0x0a, 0x0a, 0x43, 0x72, 0x61, 0x66, 0x74, 0x4d, 0x79, 0x50, 0x44, 0x46, 0x10, 0x82, 0x06, 0x12, - 0x0e, 0x0a, 0x09, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x44, 0x4b, 0x10, 0x83, 0x06, 0x12, - 0x15, 0x0a, 0x0c, 0x47, 0x6c, 0x69, 0x74, 0x74, 0x65, 0x72, 0x6c, 0x79, 0x41, 0x50, 0x49, 0x10, - 0x84, 0x06, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x0d, 0x0a, 0x08, 0x48, 0x79, 0x62, 0x69, 0x73, 0x63, - 0x75, 0x73, 0x10, 0x85, 0x06, 0x12, 0x09, 0x0a, 0x04, 0x4d, 0x69, 0x72, 0x6f, 0x10, 0x86, 0x06, - 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x70, 0x61, 0x67, 0x65, 0x10, 0x87, - 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x70, 0x61, 0x6c, 0x10, 0x88, - 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x54, 0x65, 0x6c, 0x65, 0x74, 0x79, 0x70, 0x65, 0x10, 0x89, 0x06, - 0x12, 0x0d, 0x0a, 0x08, 0x54, 0x69, 0x6d, 0x65, 0x43, 0x61, 0x6d, 0x70, 0x10, 0x8a, 0x06, 0x12, - 0x0d, 0x0a, 0x08, 0x55, 0x73, 0x65, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x10, 0x8b, 0x06, 0x12, 0x0b, - 0x0a, 0x06, 0x57, 0x69, 0x73, 0x74, 0x69, 0x61, 0x10, 0x8c, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x53, - 0x70, 0x6f, 0x72, 0x74, 0x52, 0x61, 0x64, 0x61, 0x72, 0x10, 0x8d, 0x06, 0x12, 0x10, 0x0a, 0x0b, - 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x10, 0x8e, 0x06, 0x12, 0x0e, - 0x0a, 0x09, 0x43, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x69, 0x72, 0x79, 0x10, 0x8f, 0x06, 0x12, 0x11, - 0x0a, 0x0c, 0x45, 0x78, 0x74, 0x72, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x41, 0x50, 0x49, 0x10, 0x90, - 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x10, 0x91, 0x06, - 0x12, 0x0e, 0x0a, 0x09, 0x4d, 0x61, 0x67, 0x69, 0x63, 0x42, 0x65, 0x6c, 0x6c, 0x10, 0x92, 0x06, - 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x74, 0x6f, 0x72, 0x6d, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x10, 0x93, - 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x41, 0x70, 0x69, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x10, 0x94, 0x06, - 0x12, 0x0b, 0x0a, 0x06, 0x44, 0x69, 0x73, 0x71, 0x75, 0x73, 0x10, 0x95, 0x06, 0x12, 0x0b, 0x0a, - 0x06, 0x57, 0x6f, 0x6f, 0x70, 0x72, 0x61, 0x10, 0x96, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x50, 0x61, - 0x70, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x10, 0x97, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x47, 0x75, - 0x6d, 0x72, 0x6f, 0x61, 0x64, 0x10, 0x98, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x50, 0x61, 0x79, 0x64, - 0x69, 0x72, 0x74, 0x61, 0x70, 0x70, 0x10, 0x99, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x44, 0x65, 0x74, - 0x65, 0x63, 0x74, 0x69, 0x66, 0x79, 0x10, 0x9a, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x63, 0x61, 0x6b, 0x65, 0x10, 0x9b, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x4a, 0x75, - 0x6d, 0x70, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x10, 0x9c, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x4c, - 0x75, 0x6e, 0x63, 0x68, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x10, 0x9d, 0x06, 0x12, 0x0c, 0x0a, 0x07, - 0x52, 0x6f, 0x73, 0x65, 0x74, 0x74, 0x65, 0x10, 0x9e, 0x06, 0x12, 0x09, 0x0a, 0x04, 0x59, 0x65, - 0x6c, 0x70, 0x10, 0x9f, 0x06, 0x12, 0x0a, 0x0a, 0x05, 0x41, 0x74, 0x65, 0x72, 0x61, 0x10, 0xa0, - 0x06, 0x12, 0x12, 0x0a, 0x0d, 0x45, 0x63, 0x6f, 0x53, 0x74, 0x72, 0x75, 0x78, 0x75, 0x72, 0x65, - 0x49, 0x54, 0x10, 0xa1, 0x06, 0x12, 0x08, 0x0a, 0x03, 0x41, 0x68, 0x61, 0x10, 0xa2, 0x06, 0x12, - 0x0d, 0x0a, 0x08, 0x50, 0x61, 0x72, 0x73, 0x65, 0x68, 0x75, 0x62, 0x10, 0xa3, 0x06, 0x12, 0x11, - 0x0a, 0x0c, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x10, 0xa4, - 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x73, 0x6d, 0x69, 0x74, 0x68, 0x10, - 0xa5, 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x46, 0x6c, 0x6f, 0x77, 0x64, 0x61, 0x73, 0x68, 0x10, 0xa6, - 0x06, 0x12, 0x11, 0x0a, 0x08, 0x46, 0x6c, 0x6f, 0x77, 0x64, 0x6f, 0x63, 0x6b, 0x10, 0xa7, 0x06, - 0x1a, 0x02, 0x08, 0x01, 0x12, 0x0b, 0x0a, 0x06, 0x46, 0x69, 0x62, 0x65, 0x72, 0x79, 0x10, 0xa8, - 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x54, 0x79, 0x70, 0x65, 0x74, 0x61, 0x6c, 0x6b, 0x10, 0xa9, 0x06, - 0x12, 0x0e, 0x0a, 0x09, 0x56, 0x6f, 0x6f, 0x64, 0x6f, 0x6f, 0x53, 0x4d, 0x53, 0x10, 0xaa, 0x06, - 0x12, 0x0e, 0x0a, 0x09, 0x5a, 0x75, 0x6c, 0x69, 0x70, 0x43, 0x68, 0x61, 0x74, 0x10, 0xab, 0x06, - 0x12, 0x0e, 0x0a, 0x09, 0x46, 0x6f, 0x72, 0x6d, 0x63, 0x72, 0x61, 0x66, 0x74, 0x10, 0xac, 0x06, - 0x12, 0x0c, 0x0a, 0x07, 0x49, 0x65, 0x78, 0x61, 0x70, 0x69, 0x73, 0x10, 0xad, 0x06, 0x12, 0x0e, - 0x0a, 0x09, 0x52, 0x65, 0x61, 0x63, 0x68, 0x6d, 0x61, 0x69, 0x6c, 0x10, 0xae, 0x06, 0x12, 0x0f, - 0x0a, 0x0a, 0x43, 0x68, 0x61, 0x72, 0x74, 0x6d, 0x6f, 0x67, 0x75, 0x6c, 0x10, 0xaf, 0x06, 0x12, - 0x0f, 0x0a, 0x0a, 0x41, 0x70, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x65, 0x64, 0x64, 0x10, 0xb0, 0x06, - 0x12, 0x08, 0x0a, 0x03, 0x57, 0x69, 0x74, 0x10, 0xb1, 0x06, 0x12, 0x15, 0x0a, 0x10, 0x52, 0x65, - 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x10, 0xb2, - 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x44, 0x69, 0x67, 0x67, 0x65, 0x72, 0x6e, 0x61, 0x75, 0x74, 0x10, - 0xb3, 0x06, 0x12, 0x10, 0x0a, 0x0b, 0x4d, 0x6f, 0x6e, 0x6b, 0x65, 0x79, 0x4c, 0x65, 0x61, 0x72, - 0x6e, 0x10, 0xb4, 0x06, 0x12, 0x0a, 0x0a, 0x05, 0x44, 0x75, 0x70, 0x6c, 0x79, 0x10, 0xb5, 0x06, - 0x12, 0x0e, 0x0a, 0x09, 0x50, 0x6f, 0x73, 0x74, 0x62, 0x61, 0x63, 0x6b, 0x73, 0x10, 0xb6, 0x06, - 0x12, 0x0d, 0x0a, 0x08, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x32, 0x10, 0xb7, 0x06, 0x12, - 0x0c, 0x0a, 0x07, 0x5a, 0x65, 0x6e, 0x52, 0x6f, 0x77, 0x73, 0x10, 0xb8, 0x06, 0x12, 0x10, 0x0a, - 0x0b, 0x5a, 0x69, 0x70, 0x63, 0x6f, 0x64, 0x65, 0x62, 0x61, 0x73, 0x65, 0x10, 0xb9, 0x06, 0x12, - 0x0b, 0x0a, 0x06, 0x54, 0x65, 0x66, 0x74, 0x65, 0x72, 0x10, 0xba, 0x06, 0x12, 0x0a, 0x0a, 0x05, - 0x54, 0x77, 0x69, 0x73, 0x74, 0x10, 0xbb, 0x06, 0x12, 0x16, 0x0a, 0x11, 0x42, 0x72, 0x61, 0x69, - 0x6e, 0x74, 0x72, 0x65, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x10, 0xbc, 0x06, - 0x12, 0x11, 0x0a, 0x0c, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, - 0x10, 0xbd, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x47, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x10, 0xbe, - 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x41, 0x70, 0x69, 0x10, - 0xbf, 0x06, 0x12, 0x11, 0x0a, 0x0c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x77, 0x69, - 0x73, 0x65, 0x10, 0xc0, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x42, 0x75, 0x6c, 0x6b, 0x73, 0x6d, 0x73, - 0x10, 0xc1, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x44, 0x61, 0x74, 0x61, 0x62, 0x6f, 0x78, 0x10, 0xc2, - 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x4f, 0x6e, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x10, 0xc3, - 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x52, 0x65, 0x6e, 0x74, 0x6d, 0x61, 0x6e, 0x10, 0xc4, 0x06, 0x12, - 0x0c, 0x0a, 0x07, 0x50, 0x61, 0x72, 0x73, 0x65, 0x75, 0x72, 0x10, 0xc5, 0x06, 0x12, 0x0e, 0x0a, - 0x09, 0x44, 0x6f, 0x63, 0x70, 0x61, 0x72, 0x73, 0x65, 0x72, 0x10, 0xc6, 0x06, 0x12, 0x0d, 0x0a, - 0x08, 0x46, 0x6f, 0x72, 0x6d, 0x73, 0x69, 0x74, 0x65, 0x10, 0xc7, 0x06, 0x12, 0x11, 0x0a, 0x0c, - 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x74, 0x61, 0x69, 0x6c, 0x6f, 0x72, 0x10, 0xc8, 0x06, 0x12, - 0x0c, 0x0a, 0x07, 0x4c, 0x65, 0x6d, 0x6c, 0x69, 0x73, 0x74, 0x10, 0xc9, 0x06, 0x12, 0x0c, 0x0a, - 0x07, 0x50, 0x72, 0x6f, 0x64, 0x70, 0x61, 0x64, 0x10, 0xca, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x46, - 0x6f, 0x72, 0x6d, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x10, 0xcb, 0x06, 0x12, 0x10, 0x0a, 0x0b, 0x43, - 0x6f, 0x64, 0x65, 0x63, 0x6c, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x10, 0xcc, 0x06, 0x12, 0x0e, 0x0a, - 0x09, 0x43, 0x6f, 0x64, 0x65, 0x6d, 0x61, 0x67, 0x69, 0x63, 0x10, 0xcd, 0x06, 0x12, 0x0a, 0x0a, - 0x05, 0x56, 0x62, 0x6f, 0x75, 0x74, 0x10, 0xce, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x4e, 0x69, 0x67, - 0x68, 0x74, 0x66, 0x61, 0x6c, 0x6c, 0x10, 0xcf, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x46, 0x6c, 0x69, - 0x67, 0x68, 0x74, 0x4c, 0x61, 0x62, 0x73, 0x10, 0xd0, 0x06, 0x12, 0x11, 0x0a, 0x0c, 0x53, 0x70, - 0x65, 0x65, 0x63, 0x68, 0x54, 0x65, 0x78, 0x74, 0x41, 0x49, 0x10, 0xd1, 0x06, 0x12, 0x0d, 0x0a, - 0x08, 0x50, 0x6f, 0x6c, 0x6c, 0x73, 0x41, 0x50, 0x49, 0x10, 0xd2, 0x06, 0x12, 0x0b, 0x0a, 0x06, - 0x53, 0x69, 0x6d, 0x46, 0x69, 0x6e, 0x10, 0xd3, 0x06, 0x12, 0x0a, 0x0a, 0x05, 0x53, 0x63, 0x61, - 0x6c, 0x72, 0x10, 0xd4, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x4b, 0x61, 0x6e, 0x62, 0x61, 0x6e, 0x74, - 0x6f, 0x6f, 0x6c, 0x10, 0xd5, 0x06, 0x12, 0x10, 0x0a, 0x0b, 0x42, 0x72, 0x69, 0x67, 0x68, 0x74, - 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x10, 0xd6, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x48, 0x6f, 0x74, 0x77, - 0x69, 0x72, 0x65, 0x10, 0xd7, 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x62, - 0x6f, 0x74, 0x10, 0xd8, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x54, 0x69, 0x6d, 0x65, 0x6b, 0x69, 0x74, - 0x10, 0xd9, 0x06, 0x12, 0x10, 0x0a, 0x0b, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x65, 0x6c, 0x6c, - 0x65, 0x72, 0x10, 0xda, 0x06, 0x12, 0x11, 0x0a, 0x0c, 0x4d, 0x6f, 0x6a, 0x6f, 0x68, 0x65, 0x6c, - 0x70, 0x64, 0x65, 0x73, 0x6b, 0x10, 0xdb, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x73, 0x65, 0x6e, 0x64, 0x10, 0xdc, 0x06, 0x12, 0x10, 0x0a, 0x0b, 0x47, 0x65, 0x74, - 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x10, 0xdd, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x44, - 0x79, 0x6e, 0x61, 0x64, 0x6f, 0x74, 0x10, 0xde, 0x06, 0x12, 0x0a, 0x0a, 0x05, 0x44, 0x65, 0x6d, - 0x69, 0x6f, 0x10, 0xdf, 0x06, 0x12, 0x0b, 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x65, 0x74, 0x10, - 0xe0, 0x06, 0x12, 0x11, 0x0a, 0x0c, 0x4d, 0x79, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, - 0x6e, 0x74, 0x10, 0xe1, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x43, 0x6f, 0x70, 0x79, 0x73, 0x63, 0x61, - 0x70, 0x65, 0x10, 0xe2, 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x42, 0x65, 0x73, 0x6e, 0x61, 0x70, 0x70, - 0x79, 0x10, 0xe3, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x53, 0x61, 0x6c, 0x65, 0x73, 0x6d, 0x61, 0x74, - 0x65, 0x10, 0xe4, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x48, 0x65, 0x61, 0x74, 0x6d, 0x61, 0x70, 0x61, - 0x70, 0x69, 0x10, 0xe5, 0x06, 0x12, 0x11, 0x0a, 0x0c, 0x57, 0x65, 0x62, 0x73, 0x69, 0x74, 0x65, - 0x70, 0x75, 0x6c, 0x73, 0x65, 0x10, 0xe6, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x55, 0x63, 0x6c, 0x61, - 0x73, 0x73, 0x69, 0x66, 0x79, 0x10, 0xe7, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x76, - 0x65, 0x72, 0x74, 0x10, 0xe8, 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x50, 0x44, 0x46, 0x6d, 0x79, 0x55, - 0x52, 0x4c, 0x10, 0xe9, 0x06, 0x12, 0x10, 0x0a, 0x0b, 0x41, 0x70, 0x69, 0x32, 0x43, 0x6f, 0x6e, - 0x76, 0x65, 0x72, 0x74, 0x10, 0xea, 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x4f, 0x70, 0x73, 0x67, 0x65, - 0x6e, 0x69, 0x65, 0x10, 0xeb, 0x06, 0x12, 0x0b, 0x0a, 0x06, 0x47, 0x65, 0x6d, 0x69, 0x6e, 0x69, - 0x10, 0xec, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x48, 0x6f, 0x6e, 0x65, 0x79, 0x63, 0x6f, 0x6d, 0x62, - 0x10, 0xed, 0x06, 0x12, 0x14, 0x0a, 0x0f, 0x4b, 0x61, 0x6c, 0x74, 0x75, 0x72, 0x61, 0x41, 0x70, - 0x70, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0xee, 0x06, 0x12, 0x13, 0x0a, 0x0e, 0x4b, 0x61, 0x6c, - 0x74, 0x75, 0x72, 0x61, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x10, 0xef, 0x06, 0x12, 0x0a, - 0x0a, 0x05, 0x42, 0x69, 0x74, 0x47, 0x6f, 0x10, 0xf0, 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x4f, 0x70, - 0x74, 0x69, 0x64, 0x61, 0x73, 0x68, 0x10, 0xf1, 0x06, 0x12, 0x0a, 0x0a, 0x05, 0x49, 0x6d, 0x67, - 0x69, 0x78, 0x10, 0xf2, 0x06, 0x12, 0x10, 0x0a, 0x0b, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x54, 0x6f, - 0x54, 0x65, 0x78, 0x74, 0x10, 0xf3, 0x06, 0x12, 0x10, 0x0a, 0x0b, 0x50, 0x61, 0x67, 0x65, 0x32, - 0x49, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x10, 0xf4, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x51, 0x75, 0x69, - 0x63, 0x6b, 0x62, 0x61, 0x73, 0x65, 0x10, 0xf5, 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x52, 0x65, 0x64, - 0x62, 0x6f, 0x6f, 0x74, 0x68, 0x10, 0xf6, 0x06, 0x12, 0x0b, 0x0a, 0x06, 0x4e, 0x75, 0x62, 0x65, - 0x6c, 0x61, 0x10, 0xf7, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x49, 0x6e, 0x66, 0x6f, 0x62, 0x69, 0x70, - 0x10, 0xf8, 0x06, 0x12, 0x0a, 0x0a, 0x05, 0x55, 0x70, 0x72, 0x6f, 0x63, 0x10, 0xf9, 0x06, 0x12, - 0x0f, 0x0a, 0x0a, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x62, 0x65, 0x65, 0x10, 0xfa, 0x06, - 0x12, 0x0e, 0x0a, 0x09, 0x41, 0x66, 0x74, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x10, 0xfb, 0x06, - 0x12, 0x0c, 0x0a, 0x07, 0x45, 0x64, 0x75, 0x73, 0x69, 0x67, 0x6e, 0x10, 0xfc, 0x06, 0x12, 0x0b, - 0x0a, 0x06, 0x54, 0x65, 0x61, 0x6d, 0x75, 0x70, 0x10, 0xfd, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x57, - 0x6f, 0x72, 0x6b, 0x64, 0x61, 0x79, 0x10, 0xfe, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x4d, 0x6f, 0x6e, - 0x67, 0x6f, 0x44, 0x42, 0x10, 0xff, 0x06, 0x12, 0x08, 0x0a, 0x03, 0x4e, 0x47, 0x43, 0x10, 0x80, - 0x07, 0x12, 0x13, 0x0a, 0x0e, 0x44, 0x69, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x4f, 0x63, 0x65, 0x61, - 0x6e, 0x56, 0x32, 0x10, 0x81, 0x07, 0x12, 0x0e, 0x0a, 0x09, 0x53, 0x51, 0x4c, 0x53, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x10, 0x82, 0x07, 0x12, 0x08, 0x0a, 0x03, 0x46, 0x54, 0x50, 0x10, 0x83, 0x07, - 0x12, 0x0a, 0x0a, 0x05, 0x52, 0x65, 0x64, 0x69, 0x73, 0x10, 0x84, 0x07, 0x12, 0x09, 0x0a, 0x04, - 0x4c, 0x44, 0x41, 0x50, 0x10, 0x85, 0x07, 0x12, 0x0c, 0x0a, 0x07, 0x53, 0x68, 0x6f, 0x70, 0x69, - 0x66, 0x79, 0x10, 0x86, 0x07, 0x12, 0x0d, 0x0a, 0x08, 0x52, 0x61, 0x62, 0x62, 0x69, 0x74, 0x4d, - 0x51, 0x10, 0x87, 0x07, 0x12, 0x10, 0x0a, 0x0b, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, - 0x67, 0x65, 0x78, 0x10, 0x88, 0x07, 0x12, 0x0e, 0x0a, 0x09, 0x45, 0x74, 0x68, 0x65, 0x72, 0x73, - 0x63, 0x61, 0x6e, 0x10, 0x89, 0x07, 0x12, 0x0b, 0x0a, 0x06, 0x49, 0x6e, 0x66, 0x75, 0x72, 0x61, - 0x10, 0x8a, 0x07, 0x12, 0x0c, 0x0a, 0x07, 0x41, 0x6c, 0x63, 0x68, 0x65, 0x6d, 0x79, 0x10, 0x8b, - 0x07, 0x12, 0x10, 0x0a, 0x0b, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x61, 0x74, 0x69, 0x76, 0x65, - 0x10, 0x8c, 0x07, 0x12, 0x0c, 0x0a, 0x07, 0x4d, 0x6f, 0x72, 0x61, 0x6c, 0x69, 0x73, 0x10, 0x8d, - 0x07, 0x12, 0x0c, 0x0a, 0x07, 0x42, 0x73, 0x63, 0x53, 0x63, 0x61, 0x6e, 0x10, 0x8e, 0x07, 0x12, - 0x12, 0x0a, 0x0d, 0x43, 0x6f, 0x69, 0x6e, 0x4d, 0x61, 0x72, 0x6b, 0x65, 0x74, 0x43, 0x61, 0x70, - 0x10, 0x8f, 0x07, 0x12, 0x0a, 0x0a, 0x05, 0x50, 0x65, 0x72, 0x63, 0x79, 0x10, 0x90, 0x07, 0x12, - 0x11, 0x0a, 0x0c, 0x54, 0x69, 0x6e, 0x65, 0x73, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x10, - 0x91, 0x07, 0x12, 0x0b, 0x0a, 0x06, 0x50, 0x75, 0x6c, 0x75, 0x6d, 0x69, 0x10, 0x92, 0x07, 0x12, - 0x12, 0x0a, 0x0d, 0x53, 0x75, 0x70, 0x61, 0x62, 0x61, 0x73, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x10, 0x93, 0x07, 0x12, 0x10, 0x0a, 0x0b, 0x4e, 0x75, 0x47, 0x65, 0x74, 0x41, 0x70, 0x69, 0x4b, - 0x65, 0x79, 0x10, 0x94, 0x07, 0x12, 0x0a, 0x0a, 0x05, 0x41, 0x69, 0x76, 0x65, 0x6e, 0x10, 0x95, - 0x07, 0x12, 0x0c, 0x0a, 0x07, 0x50, 0x72, 0x65, 0x66, 0x65, 0x63, 0x74, 0x10, 0x96, 0x07, 0x12, - 0x0d, 0x0a, 0x08, 0x44, 0x6f, 0x63, 0x75, 0x73, 0x69, 0x67, 0x6e, 0x10, 0x97, 0x07, 0x12, 0x0e, - 0x0a, 0x09, 0x43, 0x6f, 0x75, 0x63, 0x68, 0x62, 0x61, 0x73, 0x65, 0x10, 0x98, 0x07, 0x12, 0x0e, - 0x0a, 0x09, 0x44, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x68, 0x75, 0x62, 0x10, 0x99, 0x07, 0x12, 0x19, - 0x0a, 0x14, 0x54, 0x72, 0x75, 0x66, 0x66, 0x6c, 0x65, 0x68, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x65, - 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x10, 0x9a, 0x07, 0x12, 0x10, 0x0a, 0x0b, 0x45, 0x6e, 0x76, - 0x6f, 0x79, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x10, 0x9b, 0x07, 0x12, 0x11, 0x0a, 0x0c, 0x47, - 0x69, 0x74, 0x48, 0x75, 0x62, 0x4f, 0x61, 0x75, 0x74, 0x68, 0x32, 0x10, 0x9c, 0x07, 0x12, 0x0f, - 0x0a, 0x0a, 0x53, 0x61, 0x6c, 0x65, 0x73, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x10, 0x9d, 0x07, 0x12, - 0x10, 0x0a, 0x0b, 0x48, 0x75, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x46, 0x61, 0x63, 0x65, 0x10, 0x9e, - 0x07, 0x12, 0x0e, 0x0a, 0x09, 0x53, 0x6e, 0x6f, 0x77, 0x66, 0x6c, 0x61, 0x6b, 0x65, 0x10, 0x9f, - 0x07, 0x12, 0x10, 0x0a, 0x0b, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x67, 0x72, 0x61, 0x70, 0x68, - 0x10, 0xa0, 0x07, 0x12, 0x0e, 0x0a, 0x09, 0x54, 0x61, 0x69, 0x6c, 0x73, 0x63, 0x61, 0x6c, 0x65, - 0x10, 0xa1, 0x07, 0x12, 0x10, 0x0a, 0x0b, 0x57, 0x65, 0x62, 0x33, 0x53, 0x74, 0x6f, 0x72, 0x61, - 0x67, 0x65, 0x10, 0xa2, 0x07, 0x12, 0x11, 0x0a, 0x0c, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x53, 0x74, - 0x6f, 0x72, 0x61, 0x67, 0x65, 0x10, 0xa3, 0x07, 0x12, 0x12, 0x0a, 0x0d, 0x50, 0x6c, 0x61, 0x6e, - 0x65, 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x44, 0x62, 0x10, 0xa4, 0x07, 0x12, 0x0e, 0x0a, 0x09, - 0x41, 0x6e, 0x74, 0x68, 0x72, 0x6f, 0x70, 0x69, 0x63, 0x10, 0xa5, 0x07, 0x12, 0x09, 0x0a, 0x04, - 0x52, 0x61, 0x6d, 0x70, 0x10, 0xa6, 0x07, 0x12, 0x0c, 0x0a, 0x07, 0x4b, 0x6c, 0x61, 0x76, 0x69, - 0x79, 0x6f, 0x10, 0xa7, 0x07, 0x12, 0x14, 0x0a, 0x0f, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x67, - 0x72, 0x61, 0x70, 0x68, 0x43, 0x6f, 0x64, 0x79, 0x10, 0xa8, 0x07, 0x12, 0x0e, 0x0a, 0x09, 0x56, - 0x6f, 0x69, 0x63, 0x65, 0x66, 0x6c, 0x6f, 0x77, 0x10, 0xa9, 0x07, 0x12, 0x0c, 0x0a, 0x07, 0x50, - 0x72, 0x69, 0x76, 0x61, 0x63, 0x79, 0x10, 0xaa, 0x07, 0x12, 0x0b, 0x0a, 0x06, 0x49, 0x50, 0x49, - 0x6e, 0x66, 0x6f, 0x10, 0xab, 0x07, 0x12, 0x10, 0x0a, 0x0b, 0x49, 0x70, 0x32, 0x6c, 0x6f, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0xac, 0x07, 0x12, 0x0e, 0x0a, 0x09, 0x49, 0x6e, 0x73, 0x74, - 0x61, 0x6d, 0x6f, 0x6a, 0x6f, 0x10, 0xad, 0x07, 0x12, 0x0e, 0x0a, 0x09, 0x50, 0x6f, 0x72, 0x74, - 0x61, 0x69, 0x6e, 0x65, 0x72, 0x10, 0xae, 0x07, 0x12, 0x13, 0x0a, 0x0e, 0x50, 0x6f, 0x72, 0x74, - 0x61, 0x69, 0x6e, 0x65, 0x72, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0xaf, 0x07, 0x12, 0x0b, 0x0a, - 0x06, 0x4c, 0x6f, 0x67, 0x67, 0x6c, 0x79, 0x10, 0xb0, 0x07, 0x12, 0x0c, 0x0a, 0x07, 0x4f, 0x70, - 0x65, 0x6e, 0x56, 0x70, 0x6e, 0x10, 0xb1, 0x07, 0x12, 0x1e, 0x0a, 0x19, 0x56, 0x61, 0x67, 0x72, - 0x61, 0x6e, 0x74, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0xb2, 0x07, 0x12, 0x10, 0x0a, 0x0b, 0x42, 0x65, 0x74, 0x74, - 0x65, 0x72, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x10, 0xb3, 0x07, 0x12, 0x0d, 0x0a, 0x08, 0x5a, 0x65, - 0x72, 0x6f, 0x54, 0x69, 0x65, 0x72, 0x10, 0xb4, 0x07, 0x12, 0x0e, 0x0a, 0x09, 0x41, 0x70, 0x70, - 0x4f, 0x70, 0x74, 0x69, 0x63, 0x73, 0x10, 0xb5, 0x07, 0x12, 0x0d, 0x0a, 0x08, 0x4d, 0x65, 0x74, - 0x61, 0x62, 0x61, 0x73, 0x65, 0x10, 0xb6, 0x07, 0x12, 0x11, 0x0a, 0x0c, 0x43, 0x6f, 0x69, 0x6e, - 0x62, 0x61, 0x73, 0x65, 0x57, 0x61, 0x61, 0x53, 0x10, 0xb7, 0x07, 0x12, 0x11, 0x0a, 0x0c, 0x4c, - 0x65, 0x6d, 0x6f, 0x6e, 0x53, 0x71, 0x75, 0x65, 0x65, 0x7a, 0x79, 0x10, 0xb8, 0x07, 0x12, 0x0d, - 0x0a, 0x08, 0x42, 0x75, 0x64, 0x69, 0x62, 0x61, 0x73, 0x65, 0x10, 0xb9, 0x07, 0x12, 0x0f, 0x0a, - 0x0a, 0x44, 0x65, 0x6e, 0x6f, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x10, 0xba, 0x07, 0x12, 0x0b, - 0x0a, 0x06, 0x53, 0x74, 0x72, 0x69, 0x70, 0x6f, 0x10, 0xbb, 0x07, 0x12, 0x0c, 0x0a, 0x07, 0x52, - 0x65, 0x70, 0x6c, 0x79, 0x49, 0x4f, 0x10, 0xbc, 0x07, 0x12, 0x0e, 0x0a, 0x09, 0x52, 0x65, 0x70, - 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65, 0x10, 0xbd, 0x07, 0x42, 0x3d, 0x5a, 0x3b, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x72, 0x75, 0x66, 0x66, 0x6c, 0x65, 0x73, - 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, 0x74, 0x72, 0x75, 0x66, 0x66, 0x6c, 0x65, 0x68, - 0x6f, 0x67, 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x62, 0x2f, 0x64, 0x65, 0x74, - 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x62, 0x73, 0x10, 0x92, 0x05, 0x12, 0x14, 0x0a, 0x0b, 0x53, 0x63, 0x72, 0x61, 0x70, 0x65, 0x72, + 0x53, 0x69, 0x74, 0x65, 0x10, 0x93, 0x05, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x0d, 0x0a, 0x08, 0x53, + 0x63, 0x72, 0x61, 0x70, 0x66, 0x6c, 0x79, 0x10, 0x94, 0x05, 0x12, 0x10, 0x0a, 0x0b, 0x53, 0x69, + 0x6d, 0x70, 0x6c, 0x79, 0x4e, 0x6f, 0x74, 0x65, 0x64, 0x10, 0x95, 0x05, 0x12, 0x12, 0x0a, 0x0d, + 0x54, 0x72, 0x61, 0x76, 0x65, 0x6c, 0x50, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x73, 0x10, 0x96, 0x05, + 0x12, 0x0f, 0x0a, 0x0a, 0x57, 0x65, 0x62, 0x53, 0x63, 0x72, 0x61, 0x70, 0x65, 0x72, 0x10, 0x97, + 0x05, 0x12, 0x0c, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x76, 0x69, 0x65, 0x72, 0x10, 0x98, 0x05, 0x12, + 0x0c, 0x0a, 0x07, 0x43, 0x6f, 0x75, 0x72, 0x69, 0x65, 0x72, 0x10, 0x99, 0x05, 0x12, 0x0a, 0x0a, + 0x05, 0x44, 0x69, 0x74, 0x74, 0x6f, 0x10, 0x9a, 0x05, 0x12, 0x0a, 0x0a, 0x05, 0x46, 0x69, 0x6e, + 0x64, 0x6c, 0x10, 0x9b, 0x05, 0x12, 0x0d, 0x0a, 0x08, 0x4c, 0x65, 0x6e, 0x64, 0x66, 0x6c, 0x6f, + 0x77, 0x10, 0x9c, 0x05, 0x12, 0x0f, 0x0a, 0x0a, 0x4d, 0x6f, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x10, 0x9d, 0x05, 0x12, 0x11, 0x0a, 0x0c, 0x4f, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x74, + 0x61, 0x73, 0x6f, 0x66, 0x74, 0x10, 0x9e, 0x05, 0x12, 0x0a, 0x0a, 0x05, 0x50, 0x6f, 0x64, 0x69, + 0x6f, 0x10, 0x9f, 0x05, 0x12, 0x0c, 0x0a, 0x07, 0x52, 0x6f, 0x63, 0x6b, 0x73, 0x65, 0x74, 0x10, + 0xa0, 0x05, 0x12, 0x0a, 0x0a, 0x05, 0x52, 0x6f, 0x77, 0x6e, 0x64, 0x10, 0xa1, 0x05, 0x12, 0x0e, + 0x0a, 0x09, 0x53, 0x68, 0x6f, 0x74, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x10, 0xa2, 0x05, 0x12, 0x0d, + 0x0a, 0x08, 0x53, 0x77, 0x69, 0x66, 0x74, 0x79, 0x70, 0x65, 0x10, 0xa3, 0x05, 0x12, 0x0c, 0x0a, + 0x07, 0x54, 0x77, 0x69, 0x74, 0x74, 0x65, 0x72, 0x10, 0xa4, 0x05, 0x12, 0x0a, 0x0a, 0x05, 0x48, + 0x6f, 0x6e, 0x65, 0x79, 0x10, 0xa5, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x46, 0x72, 0x65, 0x73, 0x68, + 0x64, 0x65, 0x73, 0x6b, 0x10, 0xa6, 0x05, 0x12, 0x0b, 0x0a, 0x06, 0x55, 0x70, 0x77, 0x61, 0x76, + 0x65, 0x10, 0xa7, 0x05, 0x12, 0x0d, 0x0a, 0x08, 0x46, 0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, + 0x10, 0xa8, 0x05, 0x12, 0x0f, 0x0a, 0x0a, 0x46, 0x72, 0x65, 0x73, 0x68, 0x62, 0x6f, 0x6f, 0x6b, + 0x73, 0x10, 0xa9, 0x05, 0x12, 0x09, 0x0a, 0x04, 0x4d, 0x69, 0x74, 0x65, 0x10, 0xaa, 0x05, 0x12, + 0x0b, 0x0a, 0x06, 0x44, 0x65, 0x70, 0x75, 0x74, 0x79, 0x10, 0xab, 0x05, 0x12, 0x0c, 0x0a, 0x07, + 0x42, 0x65, 0x65, 0x62, 0x6f, 0x6c, 0x65, 0x10, 0xac, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x43, 0x61, + 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x10, 0xad, 0x05, 0x12, 0x0b, 0x0a, 0x06, 0x4b, 0x61, + 0x6e, 0x62, 0x61, 0x6e, 0x10, 0xae, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x57, 0x6f, 0x72, 0x6b, 0x73, + 0x6e, 0x61, 0x70, 0x73, 0x10, 0xaf, 0x05, 0x12, 0x10, 0x0a, 0x0b, 0x4d, 0x79, 0x49, 0x6e, 0x74, + 0x65, 0x72, 0x76, 0x61, 0x6c, 0x73, 0x10, 0xb0, 0x05, 0x12, 0x11, 0x0a, 0x0c, 0x49, 0x6e, 0x76, + 0x6f, 0x69, 0x63, 0x65, 0x4f, 0x63, 0x65, 0x61, 0x6e, 0x10, 0xb1, 0x05, 0x12, 0x0f, 0x0a, 0x0a, + 0x53, 0x68, 0x65, 0x72, 0x70, 0x61, 0x64, 0x65, 0x73, 0x6b, 0x10, 0xb2, 0x05, 0x12, 0x0f, 0x0a, + 0x0a, 0x4d, 0x72, 0x74, 0x69, 0x63, 0x6b, 0x74, 0x6f, 0x63, 0x6b, 0x10, 0xb3, 0x05, 0x12, 0x0d, + 0x0a, 0x08, 0x43, 0x68, 0x61, 0x74, 0x66, 0x75, 0x6c, 0x65, 0x10, 0xb4, 0x05, 0x12, 0x11, 0x0a, + 0x0c, 0x41, 0x65, 0x72, 0x6f, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x10, 0xb5, 0x05, + 0x12, 0x11, 0x0a, 0x0c, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x6f, 0x63, 0x74, 0x6f, 0x70, 0x75, 0x73, + 0x10, 0xb6, 0x05, 0x12, 0x0d, 0x0a, 0x08, 0x46, 0x75, 0x73, 0x65, 0x62, 0x69, 0x6c, 0x6c, 0x10, + 0xb7, 0x05, 0x12, 0x0f, 0x0a, 0x0a, 0x47, 0x65, 0x63, 0x6b, 0x6f, 0x62, 0x6f, 0x61, 0x72, 0x64, + 0x10, 0xb8, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x47, 0x6f, 0x73, 0x71, 0x75, 0x61, 0x72, 0x65, 0x64, + 0x10, 0xb9, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x4d, 0x6f, 0x6f, 0x6e, 0x63, 0x6c, 0x65, 0x72, 0x6b, + 0x10, 0xba, 0x05, 0x12, 0x0d, 0x0a, 0x08, 0x50, 0x61, 0x79, 0x6d, 0x6f, 0x61, 0x70, 0x70, 0x10, + 0xbb, 0x05, 0x12, 0x0b, 0x0a, 0x06, 0x4d, 0x69, 0x78, 0x6d, 0x61, 0x78, 0x10, 0xbc, 0x05, 0x12, + 0x0e, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x73, 0x74, 0x10, 0xbd, 0x05, 0x12, + 0x10, 0x0a, 0x0b, 0x52, 0x65, 0x70, 0x61, 0x69, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x72, 0x10, 0xbe, + 0x05, 0x12, 0x0d, 0x0a, 0x08, 0x47, 0x6f, 0x73, 0x68, 0x69, 0x70, 0x70, 0x6f, 0x10, 0xbf, 0x05, + 0x12, 0x0b, 0x0a, 0x06, 0x53, 0x69, 0x67, 0x6f, 0x70, 0x74, 0x10, 0xc0, 0x05, 0x12, 0x0d, 0x0a, + 0x08, 0x53, 0x75, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x10, 0xc1, 0x05, 0x12, 0x0c, 0x0a, 0x07, + 0x56, 0x69, 0x65, 0x77, 0x6e, 0x65, 0x6f, 0x10, 0xc2, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x42, 0x6f, + 0x6f, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x65, 0x10, 0xc3, 0x05, 0x12, 0x10, 0x0a, 0x0b, 0x43, 0x61, + 0x70, 0x74, 0x61, 0x69, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x10, 0xc4, 0x05, 0x12, 0x0e, 0x0a, 0x09, + 0x43, 0x68, 0x65, 0x63, 0x6b, 0x76, 0x69, 0x73, 0x74, 0x10, 0xc5, 0x05, 0x12, 0x0c, 0x0a, 0x07, + 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x67, 0x6f, 0x10, 0xc6, 0x05, 0x12, 0x0a, 0x0a, 0x05, 0x43, 0x6c, + 0x6f, 0x7a, 0x65, 0x10, 0xc7, 0x05, 0x12, 0x0b, 0x0a, 0x06, 0x46, 0x6f, 0x72, 0x6d, 0x49, 0x4f, + 0x10, 0xc8, 0x05, 0x12, 0x0f, 0x0a, 0x0a, 0x46, 0x6f, 0x72, 0x6d, 0x42, 0x75, 0x63, 0x6b, 0x65, + 0x74, 0x10, 0xc9, 0x05, 0x12, 0x0d, 0x0a, 0x08, 0x47, 0x6f, 0x43, 0x61, 0x6e, 0x76, 0x61, 0x73, + 0x10, 0xca, 0x05, 0x12, 0x0c, 0x0a, 0x07, 0x4d, 0x61, 0x64, 0x4b, 0x75, 0x64, 0x75, 0x10, 0xcb, + 0x05, 0x12, 0x0f, 0x0a, 0x0a, 0x4e, 0x6f, 0x7a, 0x62, 0x65, 0x54, 0x65, 0x61, 0x6d, 0x73, 0x10, + 0xcc, 0x05, 0x12, 0x0b, 0x0a, 0x06, 0x50, 0x61, 0x70, 0x79, 0x72, 0x73, 0x10, 0xcd, 0x05, 0x12, + 0x12, 0x0a, 0x0d, 0x53, 0x75, 0x70, 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x65, 0x73, 0x41, 0x50, 0x49, + 0x10, 0xce, 0x05, 0x12, 0x0c, 0x0a, 0x07, 0x54, 0x61, 0x6c, 0x6c, 0x79, 0x66, 0x79, 0x10, 0xcf, + 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x5a, 0x65, 0x6e, 0x6b, 0x69, 0x74, 0x41, 0x50, 0x49, 0x10, 0xd0, + 0x05, 0x12, 0x0f, 0x0a, 0x0a, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x10, + 0xd1, 0x05, 0x12, 0x0f, 0x0a, 0x0a, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x43, 0x61, 0x72, 0x65, + 0x10, 0xd2, 0x05, 0x12, 0x0d, 0x0a, 0x08, 0x42, 0x6f, 0x72, 0x67, 0x62, 0x61, 0x73, 0x65, 0x10, + 0xd3, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x50, 0x69, 0x70, 0x65, 0x64, 0x72, 0x65, 0x61, 0x6d, 0x10, + 0xd4, 0x05, 0x12, 0x09, 0x0a, 0x04, 0x53, 0x69, 0x72, 0x76, 0x10, 0xd5, 0x05, 0x12, 0x0c, 0x0a, + 0x07, 0x44, 0x69, 0x66, 0x66, 0x62, 0x6f, 0x74, 0x10, 0xd6, 0x05, 0x12, 0x10, 0x0a, 0x0b, 0x45, + 0x69, 0x67, 0x68, 0x74, 0x78, 0x45, 0x69, 0x67, 0x68, 0x74, 0x10, 0xd7, 0x05, 0x12, 0x0c, 0x0a, + 0x07, 0x53, 0x65, 0x6e, 0x64, 0x6f, 0x73, 0x6f, 0x10, 0xd8, 0x05, 0x12, 0x11, 0x0a, 0x0c, 0x50, + 0x72, 0x69, 0x6e, 0x74, 0x66, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0xd9, 0x05, 0x12, 0x0e, + 0x0a, 0x09, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x10, 0xda, 0x05, 0x12, 0x0f, + 0x0a, 0x0a, 0x50, 0x61, 0x6e, 0x64, 0x61, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x10, 0xdb, 0x05, 0x12, + 0x0a, 0x0a, 0x05, 0x50, 0x61, 0x79, 0x6d, 0x6f, 0x10, 0xdc, 0x05, 0x12, 0x1d, 0x0a, 0x18, 0x41, + 0x76, 0x61, 0x7a, 0x61, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x41, 0x63, 0x63, 0x65, + 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0xdd, 0x05, 0x12, 0x14, 0x0a, 0x0f, 0x50, 0x6c, + 0x61, 0x6e, 0x76, 0x69, 0x65, 0x77, 0x4c, 0x65, 0x61, 0x6e, 0x4b, 0x69, 0x74, 0x10, 0xde, 0x05, + 0x12, 0x0e, 0x0a, 0x09, 0x4c, 0x69, 0x76, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x6d, 0x10, 0xdf, 0x05, + 0x12, 0x0b, 0x0a, 0x06, 0x4b, 0x75, 0x43, 0x6f, 0x69, 0x6e, 0x10, 0xe0, 0x05, 0x12, 0x0c, 0x0a, + 0x07, 0x4d, 0x65, 0x74, 0x61, 0x41, 0x50, 0x49, 0x10, 0xe1, 0x05, 0x12, 0x0d, 0x0a, 0x08, 0x4e, + 0x69, 0x63, 0x65, 0x48, 0x61, 0x73, 0x68, 0x10, 0xe2, 0x05, 0x12, 0x0a, 0x0a, 0x05, 0x43, 0x65, + 0x78, 0x49, 0x4f, 0x10, 0xe3, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x4b, 0x6c, 0x69, 0x70, 0x66, 0x6f, + 0x6c, 0x69, 0x6f, 0x10, 0xe4, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x44, 0x79, 0x6e, 0x61, 0x74, 0x72, + 0x61, 0x63, 0x65, 0x10, 0xe5, 0x05, 0x12, 0x11, 0x0a, 0x0c, 0x4d, 0x6f, 0x6c, 0x6c, 0x69, 0x65, + 0x41, 0x50, 0x49, 0x4b, 0x65, 0x79, 0x10, 0xe6, 0x05, 0x12, 0x16, 0x0a, 0x11, 0x4d, 0x6f, 0x6c, + 0x6c, 0x69, 0x65, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0xe7, + 0x05, 0x12, 0x10, 0x0a, 0x0b, 0x42, 0x61, 0x73, 0x69, 0x73, 0x54, 0x68, 0x65, 0x6f, 0x72, 0x79, + 0x10, 0xe8, 0x05, 0x12, 0x0d, 0x0a, 0x08, 0x4e, 0x6f, 0x72, 0x64, 0x69, 0x67, 0x65, 0x6e, 0x10, + 0xe9, 0x05, 0x12, 0x1c, 0x0a, 0x17, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x6d, 0x69, 0x74, 0x68, 0x45, + 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x10, 0xea, 0x05, + 0x12, 0x13, 0x0a, 0x0e, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x6d, 0x69, 0x74, 0x68, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x10, 0xeb, 0x05, 0x12, 0x08, 0x0a, 0x03, 0x4d, 0x75, 0x78, 0x10, 0xec, 0x05, 0x12, + 0x0b, 0x0a, 0x06, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x10, 0xed, 0x05, 0x12, 0x0d, 0x0a, 0x08, + 0x53, 0x65, 0x6e, 0x64, 0x62, 0x69, 0x72, 0x64, 0x10, 0xee, 0x05, 0x12, 0x1c, 0x0a, 0x17, 0x53, + 0x65, 0x6e, 0x64, 0x62, 0x69, 0x72, 0x64, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x41, 0x50, 0x49, 0x10, 0xef, 0x05, 0x12, 0x0b, 0x0a, 0x06, 0x4d, 0x69, 0x64, + 0x69, 0x73, 0x65, 0x10, 0xf0, 0x05, 0x12, 0x0d, 0x0a, 0x08, 0x4d, 0x6f, 0x63, 0x6b, 0x61, 0x72, + 0x6f, 0x6f, 0x10, 0xf1, 0x05, 0x12, 0x0b, 0x0a, 0x06, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x34, 0x10, + 0xf2, 0x05, 0x12, 0x0b, 0x0a, 0x06, 0x50, 0x69, 0x6e, 0x61, 0x74, 0x61, 0x10, 0xf3, 0x05, 0x12, + 0x11, 0x0a, 0x0c, 0x42, 0x72, 0x6f, 0x77, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x10, + 0xf4, 0x05, 0x12, 0x18, 0x0a, 0x13, 0x43, 0x72, 0x6f, 0x73, 0x73, 0x42, 0x72, 0x6f, 0x77, 0x73, + 0x65, 0x72, 0x54, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x10, 0xf5, 0x05, 0x12, 0x0d, 0x0a, 0x08, + 0x4c, 0x6f, 0x61, 0x64, 0x6d, 0x69, 0x6c, 0x6c, 0x10, 0xf6, 0x05, 0x12, 0x0f, 0x0a, 0x0a, 0x54, + 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x42, 0x6f, 0x74, 0x10, 0xf7, 0x05, 0x12, 0x10, 0x0a, 0x0b, + 0x4b, 0x6e, 0x61, 0x70, 0x73, 0x61, 0x63, 0x6b, 0x50, 0x72, 0x6f, 0x10, 0xf8, 0x05, 0x12, 0x09, + 0x0a, 0x04, 0x51, 0x61, 0x73, 0x65, 0x10, 0xf9, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x44, 0x61, 0x72, + 0x65, 0x62, 0x6f, 0x6f, 0x73, 0x74, 0x10, 0xfa, 0x05, 0x12, 0x0d, 0x0a, 0x08, 0x47, 0x54, 0x4d, + 0x65, 0x74, 0x72, 0x69, 0x78, 0x10, 0xfb, 0x05, 0x12, 0x0d, 0x0a, 0x08, 0x48, 0x6f, 0x6c, 0x69, + 0x73, 0x74, 0x69, 0x63, 0x10, 0xfc, 0x05, 0x12, 0x0c, 0x0a, 0x07, 0x50, 0x61, 0x72, 0x73, 0x65, + 0x72, 0x73, 0x10, 0xfd, 0x05, 0x12, 0x12, 0x0a, 0x0d, 0x53, 0x63, 0x72, 0x75, 0x74, 0x69, 0x6e, + 0x69, 0x7a, 0x65, 0x72, 0x43, 0x69, 0x10, 0xfe, 0x05, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x6f, 0x6e, + 0x61, 0x72, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x10, 0xff, 0x05, 0x12, 0x10, 0x0a, 0x0b, 0x41, 0x50, + 0x49, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x10, 0x80, 0x06, 0x12, 0x14, 0x0a, 0x0f, + 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6f, 0x6c, 0x73, 0x10, + 0x81, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x43, 0x72, 0x61, 0x66, 0x74, 0x4d, 0x79, 0x50, 0x44, 0x46, + 0x10, 0x82, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x44, 0x4b, + 0x10, 0x83, 0x06, 0x12, 0x15, 0x0a, 0x0c, 0x47, 0x6c, 0x69, 0x74, 0x74, 0x65, 0x72, 0x6c, 0x79, + 0x41, 0x50, 0x49, 0x10, 0x84, 0x06, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x0d, 0x0a, 0x08, 0x48, 0x79, + 0x62, 0x69, 0x73, 0x63, 0x75, 0x73, 0x10, 0x85, 0x06, 0x12, 0x09, 0x0a, 0x04, 0x4d, 0x69, 0x72, + 0x6f, 0x10, 0x86, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x70, 0x61, + 0x67, 0x65, 0x10, 0x87, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x70, + 0x61, 0x6c, 0x10, 0x88, 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x54, 0x65, 0x6c, 0x65, 0x74, 0x79, 0x70, + 0x65, 0x10, 0x89, 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x54, 0x69, 0x6d, 0x65, 0x43, 0x61, 0x6d, 0x70, + 0x10, 0x8a, 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x55, 0x73, 0x65, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x10, + 0x8b, 0x06, 0x12, 0x0b, 0x0a, 0x06, 0x57, 0x69, 0x73, 0x74, 0x69, 0x61, 0x10, 0x8c, 0x06, 0x12, + 0x0f, 0x0a, 0x0a, 0x53, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x61, 0x64, 0x61, 0x72, 0x10, 0x8d, 0x06, + 0x12, 0x10, 0x0a, 0x0b, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x10, + 0x8e, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x43, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x69, 0x72, 0x79, 0x10, + 0x8f, 0x06, 0x12, 0x11, 0x0a, 0x0c, 0x45, 0x78, 0x74, 0x72, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x41, + 0x50, 0x49, 0x10, 0x90, 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x62, 0x6c, + 0x65, 0x10, 0x91, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x4d, 0x61, 0x67, 0x69, 0x63, 0x42, 0x65, 0x6c, + 0x6c, 0x10, 0x92, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x74, 0x6f, 0x72, 0x6d, 0x62, 0x6f, 0x61, + 0x72, 0x64, 0x10, 0x93, 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x41, 0x70, 0x69, 0x6c, 0x61, 0x79, 0x65, + 0x72, 0x10, 0x94, 0x06, 0x12, 0x0b, 0x0a, 0x06, 0x44, 0x69, 0x73, 0x71, 0x75, 0x73, 0x10, 0x95, + 0x06, 0x12, 0x0b, 0x0a, 0x06, 0x57, 0x6f, 0x6f, 0x70, 0x72, 0x61, 0x10, 0x96, 0x06, 0x12, 0x0e, + 0x0a, 0x09, 0x50, 0x61, 0x70, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x10, 0x97, 0x06, 0x12, 0x0c, + 0x0a, 0x07, 0x47, 0x75, 0x6d, 0x72, 0x6f, 0x61, 0x64, 0x10, 0x98, 0x06, 0x12, 0x0f, 0x0a, 0x0a, + 0x50, 0x61, 0x79, 0x64, 0x69, 0x72, 0x74, 0x61, 0x70, 0x70, 0x10, 0x99, 0x06, 0x12, 0x0e, 0x0a, + 0x09, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x69, 0x66, 0x79, 0x10, 0x9a, 0x06, 0x12, 0x0f, 0x0a, + 0x0a, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x63, 0x61, 0x6b, 0x65, 0x10, 0x9b, 0x06, 0x12, 0x0f, + 0x0a, 0x0a, 0x4a, 0x75, 0x6d, 0x70, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x10, 0x9c, 0x06, 0x12, + 0x0f, 0x0a, 0x0a, 0x4c, 0x75, 0x6e, 0x63, 0x68, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x10, 0x9d, 0x06, + 0x12, 0x0c, 0x0a, 0x07, 0x52, 0x6f, 0x73, 0x65, 0x74, 0x74, 0x65, 0x10, 0x9e, 0x06, 0x12, 0x09, + 0x0a, 0x04, 0x59, 0x65, 0x6c, 0x70, 0x10, 0x9f, 0x06, 0x12, 0x0a, 0x0a, 0x05, 0x41, 0x74, 0x65, + 0x72, 0x61, 0x10, 0xa0, 0x06, 0x12, 0x12, 0x0a, 0x0d, 0x45, 0x63, 0x6f, 0x53, 0x74, 0x72, 0x75, + 0x78, 0x75, 0x72, 0x65, 0x49, 0x54, 0x10, 0xa1, 0x06, 0x12, 0x08, 0x0a, 0x03, 0x41, 0x68, 0x61, + 0x10, 0xa2, 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x50, 0x61, 0x72, 0x73, 0x65, 0x68, 0x75, 0x62, 0x10, + 0xa3, 0x06, 0x12, 0x11, 0x0a, 0x0c, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x43, 0x6c, 0x6f, + 0x75, 0x64, 0x10, 0xa4, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x73, 0x6d, + 0x69, 0x74, 0x68, 0x10, 0xa5, 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x46, 0x6c, 0x6f, 0x77, 0x64, 0x61, + 0x73, 0x68, 0x10, 0xa6, 0x06, 0x12, 0x11, 0x0a, 0x08, 0x46, 0x6c, 0x6f, 0x77, 0x64, 0x6f, 0x63, + 0x6b, 0x10, 0xa7, 0x06, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x0b, 0x0a, 0x06, 0x46, 0x69, 0x62, 0x65, + 0x72, 0x79, 0x10, 0xa8, 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x54, 0x79, 0x70, 0x65, 0x74, 0x61, 0x6c, + 0x6b, 0x10, 0xa9, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x56, 0x6f, 0x6f, 0x64, 0x6f, 0x6f, 0x53, 0x4d, + 0x53, 0x10, 0xaa, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x5a, 0x75, 0x6c, 0x69, 0x70, 0x43, 0x68, 0x61, + 0x74, 0x10, 0xab, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x46, 0x6f, 0x72, 0x6d, 0x63, 0x72, 0x61, 0x66, + 0x74, 0x10, 0xac, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x49, 0x65, 0x78, 0x61, 0x70, 0x69, 0x73, 0x10, + 0xad, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x52, 0x65, 0x61, 0x63, 0x68, 0x6d, 0x61, 0x69, 0x6c, 0x10, + 0xae, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x43, 0x68, 0x61, 0x72, 0x74, 0x6d, 0x6f, 0x67, 0x75, 0x6c, + 0x10, 0xaf, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x41, 0x70, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x65, 0x64, + 0x64, 0x10, 0xb0, 0x06, 0x12, 0x08, 0x0a, 0x03, 0x57, 0x69, 0x74, 0x10, 0xb1, 0x06, 0x12, 0x15, + 0x0a, 0x10, 0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x73, 0x10, 0xb2, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x44, 0x69, 0x67, 0x67, 0x65, 0x72, 0x6e, + 0x61, 0x75, 0x74, 0x10, 0xb3, 0x06, 0x12, 0x10, 0x0a, 0x0b, 0x4d, 0x6f, 0x6e, 0x6b, 0x65, 0x79, + 0x4c, 0x65, 0x61, 0x72, 0x6e, 0x10, 0xb4, 0x06, 0x12, 0x0a, 0x0a, 0x05, 0x44, 0x75, 0x70, 0x6c, + 0x79, 0x10, 0xb5, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x50, 0x6f, 0x73, 0x74, 0x62, 0x61, 0x63, 0x6b, + 0x73, 0x10, 0xb6, 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x32, + 0x10, 0xb7, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x5a, 0x65, 0x6e, 0x52, 0x6f, 0x77, 0x73, 0x10, 0xb8, + 0x06, 0x12, 0x10, 0x0a, 0x0b, 0x5a, 0x69, 0x70, 0x63, 0x6f, 0x64, 0x65, 0x62, 0x61, 0x73, 0x65, + 0x10, 0xb9, 0x06, 0x12, 0x0b, 0x0a, 0x06, 0x54, 0x65, 0x66, 0x74, 0x65, 0x72, 0x10, 0xba, 0x06, + 0x12, 0x0a, 0x0a, 0x05, 0x54, 0x77, 0x69, 0x73, 0x74, 0x10, 0xbb, 0x06, 0x12, 0x16, 0x0a, 0x11, + 0x42, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x72, 0x65, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x10, 0xbc, 0x06, 0x12, 0x11, 0x0a, 0x0c, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x43, 0x6f, 0x6e, + 0x76, 0x65, 0x72, 0x74, 0x10, 0xbd, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x47, 0x72, 0x61, 0x66, 0x61, + 0x6e, 0x61, 0x10, 0xbe, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, + 0x41, 0x70, 0x69, 0x10, 0xbf, 0x06, 0x12, 0x11, 0x0a, 0x0c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, + 0x65, 0x72, 0x77, 0x69, 0x73, 0x65, 0x10, 0xc0, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x42, 0x75, 0x6c, + 0x6b, 0x73, 0x6d, 0x73, 0x10, 0xc1, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x44, 0x61, 0x74, 0x61, 0x62, + 0x6f, 0x78, 0x10, 0xc2, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x4f, 0x6e, 0x65, 0x73, 0x69, 0x67, 0x6e, + 0x61, 0x6c, 0x10, 0xc3, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x52, 0x65, 0x6e, 0x74, 0x6d, 0x61, 0x6e, + 0x10, 0xc4, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x50, 0x61, 0x72, 0x73, 0x65, 0x75, 0x72, 0x10, 0xc5, + 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x44, 0x6f, 0x63, 0x70, 0x61, 0x72, 0x73, 0x65, 0x72, 0x10, 0xc6, + 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x46, 0x6f, 0x72, 0x6d, 0x73, 0x69, 0x74, 0x65, 0x10, 0xc7, 0x06, + 0x12, 0x11, 0x0a, 0x0c, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x74, 0x61, 0x69, 0x6c, 0x6f, 0x72, + 0x10, 0xc8, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x4c, 0x65, 0x6d, 0x6c, 0x69, 0x73, 0x74, 0x10, 0xc9, + 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x64, 0x70, 0x61, 0x64, 0x10, 0xca, 0x06, 0x12, + 0x0e, 0x0a, 0x09, 0x46, 0x6f, 0x72, 0x6d, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x10, 0xcb, 0x06, 0x12, + 0x10, 0x0a, 0x0b, 0x43, 0x6f, 0x64, 0x65, 0x63, 0x6c, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x10, 0xcc, + 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x43, 0x6f, 0x64, 0x65, 0x6d, 0x61, 0x67, 0x69, 0x63, 0x10, 0xcd, + 0x06, 0x12, 0x0a, 0x0a, 0x05, 0x56, 0x62, 0x6f, 0x75, 0x74, 0x10, 0xce, 0x06, 0x12, 0x0e, 0x0a, + 0x09, 0x4e, 0x69, 0x67, 0x68, 0x74, 0x66, 0x61, 0x6c, 0x6c, 0x10, 0xcf, 0x06, 0x12, 0x0f, 0x0a, + 0x0a, 0x46, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x4c, 0x61, 0x62, 0x73, 0x10, 0xd0, 0x06, 0x12, 0x11, + 0x0a, 0x0c, 0x53, 0x70, 0x65, 0x65, 0x63, 0x68, 0x54, 0x65, 0x78, 0x74, 0x41, 0x49, 0x10, 0xd1, + 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x50, 0x6f, 0x6c, 0x6c, 0x73, 0x41, 0x50, 0x49, 0x10, 0xd2, 0x06, + 0x12, 0x0b, 0x0a, 0x06, 0x53, 0x69, 0x6d, 0x46, 0x69, 0x6e, 0x10, 0xd3, 0x06, 0x12, 0x0a, 0x0a, + 0x05, 0x53, 0x63, 0x61, 0x6c, 0x72, 0x10, 0xd4, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x4b, 0x61, 0x6e, + 0x62, 0x61, 0x6e, 0x74, 0x6f, 0x6f, 0x6c, 0x10, 0xd5, 0x06, 0x12, 0x10, 0x0a, 0x0b, 0x42, 0x72, + 0x69, 0x67, 0x68, 0x74, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x10, 0xd6, 0x06, 0x12, 0x0c, 0x0a, 0x07, + 0x48, 0x6f, 0x74, 0x77, 0x69, 0x72, 0x65, 0x10, 0xd7, 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x49, 0x6e, + 0x73, 0x74, 0x61, 0x62, 0x6f, 0x74, 0x10, 0xd8, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x54, 0x69, 0x6d, + 0x65, 0x6b, 0x69, 0x74, 0x10, 0xd9, 0x06, 0x12, 0x10, 0x0a, 0x0b, 0x49, 0x6e, 0x74, 0x65, 0x72, + 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x10, 0xda, 0x06, 0x12, 0x11, 0x0a, 0x0c, 0x4d, 0x6f, 0x6a, + 0x6f, 0x68, 0x65, 0x6c, 0x70, 0x64, 0x65, 0x73, 0x6b, 0x10, 0xdb, 0x06, 0x12, 0x0f, 0x0a, 0x0a, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x73, 0x65, 0x6e, 0x64, 0x10, 0xdc, 0x06, 0x12, 0x10, 0x0a, + 0x0b, 0x47, 0x65, 0x74, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x10, 0xdd, 0x06, 0x12, + 0x0c, 0x0a, 0x07, 0x44, 0x79, 0x6e, 0x61, 0x64, 0x6f, 0x74, 0x10, 0xde, 0x06, 0x12, 0x0a, 0x0a, + 0x05, 0x44, 0x65, 0x6d, 0x69, 0x6f, 0x10, 0xdf, 0x06, 0x12, 0x0b, 0x0a, 0x06, 0x54, 0x6f, 0x6b, + 0x65, 0x65, 0x74, 0x10, 0xe0, 0x06, 0x12, 0x11, 0x0a, 0x0c, 0x4d, 0x79, 0x65, 0x78, 0x70, 0x65, + 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x10, 0xe1, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x43, 0x6f, 0x70, + 0x79, 0x73, 0x63, 0x61, 0x70, 0x65, 0x10, 0xe2, 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x42, 0x65, 0x73, + 0x6e, 0x61, 0x70, 0x70, 0x79, 0x10, 0xe3, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x53, 0x61, 0x6c, 0x65, + 0x73, 0x6d, 0x61, 0x74, 0x65, 0x10, 0xe4, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x48, 0x65, 0x61, 0x74, + 0x6d, 0x61, 0x70, 0x61, 0x70, 0x69, 0x10, 0xe5, 0x06, 0x12, 0x11, 0x0a, 0x0c, 0x57, 0x65, 0x62, + 0x73, 0x69, 0x74, 0x65, 0x70, 0x75, 0x6c, 0x73, 0x65, 0x10, 0xe6, 0x06, 0x12, 0x0e, 0x0a, 0x09, + 0x55, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x69, 0x66, 0x79, 0x10, 0xe7, 0x06, 0x12, 0x0c, 0x0a, 0x07, + 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x10, 0xe8, 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x50, 0x44, + 0x46, 0x6d, 0x79, 0x55, 0x52, 0x4c, 0x10, 0xe9, 0x06, 0x12, 0x10, 0x0a, 0x0b, 0x41, 0x70, 0x69, + 0x32, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x10, 0xea, 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x4f, + 0x70, 0x73, 0x67, 0x65, 0x6e, 0x69, 0x65, 0x10, 0xeb, 0x06, 0x12, 0x0b, 0x0a, 0x06, 0x47, 0x65, + 0x6d, 0x69, 0x6e, 0x69, 0x10, 0xec, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x48, 0x6f, 0x6e, 0x65, 0x79, + 0x63, 0x6f, 0x6d, 0x62, 0x10, 0xed, 0x06, 0x12, 0x14, 0x0a, 0x0f, 0x4b, 0x61, 0x6c, 0x74, 0x75, + 0x72, 0x61, 0x41, 0x70, 0x70, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0xee, 0x06, 0x12, 0x13, 0x0a, + 0x0e, 0x4b, 0x61, 0x6c, 0x74, 0x75, 0x72, 0x61, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x10, + 0xef, 0x06, 0x12, 0x0a, 0x0a, 0x05, 0x42, 0x69, 0x74, 0x47, 0x6f, 0x10, 0xf0, 0x06, 0x12, 0x0d, + 0x0a, 0x08, 0x4f, 0x70, 0x74, 0x69, 0x64, 0x61, 0x73, 0x68, 0x10, 0xf1, 0x06, 0x12, 0x0a, 0x0a, + 0x05, 0x49, 0x6d, 0x67, 0x69, 0x78, 0x10, 0xf2, 0x06, 0x12, 0x10, 0x0a, 0x0b, 0x49, 0x6d, 0x61, + 0x67, 0x65, 0x54, 0x6f, 0x54, 0x65, 0x78, 0x74, 0x10, 0xf3, 0x06, 0x12, 0x10, 0x0a, 0x0b, 0x50, + 0x61, 0x67, 0x65, 0x32, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x10, 0xf4, 0x06, 0x12, 0x0e, 0x0a, + 0x09, 0x51, 0x75, 0x69, 0x63, 0x6b, 0x62, 0x61, 0x73, 0x65, 0x10, 0xf5, 0x06, 0x12, 0x0d, 0x0a, + 0x08, 0x52, 0x65, 0x64, 0x62, 0x6f, 0x6f, 0x74, 0x68, 0x10, 0xf6, 0x06, 0x12, 0x0b, 0x0a, 0x06, + 0x4e, 0x75, 0x62, 0x65, 0x6c, 0x61, 0x10, 0xf7, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x49, 0x6e, 0x66, + 0x6f, 0x62, 0x69, 0x70, 0x10, 0xf8, 0x06, 0x12, 0x0a, 0x0a, 0x05, 0x55, 0x70, 0x72, 0x6f, 0x63, + 0x10, 0xf9, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x62, 0x65, + 0x65, 0x10, 0xfa, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x41, 0x66, 0x74, 0x65, 0x72, 0x73, 0x68, 0x69, + 0x70, 0x10, 0xfb, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x45, 0x64, 0x75, 0x73, 0x69, 0x67, 0x6e, 0x10, + 0xfc, 0x06, 0x12, 0x0b, 0x0a, 0x06, 0x54, 0x65, 0x61, 0x6d, 0x75, 0x70, 0x10, 0xfd, 0x06, 0x12, + 0x0c, 0x0a, 0x07, 0x57, 0x6f, 0x72, 0x6b, 0x64, 0x61, 0x79, 0x10, 0xfe, 0x06, 0x12, 0x0c, 0x0a, + 0x07, 0x4d, 0x6f, 0x6e, 0x67, 0x6f, 0x44, 0x42, 0x10, 0xff, 0x06, 0x12, 0x08, 0x0a, 0x03, 0x4e, + 0x47, 0x43, 0x10, 0x80, 0x07, 0x12, 0x13, 0x0a, 0x0e, 0x44, 0x69, 0x67, 0x69, 0x74, 0x61, 0x6c, + 0x4f, 0x63, 0x65, 0x61, 0x6e, 0x56, 0x32, 0x10, 0x81, 0x07, 0x12, 0x0e, 0x0a, 0x09, 0x53, 0x51, + 0x4c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x10, 0x82, 0x07, 0x12, 0x08, 0x0a, 0x03, 0x46, 0x54, + 0x50, 0x10, 0x83, 0x07, 0x12, 0x0a, 0x0a, 0x05, 0x52, 0x65, 0x64, 0x69, 0x73, 0x10, 0x84, 0x07, + 0x12, 0x09, 0x0a, 0x04, 0x4c, 0x44, 0x41, 0x50, 0x10, 0x85, 0x07, 0x12, 0x0c, 0x0a, 0x07, 0x53, + 0x68, 0x6f, 0x70, 0x69, 0x66, 0x79, 0x10, 0x86, 0x07, 0x12, 0x0d, 0x0a, 0x08, 0x52, 0x61, 0x62, + 0x62, 0x69, 0x74, 0x4d, 0x51, 0x10, 0x87, 0x07, 0x12, 0x10, 0x0a, 0x0b, 0x43, 0x75, 0x73, 0x74, + 0x6f, 0x6d, 0x52, 0x65, 0x67, 0x65, 0x78, 0x10, 0x88, 0x07, 0x12, 0x0e, 0x0a, 0x09, 0x45, 0x74, + 0x68, 0x65, 0x72, 0x73, 0x63, 0x61, 0x6e, 0x10, 0x89, 0x07, 0x12, 0x0b, 0x0a, 0x06, 0x49, 0x6e, + 0x66, 0x75, 0x72, 0x61, 0x10, 0x8a, 0x07, 0x12, 0x0c, 0x0a, 0x07, 0x41, 0x6c, 0x63, 0x68, 0x65, + 0x6d, 0x79, 0x10, 0x8b, 0x07, 0x12, 0x10, 0x0a, 0x0b, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x61, + 0x74, 0x69, 0x76, 0x65, 0x10, 0x8c, 0x07, 0x12, 0x0c, 0x0a, 0x07, 0x4d, 0x6f, 0x72, 0x61, 0x6c, + 0x69, 0x73, 0x10, 0x8d, 0x07, 0x12, 0x0c, 0x0a, 0x07, 0x42, 0x73, 0x63, 0x53, 0x63, 0x61, 0x6e, + 0x10, 0x8e, 0x07, 0x12, 0x12, 0x0a, 0x0d, 0x43, 0x6f, 0x69, 0x6e, 0x4d, 0x61, 0x72, 0x6b, 0x65, + 0x74, 0x43, 0x61, 0x70, 0x10, 0x8f, 0x07, 0x12, 0x0a, 0x0a, 0x05, 0x50, 0x65, 0x72, 0x63, 0x79, + 0x10, 0x90, 0x07, 0x12, 0x11, 0x0a, 0x0c, 0x54, 0x69, 0x6e, 0x65, 0x73, 0x57, 0x65, 0x62, 0x68, + 0x6f, 0x6f, 0x6b, 0x10, 0x91, 0x07, 0x12, 0x0b, 0x0a, 0x06, 0x50, 0x75, 0x6c, 0x75, 0x6d, 0x69, + 0x10, 0x92, 0x07, 0x12, 0x12, 0x0a, 0x0d, 0x53, 0x75, 0x70, 0x61, 0x62, 0x61, 0x73, 0x65, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0x93, 0x07, 0x12, 0x10, 0x0a, 0x0b, 0x4e, 0x75, 0x47, 0x65, 0x74, + 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x10, 0x94, 0x07, 0x12, 0x0a, 0x0a, 0x05, 0x41, 0x69, 0x76, + 0x65, 0x6e, 0x10, 0x95, 0x07, 0x12, 0x0c, 0x0a, 0x07, 0x50, 0x72, 0x65, 0x66, 0x65, 0x63, 0x74, + 0x10, 0x96, 0x07, 0x12, 0x0d, 0x0a, 0x08, 0x44, 0x6f, 0x63, 0x75, 0x73, 0x69, 0x67, 0x6e, 0x10, + 0x97, 0x07, 0x12, 0x0e, 0x0a, 0x09, 0x43, 0x6f, 0x75, 0x63, 0x68, 0x62, 0x61, 0x73, 0x65, 0x10, + 0x98, 0x07, 0x12, 0x0e, 0x0a, 0x09, 0x44, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x68, 0x75, 0x62, 0x10, + 0x99, 0x07, 0x12, 0x19, 0x0a, 0x14, 0x54, 0x72, 0x75, 0x66, 0x66, 0x6c, 0x65, 0x68, 0x6f, 0x67, + 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x10, 0x9a, 0x07, 0x12, 0x10, 0x0a, + 0x0b, 0x45, 0x6e, 0x76, 0x6f, 0x79, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x10, 0x9b, 0x07, 0x12, + 0x11, 0x0a, 0x0c, 0x47, 0x69, 0x74, 0x48, 0x75, 0x62, 0x4f, 0x61, 0x75, 0x74, 0x68, 0x32, 0x10, + 0x9c, 0x07, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x61, 0x6c, 0x65, 0x73, 0x66, 0x6f, 0x72, 0x63, 0x65, + 0x10, 0x9d, 0x07, 0x12, 0x10, 0x0a, 0x0b, 0x48, 0x75, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x46, 0x61, + 0x63, 0x65, 0x10, 0x9e, 0x07, 0x12, 0x0e, 0x0a, 0x09, 0x53, 0x6e, 0x6f, 0x77, 0x66, 0x6c, 0x61, + 0x6b, 0x65, 0x10, 0x9f, 0x07, 0x12, 0x10, 0x0a, 0x0b, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x67, + 0x72, 0x61, 0x70, 0x68, 0x10, 0xa0, 0x07, 0x12, 0x0e, 0x0a, 0x09, 0x54, 0x61, 0x69, 0x6c, 0x73, + 0x63, 0x61, 0x6c, 0x65, 0x10, 0xa1, 0x07, 0x12, 0x10, 0x0a, 0x0b, 0x57, 0x65, 0x62, 0x33, 0x53, + 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x10, 0xa2, 0x07, 0x12, 0x11, 0x0a, 0x0c, 0x41, 0x7a, 0x75, + 0x72, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x10, 0xa3, 0x07, 0x12, 0x12, 0x0a, 0x0d, + 0x50, 0x6c, 0x61, 0x6e, 0x65, 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x44, 0x62, 0x10, 0xa4, 0x07, + 0x12, 0x0e, 0x0a, 0x09, 0x41, 0x6e, 0x74, 0x68, 0x72, 0x6f, 0x70, 0x69, 0x63, 0x10, 0xa5, 0x07, + 0x12, 0x09, 0x0a, 0x04, 0x52, 0x61, 0x6d, 0x70, 0x10, 0xa6, 0x07, 0x12, 0x0c, 0x0a, 0x07, 0x4b, + 0x6c, 0x61, 0x76, 0x69, 0x79, 0x6f, 0x10, 0xa7, 0x07, 0x12, 0x14, 0x0a, 0x0f, 0x53, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x67, 0x72, 0x61, 0x70, 0x68, 0x43, 0x6f, 0x64, 0x79, 0x10, 0xa8, 0x07, 0x12, + 0x0e, 0x0a, 0x09, 0x56, 0x6f, 0x69, 0x63, 0x65, 0x66, 0x6c, 0x6f, 0x77, 0x10, 0xa9, 0x07, 0x12, + 0x0c, 0x0a, 0x07, 0x50, 0x72, 0x69, 0x76, 0x61, 0x63, 0x79, 0x10, 0xaa, 0x07, 0x12, 0x0b, 0x0a, + 0x06, 0x49, 0x50, 0x49, 0x6e, 0x66, 0x6f, 0x10, 0xab, 0x07, 0x12, 0x10, 0x0a, 0x0b, 0x49, 0x70, + 0x32, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0xac, 0x07, 0x12, 0x0e, 0x0a, 0x09, + 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6d, 0x6f, 0x6a, 0x6f, 0x10, 0xad, 0x07, 0x12, 0x0e, 0x0a, 0x09, + 0x50, 0x6f, 0x72, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x10, 0xae, 0x07, 0x12, 0x13, 0x0a, 0x0e, + 0x50, 0x6f, 0x72, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0xaf, + 0x07, 0x12, 0x0b, 0x0a, 0x06, 0x4c, 0x6f, 0x67, 0x67, 0x6c, 0x79, 0x10, 0xb0, 0x07, 0x12, 0x0c, + 0x0a, 0x07, 0x4f, 0x70, 0x65, 0x6e, 0x56, 0x70, 0x6e, 0x10, 0xb1, 0x07, 0x12, 0x1e, 0x0a, 0x19, + 0x56, 0x61, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x50, 0x65, 0x72, 0x73, + 0x6f, 0x6e, 0x61, 0x6c, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0xb2, 0x07, 0x12, 0x10, 0x0a, 0x0b, + 0x42, 0x65, 0x74, 0x74, 0x65, 0x72, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x10, 0xb3, 0x07, 0x12, 0x0d, + 0x0a, 0x08, 0x5a, 0x65, 0x72, 0x6f, 0x54, 0x69, 0x65, 0x72, 0x10, 0xb4, 0x07, 0x12, 0x0e, 0x0a, + 0x09, 0x41, 0x70, 0x70, 0x4f, 0x70, 0x74, 0x69, 0x63, 0x73, 0x10, 0xb5, 0x07, 0x12, 0x0d, 0x0a, + 0x08, 0x4d, 0x65, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x10, 0xb6, 0x07, 0x12, 0x11, 0x0a, 0x0c, + 0x43, 0x6f, 0x69, 0x6e, 0x62, 0x61, 0x73, 0x65, 0x57, 0x61, 0x61, 0x53, 0x10, 0xb7, 0x07, 0x12, + 0x11, 0x0a, 0x0c, 0x4c, 0x65, 0x6d, 0x6f, 0x6e, 0x53, 0x71, 0x75, 0x65, 0x65, 0x7a, 0x79, 0x10, + 0xb8, 0x07, 0x12, 0x0d, 0x0a, 0x08, 0x42, 0x75, 0x64, 0x69, 0x62, 0x61, 0x73, 0x65, 0x10, 0xb9, + 0x07, 0x12, 0x0f, 0x0a, 0x0a, 0x44, 0x65, 0x6e, 0x6f, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x10, + 0xba, 0x07, 0x12, 0x0b, 0x0a, 0x06, 0x53, 0x74, 0x72, 0x69, 0x70, 0x6f, 0x10, 0xbb, 0x07, 0x12, + 0x0c, 0x0a, 0x07, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x49, 0x4f, 0x10, 0xbc, 0x07, 0x12, 0x0f, 0x0a, + 0x0a, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x42, 0x61, 0x74, 0x63, 0x68, 0x10, 0xbd, 0x07, 0x12, 0x1b, + 0x0a, 0x16, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, + 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x10, 0xbe, 0x07, 0x12, 0x12, 0x0a, 0x0d, 0x41, + 0x57, 0x53, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x10, 0xbf, 0x07, 0x12, + 0x09, 0x0a, 0x04, 0x43, 0x6f, 0x64, 0x61, 0x10, 0xc0, 0x07, 0x12, 0x0b, 0x0a, 0x06, 0x4c, 0x6f, + 0x67, 0x7a, 0x49, 0x4f, 0x10, 0xc1, 0x07, 0x12, 0x0f, 0x0a, 0x0a, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x62, 0x72, 0x69, 0x74, 0x65, 0x10, 0xc2, 0x07, 0x12, 0x1a, 0x0a, 0x15, 0x47, 0x72, 0x61, 0x66, + 0x61, 0x6e, 0x61, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x10, 0xc3, 0x07, 0x12, 0x13, 0x0a, 0x0e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x46, + 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x10, 0xc4, 0x07, 0x12, 0x0d, 0x0a, 0x08, 0x4f, 0x76, 0x65, + 0x72, 0x6c, 0x6f, 0x6f, 0x70, 0x10, 0xc5, 0x07, 0x12, 0x0a, 0x0a, 0x05, 0x4e, 0x67, 0x72, 0x6f, + 0x6b, 0x10, 0xc6, 0x07, 0x12, 0x0e, 0x0a, 0x09, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, + 0x65, 0x10, 0xc7, 0x07, 0x42, 0x3d, 0x5a, 0x3b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x74, 0x72, 0x75, 0x66, 0x66, 0x6c, 0x65, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, + 0x74, 0x79, 0x2f, 0x74, 0x72, 0x75, 0x66, 0x66, 0x6c, 0x65, 0x68, 0x6f, 0x67, 0x2f, 0x76, 0x33, + 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x62, 0x2f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x73, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/pkg/sources/chan_reporter.go b/pkg/sources/chan_reporter.go new file mode 100644 index 000000000000..11c1619806e4 --- /dev/null +++ b/pkg/sources/chan_reporter.go @@ -0,0 +1,22 @@ +package sources + +import ( + "github.com/trufflesecurity/trufflehog/v3/pkg/common" + "github.com/trufflesecurity/trufflehog/v3/pkg/context" +) + +var _ ChunkReporter = (*ChanReporter)(nil) + +// ChanReporter is a ChunkReporter that writes to a channel. +type ChanReporter struct { + Ch chan<- *Chunk +} + +func (c ChanReporter) ChunkOk(ctx context.Context, chunk Chunk) error { + return common.CancellableWrite(ctx, c.Ch, &chunk) +} + +func (ChanReporter) ChunkErr(ctx context.Context, err error) error { + ctx.Logger().Error(err, "error chunking") + return nil +} diff --git a/pkg/sources/chunker_test.go b/pkg/sources/chunker_test.go index 0a6711707156..aa350f7f0f0b 100644 --- a/pkg/sources/chunker_test.go +++ b/pkg/sources/chunker_test.go @@ -7,8 +7,8 @@ import ( "testing" "testing/iotest" - diskbufferreader "github.com/trufflesecurity/disk-buffer-reader" "github.com/stretchr/testify/assert" + diskbufferreader "github.com/trufflesecurity/disk-buffer-reader" "github.com/trufflesecurity/trufflehog/v3/pkg/context" ) diff --git a/pkg/sources/filesystem/filesystem.go b/pkg/sources/filesystem/filesystem.go index 791f5eb93f60..e969a12b1f4c 100644 --- a/pkg/sources/filesystem/filesystem.go +++ b/pkg/sources/filesystem/filesystem.go @@ -7,9 +7,9 @@ import ( "os" "path/filepath" - diskbufferreader "github.com/trufflesecurity/disk-buffer-reader" "github.com/go-errors/errors" "github.com/go-logr/logr" + diskbufferreader "github.com/trufflesecurity/disk-buffer-reader" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/anypb" @@ -172,7 +172,7 @@ func (s *Source) scanFile(ctx context.Context, path string, chunksChan chan *sou }, Verify: s.verify, } - if handlers.HandleFile(ctx, reReader, chunkSkel, chunksChan) { + if handlers.HandleFile(ctx, reReader, chunkSkel, sources.ChanReporter{Ch: chunksChan}) { return nil } diff --git a/pkg/sources/gcs/gcs.go b/pkg/sources/gcs/gcs.go index 202752085ddd..72ac33708fee 100644 --- a/pkg/sources/gcs/gcs.go +++ b/pkg/sources/gcs/gcs.go @@ -10,9 +10,9 @@ import ( "sync" "cloud.google.com/go/storage" - diskbufferreader "github.com/trufflesecurity/disk-buffer-reader" "github.com/go-errors/errors" "github.com/go-logr/logr" + diskbufferreader "github.com/trufflesecurity/disk-buffer-reader" "golang.org/x/oauth2" "golang.org/x/oauth2/endpoints" "google.golang.org/protobuf/proto" @@ -375,7 +375,7 @@ func (s *Source) readObjectData(ctx context.Context, o object, chunk *sources.Ch } defer reader.Close() - if handlers.HandleFile(ctx, reader, chunk, s.chunksCh) { + if handlers.HandleFile(ctx, reader, chunk, sources.ChanReporter{Ch: s.chunksCh}) { ctx.Logger().V(3).Info("File was handled", "name", s.name, "bucket", o.bucket, "object", o.name) return nil, nil } diff --git a/pkg/sources/git/git.go b/pkg/sources/git/git.go index 807c7b459744..8b04fe392abd 100644 --- a/pkg/sources/git/git.go +++ b/pkg/sources/git/git.go @@ -25,7 +25,6 @@ import ( "google.golang.org/protobuf/types/known/anypb" "github.com/trufflesecurity/trufflehog/v3/pkg/cleantemp" - "github.com/trufflesecurity/trufflehog/v3/pkg/common" "github.com/trufflesecurity/trufflehog/v3/pkg/context" "github.com/trufflesecurity/trufflehog/v3/pkg/gitparse" "github.com/trufflesecurity/trufflehog/v3/pkg/handlers" @@ -81,8 +80,11 @@ func NewGit(sourceType sourcespb.SourceType, jobID sources.JobID, sourceID sourc } // Ensure the Source satisfies the interfaces at compile time. -var _ sources.Source = (*Source)(nil) -var _ sources.SourceUnitUnmarshaller = (*Source)(nil) +var _ interface { + sources.Source + sources.SourceUnitEnumChunker + sources.SourceUnitUnmarshaller +} = (*Source)(nil) // Type returns the type of source. // It is used for matching source types in configuration and job input. @@ -157,10 +159,11 @@ func (s *Source) Init(aCtx context.Context, name string, jobId sources.JobID, so // Chunks emits chunks of bytes over a channel. func (s *Source) Chunks(ctx context.Context, chunksChan chan *sources.Chunk, _ ...sources.ChunkingTarget) error { - if err := s.scanRepos(ctx, chunksChan); err != nil { + reporter := sources.ChanReporter{Ch: chunksChan} + if err := s.scanRepos(ctx, reporter); err != nil { return err } - if err := s.scanDirs(ctx, chunksChan); err != nil { + if err := s.scanDirs(ctx, reporter); err != nil { return err } @@ -174,81 +177,62 @@ func (s *Source) Chunks(ctx context.Context, chunksChan chan *sources.Chunk, _ . } // scanRepos scans the configured repositories in s.conn.Repositories. -func (s *Source) scanRepos(ctx context.Context, chunksChan chan *sources.Chunk) error { +func (s *Source) scanRepos(ctx context.Context, reporter sources.ChunkReporter) error { if len(s.conn.Repositories) == 0 { return nil } totalRepos := len(s.conn.Repositories) + len(s.conn.Directories) - // TODO: refactor to remove duplicate code + for i, repoURI := range s.conn.Repositories { + s.SetProgressComplete(i, totalRepos, fmt.Sprintf("Repo: %s", repoURI), "") + if len(repoURI) == 0 { + continue + } + if err := s.scanRepo(ctx, repoURI, reporter); err != nil { + ctx.Logger().Info("error scanning repository", "repo", repoURI, "error", err) + continue + } + } + return nil +} + +// scanRepo scans a single provided repository. +func (s *Source) scanRepo(ctx context.Context, repoURI string, reporter sources.ChunkReporter) error { + var cloneFunc func() (string, *git.Repository, error) switch cred := s.conn.GetCredential().(type) { case *sourcespb.Git_BasicAuth: - user := cred.BasicAuth.Username - token := cred.BasicAuth.Password - - for i, repoURI := range s.conn.Repositories { - s.SetProgressComplete(i, totalRepos, fmt.Sprintf("Repo: %s", repoURI), "") - if len(repoURI) == 0 { - continue - } - err := func(repoURI string) error { - path, repo, err := CloneRepoUsingToken(ctx, token, repoURI, user) - defer os.RemoveAll(path) - if err != nil { - return err - } - return s.git.ScanRepo(ctx, repo, path, s.scanOptions, chunksChan) - }(repoURI) - if err != nil { - ctx.Logger().Info("error scanning repository", "repo", repoURI, "error", err) - continue - } + cloneFunc = func() (string, *git.Repository, error) { + user := cred.BasicAuth.Username + token := cred.BasicAuth.Password + return CloneRepoUsingToken(ctx, token, repoURI, user) } case *sourcespb.Git_Unauthenticated: - for i, repoURI := range s.conn.Repositories { - s.SetProgressComplete(i, totalRepos, fmt.Sprintf("Repo: %s", repoURI), "") - if len(repoURI) == 0 { - continue - } - err := func(repoURI string) error { - path, repo, err := CloneRepoUsingUnauthenticated(ctx, repoURI) - defer os.RemoveAll(path) - if err != nil { - return err - } - return s.git.ScanRepo(ctx, repo, path, s.scanOptions, chunksChan) - }(repoURI) - if err != nil { - ctx.Logger().Info("error scanning repository", "repo", repoURI, "error", err) - continue - } + cloneFunc = func() (string, *git.Repository, error) { + return CloneRepoUsingUnauthenticated(ctx, repoURI) } case *sourcespb.Git_SshAuth: - for i, repoURI := range s.conn.Repositories { - s.SetProgressComplete(i, totalRepos, fmt.Sprintf("Repo: %s", repoURI), "") - if len(repoURI) == 0 { - continue - } - err := func(repoURI string) error { - path, repo, err := CloneRepoUsingSSH(ctx, repoURI) - defer os.RemoveAll(path) - if err != nil { - return err - } - return s.git.ScanRepo(ctx, repo, path, s.scanOptions, chunksChan) - }(repoURI) - if err != nil { - ctx.Logger().Info("error scanning repository", "repo", repoURI, "error", err) - continue - } + cloneFunc = func() (string, *git.Repository, error) { + return CloneRepoUsingSSH(ctx, repoURI) } default: return errors.New("invalid connection type for git source") } + + err := func() error { + path, repo, err := cloneFunc() + defer os.RemoveAll(path) + if err != nil { + return err + } + return s.git.ScanRepo(ctx, repo, path, s.scanOptions, reporter) + }() + if err != nil { + return reporter.ChunkErr(ctx, err) + } return nil } // scanDirs scans the configured directories in s.conn.Directories. -func (s *Source) scanDirs(ctx context.Context, chunksChan chan *sources.Chunk) error { +func (s *Source) scanDirs(ctx context.Context, reporter sources.ChunkReporter) error { totalRepos := len(s.conn.Repositories) + len(s.conn.Directories) for i, gitDir := range s.conn.Directories { s.SetProgressComplete(len(s.conn.Repositories)+i, totalRepos, fmt.Sprintf("Repo: %s", gitDir), "") @@ -256,29 +240,35 @@ func (s *Source) scanDirs(ctx context.Context, chunksChan chan *sources.Chunk) e if len(gitDir) == 0 { continue } - if !s.scanOptions.Bare && strings.HasSuffix(gitDir, "git") { - // TODO: Figure out why we skip directories ending in "git". - continue - } - // try paths instead of url - repo, err := RepoFromPath(gitDir, s.scanOptions.Bare) - if err != nil { + if err := s.scanDir(ctx, gitDir, reporter); err != nil { ctx.Logger().Info("error scanning repository", "repo", gitDir, "error", err) continue } + } + return nil +} - err = func(repoPath string) error { - if !s.preserveTempDirs && strings.HasPrefix(repoPath, filepath.Join(os.TempDir(), "trufflehog")) { - defer os.RemoveAll(repoPath) - } +// scanDir scans a single provided directory. +func (s *Source) scanDir(ctx context.Context, gitDir string, reporter sources.ChunkReporter) error { + if !s.scanOptions.Bare && strings.HasSuffix(gitDir, "git") { + // TODO: Figure out why we skip directories ending in "git". + return nil + } + // try paths instead of url + repo, err := RepoFromPath(gitDir, s.scanOptions.Bare) + if err != nil { + return reporter.ChunkErr(ctx, err) + } - return s.git.ScanRepo(ctx, repo, repoPath, s.scanOptions, chunksChan) - }(gitDir) - if err != nil { - ctx.Logger().Info("error scanning repository", "repo", gitDir, "error", err) - continue + err = func() error { + if !s.preserveTempDirs && strings.HasPrefix(gitDir, filepath.Join(os.TempDir(), "trufflehog")) { + defer os.RemoveAll(gitDir) } + return s.git.ScanRepo(ctx, repo, gitDir, s.scanOptions, reporter) + }() + if err != nil { + return reporter.ChunkErr(ctx, err) } return nil } @@ -445,7 +435,7 @@ func (s *Git) CommitsScanned() uint64 { return atomic.LoadUint64(&s.metrics.commitsScanned) } -func (s *Git) ScanCommits(ctx context.Context, repo *git.Repository, path string, scanOptions *ScanOptions, chunksChan chan *sources.Chunk) error { +func (s *Git) ScanCommits(ctx context.Context, repo *git.Repository, path string, scanOptions *ScanOptions, reporter sources.ChunkReporter) error { if err := GitCmdCheck(); err != nil { return err } @@ -505,18 +495,18 @@ func (s *Git) ScanCommits(ctx context.Context, repo *git.Repository, path string SourceMetadata: metadata, Verify: s.verify, } - if err := handleBinary(ctx, repo, chunksChan, chunkSkel, commitHash, fileName); err != nil { + if err := handleBinary(ctx, repo, reporter, chunkSkel, commitHash, fileName); err != nil { logger.V(1).Info("error handling binary file", "error", err, "filename", fileName, "commit", commitHash, "file", diff.PathB) } continue } if diff.Content.Len() > sources.ChunkSize+sources.PeekSize { - s.gitChunk(ctx, diff, fileName, email, hash, when, urlMetadata, chunksChan) + s.gitChunk(ctx, diff, fileName, email, hash, when, urlMetadata, reporter) continue } metadata := s.sourceMetadataFunc(fileName, email, hash, when, urlMetadata, int64(diff.LineStart)) - chunksChan <- &sources.Chunk{ + chunk := sources.Chunk{ SourceName: s.sourceName, SourceID: s.sourceID, JobID: s.jobID, @@ -525,12 +515,15 @@ func (s *Git) ScanCommits(ctx context.Context, repo *git.Repository, path string Data: diff.Content.Bytes(), Verify: s.verify, } + if err := reporter.ChunkOk(ctx, chunk); err != nil { + return err + } } } return nil } -func (s *Git) gitChunk(ctx context.Context, diff gitparse.Diff, fileName, email, hash, when, urlMetadata string, chunksChan chan *sources.Chunk) { +func (s *Git) gitChunk(ctx context.Context, diff gitparse.Diff, fileName, email, hash, when, urlMetadata string, reporter sources.ChunkReporter) { originalChunk := bufio.NewScanner(&diff.Content) newChunkBuffer := bytes.Buffer{} lastOffset := 0 @@ -543,7 +536,7 @@ func (s *Git) gitChunk(ctx context.Context, diff gitparse.Diff, fileName, email, if newChunkBuffer.Len() > 0 { // Send the existing fragment. metadata := s.sourceMetadataFunc(fileName, email, hash, when, urlMetadata, int64(diff.LineStart+lastOffset)) - chunksChan <- &sources.Chunk{ + chunk := sources.Chunk{ SourceName: s.sourceName, SourceID: s.sourceID, JobID: s.jobID, @@ -552,13 +545,18 @@ func (s *Git) gitChunk(ctx context.Context, diff gitparse.Diff, fileName, email, Data: append([]byte{}, newChunkBuffer.Bytes()...), Verify: s.verify, } + if err := reporter.ChunkOk(ctx, chunk); err != nil { + // TODO: Return error. + return + } + newChunkBuffer.Reset() lastOffset = offset } if len(line) > sources.ChunkSize { // Send the oversize line. metadata := s.sourceMetadataFunc(fileName, email, hash, when, urlMetadata, int64(diff.LineStart+offset)) - chunksChan <- &sources.Chunk{ + chunk := sources.Chunk{ SourceName: s.sourceName, SourceID: s.sourceID, JobID: s.jobID, @@ -567,6 +565,10 @@ func (s *Git) gitChunk(ctx context.Context, diff gitparse.Diff, fileName, email, Data: line, Verify: s.verify, } + if err := reporter.ChunkOk(ctx, chunk); err != nil { + // TODO: Return error. + return + } continue } } @@ -578,7 +580,7 @@ func (s *Git) gitChunk(ctx context.Context, diff gitparse.Diff, fileName, email, // Send anything still in the new chunk buffer if newChunkBuffer.Len() > 0 { metadata := s.sourceMetadataFunc(fileName, email, hash, when, urlMetadata, int64(diff.LineStart+lastOffset)) - chunksChan <- &sources.Chunk{ + chunk := sources.Chunk{ SourceName: s.sourceName, SourceID: s.sourceID, JobID: s.jobID, @@ -587,11 +589,15 @@ func (s *Git) gitChunk(ctx context.Context, diff gitparse.Diff, fileName, email, Data: append([]byte{}, newChunkBuffer.Bytes()...), Verify: s.verify, } + if err := reporter.ChunkOk(ctx, chunk); err != nil { + // TODO: Return error. + return + } } } // ScanStaged chunks staged changes. -func (s *Git) ScanStaged(ctx context.Context, repo *git.Repository, path string, scanOptions *ScanOptions, chunksChan chan *sources.Chunk) error { +func (s *Git) ScanStaged(ctx context.Context, repo *git.Repository, path string, scanOptions *ScanOptions, reporter sources.ChunkReporter) error { // Get the URL metadata for reporting (may be empty). urlMetadata := getSafeRemoteURL(repo, "origin") @@ -652,14 +658,14 @@ func (s *Git) ScanStaged(ctx context.Context, repo *git.Repository, path string, SourceMetadata: metadata, Verify: s.verify, } - if err := handleBinary(ctx, repo, chunksChan, chunkSkel, commitHash, fileName); err != nil { + if err := handleBinary(ctx, repo, reporter, chunkSkel, commitHash, fileName); err != nil { logger.V(1).Info("error handling binary file", "error", err, "filename", fileName) } continue } metadata := s.sourceMetadataFunc(fileName, email, "Staged", when, urlMetadata, int64(diff.LineStart)) - chunksChan <- &sources.Chunk{ + chunk := sources.Chunk{ SourceName: s.sourceName, SourceID: s.sourceID, JobID: s.jobID, @@ -668,12 +674,15 @@ func (s *Git) ScanStaged(ctx context.Context, repo *git.Repository, path string, Data: diff.Content.Bytes(), Verify: s.verify, } + if err := reporter.ChunkOk(ctx, chunk); err != nil { + return err + } } } return nil } -func (s *Git) ScanRepo(ctx context.Context, repo *git.Repository, repoPath string, scanOptions *ScanOptions, chunksChan chan *sources.Chunk) error { +func (s *Git) ScanRepo(ctx context.Context, repo *git.Repository, repoPath string, scanOptions *ScanOptions, reporter sources.ChunkReporter) error { if scanOptions == nil { scanOptions = NewScanOptions() } @@ -682,11 +691,11 @@ func (s *Git) ScanRepo(ctx context.Context, repo *git.Repository, repoPath strin } start := time.Now().Unix() - if err := s.ScanCommits(ctx, repo, repoPath, scanOptions, chunksChan); err != nil { + if err := s.ScanCommits(ctx, repo, repoPath, scanOptions, reporter); err != nil { return err } if !scanOptions.Bare { - if err := s.ScanStaged(ctx, repo, repoPath, scanOptions, chunksChan); err != nil { + if err := s.ScanStaged(ctx, repo, repoPath, scanOptions, reporter); err != nil { ctx.Logger().V(1).Info("error scanning unstaged changes", "error", err) } } @@ -934,7 +943,7 @@ func getSafeRemoteURL(repo *git.Repository, preferred string) string { return safeURL } -func handleBinary(ctx context.Context, repo *git.Repository, chunksChan chan *sources.Chunk, chunkSkel *sources.Chunk, commitHash plumbing.Hash, path string) error { +func handleBinary(ctx context.Context, repo *git.Repository, reporter sources.ChunkReporter, chunkSkel *sources.Chunk, commitHash plumbing.Hash, path string) error { ctx.Logger().V(5).Info("handling binary file", "path", path) commit, err := repo.CommitObject(commitHash) if err != nil { @@ -958,7 +967,7 @@ func handleBinary(ctx context.Context, repo *git.Repository, chunksChan chan *so } defer reader.Close() - if handlers.HandleFile(ctx, reader, chunkSkel, chunksChan) { + if handlers.HandleFile(ctx, reader, chunkSkel, reporter) { return nil } @@ -976,7 +985,7 @@ func handleBinary(ctx context.Context, repo *git.Repository, chunksChan chan *so if err := data.Error(); err != nil { return err } - if err := common.CancellableWrite(ctx, chunksChan, &chunk); err != nil { + if err := reporter.ChunkOk(ctx, chunk); err != nil { return err } } @@ -984,6 +993,44 @@ func handleBinary(ctx context.Context, repo *git.Repository, chunksChan chan *so return nil } +func (s *Source) Enumerate(ctx context.Context, reporter sources.UnitReporter) error { + for _, repo := range s.conn.GetDirectories() { + if repo == "" { + continue + } + unit := SourceUnit{ID: repo, Kind: UnitDir} + if err := reporter.UnitOk(ctx, unit); err != nil { + return err + } + } + for _, repo := range s.conn.GetRepositories() { + if repo == "" { + continue + } + unit := SourceUnit{ID: repo, Kind: UnitRepo} + if err := reporter.UnitOk(ctx, unit); err != nil { + return err + } + } + return nil +} + +func (s *Source) ChunkUnit(ctx context.Context, unit sources.SourceUnit, reporter sources.ChunkReporter) error { + gitUnit, ok := unit.(SourceUnit) + if !ok { + return fmt.Errorf("unsupported unit type: %T", unit) + } + + switch gitUnit.Kind { + case UnitRepo: + return s.scanRepo(ctx, gitUnit.ID, reporter) + case UnitDir: + return s.scanDir(ctx, gitUnit.ID, reporter) + default: + return fmt.Errorf("unexpected git unit kind: %q", gitUnit.Kind) + } +} + func (s *Source) UnmarshalSourceUnit(data []byte) (sources.SourceUnit, error) { return UnmarshalUnit(data) } diff --git a/pkg/sources/git/git_test.go b/pkg/sources/git/git_test.go index 15914b9ecda9..cc9d31756d3d 100644 --- a/pkg/sources/git/git_test.go +++ b/pkg/sources/git/git_test.go @@ -16,6 +16,7 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb" "github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb" "github.com/trufflesecurity/trufflehog/v3/pkg/sources" + "github.com/trufflesecurity/trufflehog/v3/pkg/sourcestest" ) func TestSource_Scan(t *testing.T) { @@ -236,7 +237,7 @@ func TestSource_Chunks_Integration(t *testing.T) { if err != nil { panic(err) } - err = s.git.ScanRepo(ctx, repo, repoPath, &tt.scanOptions, chunksCh) + err = s.git.ScanRepo(ctx, repo, repoPath, &tt.scanOptions, sources.ChanReporter{Ch: chunksCh}) if err != nil { panic(err) } @@ -504,3 +505,72 @@ func TestGitURLParse(t *testing.T) { assert.Equal(t, tt.scheme, u.Scheme) } } + +func TestEnumerate(t *testing.T) { + t.Parallel() + ctx := context.Background() + + // Setup the connection to test enumeration. + units := []string{ + "foo", "bar", "baz", + "/path/to/dir/", "/path/to/another/dir/", + } + conn, err := anypb.New(&sourcespb.Git{ + Repositories: units[0:3], + Directories: units[3:], + }) + assert.NoError(t, err) + + // Initialize the source. + s := Source{} + err = s.Init(ctx, "test enumerate", 0, 0, true, conn, 1) + assert.NoError(t, err) + + reporter := sourcestest.TestReporter{} + err = s.Enumerate(ctx, &reporter) + assert.NoError(t, err) + + assert.Equal(t, len(units), len(reporter.Units)) + assert.Equal(t, 0, len(reporter.UnitErrs)) + for _, unit := range reporter.Units { + assert.Contains(t, units, unit.SourceUnitID()) + } + for _, unit := range units[:3] { + assert.Contains(t, reporter.Units, SourceUnit{ID: unit, Kind: UnitRepo}) + } + for _, unit := range units[3:] { + assert.Contains(t, reporter.Units, SourceUnit{ID: unit, Kind: UnitDir}) + } +} + +func TestChunkUnit(t *testing.T) { + t.Parallel() + ctx := context.Background() + // Initialize the source. + s := Source{} + conn, err := anypb.New(&sourcespb.Git{ + Credential: &sourcespb.Git_Unauthenticated{}, + }) + assert.NoError(t, err) + err = s.Init(ctx, "test chunk", 0, 0, true, conn, 1) + assert.NoError(t, err) + + reporter := sourcestest.TestReporter{} + + // Happy path single repository. + err = s.ChunkUnit(ctx, SourceUnit{ + ID: "https://github.com/dustin-decker/secretsandstuff.git", + Kind: UnitRepo, + }, &reporter) + assert.NoError(t, err) + + // Error path. + err = s.ChunkUnit(ctx, SourceUnit{ + ID: "/file/not/found", + Kind: UnitDir, + }, &reporter) + assert.NoError(t, err) + + assert.Equal(t, 11, len(reporter.Chunks)) + assert.Equal(t, 1, len(reporter.ChunkErrs)) +} diff --git a/pkg/sources/github/github.go b/pkg/sources/github/github.go index 0cfd3af305eb..6e60ff239cd1 100644 --- a/pkg/sources/github/github.go +++ b/pkg/sources/github/github.go @@ -797,7 +797,7 @@ func (s *Source) scan(ctx context.Context, installationClient *github.Client, ch logger.V(2).Info(fmt.Sprintf("scanned %d/%d repos", scanned, len(s.repos)), "repo_size", repoSize, "duration_seconds", time.Since(start).Seconds()) }(now) - if err = s.git.ScanRepo(ctx, repo, path, s.scanOptions, chunksChan); err != nil { + if err = s.git.ScanRepo(ctx, repo, path, s.scanOptions, sources.ChanReporter{Ch: chunksChan}); err != nil { scanErrs.Add(fmt.Errorf("error scanning repo %s: %w", repoURL, err)) return nil } diff --git a/pkg/sources/gitlab/gitlab.go b/pkg/sources/gitlab/gitlab.go index f1efa8917931..6923d63f0cd5 100644 --- a/pkg/sources/gitlab/gitlab.go +++ b/pkg/sources/gitlab/gitlab.go @@ -453,7 +453,7 @@ func (s *Source) scanRepos(ctx context.Context, chunksChan chan *sources.Chunk) } logger.V(2).Info(fmt.Sprintf("Starting to scan repo %d/%d", i+1, len(s.repos))) - if err = s.git.ScanRepo(ctx, repo, path, s.scanOptions, chunksChan); err != nil { + if err = s.git.ScanRepo(ctx, repo, path, s.scanOptions, sources.ChanReporter{Ch: chunksChan}); err != nil { scanErrs.Add(err) return nil } diff --git a/pkg/sources/s3/s3.go b/pkg/sources/s3/s3.go index e21830b7a5a1..977cc33b2adc 100644 --- a/pkg/sources/s3/s3.go +++ b/pkg/sources/s3/s3.go @@ -14,9 +14,9 @@ import ( "github.com/aws/aws-sdk-go/service/s3" "github.com/aws/aws-sdk-go/service/s3/s3manager" "github.com/aws/aws-sdk-go/service/sts" - diskbufferreader "github.com/trufflesecurity/disk-buffer-reader" "github.com/go-errors/errors" "github.com/go-logr/logr" + diskbufferreader "github.com/trufflesecurity/disk-buffer-reader" "golang.org/x/sync/errgroup" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/anypb" @@ -376,7 +376,7 @@ func (s *Source) pageChunker(ctx context.Context, client *s3.S3, chunksChan chan }, Verify: s.verify, } - if handlers.HandleFile(ctx, reader, chunkSkel, chunksChan) { + if handlers.HandleFile(ctx, reader, chunkSkel, sources.ChanReporter{Ch: chunksChan}) { atomic.AddUint64(objectCount, 1) s.log.V(5).Info("S3 object scanned.", "object_count", objectCount, "page_number", pageNumber) return nil diff --git a/proto/detectors.proto b/proto/detectors.proto index dad3f8554e40..83b34f3f95b7 100644 --- a/proto/detectors.proto +++ b/proto/detectors.proto @@ -253,7 +253,7 @@ enum DetectorType { Nytimes = 241; Polygon = 242; Powrbot = 243; - ProspectIO = 244; + ProspectIO = 244 [deprecated = true]; Skrappio = 245; Monday = 246; Smartsheets = 247; @@ -443,7 +443,7 @@ enum DetectorType { BitcoinAverage = 432; CommerceJS = 433; DetectLanguage = 434; - FakeJSON = 435; + FakeJSON = 435 [deprecated = true]; Graphhopper = 436; Lexigram = 437; LinkPreview = 438; @@ -582,7 +582,7 @@ enum DetectorType { Hive = 571; Hiveage = 572; Kickbox = 573; - Passbase = 574; + Passbase = 574 [deprecated = true]; PostageApp = 575; PureStake = 576; Qubole = 577; @@ -634,7 +634,7 @@ enum DetectorType { NeutrinoApi = 623; Storecove = 624; Shipday = 625; - Sentiment = 626; + Sentiment = 626 [deprecated = true]; StreamChatMessaging = 627; TeamworkCRM = 628; TeamworkDesk = 629; @@ -667,7 +667,7 @@ enum DetectorType { Meistertask = 656; Mindmeister = 657; PeopleDataLabs = 658; - ScraperSite = 659; + ScraperSite = 659 [deprecated = true]; Scrapfly = 660; SimplyNoted = 661; TravelPayouts = 662; @@ -965,7 +965,17 @@ enum DetectorType { DenoDeploy = 954; Stripo = 955; ReplyIO = 956; - Replicate = 957; + AzureBatch = 957; + AzureContainerRegistry = 958; + AWSSessionKey = 959; + Coda = 960; + LogzIO = 961; + Eventbrite = 962; + GrafanaServiceAccount = 963; + RequestFinance = 964; + Overloop = 965; + Ngrok = 966; + Replicate = 967; } message Result {