Repository: fullstorydev/grpcurl Branch: master Commit: c54eac28fd8d Files: 75 Total size: 444.5 KB Directory structure: gitextract_zt9nhjki/ ├── .circleci/ │ └── config.yml ├── .github/ │ ├── dependabot.yml │ └── workflows/ │ └── codesee-arch-diagram.yml ├── .gitignore ├── .goreleaser.yml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── cmd/ │ └── grpcurl/ │ ├── grpcurl.go │ ├── indent_test.go │ └── unix.go ├── desc_source.go ├── desc_source_test.go ├── download_protoc.sh ├── format.go ├── format_test.go ├── go.mod ├── go.sum ├── grpcurl.go ├── grpcurl_test.go ├── internal/ │ └── testing/ │ ├── cmd/ │ │ ├── bankdemo/ │ │ │ ├── README.md │ │ │ ├── auth.go │ │ │ ├── bank.go │ │ │ ├── bank.pb.go │ │ │ ├── bank.proto │ │ │ ├── bank_grpc.pb.go │ │ │ ├── chat.go │ │ │ ├── db.go │ │ │ ├── main.go │ │ │ ├── support.pb.go │ │ │ ├── support.proto │ │ │ └── support_grpc.pb.go │ │ └── testserver/ │ │ ├── README.md │ │ ├── testserver.go │ │ └── unix.go │ ├── example.proto │ ├── example.protoset │ ├── example2.proto │ ├── jsonpb_test_proto/ │ │ ├── test_objects.pb.go │ │ └── test_objects.proto │ ├── test.pb.go │ ├── test.proto │ ├── test.protoset │ ├── test_grpc.pb.go │ ├── test_server.go │ └── tls/ │ ├── ca.crl │ ├── ca.crt │ ├── ca.key │ ├── client.crt │ ├── client.csr │ ├── client.key │ ├── expired.crt │ ├── expired.csr │ ├── expired.key │ ├── other.crt │ ├── other.csr │ ├── other.key │ ├── server.crt │ ├── server.csr │ ├── server.key │ ├── wrong-ca.crl │ ├── wrong-ca.crt │ ├── wrong-ca.key │ ├── wrong-client.crt │ ├── wrong-client.csr │ └── wrong-client.key ├── invoke.go ├── mk-test-files.sh ├── releasing/ │ ├── README.md │ ├── RELEASE_NOTES.md │ └── do-release.sh ├── snap/ │ ├── README.md │ └── snapcraft.yaml └── tls_settings_test.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .circleci/config.yml ================================================ shared_configs: simple_job_steps: &simple_job_steps - checkout - run: name: Run tests command: | make test # Use the latest 2.1 version of CircleCI pipeline process engine. See: https://circleci.com/docs/2.0/configuration-reference version: 2.1 jobs: build-1-23: working_directory: ~/repo docker: - image: cimg/go:1.23 steps: *simple_job_steps build-1-24: working_directory: ~/repo docker: - image: cimg/go:1.24 steps: *simple_job_steps build-1-25: working_directory: ~/repo docker: - image: cimg/go:1.25 steps: - checkout - run: name: Run tests and linters command: | make ci workflows: pr-build-test: jobs: - build-1-23 - build-1-24 - build-1-25 ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: "gomod" directory: "/" # Check for updates once a week schedule: interval: "weekly" ================================================ FILE: .github/workflows/codesee-arch-diagram.yml ================================================ # This workflow was added by CodeSee. Learn more at https://codesee.io/ # This is v2.0 of this workflow file on: push: branches: - master pull_request_target: types: [opened, synchronize, reopened] name: CodeSee permissions: read-all jobs: codesee: runs-on: ubuntu-latest continue-on-error: true name: Analyze the repo with CodeSee steps: - uses: Codesee-io/codesee-action@v2 with: codesee-token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }} ================================================ FILE: .gitignore ================================================ dist/ .idea/ VERSION .tmp/ *.snap ================================================ FILE: .goreleaser.yml ================================================ builds: - binary: grpcurl main: ./cmd/grpcurl goos: - linux - darwin - windows goarch: - amd64 - 386 - arm - arm64 - s390x - ppc64le goarm: - 5 - 6 - 7 ignore: - goos: darwin goarch: 386 - goos: windows goarch: arm64 - goos: darwin goarch: arm - goos: windows goarch: arm - goos: darwin goarch: s390x - goos: windows goarch: s390x - goos: darwin goarch: ppc64le - goos: windows goarch: ppc64le ldflags: - -s -w -X main.version=v{{.Version}} archives: - format: tar.gz name_template: >- {{ .Binary }}_{{ .Version }}_ {{- if eq .Os "darwin" }}osx{{ else }}{{ .Os }}{{ end }}_ {{- if eq .Arch "amd64" }}x86_64 {{- else if eq .Arch "386" }}x86_32 {{- else }}{{ .Arch }}{{ end }} {{- with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }} format_overrides: - goos: windows format: zip files: - LICENSE nfpms: - vendor: Fullstory homepage: https://github.com/fullstorydev/grpcurl/ maintainer: Engineering at Fullstory description: 'Like cURL, but for gRPC: Command-line tool for interacting with gRPC servers' license: MIT id: nfpms formats: - deb - rpm ================================================ FILE: Dockerfile ================================================ FROM golang:1.25-alpine AS builder LABEL maintainer="Fullstory Engineering" # create non-privileged group and user RUN addgroup -S grpcurl && adduser -S grpcurl -G grpcurl WORKDIR /tmp/fullstorydev/grpcurl # copy just the files/sources we need to build grpcurl COPY VERSION *.go go.* /tmp/fullstorydev/grpcurl/ COPY cmd /tmp/fullstorydev/grpcurl/cmd # and build a completely static binary (so we can use # scratch as basis for the final image) ENV CGO_ENABLED=0 ENV GO111MODULE=on RUN go build -o /grpcurl \ -ldflags "-w -extldflags \"-static\" -X \"main.version=$(cat VERSION)\"" \ ./cmd/grpcurl FROM alpine:3 AS alpine WORKDIR / COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt COPY --from=builder /etc/passwd /etc/passwd COPY --from=builder /grpcurl /bin/grpcurl USER grpcurl ENTRYPOINT ["/bin/grpcurl"] # New FROM so we have a nice'n'tiny image FROM scratch WORKDIR / COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt COPY --from=builder /etc/passwd /etc/passwd COPY --from=builder /grpcurl /bin/grpcurl USER grpcurl ENTRYPOINT ["/bin/grpcurl"] ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2017 Fullstory, Inc Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ dev_build_version=$(shell git describe --tags --always --dirty) export PATH := $(shell pwd)/.tmp/protoc/bin:$(PATH) export PROTOC_VERSION := 22.0 # Disable CGO for improved compatibility across distros export CGO_ENABLED=0 export GOFLAGS=-trimpath export GOWORK=off # TODO: run golint and errcheck, but only to catch *new* violations and # decide whether to change code or not (e.g. we need to be able to whitelist # violations already in the code). They can be useful to catch errors, but # they are just too noisy to be a requirement for a CI -- we don't even *want* # to fix some of the things they consider to be violations. .PHONY: ci ci: deps checkgofmt checkgenerate vet staticcheck ineffassign predeclared test .PHONY: deps deps: go get -d -v -t ./... go mod tidy .PHONY: updatedeps updatedeps: go get -d -v -t -u -f ./... go mod tidy .PHONY: install install: go install -ldflags '-X "main.version=dev build $(dev_build_version)"' ./... .PHONY: release release: @go install github.com/goreleaser/goreleaser@v1.21.0 goreleaser release --clean .PHONY: docker docker: @echo $(dev_build_version) > VERSION docker build -t fullstorydev/grpcurl:$(dev_build_version) . @rm VERSION .PHONY: generate generate: .tmp/protoc/bin/protoc @go install google.golang.org/protobuf/cmd/protoc-gen-go@a709e31e5d12 @go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1.0 @go install github.com/jhump/protoreflect/desc/sourceinfo/cmd/protoc-gen-gosrcinfo@v1.14.1 go generate ./... go mod tidy .PHONY: checkgenerate checkgenerate: generate git status --porcelain -- '**/*.go' @if [ -n "$$(git status --porcelain -- '**/*.go')" ]; then \ git diff -- '**/*.go'; \ exit 1; \ fi .PHONY: checkgofmt checkgofmt: gofmt -s -l . @if [ -n "$$(gofmt -s -l .)" ]; then \ exit 1; \ fi .PHONY: vet vet: go vet ./... .PHONY: staticcheck staticcheck: @go install honnef.co/go/tools/cmd/staticcheck@2025.1.1 staticcheck -checks "inherit,-SA1019" ./... .PHONY: ineffassign ineffassign: @go install github.com/gordonklaus/ineffassign@7953dde2c7bf ineffassign . .PHONY: predeclared predeclared: @go install github.com/nishanths/predeclared@51e8c974458a0f93dc03fe356f91ae1a6d791e6f predeclared ./... # Intentionally omitted from CI, but target here for ad-hoc reports. .PHONY: golint golint: @go install golang.org/x/lint/golint@v0.0.0-20210508222113-6edffad5e616 golint -min_confidence 0.9 -set_exit_status ./... # Intentionally omitted from CI, but target here for ad-hoc reports. .PHONY: errcheck errcheck: @go install github.com/kisielk/errcheck@v1.2.0 errcheck ./... .PHONY: test test: deps CGO_ENABLED=1 go test -race ./... .tmp/protoc/bin/protoc: ./Makefile ./download_protoc.sh ./download_protoc.sh ================================================ FILE: README.md ================================================ # gRPCurl [![Build Status](https://circleci.com/gh/fullstorydev/grpcurl/tree/master.svg?style=svg)](https://circleci.com/gh/fullstorydev/grpcurl/tree/master) [![Go Report Card](https://goreportcard.com/badge/github.com/fullstorydev/grpcurl)](https://goreportcard.com/report/github.com/fullstorydev/grpcurl) [![Snap Release Status](https://snapcraft.io/grpcurl/badge.svg)](https://snapcraft.io/grpcurl) `grpcurl` is a command-line tool that lets you interact with gRPC servers. It's basically `curl` for gRPC servers. The main purpose for this tool is to invoke RPC methods on a gRPC server from the command-line. gRPC servers use a binary encoding on the wire ([protocol buffers](https://developers.google.com/protocol-buffers/), or "protobufs" for short). So they are basically impossible to interact with using regular `curl` (and older versions of `curl` that do not support HTTP/2 are of course non-starters). This program accepts messages using JSON encoding, which is much more friendly for both humans and scripts. With this tool you can also browse the schema for gRPC services, either by querying a server that supports [server reflection](https://github.com/grpc/grpc/blob/master/src/proto/grpc/reflection/v1/reflection.proto), by reading proto source files, or by loading in compiled "protoset" files (files that contain encoded file [descriptor protos](https://github.com/google/protobuf/blob/master/src/google/protobuf/descriptor.proto)). In fact, the way the tool transforms JSON request data into a binary encoded protobuf is using that very same schema. So, if the server you interact with does not support reflection, you will either need the proto source files that define the service or need protoset files that `grpcurl` can use. This repo also provides a library package, `github.com/fullstorydev/grpcurl`, that has functions for simplifying the construction of other command-line tools that dynamically invoke gRPC endpoints. This code is a great example of how to use the various packages of the [protoreflect](https://godoc.org/github.com/jhump/protoreflect) library, and shows off what they can do. See also the [`grpcurl` talk at GopherCon 2018](https://www.youtube.com/watch?v=dDr-8kbMnaw). ## Features `grpcurl` supports all kinds of RPC methods, including streaming methods. You can even operate bi-directional streaming methods interactively by running `grpcurl` from an interactive terminal and using stdin as the request body! `grpcurl` supports both secure/TLS servers _and_ plain-text servers (i.e. no TLS) and has numerous options for TLS configuration. It also supports mutual TLS, where the client is required to present a client certificate. As mentioned above, `grpcurl` works seamlessly if the server supports the reflection service. If not, you can supply the `.proto` source files or you can supply protoset files (containing compiled descriptors, produced by `protoc`) to `grpcurl`. ## Installation ### Binaries Download the binary from the [releases](https://github.com/fullstorydev/grpcurl/releases) page. ### Homebrew (macOS) On macOS, `grpcurl` is available via Homebrew: ```shell brew install grpcurl ``` ### Docker For platforms that support Docker, you can download an image that lets you run `grpcurl`: ```shell # Download image docker pull fullstorydev/grpcurl:latest # Run the tool docker run fullstorydev/grpcurl api.grpc.me:443 list ``` Note that there are some pitfalls when using docker: - If you need to interact with a server listening on the host's loopback network, you must specify the host as `host.docker.internal` instead of `localhost` (for Mac or Windows) _OR_ have the container use the host network with `-network="host"` (Linux only). - If you need to provide proto source files or descriptor sets, you must mount the folder containing the files as a volume (`-v $(pwd):/protos`) and adjust the import paths to container paths accordingly. - If you want to provide the request message via stdin, using the `-d @` option, you need to use the `-i` flag on the docker command. ### Other Packages There are numerous other ways to install `grpcurl`, thanks to support from third parties that have created recipes/packages for it. These include other ways to install `grpcurl` on a variety of environments, including Windows and myriad Linux distributions. You can see more details and the full list of other packages for `grpcurl` at _repology.org_: https://repology.org/project/grpcurl/information ### Snap You can install `grpcurl` using the snap package: `snap install grpcurl` ### From Source If you already have the [Go SDK](https://golang.org/doc/install) installed, you can use the `go` tool to install `grpcurl`: ```shell go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest ``` This installs the command into the `bin` sub-folder of wherever your `$GOPATH` environment variable points. (If you have no `GOPATH` environment variable set, the default install location is `$HOME/go/bin`). If this directory is already in your `$PATH`, then you should be good to go. If you have already pulled down this repo to a location that is not in your `$GOPATH` and want to build from the sources, you can `cd` into the repo and then run `make install`. If you encounter compile errors and are using a version of the Go SDK older than 1.13, you could have out-dated versions of `grpcurl`'s dependencies. You can update the dependencies by running `make updatedeps`. Or, if you are using Go 1.11 or 1.12, you can add `GO111MODULE=on` as a prefix to the commands above, which will also build using the right versions of dependencies (vs. whatever you may already have in your `GOPATH`). ## Usage The usage doc for the tool explains the numerous options: ```shell grpcurl -help ``` In the sections below, you will find numerous examples demonstrating how to use `grpcurl`. ### Invoking RPCs Invoking an RPC on a trusted server (e.g. TLS without self-signed key or custom CA) that requires no client certs and supports server reflection is the simplest thing to do with `grpcurl`. This minimal invocation sends an empty request body: ```shell grpcurl grpc.server.com:443 my.custom.server.Service/Method # no TLS grpcurl -plaintext grpc.server.com:80 my.custom.server.Service/Method ``` To send a non-empty request, use the `-d` argument. Note that all arguments must come *before* the server address and method name: ```shell grpcurl -d '{"id": 1234, "tags": ["foo","bar"]}' \ grpc.server.com:443 my.custom.server.Service/Method ``` As can be seen in the example, the supplied body must be in JSON format. The body will be parsed and then transmitted to the server in the protobuf binary format. If you want to include `grpcurl` in a command pipeline, such as when using `jq` to create a request body, you can use `-d @`, which tells `grpcurl` to read the actual request body from stdin: ```shell grpcurl -d @ grpc.server.com:443 my.custom.server.Service/Method < 0 && !*usealts { fail(nil, "The -alts-target-service-account argument must be used with the -alts argument.") } if *format != "json" && *format != "text" { fail(nil, "The -format option must be 'json' or 'text'.") } if *emitDefaults && *format != "json" { warn("The -emit-defaults is only used when using json format.") } args := flags.Args() if len(args) == 0 { fail(nil, "Too few arguments.") } var target string if args[0] != "list" && args[0] != "describe" { target = args[0] args = args[1:] } if len(args) == 0 { fail(nil, "Too few arguments.") } var list, describe, invoke bool if args[0] == "list" { list = true args = args[1:] } else if args[0] == "describe" { describe = true args = args[1:] } else { invoke = true } verbosityLevel := 0 if *verbose { verbosityLevel = 1 } var rootTiming *timingData if *veryVerbose { verbosityLevel = 2 rootTiming = &timingData{Title: "Timing Data", Start: time.Now()} defer func() { rootTiming.Done() dumpTiming(rootTiming, 0) }() } var symbol string if invoke { if len(args) == 0 { fail(nil, "Too few arguments.") } symbol = args[0] args = args[1:] } else { if *data != "" { warn("The -d argument is not used with 'list' or 'describe' verb.") } if len(rpcHeaders) > 0 { warn("The -rpc-header argument is not used with 'list' or 'describe' verb.") } if len(args) > 0 { symbol = args[0] args = args[1:] } } if len(args) > 0 { fail(nil, "Too many arguments.") } if invoke && target == "" { fail(nil, "No host:port specified.") } if len(protoset) == 0 && len(protoFiles) == 0 && target == "" { fail(nil, "No host:port specified, no protoset specified, and no proto sources specified.") } if len(protoset) > 0 && len(reflHeaders) > 0 { warn("The -reflect-header argument is not used when -protoset files are used.") } if len(protoset) > 0 && len(protoFiles) > 0 { fail(nil, "Use either -protoset files or -proto files, but not both.") } if len(importPaths) > 0 && len(protoFiles) == 0 { warn("The -import-path argument is not used unless -proto files are used.") } if !reflection.val && len(protoset) == 0 && len(protoFiles) == 0 { fail(nil, "No protoset files or proto files specified and -use-reflection set to false.") } // Protoset or protofiles provided and -use-reflection unset if !reflection.set && (len(protoset) > 0 || len(protoFiles) > 0) { reflection.val = false } ctx := context.Background() if *maxTime > 0 { timeout := floatSecondsToDuration(*maxTime) var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, timeout) defer cancel() } dial := func() *grpc.ClientConn { dialTiming := rootTiming.Child("Dial") defer dialTiming.Done() dialTime := 10 * time.Second if *connectTimeout > 0 { dialTime = floatSecondsToDuration(*connectTimeout) } ctx, cancel := context.WithTimeout(ctx, dialTime) defer cancel() var opts []grpc.DialOption if *keepaliveTime > 0 { timeout := floatSecondsToDuration(*keepaliveTime) opts = append(opts, grpc.WithKeepaliveParams(keepalive.ClientParameters{ Time: timeout, Timeout: timeout, })) } if *maxMsgSz > 0 { opts = append(opts, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(*maxMsgSz))) } if isUnixSocket != nil && isUnixSocket() && !strings.HasPrefix(target, "unix://") { // prepend unix:// to the address if it's not already there // this is to maintain backwards compatibility because the custom dialer is replaced by // the default dialer in grpc-go. // https://github.com/fullstorydev/grpcurl/pull/480 target = "unix://" + target } var creds credentials.TransportCredentials if *plaintext { if *authority != "" { opts = append(opts, grpc.WithAuthority(*authority)) } } else if *usealts { clientOptions := alts.DefaultClientOptions() if len(altsTargetServiceAccounts) > 0 { clientOptions.TargetServiceAccounts = altsTargetServiceAccounts } if *altsHandshakerServiceAddress != "" { clientOptions.HandshakerServiceAddress = *altsHandshakerServiceAddress } creds = alts.NewClientCreds(clientOptions) } else if usetls { tlsTiming := dialTiming.Child("TLS Setup") defer tlsTiming.Done() tlsConf, err := grpcurl.ClientTLSConfig(*insecure, *cacert, *cert, *key) if err != nil { fail(err, "Failed to create TLS config") } sslKeylogFile := os.Getenv("SSLKEYLOGFILE") if sslKeylogFile != "" { w, err := os.OpenFile(sslKeylogFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600) if err != nil { fail(err, "Could not open SSLKEYLOGFILE %s", sslKeylogFile) } tlsConf.KeyLogWriter = w } creds = credentials.NewTLS(tlsConf) // can use either -servername or -authority; but not both if *serverName != "" && *authority != "" { if *serverName == *authority { warn("Both -servername and -authority are present; prefer only -authority.") } else { fail(nil, "Cannot specify different values for -servername and -authority.") } } overrideName := *serverName if overrideName == "" { overrideName = *authority } if overrideName != "" { opts = append(opts, grpc.WithAuthority(overrideName)) } tlsTiming.Done() } else { panic("Should have defaulted to use TLS.") } grpcurlUA := "grpcurl/" + version if version == noVersion { grpcurlUA = "grpcurl/dev-build (no version set)" } if *userAgent != "" { grpcurlUA = *userAgent + " " + grpcurlUA } opts = append(opts, grpc.WithUserAgent(grpcurlUA)) blockingDialTiming := dialTiming.Child("BlockingDial") defer blockingDialTiming.Done() cc, err := grpcurl.BlockingDial(ctx, "", target, creds, opts...) if err != nil { fail(err, "Failed to dial target host %q", target) } return cc } printFormattedStatus := func(w io.Writer, stat *status.Status, formatter grpcurl.Formatter) { formattedStatus, err := formatter(stat.Proto()) if err != nil { fmt.Fprintf(w, "ERROR: %v", err.Error()) } fmt.Fprint(w, formattedStatus) } if *expandHeaders { var err error addlHeaders, err = grpcurl.ExpandHeaders(addlHeaders) if err != nil { fail(err, "Failed to expand additional headers") } rpcHeaders, err = grpcurl.ExpandHeaders(rpcHeaders) if err != nil { fail(err, "Failed to expand rpc headers") } reflHeaders, err = grpcurl.ExpandHeaders(reflHeaders) if err != nil { fail(err, "Failed to expand reflection headers") } } var cc *grpc.ClientConn var descSource grpcurl.DescriptorSource var refClient *grpcreflect.Client var fileSource grpcurl.DescriptorSource if len(protoset) > 0 { var err error fileSource, err = grpcurl.DescriptorSourceFromProtoSets(protoset...) if err != nil { fail(err, "Failed to process proto descriptor sets.") } } else if len(protoFiles) > 0 { var err error fileSource, err = grpcurl.DescriptorSourceFromProtoFiles(importPaths, protoFiles...) if err != nil { fail(err, "Failed to process proto source files.") } } if reflection.val { md := grpcurl.MetadataFromHeaders(append(addlHeaders, reflHeaders...)) refCtx := metadata.NewOutgoingContext(ctx, md) cc = dial() refClient = grpcreflect.NewClientAuto(refCtx, cc) refClient.AllowMissingFileDescriptors() reflSource := grpcurl.DescriptorSourceFromServer(ctx, refClient) if fileSource != nil { descSource = compositeSource{reflSource, fileSource} } else { descSource = reflSource } } else { descSource = fileSource } // arrange for the RPCs to be cleanly shutdown reset := func() { if refClient != nil { refClient.Reset() refClient = nil } if cc != nil { cc.Close() cc = nil } } defer reset() exit = func(code int) { // since defers aren't run by os.Exit... reset() os.Exit(code) } if list { if symbol == "" { svcs, err := grpcurl.ListServices(descSource) if err != nil { fail(err, "Failed to list services") } if len(svcs) == 0 { fmt.Println("(No services)") } else { for _, svc := range svcs { fmt.Printf("%s\n", svc) } } if err := writeProtoset(descSource, svcs...); err != nil { fail(err, "Failed to write protoset to %s", *protosetOut) } if err := writeProtos(descSource, svcs...); err != nil { fail(err, "Failed to write protos to %s", *protoOut) } } else { methods, err := grpcurl.ListMethods(descSource, symbol) if err != nil { fail(err, "Failed to list methods for service %q", symbol) } if len(methods) == 0 { fmt.Println("(No methods)") // probably unlikely } else { for _, m := range methods { fmt.Printf("%s\n", m) } } if err := writeProtoset(descSource, symbol); err != nil { fail(err, "Failed to write protoset to %s", *protosetOut) } if err := writeProtos(descSource, symbol); err != nil { fail(err, "Failed to write protos to %s", *protoOut) } } } else if describe { var symbols []string if symbol != "" { symbols = []string{symbol} } else { // if no symbol given, describe all exposed services svcs, err := descSource.ListServices() if err != nil { fail(err, "Failed to list services") } if len(svcs) == 0 { fmt.Println("Server returned an empty list of exposed services") } symbols = svcs } for _, s := range symbols { if s[0] == '.' { s = s[1:] } dsc, err := descSource.FindSymbol(s) if err != nil { fail(err, "Failed to resolve symbol %q", s) } fqn := dsc.GetFullyQualifiedName() var elementType string switch d := dsc.(type) { case *desc.MessageDescriptor: elementType = "a message" parent, ok := d.GetParent().(*desc.MessageDescriptor) if ok { if d.IsMapEntry() { for _, f := range parent.GetFields() { if f.IsMap() && f.GetMessageType() == d { // found it: describe the map field instead elementType = "the entry type for a map field" dsc = f break } } } else { // see if it's a group for _, f := range parent.GetFields() { if f.GetType() == descriptorpb.FieldDescriptorProto_TYPE_GROUP && f.GetMessageType() == d { // found it: describe the map field instead elementType = "the type of a group field" dsc = f break } } } } case *desc.FieldDescriptor: elementType = "a field" if d.GetType() == descriptorpb.FieldDescriptorProto_TYPE_GROUP { elementType = "a group field" } else if d.IsExtension() { elementType = "an extension" } case *desc.OneOfDescriptor: elementType = "a one-of" case *desc.EnumDescriptor: elementType = "an enum" case *desc.EnumValueDescriptor: elementType = "an enum value" case *desc.ServiceDescriptor: elementType = "a service" case *desc.MethodDescriptor: elementType = "a method" default: err = fmt.Errorf("descriptor has unrecognized type %T", dsc) fail(err, "Failed to describe symbol %q", s) } txt, err := grpcurl.GetDescriptorText(dsc, descSource) if err != nil { fail(err, "Failed to describe symbol %q", s) } fmt.Printf("%s is %s:\n", fqn, elementType) fmt.Println(txt) if dsc, ok := dsc.(*desc.MessageDescriptor); ok && *msgTemplate { // for messages, also show a template in JSON, to make it easier to // create a request to invoke an RPC tmpl := grpcurl.MakeTemplate(dsc) options := grpcurl.FormatOptions{EmitJSONDefaultFields: true} _, formatter, err := grpcurl.RequestParserAndFormatter(grpcurl.Format(*format), descSource, nil, options) if err != nil { fail(err, "Failed to construct formatter for %q", *format) } str, err := formatter(tmpl) if err != nil { fail(err, "Failed to print template for message %s", s) } fmt.Println("\nMessage template:") fmt.Println(str) } } if err := writeProtoset(descSource, symbols...); err != nil { fail(err, "Failed to write protoset to %s", *protosetOut) } if err := writeProtos(descSource, symbol); err != nil { fail(err, "Failed to write protos to %s", *protoOut) } } else { // Invoke an RPC if cc == nil { cc = dial() } var in io.Reader if *data == "@" { in = os.Stdin } else { in = strings.NewReader(*data) } // if not verbose output, then also include record delimiters // between each message, so output could potentially be piped // to another grpcurl process includeSeparators := verbosityLevel == 0 options := grpcurl.FormatOptions{ EmitJSONDefaultFields: *emitDefaults, IncludeTextSeparator: includeSeparators, AllowUnknownFields: *allowUnknownFields, } rf, formatter, err := grpcurl.RequestParserAndFormatter(grpcurl.Format(*format), descSource, in, options) if err != nil { fail(err, "Failed to construct request parser and formatter for %q", *format) } h := &grpcurl.DefaultEventHandler{ Out: os.Stdout, Formatter: formatter, VerbosityLevel: verbosityLevel, } invokeTiming := rootTiming.Child("InvokeRPC") err = grpcurl.InvokeRPC(ctx, descSource, cc, symbol, append(addlHeaders, rpcHeaders...), h, rf.Next) invokeTiming.Done() if err != nil { if errStatus, ok := status.FromError(err); ok && *formatError { h.Status = errStatus } else { fail(err, "Error invoking method %q", symbol) } } reqSuffix := "" respSuffix := "" reqCount := rf.NumRequests() if reqCount != 1 { reqSuffix = "s" } if h.NumResponses != 1 { respSuffix = "s" } if verbosityLevel > 0 { fmt.Printf("Sent %d request%s and received %d response%s\n", reqCount, reqSuffix, h.NumResponses, respSuffix) } if h.Status.Code() != codes.OK { if *formatError { printFormattedStatus(os.Stderr, h.Status, formatter) } else { grpcurl.PrintStatus(os.Stderr, h.Status, formatter) } exit(statusCodeOffset + int(h.Status.Code())) } } } func dumpTiming(td *timingData, lvl int) { var ind strings.Builder for x := 0; x < lvl; x++ { ind.WriteString(" ") } fmt.Printf("%s%s: %s\n", ind.String(), td.Title, td.Value) for _, sd := range td.Sub { dumpTiming(sd, lvl+1) } } func usage() { fmt.Fprintf(os.Stderr, `Usage: %s [flags] [address] [list|describe] [symbol] The 'address' is only optional when used with 'list' or 'describe' and a protoset or proto flag is provided. If 'list' is indicated, the symbol (if present) should be a fully-qualified service name. If present, all methods of that service are listed. If not present, all exposed services are listed, or all services defined in protosets. If 'describe' is indicated, the descriptor for the given symbol is shown. The symbol should be a fully-qualified service, enum, or message name. If no symbol is given then the descriptors for all exposed or known services are shown. If neither verb is present, the symbol must be a fully-qualified method name in 'service/method' or 'service.method' format. In this case, the request body will be used to invoke the named method. If no body is given but one is required (i.e. the method is unary or server-streaming), an empty instance of the method's request type will be sent. The address will typically be in the form "host:port" where host can be an IP address or a hostname and port is a numeric port or service name. If an IPv6 address is given, it must be surrounded by brackets, like "[2001:db8::1]". For Unix variants, if a -unix=true flag is present, then the address must be the path to the domain socket. Available flags: `, os.Args[0]) flags.PrintDefaults() } func prettify(docString string) string { parts := strings.Split(docString, "\n") // cull empty lines and also remove trailing and leading spaces // from each line in the doc string j := 0 for _, part := range parts { part = strings.TrimSpace(part) if part == "" { continue } parts[j] = part j++ } return strings.Join(parts[:j], "\n") } func warn(msg string, args ...interface{}) { msg = fmt.Sprintf("Warning: %s\n", msg) fmt.Fprintf(os.Stderr, msg, args...) } func fail(err error, msg string, args ...interface{}) { if err != nil { msg += ": %v" args = append(args, err) } fmt.Fprintf(os.Stderr, msg, args...) fmt.Fprintln(os.Stderr) if err != nil { exit(1) } else { // nil error means it was CLI usage issue fmt.Fprintf(os.Stderr, "Try '%s -help' for more details.\n", os.Args[0]) exit(2) } } func writeProtoset(descSource grpcurl.DescriptorSource, symbols ...string) error { if *protosetOut == "" { return nil } f, err := os.Create(*protosetOut) if err != nil { return err } defer f.Close() return grpcurl.WriteProtoset(f, descSource, symbols...) } func writeProtos(descSource grpcurl.DescriptorSource, symbols ...string) error { if *protoOut == "" { return nil } return grpcurl.WriteProtoFiles(*protoOut, descSource, symbols...) } type optionalBoolFlag struct { set, val bool } func (f *optionalBoolFlag) String() string { if !f.set { return "unset" } return strconv.FormatBool(f.val) } func (f *optionalBoolFlag) Set(s string) error { v, err := strconv.ParseBool(s) if err != nil { return err } f.set = true f.val = v return nil } func (f *optionalBoolFlag) IsBoolFlag() bool { return true } func floatSecondsToDuration(seconds float64) time.Duration { durationFloat := seconds * float64(time.Second) if durationFloat > math.MaxInt64 { // Avoid overflow return math.MaxInt64 } return time.Duration(durationFloat) } ================================================ FILE: cmd/grpcurl/indent_test.go ================================================ package main import ( "bytes" "flag" "testing" ) func TestFlagDocIndent(t *testing.T) { // Tests the prettify() and indent() function. The indent() function // differs by Go version, due to differences in "flags" package across // versions. Run with multiple versions of Go to ensure that doc output // is properly indented, regardless of Go version. var fs flag.FlagSet var buf bytes.Buffer fs.SetOutput(&buf) fs.String("foo", "", prettify(` This is a flag doc string. It has multiple lines. More than two, actually.`)) fs.Int("bar", 100, prettify(`This is a simple flag doc string.`)) fs.Bool("baz", false, prettify(` This is another long doc string. It also has multiple lines. But not as long as the first one.`)) fs.PrintDefaults() expected := ` -bar int This is a simple flag doc string. (default 100) -baz This is another long doc string. It also has multiple lines. But not as long as the first one. -foo string This is a flag doc string. It has multiple lines. More than two, actually. ` actual := buf.String() if actual != expected { t.Errorf("Flag output had wrong indentation.\nExpecting:\n%s\nGot:\n%s", expected, actual) } } ================================================ FILE: cmd/grpcurl/unix.go ================================================ //go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || windows // +build darwin dragonfly freebsd linux netbsd openbsd solaris windows package main var ( unix = flags.Bool("unix", false, prettify(` Indicates that the server address is the path to a Unix domain socket.`)) ) func init() { isUnixSocket = func() bool { return *unix } } ================================================ FILE: desc_source.go ================================================ package grpcurl import ( "context" "errors" "fmt" "io" "os" "path/filepath" "sync" "github.com/golang/protobuf/proto" //lint:ignore SA1019 we have to import these because some of their types appear in exported API "github.com/jhump/protoreflect/desc" //lint:ignore SA1019 same as above "github.com/jhump/protoreflect/desc/protoparse" //lint:ignore SA1019 same as above "github.com/jhump/protoreflect/desc/protoprint" "github.com/jhump/protoreflect/dynamic" //lint:ignore SA1019 same as above "github.com/jhump/protoreflect/grpcreflect" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/descriptorpb" ) // ErrReflectionNotSupported is returned by DescriptorSource operations that // rely on interacting with the reflection service when the source does not // actually expose the reflection service. When this occurs, an alternate source // (like file descriptor sets) must be used. var ErrReflectionNotSupported = errors.New("server does not support the reflection API") // DescriptorSource is a source of protobuf descriptor information. It can be backed by a FileDescriptorSet // proto (like a file generated by protoc) or a remote server that supports the reflection API. type DescriptorSource interface { // ListServices returns a list of fully-qualified service names. It will be all services in a set of // descriptor files or the set of all services exposed by a gRPC server. ListServices() ([]string, error) // FindSymbol returns a descriptor for the given fully-qualified symbol name. FindSymbol(fullyQualifiedName string) (desc.Descriptor, error) // AllExtensionsForType returns all known extension fields that extend the given message type name. AllExtensionsForType(typeName string) ([]*desc.FieldDescriptor, error) } // DescriptorSourceFromProtoSets creates a DescriptorSource that is backed by the named files, whose contents // are encoded FileDescriptorSet protos. func DescriptorSourceFromProtoSets(fileNames ...string) (DescriptorSource, error) { files := &descriptorpb.FileDescriptorSet{} for _, fileName := range fileNames { b, err := os.ReadFile(fileName) if err != nil { return nil, fmt.Errorf("could not load protoset file %q: %v", fileName, err) } var fs descriptorpb.FileDescriptorSet err = proto.Unmarshal(b, &fs) if err != nil { return nil, fmt.Errorf("could not parse contents of protoset file %q: %v", fileName, err) } files.File = append(files.File, fs.File...) } return DescriptorSourceFromFileDescriptorSet(files) } // DescriptorSourceFromProtoFiles creates a DescriptorSource that is backed by the named files, // whose contents are Protocol Buffer source files. The given importPaths are used to locate // any imported files. func DescriptorSourceFromProtoFiles(importPaths []string, fileNames ...string) (DescriptorSource, error) { fileNames, err := protoparse.ResolveFilenames(importPaths, fileNames...) if err != nil { return nil, err } p := protoparse.Parser{ ImportPaths: importPaths, InferImportPaths: len(importPaths) == 0, IncludeSourceCodeInfo: true, } fds, err := p.ParseFiles(fileNames...) if err != nil { return nil, fmt.Errorf("could not parse given files: %v", err) } return DescriptorSourceFromFileDescriptors(fds...) } // DescriptorSourceFromFileDescriptorSet creates a DescriptorSource that is backed by the FileDescriptorSet. func DescriptorSourceFromFileDescriptorSet(files *descriptorpb.FileDescriptorSet) (DescriptorSource, error) { unresolved := map[string]*descriptorpb.FileDescriptorProto{} for _, fd := range files.File { unresolved[fd.GetName()] = fd } resolved := map[string]*desc.FileDescriptor{} for _, fd := range files.File { _, err := resolveFileDescriptor(unresolved, resolved, fd.GetName()) if err != nil { return nil, err } } return &fileSource{files: resolved}, nil } func resolveFileDescriptor(unresolved map[string]*descriptorpb.FileDescriptorProto, resolved map[string]*desc.FileDescriptor, filename string) (*desc.FileDescriptor, error) { if r, ok := resolved[filename]; ok { return r, nil } fd, ok := unresolved[filename] if !ok { return nil, fmt.Errorf("no descriptor found for %q", filename) } deps := make([]*desc.FileDescriptor, 0, len(fd.GetDependency())) for _, dep := range fd.GetDependency() { depFd, err := resolveFileDescriptor(unresolved, resolved, dep) if err != nil { return nil, err } deps = append(deps, depFd) } result, err := desc.CreateFileDescriptor(fd, deps...) if err != nil { return nil, err } resolved[filename] = result return result, nil } // DescriptorSourceFromFileDescriptors creates a DescriptorSource that is backed by the given // file descriptors func DescriptorSourceFromFileDescriptors(files ...*desc.FileDescriptor) (DescriptorSource, error) { fds := map[string]*desc.FileDescriptor{} for _, fd := range files { if err := addFile(fd, fds); err != nil { return nil, err } } return &fileSource{files: fds}, nil } func addFile(fd *desc.FileDescriptor, fds map[string]*desc.FileDescriptor) error { name := fd.GetName() if existing, ok := fds[name]; ok { // already added this file if existing != fd { // doh! duplicate files provided return fmt.Errorf("given files include multiple copies of %q", name) } return nil } fds[name] = fd for _, dep := range fd.GetDependencies() { if err := addFile(dep, fds); err != nil { return err } } return nil } type fileSource struct { files map[string]*desc.FileDescriptor er *dynamic.ExtensionRegistry erInit sync.Once } func (fs *fileSource) ListServices() ([]string, error) { set := map[string]bool{} for _, fd := range fs.files { for _, svc := range fd.GetServices() { set[svc.GetFullyQualifiedName()] = true } } sl := make([]string, 0, len(set)) for svc := range set { sl = append(sl, svc) } return sl, nil } // GetAllFiles returns all of the underlying file descriptors. This is // more thorough and more efficient than the fallback strategy used by // the GetAllFiles package method, for enumerating all files from a // descriptor source. func (fs *fileSource) GetAllFiles() ([]*desc.FileDescriptor, error) { files := make([]*desc.FileDescriptor, len(fs.files)) i := 0 for _, fd := range fs.files { files[i] = fd i++ } return files, nil } func (fs *fileSource) FindSymbol(fullyQualifiedName string) (desc.Descriptor, error) { for _, fd := range fs.files { if dsc := fd.FindSymbol(fullyQualifiedName); dsc != nil { return dsc, nil } } return nil, notFound("Symbol", fullyQualifiedName) } func (fs *fileSource) AllExtensionsForType(typeName string) ([]*desc.FieldDescriptor, error) { fs.erInit.Do(func() { fs.er = &dynamic.ExtensionRegistry{} for _, fd := range fs.files { fs.er.AddExtensionsFromFile(fd) } }) return fs.er.AllExtensionsForType(typeName), nil } // DescriptorSourceFromServer creates a DescriptorSource that uses the given gRPC reflection client // to interrogate a server for descriptor information. If the server does not support the reflection // API then the various DescriptorSource methods will return ErrReflectionNotSupported func DescriptorSourceFromServer(_ context.Context, refClient *grpcreflect.Client) DescriptorSource { return serverSource{client: refClient} } type serverSource struct { client *grpcreflect.Client } func (ss serverSource) ListServices() ([]string, error) { svcs, err := ss.client.ListServices() return svcs, reflectionSupport(err) } func (ss serverSource) FindSymbol(fullyQualifiedName string) (desc.Descriptor, error) { file, err := ss.client.FileContainingSymbol(fullyQualifiedName) if err != nil { return nil, reflectionSupport(err) } d := file.FindSymbol(fullyQualifiedName) if d == nil { return nil, notFound("Symbol", fullyQualifiedName) } return d, nil } func (ss serverSource) AllExtensionsForType(typeName string) ([]*desc.FieldDescriptor, error) { var exts []*desc.FieldDescriptor nums, err := ss.client.AllExtensionNumbersForType(typeName) if err != nil { return nil, reflectionSupport(err) } for _, fieldNum := range nums { ext, err := ss.client.ResolveExtension(typeName, fieldNum) if err != nil { return nil, reflectionSupport(err) } exts = append(exts, ext) } return exts, nil } func reflectionSupport(err error) error { if err == nil { return nil } if stat, ok := status.FromError(err); ok && stat.Code() == codes.Unimplemented { return ErrReflectionNotSupported } return err } // WriteProtoset will use the given descriptor source to resolve all of the given // symbols and write a proto file descriptor set with their definitions to the // given output. The output will include descriptors for all files in which the // symbols are defined as well as their transitive dependencies. func WriteProtoset(out io.Writer, descSource DescriptorSource, symbols ...string) error { filenames, fds, err := getFileDescriptors(symbols, descSource) if err != nil { return err } // now expand that to include transitive dependencies in topologically sorted // order (such that file always appears after its dependencies) expandedFiles := make(map[string]struct{}, len(fds)) allFilesSlice := make([]*descriptorpb.FileDescriptorProto, 0, len(fds)) for _, filename := range filenames { allFilesSlice = addFilesToSet(allFilesSlice, expandedFiles, fds[filename]) } // now we can serialize to file b, err := proto.Marshal(&descriptorpb.FileDescriptorSet{File: allFilesSlice}) if err != nil { return fmt.Errorf("failed to serialize file descriptor set: %v", err) } if _, err := out.Write(b); err != nil { return fmt.Errorf("failed to write file descriptor set: %v", err) } return nil } func addFilesToSet(allFiles []*descriptorpb.FileDescriptorProto, expanded map[string]struct{}, fd *desc.FileDescriptor) []*descriptorpb.FileDescriptorProto { if _, ok := expanded[fd.GetName()]; ok { // already seen this one return allFiles } expanded[fd.GetName()] = struct{}{} // add all dependencies first for _, dep := range fd.GetDependencies() { allFiles = addFilesToSet(allFiles, expanded, dep) } return append(allFiles, fd.AsFileDescriptorProto()) } // WriteProtoFiles will use the given descriptor source to resolve all the given // symbols and write proto files with their definitions to the given output directory. func WriteProtoFiles(outProtoDirPath string, descSource DescriptorSource, symbols ...string) error { filenames, fds, err := getFileDescriptors(symbols, descSource) if err != nil { return err } // now expand that to include transitive dependencies in topologically sorted // order (such that file always appears after its dependencies) expandedFiles := make(map[string]struct{}, len(fds)) allFileDescriptors := make([]*desc.FileDescriptor, 0, len(fds)) for _, filename := range filenames { allFileDescriptors = addFilesToFileDescriptorList(allFileDescriptors, expandedFiles, fds[filename]) } pr := protoprint.Printer{} // now we can serialize to files for i := range allFileDescriptors { if err := writeProtoFile(outProtoDirPath, allFileDescriptors[i], &pr); err != nil { return err } } return nil } func writeProtoFile(outProtoDirPath string, fd *desc.FileDescriptor, pr *protoprint.Printer) error { outFile := filepath.Join(outProtoDirPath, fd.GetFullyQualifiedName()) outDir := filepath.Dir(outFile) if err := os.MkdirAll(outDir, 0777); err != nil { return fmt.Errorf("failed to create directory %q: %w", outDir, err) } f, err := os.Create(outFile) if err != nil { return fmt.Errorf("failed to create proto file %q: %w", outFile, err) } defer f.Close() if err := pr.PrintProtoFile(fd, f); err != nil { return fmt.Errorf("failed to write proto file %q: %w", outFile, err) } return nil } func getFileDescriptors(symbols []string, descSource DescriptorSource) ([]string, map[string]*desc.FileDescriptor, error) { // compute set of file descriptors filenames := make([]string, 0, len(symbols)) fds := make(map[string]*desc.FileDescriptor, len(symbols)) for _, sym := range symbols { d, err := descSource.FindSymbol(sym) if err != nil { return nil, nil, fmt.Errorf("failed to find descriptor for %q: %v", sym, err) } fd := d.GetFile() if _, ok := fds[fd.GetName()]; !ok { fds[fd.GetName()] = fd filenames = append(filenames, fd.GetName()) } } return filenames, fds, nil } func addFilesToFileDescriptorList(allFiles []*desc.FileDescriptor, expanded map[string]struct{}, fd *desc.FileDescriptor) []*desc.FileDescriptor { if _, ok := expanded[fd.GetName()]; ok { // already seen this one return allFiles } expanded[fd.GetName()] = struct{}{} // add all dependencies first for _, dep := range fd.GetDependencies() { allFiles = addFilesToFileDescriptorList(allFiles, expanded, dep) } return append(allFiles, fd) } ================================================ FILE: desc_source_test.go ================================================ package grpcurl import ( "bytes" "os" "testing" "github.com/golang/protobuf/proto" //lint:ignore SA1019 we have to import this because it appears in exported API "google.golang.org/protobuf/types/descriptorpb" ) func TestWriteProtoset(t *testing.T) { exampleProtoset, err := loadProtoset("./internal/testing/example.protoset") if err != nil { t.Fatalf("failed to load example.protoset: %v", err) } testProtoset, err := loadProtoset("./internal/testing/test.protoset") if err != nil { t.Fatalf("failed to load test.protoset: %v", err) } mergedProtoset := &descriptorpb.FileDescriptorSet{ File: append(exampleProtoset.File, testProtoset.File...), } descSrc, err := DescriptorSourceFromFileDescriptorSet(mergedProtoset) if err != nil { t.Fatalf("failed to create descriptor source: %v", err) } checkWriteProtoset(t, descSrc, exampleProtoset, "TestService") checkWriteProtoset(t, descSrc, testProtoset, "testing.TestService") checkWriteProtoset(t, descSrc, mergedProtoset, "TestService", "testing.TestService") } func loadProtoset(path string) (*descriptorpb.FileDescriptorSet, error) { b, err := os.ReadFile(path) if err != nil { return nil, err } var protoset descriptorpb.FileDescriptorSet if err := proto.Unmarshal(b, &protoset); err != nil { return nil, err } return &protoset, nil } func checkWriteProtoset(t *testing.T, descSrc DescriptorSource, protoset *descriptorpb.FileDescriptorSet, symbols ...string) { var buf bytes.Buffer if err := WriteProtoset(&buf, descSrc, symbols...); err != nil { t.Fatalf("failed to write protoset: %v", err) } var result descriptorpb.FileDescriptorSet if err := proto.Unmarshal(buf.Bytes(), &result); err != nil { t.Fatalf("failed to unmarshal written protoset: %v", err) } if !proto.Equal(protoset, &result) { t.Fatalf("written protoset not equal to input:\nExpecting: %s\nActual: %s", protoset, &result) } } ================================================ FILE: download_protoc.sh ================================================ #!/usr/bin/env bash set -e cd $(dirname $0) if [[ -z "$PROTOC_VERSION" ]]; then echo "Set PROTOC_VERSION env var to indicate the version to download" >&2 exit 1 fi PROTOC_OS="$(uname -s)" PROTOC_ARCH="$(uname -m)" case "${PROTOC_OS}" in Darwin) PROTOC_OS="osx" ;; Linux) PROTOC_OS="linux" ;; *) echo "Invalid value for uname -s: ${PROTOC_OS}" >&2 exit 1 esac # This is for macs with M1 chips. Precompiled binaries for osx/amd64 are not available for download, so for that case # we download the x86_64 version instead. This will work as long as rosetta2 is installed. if [ "$PROTOC_OS" = "osx" ] && [ "$PROTOC_ARCH" = "arm64" ]; then PROTOC_ARCH="x86_64" fi PROTOC="${PWD}/.tmp/protoc/bin/protoc" if [[ "$(${PROTOC} --version 2>/dev/null)" != "libprotoc 3.${PROTOC_VERSION}" ]]; then rm -rf ./.tmp/protoc mkdir -p .tmp/protoc curl -L "https://github.com/google/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-${PROTOC_OS}-${PROTOC_ARCH}.zip" > .tmp/protoc/protoc.zip pushd ./.tmp/protoc && unzip protoc.zip && popd fi ================================================ FILE: format.go ================================================ package grpcurl import ( "bufio" "bytes" "encoding/base64" "encoding/json" "fmt" "io" "reflect" "strings" "sync" "github.com/golang/protobuf/jsonpb" //lint:ignore SA1019 we have to import these because some of their types appear in exported API "github.com/golang/protobuf/proto" //lint:ignore SA1019 same as above "github.com/jhump/protoreflect/desc" //lint:ignore SA1019 same as above "github.com/jhump/protoreflect/dynamic" //lint:ignore SA1019 same as above "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" ) // RequestParser processes input into messages. type RequestParser interface { // Next parses input data into the given request message. If called after // input is exhausted, it returns io.EOF. If the caller re-uses the same // instance in multiple calls to Next, it should call msg.Reset() in between // each call. Next(msg proto.Message) error // NumRequests returns the number of messages that have been parsed and // returned by a call to Next. NumRequests() int } type jsonRequestParser struct { dec *json.Decoder unmarshaler jsonpb.Unmarshaler requestCount int } // NewJSONRequestParser returns a RequestParser that reads data in JSON format // from the given reader. The given resolver is used to assist with decoding of // google.protobuf.Any messages. // // Input data that contains more than one message should just include all // messages concatenated (though whitespace is necessary to separate some kinds // of values in JSON). // // If the given reader has no data, the returned parser will return io.EOF on // the very first call. func NewJSONRequestParser(in io.Reader, resolver jsonpb.AnyResolver) RequestParser { return &jsonRequestParser{ dec: json.NewDecoder(in), unmarshaler: jsonpb.Unmarshaler{AnyResolver: resolver}, } } // NewJSONRequestParserWithUnmarshaler is like NewJSONRequestParser but // accepts a protobuf jsonpb.Unmarshaler instead of jsonpb.AnyResolver. func NewJSONRequestParserWithUnmarshaler(in io.Reader, unmarshaler jsonpb.Unmarshaler) RequestParser { return &jsonRequestParser{ dec: json.NewDecoder(in), unmarshaler: unmarshaler, } } func (f *jsonRequestParser) Next(m proto.Message) error { var msg json.RawMessage if err := f.dec.Decode(&msg); err != nil { return err } f.requestCount++ return f.unmarshaler.Unmarshal(bytes.NewReader(msg), m) } func (f *jsonRequestParser) NumRequests() int { return f.requestCount } const ( textSeparatorChar = '\x1e' ) type textRequestParser struct { r *bufio.Reader err error requestCount int } // NewTextRequestParser returns a RequestParser that reads data in the protobuf // text format from the given reader. // // Input data that contains more than one message should include an ASCII // 'Record Separator' character (0x1E) between each message. // // Empty text is a valid text format and represents an empty message. So if the // given reader has no data, the returned parser will yield an empty message // for the first call to Next and then return io.EOF thereafter. This also means // that if the input data ends with a record separator, then a final empty // message will be parsed *after* the separator. func NewTextRequestParser(in io.Reader) RequestParser { return &textRequestParser{r: bufio.NewReader(in)} } func (f *textRequestParser) Next(m proto.Message) error { if f.err != nil { return f.err } var b []byte b, f.err = f.r.ReadBytes(textSeparatorChar) if f.err != nil && f.err != io.EOF { return f.err } // remove delimiter if len(b) > 0 && b[len(b)-1] == textSeparatorChar { b = b[:len(b)-1] } f.requestCount++ return proto.UnmarshalText(string(b), m) } func (f *textRequestParser) NumRequests() int { return f.requestCount } // Formatter translates messages into string representations. type Formatter func(proto.Message) (string, error) // NewJSONFormatter returns a formatter that returns JSON strings. The JSON will // include empty/default values (instead of just omitted them) if emitDefaults // is true. The given resolver is used to assist with encoding of // google.protobuf.Any messages. func NewJSONFormatter(emitDefaults bool, resolver jsonpb.AnyResolver) Formatter { marshaler := jsonpb.Marshaler{ EmitDefaults: emitDefaults, AnyResolver: resolver, } // Workaround for indentation issue in jsonpb with Any messages. // Bug was originally fixed in https://github.com/golang/protobuf/pull/834 // but later re-introduced before the module was deprecated and frozen. // If jsonpb is ever replaced with google.golang.org/protobuf/encoding/protojson // this workaround will no longer be needed. formatter := func(message proto.Message) (string, error) { output, err := marshaler.MarshalToString(message) if err != nil { return "", err } var buf bytes.Buffer if err := json.Indent(&buf, []byte(output), "", " "); err != nil { return "", err } return buf.String(), nil } return formatter } // NewTextFormatter returns a formatter that returns strings in the protobuf // text format. If includeSeparator is true then, when invoked to format // multiple messages, all messages after the first one will be prefixed with the // ASCII 'Record Separator' character (0x1E). func NewTextFormatter(includeSeparator bool) Formatter { tf := textFormatter{useSeparator: includeSeparator} return tf.format } type textFormatter struct { useSeparator bool numFormatted int } var protoTextMarshaler = proto.TextMarshaler{ExpandAny: true} func (tf *textFormatter) format(m proto.Message) (string, error) { var buf bytes.Buffer if tf.useSeparator && tf.numFormatted > 0 { if err := buf.WriteByte(textSeparatorChar); err != nil { return "", err } } // If message implements MarshalText method (such as a *dynamic.Message), // it won't get details about whether or not to format to text compactly // or with indentation. So first see if the message also implements a // MarshalTextIndent method and use that instead if available. type indentMarshaler interface { MarshalTextIndent() ([]byte, error) } if indenter, ok := m.(indentMarshaler); ok { b, err := indenter.MarshalTextIndent() if err != nil { return "", err } if _, err := buf.Write(b); err != nil { return "", err } } else if err := protoTextMarshaler.Marshal(&buf, m); err != nil { return "", err } // no trailing newline needed str := buf.String() if len(str) > 0 && str[len(str)-1] == '\n' { str = str[:len(str)-1] } tf.numFormatted++ return str, nil } // Format of request data. The allowed values are 'json' or 'text'. type Format string const ( // FormatJSON specifies input data in JSON format. Multiple request values // may be concatenated (messages with a JSON representation other than // object must be separated by whitespace, such as a newline) FormatJSON = Format("json") // FormatText specifies input data must be in the protobuf text format. // Multiple request values must be separated by the "record separator" // ASCII character: 0x1E. The stream should not end in a record separator. // If it does, it will be interpreted as a final, blank message after the // separator. FormatText = Format("text") ) // AnyResolverFromDescriptorSource returns an AnyResolver that will search for // types using the given descriptor source. func AnyResolverFromDescriptorSource(source DescriptorSource) jsonpb.AnyResolver { return &anyResolver{source: source} } // AnyResolverFromDescriptorSourceWithFallback returns an AnyResolver that will // search for types using the given descriptor source and then fallback to a // special message if the type is not found. The fallback type will render to // JSON with a "@type" property, just like an Any message, but also with a // custom "@value" property that includes the binary encoded payload. func AnyResolverFromDescriptorSourceWithFallback(source DescriptorSource) jsonpb.AnyResolver { res := anyResolver{source: source} return &anyResolverWithFallback{AnyResolver: &res} } type anyResolver struct { source DescriptorSource er dynamic.ExtensionRegistry mu sync.RWMutex mf *dynamic.MessageFactory resolved map[string]func() proto.Message } func (r *anyResolver) Resolve(typeUrl string) (proto.Message, error) { mname := typeUrl if slash := strings.LastIndex(mname, "/"); slash >= 0 { mname = mname[slash+1:] } r.mu.RLock() factory := r.resolved[mname] r.mu.RUnlock() // already resolved? if factory != nil { return factory(), nil } r.mu.Lock() defer r.mu.Unlock() // double-check, in case we were racing with another goroutine // that resolved this one factory = r.resolved[mname] if factory != nil { return factory(), nil } // use descriptor source to resolve message type d, err := r.source.FindSymbol(mname) if err != nil { return nil, err } md, ok := d.(*desc.MessageDescriptor) if !ok { return nil, fmt.Errorf("unknown message: %s", typeUrl) } // populate any extensions for this message, too (if there are any) if exts, err := r.source.AllExtensionsForType(mname); err == nil { if err := r.er.AddExtension(exts...); err != nil { return nil, err } } if r.mf == nil { r.mf = dynamic.NewMessageFactoryWithExtensionRegistry(&r.er) } factory = func() proto.Message { return r.mf.NewMessage(md) } if r.resolved == nil { r.resolved = map[string]func() proto.Message{} } r.resolved[mname] = factory return factory(), nil } // anyResolverWithFallback can provide a fallback value for unknown // messages that will format itself to JSON using an "@value" field // that has the base64-encoded data for the unknown message value. type anyResolverWithFallback struct { jsonpb.AnyResolver } func (r anyResolverWithFallback) Resolve(typeUrl string) (proto.Message, error) { msg, err := r.AnyResolver.Resolve(typeUrl) if err == nil { return msg, err } // Try "default" resolution logic. This mirrors the default behavior // of jsonpb, which checks to see if the given message name is registered // in the proto package. mname := typeUrl if slash := strings.LastIndex(mname, "/"); slash >= 0 { mname = mname[slash+1:] } //lint:ignore SA1019 new non-deprecated API requires other code changes; deferring... mt := proto.MessageType(mname) if mt != nil { return reflect.New(mt.Elem()).Interface().(proto.Message), nil } // finally, fallback to a special placeholder that can marshal itself // to JSON using a special "@value" property to show base64-encoded // data for the embedded message return &unknownAny{TypeUrl: typeUrl, Error: fmt.Sprintf("%s is not recognized; see @value for raw binary message data", mname)}, nil } type unknownAny struct { TypeUrl string `json:"@type"` Error string `json:"@error"` Value string `json:"@value"` } func (a *unknownAny) MarshalJSONPB(jsm *jsonpb.Marshaler) ([]byte, error) { if jsm.Indent != "" { return json.MarshalIndent(a, "", jsm.Indent) } return json.Marshal(a) } func (a *unknownAny) Unmarshal(b []byte) error { a.Value = base64.StdEncoding.EncodeToString(b) return nil } func (a *unknownAny) Reset() { a.Value = "" } func (a *unknownAny) String() string { b, err := a.MarshalJSONPB(&jsonpb.Marshaler{}) if err != nil { return fmt.Sprintf("ERROR: %v", err.Error()) } return string(b) } func (a *unknownAny) ProtoMessage() { } var _ proto.Message = (*unknownAny)(nil) // FormatOptions is a set of flags that are passed to a JSON or text formatter. type FormatOptions struct { // EmitJSONDefaultFields flag, when true, includes empty/default values in the output. // FormatJSON only flag. EmitJSONDefaultFields bool // AllowUnknownFields is an option for the parser. When true, // it accepts input which includes unknown fields. These unknown fields // are skipped instead of returning an error. // FormatJSON only flag. AllowUnknownFields bool // IncludeTextSeparator is true then, when invoked to format multiple messages, // all messages after the first one will be prefixed with the // ASCII 'Record Separator' character (0x1E). // It might be useful when the output is piped to another grpcurl process. // FormatText only flag. IncludeTextSeparator bool } // RequestParserAndFormatter returns a request parser and formatter for the // given format. The given descriptor source may be used for parsing message // data (if needed by the format). // It accepts a set of options. The field EmitJSONDefaultFields and IncludeTextSeparator // are options for JSON and protobuf text formats, respectively. The AllowUnknownFields field // is a JSON-only format flag. // Requests will be parsed from the given in. func RequestParserAndFormatter(format Format, descSource DescriptorSource, in io.Reader, opts FormatOptions) (RequestParser, Formatter, error) { switch format { case FormatJSON: resolver := AnyResolverFromDescriptorSource(descSource) unmarshaler := jsonpb.Unmarshaler{AnyResolver: resolver, AllowUnknownFields: opts.AllowUnknownFields} return NewJSONRequestParserWithUnmarshaler(in, unmarshaler), NewJSONFormatter(opts.EmitJSONDefaultFields, anyResolverWithFallback{AnyResolver: resolver}), nil case FormatText: return NewTextRequestParser(in), NewTextFormatter(opts.IncludeTextSeparator), nil default: return nil, nil, fmt.Errorf("unknown format: %s", format) } } // RequestParserAndFormatterFor returns a request parser and formatter for the // given format. The given descriptor source may be used for parsing message // data (if needed by the format). The flags emitJSONDefaultFields and // includeTextSeparator are options for JSON and protobuf text formats, // respectively. Requests will be parsed from the given in. // This function is deprecated. Please use RequestParserAndFormatter instead. // DEPRECATED func RequestParserAndFormatterFor(format Format, descSource DescriptorSource, emitJSONDefaultFields, includeTextSeparator bool, in io.Reader) (RequestParser, Formatter, error) { return RequestParserAndFormatter(format, descSource, in, FormatOptions{ EmitJSONDefaultFields: emitJSONDefaultFields, IncludeTextSeparator: includeTextSeparator, }) } // DefaultEventHandler logs events to a writer. This is not thread-safe, but is // safe for use with InvokeRPC as long as NumResponses and Status are not read // until the call to InvokeRPC completes. type DefaultEventHandler struct { Out io.Writer Formatter Formatter // 0 = default // 1 = verbose // 2 = very verbose VerbosityLevel int // NumResponses is the number of responses that have been received. NumResponses int // Status is the status that was received at the end of an RPC. It is // nil if the RPC is still in progress. Status *status.Status } // NewDefaultEventHandler returns an InvocationEventHandler that logs events to // the given output. If verbose is true, all events are logged. Otherwise, only // response messages are logged. // // Deprecated: NewDefaultEventHandler exists for compatibility. // It doesn't allow fine control over the `VerbosityLevel` // and provides only 0 and 1 options (which corresponds to the `verbose` argument). // Use DefaultEventHandler{} initializer directly. func NewDefaultEventHandler(out io.Writer, descSource DescriptorSource, formatter Formatter, verbose bool) *DefaultEventHandler { verbosityLevel := 0 if verbose { verbosityLevel = 1 } return &DefaultEventHandler{ Out: out, Formatter: formatter, VerbosityLevel: verbosityLevel, } } var _ InvocationEventHandler = (*DefaultEventHandler)(nil) func (h *DefaultEventHandler) OnResolveMethod(md *desc.MethodDescriptor) { if h.VerbosityLevel > 0 { txt, err := GetDescriptorText(md, nil) if err == nil { fmt.Fprintf(h.Out, "\nResolved method descriptor:\n%s\n", txt) } } } func (h *DefaultEventHandler) OnSendHeaders(md metadata.MD) { if h.VerbosityLevel > 0 { fmt.Fprintf(h.Out, "\nRequest metadata to send:\n%s\n", MetadataToString(md)) } } func (h *DefaultEventHandler) OnReceiveHeaders(md metadata.MD) { if h.VerbosityLevel > 0 { fmt.Fprintf(h.Out, "\nResponse headers received:\n%s\n", MetadataToString(md)) } } func (h *DefaultEventHandler) OnReceiveResponse(resp proto.Message) { h.NumResponses++ if h.VerbosityLevel > 1 { fmt.Fprintf(h.Out, "\nEstimated response size: %d bytes\n", proto.Size(resp)) } if h.VerbosityLevel > 0 { fmt.Fprint(h.Out, "\nResponse contents:\n") } if respStr, err := h.Formatter(resp); err != nil { fmt.Fprintf(h.Out, "Failed to format response message %d: %v\n", h.NumResponses, err) } else { fmt.Fprintln(h.Out, respStr) } } func (h *DefaultEventHandler) OnReceiveTrailers(stat *status.Status, md metadata.MD) { h.Status = stat if h.VerbosityLevel > 0 { fmt.Fprintf(h.Out, "\nResponse trailers received:\n%s\n", MetadataToString(md)) } } // PrintStatus prints details about the given status to the given writer. The given // formatter is used to print any detail messages that may be included in the status. // If the given status has a code of OK, "OK" is printed and that is all. Otherwise, // "ERROR:" is printed along with a line showing the code, one showing the message // string, and each detail message if any are present. The detail messages will be // printed as proto text format or JSON, depending on the given formatter. func PrintStatus(w io.Writer, stat *status.Status, formatter Formatter) { if stat.Code() == codes.OK { fmt.Fprintln(w, "OK") return } fmt.Fprintf(w, "ERROR:\n Code: %s\n Message: %s\n", stat.Code().String(), stat.Message()) statpb := stat.Proto() if len(statpb.Details) > 0 { fmt.Fprintf(w, " Details:\n") for i, det := range statpb.Details { prefix := fmt.Sprintf(" %d)", i+1) fmt.Fprintf(w, "%s\t", prefix) prefix = strings.Repeat(" ", len(prefix)) + "\t" output, err := formatter(det) if err != nil { fmt.Fprintf(w, "Error parsing detail message: %v\n", err) } else { lines := strings.Split(output, "\n") for i, line := range lines { if i == 0 { // first line is already indented fmt.Fprintf(w, "%s\n", line) } else { fmt.Fprintf(w, "%s%s\n", prefix, line) } } } } } } ================================================ FILE: format_test.go ================================================ package grpcurl import ( "bytes" "fmt" "io" "strings" "testing" "github.com/golang/protobuf/jsonpb" //lint:ignore SA1019 we have to import these because some of their types appear in exported API "github.com/golang/protobuf/proto" //lint:ignore SA1019 same as above "github.com/jhump/protoreflect/desc" //lint:ignore SA1019 same as above "google.golang.org/grpc/metadata" "google.golang.org/protobuf/types/known/structpb" ) func TestRequestParser(t *testing.T) { source, err := DescriptorSourceFromProtoSets("internal/testing/example.protoset") if err != nil { t.Fatalf("failed to create descriptor source: %v", err) } msg, err := makeProto() if err != nil { t.Fatalf("failed to create message: %v", err) } testCases := []struct { format Format input string expectedOutput []proto.Message }{ { format: FormatJSON, input: "", }, { format: FormatJSON, input: messageAsJSON, expectedOutput: []proto.Message{msg}, }, { format: FormatJSON, input: messageAsJSON + messageAsJSON + messageAsJSON, expectedOutput: []proto.Message{msg, msg, msg}, }, { // unlike JSON, empty input yields one empty message (vs. zero messages) format: FormatText, input: "", expectedOutput: []proto.Message{&structpb.Value{}}, }, { format: FormatText, input: messageAsText, expectedOutput: []proto.Message{msg}, }, { format: FormatText, input: messageAsText + string(textSeparatorChar), expectedOutput: []proto.Message{msg, &structpb.Value{}}, }, { format: FormatText, input: messageAsText + string(textSeparatorChar) + messageAsText + string(textSeparatorChar) + messageAsText, expectedOutput: []proto.Message{msg, msg, msg}, }, } for i, tc := range testCases { name := fmt.Sprintf("#%d, %s, %d message(s)", i+1, tc.format, len(tc.expectedOutput)) rf, _, err := RequestParserAndFormatter(tc.format, source, strings.NewReader(tc.input), FormatOptions{}) if err != nil { t.Errorf("Failed to create parser and formatter: %v", err) continue } numReqs := 0 for { var req structpb.Value err := rf.Next(&req) if err == io.EOF { break } else if err != nil { t.Errorf("%s, msg %d: unexpected error: %v", name, numReqs, err) } if !proto.Equal(&req, tc.expectedOutput[numReqs]) { t.Errorf("%s, msg %d: incorrect message;\nexpecting:\n%v\ngot:\n%v", name, numReqs, tc.expectedOutput[numReqs], &req) } numReqs++ } if rf.NumRequests() != numReqs { t.Errorf("%s: factory reported wrong number of requests: expecting %d, got %d", name, numReqs, rf.NumRequests()) } } } // Handler prints response data (and headers/trailers in verbose mode). // This verifies that we get the right output in both JSON and proto text modes. func TestHandler(t *testing.T) { source, err := DescriptorSourceFromProtoSets("internal/testing/example.protoset") if err != nil { t.Fatalf("failed to create descriptor source: %v", err) } d, err := source.FindSymbol("TestService.GetFiles") if err != nil { t.Fatalf("failed to find method 'TestService.GetFiles': %v", err) } md, ok := d.(*desc.MethodDescriptor) if !ok { t.Fatalf("wrong kind of descriptor found: %T", d) } reqHeaders := metadata.Pairs("foo", "123", "bar", "456") respHeaders := metadata.Pairs("foo", "abc", "bar", "def", "baz", "xyz") respTrailers := metadata.Pairs("a", "1", "b", "2", "c", "3") rsp, err := makeProto() if err != nil { t.Fatalf("failed to create response message: %v", err) } for _, format := range []Format{FormatJSON, FormatText} { for _, numMessages := range []int{1, 3} { for verbosityLevel := 0; verbosityLevel <= 2; verbosityLevel++ { name := fmt.Sprintf("%s, %d message(s)", format, numMessages) if verbosityLevel > 0 { name += fmt.Sprintf(", verbosityLevel=%d", verbosityLevel) } verbose := verbosityLevel > 0 _, formatter, err := RequestParserAndFormatter(format, source, nil, FormatOptions{IncludeTextSeparator: !verbose}) if err != nil { t.Errorf("Failed to create parser and formatter: %v", err) continue } var buf bytes.Buffer h := &DefaultEventHandler{ Out: &buf, Formatter: formatter, VerbosityLevel: verbosityLevel, } h.OnResolveMethod(md) h.OnSendHeaders(reqHeaders) h.OnReceiveHeaders(respHeaders) for i := 0; i < numMessages; i++ { h.OnReceiveResponse(rsp) } h.OnReceiveTrailers(nil, respTrailers) expectedOutput := "" if verbose { expectedOutput += verbosePrefix } for i := 0; i < numMessages; i++ { if verbosityLevel > 1 { expectedOutput += verboseResponseSize } if verbose { expectedOutput += verboseResponseHeader } if format == "json" { expectedOutput += messageAsJSON } else { if i > 0 && !verbose { expectedOutput += string(textSeparatorChar) } expectedOutput += messageAsText } } if verbose { expectedOutput += verboseSuffix } out := buf.String() if !compare(out, expectedOutput) { t.Errorf("%s: Incorrect output. Expected:\n%s\nGot:\n%s", name, expectedOutput, out) } } } } } // compare checks that actual and expected are equal, returning true if so. // A simple equality check (==) does not suffice because jsonpb formats // structpb.Value strangely. So if that formatting gets fixed, we don't // want this test in grpcurl to suddenly start failing. So we check each // line and compare the lines after stripping whitespace (which removes // the jsonpb format anomalies). func compare(actual, expected string) bool { actualLines := strings.Split(actual, "\n") expectedLines := strings.Split(expected, "\n") if len(actualLines) != len(expectedLines) { return false } for i := 0; i < len(actualLines); i++ { if strings.TrimSpace(actualLines[i]) != strings.TrimSpace(expectedLines[i]) { return false } } return true } func makeProto() (proto.Message, error) { var rsp structpb.Value err := jsonpb.UnmarshalString(`{ "foo": ["abc", "def", "ghi"], "bar": { "a": 1, "b": 2 }, "baz": true, "null": null }`, &rsp) if err != nil { return nil, err } return &rsp, nil } var ( verbosePrefix = ` Resolved method descriptor: rpc GetFiles ( .TestRequest ) returns ( .TestResponse ); Request metadata to send: bar: 456 foo: 123 Response headers received: bar: def baz: xyz foo: abc ` verboseSuffix = ` Response trailers received: a: 1 b: 2 c: 3 ` verboseResponseSize = ` Estimated response size: 100 bytes ` verboseResponseHeader = ` Response contents: ` messageAsJSON = `{ "bar": { "a": 1, "b": 2 }, "baz": true, "foo": [ "abc", "def", "ghi" ], "null": null } ` messageAsText = `struct_value: < fields: < key: "bar" value: < struct_value: < fields: < key: "a" value: < number_value: 1 > > fields: < key: "b" value: < number_value: 2 > > > > > fields: < key: "baz" value: < bool_value: true > > fields: < key: "foo" value: < list_value: < values: < string_value: "abc" > values: < string_value: "def" > values: < string_value: "ghi" > > > > fields: < key: "null" value: < null_value: NULL_VALUE > > > ` ) ================================================ FILE: go.mod ================================================ module github.com/fullstorydev/grpcurl go 1.24.0 toolchain go1.24.1 require ( github.com/golang/protobuf v1.5.4 github.com/jhump/protoreflect v1.18.0 google.golang.org/grpc v1.66.2 google.golang.org/protobuf v1.36.11 ) require ( cel.dev/expr v0.15.0 // indirect cloud.google.com/go/compute/metadata v0.3.0 // indirect github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b // indirect github.com/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155 // indirect github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect github.com/jhump/protoreflect/v2 v2.0.0-beta.1 // indirect github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect golang.org/x/net v0.38.0 // indirect golang.org/x/oauth2 v0.27.0 // indirect golang.org/x/sync v0.12.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/text v0.23.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect ) ================================================ FILE: go.sum ================================================ cel.dev/expr v0.15.0 h1:O1jzfJCQBfL5BFoYktaxwIhuttaQPsVWerH9/EEKx0w= cel.dev/expr v0.15.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg= cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b h1:ga8SEFjZ60pxLcmhnThWgvH2wg8376yUJmPhEH4H3kw= github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155 h1:IgJPqnrlY2Mr4pYB6oaMKvFvwJ9H+X6CCY5x1vCTcpc= github.com/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155/go.mod h1:5Wkq+JduFtdAXihLmeTJf+tRYIT4KBc2vPXDhwVo1pA= github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jhump/protoreflect v1.18.0 h1:TOz0MSR/0JOZ5kECB/0ufGnC2jdsgZ123Rd/k4Z5/2w= github.com/jhump/protoreflect v1.18.0/go.mod h1:ezWcltJIVF4zYdIFM+D/sHV4Oh5LNU08ORzCGfwvTz8= github.com/jhump/protoreflect/v2 v2.0.0-beta.1 h1:Dw1rslK/VotaUGYsv53XVWITr+5RCPXfvvlGrM/+B6w= github.com/jhump/protoreflect/v2 v2.0.0-beta.1/go.mod h1:D9LBEowZyv8/iSu97FU2zmXG3JxVTmNw21mu63niFzU= github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 h1:KPpdlQLZcHfTMQRi6bFQ7ogNO0ltFT4PmtwTLW4W+14= github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU= google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117/go.mod h1:OimBR/bc1wPO9iV4NC2bpyjy3VnAwZh5EBPQdtaE5oo= google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU= google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo= google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: grpcurl.go ================================================ // Package grpcurl provides the core functionality exposed by the grpcurl command, for // dynamically connecting to a server, using the reflection service to inspect the server, // and invoking RPCs. The grpcurl command-line tool constructs a DescriptorSource, based // on the command-line parameters, and supplies an InvocationEventHandler to supply request // data (which can come from command-line args or the process's stdin) and to log the // events (to the process's stdout). package grpcurl import ( "bytes" "context" "crypto/tls" "crypto/x509" "encoding/base64" "errors" "fmt" "net" "os" "regexp" "slices" "sort" "strings" "github.com/golang/protobuf/proto" //lint:ignore SA1019 we have to import these because some of their types appear in exported API "github.com/jhump/protoreflect/desc" //lint:ignore SA1019 same as above "github.com/jhump/protoreflect/desc/protoprint" "github.com/jhump/protoreflect/dynamic" //lint:ignore SA1019 same as above "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" xdsCredentials "google.golang.org/grpc/credentials/xds" _ "google.golang.org/grpc/health" // import grpc/health to enable transparent client side checking "google.golang.org/grpc/metadata" protov2 "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/descriptorpb" "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/structpb" ) // ListServices uses the given descriptor source to return a sorted list of fully-qualified // service names. func ListServices(source DescriptorSource) ([]string, error) { svcs, err := source.ListServices() if err != nil { return nil, err } sort.Strings(svcs) return svcs, nil } type sourceWithFiles interface { GetAllFiles() ([]*desc.FileDescriptor, error) } var _ sourceWithFiles = (*fileSource)(nil) // GetAllFiles uses the given descriptor source to return a list of file descriptors. func GetAllFiles(source DescriptorSource) ([]*desc.FileDescriptor, error) { var files []*desc.FileDescriptor srcFiles, ok := source.(sourceWithFiles) // If an error occurs, we still try to load as many files as we can, so that // caller can decide whether to ignore error or not. var firstError error if ok { files, firstError = srcFiles.GetAllFiles() } else { // Source does not implement GetAllFiles method, so use ListServices // and grab files from there. svcNames, err := source.ListServices() if err != nil { firstError = err } else { allFiles := map[string]*desc.FileDescriptor{} for _, name := range svcNames { d, err := source.FindSymbol(name) if err != nil { if firstError == nil { firstError = err } } else { addAllFilesToSet(d.GetFile(), allFiles) } } files = make([]*desc.FileDescriptor, len(allFiles)) i := 0 for _, fd := range allFiles { files[i] = fd i++ } } } sort.Sort(filesByName(files)) return files, firstError } type filesByName []*desc.FileDescriptor func (f filesByName) Len() int { return len(f) } func (f filesByName) Less(i, j int) bool { return f[i].GetName() < f[j].GetName() } func (f filesByName) Swap(i, j int) { f[i], f[j] = f[j], f[i] } func addAllFilesToSet(fd *desc.FileDescriptor, all map[string]*desc.FileDescriptor) { if _, ok := all[fd.GetName()]; ok { // already added return } all[fd.GetName()] = fd for _, dep := range fd.GetDependencies() { addAllFilesToSet(dep, all) } } // ListMethods uses the given descriptor source to return a sorted list of method names // for the specified fully-qualified service name. func ListMethods(source DescriptorSource, serviceName string) ([]string, error) { dsc, err := source.FindSymbol(serviceName) if err != nil { return nil, err } if sd, ok := dsc.(*desc.ServiceDescriptor); !ok { return nil, notFound("Service", serviceName) } else { methods := make([]string, 0, len(sd.GetMethods())) for _, method := range sd.GetMethods() { methods = append(methods, method.GetFullyQualifiedName()) } sort.Strings(methods) return methods, nil } } // MetadataFromHeaders converts a list of header strings (each string in // "Header-Name: Header-Value" form) into metadata. If a string has a header // name without a value (e.g. does not contain a colon), the value is assumed // to be blank. Binary headers (those whose names end in "-bin") should be // base64-encoded. But if they cannot be base64-decoded, they will be assumed to // be in raw form and used as is. func MetadataFromHeaders(headers []string) metadata.MD { md := make(metadata.MD) for _, part := range headers { if part != "" { pieces := strings.SplitN(part, ":", 2) if len(pieces) == 1 { pieces = append(pieces, "") // if no value was specified, just make it "" (maybe the header value doesn't matter) } headerName := strings.ToLower(strings.TrimSpace(pieces[0])) val := strings.TrimSpace(pieces[1]) if strings.HasSuffix(headerName, "-bin") { if v, err := decode(val); err == nil { val = v } } md[headerName] = append(md[headerName], val) } } return md } var envVarRegex = regexp.MustCompile(`\${\w+}`) // ExpandHeaders expands environment variables contained in the header string. // If no corresponding environment variable is found an error is returned. // TODO: Add escaping for `${` func ExpandHeaders(headers []string) ([]string, error) { expandedHeaders := make([]string, len(headers)) for idx, header := range headers { if header == "" { continue } results := envVarRegex.FindAllString(header, -1) if len(results) == 0 { expandedHeaders[idx] = headers[idx] continue } expandedHeader := header for _, result := range results { envVarName := result[2 : len(result)-1] // strip leading `${` and trailing `}` envVarValue, ok := os.LookupEnv(envVarName) if !ok { return nil, fmt.Errorf("header %q refers to missing environment variable %q", header, envVarName) } expandedHeader = strings.Replace(expandedHeader, result, envVarValue, -1) } expandedHeaders[idx] = expandedHeader } return expandedHeaders, nil } var base64Codecs = []*base64.Encoding{base64.StdEncoding, base64.URLEncoding, base64.RawStdEncoding, base64.RawURLEncoding} func decode(val string) (string, error) { var firstErr error var b []byte // we are lenient and can accept any of the flavors of base64 encoding for _, d := range base64Codecs { var err error b, err = d.DecodeString(val) if err != nil { if firstErr == nil { firstErr = err } continue } return string(b), nil } return "", firstErr } // MetadataToString returns a string representation of the given metadata, for // displaying to users. func MetadataToString(md metadata.MD) string { if len(md) == 0 { return "(empty)" } keys := make([]string, 0, len(md)) for k := range md { keys = append(keys, k) } sort.Strings(keys) var b bytes.Buffer first := true for _, k := range keys { vs := md[k] for _, v := range vs { if first { first = false } else { b.WriteString("\n") } b.WriteString(k) b.WriteString(": ") if strings.HasSuffix(k, "-bin") { v = base64.StdEncoding.EncodeToString([]byte(v)) } b.WriteString(v) } } return b.String() } var printer = &protoprint.Printer{ Compact: true, OmitComments: protoprint.CommentsNonDoc, SortElements: true, ForceFullyQualifiedNames: true, } // GetDescriptorText returns a string representation of the given descriptor. // This returns a snippet of proto source that describes the given element. func GetDescriptorText(dsc desc.Descriptor, _ DescriptorSource) (string, error) { // Note: DescriptorSource is not used, but remains an argument for backwards // compatibility with previous implementation. txt, err := printer.PrintProtoToString(dsc) if err != nil { return "", err } // callers don't expect trailing newlines if txt[len(txt)-1] == '\n' { txt = txt[:len(txt)-1] } return txt, nil } // EnsureExtensions uses the given descriptor source to download extensions for // the given message. It returns a copy of the given message, but as a dynamic // message that knows about all extensions known to the given descriptor source. func EnsureExtensions(source DescriptorSource, msg proto.Message) proto.Message { // load any server extensions so we can properly describe custom options dsc, err := desc.LoadMessageDescriptorForMessage(msg) if err != nil { return msg } var ext dynamic.ExtensionRegistry if err = fetchAllExtensions(source, &ext, dsc, map[string]bool{}); err != nil { return msg } // convert message into dynamic message that knows about applicable extensions // (that way we can show meaningful info for custom options instead of printing as unknown) msgFactory := dynamic.NewMessageFactoryWithExtensionRegistry(&ext) dm, err := fullyConvertToDynamic(msgFactory, msg) if err != nil { return msg } return dm } // fetchAllExtensions recursively fetches from the server extensions for the given message type as well as // for all message types of nested fields. The extensions are added to the given dynamic registry of extensions // so that all server-known extensions can be correctly parsed by grpcurl. func fetchAllExtensions(source DescriptorSource, ext *dynamic.ExtensionRegistry, md *desc.MessageDescriptor, alreadyFetched map[string]bool) error { msgTypeName := md.GetFullyQualifiedName() if alreadyFetched[msgTypeName] { return nil } alreadyFetched[msgTypeName] = true if len(md.GetExtensionRanges()) > 0 { fds, err := source.AllExtensionsForType(msgTypeName) if err != nil { return fmt.Errorf("failed to query for extensions of type %s: %v", msgTypeName, err) } for _, fd := range fds { if err := ext.AddExtension(fd); err != nil { return fmt.Errorf("could not register extension %s of type %s: %v", fd.GetFullyQualifiedName(), msgTypeName, err) } } } // recursively fetch extensions for the types of any message fields for _, fd := range md.GetFields() { if fd.GetMessageType() != nil { err := fetchAllExtensions(source, ext, fd.GetMessageType(), alreadyFetched) if err != nil { return err } } } return nil } // fullyConvertToDynamic attempts to convert the given message to a dynamic message as well // as any nested messages it may contain as field values. If the given message factory has // extensions registered that were not known when the given message was parsed, this effectively // allows re-parsing to identify those extensions. func fullyConvertToDynamic(msgFact *dynamic.MessageFactory, msg proto.Message) (proto.Message, error) { if _, ok := msg.(*dynamic.Message); ok { return msg, nil // already a dynamic message } md, err := desc.LoadMessageDescriptorForMessage(msg) if err != nil { return nil, err } newMsg := msgFact.NewMessage(md) dm, ok := newMsg.(*dynamic.Message) if !ok { // if message factory didn't produce a dynamic message, then we should leave msg as is return msg, nil } if err := dm.ConvertFrom(msg); err != nil { return nil, err } // recursively convert all field values, too for _, fd := range md.GetFields() { if fd.IsMap() { if fd.GetMapValueType().GetMessageType() != nil { m := dm.GetField(fd).(map[interface{}]interface{}) for k, v := range m { // keys can't be nested messages; so we only need to recurse through map values, not keys newVal, err := fullyConvertToDynamic(msgFact, v.(proto.Message)) if err != nil { return nil, err } dm.PutMapField(fd, k, newVal) } } } else if fd.IsRepeated() { if fd.GetMessageType() != nil { s := dm.GetField(fd).([]interface{}) for i, e := range s { newVal, err := fullyConvertToDynamic(msgFact, e.(proto.Message)) if err != nil { return nil, err } dm.SetRepeatedField(fd, i, newVal) } } } else { if fd.GetMessageType() != nil { v := dm.GetField(fd) newVal, err := fullyConvertToDynamic(msgFact, v.(proto.Message)) if err != nil { return nil, err } dm.SetField(fd, newVal) } } } return dm, nil } // MakeTemplate returns a message instance for the given descriptor that is a // suitable template for creating an instance of that message in JSON. In // particular, it ensures that any repeated fields (which include map fields) // are not empty, so they will render with a single element (to show the types // and optionally nested fields). It also ensures that nested messages are not // nil by setting them to a message that is also fleshed out as a template // message. func MakeTemplate(md *desc.MessageDescriptor) proto.Message { return makeTemplate(md, nil) } func makeTemplate(md *desc.MessageDescriptor, path []*desc.MessageDescriptor) proto.Message { switch md.GetFullyQualifiedName() { case "google.protobuf.Any": // empty type URL is not allowed by JSON representation // so we must give it a dummy type var anyVal anypb.Any _ = anypb.MarshalFrom(&anyVal, &emptypb.Empty{}, protov2.MarshalOptions{}) return &anyVal case "google.protobuf.Value": // unset kind is not allowed by JSON representation // so we must give it something return &structpb.Value{ Kind: &structpb.Value_StructValue{StructValue: &structpb.Struct{ Fields: map[string]*structpb.Value{ "google.protobuf.Value": {Kind: &structpb.Value_StringValue{ StringValue: "supports arbitrary JSON", }}, }, }}, } case "google.protobuf.ListValue": return &structpb.ListValue{ Values: []*structpb.Value{ { Kind: &structpb.Value_StructValue{StructValue: &structpb.Struct{ Fields: map[string]*structpb.Value{ "google.protobuf.ListValue": {Kind: &structpb.Value_StringValue{ StringValue: "is an array of arbitrary JSON values", }}, }, }}, }, }, } case "google.protobuf.Struct": return &structpb.Struct{ Fields: map[string]*structpb.Value{ "google.protobuf.Struct": {Kind: &structpb.Value_StringValue{ StringValue: "supports arbitrary JSON objects", }}, }, } } dm := dynamic.NewMessage(md) // if the message is a recursive structure, we don't want to blow the stack if slices.Contains(path, md) { // already visited this type; avoid infinite recursion return dm } path = append(path, dm.GetMessageDescriptor()) // for repeated fields, add a single element with default value // and for message fields, add a message with all default fields // that also has non-nil message and non-empty repeated fields for _, fd := range dm.GetMessageDescriptor().GetFields() { if fd.IsRepeated() { switch fd.GetType() { case descriptorpb.FieldDescriptorProto_TYPE_FIXED32, descriptorpb.FieldDescriptorProto_TYPE_UINT32: dm.AddRepeatedField(fd, uint32(0)) case descriptorpb.FieldDescriptorProto_TYPE_SFIXED32, descriptorpb.FieldDescriptorProto_TYPE_SINT32, descriptorpb.FieldDescriptorProto_TYPE_INT32, descriptorpb.FieldDescriptorProto_TYPE_ENUM: dm.AddRepeatedField(fd, int32(0)) case descriptorpb.FieldDescriptorProto_TYPE_FIXED64, descriptorpb.FieldDescriptorProto_TYPE_UINT64: dm.AddRepeatedField(fd, uint64(0)) case descriptorpb.FieldDescriptorProto_TYPE_SFIXED64, descriptorpb.FieldDescriptorProto_TYPE_SINT64, descriptorpb.FieldDescriptorProto_TYPE_INT64: dm.AddRepeatedField(fd, int64(0)) case descriptorpb.FieldDescriptorProto_TYPE_STRING: dm.AddRepeatedField(fd, "") case descriptorpb.FieldDescriptorProto_TYPE_BYTES: dm.AddRepeatedField(fd, []byte{}) case descriptorpb.FieldDescriptorProto_TYPE_BOOL: dm.AddRepeatedField(fd, false) case descriptorpb.FieldDescriptorProto_TYPE_FLOAT: dm.AddRepeatedField(fd, float32(0)) case descriptorpb.FieldDescriptorProto_TYPE_DOUBLE: dm.AddRepeatedField(fd, float64(0)) case descriptorpb.FieldDescriptorProto_TYPE_MESSAGE, descriptorpb.FieldDescriptorProto_TYPE_GROUP: dm.AddRepeatedField(fd, makeTemplate(fd.GetMessageType(), path)) } } else if fd.GetMessageType() != nil { dm.SetField(fd, makeTemplate(fd.GetMessageType(), path)) } } return dm } // ClientTransportCredentials is a helper function that constructs a TLS config with // the given properties (see ClientTLSConfig) and then constructs and returns gRPC // transport credentials using that config. // // Deprecated: Use grpcurl.ClientTLSConfig and credentials.NewTLS instead. func ClientTransportCredentials(insecureSkipVerify bool, cacertFile, clientCertFile, clientKeyFile string) (credentials.TransportCredentials, error) { tlsConf, err := ClientTLSConfig(insecureSkipVerify, cacertFile, clientCertFile, clientKeyFile) if err != nil { return nil, err } return credentials.NewTLS(tlsConf), nil } // ClientTLSConfig builds transport-layer config for a gRPC client using the // given properties. If cacertFile is blank, only standard trusted certs are used to // verify the server certs. If clientCertFile is blank, the client will not use a client // certificate. If clientCertFile is not blank then clientKeyFile must not be blank. func ClientTLSConfig(insecureSkipVerify bool, cacertFile, clientCertFile, clientKeyFile string) (*tls.Config, error) { var tlsConf tls.Config if clientCertFile != "" { // Load the client certificates from disk certificate, err := tls.LoadX509KeyPair(clientCertFile, clientKeyFile) if err != nil { return nil, fmt.Errorf("could not load client key pair: %v", err) } tlsConf.Certificates = []tls.Certificate{certificate} } if insecureSkipVerify { tlsConf.InsecureSkipVerify = true } else if cacertFile != "" { // Create a certificate pool from the certificate authority certPool := x509.NewCertPool() ca, err := os.ReadFile(cacertFile) if err != nil { return nil, fmt.Errorf("could not read ca certificate: %v", err) } // Append the certificates from the CA if ok := certPool.AppendCertsFromPEM(ca); !ok { return nil, errors.New("failed to append ca certs") } tlsConf.RootCAs = certPool } return &tlsConf, nil } // ServerTransportCredentials builds transport credentials for a gRPC server using the // given properties. If cacertFile is blank, the server will not request client certs // unless requireClientCerts is true. When requireClientCerts is false and cacertFile is // not blank, the server will verify client certs when presented, but will not require // client certs. The serverCertFile and serverKeyFile must both not be blank. func ServerTransportCredentials(cacertFile, serverCertFile, serverKeyFile string, requireClientCerts bool) (credentials.TransportCredentials, error) { var tlsConf tls.Config // TODO(jh): Remove this line once https://github.com/golang/go/issues/28779 is fixed // in Go tip. Until then, the recently merged TLS 1.3 support breaks the TLS tests. tlsConf.MaxVersion = tls.VersionTLS12 // Load the server certificates from disk certificate, err := tls.LoadX509KeyPair(serverCertFile, serverKeyFile) if err != nil { return nil, fmt.Errorf("could not load key pair: %v", err) } tlsConf.Certificates = []tls.Certificate{certificate} if cacertFile != "" { // Create a certificate pool from the certificate authority certPool := x509.NewCertPool() ca, err := os.ReadFile(cacertFile) if err != nil { return nil, fmt.Errorf("could not read ca certificate: %v", err) } // Append the certificates from the CA if ok := certPool.AppendCertsFromPEM(ca); !ok { return nil, errors.New("failed to append ca certs") } tlsConf.ClientCAs = certPool } if requireClientCerts { tlsConf.ClientAuth = tls.RequireAndVerifyClientCert } else if cacertFile != "" { tlsConf.ClientAuth = tls.VerifyClientCertIfGiven } else { tlsConf.ClientAuth = tls.NoClientCert } return credentials.NewTLS(&tlsConf), nil } // BlockingDial is a helper method to dial the given address, using optional TLS credentials, // and blocking until the returned connection is ready. If the given credentials are nil, the // connection will be insecure (plain-text). // The network parameter should be left empty in most cases when your address is a RFC 3986 // compliant URI. The resolver from grpc-go will resolve the correct network type. func BlockingDial(ctx context.Context, network, address string, creds credentials.TransportCredentials, opts ...grpc.DialOption) (*grpc.ClientConn, error) { if creds == nil { creds = insecure.NewCredentials() } var err error if strings.HasPrefix(address, "xds:///") { // The xds:/// prefix is used to signal to the gRPC client to use an xDS server to resolve the // target. The relevant credentials will be automatically pulled from the GRPC_XDS_BOOTSTRAP or // GRPC_XDS_BOOTSTRAP_CONFIG env vars. creds, err = xdsCredentials.NewClientCredentials(xdsCredentials.ClientOptions{FallbackCreds: creds}) if err != nil { return nil, err } } // grpc.Dial doesn't provide any information on permanent connection errors (like // TLS handshake failures). So in order to provide good error messages, we need a // custom dialer that can provide that info. That means we manage the TLS handshake. result := make(chan interface{}, 1) writeResult := func(res interface{}) { // non-blocking write: we only need the first result select { case result <- res: default: } } // custom credentials and dialer will notify on error via the // writeResult function creds = &errSignalingCreds{ TransportCredentials: creds, writeResult: writeResult, } switch network { case "": // no-op, use address as-is case "tcp": if strings.HasPrefix(address, "unix://") { return nil, fmt.Errorf("tcp network type cannot use unix address %s", address) } case "unix": if !strings.HasPrefix(address, "unix://") { // prepend unix:// to the address if it's not already there // this is to maintain backwards compatibility because the custom dialer is replaced by // the default dialer in grpc-go. // https://github.com/fullstorydev/grpcurl/pull/480 address = "unix://" + address } default: // custom dialer for other networks dialer := func(ctx context.Context, address string) (net.Conn, error) { conn, err := (&net.Dialer{}).DialContext(ctx, network, address) if err != nil { // capture the error so we can provide a better message writeResult(err) } return conn, err } opts = append([]grpc.DialOption{grpc.WithContextDialer(dialer)}, opts...) } // Even with grpc.FailOnNonTempDialError, this call will usually timeout in // the face of TLS handshake errors. So we can't rely on grpc.WithBlock() to // know when we're done. So we run it in a goroutine and then use result // channel to either get the connection or fail-fast. go func() { // We put grpc.FailOnNonTempDialError *before* the explicitly provided // options so that it could be overridden. opts = append([]grpc.DialOption{grpc.FailOnNonTempDialError(true)}, opts...) // But we don't want caller to be able to override these two, so we put // them *after* the explicitly provided options. opts = append(opts, grpc.WithBlock(), grpc.WithTransportCredentials(creds)) conn, err := grpc.DialContext(ctx, address, opts...) var res interface{} if err != nil { res = err } else { res = conn } writeResult(res) }() select { case res := <-result: if conn, ok := res.(*grpc.ClientConn); ok { return conn, nil } return nil, res.(error) case <-ctx.Done(): return nil, ctx.Err() } } // errSignalingCreds is a wrapper around a TransportCredentials value, but // it will use the writeResult function to notify on error. type errSignalingCreds struct { credentials.TransportCredentials writeResult func(res interface{}) } func (c *errSignalingCreds) ClientHandshake(ctx context.Context, addr string, rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) { conn, auth, err := c.TransportCredentials.ClientHandshake(ctx, addr, rawConn) if err != nil { c.writeResult(err) } return conn, auth, err } ================================================ FILE: grpcurl_test.go ================================================ package grpcurl_test import ( "context" "encoding/json" "fmt" "io" "net" "os" "reflect" "strings" "testing" "time" "github.com/golang/protobuf/jsonpb" //lint:ignore SA1019 we have to import these because some of their types appear in exported API "github.com/golang/protobuf/proto" //lint:ignore SA1019 same as above "github.com/jhump/protoreflect/desc" //lint:ignore SA1019 same as above "github.com/jhump/protoreflect/grpcreflect" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/metadata" "google.golang.org/grpc/reflection" "google.golang.org/grpc/status" . "github.com/fullstorydev/grpcurl" grpcurl_testing "github.com/fullstorydev/grpcurl/internal/testing" jsonpbtest "github.com/fullstorydev/grpcurl/internal/testing/jsonpb_test_proto" ) var ( sourceProtoset DescriptorSource sourceProtoFiles DescriptorSource ccNoReflect *grpc.ClientConn sourceReflect DescriptorSource ccReflect *grpc.ClientConn descSources []descSourceCase ) type descSourceCase struct { name string source DescriptorSource includeRefl bool } // NB: These tests intentionally use the deprecated InvokeRpc since that // calls the other (non-deprecated InvokeRPC). That allows the tests to // easily exercise both functions. func TestMain(m *testing.M) { var err error sourceProtoset, err = DescriptorSourceFromProtoSets("internal/testing/test.protoset") if err != nil { panic(err) } sourceProtoFiles, err = DescriptorSourceFromProtoFiles([]string{"internal/testing"}, "test.proto") if err != nil { panic(err) } // Create a server that includes the reflection service svrReflect := grpc.NewServer() grpcurl_testing.RegisterTestServiceServer(svrReflect, grpcurl_testing.TestServer{}) reflection.Register(svrReflect) var portReflect int if l, err := net.Listen("tcp", "127.0.0.1:0"); err != nil { panic(err) } else { portReflect = l.Addr().(*net.TCPAddr).Port go svrReflect.Serve(l) } defer svrReflect.Stop() // And a corresponding client ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if ccReflect, err = grpc.DialContext(ctx, fmt.Sprintf("127.0.0.1:%d", portReflect), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()); err != nil { panic(err) } defer ccReflect.Close() refClient := grpcreflect.NewClientAuto(context.Background(), ccReflect) defer refClient.Reset() sourceReflect = DescriptorSourceFromServer(context.Background(), refClient) // Also create a server that does *not* include the reflection service svrProtoset := grpc.NewServer() grpcurl_testing.RegisterTestServiceServer(svrProtoset, grpcurl_testing.TestServer{}) var portProtoset int if l, err := net.Listen("tcp", "127.0.0.1:0"); err != nil { panic(err) } else { portProtoset = l.Addr().(*net.TCPAddr).Port go svrProtoset.Serve(l) } defer svrProtoset.Stop() // And a corresponding client ctx, cancel = context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if ccNoReflect, err = grpc.DialContext(ctx, fmt.Sprintf("127.0.0.1:%d", portProtoset), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock()); err != nil { panic(err) } defer ccNoReflect.Close() descSources = []descSourceCase{ {"protoset", sourceProtoset, false}, {"proto", sourceProtoFiles, false}, {"reflect", sourceReflect, true}, } os.Exit(m.Run()) } func TestServerDoesNotSupportReflection(t *testing.T) { refClient := grpcreflect.NewClientAuto(context.Background(), ccNoReflect) defer refClient.Reset() refSource := DescriptorSourceFromServer(context.Background(), refClient) _, err := ListServices(refSource) if err != ErrReflectionNotSupported { t.Errorf("ListServices should have returned ErrReflectionNotSupported; instead got %v", err) } _, err = ListMethods(refSource, "SomeService") if err != ErrReflectionNotSupported { t.Errorf("ListMethods should have returned ErrReflectionNotSupported; instead got %v", err) } err = InvokeRpc(context.Background(), refSource, ccNoReflect, "FooService/Method", nil, nil, nil) // InvokeRpc wraps the error, so we just verify the returned error includes the right message if err == nil || !strings.Contains(err.Error(), ErrReflectionNotSupported.Error()) { t.Errorf("InvokeRpc should have returned ErrReflectionNotSupported; instead got %v", err) } } func TestProtosetWithImports(t *testing.T) { sourceProtoset, err := DescriptorSourceFromProtoSets("internal/testing/example.protoset") if err != nil { t.Fatalf("failed to load protoset: %v", err) } // really shallow check of the loaded descriptors if sd, err := sourceProtoset.FindSymbol("TestService"); err != nil { t.Errorf("failed to find TestService in protoset: %v", err) } else if sd == nil { t.Errorf("FindSymbol returned nil for TestService") } else if _, ok := sd.(*desc.ServiceDescriptor); !ok { t.Errorf("FindSymbol returned wrong kind of descriptor for TestService: %T", sd) } if md, err := sourceProtoset.FindSymbol("TestRequest"); err != nil { t.Errorf("failed to find TestRequest in protoset: %v", err) } else if md == nil { t.Errorf("FindSymbol returned nil for TestRequest") } else if _, ok := md.(*desc.MessageDescriptor); !ok { t.Errorf("FindSymbol returned wrong kind of descriptor for TestRequest: %T", md) } } func TestListServices(t *testing.T) { for _, ds := range descSources { t.Run(ds.name, func(t *testing.T) { doTestListServices(t, ds.source, ds.includeRefl) }) } } func doTestListServices(t *testing.T, source DescriptorSource, includeReflection bool) { names, err := ListServices(source) if err != nil { t.Fatalf("failed to list services: %v", err) } var expected []string if includeReflection { // when using server reflection, we see the TestService as well as the ServerReflection service expected = []string{"grpc.reflection.v1.ServerReflection", "grpc.reflection.v1alpha.ServerReflection", "testing.TestService"} } else { // without reflection, we see all services defined in the same test.proto file, which is the // TestService as well as UnimplementedService expected = []string{"testing.TestService", "testing.UnimplementedService"} } if !reflect.DeepEqual(expected, names) { t.Errorf("ListServices returned wrong results: wanted %v, got %v", expected, names) } } func TestListMethods(t *testing.T) { for _, ds := range descSources { t.Run(ds.name, func(t *testing.T) { doTestListMethods(t, ds.source, ds.includeRefl) }) } } func doTestListMethods(t *testing.T, source DescriptorSource, includeReflection bool) { names, err := ListMethods(source, "testing.TestService") if err != nil { t.Fatalf("failed to list methods for TestService: %v", err) } expected := []string{ "testing.TestService.EmptyCall", "testing.TestService.FullDuplexCall", "testing.TestService.HalfDuplexCall", "testing.TestService.StreamingInputCall", "testing.TestService.StreamingOutputCall", "testing.TestService.UnaryCall", } if !reflect.DeepEqual(expected, names) { t.Errorf("ListMethods returned wrong results: wanted %v, got %v", expected, names) } if includeReflection { // when using server reflection, we see the TestService as well as the ServerReflection service names, err = ListMethods(source, "grpc.reflection.v1.ServerReflection") if err != nil { t.Fatalf("failed to list methods for ServerReflection: %v", err) } expected = []string{"grpc.reflection.v1.ServerReflection.ServerReflectionInfo"} } else { // without reflection, we see all services defined in the same test.proto file, which is the // TestService as well as UnimplementedService names, err = ListMethods(source, "testing.UnimplementedService") if err != nil { t.Fatalf("failed to list methods for ServerReflection: %v", err) } expected = []string{"testing.UnimplementedService.UnimplementedCall"} } if !reflect.DeepEqual(expected, names) { t.Errorf("ListMethods returned wrong results: wanted %v, got %v", expected, names) } // force an error _, err = ListMethods(source, "FooService") if err != nil && !strings.Contains(err.Error(), "Symbol not found: FooService") { t.Errorf("ListMethods should have returned 'not found' error but instead returned %v", err) } } func TestGetAllFiles(t *testing.T) { expectedFiles := []string{"test.proto"} expectedFilesWithReflection := []string{ "grpc/reflection/v1/reflection.proto", "grpc/reflection/v1alpha/reflection.proto", "test.proto", } for _, ds := range descSources { t.Run(ds.name, func(t *testing.T) { files, err := GetAllFiles(ds.source) if err != nil { t.Fatalf("failed to get all files: %v", err) } names := fileNames(files) match := false var expected []string if ds.includeRefl { expected = expectedFilesWithReflection } else { expected = expectedFiles } match = reflect.DeepEqual(expected, names) if !match { t.Errorf("GetAllFiles returned wrong results: wanted %v, got %v", expected, names) } }) } // try cases with more complicated set of files otherSourceProtoset, err := DescriptorSourceFromProtoSets("internal/testing/test.protoset", "internal/testing/example.protoset") if err != nil { t.Fatal(err.Error()) } otherSourceProtoFiles, err := DescriptorSourceFromProtoFiles([]string{"internal/testing"}, "test.proto", "example.proto") if err != nil { t.Fatal(err.Error()) } otherDescSources := []descSourceCase{ {"protoset[b]", otherSourceProtoset, false}, {"proto[b]", otherSourceProtoFiles, false}, } expectedFiles = []string{ "example.proto", "example2.proto", "google/protobuf/any.proto", "google/protobuf/descriptor.proto", "google/protobuf/empty.proto", "google/protobuf/timestamp.proto", "test.proto", } for _, ds := range otherDescSources { t.Run(ds.name, func(t *testing.T) { files, err := GetAllFiles(ds.source) if err != nil { t.Fatalf("failed to get all files: %v", err) } names := fileNames(files) if !reflect.DeepEqual(expectedFiles, names) { t.Errorf("GetAllFiles returned wrong results: wanted %v, got %v", expectedFiles, names) } }) } } func TestExpandHeaders(t *testing.T) { inHeaders := []string{"key1: ${value}", "key2: bar", "key3: ${woo", "key4: woo}", "key5: ${TEST}", "key6: ${TEST_VAR}", "${TEST}: ${TEST_VAR}", "key8: ${EMPTY}"} os.Setenv("value", "value") os.Setenv("TEST", "value5") os.Setenv("TEST_VAR", "value6") os.Setenv("EMPTY", "") expectedHeaders := map[string]bool{"key1: value": true, "key2: bar": true, "key3: ${woo": true, "key4: woo}": true, "key5: value5": true, "key6: value6": true, "value5: value6": true, "key8: ": true} outHeaders, err := ExpandHeaders(inHeaders) if err != nil { t.Errorf("The ExpandHeaders function generated an unexpected error %s", err) } for _, expandedHeader := range outHeaders { if _, ok := expectedHeaders[expandedHeader]; !ok { t.Errorf("The ExpandHeaders function has returned an unexpected header. Received unexpected header %s", expandedHeader) } } badHeaders := []string{"key: ${DNE}"} _, err = ExpandHeaders(badHeaders) if err == nil { t.Errorf("The ExpandHeaders function should return an error for missing environment variables %q", badHeaders) } } func fileNames(files []*desc.FileDescriptor) []string { names := make([]string, len(files)) for i, f := range files { names[i] = f.GetName() } return names } const expectKnownType = `{ "dur": "0s", "ts": "1970-01-01T00:00:00Z", "dbl": 0, "flt": 0, "i64": "0", "u64": "0", "i32": 0, "u32": 0, "bool": false, "str": "", "bytes": null, "st": {"google.protobuf.Struct": "supports arbitrary JSON objects"}, "an": {"@type": "type.googleapis.com/google.protobuf.Empty", "value": {}}, "lv": [{"google.protobuf.ListValue": "is an array of arbitrary JSON values"}], "val": {"google.protobuf.Value": "supports arbitrary JSON"} }` func TestMakeTemplateKnownTypes(t *testing.T) { descriptor, err := desc.LoadMessageDescriptorForMessage((*jsonpbtest.KnownTypes)(nil)) if err != nil { t.Fatalf("failed to load descriptor: %v", err) } message := MakeTemplate(descriptor) jsm := jsonpb.Marshaler{EmitDefaults: true} out, err := jsm.MarshalToString(message) if err != nil { t.Fatalf("failed to marshal to JSON: %v", err) } // make sure template JSON matches expected var actual, expected interface{} if err := json.Unmarshal([]byte(out), &actual); err != nil { t.Fatalf("failed to parse actual JSON: %v", err) } if err := json.Unmarshal([]byte(expectKnownType), &expected); err != nil { t.Fatalf("failed to parse expected JSON: %v", err) } if !reflect.DeepEqual(actual, expected) { t.Errorf("template message is not as expected; want:\n%s\ngot:\n%s", expectKnownType, out) } } func TestDescribe(t *testing.T) { for _, ds := range descSources { t.Run(ds.name, func(t *testing.T) { doTestDescribe(t, ds.source) }) } } func doTestDescribe(t *testing.T, source DescriptorSource) { sym := "testing.TestService.EmptyCall" dsc, err := source.FindSymbol(sym) if err != nil { t.Fatalf("failed to get descriptor for %q: %v", sym, err) } if _, ok := dsc.(*desc.MethodDescriptor); !ok { t.Fatalf("descriptor for %q was a %T (expecting a MethodDescriptor)", sym, dsc) } txt := proto.MarshalTextString(dsc.AsProto()) expected := `name: "EmptyCall" input_type: ".testing.Empty" output_type: ".testing.Empty" ` if expected != txt { t.Errorf("descriptor mismatch: expected %s, got %s", expected, txt) } sym = "testing.StreamingOutputCallResponse" dsc, err = source.FindSymbol(sym) if err != nil { t.Fatalf("failed to get descriptor for %q: %v", sym, err) } if _, ok := dsc.(*desc.MessageDescriptor); !ok { t.Fatalf("descriptor for %q was a %T (expecting a MessageDescriptor)", sym, dsc) } txt = proto.MarshalTextString(dsc.AsProto()) expected = `name: "StreamingOutputCallResponse" field: < name: "payload" number: 1 label: LABEL_OPTIONAL type: TYPE_MESSAGE type_name: ".testing.Payload" json_name: "payload" > ` if expected != txt { t.Errorf("descriptor mismatch: expected %s, got %s", expected, txt) } _, err = source.FindSymbol("FooService") if err != nil && !strings.Contains(err.Error(), "Symbol not found: FooService") { t.Errorf("FindSymbol should have returned 'not found' error but instead returned %v", err) } } const ( // type == COMPRESSABLE, but that is default (since it has // numeric value == 0) and thus doesn't actually get included // on the wire payload1 = `{ "payload": { "body": "SXQncyBCdXNpbmVzcyBUaW1l" } }` payload2 = `{ "payload": { "type": "RANDOM", "body": "Rm91eCBkdSBGYUZh" } }` payload3 = `{ "payload": { "type": "UNCOMPRESSABLE", "body": "SGlwaG9wb3BvdGFtdXMgdnMuIFJoeW1lbm9jZXJvcw==" } }` ) func getCC(includeRefl bool) *grpc.ClientConn { if includeRefl { return ccReflect } else { return ccNoReflect } } func TestUnary(t *testing.T) { for _, ds := range descSources { t.Run(ds.name, func(t *testing.T) { doTestUnary(t, getCC(ds.includeRefl), ds.source) }) } } func doTestUnary(t *testing.T, cc *grpc.ClientConn, source DescriptorSource) { // Success h := &handler{reqMessages: []string{payload1}} err := InvokeRpc(context.Background(), source, cc, "testing.TestService/UnaryCall", makeHeaders(codes.OK), h, h.getRequestData) if err != nil { t.Fatalf("unexpected error during RPC: %v", err) } if h.check(t, "testing.TestService.UnaryCall", codes.OK, 1, 1) { if h.respMessages[0] != payload1 { t.Errorf("unexpected response from RPC: expecting %s; got %s", payload1, h.respMessages[0]) } } // Failure h = &handler{reqMessages: []string{payload1}} err = InvokeRpc(context.Background(), source, cc, "testing.TestService/UnaryCall", makeHeaders(codes.NotFound), h, h.getRequestData) if err != nil { t.Fatalf("unexpected error during RPC: %v", err) } h.check(t, "testing.TestService.UnaryCall", codes.NotFound, 1, 0) } func TestClientStream(t *testing.T) { for _, ds := range descSources { t.Run(ds.name, func(t *testing.T) { doTestClientStream(t, getCC(ds.includeRefl), ds.source) }) } } func doTestClientStream(t *testing.T, cc *grpc.ClientConn, source DescriptorSource) { // Success h := &handler{reqMessages: []string{payload1, payload2, payload3}} err := InvokeRpc(context.Background(), source, cc, "testing.TestService/StreamingInputCall", makeHeaders(codes.OK), h, h.getRequestData) if err != nil { t.Fatalf("unexpected error during RPC: %v", err) } if h.check(t, "testing.TestService.StreamingInputCall", codes.OK, 3, 1) { expected := `{ "aggregatedPayloadSize": 61 }` if h.respMessages[0] != expected { t.Errorf("unexpected response from RPC: expecting %s; got %s", expected, h.respMessages[0]) } } // Fail fast (server rejects as soon as possible) h = &handler{reqMessages: []string{payload1, payload2, payload3}} err = InvokeRpc(context.Background(), source, cc, "testing.TestService/StreamingInputCall", makeHeaders(codes.InvalidArgument), h, h.getRequestData) if err != nil { t.Fatalf("unexpected error during RPC: %v", err) } h.check(t, "testing.TestService.StreamingInputCall", codes.InvalidArgument, -3, 0) // Fail late (server waits until stream is complete to reject) h = &handler{reqMessages: []string{payload1, payload2, payload3}} err = InvokeRpc(context.Background(), source, cc, "testing.TestService/StreamingInputCall", makeHeaders(codes.Internal, true), h, h.getRequestData) if err != nil { t.Fatalf("unexpected error during RPC: %v", err) } h.check(t, "testing.TestService.StreamingInputCall", codes.Internal, 3, 0) } func TestServerStream(t *testing.T) { for _, ds := range descSources { t.Run(ds.name, func(t *testing.T) { doTestServerStream(t, getCC(ds.includeRefl), ds.source) }) } } func doTestServerStream(t *testing.T, cc *grpc.ClientConn, source DescriptorSource) { req := &grpcurl_testing.StreamingOutputCallRequest{ ResponseType: grpcurl_testing.PayloadType_COMPRESSABLE, ResponseParameters: []*grpcurl_testing.ResponseParameters{ {Size: 10}, {Size: 20}, {Size: 30}, {Size: 40}, {Size: 50}, }, } payload, err := (&jsonpb.Marshaler{}).MarshalToString(req) if err != nil { t.Fatalf("failed to construct request: %v", err) } // Success h := &handler{reqMessages: []string{payload}} err = InvokeRpc(context.Background(), source, cc, "testing.TestService/StreamingOutputCall", makeHeaders(codes.OK), h, h.getRequestData) if err != nil { t.Fatalf("unexpected error during RPC: %v", err) } if h.check(t, "testing.TestService.StreamingOutputCall", codes.OK, 1, 5) { resp := &grpcurl_testing.StreamingOutputCallResponse{} for i, msg := range h.respMessages { if err := jsonpb.UnmarshalString(msg, resp); err != nil { t.Errorf("failed to parse response %d: %v", i+1, err) } if resp.Payload.GetType() != grpcurl_testing.PayloadType_COMPRESSABLE { t.Errorf("response %d has wrong payload type; expecting %v, got %v", i, grpcurl_testing.PayloadType_COMPRESSABLE, resp.Payload.Type) } if len(resp.Payload.Body) != (i+1)*10 { t.Errorf("response %d has wrong payload size; expecting %d, got %d", i, (i+1)*10, len(resp.Payload.Body)) } resp.Reset() } } // Fail fast (server rejects as soon as possible) h = &handler{reqMessages: []string{payload}} err = InvokeRpc(context.Background(), source, cc, "testing.TestService/StreamingOutputCall", makeHeaders(codes.Aborted), h, h.getRequestData) if err != nil { t.Fatalf("unexpected error during RPC: %v", err) } h.check(t, "testing.TestService.StreamingOutputCall", codes.Aborted, 1, 0) // Fail late (server waits until stream is complete to reject) h = &handler{reqMessages: []string{payload}} err = InvokeRpc(context.Background(), source, cc, "testing.TestService/StreamingOutputCall", makeHeaders(codes.AlreadyExists, true), h, h.getRequestData) if err != nil { t.Fatalf("unexpected error during RPC: %v", err) } h.check(t, "testing.TestService.StreamingOutputCall", codes.AlreadyExists, 1, 5) } func TestHalfDuplexStream(t *testing.T) { for _, ds := range descSources { t.Run(ds.name, func(t *testing.T) { doTestHalfDuplexStream(t, getCC(ds.includeRefl), ds.source) }) } } func doTestHalfDuplexStream(t *testing.T, cc *grpc.ClientConn, source DescriptorSource) { reqs := []string{payload1, payload2, payload3} // Success h := &handler{reqMessages: reqs} err := InvokeRpc(context.Background(), source, cc, "testing.TestService/HalfDuplexCall", makeHeaders(codes.OK), h, h.getRequestData) if err != nil { t.Fatalf("unexpected error during RPC: %v", err) } if h.check(t, "testing.TestService.HalfDuplexCall", codes.OK, 3, 3) { for i, resp := range h.respMessages { if resp != reqs[i] { t.Errorf("unexpected response %d from RPC:\nexpecting %q\ngot %q", i, reqs[i], resp) } } } // Fail fast (server rejects as soon as possible) h = &handler{reqMessages: reqs} err = InvokeRpc(context.Background(), source, cc, "testing.TestService/HalfDuplexCall", makeHeaders(codes.Canceled), h, h.getRequestData) if err != nil { t.Fatalf("unexpected error during RPC: %v", err) } h.check(t, "testing.TestService.HalfDuplexCall", codes.Canceled, -3, 0) // Fail late (server waits until stream is complete to reject) h = &handler{reqMessages: reqs} err = InvokeRpc(context.Background(), source, cc, "testing.TestService/HalfDuplexCall", makeHeaders(codes.DataLoss, true), h, h.getRequestData) if err != nil { t.Fatalf("unexpected error during RPC: %v", err) } h.check(t, "testing.TestService.HalfDuplexCall", codes.DataLoss, 3, 3) } func TestFullDuplexStream(t *testing.T) { for _, ds := range descSources { t.Run(ds.name, func(t *testing.T) { doTestFullDuplexStream(t, getCC(ds.includeRefl), ds.source) }) } } func doTestFullDuplexStream(t *testing.T, cc *grpc.ClientConn, source DescriptorSource) { reqs := make([]string, 3) req := &grpcurl_testing.StreamingOutputCallRequest{ ResponseType: grpcurl_testing.PayloadType_RANDOM, } for i := range reqs { req.ResponseParameters = append(req.ResponseParameters, &grpcurl_testing.ResponseParameters{Size: int32((i + 1) * 10)}) payload, err := (&jsonpb.Marshaler{}).MarshalToString(req) if err != nil { t.Fatalf("failed to construct request %d: %v", i, err) } reqs[i] = payload } // Success h := &handler{reqMessages: reqs} err := InvokeRpc(context.Background(), source, cc, "testing.TestService/FullDuplexCall", makeHeaders(codes.OK), h, h.getRequestData) if err != nil { t.Fatalf("unexpected error during RPC: %v", err) } if h.check(t, "testing.TestService.FullDuplexCall", codes.OK, 3, 6) { resp := &grpcurl_testing.StreamingOutputCallResponse{} i := 0 for j := 1; j < 3; j++ { // three requests for k := 0; k < j; k++ { // 1 response for first request, 2 for second, etc msg := h.respMessages[i] if err := jsonpb.UnmarshalString(msg, resp); err != nil { t.Errorf("failed to parse response %d: %v", i+1, err) } if resp.Payload.GetType() != grpcurl_testing.PayloadType_RANDOM { t.Errorf("response %d has wrong payload type; expecting %v, got %v", i, grpcurl_testing.PayloadType_RANDOM, resp.Payload.Type) } if len(resp.Payload.Body) != (k+1)*10 { t.Errorf("response %d has wrong payload size; expecting %d, got %d", i, (k+1)*10, len(resp.Payload.Body)) } resp.Reset() i++ } } } // Fail fast (server rejects as soon as possible) h = &handler{reqMessages: reqs} err = InvokeRpc(context.Background(), source, cc, "testing.TestService/FullDuplexCall", makeHeaders(codes.PermissionDenied), h, h.getRequestData) if err != nil { t.Fatalf("unexpected error during RPC: %v", err) } h.check(t, "testing.TestService.FullDuplexCall", codes.PermissionDenied, -3, 0) // Fail late (server waits until stream is complete to reject) h = &handler{reqMessages: reqs} err = InvokeRpc(context.Background(), source, cc, "testing.TestService/FullDuplexCall", makeHeaders(codes.ResourceExhausted, true), h, h.getRequestData) if err != nil { t.Fatalf("unexpected error during RPC: %v", err) } h.check(t, "testing.TestService.FullDuplexCall", codes.ResourceExhausted, 3, 6) } type handler struct { method *desc.MethodDescriptor methodCount int reqHeaders metadata.MD reqHeadersCount int reqMessages []string reqMessagesCount int respHeaders metadata.MD respHeadersCount int respMessages []string respTrailers metadata.MD respStatus *status.Status respTrailersCount int } func (h *handler) getRequestData() ([]byte, error) { // we don't use a mutex, though this method will be called from different goroutine // than other methods for bidi calls, because this method does not share any state // with the other methods. h.reqMessagesCount++ if h.reqMessagesCount > len(h.reqMessages) { return nil, io.EOF } if h.reqMessagesCount > 1 { // insert delay between messages in request stream time.Sleep(time.Millisecond * 50) } return []byte(h.reqMessages[h.reqMessagesCount-1]), nil } func (h *handler) OnResolveMethod(md *desc.MethodDescriptor) { h.methodCount++ h.method = md } func (h *handler) OnSendHeaders(md metadata.MD) { h.reqHeadersCount++ h.reqHeaders = md } func (h *handler) OnReceiveHeaders(md metadata.MD) { h.respHeadersCount++ h.respHeaders = md } func (h *handler) OnReceiveResponse(msg proto.Message) { jsm := jsonpb.Marshaler{Indent: " "} respStr, err := jsm.MarshalToString(msg) if err != nil { panic(fmt.Errorf("failed to generate JSON form of response message: %v", err)) } h.respMessages = append(h.respMessages, respStr) } func (h *handler) OnReceiveTrailers(stat *status.Status, md metadata.MD) { h.respTrailersCount++ h.respTrailers = md h.respStatus = stat } func (h *handler) check(t *testing.T, expectedMethod string, expectedCode codes.Code, expectedRequestQueries, expectedResponses int) bool { // verify a few things were only ever called once if h.methodCount != 1 { t.Errorf("expected grpcurl to invoke OnResolveMethod once; was %d", h.methodCount) } if h.reqHeadersCount != 1 { t.Errorf("expected grpcurl to invoke OnSendHeaders once; was %d", h.reqHeadersCount) } if h.reqHeadersCount != 1 { t.Errorf("expected grpcurl to invoke OnSendHeaders once; was %d", h.reqHeadersCount) } if h.respHeadersCount != 1 { t.Errorf("expected grpcurl to invoke OnReceiveHeaders once; was %d", h.respHeadersCount) } if h.respTrailersCount != 1 { t.Errorf("expected grpcurl to invoke OnReceiveTrailers once; was %d", h.respTrailersCount) } // check other stuff against given expectations if h.method.GetFullyQualifiedName() != expectedMethod { t.Errorf("wrong method: expecting %v, got %v", expectedMethod, h.method.GetFullyQualifiedName()) } if h.respStatus.Code() != expectedCode { t.Errorf("wrong code: expecting %v, got %v", expectedCode, h.respStatus.Code()) } if expectedRequestQueries < 0 { // negative expectation means "negate and expect up to that number; could be fewer" if h.reqMessagesCount > -expectedRequestQueries+1 { // the + 1 is because there will be an extra query that returns EOF t.Errorf("wrong number of messages queried: expecting no more than %v, got %v", -expectedRequestQueries, h.reqMessagesCount-1) } } else { if h.reqMessagesCount != expectedRequestQueries+1 { // the + 1 is because there will be an extra query that returns EOF t.Errorf("wrong number of messages queried: expecting %v, got %v", expectedRequestQueries, h.reqMessagesCount-1) } } if len(h.respMessages) != expectedResponses { t.Errorf("wrong number of messages received: expecting %v, got %v", expectedResponses, len(h.respMessages)) } // also check headers and trailers came through as expected v := h.respHeaders["some-fake-header-1"] if len(v) != 1 || v[0] != "val1" { t.Errorf("wrong request header for %q: %v", "some-fake-header-1", v) } v = h.respHeaders["some-fake-header-2"] if len(v) != 1 || v[0] != "val2" { t.Errorf("wrong request header for %q: %v", "some-fake-header-2", v) } v = h.respTrailers["some-fake-trailer-1"] if len(v) != 1 || v[0] != "valA" { t.Errorf("wrong request header for %q: %v", "some-fake-trailer-1", v) } v = h.respTrailers["some-fake-trailer-2"] if len(v) != 1 || v[0] != "valB" { t.Errorf("wrong request header for %q: %v", "some-fake-trailer-2", v) } return len(h.respMessages) == expectedResponses } func makeHeaders(code codes.Code, failLate ...bool) []string { if len(failLate) > 1 { panic("incorrect use of makeContext; should be at most one failLate flag") } hdrs := append(make([]string, 0, 5), fmt.Sprintf("%s: %s", grpcurl_testing.MetadataReplyHeaders, "some-fake-header-1: val1"), fmt.Sprintf("%s: %s", grpcurl_testing.MetadataReplyHeaders, "some-fake-header-2: val2"), fmt.Sprintf("%s: %s", grpcurl_testing.MetadataReplyTrailers, "some-fake-trailer-1: valA"), fmt.Sprintf("%s: %s", grpcurl_testing.MetadataReplyTrailers, "some-fake-trailer-2: valB")) if code != codes.OK { if len(failLate) > 0 && failLate[0] { hdrs = append(hdrs, fmt.Sprintf("%s: %d", grpcurl_testing.MetadataFailLate, code)) } else { hdrs = append(hdrs, fmt.Sprintf("%s: %d", grpcurl_testing.MetadataFailEarly, code)) } } return hdrs } ================================================ FILE: internal/testing/cmd/bankdemo/README.md ================================================ # bankdemo The `bankdemo` program is an example gRPC server that was used to demo `grpcurl` at Gophercon 2018. It demonstrates interesting concepts for building a gRPC server, including chat functionality (that relies on full-duplex bidirectional streams). This code was written specifically to provide an interesting concrete demonstration and, as such, should not be considered in any way production-worthy. The demo app tracks user accounts, transactions, and balances completely in memory. Every few seconds, as well as on graceful shutdown (like when the server receives a SIGTERM or SIGINT signal), this state is saved to a file named `accounts.json`, so that the data can be restored if the process restarts. In addition to bank account data, the server also tracks "chat sessions", for demonstrating bidirectional streams in the form of an application where customers can chat with support agents. ================================================ FILE: internal/testing/cmd/bankdemo/auth.go ================================================ package main import ( "context" "strings" "google.golang.org/grpc/metadata" ) func getCustomer(ctx context.Context) string { // we'll just treat the "auth token" as if it is a // customer ID, but reject tokens that begin with "agent" // (those are auth tokens for support agents, not customers) cust := getAuthCode(ctx) if strings.HasPrefix(cust, "agent") { return "" } return cust } func getAgent(ctx context.Context) string { // we'll just treat the "auth token" as if it is an agent's // user ID, but reject tokens that don't begin with "agent" // (those are auth tokens for customers, not support agents) agent := getAuthCode(ctx) if !strings.HasPrefix(agent, "agent") { return "" } return agent } func getAuthCode(ctx context.Context) string { md, ok := metadata.FromIncomingContext(ctx) if !ok { return "" } vals := md.Get("authorization") if len(vals) != 1 { return "" } pieces := strings.SplitN(strings.ToLower(vals[0]), " ", 2) if len(pieces) != 2 { return "" } if pieces[0] != "token" { return "" } return pieces[1] } ================================================ FILE: internal/testing/cmd/bankdemo/bank.go ================================================ package main import ( "context" "fmt" "time" "github.com/golang/protobuf/ptypes/empty" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) // bankServer implements the Bank gRPC service. type bankServer struct { UnimplementedBankServer allAccounts *accounts } func (s *bankServer) OpenAccount(ctx context.Context, req *OpenAccountRequest) (*Account, error) { cust := getCustomer(ctx) if cust == "" { return nil, status.Error(codes.Unauthenticated, codes.Unauthenticated.String()) } switch req.Type { case Account_CHECKING, Account_SAVING, Account_MONEY_MARKET: if req.InitialDepositCents < 0 { return nil, status.Errorf(codes.InvalidArgument, "initial deposit amount cannot be negative: %s", dollars(req.InitialDepositCents)) } case Account_LINE_OF_CREDIT, Account_LOAN, Account_EQUITIES: if req.InitialDepositCents != 0 { return nil, status.Errorf(codes.InvalidArgument, "initial deposit amount must be zero for account type %v: %s", req.Type, dollars(req.InitialDepositCents)) } default: return nil, status.Errorf(codes.InvalidArgument, "invalid account type: %v", req.Type) } return s.allAccounts.openAccount(cust, req.Type, req.InitialDepositCents), nil } func (s *bankServer) CloseAccount(ctx context.Context, req *CloseAccountRequest) (*empty.Empty, error) { cust := getCustomer(ctx) if cust == "" { return nil, status.Error(codes.Unauthenticated, codes.Unauthenticated.String()) } if err := s.allAccounts.closeAccount(cust, req.AccountNumber); err != nil { return nil, err } return &empty.Empty{}, nil } func (s *bankServer) GetAccounts(ctx context.Context, _ *empty.Empty) (*GetAccountsResponse, error) { cust := getCustomer(ctx) if cust == "" { return nil, status.Error(codes.Unauthenticated, codes.Unauthenticated.String()) } accounts := s.allAccounts.getAllAccounts(cust) return &GetAccountsResponse{Accounts: accounts}, nil } func (s *bankServer) GetTransactions(req *GetTransactionsRequest, stream Bank_GetTransactionsServer) error { cust := getCustomer(stream.Context()) if cust == "" { return status.Error(codes.Unauthenticated, codes.Unauthenticated.String()) } acct, err := s.allAccounts.getAccount(cust, req.AccountNumber) if err != nil { return err } var start, end time.Time if req.Start != nil { err := req.Start.CheckValid() if err != nil { return err } start = req.Start.AsTime() } if req.End != nil { err := req.End.CheckValid() if err != nil { return err } end = req.End.AsTime() } else { end = time.Date(9999, 12, 31, 23, 59, 59, 999999999, time.Local) } txns := acct.getTransactions() for _, txn := range txns { err := txn.Date.CheckValid() if err != nil { return err } t := txn.Date.AsTime() if (t.After(start) || t.Equal(start)) && (t.Before(end) || t.Equal(end)) { if err := stream.Send(txn); err != nil { return err } } } return nil } func (s *bankServer) Deposit(ctx context.Context, req *DepositRequest) (*BalanceResponse, error) { cust := getCustomer(ctx) if cust == "" { return nil, status.Error(codes.Unauthenticated, codes.Unauthenticated.String()) } switch req.Source { case DepositRequest_ACH, DepositRequest_CASH, DepositRequest_CHECK, DepositRequest_WIRE: // ok default: return nil, status.Errorf(codes.InvalidArgument, "unknown deposit source: %v", req.Source) } if req.AmountCents <= 0 { return nil, status.Errorf(codes.InvalidArgument, "deposit amount cannot be non-positive: %s", dollars(req.AmountCents)) } desc := fmt.Sprintf("%v deposit", req.Source) if req.Desc != "" { desc = fmt.Sprintf("%s: %s", desc, req.Desc) } acct, err := s.allAccounts.getAccount(cust, req.AccountNumber) if err != nil { return nil, err } newBalance, err := acct.newTransaction(req.AmountCents, desc) if err != nil { return nil, err } return &BalanceResponse{ AccountNumber: req.AccountNumber, BalanceCents: newBalance, }, nil } func (s *bankServer) Withdraw(ctx context.Context, req *WithdrawRequest) (*BalanceResponse, error) { cust := getCustomer(ctx) if cust == "" { return nil, status.Error(codes.Unauthenticated, codes.Unauthenticated.String()) } if req.AmountCents >= 0 { return nil, status.Errorf(codes.InvalidArgument, "withdrawal amount cannot be non-negative: %s", dollars(req.AmountCents)) } acct, err := s.allAccounts.getAccount(cust, req.AccountNumber) if err != nil { return nil, err } newBalance, err := acct.newTransaction(req.AmountCents, req.Desc) if err != nil { return nil, err } return &BalanceResponse{ AccountNumber: req.AccountNumber, BalanceCents: newBalance, }, nil } func (s *bankServer) Transfer(ctx context.Context, req *TransferRequest) (*TransferResponse, error) { cust := getCustomer(ctx) if cust == "" { return nil, status.Error(codes.Unauthenticated, codes.Unauthenticated.String()) } if req.AmountCents <= 0 { return nil, status.Errorf(codes.InvalidArgument, "transfer amount cannot be non-positive: %s", dollars(req.AmountCents)) } var srcAcct *account var srcDesc string switch src := req.Source.(type) { case *TransferRequest_ExternalSource: srcDesc = fmt.Sprintf("ACH %09d:%06d", src.ExternalSource.AchRoutingNumber, src.ExternalSource.AchAccountNumber) if src.ExternalSource.AchAccountNumber == 0 || src.ExternalSource.AchRoutingNumber == 0 { return nil, status.Errorf(codes.InvalidArgument, "external source routing and account numbers cannot be zero: %s", srcDesc) } case *TransferRequest_SourceAccountNumber: srcDesc = fmt.Sprintf("account %06d", src.SourceAccountNumber) var err error if srcAcct, err = s.allAccounts.getAccount(cust, src.SourceAccountNumber); err != nil { return nil, err } } var destAcct *account var destDesc string switch dest := req.Dest.(type) { case *TransferRequest_ExternalDest: destDesc = fmt.Sprintf("ACH %09d:%06d", dest.ExternalDest.AchRoutingNumber, dest.ExternalDest.AchAccountNumber) if dest.ExternalDest.AchAccountNumber == 0 || dest.ExternalDest.AchRoutingNumber == 0 { return nil, status.Errorf(codes.InvalidArgument, "external source routing and account numbers cannot be zero: %s", destDesc) } case *TransferRequest_DestAccountNumber: destDesc = fmt.Sprintf("account %06d", dest.DestAccountNumber) var err error if destAcct, err = s.allAccounts.getAccount(cust, dest.DestAccountNumber); err != nil { return nil, err } } var srcBalance int32 if srcAcct != nil { desc := fmt.Sprintf("transfer to %s", destDesc) if req.Desc != "" { desc = fmt.Sprintf("%s: %s", desc, req.Desc) } var err error if srcBalance, err = srcAcct.newTransaction(-req.AmountCents, desc); err != nil { return nil, err } } var destBalance int32 if destAcct != nil { desc := fmt.Sprintf("transfer from %s", srcDesc) if req.Desc != "" { desc = fmt.Sprintf("%s: %s", desc, req.Desc) } var err error if destBalance, err = destAcct.newTransaction(req.AmountCents, desc); err != nil { return nil, err } } return &TransferResponse{ SrcAccountNumber: req.GetSourceAccountNumber(), SrcBalanceCents: srcBalance, DestAccountNumber: req.GetDestAccountNumber(), DestBalanceCents: destBalance, }, nil } ================================================ FILE: internal/testing/cmd/bankdemo/bank.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0-devel // protoc v4.22.0 // source: bank.proto package main import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" emptypb "google.golang.org/protobuf/types/known/emptypb" timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Account_Type int32 const ( Account_UNKNOWN Account_Type = 0 Account_CHECKING Account_Type = 1 Account_SAVING Account_Type = 2 Account_MONEY_MARKET Account_Type = 3 Account_LINE_OF_CREDIT Account_Type = 4 Account_LOAN Account_Type = 5 Account_EQUITIES Account_Type = 6 ) // Enum value maps for Account_Type. var ( Account_Type_name = map[int32]string{ 0: "UNKNOWN", 1: "CHECKING", 2: "SAVING", 3: "MONEY_MARKET", 4: "LINE_OF_CREDIT", 5: "LOAN", 6: "EQUITIES", } Account_Type_value = map[string]int32{ "UNKNOWN": 0, "CHECKING": 1, "SAVING": 2, "MONEY_MARKET": 3, "LINE_OF_CREDIT": 4, "LOAN": 5, "EQUITIES": 6, } ) func (x Account_Type) Enum() *Account_Type { p := new(Account_Type) *p = x return p } func (x Account_Type) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (Account_Type) Descriptor() protoreflect.EnumDescriptor { return file_bank_proto_enumTypes[0].Descriptor() } func (Account_Type) Type() protoreflect.EnumType { return &file_bank_proto_enumTypes[0] } func (x Account_Type) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use Account_Type.Descriptor instead. func (Account_Type) EnumDescriptor() ([]byte, []int) { return file_bank_proto_rawDescGZIP(), []int{3, 0} } type DepositRequest_Source int32 const ( DepositRequest_UNKNOWN DepositRequest_Source = 0 DepositRequest_CASH DepositRequest_Source = 1 DepositRequest_CHECK DepositRequest_Source = 2 DepositRequest_ACH DepositRequest_Source = 3 DepositRequest_WIRE DepositRequest_Source = 4 ) // Enum value maps for DepositRequest_Source. var ( DepositRequest_Source_name = map[int32]string{ 0: "UNKNOWN", 1: "CASH", 2: "CHECK", 3: "ACH", 4: "WIRE", } DepositRequest_Source_value = map[string]int32{ "UNKNOWN": 0, "CASH": 1, "CHECK": 2, "ACH": 3, "WIRE": 4, } ) func (x DepositRequest_Source) Enum() *DepositRequest_Source { p := new(DepositRequest_Source) *p = x return p } func (x DepositRequest_Source) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (DepositRequest_Source) Descriptor() protoreflect.EnumDescriptor { return file_bank_proto_enumTypes[1].Descriptor() } func (DepositRequest_Source) Type() protoreflect.EnumType { return &file_bank_proto_enumTypes[1] } func (x DepositRequest_Source) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use DepositRequest_Source.Descriptor instead. func (DepositRequest_Source) EnumDescriptor() ([]byte, []int) { return file_bank_proto_rawDescGZIP(), []int{6, 0} } type OpenAccountRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields InitialDepositCents int32 `protobuf:"varint,1,opt,name=initial_deposit_cents,json=initialDepositCents,proto3" json:"initial_deposit_cents,omitempty"` Type Account_Type `protobuf:"varint,2,opt,name=type,proto3,enum=Account_Type" json:"type,omitempty"` } func (x *OpenAccountRequest) Reset() { *x = OpenAccountRequest{} if protoimpl.UnsafeEnabled { mi := &file_bank_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *OpenAccountRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*OpenAccountRequest) ProtoMessage() {} func (x *OpenAccountRequest) ProtoReflect() protoreflect.Message { mi := &file_bank_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use OpenAccountRequest.ProtoReflect.Descriptor instead. func (*OpenAccountRequest) Descriptor() ([]byte, []int) { return file_bank_proto_rawDescGZIP(), []int{0} } func (x *OpenAccountRequest) GetInitialDepositCents() int32 { if x != nil { return x.InitialDepositCents } return 0 } func (x *OpenAccountRequest) GetType() Account_Type { if x != nil { return x.Type } return Account_UNKNOWN } type CloseAccountRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields AccountNumber uint64 `protobuf:"varint,1,opt,name=account_number,json=accountNumber,proto3" json:"account_number,omitempty"` } func (x *CloseAccountRequest) Reset() { *x = CloseAccountRequest{} if protoimpl.UnsafeEnabled { mi := &file_bank_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *CloseAccountRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*CloseAccountRequest) ProtoMessage() {} func (x *CloseAccountRequest) ProtoReflect() protoreflect.Message { mi := &file_bank_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use CloseAccountRequest.ProtoReflect.Descriptor instead. func (*CloseAccountRequest) Descriptor() ([]byte, []int) { return file_bank_proto_rawDescGZIP(), []int{1} } func (x *CloseAccountRequest) GetAccountNumber() uint64 { if x != nil { return x.AccountNumber } return 0 } type GetAccountsResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Accounts []*Account `protobuf:"bytes,1,rep,name=accounts,proto3" json:"accounts,omitempty"` } func (x *GetAccountsResponse) Reset() { *x = GetAccountsResponse{} if protoimpl.UnsafeEnabled { mi := &file_bank_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetAccountsResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetAccountsResponse) ProtoMessage() {} func (x *GetAccountsResponse) ProtoReflect() protoreflect.Message { mi := &file_bank_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetAccountsResponse.ProtoReflect.Descriptor instead. func (*GetAccountsResponse) Descriptor() ([]byte, []int) { return file_bank_proto_rawDescGZIP(), []int{2} } func (x *GetAccountsResponse) GetAccounts() []*Account { if x != nil { return x.Accounts } return nil } type Account struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields AccountNumber uint64 `protobuf:"varint,1,opt,name=account_number,json=accountNumber,proto3" json:"account_number,omitempty"` Type Account_Type `protobuf:"varint,2,opt,name=type,proto3,enum=Account_Type" json:"type,omitempty"` BalanceCents int32 `protobuf:"varint,3,opt,name=balance_cents,json=balanceCents,proto3" json:"balance_cents,omitempty"` } func (x *Account) Reset() { *x = Account{} if protoimpl.UnsafeEnabled { mi := &file_bank_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Account) String() string { return protoimpl.X.MessageStringOf(x) } func (*Account) ProtoMessage() {} func (x *Account) ProtoReflect() protoreflect.Message { mi := &file_bank_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Account.ProtoReflect.Descriptor instead. func (*Account) Descriptor() ([]byte, []int) { return file_bank_proto_rawDescGZIP(), []int{3} } func (x *Account) GetAccountNumber() uint64 { if x != nil { return x.AccountNumber } return 0 } func (x *Account) GetType() Account_Type { if x != nil { return x.Type } return Account_UNKNOWN } func (x *Account) GetBalanceCents() int32 { if x != nil { return x.BalanceCents } return 0 } type GetTransactionsRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields AccountNumber uint64 `protobuf:"varint,1,opt,name=account_number,json=accountNumber,proto3" json:"account_number,omitempty"` Start *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=start,proto3" json:"start,omitempty"` End *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=end,proto3" json:"end,omitempty"` } func (x *GetTransactionsRequest) Reset() { *x = GetTransactionsRequest{} if protoimpl.UnsafeEnabled { mi := &file_bank_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GetTransactionsRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*GetTransactionsRequest) ProtoMessage() {} func (x *GetTransactionsRequest) ProtoReflect() protoreflect.Message { mi := &file_bank_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GetTransactionsRequest.ProtoReflect.Descriptor instead. func (*GetTransactionsRequest) Descriptor() ([]byte, []int) { return file_bank_proto_rawDescGZIP(), []int{4} } func (x *GetTransactionsRequest) GetAccountNumber() uint64 { if x != nil { return x.AccountNumber } return 0 } func (x *GetTransactionsRequest) GetStart() *timestamppb.Timestamp { if x != nil { return x.Start } return nil } func (x *GetTransactionsRequest) GetEnd() *timestamppb.Timestamp { if x != nil { return x.End } return nil } type Transaction struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields AccountNumber uint64 `protobuf:"varint,1,opt,name=account_number,json=accountNumber,proto3" json:"account_number,omitempty"` SeqNumber uint64 `protobuf:"varint,2,opt,name=seq_number,json=seqNumber,proto3" json:"seq_number,omitempty"` Date *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=date,proto3" json:"date,omitempty"` AmountCents int32 `protobuf:"varint,4,opt,name=amount_cents,json=amountCents,proto3" json:"amount_cents,omitempty"` Desc string `protobuf:"bytes,5,opt,name=desc,proto3" json:"desc,omitempty"` } func (x *Transaction) Reset() { *x = Transaction{} if protoimpl.UnsafeEnabled { mi := &file_bank_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Transaction) String() string { return protoimpl.X.MessageStringOf(x) } func (*Transaction) ProtoMessage() {} func (x *Transaction) ProtoReflect() protoreflect.Message { mi := &file_bank_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Transaction.ProtoReflect.Descriptor instead. func (*Transaction) Descriptor() ([]byte, []int) { return file_bank_proto_rawDescGZIP(), []int{5} } func (x *Transaction) GetAccountNumber() uint64 { if x != nil { return x.AccountNumber } return 0 } func (x *Transaction) GetSeqNumber() uint64 { if x != nil { return x.SeqNumber } return 0 } func (x *Transaction) GetDate() *timestamppb.Timestamp { if x != nil { return x.Date } return nil } func (x *Transaction) GetAmountCents() int32 { if x != nil { return x.AmountCents } return 0 } func (x *Transaction) GetDesc() string { if x != nil { return x.Desc } return "" } type DepositRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields AccountNumber uint64 `protobuf:"varint,1,opt,name=account_number,json=accountNumber,proto3" json:"account_number,omitempty"` AmountCents int32 `protobuf:"varint,2,opt,name=amount_cents,json=amountCents,proto3" json:"amount_cents,omitempty"` Source DepositRequest_Source `protobuf:"varint,3,opt,name=source,proto3,enum=DepositRequest_Source" json:"source,omitempty"` Desc string `protobuf:"bytes,4,opt,name=desc,proto3" json:"desc,omitempty"` } func (x *DepositRequest) Reset() { *x = DepositRequest{} if protoimpl.UnsafeEnabled { mi := &file_bank_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *DepositRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*DepositRequest) ProtoMessage() {} func (x *DepositRequest) ProtoReflect() protoreflect.Message { mi := &file_bank_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use DepositRequest.ProtoReflect.Descriptor instead. func (*DepositRequest) Descriptor() ([]byte, []int) { return file_bank_proto_rawDescGZIP(), []int{6} } func (x *DepositRequest) GetAccountNumber() uint64 { if x != nil { return x.AccountNumber } return 0 } func (x *DepositRequest) GetAmountCents() int32 { if x != nil { return x.AmountCents } return 0 } func (x *DepositRequest) GetSource() DepositRequest_Source { if x != nil { return x.Source } return DepositRequest_UNKNOWN } func (x *DepositRequest) GetDesc() string { if x != nil { return x.Desc } return "" } type BalanceResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields AccountNumber uint64 `protobuf:"varint,1,opt,name=account_number,json=accountNumber,proto3" json:"account_number,omitempty"` BalanceCents int32 `protobuf:"varint,2,opt,name=balance_cents,json=balanceCents,proto3" json:"balance_cents,omitempty"` } func (x *BalanceResponse) Reset() { *x = BalanceResponse{} if protoimpl.UnsafeEnabled { mi := &file_bank_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *BalanceResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*BalanceResponse) ProtoMessage() {} func (x *BalanceResponse) ProtoReflect() protoreflect.Message { mi := &file_bank_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use BalanceResponse.ProtoReflect.Descriptor instead. func (*BalanceResponse) Descriptor() ([]byte, []int) { return file_bank_proto_rawDescGZIP(), []int{7} } func (x *BalanceResponse) GetAccountNumber() uint64 { if x != nil { return x.AccountNumber } return 0 } func (x *BalanceResponse) GetBalanceCents() int32 { if x != nil { return x.BalanceCents } return 0 } type WithdrawRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields AccountNumber uint64 `protobuf:"varint,1,opt,name=account_number,json=accountNumber,proto3" json:"account_number,omitempty"` AmountCents int32 `protobuf:"varint,2,opt,name=amount_cents,json=amountCents,proto3" json:"amount_cents,omitempty"` Desc string `protobuf:"bytes,3,opt,name=desc,proto3" json:"desc,omitempty"` } func (x *WithdrawRequest) Reset() { *x = WithdrawRequest{} if protoimpl.UnsafeEnabled { mi := &file_bank_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *WithdrawRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*WithdrawRequest) ProtoMessage() {} func (x *WithdrawRequest) ProtoReflect() protoreflect.Message { mi := &file_bank_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use WithdrawRequest.ProtoReflect.Descriptor instead. func (*WithdrawRequest) Descriptor() ([]byte, []int) { return file_bank_proto_rawDescGZIP(), []int{8} } func (x *WithdrawRequest) GetAccountNumber() uint64 { if x != nil { return x.AccountNumber } return 0 } func (x *WithdrawRequest) GetAmountCents() int32 { if x != nil { return x.AmountCents } return 0 } func (x *WithdrawRequest) GetDesc() string { if x != nil { return x.Desc } return "" } type TransferRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Types that are assignable to Source: // // *TransferRequest_SourceAccountNumber // *TransferRequest_ExternalSource Source isTransferRequest_Source `protobuf_oneof:"source"` // Types that are assignable to Dest: // // *TransferRequest_DestAccountNumber // *TransferRequest_ExternalDest Dest isTransferRequest_Dest `protobuf_oneof:"dest"` AmountCents int32 `protobuf:"varint,5,opt,name=amount_cents,json=amountCents,proto3" json:"amount_cents,omitempty"` Desc string `protobuf:"bytes,6,opt,name=desc,proto3" json:"desc,omitempty"` } func (x *TransferRequest) Reset() { *x = TransferRequest{} if protoimpl.UnsafeEnabled { mi := &file_bank_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *TransferRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*TransferRequest) ProtoMessage() {} func (x *TransferRequest) ProtoReflect() protoreflect.Message { mi := &file_bank_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use TransferRequest.ProtoReflect.Descriptor instead. func (*TransferRequest) Descriptor() ([]byte, []int) { return file_bank_proto_rawDescGZIP(), []int{9} } func (m *TransferRequest) GetSource() isTransferRequest_Source { if m != nil { return m.Source } return nil } func (x *TransferRequest) GetSourceAccountNumber() uint64 { if x, ok := x.GetSource().(*TransferRequest_SourceAccountNumber); ok { return x.SourceAccountNumber } return 0 } func (x *TransferRequest) GetExternalSource() *TransferRequest_ExternalAccount { if x, ok := x.GetSource().(*TransferRequest_ExternalSource); ok { return x.ExternalSource } return nil } func (m *TransferRequest) GetDest() isTransferRequest_Dest { if m != nil { return m.Dest } return nil } func (x *TransferRequest) GetDestAccountNumber() uint64 { if x, ok := x.GetDest().(*TransferRequest_DestAccountNumber); ok { return x.DestAccountNumber } return 0 } func (x *TransferRequest) GetExternalDest() *TransferRequest_ExternalAccount { if x, ok := x.GetDest().(*TransferRequest_ExternalDest); ok { return x.ExternalDest } return nil } func (x *TransferRequest) GetAmountCents() int32 { if x != nil { return x.AmountCents } return 0 } func (x *TransferRequest) GetDesc() string { if x != nil { return x.Desc } return "" } type isTransferRequest_Source interface { isTransferRequest_Source() } type TransferRequest_SourceAccountNumber struct { SourceAccountNumber uint64 `protobuf:"varint,1,opt,name=source_account_number,json=sourceAccountNumber,proto3,oneof"` } type TransferRequest_ExternalSource struct { ExternalSource *TransferRequest_ExternalAccount `protobuf:"bytes,2,opt,name=external_source,json=externalSource,proto3,oneof"` } func (*TransferRequest_SourceAccountNumber) isTransferRequest_Source() {} func (*TransferRequest_ExternalSource) isTransferRequest_Source() {} type isTransferRequest_Dest interface { isTransferRequest_Dest() } type TransferRequest_DestAccountNumber struct { DestAccountNumber uint64 `protobuf:"varint,3,opt,name=dest_account_number,json=destAccountNumber,proto3,oneof"` } type TransferRequest_ExternalDest struct { ExternalDest *TransferRequest_ExternalAccount `protobuf:"bytes,4,opt,name=external_dest,json=externalDest,proto3,oneof"` } func (*TransferRequest_DestAccountNumber) isTransferRequest_Dest() {} func (*TransferRequest_ExternalDest) isTransferRequest_Dest() {} type TransferResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields SrcAccountNumber uint64 `protobuf:"varint,1,opt,name=src_account_number,json=srcAccountNumber,proto3" json:"src_account_number,omitempty"` SrcBalanceCents int32 `protobuf:"varint,2,opt,name=src_balance_cents,json=srcBalanceCents,proto3" json:"src_balance_cents,omitempty"` DestAccountNumber uint64 `protobuf:"varint,3,opt,name=dest_account_number,json=destAccountNumber,proto3" json:"dest_account_number,omitempty"` DestBalanceCents int32 `protobuf:"varint,4,opt,name=dest_balance_cents,json=destBalanceCents,proto3" json:"dest_balance_cents,omitempty"` } func (x *TransferResponse) Reset() { *x = TransferResponse{} if protoimpl.UnsafeEnabled { mi := &file_bank_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *TransferResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*TransferResponse) ProtoMessage() {} func (x *TransferResponse) ProtoReflect() protoreflect.Message { mi := &file_bank_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use TransferResponse.ProtoReflect.Descriptor instead. func (*TransferResponse) Descriptor() ([]byte, []int) { return file_bank_proto_rawDescGZIP(), []int{10} } func (x *TransferResponse) GetSrcAccountNumber() uint64 { if x != nil { return x.SrcAccountNumber } return 0 } func (x *TransferResponse) GetSrcBalanceCents() int32 { if x != nil { return x.SrcBalanceCents } return 0 } func (x *TransferResponse) GetDestAccountNumber() uint64 { if x != nil { return x.DestAccountNumber } return 0 } func (x *TransferResponse) GetDestBalanceCents() int32 { if x != nil { return x.DestBalanceCents } return 0 } type TransferRequest_ExternalAccount struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields AchRoutingNumber uint64 `protobuf:"varint,1,opt,name=ach_routing_number,json=achRoutingNumber,proto3" json:"ach_routing_number,omitempty"` AchAccountNumber uint64 `protobuf:"varint,2,opt,name=ach_account_number,json=achAccountNumber,proto3" json:"ach_account_number,omitempty"` } func (x *TransferRequest_ExternalAccount) Reset() { *x = TransferRequest_ExternalAccount{} if protoimpl.UnsafeEnabled { mi := &file_bank_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *TransferRequest_ExternalAccount) String() string { return protoimpl.X.MessageStringOf(x) } func (*TransferRequest_ExternalAccount) ProtoMessage() {} func (x *TransferRequest_ExternalAccount) ProtoReflect() protoreflect.Message { mi := &file_bank_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use TransferRequest_ExternalAccount.ProtoReflect.Descriptor instead. func (*TransferRequest_ExternalAccount) Descriptor() ([]byte, []int) { return file_bank_proto_rawDescGZIP(), []int{9, 0} } func (x *TransferRequest_ExternalAccount) GetAchRoutingNumber() uint64 { if x != nil { return x.AchRoutingNumber } return 0 } func (x *TransferRequest_ExternalAccount) GetAchAccountNumber() uint64 { if x != nil { return x.AchAccountNumber } return 0 } var File_bank_proto protoreflect.FileDescriptor var file_bank_proto_rawDesc = []byte{ 0x0a, 0x0a, 0x62, 0x61, 0x6e, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x6b, 0x0a, 0x12, 0x4f, 0x70, 0x65, 0x6e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x5f, 0x63, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x21, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3c, 0x0a, 0x13, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x3b, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0xe5, 0x01, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x63, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x43, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x6b, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x41, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c, 0x4d, 0x4f, 0x4e, 0x45, 0x59, 0x5f, 0x4d, 0x41, 0x52, 0x4b, 0x45, 0x54, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x4c, 0x49, 0x4e, 0x45, 0x5f, 0x4f, 0x46, 0x5f, 0x43, 0x52, 0x45, 0x44, 0x49, 0x54, 0x10, 0x04, 0x12, 0x08, 0x0a, 0x04, 0x4c, 0x4f, 0x41, 0x4e, 0x10, 0x05, 0x12, 0x0c, 0x0a, 0x08, 0x45, 0x51, 0x55, 0x49, 0x54, 0x49, 0x45, 0x53, 0x10, 0x06, 0x22, 0x9f, 0x01, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x30, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x2c, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x22, 0xba, 0x01, 0x0a, 0x0b, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x71, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x73, 0x65, 0x71, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x2e, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x04, 0x64, 0x61, 0x74, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x63, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x73, 0x63, 0x22, 0xdd, 0x01, 0x0a, 0x0e, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x63, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2e, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x73, 0x63, 0x22, 0x3d, 0x0a, 0x06, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x43, 0x41, 0x53, 0x48, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x43, 0x48, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x49, 0x52, 0x45, 0x10, 0x04, 0x22, 0x5d, 0x0a, 0x0f, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x63, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x43, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x6f, 0x0a, 0x0f, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x63, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x73, 0x63, 0x22, 0xc7, 0x03, 0x0a, 0x0f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x15, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x48, 0x00, 0x52, 0x13, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x4b, 0x0a, 0x0f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x30, 0x0a, 0x13, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x48, 0x01, 0x52, 0x11, 0x64, 0x65, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x47, 0x0a, 0x0d, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x48, 0x01, 0x52, 0x0c, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x44, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x63, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x43, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x63, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x73, 0x63, 0x1a, 0x6d, 0x0a, 0x0f, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x63, 0x68, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x61, 0x63, 0x68, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x63, 0x68, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x61, 0x63, 0x68, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x42, 0x08, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x74, 0x22, 0xca, 0x01, 0x0a, 0x10, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x73, 0x72, 0x63, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x73, 0x72, 0x63, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x72, 0x63, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x63, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x73, 0x72, 0x63, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x43, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x64, 0x65, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x2c, 0x0a, 0x12, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x63, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x64, 0x65, 0x73, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x43, 0x65, 0x6e, 0x74, 0x73, 0x32, 0xfa, 0x02, 0x0a, 0x04, 0x42, 0x61, 0x6e, 0x6b, 0x12, 0x2c, 0x0a, 0x0b, 0x4f, 0x70, 0x65, 0x6e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x13, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x08, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x0c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x14, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3b, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x14, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x17, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x30, 0x01, 0x12, 0x2c, 0x0a, 0x07, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x12, 0x0f, 0x2e, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x08, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x12, 0x10, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x08, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x12, 0x10, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x3b, 0x6d, 0x61, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_bank_proto_rawDescOnce sync.Once file_bank_proto_rawDescData = file_bank_proto_rawDesc ) func file_bank_proto_rawDescGZIP() []byte { file_bank_proto_rawDescOnce.Do(func() { file_bank_proto_rawDescData = protoimpl.X.CompressGZIP(file_bank_proto_rawDescData) }) return file_bank_proto_rawDescData } var file_bank_proto_enumTypes = make([]protoimpl.EnumInfo, 2) var file_bank_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_bank_proto_goTypes = []interface{}{ (Account_Type)(0), // 0: Account.Type (DepositRequest_Source)(0), // 1: DepositRequest.Source (*OpenAccountRequest)(nil), // 2: OpenAccountRequest (*CloseAccountRequest)(nil), // 3: CloseAccountRequest (*GetAccountsResponse)(nil), // 4: GetAccountsResponse (*Account)(nil), // 5: Account (*GetTransactionsRequest)(nil), // 6: GetTransactionsRequest (*Transaction)(nil), // 7: Transaction (*DepositRequest)(nil), // 8: DepositRequest (*BalanceResponse)(nil), // 9: BalanceResponse (*WithdrawRequest)(nil), // 10: WithdrawRequest (*TransferRequest)(nil), // 11: TransferRequest (*TransferResponse)(nil), // 12: TransferResponse (*TransferRequest_ExternalAccount)(nil), // 13: TransferRequest.ExternalAccount (*timestamppb.Timestamp)(nil), // 14: google.protobuf.Timestamp (*emptypb.Empty)(nil), // 15: google.protobuf.Empty } var file_bank_proto_depIdxs = []int32{ 0, // 0: OpenAccountRequest.type:type_name -> Account.Type 5, // 1: GetAccountsResponse.accounts:type_name -> Account 0, // 2: Account.type:type_name -> Account.Type 14, // 3: GetTransactionsRequest.start:type_name -> google.protobuf.Timestamp 14, // 4: GetTransactionsRequest.end:type_name -> google.protobuf.Timestamp 14, // 5: Transaction.date:type_name -> google.protobuf.Timestamp 1, // 6: DepositRequest.source:type_name -> DepositRequest.Source 13, // 7: TransferRequest.external_source:type_name -> TransferRequest.ExternalAccount 13, // 8: TransferRequest.external_dest:type_name -> TransferRequest.ExternalAccount 2, // 9: Bank.OpenAccount:input_type -> OpenAccountRequest 3, // 10: Bank.CloseAccount:input_type -> CloseAccountRequest 15, // 11: Bank.GetAccounts:input_type -> google.protobuf.Empty 6, // 12: Bank.GetTransactions:input_type -> GetTransactionsRequest 8, // 13: Bank.Deposit:input_type -> DepositRequest 10, // 14: Bank.Withdraw:input_type -> WithdrawRequest 11, // 15: Bank.Transfer:input_type -> TransferRequest 5, // 16: Bank.OpenAccount:output_type -> Account 15, // 17: Bank.CloseAccount:output_type -> google.protobuf.Empty 4, // 18: Bank.GetAccounts:output_type -> GetAccountsResponse 7, // 19: Bank.GetTransactions:output_type -> Transaction 9, // 20: Bank.Deposit:output_type -> BalanceResponse 9, // 21: Bank.Withdraw:output_type -> BalanceResponse 12, // 22: Bank.Transfer:output_type -> TransferResponse 16, // [16:23] is the sub-list for method output_type 9, // [9:16] is the sub-list for method input_type 9, // [9:9] is the sub-list for extension type_name 9, // [9:9] is the sub-list for extension extendee 0, // [0:9] is the sub-list for field type_name } func init() { file_bank_proto_init() } func file_bank_proto_init() { if File_bank_proto != nil { return } if !protoimpl.UnsafeEnabled { file_bank_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*OpenAccountRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_bank_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CloseAccountRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_bank_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetAccountsResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_bank_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Account); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_bank_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GetTransactionsRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_bank_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Transaction); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_bank_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DepositRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_bank_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*BalanceResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_bank_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*WithdrawRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_bank_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*TransferRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_bank_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*TransferResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_bank_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*TransferRequest_ExternalAccount); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } file_bank_proto_msgTypes[9].OneofWrappers = []interface{}{ (*TransferRequest_SourceAccountNumber)(nil), (*TransferRequest_ExternalSource)(nil), (*TransferRequest_DestAccountNumber)(nil), (*TransferRequest_ExternalDest)(nil), } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_bank_proto_rawDesc, NumEnums: 2, NumMessages: 12, NumExtensions: 0, NumServices: 1, }, GoTypes: file_bank_proto_goTypes, DependencyIndexes: file_bank_proto_depIdxs, EnumInfos: file_bank_proto_enumTypes, MessageInfos: file_bank_proto_msgTypes, }.Build() File_bank_proto = out.File file_bank_proto_rawDesc = nil file_bank_proto_goTypes = nil file_bank_proto_depIdxs = nil } ================================================ FILE: internal/testing/cmd/bankdemo/bank.proto ================================================ syntax = "proto3"; option go_package = ".;main"; import "google/protobuf/empty.proto"; import "google/protobuf/timestamp.proto"; // Bank provides operations for interacting with bank accounts. All // operations operate for the authenticated user (identified via an // "authorization" request header, where the type is "token" and the // credential is the customer's ID). service Bank { // OpenAccount creates an account with the type and given initial deposit // as its balance. rpc OpenAccount(OpenAccountRequest) returns (Account); // CloseAccount closes the indicated account. An account can only be // closed if its balance is zero. rpc CloseAccount(CloseAccountRequest) returns (google.protobuf.Empty); // GetAccounts lists all accounts for the current customer. rpc GetAccounts(google.protobuf.Empty) returns (GetAccountsResponse); // GetTransactions streams all transactions that match the given criteria. // If the given start date is not specified, transactions since beginning // of time are included. Similarly, if the given end date is not specified, // transactions all the way to the presnet are included. rpc GetTransactions(GetTransactionsRequest) returns (stream Transaction); // Deposit increases the balance of an account by depositing funds into it. rpc Deposit(DepositRequest) returns (BalanceResponse); // Withdraw decreases the balance of an account by withdrawing funds from it. rpc Withdraw(WithdrawRequest) returns (BalanceResponse); // Transfer moves money from one account to another. The source and destination // accounts can be with this bank (e.g. "local" account numbers) or can be // external accounts, identified by their ACH routing and account numbers. rpc Transfer(TransferRequest) returns (TransferResponse); } message OpenAccountRequest { int32 initial_deposit_cents = 1; Account.Type type = 2; } message CloseAccountRequest { uint64 account_number = 1; } message GetAccountsResponse { repeated Account accounts = 1; } message Account { uint64 account_number = 1; enum Type { UNKNOWN = 0; CHECKING = 1; SAVING = 2; MONEY_MARKET = 3; LINE_OF_CREDIT = 4; LOAN = 5; EQUITIES = 6; } Type type = 2; int32 balance_cents = 3; } message GetTransactionsRequest { uint64 account_number = 1; google.protobuf.Timestamp start = 2; google.protobuf.Timestamp end = 3; } message Transaction { uint64 account_number = 1; uint64 seq_number = 2; google.protobuf.Timestamp date = 3; int32 amount_cents = 4; string desc = 5; } message DepositRequest { uint64 account_number = 1; int32 amount_cents = 2; enum Source { UNKNOWN = 0; CASH = 1; CHECK = 2; ACH = 3; WIRE = 4; } Source source = 3; string desc = 4; } message BalanceResponse { uint64 account_number = 1; int32 balance_cents = 2; } message WithdrawRequest { uint64 account_number = 1; int32 amount_cents = 2; string desc = 3; } message TransferRequest { message ExternalAccount { uint64 ach_routing_number = 1; uint64 ach_account_number = 2; } oneof source { uint64 source_account_number = 1; ExternalAccount external_source = 2; } oneof dest { uint64 dest_account_number = 3; ExternalAccount external_dest = 4; } int32 amount_cents = 5; string desc = 6; } message TransferResponse { uint64 src_account_number = 1; int32 src_balance_cents = 2; uint64 dest_account_number = 3; int32 dest_balance_cents = 4; } ================================================ FILE: internal/testing/cmd/bankdemo/bank_grpc.pb.go ================================================ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. package main import ( context "context" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" emptypb "google.golang.org/protobuf/types/known/emptypb" ) // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. // Requires gRPC-Go v1.32.0 or later. const _ = grpc.SupportPackageIsVersion7 // BankClient is the client API for Bank service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type BankClient interface { // OpenAccount creates an account with the type and given initial deposit // as its balance. OpenAccount(ctx context.Context, in *OpenAccountRequest, opts ...grpc.CallOption) (*Account, error) // CloseAccount closes the indicated account. An account can only be // closed if its balance is zero. CloseAccount(ctx context.Context, in *CloseAccountRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) // GetAccounts lists all accounts for the current customer. GetAccounts(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*GetAccountsResponse, error) // GetTransactions streams all transactions that match the given criteria. // If the given start date is not specified, transactions since beginning // of time are included. Similarly, if the given end date is not specified, // transactions all the way to the presnet are included. GetTransactions(ctx context.Context, in *GetTransactionsRequest, opts ...grpc.CallOption) (Bank_GetTransactionsClient, error) // Deposit increases the balance of an account by depositing funds into it. Deposit(ctx context.Context, in *DepositRequest, opts ...grpc.CallOption) (*BalanceResponse, error) // Withdraw decreases the balance of an account by withdrawing funds from it. Withdraw(ctx context.Context, in *WithdrawRequest, opts ...grpc.CallOption) (*BalanceResponse, error) // Transfer moves money from one account to another. The source and destination // accounts can be with this bank (e.g. "local" account numbers) or can be // external accounts, identified by their ACH routing and account numbers. Transfer(ctx context.Context, in *TransferRequest, opts ...grpc.CallOption) (*TransferResponse, error) } type bankClient struct { cc grpc.ClientConnInterface } func NewBankClient(cc grpc.ClientConnInterface) BankClient { return &bankClient{cc} } func (c *bankClient) OpenAccount(ctx context.Context, in *OpenAccountRequest, opts ...grpc.CallOption) (*Account, error) { out := new(Account) err := c.cc.Invoke(ctx, "/Bank/OpenAccount", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *bankClient) CloseAccount(ctx context.Context, in *CloseAccountRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { out := new(emptypb.Empty) err := c.cc.Invoke(ctx, "/Bank/CloseAccount", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *bankClient) GetAccounts(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*GetAccountsResponse, error) { out := new(GetAccountsResponse) err := c.cc.Invoke(ctx, "/Bank/GetAccounts", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *bankClient) GetTransactions(ctx context.Context, in *GetTransactionsRequest, opts ...grpc.CallOption) (Bank_GetTransactionsClient, error) { stream, err := c.cc.NewStream(ctx, &Bank_ServiceDesc.Streams[0], "/Bank/GetTransactions", opts...) if err != nil { return nil, err } x := &bankGetTransactionsClient{stream} if err := x.ClientStream.SendMsg(in); err != nil { return nil, err } if err := x.ClientStream.CloseSend(); err != nil { return nil, err } return x, nil } type Bank_GetTransactionsClient interface { Recv() (*Transaction, error) grpc.ClientStream } type bankGetTransactionsClient struct { grpc.ClientStream } func (x *bankGetTransactionsClient) Recv() (*Transaction, error) { m := new(Transaction) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func (c *bankClient) Deposit(ctx context.Context, in *DepositRequest, opts ...grpc.CallOption) (*BalanceResponse, error) { out := new(BalanceResponse) err := c.cc.Invoke(ctx, "/Bank/Deposit", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *bankClient) Withdraw(ctx context.Context, in *WithdrawRequest, opts ...grpc.CallOption) (*BalanceResponse, error) { out := new(BalanceResponse) err := c.cc.Invoke(ctx, "/Bank/Withdraw", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *bankClient) Transfer(ctx context.Context, in *TransferRequest, opts ...grpc.CallOption) (*TransferResponse, error) { out := new(TransferResponse) err := c.cc.Invoke(ctx, "/Bank/Transfer", in, out, opts...) if err != nil { return nil, err } return out, nil } // BankServer is the server API for Bank service. // All implementations must embed UnimplementedBankServer // for forward compatibility type BankServer interface { // OpenAccount creates an account with the type and given initial deposit // as its balance. OpenAccount(context.Context, *OpenAccountRequest) (*Account, error) // CloseAccount closes the indicated account. An account can only be // closed if its balance is zero. CloseAccount(context.Context, *CloseAccountRequest) (*emptypb.Empty, error) // GetAccounts lists all accounts for the current customer. GetAccounts(context.Context, *emptypb.Empty) (*GetAccountsResponse, error) // GetTransactions streams all transactions that match the given criteria. // If the given start date is not specified, transactions since beginning // of time are included. Similarly, if the given end date is not specified, // transactions all the way to the presnet are included. GetTransactions(*GetTransactionsRequest, Bank_GetTransactionsServer) error // Deposit increases the balance of an account by depositing funds into it. Deposit(context.Context, *DepositRequest) (*BalanceResponse, error) // Withdraw decreases the balance of an account by withdrawing funds from it. Withdraw(context.Context, *WithdrawRequest) (*BalanceResponse, error) // Transfer moves money from one account to another. The source and destination // accounts can be with this bank (e.g. "local" account numbers) or can be // external accounts, identified by their ACH routing and account numbers. Transfer(context.Context, *TransferRequest) (*TransferResponse, error) mustEmbedUnimplementedBankServer() } // UnimplementedBankServer must be embedded to have forward compatible implementations. type UnimplementedBankServer struct { } func (UnimplementedBankServer) OpenAccount(context.Context, *OpenAccountRequest) (*Account, error) { return nil, status.Errorf(codes.Unimplemented, "method OpenAccount not implemented") } func (UnimplementedBankServer) CloseAccount(context.Context, *CloseAccountRequest) (*emptypb.Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method CloseAccount not implemented") } func (UnimplementedBankServer) GetAccounts(context.Context, *emptypb.Empty) (*GetAccountsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetAccounts not implemented") } func (UnimplementedBankServer) GetTransactions(*GetTransactionsRequest, Bank_GetTransactionsServer) error { return status.Errorf(codes.Unimplemented, "method GetTransactions not implemented") } func (UnimplementedBankServer) Deposit(context.Context, *DepositRequest) (*BalanceResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Deposit not implemented") } func (UnimplementedBankServer) Withdraw(context.Context, *WithdrawRequest) (*BalanceResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Withdraw not implemented") } func (UnimplementedBankServer) Transfer(context.Context, *TransferRequest) (*TransferResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Transfer not implemented") } func (UnimplementedBankServer) mustEmbedUnimplementedBankServer() {} // UnsafeBankServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to BankServer will // result in compilation errors. type UnsafeBankServer interface { mustEmbedUnimplementedBankServer() } func RegisterBankServer(s grpc.ServiceRegistrar, srv BankServer) { s.RegisterService(&Bank_ServiceDesc, srv) } func _Bank_OpenAccount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(OpenAccountRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(BankServer).OpenAccount(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/Bank/OpenAccount", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(BankServer).OpenAccount(ctx, req.(*OpenAccountRequest)) } return interceptor(ctx, in, info, handler) } func _Bank_CloseAccount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(CloseAccountRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(BankServer).CloseAccount(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/Bank/CloseAccount", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(BankServer).CloseAccount(ctx, req.(*CloseAccountRequest)) } return interceptor(ctx, in, info, handler) } func _Bank_GetAccounts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(emptypb.Empty) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(BankServer).GetAccounts(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/Bank/GetAccounts", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(BankServer).GetAccounts(ctx, req.(*emptypb.Empty)) } return interceptor(ctx, in, info, handler) } func _Bank_GetTransactions_Handler(srv interface{}, stream grpc.ServerStream) error { m := new(GetTransactionsRequest) if err := stream.RecvMsg(m); err != nil { return err } return srv.(BankServer).GetTransactions(m, &bankGetTransactionsServer{stream}) } type Bank_GetTransactionsServer interface { Send(*Transaction) error grpc.ServerStream } type bankGetTransactionsServer struct { grpc.ServerStream } func (x *bankGetTransactionsServer) Send(m *Transaction) error { return x.ServerStream.SendMsg(m) } func _Bank_Deposit_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DepositRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(BankServer).Deposit(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/Bank/Deposit", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(BankServer).Deposit(ctx, req.(*DepositRequest)) } return interceptor(ctx, in, info, handler) } func _Bank_Withdraw_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(WithdrawRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(BankServer).Withdraw(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/Bank/Withdraw", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(BankServer).Withdraw(ctx, req.(*WithdrawRequest)) } return interceptor(ctx, in, info, handler) } func _Bank_Transfer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(TransferRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(BankServer).Transfer(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/Bank/Transfer", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(BankServer).Transfer(ctx, req.(*TransferRequest)) } return interceptor(ctx, in, info, handler) } // Bank_ServiceDesc is the grpc.ServiceDesc for Bank service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var Bank_ServiceDesc = grpc.ServiceDesc{ ServiceName: "Bank", HandlerType: (*BankServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "OpenAccount", Handler: _Bank_OpenAccount_Handler, }, { MethodName: "CloseAccount", Handler: _Bank_CloseAccount_Handler, }, { MethodName: "GetAccounts", Handler: _Bank_GetAccounts_Handler, }, { MethodName: "Deposit", Handler: _Bank_Deposit_Handler, }, { MethodName: "Withdraw", Handler: _Bank_Withdraw_Handler, }, { MethodName: "Transfer", Handler: _Bank_Transfer_Handler, }, }, Streams: []grpc.StreamDesc{ { StreamName: "GetTransactions", Handler: _Bank_GetTransactions_Handler, ServerStreams: true, }, }, Metadata: "bank.proto", } ================================================ FILE: internal/testing/cmd/bankdemo/chat.go ================================================ package main import ( "context" "fmt" "io" "sync" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/timestamppb" ) // chatServer implements the Support gRPC service, for providing // a capability to connect customers and support agents in real-time // chat. type chatServer struct { UnimplementedSupportServer chatsBySession map[string]*session chatsAwaitingAgent []string lastSession int32 mu sync.RWMutex } type session struct { Session active bool cust *listener agents map[string]*listener mu sync.RWMutex } type listener struct { ch chan<- *ChatEntry ctx context.Context } func (l *listener) send(e *ChatEntry) { select { case l.ch <- e: case <-l.ctx.Done(): } } func (s *session) copySession() *Session { s.mu.RLock() defer s.mu.RUnlock() return &Session{ SessionId: s.SessionId, CustomerName: s.Session.CustomerName, History: s.Session.History, } } func (s *chatServer) ChatCustomer(stream Support_ChatCustomerServer) error { ctx, cancel := context.WithCancel(stream.Context()) defer cancel() cust := getCustomer(ctx) if cust == "" { return status.Error(codes.Unauthenticated, codes.Unauthenticated.String()) } var sess *session var ch chan *ChatEntry var chCancel context.CancelFunc cleanup := func() { if sess != nil { sess.mu.Lock() sess.cust = nil sess.mu.Unlock() chCancel() close(ch) go func() { // drain channel to prevent deadlock for range ch { } }() } } defer cleanup() for { req, err := stream.Recv() if err != nil { if err == io.EOF { return nil } return err } switch req := req.Req.(type) { case *ChatCustomerRequest_Init: if sess != nil { return status.Errorf(codes.FailedPrecondition, "already called init, currently in chat session %q", sess.SessionId) } sessionID := req.Init.ResumeSessionId if sessionID == "" { sess, ch, chCancel = s.newSession(ctx, cust) } else if sess, ch, chCancel = s.resumeSession(ctx, cust, sessionID); sess == nil { return status.Errorf(codes.FailedPrecondition, "cannot resume session %q; it is not an open session", sessionID) } err := stream.Send(&ChatCustomerResponse{ Resp: &ChatCustomerResponse_Session{ Session: sess.copySession(), }, }) if err != nil { return err } // monitor the returned channel, sending incoming agent messages down the pipe go func() { for { select { case entry, ok := <-ch: if !ok { return } if e, ok := entry.Entry.(*ChatEntry_AgentMsg); ok { stream.Send(&ChatCustomerResponse{ Resp: &ChatCustomerResponse_Msg{ Msg: e.AgentMsg, }, }) } case <-ctx.Done(): return } } }() case *ChatCustomerRequest_Msg: if sess == nil { return status.Errorf(codes.FailedPrecondition, "never called init, no chat session for message") } entry := &ChatEntry{ Date: timestamppb.Now(), Entry: &ChatEntry_CustomerMsg{ CustomerMsg: req.Msg, }, } func() { sess.mu.Lock() sess.Session.History = append(sess.Session.History, entry) sess.mu.Unlock() sess.mu.RLock() defer sess.mu.RUnlock() for _, l := range sess.agents { l.send(entry) } }() case *ChatCustomerRequest_HangUp: if sess == nil { return status.Errorf(codes.FailedPrecondition, "never called init, no chat session to hang up") } s.closeSession(sess) cleanup() sess = nil default: return status.Error(codes.InvalidArgument, "unknown request type") } } } func (s *chatServer) ChatAgent(stream Support_ChatAgentServer) error { ctx, cancel := context.WithCancel(stream.Context()) defer cancel() agent := getAgent(ctx) if agent == "" { return status.Error(codes.Unauthenticated, codes.Unauthenticated.String()) } var sess *session var ch chan *ChatEntry var chCancel context.CancelFunc cleanup := func() { if sess != nil { sess.mu.Lock() delete(sess.agents, agent) if len(sess.agents) == 0 { s.mu.Lock() s.chatsAwaitingAgent = append(s.chatsAwaitingAgent, sess.SessionId) s.mu.Unlock() } sess.mu.Unlock() chCancel() close(ch) go func() { // drain channel to prevent deadlock for range ch { } }() } } defer cleanup() checkSession := func() { // see if session was concurrently closed if sess != nil { sess.mu.RLock() active := sess.active sess.mu.RUnlock() if !active { cleanup() sess = nil } } } for { req, err := stream.Recv() if err != nil { if err == io.EOF { return nil } return err } checkSession() switch req := req.Req.(type) { case *ChatAgentRequest_Accept: if sess != nil { return status.Errorf(codes.FailedPrecondition, "already called accept, currently in chat session %q", sess.SessionId) } sess, ch, chCancel = s.acceptSession(ctx, agent, req.Accept.SessionId) if sess == nil { return status.Errorf(codes.FailedPrecondition, "no session to accept") } err := stream.Send(&ChatAgentResponse{ Resp: &ChatAgentResponse_AcceptedSession{ AcceptedSession: sess.copySession(), }, }) if err != nil { return err } // monitor the returned channel, sending incoming agent messages down the pipe go func() { for { select { case entry, ok := <-ch: if !ok { return } if entry == nil { stream.Send(&ChatAgentResponse{ Resp: &ChatAgentResponse_SessionEnded{ SessionEnded: Void_VOID, }, }) continue } if agentMsg, ok := entry.Entry.(*ChatEntry_AgentMsg); ok { if agentMsg.AgentMsg.AgentName == agent { continue } } stream.Send(&ChatAgentResponse{ Resp: &ChatAgentResponse_Msg{ Msg: entry, }, }) case <-ctx.Done(): return } } }() case *ChatAgentRequest_Msg: if sess == nil { return status.Errorf(codes.FailedPrecondition, "never called accept, no chat session for message") } entry := &ChatEntry{ Date: timestamppb.Now(), Entry: &ChatEntry_AgentMsg{ AgentMsg: &AgentMessage{ AgentName: agent, Msg: req.Msg, }, }, } active := true func() { sess.mu.Lock() active = sess.active if active { sess.Session.History = append(sess.Session.History, entry) } sess.mu.Unlock() if !active { return } sess.mu.RLock() defer sess.mu.RUnlock() if sess.cust != nil { sess.cust.send(entry) } for otherAgent, l := range sess.agents { if otherAgent == agent { continue } l.send(entry) } }() if !active { return status.Errorf(codes.FailedPrecondition, "customer hung up on chat session %s", sess.SessionId) } case *ChatAgentRequest_LeaveSession: if sess == nil { return status.Errorf(codes.FailedPrecondition, "never called init, no chat session to hang up") } s.closeSession(sess) cleanup() sess = nil default: return status.Error(codes.InvalidArgument, "unknown request type") } } } func (s *chatServer) newSession(ctx context.Context, cust string) (*session, chan *ChatEntry, context.CancelFunc) { s.mu.Lock() defer s.mu.Unlock() s.lastSession++ id := fmt.Sprintf("%06d", s.lastSession) s.chatsAwaitingAgent = append(s.chatsAwaitingAgent, id) ch := make(chan *ChatEntry, 1) ctx, cancel := context.WithCancel(ctx) l := &listener{ ch: ch, ctx: ctx, } sess := session{ active: true, Session: Session{ SessionId: id, CustomerName: cust, }, cust: l, } s.chatsBySession[id] = &sess return &sess, ch, cancel } func (s *chatServer) resumeSession(ctx context.Context, cust, sessionID string) (*session, chan *ChatEntry, context.CancelFunc) { s.mu.Lock() defer s.mu.Unlock() sess := s.chatsBySession[sessionID] if sess.CustomerName != cust { // customer cannot join chat that they did not start return nil, nil, nil } if !sess.active { // chat has been closed return nil, nil, nil } if sess.cust != nil { // customer is active in the chat in another stream! return nil, nil, nil } ch := make(chan *ChatEntry, 1) ctx, cancel := context.WithCancel(ctx) l := &listener{ ch: ch, ctx: ctx, } sess.cust = l return sess, ch, cancel } func (s *chatServer) closeSession(sess *session) { active := true func() { sess.mu.Lock() active = sess.active sess.active = false sess.mu.Unlock() if !active { // already closed return } sess.mu.RLock() defer sess.mu.RUnlock() for _, l := range sess.agents { l.send(nil) } }() if !active { // already closed return } s.mu.Lock() defer s.mu.Unlock() delete(s.chatsBySession, sess.SessionId) for i, id := range s.chatsAwaitingAgent { if id == sess.SessionId { s.chatsAwaitingAgent = append(s.chatsAwaitingAgent[:i], s.chatsAwaitingAgent[i+1:]...) break } } } func (s *chatServer) acceptSession(ctx context.Context, agent, sessionID string) (*session, chan *ChatEntry, context.CancelFunc) { var sess *session func() { s.mu.Lock() defer s.mu.Unlock() if len(s.chatsAwaitingAgent) == 0 { return } if sessionID == "" { sessionID = s.chatsAwaitingAgent[0] s.chatsAwaitingAgent = s.chatsAwaitingAgent[1:] } else { found := false for i, id := range s.chatsAwaitingAgent { if id == sessionID { found = true s.chatsAwaitingAgent = append(s.chatsAwaitingAgent[:i], s.chatsAwaitingAgent[i+1:]...) break } } if !found { return } } sess = s.chatsBySession[sessionID] }() if sess == nil { return nil, nil, nil } ch := make(chan *ChatEntry, 1) ctx, cancel := context.WithCancel(ctx) l := &listener{ ch: ch, ctx: ctx, } sess.mu.Lock() if sess.agents == nil { sess.agents = map[string]*listener{} } sess.agents[agent] = l sess.mu.Unlock() return sess, ch, cancel } ================================================ FILE: internal/testing/cmd/bankdemo/db.go ================================================ package main import ( "fmt" "sync" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/types/known/timestamppb" ) // In-memory database that is periodically saved to a JSON file. type accounts struct { AccountNumbersByCustomer map[string][]uint64 AccountsByNumber map[uint64]*account AccountNumbers []uint64 Customers []string LastAccountNum uint64 mu sync.RWMutex } func (a *accounts) openAccount(customer string, accountType Account_Type, initialBalanceCents int32) *Account { a.mu.Lock() defer a.mu.Unlock() accountNums, ok := a.AccountNumbersByCustomer[customer] if !ok { // no accounts for this customer? it's a new customer a.Customers = append(a.Customers, customer) } num := a.LastAccountNum + 1 a.LastAccountNum = num a.AccountNumbers = append(a.AccountNumbers, num) accountNums = append(accountNums, num) a.AccountNumbersByCustomer[customer] = accountNums var acct account acct.AccountNumber = num acct.Type = accountType acct.BalanceCents = initialBalanceCents acct.Transactions = append(acct.Transactions, &Transaction{ AccountNumber: num, SeqNumber: 1, Date: timestamppb.Now(), AmountCents: initialBalanceCents, Desc: "initial deposit", }) a.AccountsByNumber[num] = &acct return &acct.Account } func (a *accounts) closeAccount(customer string, accountNumber uint64) error { a.mu.Lock() defer a.mu.Unlock() acctNums := a.AccountNumbersByCustomer[customer] found := -1 for i, num := range acctNums { if num == accountNumber { found = i break } } if found == -1 { return status.Errorf(codes.NotFound, "you have no account numbered %d", accountNumber) } acct := a.AccountsByNumber[accountNumber] if acct.BalanceCents != 0 { return status.Errorf(codes.FailedPrecondition, "account %d cannot be closed because it has a non-zero balance: %s", accountNumber, dollars(acct.BalanceCents)) } for i, num := range a.AccountNumbers { if num == accountNumber { a.AccountNumbers = append(a.AccountNumbers[:i], a.AccountNumbers[i+1:]...) break } } a.AccountNumbersByCustomer[customer] = append(acctNums[:found], acctNums[found+1:]...) delete(a.AccountsByNumber, accountNumber) return nil } func (a *accounts) getAccount(customer string, accountNumber uint64) (*account, error) { a.mu.RLock() defer a.mu.RUnlock() acctNums := a.AccountNumbersByCustomer[customer] for _, num := range acctNums { if num == accountNumber { return a.AccountsByNumber[num], nil } } return nil, status.Errorf(codes.NotFound, "you have no account numbered %d", accountNumber) } func (a *accounts) getAllAccounts(customer string) []*Account { a.mu.RLock() defer a.mu.RUnlock() accountNums := a.AccountNumbersByCustomer[customer] var accounts []*Account for _, num := range accountNums { accounts = append(accounts, &a.AccountsByNumber[num].Account) } return accounts } type account struct { Account Transactions []*Transaction mu sync.RWMutex } func (a *account) getTransactions() []*Transaction { a.mu.RLock() defer a.mu.RUnlock() return a.Transactions } func (a *account) newTransaction(amountCents int32, desc string) (newBalance int32, err error) { a.mu.Lock() defer a.mu.Unlock() bal := a.BalanceCents + amountCents if bal < 0 { return 0, status.Errorf(codes.FailedPrecondition, "insufficient funds: cannot withdraw %s when balance is %s", dollars(amountCents), dollars(a.BalanceCents)) } a.BalanceCents = bal a.Transactions = append(a.Transactions, &Transaction{ AccountNumber: a.AccountNumber, Date: timestamppb.Now(), AmountCents: amountCents, SeqNumber: uint64(len(a.Transactions) + 1), Desc: desc, }) return bal, nil } func (t *Transaction) MarshalJSON() ([]byte, error) { return protojson.Marshal(t) } func (t *Transaction) UnmarshalJSON(b []byte) error { return protojson.Unmarshal(b, t) } func (a *accounts) clone() *accounts { var clone accounts clone.AccountNumbersByCustomer = map[string][]uint64{} clone.AccountsByNumber = map[uint64]*account{} a.mu.RLock() clone.Customers = a.Customers a.mu.RUnlock() for _, cust := range clone.Customers { var acctNums []uint64 a.mu.RLock() acctNums = a.AccountNumbersByCustomer[cust] a.mu.RUnlock() clone.AccountNumbersByCustomer[cust] = acctNums clone.AccountNumbers = append(clone.AccountNumbers, acctNums...) for _, acctNum := range acctNums { a.mu.RLock() acct := a.AccountsByNumber[acctNum] a.mu.RUnlock() acct.mu.RLock() txns := acct.Transactions acct.mu.RUnlock() clone.AccountsByNumber[acctNum] = &account{Transactions: txns} } } return &clone } func dollars(amountCents int32) string { return fmt.Sprintf("$%02f", float64(amountCents)/100) } ================================================ FILE: internal/testing/cmd/bankdemo/main.go ================================================ package main //go:generate protoc --go_out=. --go-grpc_out=. bank.proto support.proto import ( "context" "encoding/json" "flag" "fmt" "net" "os" "os/signal" "sync/atomic" "syscall" "time" "google.golang.org/grpc" "google.golang.org/grpc/grpclog" "google.golang.org/grpc/peer" "google.golang.org/grpc/reflection" "google.golang.org/grpc/status" ) func main() { grpclog.SetLoggerV2(grpclog.NewLoggerV2(os.Stdout, os.Stdout, os.Stderr)) port := flag.Int("port", 12345, "The port on which bankdemo gRPC server will listen.") datafile := flag.String("datafile", "accounts.json", "The path and filename to which bank account data is saved and from which data will be loaded.") flag.Parse() // create the server and load initial dataset ctx, cancel := context.WithCancel(context.Background()) s := &svr{ datafile: *datafile, ctx: ctx, cancel: cancel, } if err := s.load(); err != nil { panic(err) } l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", *port)) if err != nil { panic(err) } grpcSvr := gRPCServer() // Register gRPC service implementations bankSvc := bankServer{ allAccounts: &s.allAccounts, } RegisterBankServer(grpcSvr, &bankSvc) chatSvc := chatServer{ chatsBySession: map[string]*session{}, } RegisterSupportServer(grpcSvr, &chatSvc) go s.bgSaver() // don't forget to include server reflection support! reflection.Register(grpcSvr) defer func() { cancel() s.flush() }() // trap SIGINT / SIGTERM to exit cleanly c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGINT) signal.Notify(c, syscall.SIGTERM) go func() { <-c fmt.Println("Shutting down...") grpcSvr.GracefulStop() }() grpclog.Infof("server starting, listening on %v", l.Addr()) if err := grpcSvr.Serve(l); err != nil { panic(err) } } func gRPCServer() *grpc.Server { var reqCounter uint64 return grpc.NewServer( grpc.UnaryInterceptor(func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { reqID := atomic.AddUint64(&reqCounter, 1) var client string if p, ok := peer.FromContext(ctx); ok { client = p.Addr.String() } else { client = "?" } grpclog.Infof("request %d started for %s from %s", reqID, info.FullMethod, client) rsp, err := handler(ctx, req) stat, _ := status.FromError(err) grpclog.Infof("request %d completed for %s from %s: %v %s", reqID, info.FullMethod, client, stat.Code(), stat.Message()) return rsp, err }), grpc.StreamInterceptor(func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { reqID := atomic.AddUint64(&reqCounter, 1) var client string if p, ok := peer.FromContext(ss.Context()); ok { client = p.Addr.String() } else { client = "?" } grpclog.Infof("request %d started for %s from %s", reqID, info.FullMethod, client) err := handler(srv, ss) stat, _ := status.FromError(err) grpclog.Infof("request %d completed for %s from %s: %v %s", reqID, info.FullMethod, client, stat.Code(), stat.Message()) return err })) } type svr struct { datafile string ctx context.Context cancel context.CancelFunc allAccounts accounts } func (s *svr) load() error { accts, err := os.ReadFile(s.datafile) if err != nil && !os.IsNotExist(err) { return err } if len(accts) == 0 { s.allAccounts.AccountNumbersByCustomer = map[string][]uint64{} s.allAccounts.AccountsByNumber = map[uint64]*account{} } else if err := json.Unmarshal(accts, &s.allAccounts); err != nil { return err } return nil } func (s *svr) bgSaver() { ticker := time.NewTicker(5 * time.Second) for { select { case <-ticker.C: s.flush() case <-s.ctx.Done(): ticker.Stop() return } } } func (s *svr) flush() { accounts := s.allAccounts.clone() if b, err := json.Marshal(accounts); err != nil { grpclog.Errorf("failed to save data to %q", s.datafile) } else if err := os.WriteFile(s.datafile, b, 0666); err != nil { grpclog.Errorf("failed to save data to %q", s.datafile) } } ================================================ FILE: internal/testing/cmd/bankdemo/support.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0-devel // protoc v4.22.0 // source: support.proto package main import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Void int32 const ( Void_VOID Void = 0 ) // Enum value maps for Void. var ( Void_name = map[int32]string{ 0: "VOID", } Void_value = map[string]int32{ "VOID": 0, } ) func (x Void) Enum() *Void { p := new(Void) *p = x return p } func (x Void) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (Void) Descriptor() protoreflect.EnumDescriptor { return file_support_proto_enumTypes[0].Descriptor() } func (Void) Type() protoreflect.EnumType { return &file_support_proto_enumTypes[0] } func (x Void) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use Void.Descriptor instead. func (Void) EnumDescriptor() ([]byte, []int) { return file_support_proto_rawDescGZIP(), []int{0} } type ChatCustomerRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Types that are assignable to Req: // // *ChatCustomerRequest_Init // *ChatCustomerRequest_Msg // *ChatCustomerRequest_HangUp Req isChatCustomerRequest_Req `protobuf_oneof:"req"` } func (x *ChatCustomerRequest) Reset() { *x = ChatCustomerRequest{} if protoimpl.UnsafeEnabled { mi := &file_support_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ChatCustomerRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*ChatCustomerRequest) ProtoMessage() {} func (x *ChatCustomerRequest) ProtoReflect() protoreflect.Message { mi := &file_support_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ChatCustomerRequest.ProtoReflect.Descriptor instead. func (*ChatCustomerRequest) Descriptor() ([]byte, []int) { return file_support_proto_rawDescGZIP(), []int{0} } func (m *ChatCustomerRequest) GetReq() isChatCustomerRequest_Req { if m != nil { return m.Req } return nil } func (x *ChatCustomerRequest) GetInit() *InitiateChat { if x, ok := x.GetReq().(*ChatCustomerRequest_Init); ok { return x.Init } return nil } func (x *ChatCustomerRequest) GetMsg() string { if x, ok := x.GetReq().(*ChatCustomerRequest_Msg); ok { return x.Msg } return "" } func (x *ChatCustomerRequest) GetHangUp() Void { if x, ok := x.GetReq().(*ChatCustomerRequest_HangUp); ok { return x.HangUp } return Void_VOID } type isChatCustomerRequest_Req interface { isChatCustomerRequest_Req() } type ChatCustomerRequest_Init struct { // init is used when a chat stream is not part of a // chat session. This is a stream's initial state, as well as // the state after a "hang_up" request is sent. This creates // a new state session or resumes an existing one. Init *InitiateChat `protobuf:"bytes,1,opt,name=init,proto3,oneof"` } type ChatCustomerRequest_Msg struct { // msg is used to send the customer's messages to support // agents. Msg string `protobuf:"bytes,2,opt,name=msg,proto3,oneof"` } type ChatCustomerRequest_HangUp struct { // hang_up is used to terminate a chat session. If a stream // is broken, but the session was not terminated, the client // may initiate a new stream and use init to resume that // session. Sessions are not terminated unless done so // explicitly via sending this kind of request on the stream. HangUp Void `protobuf:"varint,3,opt,name=hang_up,json=hangUp,proto3,enum=Void,oneof"` } func (*ChatCustomerRequest_Init) isChatCustomerRequest_Req() {} func (*ChatCustomerRequest_Msg) isChatCustomerRequest_Req() {} func (*ChatCustomerRequest_HangUp) isChatCustomerRequest_Req() {} type InitiateChat struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields ResumeSessionId string `protobuf:"bytes,1,opt,name=resume_session_id,json=resumeSessionId,proto3" json:"resume_session_id,omitempty"` } func (x *InitiateChat) Reset() { *x = InitiateChat{} if protoimpl.UnsafeEnabled { mi := &file_support_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *InitiateChat) String() string { return protoimpl.X.MessageStringOf(x) } func (*InitiateChat) ProtoMessage() {} func (x *InitiateChat) ProtoReflect() protoreflect.Message { mi := &file_support_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use InitiateChat.ProtoReflect.Descriptor instead. func (*InitiateChat) Descriptor() ([]byte, []int) { return file_support_proto_rawDescGZIP(), []int{1} } func (x *InitiateChat) GetResumeSessionId() string { if x != nil { return x.ResumeSessionId } return "" } type AgentMessage struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields AgentName string `protobuf:"bytes,1,opt,name=agent_name,json=agentName,proto3" json:"agent_name,omitempty"` Msg string `protobuf:"bytes,2,opt,name=msg,proto3" json:"msg,omitempty"` } func (x *AgentMessage) Reset() { *x = AgentMessage{} if protoimpl.UnsafeEnabled { mi := &file_support_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AgentMessage) String() string { return protoimpl.X.MessageStringOf(x) } func (*AgentMessage) ProtoMessage() {} func (x *AgentMessage) ProtoReflect() protoreflect.Message { mi := &file_support_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AgentMessage.ProtoReflect.Descriptor instead. func (*AgentMessage) Descriptor() ([]byte, []int) { return file_support_proto_rawDescGZIP(), []int{2} } func (x *AgentMessage) GetAgentName() string { if x != nil { return x.AgentName } return "" } func (x *AgentMessage) GetMsg() string { if x != nil { return x.Msg } return "" } type ChatCustomerResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Types that are assignable to Resp: // // *ChatCustomerResponse_Session // *ChatCustomerResponse_Msg Resp isChatCustomerResponse_Resp `protobuf_oneof:"resp"` } func (x *ChatCustomerResponse) Reset() { *x = ChatCustomerResponse{} if protoimpl.UnsafeEnabled { mi := &file_support_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ChatCustomerResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*ChatCustomerResponse) ProtoMessage() {} func (x *ChatCustomerResponse) ProtoReflect() protoreflect.Message { mi := &file_support_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ChatCustomerResponse.ProtoReflect.Descriptor instead. func (*ChatCustomerResponse) Descriptor() ([]byte, []int) { return file_support_proto_rawDescGZIP(), []int{3} } func (m *ChatCustomerResponse) GetResp() isChatCustomerResponse_Resp { if m != nil { return m.Resp } return nil } func (x *ChatCustomerResponse) GetSession() *Session { if x, ok := x.GetResp().(*ChatCustomerResponse_Session); ok { return x.Session } return nil } func (x *ChatCustomerResponse) GetMsg() *AgentMessage { if x, ok := x.GetResp().(*ChatCustomerResponse_Msg); ok { return x.Msg } return nil } type isChatCustomerResponse_Resp interface { isChatCustomerResponse_Resp() } type ChatCustomerResponse_Session struct { // session is sent from the server when the stream is connected // to a chat session. This happens after an init request is sent // and the stream is connected to either a new or resumed session. Session *Session `protobuf:"bytes,1,opt,name=session,proto3,oneof"` } type ChatCustomerResponse_Msg struct { // msg is sent from the server to convey agents' messages to the // customer. Msg *AgentMessage `protobuf:"bytes,2,opt,name=msg,proto3,oneof"` } func (*ChatCustomerResponse_Session) isChatCustomerResponse_Resp() {} func (*ChatCustomerResponse_Msg) isChatCustomerResponse_Resp() {} type ChatAgentRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Types that are assignable to Req: // // *ChatAgentRequest_Accept // *ChatAgentRequest_Msg // *ChatAgentRequest_LeaveSession Req isChatAgentRequest_Req `protobuf_oneof:"req"` } func (x *ChatAgentRequest) Reset() { *x = ChatAgentRequest{} if protoimpl.UnsafeEnabled { mi := &file_support_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ChatAgentRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*ChatAgentRequest) ProtoMessage() {} func (x *ChatAgentRequest) ProtoReflect() protoreflect.Message { mi := &file_support_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ChatAgentRequest.ProtoReflect.Descriptor instead. func (*ChatAgentRequest) Descriptor() ([]byte, []int) { return file_support_proto_rawDescGZIP(), []int{4} } func (m *ChatAgentRequest) GetReq() isChatAgentRequest_Req { if m != nil { return m.Req } return nil } func (x *ChatAgentRequest) GetAccept() *AcceptChat { if x, ok := x.GetReq().(*ChatAgentRequest_Accept); ok { return x.Accept } return nil } func (x *ChatAgentRequest) GetMsg() string { if x, ok := x.GetReq().(*ChatAgentRequest_Msg); ok { return x.Msg } return "" } func (x *ChatAgentRequest) GetLeaveSession() Void { if x, ok := x.GetReq().(*ChatAgentRequest_LeaveSession); ok { return x.LeaveSession } return Void_VOID } type isChatAgentRequest_Req interface { isChatAgentRequest_Req() } type ChatAgentRequest_Accept struct { // accept is used when an agent wants to join a customer chat // session. It can be used to connect to a specific session (by // ID), or to just accept the session for which the customer has // been waiting the longest (e.g. poll a FIFO queue of sessions // awaiting a support agent). It is possible for multiple agents // to be connected to the same chat session. Accept *AcceptChat `protobuf:"bytes,1,opt,name=accept,proto3,oneof"` } type ChatAgentRequest_Msg struct { // msg is used to send a message to the customer. It will also be // delivered to any other connected support agents. Msg string `protobuf:"bytes,2,opt,name=msg,proto3,oneof"` } type ChatAgentRequest_LeaveSession struct { // leave_session allows an agent to exit a chat session. They can // always re-enter later by sending an accept message for that // session ID. LeaveSession Void `protobuf:"varint,3,opt,name=leave_session,json=leaveSession,proto3,enum=Void,oneof"` } func (*ChatAgentRequest_Accept) isChatAgentRequest_Req() {} func (*ChatAgentRequest_Msg) isChatAgentRequest_Req() {} func (*ChatAgentRequest_LeaveSession) isChatAgentRequest_Req() {} type AcceptChat struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields SessionId string `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` } func (x *AcceptChat) Reset() { *x = AcceptChat{} if protoimpl.UnsafeEnabled { mi := &file_support_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AcceptChat) String() string { return protoimpl.X.MessageStringOf(x) } func (*AcceptChat) ProtoMessage() {} func (x *AcceptChat) ProtoReflect() protoreflect.Message { mi := &file_support_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AcceptChat.ProtoReflect.Descriptor instead. func (*AcceptChat) Descriptor() ([]byte, []int) { return file_support_proto_rawDescGZIP(), []int{5} } func (x *AcceptChat) GetSessionId() string { if x != nil { return x.SessionId } return "" } type ChatEntry struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Date *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=date,proto3" json:"date,omitempty"` // Types that are assignable to Entry: // // *ChatEntry_CustomerMsg // *ChatEntry_AgentMsg Entry isChatEntry_Entry `protobuf_oneof:"entry"` } func (x *ChatEntry) Reset() { *x = ChatEntry{} if protoimpl.UnsafeEnabled { mi := &file_support_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ChatEntry) String() string { return protoimpl.X.MessageStringOf(x) } func (*ChatEntry) ProtoMessage() {} func (x *ChatEntry) ProtoReflect() protoreflect.Message { mi := &file_support_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ChatEntry.ProtoReflect.Descriptor instead. func (*ChatEntry) Descriptor() ([]byte, []int) { return file_support_proto_rawDescGZIP(), []int{6} } func (x *ChatEntry) GetDate() *timestamppb.Timestamp { if x != nil { return x.Date } return nil } func (m *ChatEntry) GetEntry() isChatEntry_Entry { if m != nil { return m.Entry } return nil } func (x *ChatEntry) GetCustomerMsg() string { if x, ok := x.GetEntry().(*ChatEntry_CustomerMsg); ok { return x.CustomerMsg } return "" } func (x *ChatEntry) GetAgentMsg() *AgentMessage { if x, ok := x.GetEntry().(*ChatEntry_AgentMsg); ok { return x.AgentMsg } return nil } type isChatEntry_Entry interface { isChatEntry_Entry() } type ChatEntry_CustomerMsg struct { CustomerMsg string `protobuf:"bytes,2,opt,name=customer_msg,json=customerMsg,proto3,oneof"` } type ChatEntry_AgentMsg struct { AgentMsg *AgentMessage `protobuf:"bytes,3,opt,name=agent_msg,json=agentMsg,proto3,oneof"` } func (*ChatEntry_CustomerMsg) isChatEntry_Entry() {} func (*ChatEntry_AgentMsg) isChatEntry_Entry() {} type ChatAgentResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Types that are assignable to Resp: // // *ChatAgentResponse_AcceptedSession // *ChatAgentResponse_Msg // *ChatAgentResponse_SessionEnded Resp isChatAgentResponse_Resp `protobuf_oneof:"resp"` } func (x *ChatAgentResponse) Reset() { *x = ChatAgentResponse{} if protoimpl.UnsafeEnabled { mi := &file_support_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ChatAgentResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*ChatAgentResponse) ProtoMessage() {} func (x *ChatAgentResponse) ProtoReflect() protoreflect.Message { mi := &file_support_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ChatAgentResponse.ProtoReflect.Descriptor instead. func (*ChatAgentResponse) Descriptor() ([]byte, []int) { return file_support_proto_rawDescGZIP(), []int{7} } func (m *ChatAgentResponse) GetResp() isChatAgentResponse_Resp { if m != nil { return m.Resp } return nil } func (x *ChatAgentResponse) GetAcceptedSession() *Session { if x, ok := x.GetResp().(*ChatAgentResponse_AcceptedSession); ok { return x.AcceptedSession } return nil } func (x *ChatAgentResponse) GetMsg() *ChatEntry { if x, ok := x.GetResp().(*ChatAgentResponse_Msg); ok { return x.Msg } return nil } func (x *ChatAgentResponse) GetSessionEnded() Void { if x, ok := x.GetResp().(*ChatAgentResponse_SessionEnded); ok { return x.SessionEnded } return Void_VOID } type isChatAgentResponse_Resp interface { isChatAgentResponse_Resp() } type ChatAgentResponse_AcceptedSession struct { // accepted_session provides the detail of a chat session. The server // sends this message after the agent has accepted a chat session. AcceptedSession *Session `protobuf:"bytes,1,opt,name=accepted_session,json=acceptedSession,proto3,oneof"` } type ChatAgentResponse_Msg struct { // msg is sent by the server when the customer, or another support // agent, sends a message in stream's current session. Msg *ChatEntry `protobuf:"bytes,2,opt,name=msg,proto3,oneof"` } type ChatAgentResponse_SessionEnded struct { // session_ended notifies the support agent that their currently // connected chat session has been terminated by the customer. SessionEnded Void `protobuf:"varint,3,opt,name=session_ended,json=sessionEnded,proto3,enum=Void,oneof"` } func (*ChatAgentResponse_AcceptedSession) isChatAgentResponse_Resp() {} func (*ChatAgentResponse_Msg) isChatAgentResponse_Resp() {} func (*ChatAgentResponse_SessionEnded) isChatAgentResponse_Resp() {} type Session struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields SessionId string `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` CustomerName string `protobuf:"bytes,2,opt,name=customer_name,json=customerName,proto3" json:"customer_name,omitempty"` History []*ChatEntry `protobuf:"bytes,3,rep,name=history,proto3" json:"history,omitempty"` } func (x *Session) Reset() { *x = Session{} if protoimpl.UnsafeEnabled { mi := &file_support_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Session) String() string { return protoimpl.X.MessageStringOf(x) } func (*Session) ProtoMessage() {} func (x *Session) ProtoReflect() protoreflect.Message { mi := &file_support_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Session.ProtoReflect.Descriptor instead. func (*Session) Descriptor() ([]byte, []int) { return file_support_proto_rawDescGZIP(), []int{8} } func (x *Session) GetSessionId() string { if x != nil { return x.SessionId } return "" } func (x *Session) GetCustomerName() string { if x != nil { return x.CustomerName } return "" } func (x *Session) GetHistory() []*ChatEntry { if x != nil { return x.History } return nil } var File_support_proto protoreflect.FileDescriptor var file_support_proto_rawDesc = []byte{ 0x0a, 0x0d, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x77, 0x0a, 0x13, 0x43, 0x68, 0x61, 0x74, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x04, 0x69, 0x6e, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x74, 0x48, 0x00, 0x52, 0x04, 0x69, 0x6e, 0x69, 0x74, 0x12, 0x12, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x20, 0x0a, 0x07, 0x68, 0x61, 0x6e, 0x67, 0x5f, 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x05, 0x2e, 0x56, 0x6f, 0x69, 0x64, 0x48, 0x00, 0x52, 0x06, 0x68, 0x61, 0x6e, 0x67, 0x55, 0x70, 0x42, 0x05, 0x0a, 0x03, 0x72, 0x65, 0x71, 0x22, 0x3a, 0x0a, 0x0c, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x72, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x3f, 0x0a, 0x0c, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x22, 0x67, 0x0a, 0x14, 0x43, 0x68, 0x61, 0x74, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x07, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x07, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x42, 0x06, 0x0a, 0x04, 0x72, 0x65, 0x73, 0x70, 0x22, 0x82, 0x01, 0x0a, 0x10, 0x43, 0x68, 0x61, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x43, 0x68, 0x61, 0x74, 0x48, 0x00, 0x52, 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x12, 0x12, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x2c, 0x0a, 0x0d, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x05, 0x2e, 0x56, 0x6f, 0x69, 0x64, 0x48, 0x00, 0x52, 0x0c, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x05, 0x0a, 0x03, 0x72, 0x65, 0x71, 0x22, 0x2b, 0x0a, 0x0a, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x43, 0x68, 0x61, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x97, 0x01, 0x0a, 0x09, 0x43, 0x68, 0x61, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x2e, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x04, 0x64, 0x61, 0x74, 0x65, 0x12, 0x23, 0x0a, 0x0c, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x4d, 0x73, 0x67, 0x12, 0x2c, 0x0a, 0x09, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x4d, 0x73, 0x67, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x22, 0xa0, 0x01, 0x0a, 0x11, 0x43, 0x68, 0x61, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x10, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x0f, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x43, 0x68, 0x61, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x48, 0x00, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x2c, 0x0a, 0x0d, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x05, 0x2e, 0x56, 0x6f, 0x69, 0x64, 0x48, 0x00, 0x52, 0x0c, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x65, 0x64, 0x42, 0x06, 0x0a, 0x04, 0x72, 0x65, 0x73, 0x70, 0x22, 0x73, 0x0a, 0x07, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x07, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x43, 0x68, 0x61, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x2a, 0x10, 0x0a, 0x04, 0x56, 0x6f, 0x69, 0x64, 0x12, 0x08, 0x0a, 0x04, 0x56, 0x4f, 0x49, 0x44, 0x10, 0x00, 0x32, 0x82, 0x01, 0x0a, 0x07, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x3f, 0x0a, 0x0c, 0x43, 0x68, 0x61, 0x74, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x12, 0x14, 0x2e, 0x43, 0x68, 0x61, 0x74, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x43, 0x68, 0x61, 0x74, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x12, 0x36, 0x0a, 0x09, 0x43, 0x68, 0x61, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x11, 0x2e, 0x43, 0x68, 0x61, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x43, 0x68, 0x61, 0x74, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x3b, 0x6d, 0x61, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_support_proto_rawDescOnce sync.Once file_support_proto_rawDescData = file_support_proto_rawDesc ) func file_support_proto_rawDescGZIP() []byte { file_support_proto_rawDescOnce.Do(func() { file_support_proto_rawDescData = protoimpl.X.CompressGZIP(file_support_proto_rawDescData) }) return file_support_proto_rawDescData } var file_support_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_support_proto_msgTypes = make([]protoimpl.MessageInfo, 9) var file_support_proto_goTypes = []interface{}{ (Void)(0), // 0: Void (*ChatCustomerRequest)(nil), // 1: ChatCustomerRequest (*InitiateChat)(nil), // 2: InitiateChat (*AgentMessage)(nil), // 3: AgentMessage (*ChatCustomerResponse)(nil), // 4: ChatCustomerResponse (*ChatAgentRequest)(nil), // 5: ChatAgentRequest (*AcceptChat)(nil), // 6: AcceptChat (*ChatEntry)(nil), // 7: ChatEntry (*ChatAgentResponse)(nil), // 8: ChatAgentResponse (*Session)(nil), // 9: Session (*timestamppb.Timestamp)(nil), // 10: google.protobuf.Timestamp } var file_support_proto_depIdxs = []int32{ 2, // 0: ChatCustomerRequest.init:type_name -> InitiateChat 0, // 1: ChatCustomerRequest.hang_up:type_name -> Void 9, // 2: ChatCustomerResponse.session:type_name -> Session 3, // 3: ChatCustomerResponse.msg:type_name -> AgentMessage 6, // 4: ChatAgentRequest.accept:type_name -> AcceptChat 0, // 5: ChatAgentRequest.leave_session:type_name -> Void 10, // 6: ChatEntry.date:type_name -> google.protobuf.Timestamp 3, // 7: ChatEntry.agent_msg:type_name -> AgentMessage 9, // 8: ChatAgentResponse.accepted_session:type_name -> Session 7, // 9: ChatAgentResponse.msg:type_name -> ChatEntry 0, // 10: ChatAgentResponse.session_ended:type_name -> Void 7, // 11: Session.history:type_name -> ChatEntry 1, // 12: Support.ChatCustomer:input_type -> ChatCustomerRequest 5, // 13: Support.ChatAgent:input_type -> ChatAgentRequest 4, // 14: Support.ChatCustomer:output_type -> ChatCustomerResponse 8, // 15: Support.ChatAgent:output_type -> ChatAgentResponse 14, // [14:16] is the sub-list for method output_type 12, // [12:14] is the sub-list for method input_type 12, // [12:12] is the sub-list for extension type_name 12, // [12:12] is the sub-list for extension extendee 0, // [0:12] is the sub-list for field type_name } func init() { file_support_proto_init() } func file_support_proto_init() { if File_support_proto != nil { return } if !protoimpl.UnsafeEnabled { file_support_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ChatCustomerRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_support_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*InitiateChat); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_support_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AgentMessage); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_support_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ChatCustomerResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_support_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ChatAgentRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_support_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AcceptChat); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_support_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ChatEntry); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_support_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ChatAgentResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_support_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Session); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } file_support_proto_msgTypes[0].OneofWrappers = []interface{}{ (*ChatCustomerRequest_Init)(nil), (*ChatCustomerRequest_Msg)(nil), (*ChatCustomerRequest_HangUp)(nil), } file_support_proto_msgTypes[3].OneofWrappers = []interface{}{ (*ChatCustomerResponse_Session)(nil), (*ChatCustomerResponse_Msg)(nil), } file_support_proto_msgTypes[4].OneofWrappers = []interface{}{ (*ChatAgentRequest_Accept)(nil), (*ChatAgentRequest_Msg)(nil), (*ChatAgentRequest_LeaveSession)(nil), } file_support_proto_msgTypes[6].OneofWrappers = []interface{}{ (*ChatEntry_CustomerMsg)(nil), (*ChatEntry_AgentMsg)(nil), } file_support_proto_msgTypes[7].OneofWrappers = []interface{}{ (*ChatAgentResponse_AcceptedSession)(nil), (*ChatAgentResponse_Msg)(nil), (*ChatAgentResponse_SessionEnded)(nil), } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_support_proto_rawDesc, NumEnums: 1, NumMessages: 9, NumExtensions: 0, NumServices: 1, }, GoTypes: file_support_proto_goTypes, DependencyIndexes: file_support_proto_depIdxs, EnumInfos: file_support_proto_enumTypes, MessageInfos: file_support_proto_msgTypes, }.Build() File_support_proto = out.File file_support_proto_rawDesc = nil file_support_proto_goTypes = nil file_support_proto_depIdxs = nil } ================================================ FILE: internal/testing/cmd/bankdemo/support.proto ================================================ syntax = "proto3"; option go_package = ".;main"; import "google/protobuf/timestamp.proto"; // Support provides an interactive chat service, for customers to interact with // the bank's support agents. A single stream, for either of the two methods, is // a stateful connection to a single "chat session". Streams are initially disconnected // (not part of any session). A stream must be disconnected from a session (via customer // hang up or via agent leaving a session) before it can be connected to a new one. service Support { // ChatCustomer is used by a customer-facing app to send the customer's messages // to a chat session. The customer is how initiates and terminates (via "hangup") // a chat session. Only customers may invoke this method (e.g. requests must // include customer auth credentials). rpc ChatCustomer(stream ChatCustomerRequest) returns (stream ChatCustomerResponse); // ChatAgent is used by an agent-facing app to allow an agent to reply to a // customer's messages in a chat session. The agent may accept a chat session, // which defaults to the session awaiting an agent for the longest period of time // (FIFO queue). rpc ChatAgent(stream ChatAgentRequest) returns (stream ChatAgentResponse); } enum Void { VOID = 0; } message ChatCustomerRequest { oneof req { // init is used when a chat stream is not part of a // chat session. This is a stream's initial state, as well as // the state after a "hang_up" request is sent. This creates // a new state session or resumes an existing one. InitiateChat init = 1; // msg is used to send the customer's messages to support // agents. string msg = 2; // hang_up is used to terminate a chat session. If a stream // is broken, but the session was not terminated, the client // may initiate a new stream and use init to resume that // session. Sessions are not terminated unless done so // explicitly via sending this kind of request on the stream. Void hang_up = 3; } } message InitiateChat { string resume_session_id = 1; } message AgentMessage { string agent_name = 1; string msg = 2; } message ChatCustomerResponse { oneof resp { // session is sent from the server when the stream is connected // to a chat session. This happens after an init request is sent // and the stream is connected to either a new or resumed session. Session session = 1; // msg is sent from the server to convey agents' messages to the // customer. AgentMessage msg = 2; } } message ChatAgentRequest { oneof req { // accept is used when an agent wants to join a customer chat // session. It can be used to connect to a specific session (by // ID), or to just accept the session for which the customer has // been waiting the longest (e.g. poll a FIFO queue of sessions // awaiting a support agent). It is possible for multiple agents // to be connected to the same chat session. AcceptChat accept = 1; // msg is used to send a message to the customer. It will also be // delivered to any other connected support agents. string msg = 2; // leave_session allows an agent to exit a chat session. They can // always re-enter later by sending an accept message for that // session ID. Void leave_session = 3; } } message AcceptChat { string session_id = 1; } message ChatEntry { google.protobuf.Timestamp date = 1; oneof entry { string customer_msg = 2; AgentMessage agent_msg = 3; } } message ChatAgentResponse { oneof resp { // accepted_session provides the detail of a chat session. The server // sends this message after the agent has accepted a chat session. Session accepted_session = 1; // msg is sent by the server when the customer, or another support // agent, sends a message in stream's current session. ChatEntry msg = 2; // session_ended notifies the support agent that their currently // connected chat session has been terminated by the customer. Void session_ended = 3; } } message Session { string session_id = 1; string customer_name = 2; repeated ChatEntry history = 3; } ================================================ FILE: internal/testing/cmd/bankdemo/support_grpc.pb.go ================================================ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. package main import ( context "context" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" ) // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. // Requires gRPC-Go v1.32.0 or later. const _ = grpc.SupportPackageIsVersion7 // SupportClient is the client API for Support service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type SupportClient interface { // ChatCustomer is used by a customer-facing app to send the customer's messages // to a chat session. The customer is how initiates and terminates (via "hangup") // a chat session. Only customers may invoke this method (e.g. requests must // include customer auth credentials). ChatCustomer(ctx context.Context, opts ...grpc.CallOption) (Support_ChatCustomerClient, error) // ChatAgent is used by an agent-facing app to allow an agent to reply to a // customer's messages in a chat session. The agent may accept a chat session, // which defaults to the session awaiting an agent for the longest period of time // (FIFO queue). ChatAgent(ctx context.Context, opts ...grpc.CallOption) (Support_ChatAgentClient, error) } type supportClient struct { cc grpc.ClientConnInterface } func NewSupportClient(cc grpc.ClientConnInterface) SupportClient { return &supportClient{cc} } func (c *supportClient) ChatCustomer(ctx context.Context, opts ...grpc.CallOption) (Support_ChatCustomerClient, error) { stream, err := c.cc.NewStream(ctx, &Support_ServiceDesc.Streams[0], "/Support/ChatCustomer", opts...) if err != nil { return nil, err } x := &supportChatCustomerClient{stream} return x, nil } type Support_ChatCustomerClient interface { Send(*ChatCustomerRequest) error Recv() (*ChatCustomerResponse, error) grpc.ClientStream } type supportChatCustomerClient struct { grpc.ClientStream } func (x *supportChatCustomerClient) Send(m *ChatCustomerRequest) error { return x.ClientStream.SendMsg(m) } func (x *supportChatCustomerClient) Recv() (*ChatCustomerResponse, error) { m := new(ChatCustomerResponse) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func (c *supportClient) ChatAgent(ctx context.Context, opts ...grpc.CallOption) (Support_ChatAgentClient, error) { stream, err := c.cc.NewStream(ctx, &Support_ServiceDesc.Streams[1], "/Support/ChatAgent", opts...) if err != nil { return nil, err } x := &supportChatAgentClient{stream} return x, nil } type Support_ChatAgentClient interface { Send(*ChatAgentRequest) error Recv() (*ChatAgentResponse, error) grpc.ClientStream } type supportChatAgentClient struct { grpc.ClientStream } func (x *supportChatAgentClient) Send(m *ChatAgentRequest) error { return x.ClientStream.SendMsg(m) } func (x *supportChatAgentClient) Recv() (*ChatAgentResponse, error) { m := new(ChatAgentResponse) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } // SupportServer is the server API for Support service. // All implementations must embed UnimplementedSupportServer // for forward compatibility type SupportServer interface { // ChatCustomer is used by a customer-facing app to send the customer's messages // to a chat session. The customer is how initiates and terminates (via "hangup") // a chat session. Only customers may invoke this method (e.g. requests must // include customer auth credentials). ChatCustomer(Support_ChatCustomerServer) error // ChatAgent is used by an agent-facing app to allow an agent to reply to a // customer's messages in a chat session. The agent may accept a chat session, // which defaults to the session awaiting an agent for the longest period of time // (FIFO queue). ChatAgent(Support_ChatAgentServer) error mustEmbedUnimplementedSupportServer() } // UnimplementedSupportServer must be embedded to have forward compatible implementations. type UnimplementedSupportServer struct { } func (UnimplementedSupportServer) ChatCustomer(Support_ChatCustomerServer) error { return status.Errorf(codes.Unimplemented, "method ChatCustomer not implemented") } func (UnimplementedSupportServer) ChatAgent(Support_ChatAgentServer) error { return status.Errorf(codes.Unimplemented, "method ChatAgent not implemented") } func (UnimplementedSupportServer) mustEmbedUnimplementedSupportServer() {} // UnsafeSupportServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to SupportServer will // result in compilation errors. type UnsafeSupportServer interface { mustEmbedUnimplementedSupportServer() } func RegisterSupportServer(s grpc.ServiceRegistrar, srv SupportServer) { s.RegisterService(&Support_ServiceDesc, srv) } func _Support_ChatCustomer_Handler(srv interface{}, stream grpc.ServerStream) error { return srv.(SupportServer).ChatCustomer(&supportChatCustomerServer{stream}) } type Support_ChatCustomerServer interface { Send(*ChatCustomerResponse) error Recv() (*ChatCustomerRequest, error) grpc.ServerStream } type supportChatCustomerServer struct { grpc.ServerStream } func (x *supportChatCustomerServer) Send(m *ChatCustomerResponse) error { return x.ServerStream.SendMsg(m) } func (x *supportChatCustomerServer) Recv() (*ChatCustomerRequest, error) { m := new(ChatCustomerRequest) if err := x.ServerStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func _Support_ChatAgent_Handler(srv interface{}, stream grpc.ServerStream) error { return srv.(SupportServer).ChatAgent(&supportChatAgentServer{stream}) } type Support_ChatAgentServer interface { Send(*ChatAgentResponse) error Recv() (*ChatAgentRequest, error) grpc.ServerStream } type supportChatAgentServer struct { grpc.ServerStream } func (x *supportChatAgentServer) Send(m *ChatAgentResponse) error { return x.ServerStream.SendMsg(m) } func (x *supportChatAgentServer) Recv() (*ChatAgentRequest, error) { m := new(ChatAgentRequest) if err := x.ServerStream.RecvMsg(m); err != nil { return nil, err } return m, nil } // Support_ServiceDesc is the grpc.ServiceDesc for Support service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var Support_ServiceDesc = grpc.ServiceDesc{ ServiceName: "Support", HandlerType: (*SupportServer)(nil), Methods: []grpc.MethodDesc{}, Streams: []grpc.StreamDesc{ { StreamName: "ChatCustomer", Handler: _Support_ChatCustomer_Handler, ServerStreams: true, ClientStreams: true, }, { StreamName: "ChatAgent", Handler: _Support_ChatAgent_Handler, ServerStreams: true, ClientStreams: true, }, }, Metadata: "support.proto", } ================================================ FILE: internal/testing/cmd/testserver/README.md ================================================ # testserver The `testserver` program is a simple server that can be used for testing RPC clients such as `grpcurl`. It implements an RPC interface that is defined in `grpcurl`'s [testing package](https://github.com/fullstorydev/grpcurl/blob/master/testing/example.proto) and also exposes [the implementation](https://godoc.org/github.com/fullstorydev/grpcurl/testing#TestServer) that is defined in that same package. This is the same test interface and implementation that is used in unit tests for `grpcurl`. For a possibly more interesting test server, take a look at `bankdemo`, which is a demo gRPC app that provides a more concrete RPC interface, including full-duplex bidirectional streaming methods, plus an example implementation. ================================================ FILE: internal/testing/cmd/testserver/testserver.go ================================================ // Command testserver spins up a test GRPC server. package main import ( "context" "flag" "fmt" "net" "os" "sync/atomic" "time" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/grpclog" "google.golang.org/grpc/metadata" "google.golang.org/grpc/reflection" "google.golang.org/grpc/status" "github.com/fullstorydev/grpcurl" grpcurl_testing "github.com/fullstorydev/grpcurl/internal/testing" ) var ( getUnixSocket func() string // nil when run on non-unix platforms help = flag.Bool("help", false, "Print usage instructions and exit.") cacert = flag.String("cacert", "", `File containing trusted root certificates for verifying client certs. Ignored if TLS is not in use (e.g. no -cert or -key specified).`) cert = flag.String("cert", "", `File containing server certificate (public key). Must also provide -key option. Server uses plain-text if no -cert and -key options are given.`) key = flag.String("key", "", `File containing server private key. Must also provide -cert option. Server uses plain-text if no -cert and -key options are given.`) requirecert = flag.Bool("requirecert", false, `Require clients to authenticate via client certs. Must be using TLS (e.g. must also provide -cert and -key options).`) port = flag.Int("p", 0, "Port on which to listen. Ephemeral port used if not specified.") noreflect = flag.Bool("noreflect", false, "Indicates that server should not support server reflection.") quiet = flag.Bool("q", false, "Suppresses server request and stream logging.") ) func main() { flag.Parse() if *help { flag.PrintDefaults() os.Exit(0) } grpclog.SetLoggerV2(grpclog.NewLoggerV2(os.Stdout, os.Stdout, os.Stderr)) if len(flag.Args()) > 0 { fmt.Fprintln(os.Stderr, "No arguments expected.") os.Exit(2) } if (*cert == "") != (*key == "") { fmt.Fprintln(os.Stderr, "The -cert and -key arguments must be used together and both be present.") os.Exit(2) } if *requirecert && *cert == "" { fmt.Fprintln(os.Stderr, "The -requirecert arg cannot be used without -cert and -key arguments.") os.Exit(2) } var opts []grpc.ServerOption if *cert != "" { creds, err := grpcurl.ServerTransportCredentials(*cacert, *cert, *key, *requirecert) if err != nil { fmt.Fprintf(os.Stderr, "Failed to configure transport credentials: %v\n", err) os.Exit(1) } opts = []grpc.ServerOption{grpc.Creds(creds)} } if !*quiet { opts = append(opts, grpc.UnaryInterceptor(unaryLogger), grpc.StreamInterceptor(streamLogger)) } var network, addr string if getUnixSocket != nil && getUnixSocket() != "" { network = "unix" addr = getUnixSocket() } else { network = "tcp" addr = fmt.Sprintf("127.0.0.1:%d", *port) } l, err := net.Listen(network, addr) if err != nil { fmt.Fprintf(os.Stderr, "Failed to listen on socket: %v\n", err) os.Exit(1) } fmt.Printf("Listening on %v\n", l.Addr()) svr := grpc.NewServer(opts...) grpcurl_testing.RegisterTestServiceServer(svr, grpcurl_testing.TestServer{}) if !*noreflect { reflection.Register(svr) } if err := svr.Serve(l); err != nil { fmt.Fprintf(os.Stderr, "GRPC server returned error: %v\n", err) os.Exit(1) } } var id int32 func unaryLogger(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { i := atomic.AddInt32(&id, 1) - 1 grpclog.Infof("start <%d>: %s\n", i, info.FullMethod) start := time.Now() rsp, err := handler(ctx, req) var code codes.Code if stat, ok := status.FromError(err); ok { code = stat.Code() } else { code = codes.Unknown } grpclog.Infof("completed <%d>: %v (%d) %v\n", i, code, code, time.Since(start)) return rsp, err } func streamLogger(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { i := atomic.AddInt32(&id, 1) - 1 start := time.Now() grpclog.Infof("start <%d>: %s\n", i, info.FullMethod) err := handler(srv, loggingStream{ss: ss, id: i}) var code codes.Code if stat, ok := status.FromError(err); ok { code = stat.Code() } else { code = codes.Unknown } grpclog.Infof("completed <%d>: %v(%d) %v\n", i, code, code, time.Since(start)) return err } type loggingStream struct { ss grpc.ServerStream id int32 } func (l loggingStream) SetHeader(md metadata.MD) error { return l.ss.SetHeader(md) } func (l loggingStream) SendHeader(md metadata.MD) error { return l.ss.SendHeader(md) } func (l loggingStream) SetTrailer(md metadata.MD) { l.ss.SetTrailer(md) } func (l loggingStream) Context() context.Context { return l.ss.Context() } func (l loggingStream) SendMsg(m interface{}) error { err := l.ss.SendMsg(m) if err == nil { grpclog.Infof("stream <%d>: sent message\n", l.id) } return err } func (l loggingStream) RecvMsg(m interface{}) error { err := l.ss.RecvMsg(m) if err == nil { grpclog.Infof("stream <%d>: received message\n", l.id) } return err } ================================================ FILE: internal/testing/cmd/testserver/unix.go ================================================ //go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris // +build darwin dragonfly freebsd linux netbsd openbsd solaris package main import "flag" var ( unix = flag.String("unix", "", `Use instead of -p to indicate listening on a Unix domain socket instead of a TCP port. If present, must be the path to a domain socket.`) ) func init() { getUnixSocket = func() string { return *unix } } ================================================ FILE: internal/testing/example.proto ================================================ syntax = "proto3"; import "google/protobuf/descriptor.proto"; import "google/protobuf/empty.proto"; import "google/protobuf/timestamp.proto"; import "example2.proto"; message TestRequest { repeated string file_names = 1; repeated Extension extensions = 2; } message TestResponse { map file_protos = 1; google.protobuf.Timestamp last_update_date = 2; } service TestService { rpc GetFiles (TestRequest) returns (TestResponse); rpc Ping (google.protobuf.Empty) returns (google.protobuf.Empty); } ================================================ FILE: internal/testing/example2.proto ================================================ syntax = "proto3"; import "google/protobuf/any.proto"; message Extension { uint64 id = 1; google.protobuf.Any data = 2; } ================================================ FILE: internal/testing/jsonpb_test_proto/test_objects.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0-devel // protoc v3.14.0 // source: test_objects.proto package jsonpb import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" anypb "google.golang.org/protobuf/types/known/anypb" durationpb "google.golang.org/protobuf/types/known/durationpb" structpb "google.golang.org/protobuf/types/known/structpb" timestamppb "google.golang.org/protobuf/types/known/timestamppb" wrapperspb "google.golang.org/protobuf/types/known/wrapperspb" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type KnownTypes struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields An *anypb.Any `protobuf:"bytes,14,opt,name=an" json:"an,omitempty"` Dur *durationpb.Duration `protobuf:"bytes,1,opt,name=dur" json:"dur,omitempty"` St *structpb.Struct `protobuf:"bytes,12,opt,name=st" json:"st,omitempty"` Ts *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=ts" json:"ts,omitempty"` Lv *structpb.ListValue `protobuf:"bytes,15,opt,name=lv" json:"lv,omitempty"` Val *structpb.Value `protobuf:"bytes,16,opt,name=val" json:"val,omitempty"` Dbl *wrapperspb.DoubleValue `protobuf:"bytes,3,opt,name=dbl" json:"dbl,omitempty"` Flt *wrapperspb.FloatValue `protobuf:"bytes,4,opt,name=flt" json:"flt,omitempty"` I64 *wrapperspb.Int64Value `protobuf:"bytes,5,opt,name=i64" json:"i64,omitempty"` U64 *wrapperspb.UInt64Value `protobuf:"bytes,6,opt,name=u64" json:"u64,omitempty"` I32 *wrapperspb.Int32Value `protobuf:"bytes,7,opt,name=i32" json:"i32,omitempty"` U32 *wrapperspb.UInt32Value `protobuf:"bytes,8,opt,name=u32" json:"u32,omitempty"` Bool *wrapperspb.BoolValue `protobuf:"bytes,9,opt,name=bool" json:"bool,omitempty"` Str *wrapperspb.StringValue `protobuf:"bytes,10,opt,name=str" json:"str,omitempty"` Bytes *wrapperspb.BytesValue `protobuf:"bytes,11,opt,name=bytes" json:"bytes,omitempty"` } func (x *KnownTypes) Reset() { *x = KnownTypes{} if protoimpl.UnsafeEnabled { mi := &file_test_objects_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *KnownTypes) String() string { return protoimpl.X.MessageStringOf(x) } func (*KnownTypes) ProtoMessage() {} func (x *KnownTypes) ProtoReflect() protoreflect.Message { mi := &file_test_objects_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use KnownTypes.ProtoReflect.Descriptor instead. func (*KnownTypes) Descriptor() ([]byte, []int) { return file_test_objects_proto_rawDescGZIP(), []int{0} } func (x *KnownTypes) GetAn() *anypb.Any { if x != nil { return x.An } return nil } func (x *KnownTypes) GetDur() *durationpb.Duration { if x != nil { return x.Dur } return nil } func (x *KnownTypes) GetSt() *structpb.Struct { if x != nil { return x.St } return nil } func (x *KnownTypes) GetTs() *timestamppb.Timestamp { if x != nil { return x.Ts } return nil } func (x *KnownTypes) GetLv() *structpb.ListValue { if x != nil { return x.Lv } return nil } func (x *KnownTypes) GetVal() *structpb.Value { if x != nil { return x.Val } return nil } func (x *KnownTypes) GetDbl() *wrapperspb.DoubleValue { if x != nil { return x.Dbl } return nil } func (x *KnownTypes) GetFlt() *wrapperspb.FloatValue { if x != nil { return x.Flt } return nil } func (x *KnownTypes) GetI64() *wrapperspb.Int64Value { if x != nil { return x.I64 } return nil } func (x *KnownTypes) GetU64() *wrapperspb.UInt64Value { if x != nil { return x.U64 } return nil } func (x *KnownTypes) GetI32() *wrapperspb.Int32Value { if x != nil { return x.I32 } return nil } func (x *KnownTypes) GetU32() *wrapperspb.UInt32Value { if x != nil { return x.U32 } return nil } func (x *KnownTypes) GetBool() *wrapperspb.BoolValue { if x != nil { return x.Bool } return nil } func (x *KnownTypes) GetStr() *wrapperspb.StringValue { if x != nil { return x.Str } return nil } func (x *KnownTypes) GetBytes() *wrapperspb.BytesValue { if x != nil { return x.Bytes } return nil } var File_test_objects_proto protoreflect.FileDescriptor var file_test_objects_proto_rawDesc = []byte{ 0x0a, 0x12, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x6a, 0x73, 0x6f, 0x6e, 0x70, 0x62, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xba, 0x05, 0x0a, 0x0a, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x02, 0x61, 0x6e, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x02, 0x61, 0x6e, 0x12, 0x2b, 0x0a, 0x03, 0x64, 0x75, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x03, 0x64, 0x75, 0x72, 0x12, 0x27, 0x0a, 0x02, 0x73, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x02, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x02, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x02, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x02, 0x6c, 0x76, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x02, 0x6c, 0x76, 0x12, 0x28, 0x0a, 0x03, 0x76, 0x61, 0x6c, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x03, 0x76, 0x61, 0x6c, 0x12, 0x2e, 0x0a, 0x03, 0x64, 0x62, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x03, 0x64, 0x62, 0x6c, 0x12, 0x2d, 0x0a, 0x03, 0x66, 0x6c, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x03, 0x66, 0x6c, 0x74, 0x12, 0x2d, 0x0a, 0x03, 0x69, 0x36, 0x34, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x03, 0x69, 0x36, 0x34, 0x12, 0x2e, 0x0a, 0x03, 0x75, 0x36, 0x34, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x03, 0x75, 0x36, 0x34, 0x12, 0x2d, 0x0a, 0x03, 0x69, 0x33, 0x32, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x03, 0x69, 0x33, 0x32, 0x12, 0x2e, 0x0a, 0x03, 0x75, 0x33, 0x32, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x03, 0x75, 0x33, 0x32, 0x12, 0x2e, 0x0a, 0x04, 0x62, 0x6f, 0x6f, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x04, 0x62, 0x6f, 0x6f, 0x6c, 0x12, 0x2e, 0x0a, 0x03, 0x73, 0x74, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x03, 0x73, 0x74, 0x72, 0x12, 0x31, 0x0a, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x79, 0x74, 0x65, 0x73, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x42, 0x0a, 0x5a, 0x08, 0x2e, 0x3b, 0x6a, 0x73, 0x6f, 0x6e, 0x70, 0x62, } var ( file_test_objects_proto_rawDescOnce sync.Once file_test_objects_proto_rawDescData = file_test_objects_proto_rawDesc ) func file_test_objects_proto_rawDescGZIP() []byte { file_test_objects_proto_rawDescOnce.Do(func() { file_test_objects_proto_rawDescData = protoimpl.X.CompressGZIP(file_test_objects_proto_rawDescData) }) return file_test_objects_proto_rawDescData } var file_test_objects_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_test_objects_proto_goTypes = []interface{}{ (*KnownTypes)(nil), // 0: jsonpb.KnownTypes (*anypb.Any)(nil), // 1: google.protobuf.Any (*durationpb.Duration)(nil), // 2: google.protobuf.Duration (*structpb.Struct)(nil), // 3: google.protobuf.Struct (*timestamppb.Timestamp)(nil), // 4: google.protobuf.Timestamp (*structpb.ListValue)(nil), // 5: google.protobuf.ListValue (*structpb.Value)(nil), // 6: google.protobuf.Value (*wrapperspb.DoubleValue)(nil), // 7: google.protobuf.DoubleValue (*wrapperspb.FloatValue)(nil), // 8: google.protobuf.FloatValue (*wrapperspb.Int64Value)(nil), // 9: google.protobuf.Int64Value (*wrapperspb.UInt64Value)(nil), // 10: google.protobuf.UInt64Value (*wrapperspb.Int32Value)(nil), // 11: google.protobuf.Int32Value (*wrapperspb.UInt32Value)(nil), // 12: google.protobuf.UInt32Value (*wrapperspb.BoolValue)(nil), // 13: google.protobuf.BoolValue (*wrapperspb.StringValue)(nil), // 14: google.protobuf.StringValue (*wrapperspb.BytesValue)(nil), // 15: google.protobuf.BytesValue } var file_test_objects_proto_depIdxs = []int32{ 1, // 0: jsonpb.KnownTypes.an:type_name -> google.protobuf.Any 2, // 1: jsonpb.KnownTypes.dur:type_name -> google.protobuf.Duration 3, // 2: jsonpb.KnownTypes.st:type_name -> google.protobuf.Struct 4, // 3: jsonpb.KnownTypes.ts:type_name -> google.protobuf.Timestamp 5, // 4: jsonpb.KnownTypes.lv:type_name -> google.protobuf.ListValue 6, // 5: jsonpb.KnownTypes.val:type_name -> google.protobuf.Value 7, // 6: jsonpb.KnownTypes.dbl:type_name -> google.protobuf.DoubleValue 8, // 7: jsonpb.KnownTypes.flt:type_name -> google.protobuf.FloatValue 9, // 8: jsonpb.KnownTypes.i64:type_name -> google.protobuf.Int64Value 10, // 9: jsonpb.KnownTypes.u64:type_name -> google.protobuf.UInt64Value 11, // 10: jsonpb.KnownTypes.i32:type_name -> google.protobuf.Int32Value 12, // 11: jsonpb.KnownTypes.u32:type_name -> google.protobuf.UInt32Value 13, // 12: jsonpb.KnownTypes.bool:type_name -> google.protobuf.BoolValue 14, // 13: jsonpb.KnownTypes.str:type_name -> google.protobuf.StringValue 15, // 14: jsonpb.KnownTypes.bytes:type_name -> google.protobuf.BytesValue 15, // [15:15] is the sub-list for method output_type 15, // [15:15] is the sub-list for method input_type 15, // [15:15] is the sub-list for extension type_name 15, // [15:15] is the sub-list for extension extendee 0, // [0:15] is the sub-list for field type_name } func init() { file_test_objects_proto_init() } func file_test_objects_proto_init() { if File_test_objects_proto != nil { return } if !protoimpl.UnsafeEnabled { file_test_objects_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*KnownTypes); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_test_objects_proto_rawDesc, NumEnums: 0, NumMessages: 1, NumExtensions: 0, NumServices: 0, }, GoTypes: file_test_objects_proto_goTypes, DependencyIndexes: file_test_objects_proto_depIdxs, MessageInfos: file_test_objects_proto_msgTypes, }.Build() File_test_objects_proto = out.File file_test_objects_proto_rawDesc = nil file_test_objects_proto_goTypes = nil file_test_objects_proto_depIdxs = nil } ================================================ FILE: internal/testing/jsonpb_test_proto/test_objects.proto ================================================ syntax = "proto2"; import "google/protobuf/any.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/struct.proto"; import "google/protobuf/timestamp.proto"; import "google/protobuf/wrappers.proto"; package jsonpb; option go_package=".;jsonpb"; message KnownTypes { optional google.protobuf.Any an = 14; optional google.protobuf.Duration dur = 1; optional google.protobuf.Struct st = 12; optional google.protobuf.Timestamp ts = 2; optional google.protobuf.ListValue lv = 15; optional google.protobuf.Value val = 16; optional google.protobuf.DoubleValue dbl = 3; optional google.protobuf.FloatValue flt = 4; optional google.protobuf.Int64Value i64 = 5; optional google.protobuf.UInt64Value u64 = 6; optional google.protobuf.Int32Value i32 = 7; optional google.protobuf.UInt32Value u32 = 8; optional google.protobuf.BoolValue bool = 9; optional google.protobuf.StringValue str = 10; optional google.protobuf.BytesValue bytes = 11; } ================================================ FILE: internal/testing/test.pb.go ================================================ // NB: Copied from the gRPC Go repo: google.golang.org/grpc/interop/grpc_testing/test.proto // Copyright 2017 gRPC authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // An integration test service that covers all the method signature permutations // of unary/streaming requests/responses. // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0-devel // protoc v4.22.0 // source: test.proto package testing import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 // The type of payload that should be returned. type PayloadType int32 const ( // Compressable text format. PayloadType_COMPRESSABLE PayloadType = 0 // Uncompressable binary format. PayloadType_UNCOMPRESSABLE PayloadType = 1 // Randomly chosen from all other formats defined in this enum. PayloadType_RANDOM PayloadType = 2 ) // Enum value maps for PayloadType. var ( PayloadType_name = map[int32]string{ 0: "COMPRESSABLE", 1: "UNCOMPRESSABLE", 2: "RANDOM", } PayloadType_value = map[string]int32{ "COMPRESSABLE": 0, "UNCOMPRESSABLE": 1, "RANDOM": 2, } ) func (x PayloadType) Enum() *PayloadType { p := new(PayloadType) *p = x return p } func (x PayloadType) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (PayloadType) Descriptor() protoreflect.EnumDescriptor { return file_test_proto_enumTypes[0].Descriptor() } func (PayloadType) Type() protoreflect.EnumType { return &file_test_proto_enumTypes[0] } func (x PayloadType) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use PayloadType.Descriptor instead. func (PayloadType) EnumDescriptor() ([]byte, []int) { return file_test_proto_rawDescGZIP(), []int{0} } type Empty struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *Empty) Reset() { *x = Empty{} if protoimpl.UnsafeEnabled { mi := &file_test_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Empty) String() string { return protoimpl.X.MessageStringOf(x) } func (*Empty) ProtoMessage() {} func (x *Empty) ProtoReflect() protoreflect.Message { mi := &file_test_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Empty.ProtoReflect.Descriptor instead. func (*Empty) Descriptor() ([]byte, []int) { return file_test_proto_rawDescGZIP(), []int{0} } // A block of data, to simply increase gRPC message size. type Payload struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // The type of data in body. Type PayloadType `protobuf:"varint,1,opt,name=type,proto3,enum=testing.PayloadType" json:"type,omitempty"` // Primary contents of payload. Body []byte `protobuf:"bytes,2,opt,name=body,proto3" json:"body,omitempty"` } func (x *Payload) Reset() { *x = Payload{} if protoimpl.UnsafeEnabled { mi := &file_test_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Payload) String() string { return protoimpl.X.MessageStringOf(x) } func (*Payload) ProtoMessage() {} func (x *Payload) ProtoReflect() protoreflect.Message { mi := &file_test_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Payload.ProtoReflect.Descriptor instead. func (*Payload) Descriptor() ([]byte, []int) { return file_test_proto_rawDescGZIP(), []int{1} } func (x *Payload) GetType() PayloadType { if x != nil { return x.Type } return PayloadType_COMPRESSABLE } func (x *Payload) GetBody() []byte { if x != nil { return x.Body } return nil } // A protobuf representation for grpc status. This is used by test // clients to specify a status that the server should attempt to return. type EchoStatus struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Code int32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"` Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` } func (x *EchoStatus) Reset() { *x = EchoStatus{} if protoimpl.UnsafeEnabled { mi := &file_test_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *EchoStatus) String() string { return protoimpl.X.MessageStringOf(x) } func (*EchoStatus) ProtoMessage() {} func (x *EchoStatus) ProtoReflect() protoreflect.Message { mi := &file_test_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use EchoStatus.ProtoReflect.Descriptor instead. func (*EchoStatus) Descriptor() ([]byte, []int) { return file_test_proto_rawDescGZIP(), []int{2} } func (x *EchoStatus) GetCode() int32 { if x != nil { return x.Code } return 0 } func (x *EchoStatus) GetMessage() string { if x != nil { return x.Message } return "" } // Unary request. type SimpleRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Desired payload type in the response from the server. // If response_type is RANDOM, server randomly chooses one from other formats. ResponseType PayloadType `protobuf:"varint,1,opt,name=response_type,json=responseType,proto3,enum=testing.PayloadType" json:"response_type,omitempty"` // Desired payload size in the response from the server. // If response_type is COMPRESSABLE, this denotes the size before compression. ResponseSize int32 `protobuf:"varint,2,opt,name=response_size,json=responseSize,proto3" json:"response_size,omitempty"` // Optional input payload sent along with the request. Payload *Payload `protobuf:"bytes,3,opt,name=payload,proto3" json:"payload,omitempty"` // Whether SimpleResponse should include username. FillUsername bool `protobuf:"varint,4,opt,name=fill_username,json=fillUsername,proto3" json:"fill_username,omitempty"` // Whether SimpleResponse should include OAuth scope. FillOauthScope bool `protobuf:"varint,5,opt,name=fill_oauth_scope,json=fillOauthScope,proto3" json:"fill_oauth_scope,omitempty"` // Whether server should return a given status ResponseStatus *EchoStatus `protobuf:"bytes,7,opt,name=response_status,json=responseStatus,proto3" json:"response_status,omitempty"` } func (x *SimpleRequest) Reset() { *x = SimpleRequest{} if protoimpl.UnsafeEnabled { mi := &file_test_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SimpleRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*SimpleRequest) ProtoMessage() {} func (x *SimpleRequest) ProtoReflect() protoreflect.Message { mi := &file_test_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SimpleRequest.ProtoReflect.Descriptor instead. func (*SimpleRequest) Descriptor() ([]byte, []int) { return file_test_proto_rawDescGZIP(), []int{3} } func (x *SimpleRequest) GetResponseType() PayloadType { if x != nil { return x.ResponseType } return PayloadType_COMPRESSABLE } func (x *SimpleRequest) GetResponseSize() int32 { if x != nil { return x.ResponseSize } return 0 } func (x *SimpleRequest) GetPayload() *Payload { if x != nil { return x.Payload } return nil } func (x *SimpleRequest) GetFillUsername() bool { if x != nil { return x.FillUsername } return false } func (x *SimpleRequest) GetFillOauthScope() bool { if x != nil { return x.FillOauthScope } return false } func (x *SimpleRequest) GetResponseStatus() *EchoStatus { if x != nil { return x.ResponseStatus } return nil } // Unary response, as configured by the request. type SimpleResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Payload to increase message size. Payload *Payload `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` // The user the request came from, for verifying authentication was // successful when the client expected it. Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"` // OAuth scope. OauthScope string `protobuf:"bytes,3,opt,name=oauth_scope,json=oauthScope,proto3" json:"oauth_scope,omitempty"` } func (x *SimpleResponse) Reset() { *x = SimpleResponse{} if protoimpl.UnsafeEnabled { mi := &file_test_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *SimpleResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*SimpleResponse) ProtoMessage() {} func (x *SimpleResponse) ProtoReflect() protoreflect.Message { mi := &file_test_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use SimpleResponse.ProtoReflect.Descriptor instead. func (*SimpleResponse) Descriptor() ([]byte, []int) { return file_test_proto_rawDescGZIP(), []int{4} } func (x *SimpleResponse) GetPayload() *Payload { if x != nil { return x.Payload } return nil } func (x *SimpleResponse) GetUsername() string { if x != nil { return x.Username } return "" } func (x *SimpleResponse) GetOauthScope() string { if x != nil { return x.OauthScope } return "" } // Client-streaming request. type StreamingInputCallRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Optional input payload sent along with the request. Payload *Payload `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` } func (x *StreamingInputCallRequest) Reset() { *x = StreamingInputCallRequest{} if protoimpl.UnsafeEnabled { mi := &file_test_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *StreamingInputCallRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*StreamingInputCallRequest) ProtoMessage() {} func (x *StreamingInputCallRequest) ProtoReflect() protoreflect.Message { mi := &file_test_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use StreamingInputCallRequest.ProtoReflect.Descriptor instead. func (*StreamingInputCallRequest) Descriptor() ([]byte, []int) { return file_test_proto_rawDescGZIP(), []int{5} } func (x *StreamingInputCallRequest) GetPayload() *Payload { if x != nil { return x.Payload } return nil } // Client-streaming response. type StreamingInputCallResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Aggregated size of payloads received from the client. AggregatedPayloadSize int32 `protobuf:"varint,1,opt,name=aggregated_payload_size,json=aggregatedPayloadSize,proto3" json:"aggregated_payload_size,omitempty"` } func (x *StreamingInputCallResponse) Reset() { *x = StreamingInputCallResponse{} if protoimpl.UnsafeEnabled { mi := &file_test_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *StreamingInputCallResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*StreamingInputCallResponse) ProtoMessage() {} func (x *StreamingInputCallResponse) ProtoReflect() protoreflect.Message { mi := &file_test_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use StreamingInputCallResponse.ProtoReflect.Descriptor instead. func (*StreamingInputCallResponse) Descriptor() ([]byte, []int) { return file_test_proto_rawDescGZIP(), []int{6} } func (x *StreamingInputCallResponse) GetAggregatedPayloadSize() int32 { if x != nil { return x.AggregatedPayloadSize } return 0 } // Configuration for a particular response. type ResponseParameters struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Desired payload sizes in responses from the server. // If response_type is COMPRESSABLE, this denotes the size before compression. Size int32 `protobuf:"varint,1,opt,name=size,proto3" json:"size,omitempty"` // Desired interval between consecutive responses in the response stream in // microseconds. IntervalUs int32 `protobuf:"varint,2,opt,name=interval_us,json=intervalUs,proto3" json:"interval_us,omitempty"` } func (x *ResponseParameters) Reset() { *x = ResponseParameters{} if protoimpl.UnsafeEnabled { mi := &file_test_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ResponseParameters) String() string { return protoimpl.X.MessageStringOf(x) } func (*ResponseParameters) ProtoMessage() {} func (x *ResponseParameters) ProtoReflect() protoreflect.Message { mi := &file_test_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ResponseParameters.ProtoReflect.Descriptor instead. func (*ResponseParameters) Descriptor() ([]byte, []int) { return file_test_proto_rawDescGZIP(), []int{7} } func (x *ResponseParameters) GetSize() int32 { if x != nil { return x.Size } return 0 } func (x *ResponseParameters) GetIntervalUs() int32 { if x != nil { return x.IntervalUs } return 0 } // Server-streaming request. type StreamingOutputCallRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Desired payload type in the response from the server. // If response_type is RANDOM, the payload from each response in the stream // might be of different types. This is to simulate a mixed type of payload // stream. ResponseType PayloadType `protobuf:"varint,1,opt,name=response_type,json=responseType,proto3,enum=testing.PayloadType" json:"response_type,omitempty"` // Configuration for each expected response message. ResponseParameters []*ResponseParameters `protobuf:"bytes,2,rep,name=response_parameters,json=responseParameters,proto3" json:"response_parameters,omitempty"` // Optional input payload sent along with the request. Payload *Payload `protobuf:"bytes,3,opt,name=payload,proto3" json:"payload,omitempty"` // Whether server should return a given status ResponseStatus *EchoStatus `protobuf:"bytes,7,opt,name=response_status,json=responseStatus,proto3" json:"response_status,omitempty"` } func (x *StreamingOutputCallRequest) Reset() { *x = StreamingOutputCallRequest{} if protoimpl.UnsafeEnabled { mi := &file_test_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *StreamingOutputCallRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*StreamingOutputCallRequest) ProtoMessage() {} func (x *StreamingOutputCallRequest) ProtoReflect() protoreflect.Message { mi := &file_test_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use StreamingOutputCallRequest.ProtoReflect.Descriptor instead. func (*StreamingOutputCallRequest) Descriptor() ([]byte, []int) { return file_test_proto_rawDescGZIP(), []int{8} } func (x *StreamingOutputCallRequest) GetResponseType() PayloadType { if x != nil { return x.ResponseType } return PayloadType_COMPRESSABLE } func (x *StreamingOutputCallRequest) GetResponseParameters() []*ResponseParameters { if x != nil { return x.ResponseParameters } return nil } func (x *StreamingOutputCallRequest) GetPayload() *Payload { if x != nil { return x.Payload } return nil } func (x *StreamingOutputCallRequest) GetResponseStatus() *EchoStatus { if x != nil { return x.ResponseStatus } return nil } // Server-streaming response, as configured by the request and parameters. type StreamingOutputCallResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Payload to increase response size. Payload *Payload `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` } func (x *StreamingOutputCallResponse) Reset() { *x = StreamingOutputCallResponse{} if protoimpl.UnsafeEnabled { mi := &file_test_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *StreamingOutputCallResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*StreamingOutputCallResponse) ProtoMessage() {} func (x *StreamingOutputCallResponse) ProtoReflect() protoreflect.Message { mi := &file_test_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use StreamingOutputCallResponse.ProtoReflect.Descriptor instead. func (*StreamingOutputCallResponse) Descriptor() ([]byte, []int) { return file_test_proto_rawDescGZIP(), []int{9} } func (x *StreamingOutputCallResponse) GetPayload() *Payload { if x != nil { return x.Payload } return nil } var File_test_proto protoreflect.FileDescriptor var file_test_proto_rawDesc = []byte{ 0x0a, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x47, 0x0a, 0x07, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x28, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x22, 0x3a, 0x0a, 0x0a, 0x45, 0x63, 0x68, 0x6f, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xa8, 0x02, 0x0a, 0x0d, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x39, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x2a, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x69, 0x6c, 0x6c, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x66, 0x69, 0x6c, 0x6c, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x69, 0x6c, 0x6c, 0x5f, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x66, 0x69, 0x6c, 0x6c, 0x4f, 0x61, 0x75, 0x74, 0x68, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x3c, 0x0a, 0x0f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x45, 0x63, 0x68, 0x6f, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0e, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x79, 0x0a, 0x0e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x22, 0x47, 0x0a, 0x19, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x54, 0x0a, 0x1a, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x17, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x15, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x49, 0x0a, 0x12, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x55, 0x73, 0x22, 0x8f, 0x02, 0x0a, 0x1a, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x39, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x4c, 0x0a, 0x13, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x52, 0x12, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x2a, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x3c, 0x0a, 0x0f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x45, 0x63, 0x68, 0x6f, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0e, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x49, 0x0a, 0x1b, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x2a, 0x3f, 0x0a, 0x0b, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x54, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x43, 0x4f, 0x4d, 0x50, 0x52, 0x45, 0x53, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x55, 0x4e, 0x43, 0x4f, 0x4d, 0x50, 0x52, 0x45, 0x53, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x52, 0x41, 0x4e, 0x44, 0x4f, 0x4d, 0x10, 0x02, 0x32, 0xff, 0x03, 0x0a, 0x0b, 0x54, 0x65, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2b, 0x0a, 0x09, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x43, 0x61, 0x6c, 0x6c, 0x12, 0x0e, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0e, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3c, 0x0a, 0x09, 0x55, 0x6e, 0x61, 0x72, 0x79, 0x43, 0x61, 0x6c, 0x6c, 0x12, 0x16, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x13, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x43, 0x61, 0x6c, 0x6c, 0x12, 0x23, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x5f, 0x0a, 0x12, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x43, 0x61, 0x6c, 0x6c, 0x12, 0x22, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x12, 0x5f, 0x0a, 0x0e, 0x46, 0x75, 0x6c, 0x6c, 0x44, 0x75, 0x70, 0x6c, 0x65, 0x78, 0x43, 0x61, 0x6c, 0x6c, 0x12, 0x23, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x12, 0x5f, 0x0a, 0x0e, 0x48, 0x61, 0x6c, 0x66, 0x44, 0x75, 0x70, 0x6c, 0x65, 0x78, 0x43, 0x61, 0x6c, 0x6c, 0x12, 0x23, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x32, 0x4b, 0x0a, 0x14, 0x55, 0x6e, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x65, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x11, 0x55, 0x6e, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x65, 0x64, 0x43, 0x61, 0x6c, 0x6c, 0x12, 0x0e, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0e, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x0b, 0x5a, 0x09, 0x2e, 0x3b, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_test_proto_rawDescOnce sync.Once file_test_proto_rawDescData = file_test_proto_rawDesc ) func file_test_proto_rawDescGZIP() []byte { file_test_proto_rawDescOnce.Do(func() { file_test_proto_rawDescData = protoimpl.X.CompressGZIP(file_test_proto_rawDescData) }) return file_test_proto_rawDescData } var file_test_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_test_proto_msgTypes = make([]protoimpl.MessageInfo, 10) var file_test_proto_goTypes = []interface{}{ (PayloadType)(0), // 0: testing.PayloadType (*Empty)(nil), // 1: testing.Empty (*Payload)(nil), // 2: testing.Payload (*EchoStatus)(nil), // 3: testing.EchoStatus (*SimpleRequest)(nil), // 4: testing.SimpleRequest (*SimpleResponse)(nil), // 5: testing.SimpleResponse (*StreamingInputCallRequest)(nil), // 6: testing.StreamingInputCallRequest (*StreamingInputCallResponse)(nil), // 7: testing.StreamingInputCallResponse (*ResponseParameters)(nil), // 8: testing.ResponseParameters (*StreamingOutputCallRequest)(nil), // 9: testing.StreamingOutputCallRequest (*StreamingOutputCallResponse)(nil), // 10: testing.StreamingOutputCallResponse } var file_test_proto_depIdxs = []int32{ 0, // 0: testing.Payload.type:type_name -> testing.PayloadType 0, // 1: testing.SimpleRequest.response_type:type_name -> testing.PayloadType 2, // 2: testing.SimpleRequest.payload:type_name -> testing.Payload 3, // 3: testing.SimpleRequest.response_status:type_name -> testing.EchoStatus 2, // 4: testing.SimpleResponse.payload:type_name -> testing.Payload 2, // 5: testing.StreamingInputCallRequest.payload:type_name -> testing.Payload 0, // 6: testing.StreamingOutputCallRequest.response_type:type_name -> testing.PayloadType 8, // 7: testing.StreamingOutputCallRequest.response_parameters:type_name -> testing.ResponseParameters 2, // 8: testing.StreamingOutputCallRequest.payload:type_name -> testing.Payload 3, // 9: testing.StreamingOutputCallRequest.response_status:type_name -> testing.EchoStatus 2, // 10: testing.StreamingOutputCallResponse.payload:type_name -> testing.Payload 1, // 11: testing.TestService.EmptyCall:input_type -> testing.Empty 4, // 12: testing.TestService.UnaryCall:input_type -> testing.SimpleRequest 9, // 13: testing.TestService.StreamingOutputCall:input_type -> testing.StreamingOutputCallRequest 6, // 14: testing.TestService.StreamingInputCall:input_type -> testing.StreamingInputCallRequest 9, // 15: testing.TestService.FullDuplexCall:input_type -> testing.StreamingOutputCallRequest 9, // 16: testing.TestService.HalfDuplexCall:input_type -> testing.StreamingOutputCallRequest 1, // 17: testing.UnimplementedService.UnimplementedCall:input_type -> testing.Empty 1, // 18: testing.TestService.EmptyCall:output_type -> testing.Empty 5, // 19: testing.TestService.UnaryCall:output_type -> testing.SimpleResponse 10, // 20: testing.TestService.StreamingOutputCall:output_type -> testing.StreamingOutputCallResponse 7, // 21: testing.TestService.StreamingInputCall:output_type -> testing.StreamingInputCallResponse 10, // 22: testing.TestService.FullDuplexCall:output_type -> testing.StreamingOutputCallResponse 10, // 23: testing.TestService.HalfDuplexCall:output_type -> testing.StreamingOutputCallResponse 1, // 24: testing.UnimplementedService.UnimplementedCall:output_type -> testing.Empty 18, // [18:25] is the sub-list for method output_type 11, // [11:18] is the sub-list for method input_type 11, // [11:11] is the sub-list for extension type_name 11, // [11:11] is the sub-list for extension extendee 0, // [0:11] is the sub-list for field type_name } func init() { file_test_proto_init() } func file_test_proto_init() { if File_test_proto != nil { return } if !protoimpl.UnsafeEnabled { file_test_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Empty); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_test_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Payload); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_test_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*EchoStatus); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_test_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SimpleRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_test_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SimpleResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_test_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*StreamingInputCallRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_test_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*StreamingInputCallResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_test_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ResponseParameters); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_test_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*StreamingOutputCallRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_test_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*StreamingOutputCallResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_test_proto_rawDesc, NumEnums: 1, NumMessages: 10, NumExtensions: 0, NumServices: 2, }, GoTypes: file_test_proto_goTypes, DependencyIndexes: file_test_proto_depIdxs, EnumInfos: file_test_proto_enumTypes, MessageInfos: file_test_proto_msgTypes, }.Build() File_test_proto = out.File file_test_proto_rawDesc = nil file_test_proto_goTypes = nil file_test_proto_depIdxs = nil } ================================================ FILE: internal/testing/test.proto ================================================ // NB: Copied from the gRPC Go repo: google.golang.org/grpc/interop/grpc_testing/test.proto // Copyright 2017 gRPC authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // An integration test service that covers all the method signature permutations // of unary/streaming requests/responses. syntax = "proto3"; package testing; option go_package = ".;testing"; message Empty {} // The type of payload that should be returned. enum PayloadType { // Compressable text format. COMPRESSABLE = 0; // Uncompressable binary format. UNCOMPRESSABLE = 1; // Randomly chosen from all other formats defined in this enum. RANDOM = 2; } // A block of data, to simply increase gRPC message size. message Payload { // The type of data in body. PayloadType type = 1; // Primary contents of payload. bytes body = 2; } // A protobuf representation for grpc status. This is used by test // clients to specify a status that the server should attempt to return. message EchoStatus { int32 code = 1; string message = 2; } // Unary request. message SimpleRequest { // Desired payload type in the response from the server. // If response_type is RANDOM, server randomly chooses one from other formats. PayloadType response_type = 1; // Desired payload size in the response from the server. // If response_type is COMPRESSABLE, this denotes the size before compression. int32 response_size = 2; // Optional input payload sent along with the request. Payload payload = 3; // Whether SimpleResponse should include username. bool fill_username = 4; // Whether SimpleResponse should include OAuth scope. bool fill_oauth_scope = 5; // Whether server should return a given status EchoStatus response_status = 7; } // Unary response, as configured by the request. message SimpleResponse { // Payload to increase message size. Payload payload = 1; // The user the request came from, for verifying authentication was // successful when the client expected it. string username = 2; // OAuth scope. string oauth_scope = 3; } // Client-streaming request. message StreamingInputCallRequest { // Optional input payload sent along with the request. Payload payload = 1; // Not expecting any payload from the response. } // Client-streaming response. message StreamingInputCallResponse { // Aggregated size of payloads received from the client. int32 aggregated_payload_size = 1; } // Configuration for a particular response. message ResponseParameters { // Desired payload sizes in responses from the server. // If response_type is COMPRESSABLE, this denotes the size before compression. int32 size = 1; // Desired interval between consecutive responses in the response stream in // microseconds. int32 interval_us = 2; } // Server-streaming request. message StreamingOutputCallRequest { // Desired payload type in the response from the server. // If response_type is RANDOM, the payload from each response in the stream // might be of different types. This is to simulate a mixed type of payload // stream. PayloadType response_type = 1; // Configuration for each expected response message. repeated ResponseParameters response_parameters = 2; // Optional input payload sent along with the request. Payload payload = 3; // Whether server should return a given status EchoStatus response_status = 7; } // Server-streaming response, as configured by the request and parameters. message StreamingOutputCallResponse { // Payload to increase response size. Payload payload = 1; } // A simple service to test the various types of RPCs and experiment with // performance with various types of payload. service TestService { // One empty request followed by one empty response. rpc EmptyCall(Empty) returns (Empty); // One request followed by one response. // The server returns the client payload as-is. rpc UnaryCall(SimpleRequest) returns (SimpleResponse); // One request followed by a sequence of responses (streamed download). // The server returns the payload with client desired type and sizes. rpc StreamingOutputCall(StreamingOutputCallRequest) returns (stream StreamingOutputCallResponse); // A sequence of requests followed by one response (streamed upload). // The server returns the aggregated size of client payload as the result. rpc StreamingInputCall(stream StreamingInputCallRequest) returns (StreamingInputCallResponse); // A sequence of requests with each request served by the server immediately. // As one request could lead to multiple responses, this interface // demonstrates the idea of full duplexing. rpc FullDuplexCall(stream StreamingOutputCallRequest) returns (stream StreamingOutputCallResponse); // A sequence of requests followed by a sequence of responses. // The server buffers all the client requests and then serves them in order. A // stream of responses are returned to the client when the server starts with // first request. rpc HalfDuplexCall(stream StreamingOutputCallRequest) returns (stream StreamingOutputCallResponse); } // A simple service NOT implemented at servers so clients can test for // that case. service UnimplementedService { // A call that no server should implement rpc UnimplementedCall(Empty) returns (Empty); } ================================================ FILE: internal/testing/test_grpc.pb.go ================================================ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. package testing import ( context "context" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" ) // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. // Requires gRPC-Go v1.32.0 or later. const _ = grpc.SupportPackageIsVersion7 // TestServiceClient is the client API for TestService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type TestServiceClient interface { // One empty request followed by one empty response. EmptyCall(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) // One request followed by one response. // The server returns the client payload as-is. UnaryCall(ctx context.Context, in *SimpleRequest, opts ...grpc.CallOption) (*SimpleResponse, error) // One request followed by a sequence of responses (streamed download). // The server returns the payload with client desired type and sizes. StreamingOutputCall(ctx context.Context, in *StreamingOutputCallRequest, opts ...grpc.CallOption) (TestService_StreamingOutputCallClient, error) // A sequence of requests followed by one response (streamed upload). // The server returns the aggregated size of client payload as the result. StreamingInputCall(ctx context.Context, opts ...grpc.CallOption) (TestService_StreamingInputCallClient, error) // A sequence of requests with each request served by the server immediately. // As one request could lead to multiple responses, this interface // demonstrates the idea of full duplexing. FullDuplexCall(ctx context.Context, opts ...grpc.CallOption) (TestService_FullDuplexCallClient, error) // A sequence of requests followed by a sequence of responses. // The server buffers all the client requests and then serves them in order. A // stream of responses are returned to the client when the server starts with // first request. HalfDuplexCall(ctx context.Context, opts ...grpc.CallOption) (TestService_HalfDuplexCallClient, error) } type testServiceClient struct { cc grpc.ClientConnInterface } func NewTestServiceClient(cc grpc.ClientConnInterface) TestServiceClient { return &testServiceClient{cc} } func (c *testServiceClient) EmptyCall(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) { out := new(Empty) err := c.cc.Invoke(ctx, "/testing.TestService/EmptyCall", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *testServiceClient) UnaryCall(ctx context.Context, in *SimpleRequest, opts ...grpc.CallOption) (*SimpleResponse, error) { out := new(SimpleResponse) err := c.cc.Invoke(ctx, "/testing.TestService/UnaryCall", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *testServiceClient) StreamingOutputCall(ctx context.Context, in *StreamingOutputCallRequest, opts ...grpc.CallOption) (TestService_StreamingOutputCallClient, error) { stream, err := c.cc.NewStream(ctx, &TestService_ServiceDesc.Streams[0], "/testing.TestService/StreamingOutputCall", opts...) if err != nil { return nil, err } x := &testServiceStreamingOutputCallClient{stream} if err := x.ClientStream.SendMsg(in); err != nil { return nil, err } if err := x.ClientStream.CloseSend(); err != nil { return nil, err } return x, nil } type TestService_StreamingOutputCallClient interface { Recv() (*StreamingOutputCallResponse, error) grpc.ClientStream } type testServiceStreamingOutputCallClient struct { grpc.ClientStream } func (x *testServiceStreamingOutputCallClient) Recv() (*StreamingOutputCallResponse, error) { m := new(StreamingOutputCallResponse) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func (c *testServiceClient) StreamingInputCall(ctx context.Context, opts ...grpc.CallOption) (TestService_StreamingInputCallClient, error) { stream, err := c.cc.NewStream(ctx, &TestService_ServiceDesc.Streams[1], "/testing.TestService/StreamingInputCall", opts...) if err != nil { return nil, err } x := &testServiceStreamingInputCallClient{stream} return x, nil } type TestService_StreamingInputCallClient interface { Send(*StreamingInputCallRequest) error CloseAndRecv() (*StreamingInputCallResponse, error) grpc.ClientStream } type testServiceStreamingInputCallClient struct { grpc.ClientStream } func (x *testServiceStreamingInputCallClient) Send(m *StreamingInputCallRequest) error { return x.ClientStream.SendMsg(m) } func (x *testServiceStreamingInputCallClient) CloseAndRecv() (*StreamingInputCallResponse, error) { if err := x.ClientStream.CloseSend(); err != nil { return nil, err } m := new(StreamingInputCallResponse) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func (c *testServiceClient) FullDuplexCall(ctx context.Context, opts ...grpc.CallOption) (TestService_FullDuplexCallClient, error) { stream, err := c.cc.NewStream(ctx, &TestService_ServiceDesc.Streams[2], "/testing.TestService/FullDuplexCall", opts...) if err != nil { return nil, err } x := &testServiceFullDuplexCallClient{stream} return x, nil } type TestService_FullDuplexCallClient interface { Send(*StreamingOutputCallRequest) error Recv() (*StreamingOutputCallResponse, error) grpc.ClientStream } type testServiceFullDuplexCallClient struct { grpc.ClientStream } func (x *testServiceFullDuplexCallClient) Send(m *StreamingOutputCallRequest) error { return x.ClientStream.SendMsg(m) } func (x *testServiceFullDuplexCallClient) Recv() (*StreamingOutputCallResponse, error) { m := new(StreamingOutputCallResponse) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func (c *testServiceClient) HalfDuplexCall(ctx context.Context, opts ...grpc.CallOption) (TestService_HalfDuplexCallClient, error) { stream, err := c.cc.NewStream(ctx, &TestService_ServiceDesc.Streams[3], "/testing.TestService/HalfDuplexCall", opts...) if err != nil { return nil, err } x := &testServiceHalfDuplexCallClient{stream} return x, nil } type TestService_HalfDuplexCallClient interface { Send(*StreamingOutputCallRequest) error Recv() (*StreamingOutputCallResponse, error) grpc.ClientStream } type testServiceHalfDuplexCallClient struct { grpc.ClientStream } func (x *testServiceHalfDuplexCallClient) Send(m *StreamingOutputCallRequest) error { return x.ClientStream.SendMsg(m) } func (x *testServiceHalfDuplexCallClient) Recv() (*StreamingOutputCallResponse, error) { m := new(StreamingOutputCallResponse) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } // TestServiceServer is the server API for TestService service. // All implementations must embed UnimplementedTestServiceServer // for forward compatibility type TestServiceServer interface { // One empty request followed by one empty response. EmptyCall(context.Context, *Empty) (*Empty, error) // One request followed by one response. // The server returns the client payload as-is. UnaryCall(context.Context, *SimpleRequest) (*SimpleResponse, error) // One request followed by a sequence of responses (streamed download). // The server returns the payload with client desired type and sizes. StreamingOutputCall(*StreamingOutputCallRequest, TestService_StreamingOutputCallServer) error // A sequence of requests followed by one response (streamed upload). // The server returns the aggregated size of client payload as the result. StreamingInputCall(TestService_StreamingInputCallServer) error // A sequence of requests with each request served by the server immediately. // As one request could lead to multiple responses, this interface // demonstrates the idea of full duplexing. FullDuplexCall(TestService_FullDuplexCallServer) error // A sequence of requests followed by a sequence of responses. // The server buffers all the client requests and then serves them in order. A // stream of responses are returned to the client when the server starts with // first request. HalfDuplexCall(TestService_HalfDuplexCallServer) error mustEmbedUnimplementedTestServiceServer() } // UnimplementedTestServiceServer must be embedded to have forward compatible implementations. type UnimplementedTestServiceServer struct { } func (UnimplementedTestServiceServer) EmptyCall(context.Context, *Empty) (*Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method EmptyCall not implemented") } func (UnimplementedTestServiceServer) UnaryCall(context.Context, *SimpleRequest) (*SimpleResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UnaryCall not implemented") } func (UnimplementedTestServiceServer) StreamingOutputCall(*StreamingOutputCallRequest, TestService_StreamingOutputCallServer) error { return status.Errorf(codes.Unimplemented, "method StreamingOutputCall not implemented") } func (UnimplementedTestServiceServer) StreamingInputCall(TestService_StreamingInputCallServer) error { return status.Errorf(codes.Unimplemented, "method StreamingInputCall not implemented") } func (UnimplementedTestServiceServer) FullDuplexCall(TestService_FullDuplexCallServer) error { return status.Errorf(codes.Unimplemented, "method FullDuplexCall not implemented") } func (UnimplementedTestServiceServer) HalfDuplexCall(TestService_HalfDuplexCallServer) error { return status.Errorf(codes.Unimplemented, "method HalfDuplexCall not implemented") } func (UnimplementedTestServiceServer) mustEmbedUnimplementedTestServiceServer() {} // UnsafeTestServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to TestServiceServer will // result in compilation errors. type UnsafeTestServiceServer interface { mustEmbedUnimplementedTestServiceServer() } func RegisterTestServiceServer(s grpc.ServiceRegistrar, srv TestServiceServer) { s.RegisterService(&TestService_ServiceDesc, srv) } func _TestService_EmptyCall_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(Empty) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(TestServiceServer).EmptyCall(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/testing.TestService/EmptyCall", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(TestServiceServer).EmptyCall(ctx, req.(*Empty)) } return interceptor(ctx, in, info, handler) } func _TestService_UnaryCall_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(SimpleRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(TestServiceServer).UnaryCall(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/testing.TestService/UnaryCall", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(TestServiceServer).UnaryCall(ctx, req.(*SimpleRequest)) } return interceptor(ctx, in, info, handler) } func _TestService_StreamingOutputCall_Handler(srv interface{}, stream grpc.ServerStream) error { m := new(StreamingOutputCallRequest) if err := stream.RecvMsg(m); err != nil { return err } return srv.(TestServiceServer).StreamingOutputCall(m, &testServiceStreamingOutputCallServer{stream}) } type TestService_StreamingOutputCallServer interface { Send(*StreamingOutputCallResponse) error grpc.ServerStream } type testServiceStreamingOutputCallServer struct { grpc.ServerStream } func (x *testServiceStreamingOutputCallServer) Send(m *StreamingOutputCallResponse) error { return x.ServerStream.SendMsg(m) } func _TestService_StreamingInputCall_Handler(srv interface{}, stream grpc.ServerStream) error { return srv.(TestServiceServer).StreamingInputCall(&testServiceStreamingInputCallServer{stream}) } type TestService_StreamingInputCallServer interface { SendAndClose(*StreamingInputCallResponse) error Recv() (*StreamingInputCallRequest, error) grpc.ServerStream } type testServiceStreamingInputCallServer struct { grpc.ServerStream } func (x *testServiceStreamingInputCallServer) SendAndClose(m *StreamingInputCallResponse) error { return x.ServerStream.SendMsg(m) } func (x *testServiceStreamingInputCallServer) Recv() (*StreamingInputCallRequest, error) { m := new(StreamingInputCallRequest) if err := x.ServerStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func _TestService_FullDuplexCall_Handler(srv interface{}, stream grpc.ServerStream) error { return srv.(TestServiceServer).FullDuplexCall(&testServiceFullDuplexCallServer{stream}) } type TestService_FullDuplexCallServer interface { Send(*StreamingOutputCallResponse) error Recv() (*StreamingOutputCallRequest, error) grpc.ServerStream } type testServiceFullDuplexCallServer struct { grpc.ServerStream } func (x *testServiceFullDuplexCallServer) Send(m *StreamingOutputCallResponse) error { return x.ServerStream.SendMsg(m) } func (x *testServiceFullDuplexCallServer) Recv() (*StreamingOutputCallRequest, error) { m := new(StreamingOutputCallRequest) if err := x.ServerStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func _TestService_HalfDuplexCall_Handler(srv interface{}, stream grpc.ServerStream) error { return srv.(TestServiceServer).HalfDuplexCall(&testServiceHalfDuplexCallServer{stream}) } type TestService_HalfDuplexCallServer interface { Send(*StreamingOutputCallResponse) error Recv() (*StreamingOutputCallRequest, error) grpc.ServerStream } type testServiceHalfDuplexCallServer struct { grpc.ServerStream } func (x *testServiceHalfDuplexCallServer) Send(m *StreamingOutputCallResponse) error { return x.ServerStream.SendMsg(m) } func (x *testServiceHalfDuplexCallServer) Recv() (*StreamingOutputCallRequest, error) { m := new(StreamingOutputCallRequest) if err := x.ServerStream.RecvMsg(m); err != nil { return nil, err } return m, nil } // TestService_ServiceDesc is the grpc.ServiceDesc for TestService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var TestService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "testing.TestService", HandlerType: (*TestServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "EmptyCall", Handler: _TestService_EmptyCall_Handler, }, { MethodName: "UnaryCall", Handler: _TestService_UnaryCall_Handler, }, }, Streams: []grpc.StreamDesc{ { StreamName: "StreamingOutputCall", Handler: _TestService_StreamingOutputCall_Handler, ServerStreams: true, }, { StreamName: "StreamingInputCall", Handler: _TestService_StreamingInputCall_Handler, ClientStreams: true, }, { StreamName: "FullDuplexCall", Handler: _TestService_FullDuplexCall_Handler, ServerStreams: true, ClientStreams: true, }, { StreamName: "HalfDuplexCall", Handler: _TestService_HalfDuplexCall_Handler, ServerStreams: true, ClientStreams: true, }, }, Metadata: "test.proto", } // UnimplementedServiceClient is the client API for UnimplementedService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type UnimplementedServiceClient interface { // A call that no server should implement UnimplementedCall(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) } type unimplementedServiceClient struct { cc grpc.ClientConnInterface } func NewUnimplementedServiceClient(cc grpc.ClientConnInterface) UnimplementedServiceClient { return &unimplementedServiceClient{cc} } func (c *unimplementedServiceClient) UnimplementedCall(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) { out := new(Empty) err := c.cc.Invoke(ctx, "/testing.UnimplementedService/UnimplementedCall", in, out, opts...) if err != nil { return nil, err } return out, nil } // UnimplementedServiceServer is the server API for UnimplementedService service. // All implementations must embed UnimplementedUnimplementedServiceServer // for forward compatibility type UnimplementedServiceServer interface { // A call that no server should implement UnimplementedCall(context.Context, *Empty) (*Empty, error) mustEmbedUnimplementedUnimplementedServiceServer() } // UnimplementedUnimplementedServiceServer must be embedded to have forward compatible implementations. type UnimplementedUnimplementedServiceServer struct { } func (UnimplementedUnimplementedServiceServer) UnimplementedCall(context.Context, *Empty) (*Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method UnimplementedCall not implemented") } func (UnimplementedUnimplementedServiceServer) mustEmbedUnimplementedUnimplementedServiceServer() {} // UnsafeUnimplementedServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to UnimplementedServiceServer will // result in compilation errors. type UnsafeUnimplementedServiceServer interface { mustEmbedUnimplementedUnimplementedServiceServer() } func RegisterUnimplementedServiceServer(s grpc.ServiceRegistrar, srv UnimplementedServiceServer) { s.RegisterService(&UnimplementedService_ServiceDesc, srv) } func _UnimplementedService_UnimplementedCall_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(Empty) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(UnimplementedServiceServer).UnimplementedCall(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/testing.UnimplementedService/UnimplementedCall", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(UnimplementedServiceServer).UnimplementedCall(ctx, req.(*Empty)) } return interceptor(ctx, in, info, handler) } // UnimplementedService_ServiceDesc is the grpc.ServiceDesc for UnimplementedService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var UnimplementedService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "testing.UnimplementedService", HandlerType: (*UnimplementedServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "UnimplementedCall", Handler: _UnimplementedService_UnimplementedCall_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "test.proto", } ================================================ FILE: internal/testing/test_server.go ================================================ package testing //go:generate protoc --go_out=. --go-grpc_out=. test.proto //go:generate protoc --descriptor_set_out=./test.protoset test.proto //go:generate protoc --descriptor_set_out=./example.protoset --include_imports example.proto import ( "context" "io" "strconv" "time" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" "github.com/fullstorydev/grpcurl" ) // TestServer implements the TestService interface defined in example.proto. type TestServer struct { UnimplementedTestServiceServer } // EmptyCall accepts one empty request and issues one empty response. func (TestServer) EmptyCall(ctx context.Context, req *Empty) (*Empty, error) { headers, trailers, failEarly, failLate := processMetadata(ctx) grpc.SetHeader(ctx, headers) grpc.SetTrailer(ctx, trailers) if failEarly != codes.OK { return nil, status.Error(failEarly, "fail") } if failLate != codes.OK { return nil, status.Error(failLate, "fail") } return req, nil } // UnaryCall accepts one request and issues one response. The response includes // the client's payload as-is. func (TestServer) UnaryCall(ctx context.Context, req *SimpleRequest) (*SimpleResponse, error) { headers, trailers, failEarly, failLate := processMetadata(ctx) grpc.SetHeader(ctx, headers) grpc.SetTrailer(ctx, trailers) if failEarly != codes.OK { return nil, status.Error(failEarly, "fail") } if failLate != codes.OK { return nil, status.Error(failLate, "fail") } return &SimpleResponse{ Payload: req.Payload, }, nil } // StreamingOutputCall accepts one request and issues a sequence of responses // (streamed download). The server returns the payload with client desired type // and sizes as specified in the request's ResponseParameters. func (TestServer) StreamingOutputCall(req *StreamingOutputCallRequest, str TestService_StreamingOutputCallServer) error { headers, trailers, failEarly, failLate := processMetadata(str.Context()) str.SetHeader(headers) str.SetTrailer(trailers) if failEarly != codes.OK { return status.Error(failEarly, "fail") } rsp := &StreamingOutputCallResponse{Payload: &Payload{}} for _, param := range req.ResponseParameters { if str.Context().Err() != nil { return str.Context().Err() } delayMicros := int64(param.GetIntervalUs()) * int64(time.Microsecond) if delayMicros > 0 { time.Sleep(time.Duration(delayMicros)) } sz := int(param.GetSize()) buf := make([]byte, sz) for i := 0; i < sz; i++ { buf[i] = byte(i) } rsp.Payload.Type = req.ResponseType rsp.Payload.Body = buf if err := str.Send(rsp); err != nil { return err } } if failLate != codes.OK { return status.Error(failLate, "fail") } return nil } // StreamingInputCall accepts a sequence of requests and issues one response // (streamed upload). The server returns the aggregated size of client payloads // as the result. func (TestServer) StreamingInputCall(str TestService_StreamingInputCallServer) error { headers, trailers, failEarly, failLate := processMetadata(str.Context()) str.SetHeader(headers) str.SetTrailer(trailers) if failEarly != codes.OK { return status.Error(failEarly, "fail") } sz := 0 for { if str.Context().Err() != nil { return str.Context().Err() } if req, err := str.Recv(); err != nil { if err == io.EOF { break } return err } else { sz += len(req.Payload.Body) } } if err := str.SendAndClose(&StreamingInputCallResponse{AggregatedPayloadSize: int32(sz)}); err != nil { return err } if failLate != codes.OK { return status.Error(failLate, "fail") } return nil } // FullDuplexCall accepts a sequence of requests with each request served by the // server immediately. As one request could lead to multiple responses, this // interface demonstrates the idea of full duplexing. func (TestServer) FullDuplexCall(str TestService_FullDuplexCallServer) error { headers, trailers, failEarly, failLate := processMetadata(str.Context()) str.SetHeader(headers) str.SetTrailer(trailers) if failEarly != codes.OK { return status.Error(failEarly, "fail") } rsp := &StreamingOutputCallResponse{Payload: &Payload{}} for { if str.Context().Err() != nil { return str.Context().Err() } req, err := str.Recv() if err == io.EOF { break } else if err != nil { return err } for _, param := range req.ResponseParameters { sz := int(param.GetSize()) buf := make([]byte, sz) for i := 0; i < sz; i++ { buf[i] = byte(i) } rsp.Payload.Type = req.ResponseType rsp.Payload.Body = buf if err := str.Send(rsp); err != nil { return err } } } if failLate != codes.OK { return status.Error(failLate, "fail") } return nil } // HalfDuplexCall accepts a sequence of requests and issues a sequence of // responses. The server buffers all the client requests and then serves them // in order. A stream of responses is returned to the client once the client // half-closes the stream. func (TestServer) HalfDuplexCall(str TestService_HalfDuplexCallServer) error { headers, trailers, failEarly, failLate := processMetadata(str.Context()) str.SetHeader(headers) str.SetTrailer(trailers) if failEarly != codes.OK { return status.Error(failEarly, "fail") } var reqs []*StreamingOutputCallRequest for { if str.Context().Err() != nil { return str.Context().Err() } if req, err := str.Recv(); err != nil { if err == io.EOF { break } return err } else { reqs = append(reqs, req) } } rsp := &StreamingOutputCallResponse{} for _, req := range reqs { rsp.Payload = req.Payload if err := str.Send(rsp); err != nil { return err } } if failLate != codes.OK { return status.Error(failLate, "fail") } return nil } const ( // MetadataReplyHeaders is a request header that contains values that will // be echoed back to the client as response headers. The format of the value // is "key: val". To have the server reply with more than one response // header, supply multiple values in request metadata. MetadataReplyHeaders = "reply-with-headers" // MetadataReplyTrailers is a request header that contains values that will // be echoed back to the client as response trailers. Its format its the // same as MetadataReplyHeaders. MetadataReplyTrailers = "reply-with-trailers" // MetadataFailEarly is a request header that, if present and not zero, // indicates that the RPC should fail immediately with that code. MetadataFailEarly = "fail-early" // MetadataFailLate is a request header that, if present and not zero, // indicates that the RPC should fail at the end with that code. This is // different from MetadataFailEarly only for streaming calls. An early // failure means the call to fail before any request stream is read or any // response stream is generated. A late failure means the entire request and // response streams will be consumed/processed and only then will the error // code be sent. MetadataFailLate = "fail-late" ) func processMetadata(ctx context.Context) (metadata.MD, metadata.MD, codes.Code, codes.Code) { md, ok := metadata.FromIncomingContext(ctx) if !ok { return nil, nil, codes.OK, codes.OK } return grpcurl.MetadataFromHeaders(md[MetadataReplyHeaders]), grpcurl.MetadataFromHeaders(md[MetadataReplyTrailers]), toCode(md[MetadataFailEarly]), toCode(md[MetadataFailLate]) } func toCode(vals []string) codes.Code { if len(vals) == 0 { return codes.OK } i, err := strconv.Atoi(vals[len(vals)-1]) if err != nil { return codes.Code(i) } return codes.Code(i) } var _ TestServiceServer = TestServer{} ================================================ FILE: internal/testing/tls/ca.crl ================================================ -----BEGIN X509 CRL----- MIICfDBmAgEBMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNVBAMTAmNhFw0xNzA4MjUx NTQ1NTNaFw0yNzA4MjUxNTQ1NTNaMACgIzAhMB8GA1UdIwQYMBaAFM0FLuuYBwuA J+tocRlu+xUuOw6FMA0GCSqGSIb3DQEBCwUAA4ICAQCcN8WJKbvGrunXgRBjSnsM j/sejaX3CCZPmrXeditekSNMatO0JDXOjyoEvv7s9aZrAf3eFOU3Vr5N7PlbLRdj tovuKTeVp3ungqMoT70cFEf/7eMlpWMB2GkfpV9LtF5Tb8dOYT3kllqtMKv4TeZo 2adu+GXdeQsqlz9fDEi0ZV4RBruuO0QyLWXpNrUB6fznUDfE4KVBsAIadjsg+Aew 6jeTkYuUILWMwBM6MzOG/InTKqXpe4ghMufI9fO+phxY10gz4QQ44ZNOa18OuiJw IH8MoKzhrgUAPLs135hpdGbDePVw5SIKMHUAU2UEKtozAMVfCW45MZHREDdMV3NA w5QWDoBYl4jol08Orbccmhu4fbauXmB5Id4IPVgGEGFPpiH/QVyJgZIv1AD2dlRg Td26iz9I25hyrpEfF1gJMtOsDOklDsUiMo8ncQ3CL+pkKnMjhm54k6OFe0qlGsdO KSavNlEmW/F9h/gs5kaLeFv0v4JxLh12TY28pCE60yoB/UkuB1a+VTHcP3Fa6uUC uyv0T0f5yHujaM1isGjI3XGgVgLyJiFxtKMPsMEwRrsrEafqp7JCeLpnIWt1J0C/ Zz3roGcCGj86Oq5zUjdguHS6Ra+uaX+IMJGohWq1cndzVzNfUNkRIyl8IdkCv9o3 J8fVwBzN6sAu6zWd3BqT4A== -----END X509 CRL----- ================================================ FILE: internal/testing/tls/ca.crt ================================================ -----BEGIN CERTIFICATE----- MIIE2jCCAsKgAwIBAgIBATANBgkqhkiG9w0BAQsFADANMQswCQYDVQQDEwJjYTAe Fw0xNzA4MjUxNTQ1NTJaFw0yNzA4MjUxNTQ1NTNaMA0xCzAJBgNVBAMTAmNhMIIC IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnjw7iZyn9EtjtK7zT+M59OxS J43a3kMm11Vnh1Fw8oQ7tH0kQW6COyAwlBhAzWGtfDC6jG7A2n+8mPWoinsxoLA5 viSZrEkWZ4tGxWWZ5y/xWh5NBrHa3Jsg1rZ5dstAl08gJPwl0v32aYkMdtk16K0k jtdPpKtO98v1N6ea7fvQKdyrAaUHY/BY+onoqmSBGPX6vVV7FGybWDf72J5vcwyA 07wdaZ7TrUYbD28nhXuc4Dt2KbZbWvkT4OYZ+4c+eiehRFVGtuXXi0cKG0xF516b DnrSO1/2ZbIB2xmcgKvcay0jLzWqhnsSc+qTqODVLODQAMvrMlY0RUXQPn/WTfAw aQ+u/j7qIR1KFZcLn7uVq8bCM9g+VeyLjq+6XUvgGL9KhvrR/FA4R4DUka+ZVQqh s262Qs7pNFdoIIrTsJyPd7/UYWCcQbkCKw0aRoUfBeZgkg4bylcABygZqY9+apRF NBEhpycAEvWFarr6rosqII9kLm1LpnPNEgSvQ/CIRIHq5z5iKeSvHFYOVxLS44HH M16Mry5UF/jW7Vg8JY5Jrg5YwyOhdGoOSJ3+c/pbLq1TRkwK9bwEJwGr7FdkrjPZ uNJ7HQiFn2IaeLlbtzwJ+q3vGSFEjlujOigwUJVzXz16Q93vYIuK3FcyDuOMzpwW HHgSLH/+rdtd+7hoLwMCAwEAAaNFMEMwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB /wQIMAYBAf8CAQAwHQYDVR0OBBYEFM0FLuuYBwuAJ+tocRlu+xUuOw6FMA0GCSqG SIb3DQEBCwUAA4ICAQBLNuP8ovTgFcYf7Ydgc+aDB2v+qMYVIqJMrnp8DuYy20yv 64jYcIxh35IbQWOZxZshJsebRKM9vr6huEo2c/SuHLQ5HZGPxSt++aG+iY4Y1zL5 KHtG558lK4S5VsXymMkUjGZtm+ZuJida9ZcV+jz/kePMHpErWPeMvH2jDmD4mWgA YdjipD4cxEn+9O3lBSCkeSjaAd5rQeD9XomV4a2/uL4Y7RDbn9BNt+jdLvfu2pmo O1zcp0f578oFlUIg0H9fb6YzL3MKOXiuh7KE1/W9el5zsN/kLlyWFbopN34A6PlO ZHEvZZcQW06bmy2FRWgqkqWMqBwzWk7JKGp+ozv8IBvimhgjNun068FQAZV9nfKU 6U728P6T1USDhgwtpX7/2IaukXcmO2FE9XzKZyYAbmAcOhPLzFO4pdwapU2lPbFE l2HLkYaHLXzMxB30kQQHW2l8+8xr+MAa+bBcD9Jaxaz/t3ZpLt62/1nxT7SWNwH4 Sa83BaG3EHBotlBc18hqrFWEKR4KYenqY8xa7kblDI0rXqlXBblUXp0TwIctOmzR coqR8q6/R4VXhD9FZBIW1/uX2KKEPfTM46aQdaTtdzjd3UzwTP0SRwkvZ4oFftW6 s1GljfCGsrOpi6O/Uy/IVTE7Xn/oVnlJvGbaP+AHexLytBiBVUBukLBwvpJ8bg== -----END CERTIFICATE----- ================================================ FILE: internal/testing/tls/ca.key ================================================ -----BEGIN RSA PRIVATE KEY----- MIIJKAIBAAKCAgEAnjw7iZyn9EtjtK7zT+M59OxSJ43a3kMm11Vnh1Fw8oQ7tH0k QW6COyAwlBhAzWGtfDC6jG7A2n+8mPWoinsxoLA5viSZrEkWZ4tGxWWZ5y/xWh5N BrHa3Jsg1rZ5dstAl08gJPwl0v32aYkMdtk16K0kjtdPpKtO98v1N6ea7fvQKdyr AaUHY/BY+onoqmSBGPX6vVV7FGybWDf72J5vcwyA07wdaZ7TrUYbD28nhXuc4Dt2 KbZbWvkT4OYZ+4c+eiehRFVGtuXXi0cKG0xF516bDnrSO1/2ZbIB2xmcgKvcay0j LzWqhnsSc+qTqODVLODQAMvrMlY0RUXQPn/WTfAwaQ+u/j7qIR1KFZcLn7uVq8bC M9g+VeyLjq+6XUvgGL9KhvrR/FA4R4DUka+ZVQqhs262Qs7pNFdoIIrTsJyPd7/U YWCcQbkCKw0aRoUfBeZgkg4bylcABygZqY9+apRFNBEhpycAEvWFarr6rosqII9k Lm1LpnPNEgSvQ/CIRIHq5z5iKeSvHFYOVxLS44HHM16Mry5UF/jW7Vg8JY5Jrg5Y wyOhdGoOSJ3+c/pbLq1TRkwK9bwEJwGr7FdkrjPZuNJ7HQiFn2IaeLlbtzwJ+q3v GSFEjlujOigwUJVzXz16Q93vYIuK3FcyDuOMzpwWHHgSLH/+rdtd+7hoLwMCAwEA AQKCAgBvitoVWX7zsKkqVyFhMTZLtsL66v5cK04YATYnp3tNGXXU91o1Xacj8r8L xkT4AmD+6IK4N+JupBjYYmNaqxkCwvcRWE+TqTnH5+ANil+BHsSt2CpIC9vSIvB1 KtBYs1Jm1vo72Br5rtii8F7+8IMV7+eTYafc1n2mI/pKLzYBiL7mo41QbXrWMjkm 80w1wP9YDx2flcBbV2vyNhSsUJMTsL6ngzXgnHtu67prmNltOQQO9RuIr+maKXaf 1NSAAIhEJ+eAefSNPVxB6+Pt9khYntIC1QWZoT3Z1i+EuXsfIQcR7hGdV+FLRzps x/Eq3MKpDhjSVu0G4MmcA2iWhhsUXbOihpXKWnAdLv0cUP0tbbxcUsEa4igAruBW n6+hYrVkbD5sZuGoMdzKvnGkqRTf/ragdcFlfty7KaAUBFr5V0fzsaW53+/5zG5o eSRoyCNLQSbBh2TVVpnNIbXELuYmwoDvnPNup4G7ITFsUQZEW2Tm3LHQt7EAi/wn hJU/21rI46ubB3r5wJZ+6JOK4PiqPeSIokyVfFyPk2ny4LT7ne5MtPHF+wAVAOYj 0wcLEyh2s1b0VSlko6GnPjbLi8eAtOAk2ggVK5GofnkhsPohA+yoNvcDDLbcdQ8v 9Q/nbL2dENce6HZEBSi5RlElU9+BbOJc4qFM2o9mzrWuOpHRsQKCAQEA0j+OhUag qbu7tNcWvE8w0I9vt1CXuZrS2ypKpuaaMYknb2Lo4YtuV9+bHx1isGHnAc2RAbUB 23mLANhquRIOo7u3NTvtRsvyzrRuuFviQZ5p1b8MHfqpg+/mA+yve8J3zyMMNC5f c7m/13J+dsQNf/WWhqbnWU3wOoRa0NQBtOw7UhVFl+1JeBfSiVmYQXH1n5VnOWs8 Vab5kGkeYpUssgMFJG07qgdX5Ux3KzAQm7Onvsn4tT5UMtHt67f/wWzlP/xcoBJW 67clhCuO2Jiojo4jSNko0PGgTmFPmF3EOW+zd+iEYP3LF+S4uTR7yz4+foUNa9e2 xf6XwMp3ymjbfwKCAQEAwKsnMt0KErZ1/iRbN6U5Rcgxcq6FxRivXyzQX/3QrWc0 r6H4WWk5+gwy/Fb1CpQyiJkXG7PpVysdWaWF5S3NRVL3Ixuyp1R10DmPYch/4Pn6 4BD9UgKUhS2nxBVcMyyM/mN0W1Img22tCaJhaI+/raYf61JxgWmUJmUq8k8Xzgfv ndEYQGgf62jG35aopkqfwiC8+rApgbiLoN1mGiusyJUcZmYLLYp2ao/xHc7UtjMP N5tQeE0aZgSaBBwDAMQxMdWovo5qThvpJdy8q8EVq6sCO1G1MzLzIMxd/4asVzLc wUHSG/8c9qdgxBGGhYAbTSVegWTaqznDrlFB8RP+fQKCAQBOx+vyeqWHFEZgm9v0 EcRb0fNtgDBqJt5tqyov4ebTOu5g6XIT2XguSyZIAW3SY8z4uvtj5VxdzexNE8rh sCd2KMeclejyB0fjNm7qe9uK9P35Ts4OibdtLb5FqDGVMShNoHdZMisoJOkCpO9I N2xLj02pBO9ZYj/q3V9eMqK1FXOg7UGXjR1jd6G3P7Ayja4Y7xWvyUPhYGDRQOJW 1EjcJw+NN7UMoBXKYN2ifC8s+KOZdPrRhxprtIfvNJIL+27ni/t1K4oQZx8SqHOt K361dAM6r8yAhpmn5QS7Nh9p2jYobyLzaQXp3RVuqIDehmNKaza9OyZMiHp6jiNW 3/WnAoIBAQCTeK3ZRc03A4gPDd7wGbxbyF7o6+KiOUHKtK+OOeWnRI7UPEKulVd2 KC5CbYDEJykC20MPxka9nNerTYHOKJ+tB1L5AXNelsxSpCw2aVRQbKb1KKvtQOJT id2Wvc7DsL7+3DssxxWJlcJT1IGAmj7Z+IUIByOwLZLjTJ5xt859uh9Tib9pVQnR k3Jdo6DVH9tmqM5dh8dNbmcZqz1CnNl08oU5b7PwmMII0MJ60VyJVU25f105p7Kk EbOdn59Az+rjvSmbKcD+pmhvvaSARpuCubNMmj76wG3OVf9A3eE+IUVNe0cKfNu7 g+QST2PK/YJoK0lJ+1tQojdATxwNHgO1AoIBAHjpZ9Px6L3Ek6O3ZxRfuDTkmoVB APmvm4IcvajB0BPd1mdcf4sWYmGhNqf+xajwsKB8jIkd4LYfINjfIZOdnYgq0oQY cM7K4+b8gkLKsIV2gFI4b95TYcbmanxdTDdbERGTJPsIBajXO5XapAswAJfllSDH pUvLb2CUgLhMhR9SFZAjyRo0HV++jMqxWJKzhlTOkoPzBY5xAleft+hzVch3WuvP zZn/NrpzTEpslV7dZ05Wuh8E+vJMoQNCReGlmAwNlrt/vxDuyv6ibNPxBHax82On yo6EP59d7OE0951FruUIITUgzKG2jIqeR/e5Yb0LJusXnj4RPuvfRULFD00= -----END RSA PRIVATE KEY----- ================================================ FILE: internal/testing/tls/client.crt ================================================ -----BEGIN CERTIFICATE----- MIIEGjCCAgKgAwIBAgIRAPt0KCF12GYbCoUj7klj5/AwDQYJKoZIhvcNAQELBQAw DTELMAkGA1UEAxMCY2EwHhcNMTcwODI1MTU0NTUzWhcNMjcwODI1MTU0NTUyWjAR MQ8wDQYDVQQDEwZjbGllbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB AQC1JxEPOsZyf8883tlPBEajotyECtrYMZ48FsYEmQ1XvKPoH3eb7+Ev7tRBVAup yB87XQ5PU/oNqAtpo/6WD5JGnKSVs+EAMESXmzEF04T9hK8uSd0cVEEkd0tbVNpX bWMbivHnx5Vp8o2mIx0sVrgGsJW3t+cYbNTp3bOTdmz7LKbiQN2Ix0wH+2/sPXYa cZsgbI0Ydo9KnqykPm2TqBYCL1kzhGlvaAotjdDIm7OgnaGCFe4CbK4QZB4uFw3e M+PmLG0TsaH9CT/ZRrE21iBfg0rqgpKZKMcqYQftXdLqlikuV69F+0L84xRfeVqB 1E4j0RwBGWW8EwY4WHK3VE25AgMBAAGjcTBvMA4GA1UdDwEB/wQEAwIDuDAdBgNV HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFMs+/QF/ZJaRu8Wv vcaMC7jGmPwxMB8GA1UdIwQYMBaAFM0FLuuYBwuAJ+tocRlu+xUuOw6FMA0GCSqG SIb3DQEBCwUAA4ICAQB0gPDs86Rjy/O2+l8QyaYfwmmyTMPjNVqKgVP1uuikWErN 5hTAlwtDI9FuiMFBqeBdeiT8IQvzEEQPYu69kAX2XYBWBMWDa85co5fJztAzV7Yz VL1byhxd2jgM14usyx6PbzkhYKBNesujHj7wQ0ur+85Kp66HqKCuNCvbj0zv58PH RWkojRPgyTpbLdXXCOWJXp62XfddL1Bf7NJCW5QTyHoHoOsOeoPajb4OOmQehzqv b9FPAHVFBPrU53Xn1CURAzTeBQ2T/OK4nx6EdQgxP9+VVurBQ9N2YBM9VEJmfQK8 Lf5/+EJHe5ctOy1Xm4A3A52zZ1kGjfvWUtGJUSnJ5ahhMm6Dx63wk7oYNCTXnPup aVtINWygNlS/dQsWubHaWSFwB9/QwK074+H/4EpDq9HCMMl8yPMktOmv69Hyaju3 MvGshz/DLNZf9oYpO+lbU8X124Z6XifEztMiBlUPW75KYv9X4CTbKTdE45QaRMiO ZXcH4HE1/iQ9IOGg7CplMlMcHg+lQ7CpXQjtUUjCEpkj8BAs8YLDodLnjigs56/8 75+3cVZu0+dY+9eNt/EIqzjaFwEx72hbLyhk2IeS++7QloJDhYqlq+ng54Ul957A 8e7TsSVHlLZVGXw8yqjyxxOwWaFx6mvFzGrcBtvCgK2HwEiYQ9qXJ5VPkdo42w== -----END CERTIFICATE----- ================================================ FILE: internal/testing/tls/client.csr ================================================ -----BEGIN CERTIFICATE REQUEST----- MIICVjCCAT4CAQAwETEPMA0GA1UEAxMGY2xpZW50MIIBIjANBgkqhkiG9w0BAQEF AAOCAQ8AMIIBCgKCAQEAtScRDzrGcn/PPN7ZTwRGo6LchAra2DGePBbGBJkNV7yj 6B93m+/hL+7UQVQLqcgfO10OT1P6DagLaaP+lg+SRpyklbPhADBEl5sxBdOE/YSv LkndHFRBJHdLW1TaV21jG4rx58eVafKNpiMdLFa4BrCVt7fnGGzU6d2zk3Zs+yym 4kDdiMdMB/tv7D12GnGbIGyNGHaPSp6spD5tk6gWAi9ZM4Rpb2gKLY3QyJuzoJ2h ghXuAmyuEGQeLhcN3jPj5ixtE7Gh/Qk/2UaxNtYgX4NK6oKSmSjHKmEH7V3S6pYp LlevRftC/OMUX3lagdROI9EcARllvBMGOFhyt1RNuQIDAQABoAAwDQYJKoZIhvcN AQELBQADggEBAFnxmVCuM3J2bt79JcFOqsXNsvGUUT+4kMl3BcfSWaf1pviuhiXT fsKkk1WItvaRQvpNdQoFQDjKHGcd6+0vCz+Q6Nni2Vniz3+f3+h/rOzWGA656Xxm lgByryixnngWZBNLZkLWCz/H1MAlQYu8PTdy0N+JBF/E5SAGfaaXtfTC6tjnnZIm 3rjxC7C3EyELpo3X3erTcHpnFvhl6ZSkViVWfhOjxU0n+TGGohczesbHZc8YC37y JrkrnRDrNKnca1XkXWUnbV6rH8cVDnJ0Fvs54RI686Tlv+LxW2xa3D2+pV7Koduj Ru+PguJ3BbaRpieGTxHg7hH/1T5HsZnD2E0= -----END CERTIFICATE REQUEST----- ================================================ FILE: internal/testing/tls/client.key ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAtScRDzrGcn/PPN7ZTwRGo6LchAra2DGePBbGBJkNV7yj6B93 m+/hL+7UQVQLqcgfO10OT1P6DagLaaP+lg+SRpyklbPhADBEl5sxBdOE/YSvLknd HFRBJHdLW1TaV21jG4rx58eVafKNpiMdLFa4BrCVt7fnGGzU6d2zk3Zs+yym4kDd iMdMB/tv7D12GnGbIGyNGHaPSp6spD5tk6gWAi9ZM4Rpb2gKLY3QyJuzoJ2hghXu AmyuEGQeLhcN3jPj5ixtE7Gh/Qk/2UaxNtYgX4NK6oKSmSjHKmEH7V3S6pYpLlev RftC/OMUX3lagdROI9EcARllvBMGOFhyt1RNuQIDAQABAoIBACE5jRNyADu33VaY uNqZOiuBD1jYdNL6Jr92ndLyD1RsMNO+Eb3z/SVBdISW2ZzGK5RDuQArss0WaSFz BpqXOIji6fzbBQV31NzJhfA/n0CwOUEQIxGzEk+R4axan8ExOuAuV7ffDzRjXD+A aTVcolv3vz326Ne9/j72fp0pN0vJ0b8mk1xmDWNOHhfoWmIGrUZAjqAkA1kh5aLk Q8MCjVyjT+KYDkFT6NscFVxKslDVhb2OFC7oy+9l/hBru12bsi9eBdYpPT9E1cpR U9N8+9XS9d7wgVnmVh8CIrFToLsvSrwD8SG0Indot0C6dsy0PkoMUekVxvM5/wXm YLZnZEECgYEA28JfZxFxO+bjd+zBC+yrusHCVfZK6MZZV0u/V82Bn+gftwgxagI7 q2h7m56WBtq9MeLlhicZ+em4BtA3yHwVGhqr+d5CaXjkYype1EditqGx8JYRUwlG 9z7W6jCEDJsjrzvGgua1qsyCZFePG78i4rLumK7UVvWEK/OaiZu3Pr0CgYEA0wbT 3STBc4THLXR8nx39b6RP+qH8jO9UcD/V7Hi/SWTCcGB8IIlTV2EJVndKHPregcmI dN61uH3d+3UtI/WxEPMcfrSlEwVrjF2m5szYjLIAeFynw7pQY95qIhgKi6OH0Yn6 9OCmieL0x1ez5zOXiv+GVjmn9tDCxXvqfsW9CK0CgYEApOd0Y4kpKUQWyPT135bX PqsKwyqwB4BfpiwHB0IE1ROASP5y5hOK5xLePmaAOeCGPBsBFOveiDQjjalNUroZ s570EeoAd9jpuKggxLZUkqs/NUPG+EJr6DhVWSLS1ArOej4mti+dfu87oUQ69R02 dlrCw/vdBuvxJHIGMuCQXxkCgYEAinSFVygBgQCSCkHObjuoB7LgAsp7QCDa3tcT TZafstDYPhEf/9z6AG+bR872onL6wF7xF/Tzd7ulhJGJ73kJFtzbSkrNr+AzgyID GpU2U4GKi24HaIT6r7vDGOF7Mck2mIWWUUqAGiH9hjkFwWD5QeqLQlGL4YVw9U9r OIgWkfUCgYAoMub8wHJe9zhq7UCBCa4zPXqWVQcN7ANjL6fESvyK+A6TfwL5j782 CNIVl8ewU9TthEY+AdbJeiAevz+pIazSqi0ln1JKO5YpCOC1Y0UxcEpghplBTlPU yoQyTJP81iLynwOM4pC322ptJISXIndL7Ig/9AoRZtAJV4Ot6z9b1Q== -----END RSA PRIVATE KEY----- ================================================ FILE: internal/testing/tls/expired.crt ================================================ -----BEGIN CERTIFICATE----- MIIEODCCAiCgAwIBAgIQP6vqM4GEKFdwrZvr/s/5KTANBgkqhkiG9w0BAQsFADAN MQswCQYDVQQDEwJjYTAeFw0xNzA4MjUxNTQ1NThaFw0xNzA4MjUxNTQ1NThaMBIx EDAOBgNVBAMTB2V4cGlyZWQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB AQDDlGu08Ye7IGMnh6zyiVbW64btT2BUfj4fZlyTraLNzVPfULOXezgJRAwAXEnY eYA5j6e7iAO/yGXq+Pxh9rbGlXTVC2BsPIfKzTOcCTxgO4tfrdfSWFRDGgcFtpAA /4b8fYQ8BMw7khVpOqj9n8TyqsN8DGyZTyNhQuc23zmmY1eVYtDIwVvVBp8/YzDT 3RzO+FyoRpKgdngX6kdqqZ+vaPxP078JM5zAMnysjSkpQCdAdu4EupvA5xRtOrDt jJoAi4iUf4sG20RiF9XFpiDkyUiOa0ysFC4wrWbXkaT1xHZJJPwwJZ8VyGDRFlFa POEaM9dtLPKtEV2af3G9Q9s/AgMBAAGjgY4wgYswDgYDVR0PAQH/BAQDAgO4MB0G A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQUNX8nKbJ+G0bv k88bTHkULUX1U5AwHwYDVR0jBBgwFoAUzQUu65gHC4An62hxGW77FS47DoUwGgYD VR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3DQEBCwUAA4ICAQBHZsxC GGOUhNi1b0xUYmKwFaRP641R69NydzVMma+2rpLZtL8MQq7lWAgWGE8EiKfTewk8 2Gm+rNwReBRkw56zka8WtLomO4GKKJHvfRsbSempsp9MShc3vcsEtzXGjDQi9c4Y BJjWbh666jCKPRJ09RewcTyTYPtZ96lVeYRj9HhXF1EGnURG5sM/zCantlZXxInk 1FIOKDuawMbgf3GR6n/bM2oybYCs4Skv3yOp8x3lyhlJ/zmSPGVVkPa7Vki5+sPh /eIZJ9mzEXsu7IXfg1isZ01iB28+6UgpZt/3017PvgopiYrL0gMKO3EL8UqJDweN GzJh5VYOqsjTbsrYYsWGqM66vJmfPvqDyYA1jj/EH+q6TBz0s99rzF44bBKKie6T 0KrZT7ohXQ18Vhl2UpqahMqxMeAW/QP5asGuzS5EalUir5mNcOljtq1NnfmYvqok aDC7rURoANwZ2L1oKN0oB6jn36g791pFdKycw+HdsrGgQGPOMak9P32z5kmDsODH 6aVrfio2WwSGg+1CIBH0QsclHAUgLsAXjyGbRWxPsvMcsLB6OOymuTP2UfriT05X duabvbEP5IIRehVUfrP5uvoo29xnoPL4UB0C8gwr21IDn7Zew5/ALekN+s6IgsfL 9yKTGSD+6Ir3NqBgL8T+uhOAekyLE5S4CcwCHw== -----END CERTIFICATE----- ================================================ FILE: internal/testing/tls/expired.csr ================================================ -----BEGIN CERTIFICATE REQUEST----- MIIChDCCAWwCAQAwEjEQMA4GA1UEAxMHZXhwaXJlZDCCASIwDQYJKoZIhvcNAQEB BQADggEPADCCAQoCggEBAMOUa7Txh7sgYyeHrPKJVtbrhu1PYFR+Ph9mXJOtos3N U99Qs5d7OAlEDABcSdh5gDmPp7uIA7/IZer4/GH2tsaVdNULYGw8h8rNM5wJPGA7 i1+t19JYVEMaBwW2kAD/hvx9hDwEzDuSFWk6qP2fxPKqw3wMbJlPI2FC5zbfOaZj V5Vi0MjBW9UGnz9jMNPdHM74XKhGkqB2eBfqR2qpn69o/E/TvwkznMAyfKyNKSlA J0B27gS6m8DnFG06sO2MmgCLiJR/iwbbRGIX1cWmIOTJSI5rTKwULjCtZteRpPXE dkkk/DAlnxXIYNEWUVo84Roz120s8q0RXZp/cb1D2z8CAwEAAaAtMCsGCSqGSIb3 DQEJDjEeMBwwGgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3DQEB CwUAA4IBAQA0GTIB6PxgmHBa234rSYqIew4qRfY9MeUkVQEFRDwodqxa+LWvZx2T 5JmTZYyXBfQwnSye18fDjQuHv1KaI7bnJuMRv9KU8L6ynLkAqrFWRSBjt3eCum01 IWZFyWu+dUN2c12C79zUQh8uZc15oDNFrD8ivBbGRpWvR1CSG/DH52kJ8nckgEsh SwxbzSPOXBgLH6ke5z9QGHJMK2rhRFutFOecAId7VBiWqfZJv15+P2ZcyJNQGThs V2sT974YGFkc0Y1MWlCgi3XhyQzIzqV1tEILGSTDE8biuKlm1nX3H0K8oI3hoGcM CBjE3HQ1/rs10IY/WvkpIfAU71D/ExMc -----END CERTIFICATE REQUEST----- ================================================ FILE: internal/testing/tls/expired.key ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAw5RrtPGHuyBjJ4es8olW1uuG7U9gVH4+H2Zck62izc1T31Cz l3s4CUQMAFxJ2HmAOY+nu4gDv8hl6vj8Yfa2xpV01QtgbDyHys0znAk8YDuLX63X 0lhUQxoHBbaQAP+G/H2EPATMO5IVaTqo/Z/E8qrDfAxsmU8jYULnNt85pmNXlWLQ yMFb1QafP2Mw090czvhcqEaSoHZ4F+pHaqmfr2j8T9O/CTOcwDJ8rI0pKUAnQHbu BLqbwOcUbTqw7YyaAIuIlH+LBttEYhfVxaYg5MlIjmtMrBQuMK1m15Gk9cR2SST8 MCWfFchg0RZRWjzhGjPXbSzyrRFdmn9xvUPbPwIDAQABAoIBADa1IZuvpCP330SD cyE0wZHEuC1RcsSvu3jVDThR7aRbtwZUcKgC053j5ueC6TUgZ3mycVzHoyTWTYv4 scBFXsMVs2SUlhgwpltYIwOWocjZXxcYbbJs+sT6VtSGSKm+0Gd4RLD1NpvDNTIG MpcfRdwLYDsmzonj1SWzrTFwJ5Qe33cHSR8Oi9OarrxzIHWLDNgp8x+5j2l0T3AG RPQMXj7jaK6qEdHGD6geg+ButeyjYyxu64l8Ooax11/jfdYHcK+BmmtmP3Bd/6FQ DCtrXTBv5Txl/T6D/6OsyabSlwGWFDNEAaS47hKCFiYJBR+Az6r7d1QFBIzuqF0+ T7EwaKECgYEA/tUMcTXnBRquAtQHW4cSvHH2qoZAhGjXCmj/pk4UCOZDT1DP/K5u m7tOZB4RJBmzg0a1QS89aJ6lhY0Oy1Y9MZTM6ofkBKd8+P0Rgo04UDVeQ8sA9c8x 4bFOrOEqe7NoK7Mwxyn5NOmNi/wxy+tpiMH4Jt3y4TLDEZPg0Tsfu90CgYEAxHnc fN3gKeY3SV6nHi+johBirSNazhr4n0fx/TCsy02e5Rrn0rksGVfPij5Y2g8HQLel hdbG7tVyA7UtGgVjwiT+4j0VXmWpCIqfPsibRoy29oPqO4p5pULsz+ueb4biRW2Q tdLqS4OldM8YUFwkS7k3Pp13SAY+Ir9rHL6wv8sCgYEA/AZaYtCrZMnpFNT7XdLt fb+78xQJVKqXGi2TwLbxa4fHQ/cpa75bl9scAToXO7vLZOaWNhxxQDm+e6Fw4zqs FJAURVMV+GBo4ZrvKU1fRzwwuR1ZGsHKlGoV5DZgHKznNmjmseJaG7FsEujdms58 tgsXz+Cr53qbn5O/wU4W6WUCgYBA16sB9sPtcBIc/8UNvFE3wkqes4VbciFNiBQA KJlOe26OVCPgMsawEn/nMw5l4QHWxQU2t5xt5Dm9qYSaCt9Sip0oE1rDDbAMpptJ wDEmxnf3wa+DOP9OoFjBghSG4DA7E57nsxUqGOd5NoPiuZYs+5KU8qkUNyM4mo4C LZjtowKBgCWuDPr8MBL1S3ym55VTNUWcuMKUHZkMg0HjRpi8ABXeARoM5FAWykbk hFnI/Waj6EHNGoVVpKttJldP+uRRA+S3wZYKH4gRQcsWI/BVdiBkZgoTG7cwnixs CqMQ4p6In1Q/6EafPqkVQOY1abNKQ7ZGbhksB/fVbf37XfOnDj3H -----END RSA PRIVATE KEY----- ================================================ FILE: internal/testing/tls/other.crt ================================================ -----BEGIN CERTIFICATE----- MIIEODCCAiCgAwIBAgIRAMcIOg3oRf7n7mR1BUqNnlcwDQYJKoZIhvcNAQELBQAw DTELMAkGA1UEAxMCY2EwHhcNMTcwODI1MTU0NTUzWhcNMjcwODI1MTU0NTUyWjAQ MQ4wDAYDVQQDEwVvdGhlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB AM4lPXHK6YV7GFMeSsHKVq33LDn8Zt0IkY5pGNCuuksPa3vYBFRkwTrrobKQDnmw jLeaQISyxyelhT4aIU34OQpGjOmCzceYPG0uOcecq7noVvbgw0UeQHkEL0p4NOlb 9LMNVhKTgQUhZC5mYSIpXNxlE/LlGmqrFX/8peSaJ1oAkkB5FYN0gAbYhd8kpJX3 9Nr2A+f88oicSX5K0L73LUFUrxoTcrFP1pWnFgg28vLvvzrW/VZtq4qJPl3GTLbM xHOu000LaHK8TgIJMCQUalh2q2nz+Htmtv+g/b27YEMGcDBW1qBX9oMKgoM/Z1Hv zjVhAm9I649heDUguFXCaoMCAwEAAaOBjzCBjDAOBgNVHQ8BAf8EBAMCA7gwHQYD VR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBSUR1DE/STLJwf2 hW4qXPrOSLqlmDAfBgNVHSMEGDAWgBTNBS7rmAcLgCfraHEZbvsVLjsOhTAbBgNV HREEFDASggpmb29iYXIuY29thwQBAgMEMA0GCSqGSIb3DQEBCwUAA4ICAQBG87BU UuDuUCnvcwUNbga8fhe1PR6z2jueKQiI10SxkYG/g6PMQGGYDNO9DZFKu9l/TMTf LnuEozN+Csig+wC+sc32/MdR35XTmkKtNhL0cVgvKP0Q6zNk97/QJErLtDpYb9VR Gm2Ky6FGDp+/EEUvQUKRpGBmWIqOtjxqQu8lLoJlt/TPhxJ0lGDd3c8WwaVFYTbS isBKdHpS2hkn/O1Yd4QtNk06pCpUDQuPumUOBoa+dK3y0jZ+e34h1NoR5EZvfRJy p3n7CLD2eZSNc4oWKb67X0RDao5LD0b51crjgsFYHhCTS+Mgh0YkgukQZ8uBKpUJ IBhz2Nr1QXykrJUhal9MrKukjczEikGxzK1VsDgxYY1kLBURhM9/TfvICmcAaQqv MF9B78lnoJiPZZxD+a5N9MawzN6QBqX8GpvhZoAnj6iAuNwKJVyENpZqravxeq2o buNjgQ+SmfqxQDfMD3lu95yidqD7bcDipJsXEPQzdBjZ1JOJCGi2eiAm50e6bq94 CMKmmRjtIbF1hJnHeEFPvXqdPpqcyEvcaDebph/f+54wubTgwFI3VMnhhlv2EPIe rwcbZV3kNpUZWXAZVzYlQcbK+9US8PocOUzmqzA7ZZRO+rCWNxahHjdgrMK3fG6r WudSHHXawj3dkPeWrQde6SILSK/myhGLdjg1fg== -----END CERTIFICATE----- ================================================ FILE: internal/testing/tls/other.csr ================================================ -----BEGIN CERTIFICATE REQUEST----- MIICgzCCAWsCAQAwEDEOMAwGA1UEAxMFb3RoZXIwggEiMA0GCSqGSIb3DQEBAQUA A4IBDwAwggEKAoIBAQDOJT1xyumFexhTHkrBylat9yw5/GbdCJGOaRjQrrpLD2t7 2ARUZME666GykA55sIy3mkCEsscnpYU+GiFN+DkKRozpgs3HmDxtLjnHnKu56Fb2 4MNFHkB5BC9KeDTpW/SzDVYSk4EFIWQuZmEiKVzcZRPy5RpqqxV//KXkmidaAJJA eRWDdIAG2IXfJKSV9/Ta9gPn/PKInEl+StC+9y1BVK8aE3KxT9aVpxYINvLy7786 1v1WbauKiT5dxky2zMRzrtNNC2hyvE4CCTAkFGpYdqtp8/h7Zrb/oP29u2BDBnAw VtagV/aDCoKDP2dR7841YQJvSOuPYXg1ILhVwmqDAgMBAAGgLjAsBgkqhkiG9w0B CQ4xHzAdMBsGA1UdEQQUMBKCCmZvb2Jhci5jb22HBAECAwQwDQYJKoZIhvcNAQEL BQADggEBABuaL2t2Zcv9R72OH93EpIzgExL37odLUiIjTIIykK2TT/gb1LtnE1WK THdqaLpnPot9IqBofppfkXPMrG7vavJoPlAp0lU2FHYIz64PHou8lj9yiXezDKDn Jia3TOxCu5VTRnYT7Ypt8kSull/jlyBQgTP+P0YXwoYAXJteQr9O6yD75yWOAx3A f/oQS77xoe0jdU4RkEMQRQQUIiaNyH8Bx4CeETmPoJDzEiIvnC5xDoySks1VJK7b w11IANF7zO5UWtYv/i3+Wh5XLMJ0GIIVTpuGkeVaZCjB8goiBJXoaHOKsO5ygJo5 N+nxwiDTwUIZM+dU88mtCh0dvJeMMfA= -----END CERTIFICATE REQUEST----- ================================================ FILE: internal/testing/tls/other.key ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEAziU9ccrphXsYUx5KwcpWrfcsOfxm3QiRjmkY0K66Sw9re9gE VGTBOuuhspAOebCMt5pAhLLHJ6WFPhohTfg5CkaM6YLNx5g8bS45x5yruehW9uDD RR5AeQQvSng06Vv0sw1WEpOBBSFkLmZhIilc3GUT8uUaaqsVf/yl5JonWgCSQHkV g3SABtiF3ySklff02vYD5/zyiJxJfkrQvvctQVSvGhNysU/WlacWCDby8u+/Otb9 Vm2riok+XcZMtszEc67TTQtocrxOAgkwJBRqWHarafP4e2a2/6D9vbtgQwZwMFbW oFf2gwqCgz9nUe/ONWECb0jrj2F4NSC4VcJqgwIDAQABAoIBAQCmygix5gwU/KiM r6iqrOx+6sq0y9vqIIGsaKo0RfriukIrvHacVbzl0DpPADFGEit4beyfsQpjsI9i 1L93l0uHXderIzMdt7XEXK9RKxjiXPLn4qj7ZmOhxloA9ctRuB3/NN4cP44XOZIV 3K3gdvj0NS/zyZwbC/tkR2Vt1a/bJ8DFfaFrSdk/btpVY2BH/uWjMls1BYIs3tEk nroJYb+fyliC+n/QXrLQAPTVLfI3jyVjRYpW5b6mZHQk4SGDde1XbtqRCp/f6PLu H8FQumd+SfrjgTwefphSWCW2H/aMNsZpL9NK/hmglKg3OcGKhwQswdmL9rDVFQmy AqxXwxk5AoGBAPPmijvwg6bKwqeSCdhoYjQVNXdTbdda3arjC/G/vxhsVntXluOX wGF2jInAu+sAShBFdhG4JlL+itlpA/aDDIMUxsF+YnhTOX5pofL8Nr4sstY6wjBf 4jVHQKLnaOm/mVpHqWABVv/M085XK7HHZR9e9y32ry1geRSiPF/E5I03AoGBANhf OPL5WWO7TOchWci6qRcv0kZ47iSE1JxSGnXr+KIIBPIAgrwhYkqgrl6noSe3x1qB tP7ZvmFWewxmo3mN2OwPTsxAhnjQK4D1PGJcsvf2A3f1uiwG+emqWpcMshTQnjda Hi2krfMaHwErE+dEbZiilLDRYAMAWlQodXnhllMVAoGAKdDIumYN7DavENO05Glh DNTmCcM//cASaQ3sKlJZjPJmEVd/Ax4tWYhdp/BnR28RQ6DlETylNW12mLesekMV jhOtz9a/QynhnY62uVYMfKZlMt14FZsayU+iAUvzbL/wps3KeC9CnzCaz7GaSCyL Zcl+T18PwZPcrnDyMOks1hkCgYEArrQUE3tpxbER4v12tTCiHuqp6eTyw+HMmXth ih1B3/KBq7Tl2mlKJ9+daygGYz9sY5OfRLcjlQxyxgyJqjfyEog5o4nmCd5rgfCB FRqsFrI5Er8B11K6rwSxqIzDrTLUzPSisU/qdAN/TT4vD+icZUXAsRQdZc7/IDya vhJ7ghECgYEAiZ6v380l2jcEk+tm4UYZ+nXq3c2wq8mZkQcEDVpqvDGL8k/dPEq9 xOy7PVooQHQJyC2bgTbEKs6JYvzAYQmohiq7L3y9WxDQFJeImtzuNi0s/dwP+mBh R6htM5JwAO3JnE9V863M6kvtLEIk7XptI5gC0kN3Thi70yT3lnU+emk= -----END RSA PRIVATE KEY----- ================================================ FILE: internal/testing/tls/server.crt ================================================ -----BEGIN CERTIFICATE----- MIIENzCCAh+gAwIBAgIQSCdvhIY3KZ9w9YRcDH1oRzANBgkqhkiG9w0BAQsFADAN MQswCQYDVQQDEwJjYTAeFw0xNzA4MjUxNTQ1NTNaFw0yNzA4MjUxNTQ1NTJaMBEx DzANBgNVBAMTBnNlcnZlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB ANlVMhHZHjRWiGqmpOse1KZTmdUaiSgl3T88+Mie17UbiLmsOfnkd3PuEnKlXRXM sqOg15k9xfnV6SQebpBfkcwqJVH/USjWY4C1e2vDrva+j95L+uzDMZDF9nxpnjHE uHT9+hnrmB3Xted0tzxRC/77Cht8Kn4gaoljbBoZsRnv0vRRUYKA2OJHJRRCHhzZ AN6A+lWbWyvyvd3UeOLl3oFhk4lS5fwYY6RY8W9ZTJVxetVycvro42kK2jtpqUeF NercfjOg8VBWYjqB3Ey1wHDpjS407TW7RWEGA+3mP8ZFsuoYr7Rs+z8LprbYEPpF ojgvss+vMvjGkrcU8v0MR/sCAwEAAaOBjjCBizAOBgNVHQ8BAf8EBAMCA7gwHQYD VR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBRS8x8oOcBJnrrW jiS0t3qjKee63TAfBgNVHSMEGDAWgBTNBS7rmAcLgCfraHEZbvsVLjsOhTAaBgNV HREEEzARgglsb2NhbGhvc3SHBH8AAAEwDQYJKoZIhvcNAQELBQADggIBAI9t4Pvn o0cW0SrC4EFNJqUaffbqUNd3i+dBn5/EQc8EGYVY3k8E8iUMHzRDyH+/VRp3RUOH oJDDS2uyeJ2InkC193MOpNJDQV5qf3yOmQCeVmmjcXkg1Nc+3oe8ttWiVW0ArFXS oud6V7/6qzIz3850ypi0Zz4KLVhSGzaI8dnzPopqNEG+ZbgIwvQLqWhv2bLpqycY 5ANECpjH6QIEp8JNjga9Sp/LspNCqMDmVswBGarySNWZ1+uflg9X30hsdCzVPgX3 KMy0wVelT/4y893BCTo2KendGh70jaGxm8nBH7OXkeki2TI6boAvsn2Iash0HSZ4 hPacZ4QWFEYW4jZeu3ZNTJ9tc2u2jgpAGueOcWRY75CraLid1V5t1kpGxAtX4NvX X56e/IlmEI6qPsaoPouQ1riAWMdRQUT1FLNPanv4vElDYXBNcFl7knuS14JDCC0M K6ttSb2MxJYfC6J+OJpHQd2GWU5aO2uZcgi9jRMslNwR+R94bxI+q/bjrI31JsDz 1pVRnGRWH7cDejA2f+q7X8/uRuA8bfnqBcu0uI9YR64W0VMMLe41+iR0wt5N3yWr /DalWIvmUvE8LoaGDwxV9T3xq1I1dWnASX+Xmb1SQ0CnhEEooZfQYfb3ffciG6mU UvVC0YW1cjOgb193W/N+Dgju7/a/e+XgbsgT -----END CERTIFICATE----- ================================================ FILE: internal/testing/tls/server.csr ================================================ -----BEGIN CERTIFICATE REQUEST----- MIICgzCCAWsCAQAwETEPMA0GA1UEAxMGc2VydmVyMIIBIjANBgkqhkiG9w0BAQEF AAOCAQ8AMIIBCgKCAQEA2VUyEdkeNFaIaqak6x7UplOZ1RqJKCXdPzz4yJ7XtRuI uaw5+eR3c+4ScqVdFcyyo6DXmT3F+dXpJB5ukF+RzColUf9RKNZjgLV7a8Ou9r6P 3kv67MMxkMX2fGmeMcS4dP36GeuYHde153S3PFEL/vsKG3wqfiBqiWNsGhmxGe/S 9FFRgoDY4kclFEIeHNkA3oD6VZtbK/K93dR44uXegWGTiVLl/BhjpFjxb1lMlXF6 1XJy+ujjaQraO2mpR4U16tx+M6DxUFZiOoHcTLXAcOmNLjTtNbtFYQYD7eY/xkWy 6hivtGz7PwumttgQ+kWiOC+yz68y+MaStxTy/QxH+wIDAQABoC0wKwYJKoZIhvcN AQkOMR4wHDAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8AAAEwDQYJKoZIhvcNAQEL BQADggEBAA76r3SIQUER3XyGp4MOrfKGwBE7RnALcxW1XkrhID2bng2hzovrZZNO xutL1zqPFCxClIKUxYAXpMeY8lnS6H8I6FoM6ALCZbK7q9rmMK198LPMo3zC6TsO rEP8HOF9wxaYubg8xaq8iDlaL4e418M0UPOlE75PtkDAjhY++7ZTsjPVr/9WsJpZ MmEZ5kSS59PZbMbyqXn5MxE0iSD0LfM+lmkIBwSvD8rjq3SQ5NKCg6CJkRRq7BVe bujA2pPb6ivS5pujjIxkdUoz6S0G+ewZG16kbBoygWuRVFD8xqR9Pa41KSPhpx85 1qSrqR4zHvS3r+RS9UVIXnbh9ejW6Vg= -----END CERTIFICATE REQUEST----- ================================================ FILE: internal/testing/tls/server.key ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEA2VUyEdkeNFaIaqak6x7UplOZ1RqJKCXdPzz4yJ7XtRuIuaw5 +eR3c+4ScqVdFcyyo6DXmT3F+dXpJB5ukF+RzColUf9RKNZjgLV7a8Ou9r6P3kv6 7MMxkMX2fGmeMcS4dP36GeuYHde153S3PFEL/vsKG3wqfiBqiWNsGhmxGe/S9FFR goDY4kclFEIeHNkA3oD6VZtbK/K93dR44uXegWGTiVLl/BhjpFjxb1lMlXF61XJy +ujjaQraO2mpR4U16tx+M6DxUFZiOoHcTLXAcOmNLjTtNbtFYQYD7eY/xkWy6hiv tGz7PwumttgQ+kWiOC+yz68y+MaStxTy/QxH+wIDAQABAoIBAEzhDVAw/LVI8wK/ JlGh21lm82Dl/SS9mDE5kUvunKGNNuVvXibewb65tb7mbjI68epeCEZGCtVg7RMA zN23YOzW79K8vWnzxMkP6bPqSecw69WYDRBZ0BvFW3cRKYuzagjAmws2Qt4zoz5Y FEV66gJtrVqhpqptLyKgj+n/sp1YiHMjkcJF5PGnAPudYxpiDHiPFked7vZtigyq eE2GCVbaOmRGPMe28JzRmOuFqEN9GccRjlq+AuzYe14lWKa6fZLVke78luByF//M RA+gGoKfHq859wACOEGbqShMWxC++y1HJ2Mu4adUlikzVq8rboinaAxzv56kc1n6 EDc/qPECgYEA2x+qxHKSJnQuItZp0CpsDk51yr1fJcWtdEbcgmomiwMwl/Nk0OAB rplrW5gezyVRfMDsoqAnmBS301JEL/7QuEWvedTpptKC503Z+mCINPTZAaJfa7Jb KUCSQHO3hThfOXFMkb9mcJEVVNqtobnK61tC+JNOSd00Z9TdLQAbcokCgYEA/ehf Vq3md8bkQFlnMtFib8SIr8IP5j4JecFSeqa/OnBFfHJr0trUhoMO04uaQOl6lTT7 9Ca36WfJsv3EGkMHEFeceOsPswf65bI/qgxgUS7Qmu+NZTjvXL0gCpeHLPzEaLTV CDXoo+YAJzzv9yWwVEvIIru5SJPVud6Gap5L1WMCgYAjfO91PXD6FVrbfYpJknVJ o99j5GOihG9hI5DW9kYjwXJ/SYYMZhsfoe1HOk3TEqIt6Djq5bFD6icTbIFqnIRF M9QFkTv+Lp3QxEUHTdcBbJ4wq5F0qcAl4DVPhu4z/zs83GKgQDVhCb5AreHtDWAV 2gPwqjrFr7OrFUh0302SsQKBgQCOHKZn9HNfLOIKJj/9kHYhCoZaoSqW+rgA/rQ0 U+oKQlaR/dTdsn9rPiVpP+S5WjSzGHHAyH79U4rv9Nryu/tTKUY5447o7JmAQJEj k0PBjItTfKrOMdy/MlehtggBpQQlerkVnF62hYAmdhP1Z5HWzIea8SkWNzBTlPn0 6N6W8wKBgQCDBPXqGii/ur5IBQ+RASIL78RgIGH49XYcq7IrYFOuztGBbqf4qorR BTl/mWFFHr+fAcLHlC/qTSBBflGClElqcg+j+92RAI6HbCFemdSbnsv5FCmciL6e 09x+oprKq3/WAVARUBif3RtDq92SFwSBfrx8JFhGIa9kUcsDFSBaDQ== -----END RSA PRIVATE KEY----- ================================================ FILE: internal/testing/tls/wrong-ca.crl ================================================ -----BEGIN X509 CRL----- MIICgjBsAgEBMA0GCSqGSIb3DQEBCwUAMBMxETAPBgNVBAMTCHdyb25nLWNhFw0x NzA4MjUxNTQ1NTdaFw0yNzA4MjUxNTQ1NTdaMACgIzAhMB8GA1UdIwQYMBaAFGM6 559SRpX6VJlKbIObtrLXvc1rMA0GCSqGSIb3DQEBCwUAA4ICAQA+6zsHCq9YRZ+a fwsZmbGQqDUBVp5TWtDsy+qvKf/084CgTn0sR28HKEONQvX+R1CyzAaCGrkm081k yDUizdyGrVR8zmCc7O3ztPobfZBmQXbR0pcxwweFiELBO1exEQ5IpM4J0KOPc+CB AwVA227Q4oKrKyUtNQ9d3qr0/2E2HE8W0aV7Ax38MvXsUfafWk0SNPDusFiYNsTt v50Gmd2yBlaMzT9Dsze9wuoTvT42lpCby/NSSDYynG9Cra2y0VoIpVxvPqVzn77L 1otkaQRatbfktaa1WVufEK81FXEyeYdM/T4bKCbB5oBKxmwwiS8ukvunc9gdrosx /7QIlpr3iBEu+X+GeOdGyPA+a+S566Hil38QCyMUX4fI7xjYt3ek2MI1YeNEv576 CwEihc7NvPh5MI0OQq0nIjTcIeEGQag0eAaGwmJ+AmrZ+4bop5QavuwHVh/7sNel rNhFucmx+gEPeS/Ae0cp2BeXjEXHmdfwKDCT1n1Rdd5wfLeFuKlBG8NdZKwZ7HH7 vwi+JwIBx/WJ/f1qcPlWAyF4Y/HUJIRRNSXJOUQCWhCvfGtLQ4xs7uo4ViE8CtyO RE/3xHrX9UwJUymw50Efj3PpOxWPvJ9B7A+8ED1kJR29HQz5gVZhEcW6nDTntwDQ 5nrvhzi93xzaTQIhEe53uz+rTGs/fw== -----END X509 CRL----- ================================================ FILE: internal/testing/tls/wrong-ca.crt ================================================ -----BEGIN CERTIFICATE----- MIIE5jCCAs6gAwIBAgIBATANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDEwh3cm9u Zy1jYTAeFw0xNzA4MjUxNTQ1NTRaFw0yNzA4MjUxNTQ1NTdaMBMxETAPBgNVBAMT CHdyb25nLWNhMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzs91DHKf enUfIPigN6n4CsTSHGMkRpWQw9T5vIqUd3ZgMJLCoEnj0OpVlFVcb9+P2/f5RYg8 H0dmM1iHGi5uCoDnJk7K5zaLMgxGtRy8dwZ1nHri3sZwM6Z9AyvktuSVbuElzp08 utwB+fstR2METN5Dovp738rdx2q3zYYYcfnEXcYvxBdxVeitFgrXauRDxuqMio+5 5bHVUpIWlpu8Fd3CqnMUS4N6McijIn6T2wiyALJDf9xE7edAYl4ExYVKaOyR3Ff3 +BhiS+IEPd9AoctU9JYFpDavfaiZz77AukwwfU+W93NTTFQ+rf/ev8XzsgkFyZPw CCfKuet+o2/8MIxv4nwKxv6GGMFbQz1gNw3RqG5m19zppqVzp1vgMcNXSeRPFlQI fXYFN9BY9bvx2L2ZpTn3gsdgzDGNzYU5fGro3YzmtelNBCY3sAz/riG6+wMDHCId 5K5NxrJBW3tTvEZQyKVZA1W22/F/Wz2LxA+4ZLhUoUuXTkJxLS75EWLkK2xK+IXv h4s8n8CwfhFV/De7u18Pho0XKTm2IPir1nL0WNhjy4jvBYDN/Jy4fE4QALt4oH9t +GITkDo0Zd/USZSkAOXgEb+Ks5F/fztI9yVp7/nhhj1KnSIrkObBaltlfEOe4vgz 3dNFAG5kH5lyG90uffKR7h1Vo7UkYcpJ3IMCAwEAAaNFMEMwDgYDVR0PAQH/BAQD AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFGM6559SRpX6VJlKbIOb trLXvc1rMA0GCSqGSIb3DQEBCwUAA4ICAQCIm4pRbel8V/W1OKSzJXg3F76ORIjz zN4mAtCX8YtFQwawBlES25Ju2IQ9XfvqM0CPO5LEe1v8ZTXeke9Vjf/XGReBCCqy /STzLSBHflQqvybMYH87K5h5e91Ow9T2HjyPtzS3RdyaahU/Y8/EnOTG89uJlpN3 0k0/KXfwVKAyjrOaoTeGPM9BjDssNq2S07h5C8sCby3MpR26CIMGbFnotwTjmSww qkDSVd63/ZIB5/dOcOlBd1+rE3LOzYxDiZtKWu0NM+o7N0m4Y+gD4siyxRWuKslz cTwiwnLmzZG5BUvRT2FmzCwejp45+LjrXmUZ8hCznk68hnkilx9XLdkBL/1qyk40 I1IUFQtkkcyznwUyKpC0z4VJZAVL8xi6KO60TOYtidxFTJxkPrWcAHvgzItao3XZ C4hLlNk7RD6BJ8oyMtpXFq7MHAAb8MWSLu/rSAhQHoKqlCEK4Iks9nWLmRP0OdAw BcXGMuTIn1jFRM0CQvg68GPFOH3FKv+cyUbjPoXvCBYiXKmxA/WX3rYvDo2paZKU /mDMu+EdAR3Zk/wYXl4738ujqzO88Nw2LBHLKhXytHMaSbfmWf085r0L+fqHLuVM jlpPEi6vQum25j9tvGnp6GyO8lUDAUqk5gtYIp+D67+NG+9eBocA1ADVpeKZHBQV xGgCdjnoP+nDVw== -----END CERTIFICATE----- ================================================ FILE: internal/testing/tls/wrong-ca.key ================================================ -----BEGIN RSA PRIVATE KEY----- MIIJKQIBAAKCAgEAzs91DHKfenUfIPigN6n4CsTSHGMkRpWQw9T5vIqUd3ZgMJLC oEnj0OpVlFVcb9+P2/f5RYg8H0dmM1iHGi5uCoDnJk7K5zaLMgxGtRy8dwZ1nHri 3sZwM6Z9AyvktuSVbuElzp08utwB+fstR2METN5Dovp738rdx2q3zYYYcfnEXcYv xBdxVeitFgrXauRDxuqMio+55bHVUpIWlpu8Fd3CqnMUS4N6McijIn6T2wiyALJD f9xE7edAYl4ExYVKaOyR3Ff3+BhiS+IEPd9AoctU9JYFpDavfaiZz77AukwwfU+W 93NTTFQ+rf/ev8XzsgkFyZPwCCfKuet+o2/8MIxv4nwKxv6GGMFbQz1gNw3RqG5m 19zppqVzp1vgMcNXSeRPFlQIfXYFN9BY9bvx2L2ZpTn3gsdgzDGNzYU5fGro3Yzm telNBCY3sAz/riG6+wMDHCId5K5NxrJBW3tTvEZQyKVZA1W22/F/Wz2LxA+4ZLhU oUuXTkJxLS75EWLkK2xK+IXvh4s8n8CwfhFV/De7u18Pho0XKTm2IPir1nL0WNhj y4jvBYDN/Jy4fE4QALt4oH9t+GITkDo0Zd/USZSkAOXgEb+Ks5F/fztI9yVp7/nh hj1KnSIrkObBaltlfEOe4vgz3dNFAG5kH5lyG90uffKR7h1Vo7UkYcpJ3IMCAwEA AQKCAgEAhCK25YIi9Sn5/qX8MDSP/8lream6lsKfIRBllBpy67UdlktewO0U+vmO Pl0f13bewqu4f72gtFd5LBtHDupVcq6Tgb1cFMibvRls3/EBVYcyBA3cAHyHWejo /OrBkj2QYKzH7DA4iidht+fNMUxJhheI3YvvM7i5ZN2BnHYuDjyIQ2YKRN65kis8 09WPd4Nq7qATtcBJBUJPSxd+CTJtxQbQhvlKIUla/I319WcsbwkqOhmr2PjSrbJQ R8lMgSs9tLZaJ4+pJsHlpBg/n4ySDg4NNMzZw+cQz1e3Fq4JE770SExe57Guqhk1 hxTxrFP89WagZP/5oCxUcd/OJPy7At+MzLY+xDySXUqJjXO32FvSY1QCXySKxwxT jT2uOEEUiQs3Aap94ejm6rPEaifrGLlv+a28R+6gaaJIAQ+b/U8NapkhQI4k7uZT IY2FeIJKbbthjYYmvlpTMbIMKMTvRrqlWWOJ7Nd8gJo8vtFT0rRbm3joLzfJy3M+ ITIUjrLPIMHkEJ+A8OaqIEG7Wy97ONevUZDKTj0oElaTgIcIuI0aPdjB5cC7R/iz 4G0SJ62UheFrq10RX3IG7xRtyyNiF7Qy7CIJAFYYZuXknNPGUve+Dnbn/TInewSV 96pJf3xZj0PY1sYWLmFIJYoHLGK4VLmd4zm4Tw1ewYz/7Oh/ZYECggEBAPEzseYe Jkx6+Wz8v6hZjcYRn1+8Awdbb3mv5oW2eNHxtjX+ltYeABjUBYJsvO54ptIV5Bq1 wojVSCAOy8z750SiRCzmm7yEr9Zc3bRoY03L/fi/pKlTxhS2zwrZIKIn/0Z4hYz+ 7UILIu23Pv0ctCi3zy09vGVvQi9VJ6KTuFWgaIDnGq5heQ0/7Ae+FfTVuqLOMUCb 4x+9ui2r7Xlu/TPMNaiNXb7OxJ7Yw0xr2w1OiHqBuYRGMXULc24nkTYPmCbZbHcA rUPq/JPP2HYvjqK/4iWEkuAYzTPTXWBmD1zGD644dazCBn6QKuwICjo9F+/mI2rP SMwD5UmZsIrxQEMCggEBANt/nD+A/66u7zJiStbIjryMXHuVzJsMNBt66sA3zxES wnVjpZ0vL+qrwsFa8rHLh90LmcOCJ6u3NYb2GZidK9P/uyAjKQdjVJEvk7T7QGMX yQHhQBIxh7TqK0CWYJy69E+Mhmn+HwsMX8GH/tHc+wdr5K6t9RgvWgMeV5sFfGrf fJr9VhVRhBqxpoL1fp+A3ya+z5Bn/dcXJuBs7lqHhKCySJKCMmvjijRj4zuVHaHX KI47gwX1vsxerqNEKA2kBuQOuKtd+ySQ6EvdhkE4G1lDESr5NGBjTtIsLlB7UcOK GIFSbmbYwCgzjFU9/sz5gfrgNRFb0gd/TDR4+CCcTsECggEBAI7m/cNEoZQ2V4iG xlZLmH98+VuS3IiDV6xU1tLppPNdrYKX7220IIKVOx5mphjzSoK1jYt1nGfNVQoJ Oh2cMQysxo+DoUkzo6nxIzk7j3oMHdA+WqQniffDxy66LWdlIwzxYs6CSrcSOgN0 ydDULLjjDc/T/8ZpAGFipjTgKBozCzcztM8T2NBMyt5bdE62QfkrCGsq8IlhsuhU MEH9y+3gUvolpyDhCATEkBC65fEgUiOir/L6U1rxCdZ9gr7wxkheELEAqabPlg1M 2wZKbstlu+pWfV5f01OdKnlufjOM9MVXlgBgg9CAQa3NpaGTiJcNVnZ1kL+uny3X 7IylGlkCggEATH/wO+3ArugHM78wKCVkIfCldukhk1QwgPdZA78vqtqn7XPaT6sX fyl3yh3hgffWlUKqx4oAO4ex3yS8jQUSNmPlmvDGJu4GlkdHqob6zM6IXuBbjTu3 +WS3yF3gtB8wcN0gJ6bKuPYKFZBJTmk/EDoZTIwSZOhz7axQihXiY/kaG4Z5zxpG +Wq7Bt96zyqCG6Xa/5BO1v0ZrpQoimK65ardQjqgShvWmiXKF4UD+9jaKKAzLQuW APJq2Toy33YwdKFw2UD6+6aJX4+IcAiW94g5XonWKFXULcn6JlCkkYr6uW+6TJv0 dM5qdXcS6+t10rL7q94dmEFUlOEoUW1IwQKCAQA4687RyeOp0wS21ZFpRwAhuWkQ ZxPNeOG/lxERYD/rj3cE/zSdqun+Z2HwD3tndjbjOZhw5XoIfsRl7PsOGx5ngrmI fdYE8n25myO1TQADblD79/kypYauXLbquJwXRNhGqzJPBNlN5rga0OzbK3YH+oG0 sndBVW3UIr071Zs3dO45+EJKbgKU2sYgi7yhMaSVkZxey3BteRBhqzqWlgUfqMwK Nbj1vuE/Ghso0dbZiYDX9IxrS7BT4ddZ0Wm24KIj1WlXNKv/zZhqktlh+GM2pRlx DQyYdp4njnkFpRuDSMSAW5V3/zyFnsZpH4ToT+MQwKFe0p7T00evdPdojRST -----END RSA PRIVATE KEY----- ================================================ FILE: internal/testing/tls/wrong-client.crt ================================================ -----BEGIN CERTIFICATE----- MIIEJTCCAg2gAwIBAgIQINVcdjlrWhsKxwcXL4vbUDANBgkqhkiG9w0BAQsFADAT MREwDwYDVQQDEwh3cm9uZy1jYTAeFw0xNzA4MjUxNTQ1NThaFw0yNzA4MjUxNTQ1 NTZaMBcxFTATBgNVBAMTDHdyb25nLWNsaWVudDCCASIwDQYJKoZIhvcNAQEBBQAD ggEPADCCAQoCggEBANHAXfyT8vuNKc3FeedZH3865bF5PLbRs2R8CaUOdQj/HTM5 xSsb5Tr3x6IkWK+5SwtnGdaLY7GktSktXyUNf2uZflXHCLAqiqcBpLNO9mcFAACz pRb7C18ZZ6d9b7UtPA5oK1Vt45iUzI+mdCC0BtRTWeyKdtTF4muD2TtF7RMQwnjf RGV1EkfQ3sKpX3P7daiA/W116NlESpX3J/VAzoQu+3BrDeXrqgEbNVl+/NN/7uA0 RY0HxE75RNhL9yuz1VFP4/NaFOdWN3pSMKwcmiIeNC5n0eyW/fodQpOT/dcscfbP 3a0XeoZkfB8nuVcGkmtUkuw0jPy+R64vnQvlugsCAwEAAaNxMG8wDgYDVR0PAQH/ BAQDAgO4MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQU 0pX6qc7YwiehN5AYh+p4JNkFEXwwHwYDVR0jBBgwFoAUYzrnn1JGlfpUmUpsg5u2 ste9zWswDQYJKoZIhvcNAQELBQADggIBALH5pFNhbnro2vFE+8RqbRqOZZNoyKqL INY/e0MNPmhp4CE2BQcrxgFcRgJmyh4lOP8gmIHT8q/9kOvYqMmfU1vbVF/XLFsa NJxkQSX9uilV1LDykyRbwlI4McjdTW7rLEkW8YrZueMXnDYQHGx9L2qYWgXzA5yA Mfsgq3pr39sDVDfYg1H+0daA3nIw+OWDsWjORXvzo5TQzjUXLhREp6WuuRKBT1+p VHGAnUcwDEb6L1bWEloG9ogXJfsXuCUxF+/II1RYSKiAmjge1nDOM2USfIKfD5nz tLJn0pn0B5dyceJTgOK6dwCXwn0Gc99qVzBSSHtPe+abSuY5dNoIwtL4R4rDE4U+ +y2vQwzum+GhHn/ZEDuYT/0+IDqkVxeWBiZ5IFEkRpBEFpmEJOdKWaSrIQWMpIjf FIlxY3VzUD8H5M65kMSRKXbRJ1zSHMcIFKK2R98SPuYnYmgc4kOh49WkEr6dj2B1 0QNZxPg70HP3qWgCxf8F5Mxg5YOtz7gN6N3AutrlYV0KB/OT0h4lhtvW3inxRgID iAHw0A4X/1qbFUeSUpINZaVQFtBh6fT/JfYDDTFFBcoqrLOZpFaS7r6FbGP3FDS4 v9MqsYOSA6LrOHMdRop+eDV718iGDcUVtIItRZZV0s4UTo5q0JpGBtVPo/R8ui19 eaGxvLJT1Gd+ -----END CERTIFICATE----- ================================================ FILE: internal/testing/tls/wrong-client.csr ================================================ -----BEGIN CERTIFICATE REQUEST----- MIICXDCCAUQCAQAwFzEVMBMGA1UEAxMMd3JvbmctY2xpZW50MIIBIjANBgkqhkiG 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0cBd/JPy+40pzcV551kffzrlsXk8ttGzZHwJ pQ51CP8dMznFKxvlOvfHoiRYr7lLC2cZ1otjsaS1KS1fJQ1/a5l+VccIsCqKpwGk s072ZwUAALOlFvsLXxlnp31vtS08DmgrVW3jmJTMj6Z0ILQG1FNZ7Ip21MXia4PZ O0XtExDCeN9EZXUSR9Dewqlfc/t1qID9bXXo2URKlfcn9UDOhC77cGsN5euqARs1 WX7803/u4DRFjQfETvlE2Ev3K7PVUU/j81oU51Y3elIwrByaIh40LmfR7Jb9+h1C k5P91yxx9s/drRd6hmR8Hye5VwaSa1SS7DSM/L5Hri+dC+W6CwIDAQABoAAwDQYJ KoZIhvcNAQELBQADggEBAMxscjfVRQ0/0c6f0MWtJJe+vy5Gj26XHVy5EsbH1ofq eWF00CFlVw5CdznGV0NL6LOE+sz5sBKsN2sZU7xPeV5XRHVXpAuECcOcgWK6FkqA wSmwVWZ93o+kJXrUZTyZBMkvQMUUr30JIpXIXJmLWKPBq5KRBJLirHZYw4FmbARv iZdNlQ1rLOZKZl7yUVkAfyfw+ueb0OPFp/fzuCerNB0ySSmYdHzqDFsMLm4Bq/2z FJOgasAU2RvxFVoRp7P/ZUSvtOKACMUHaYBnKZAvkKv3MIS/qLCSkzxNwsclT8uF aKRFZnOkZZHvEaXVAwCfJB7tI3TELb+L2KyqU36Q1Oc= -----END CERTIFICATE REQUEST----- ================================================ FILE: internal/testing/tls/wrong-client.key ================================================ -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEA0cBd/JPy+40pzcV551kffzrlsXk8ttGzZHwJpQ51CP8dMznF KxvlOvfHoiRYr7lLC2cZ1otjsaS1KS1fJQ1/a5l+VccIsCqKpwGks072ZwUAALOl FvsLXxlnp31vtS08DmgrVW3jmJTMj6Z0ILQG1FNZ7Ip21MXia4PZO0XtExDCeN9E ZXUSR9Dewqlfc/t1qID9bXXo2URKlfcn9UDOhC77cGsN5euqARs1WX7803/u4DRF jQfETvlE2Ev3K7PVUU/j81oU51Y3elIwrByaIh40LmfR7Jb9+h1Ck5P91yxx9s/d rRd6hmR8Hye5VwaSa1SS7DSM/L5Hri+dC+W6CwIDAQABAoIBAHml4JyRTdX4q+sM gcPcG3lVtkt0rfK1sh4wFgPlW5kpJE1GTwTOe+b0N5LhE5Jum4h0djbIxrwLc4n7 J3g82M6VygCDm5VYRuvO9y+LNzrOWo8NoUyvsouoF0a7aCMipfcRETjNr7cZbX5O ooEpB+Dyqm+Wao7CaavDXySSTInGHG4AD9HM5nQsVIebS1HOkhI7SmNkZTOd3gzp bR/iZgaYI5eC7Zj7hHNr4gWdRBuefU8wLZZGoqByHRTSrKwICRLIkGyoMRAD9p9r S48lyUmd3BGHRPLmNl4u0kfsVlcCYBNKVZV6kkSVv4Wht/KVr3tl0+2wrNUe17w7 vlCsCPECgYEA9AdHq3UjswD6PYITCdrDOGVLMpgL0obA6X2Fi6GsMiRXQgsvxMVY a4D2vtfZvLa8TSA+b8bK6uSMyD3mOHLxBkUPMQZxiHzq/ldK9vSIvzky0woF6suT J8fa2F0QfjlKWhFMwf6JVGyZl+vmYqqF55lxRJSWGZHSSxaxYPU7dbkCgYEA3Aqb YInpCp7zSlWYfXorwnyvsHsTdFbsoGGgMrc5l2B7PcP+Na7lm0dsNCrp7n7TSrE0 8iIoqhYq5u7RlGf5QXzXcGgZQMHcxLcrqBPviEktuUifXZEwfw9NXrlt4iF/oVTc ++7jqUZ+iH9NIMoxrQPXVdXJSJN9iwc7/yG6j+MCgYA+ehqsWCpSqx5mXwYW0M6I gs6U3n6wYNXFMeDeFf9rOwioHQsW2tu/cl46EDNr8HEXYfj6TzAmoWs13TszGqKA 02+HQroQksLraVgFEChupOtRQtCvA33ignWSTYlqd6qEksdPJ6brWX6decUbX8M2 v39TaqNfWok3tlClnUOi6QKBgQCIhfQ9g5OZyWE937nLMH/yHZaMMvCxIDWUlL3m eZQ7/dq5Sd9xw2AmZbwW6gFWvk2ubCBjkxoT3ckkm0xhfdlC7ohk79GrQh0N2HA3 ypa1wmGiMhLe5PRoAUCJ4xbwVMRxfsvVbDTIlDpxyjo6e/kyVc3HLevDIe+k0QpC k9TC7QKBgDyghEPAM5euQHk2o7cMr0YK52vzoM1FGhrRAF5MhTYJEYBNu0Z+0McB G8kwy4WH5zMuvKj1zZckAzkbpD/iL3XQuzs9pZdNnXzdf25/us0pZ2a/6v5+fpmF JEuwQ1AztPEv4tLd3+xrmE+j+qd3xDqYt8eaWFswcuxchB6nUdqq -----END RSA PRIVATE KEY----- ================================================ FILE: invoke.go ================================================ package grpcurl import ( "bytes" "context" "fmt" "io" "strings" "sync" "sync/atomic" "github.com/golang/protobuf/jsonpb" //lint:ignore SA1019 we have to import these because some of their types appear in exported API "github.com/golang/protobuf/proto" //lint:ignore SA1019 same as above "github.com/jhump/protoreflect/desc" //lint:ignore SA1019 same as above "github.com/jhump/protoreflect/dynamic" //lint:ignore SA1019 same as above "github.com/jhump/protoreflect/dynamic/grpcdynamic" "github.com/jhump/protoreflect/grpcreflect" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" ) // InvocationEventHandler is a bag of callbacks for handling events that occur in the course // of invoking an RPC. The handler also provides request data that is sent. The callbacks are // generally called in the order they are listed below. type InvocationEventHandler interface { // OnResolveMethod is called with a descriptor of the method that is being invoked. OnResolveMethod(*desc.MethodDescriptor) // OnSendHeaders is called with the request metadata that is being sent. OnSendHeaders(metadata.MD) // OnReceiveHeaders is called when response headers have been received. OnReceiveHeaders(metadata.MD) // OnReceiveResponse is called for each response message received. OnReceiveResponse(proto.Message) // OnReceiveTrailers is called when response trailers and final RPC status have been received. OnReceiveTrailers(*status.Status, metadata.MD) } // RequestMessageSupplier is a function that is called to retrieve request // messages for a GRPC operation. This type is deprecated and will be removed in // a future release. // // Deprecated: This is only used with the deprecated InvokeRpc. Instead, use // RequestSupplier with InvokeRPC. type RequestMessageSupplier func() ([]byte, error) // InvokeRpc uses the given gRPC connection to invoke the given method. This function is deprecated // and will be removed in a future release. It just delegates to the similarly named InvokeRPC // method, whose signature is only slightly different. // // Deprecated: use InvokeRPC instead. func InvokeRpc(ctx context.Context, source DescriptorSource, cc *grpc.ClientConn, methodName string, headers []string, handler InvocationEventHandler, requestData RequestMessageSupplier) error { return InvokeRPC(ctx, source, cc, methodName, headers, handler, func(m proto.Message) error { // New function is almost identical, but the request supplier function works differently. // So we adapt the logic here to maintain compatibility. data, err := requestData() if err != nil { return err } return jsonpb.Unmarshal(bytes.NewReader(data), m) }) } // RequestSupplier is a function that is called to populate messages for a gRPC operation. The // function should populate the given message or return a non-nil error. If the supplier has no // more messages, it should return io.EOF. When it returns io.EOF, it should not in any way // modify the given message argument. type RequestSupplier func(proto.Message) error // InvokeRPC uses the given gRPC channel to invoke the given method. The given descriptor source // is used to determine the type of method and the type of request and response message. The given // headers are sent as request metadata. Methods on the given event handler are called as the // invocation proceeds. // // The given requestData function supplies the actual data to send. It should return io.EOF when // there is no more request data. If the method being invoked is a unary or server-streaming RPC // (e.g. exactly one request message) and there is no request data (e.g. the first invocation of // the function returns io.EOF), then an empty request message is sent. // // If the requestData function and the given event handler coordinate or share any state, they should // be thread-safe. This is because the requestData function may be called from a different goroutine // than the one invoking event callbacks. (This only happens for bi-directional streaming RPCs, where // one goroutine sends request messages and another consumes the response messages). func InvokeRPC(ctx context.Context, source DescriptorSource, ch grpcdynamic.Channel, methodName string, headers []string, handler InvocationEventHandler, requestData RequestSupplier) error { md := MetadataFromHeaders(headers) svc, mth := parseSymbol(methodName) if svc == "" || mth == "" { return fmt.Errorf("given method name %q is not in expected format: 'service/method' or 'service.method'", methodName) } dsc, err := source.FindSymbol(svc) if err != nil { // return a gRPC status error if hasStatus is true errStatus, hasStatus := status.FromError(err) switch { case hasStatus && isNotFoundError(err): return status.Errorf(errStatus.Code(), "target server does not expose service %q: %s", svc, errStatus.Message()) case hasStatus: return status.Errorf(errStatus.Code(), "failed to query for service descriptor %q: %s", svc, errStatus.Message()) case isNotFoundError(err): return fmt.Errorf("target server does not expose service %q", svc) } return fmt.Errorf("failed to query for service descriptor %q: %v", svc, err) } sd, ok := dsc.(*desc.ServiceDescriptor) if !ok { return fmt.Errorf("target server does not expose service %q", svc) } mtd := sd.FindMethodByName(mth) if mtd == nil { return fmt.Errorf("service %q does not include a method named %q", svc, mth) } handler.OnResolveMethod(mtd) // we also download any applicable extensions so we can provide full support for parsing user-provided data var ext dynamic.ExtensionRegistry alreadyFetched := map[string]bool{} if err = fetchAllExtensions(source, &ext, mtd.GetInputType(), alreadyFetched); err != nil { return fmt.Errorf("error resolving server extensions for message %s: %v", mtd.GetInputType().GetFullyQualifiedName(), err) } if err = fetchAllExtensions(source, &ext, mtd.GetOutputType(), alreadyFetched); err != nil { return fmt.Errorf("error resolving server extensions for message %s: %v", mtd.GetOutputType().GetFullyQualifiedName(), err) } msgFactory := dynamic.NewMessageFactoryWithExtensionRegistry(&ext) req := msgFactory.NewMessage(mtd.GetInputType()) handler.OnSendHeaders(md) ctx = metadata.NewOutgoingContext(ctx, md) stub := grpcdynamic.NewStubWithMessageFactory(ch, msgFactory) ctx, cancel := context.WithCancel(ctx) defer cancel() if mtd.IsClientStreaming() && mtd.IsServerStreaming() { return invokeBidi(ctx, stub, mtd, handler, requestData, req) } else if mtd.IsClientStreaming() { return invokeClientStream(ctx, stub, mtd, handler, requestData, req) } else if mtd.IsServerStreaming() { return invokeServerStream(ctx, stub, mtd, handler, requestData, req) } else { return invokeUnary(ctx, stub, mtd, handler, requestData, req) } } func invokeUnary(ctx context.Context, stub grpcdynamic.Stub, md *desc.MethodDescriptor, handler InvocationEventHandler, requestData RequestSupplier, req proto.Message) error { err := requestData(req) if err != nil && err != io.EOF { return fmt.Errorf("error getting request data: %v", err) } if err != io.EOF { // verify there is no second message, which is a usage error err := requestData(req) if err == nil { return fmt.Errorf("method %q is a unary RPC, but request data contained more than 1 message", md.GetFullyQualifiedName()) } else if err != io.EOF { return fmt.Errorf("error getting request data: %v", err) } } // Now we can actually invoke the RPC! var respHeaders metadata.MD var respTrailers metadata.MD resp, err := stub.InvokeRpc(ctx, md, req, grpc.Trailer(&respTrailers), grpc.Header(&respHeaders)) stat, ok := status.FromError(err) if !ok { // Error codes sent from the server will get printed differently below. // So just bail for other kinds of errors here. return fmt.Errorf("grpc call for %q failed: %v", md.GetFullyQualifiedName(), err) } handler.OnReceiveHeaders(respHeaders) if stat.Code() == codes.OK { handler.OnReceiveResponse(resp) } handler.OnReceiveTrailers(stat, respTrailers) return nil } func invokeClientStream(ctx context.Context, stub grpcdynamic.Stub, md *desc.MethodDescriptor, handler InvocationEventHandler, requestData RequestSupplier, req proto.Message) error { // invoke the RPC! str, err := stub.InvokeRpcClientStream(ctx, md) // Upload each request message in the stream var resp proto.Message for err == nil { err = requestData(req) if err == io.EOF { resp, err = str.CloseAndReceive() break } if err != nil { return fmt.Errorf("error getting request data: %v", err) } err = str.SendMsg(req) if err == io.EOF { // We get EOF on send if the server says "go away" // We have to use CloseAndReceive to get the actual code resp, err = str.CloseAndReceive() break } req.Reset() } // finally, process response data stat, ok := status.FromError(err) if !ok { // Error codes sent from the server will get printed differently below. // So just bail for other kinds of errors here. return fmt.Errorf("grpc call for %q failed: %v", md.GetFullyQualifiedName(), err) } if str != nil { if respHeaders, err := str.Header(); err == nil { handler.OnReceiveHeaders(respHeaders) } } if stat.Code() == codes.OK { handler.OnReceiveResponse(resp) } if str != nil { handler.OnReceiveTrailers(stat, str.Trailer()) } return nil } func invokeServerStream(ctx context.Context, stub grpcdynamic.Stub, md *desc.MethodDescriptor, handler InvocationEventHandler, requestData RequestSupplier, req proto.Message) error { err := requestData(req) if err != nil && err != io.EOF { return fmt.Errorf("error getting request data: %v", err) } if err != io.EOF { // verify there is no second message, which is a usage error err := requestData(req) if err == nil { return fmt.Errorf("method %q is a server-streaming RPC, but request data contained more than 1 message", md.GetFullyQualifiedName()) } else if err != io.EOF { return fmt.Errorf("error getting request data: %v", err) } } // Now we can actually invoke the RPC! str, err := stub.InvokeRpcServerStream(ctx, md, req) if str != nil { if respHeaders, err := str.Header(); err == nil { handler.OnReceiveHeaders(respHeaders) } } // Download each response message for err == nil { var resp proto.Message resp, err = str.RecvMsg() if err != nil { if err == io.EOF { err = nil } break } handler.OnReceiveResponse(resp) } stat, ok := status.FromError(err) if !ok { // Error codes sent from the server will get printed differently below. // So just bail for other kinds of errors here. return fmt.Errorf("grpc call for %q failed: %v", md.GetFullyQualifiedName(), err) } if str != nil { handler.OnReceiveTrailers(stat, str.Trailer()) } return nil } func invokeBidi(ctx context.Context, stub grpcdynamic.Stub, md *desc.MethodDescriptor, handler InvocationEventHandler, requestData RequestSupplier, req proto.Message) error { ctx, cancel := context.WithCancel(ctx) defer cancel() // invoke the RPC! str, err := stub.InvokeRpcBidiStream(ctx, md) var wg sync.WaitGroup var sendErr atomic.Value defer wg.Wait() if err == nil { wg.Add(1) go func() { defer wg.Done() // Concurrently upload each request message in the stream var err error for err == nil { err = requestData(req) if err == io.EOF { err = str.CloseSend() break } if err != nil { err = fmt.Errorf("error getting request data: %v", err) cancel() break } err = str.SendMsg(req) req.Reset() } if err != nil { sendErr.Store(err) } }() } if str != nil { if respHeaders, err := str.Header(); err == nil { handler.OnReceiveHeaders(respHeaders) } } // Download each response message for err == nil { var resp proto.Message resp, err = str.RecvMsg() if err != nil { if err == io.EOF { err = nil } break } handler.OnReceiveResponse(resp) } if se, ok := sendErr.Load().(error); ok && se != io.EOF { err = se } stat, ok := status.FromError(err) if !ok { // Error codes sent from the server will get printed differently below. // So just bail for other kinds of errors here. return fmt.Errorf("grpc call for %q failed: %v", md.GetFullyQualifiedName(), err) } if str != nil { handler.OnReceiveTrailers(stat, str.Trailer()) } return nil } type notFoundError string func notFound(kind, name string) error { return notFoundError(fmt.Sprintf("%s not found: %s", kind, name)) } func (e notFoundError) Error() string { return string(e) } func isNotFoundError(err error) bool { if grpcreflect.IsElementNotFoundError(err) { return true } _, ok := err.(notFoundError) return ok } func parseSymbol(svcAndMethod string) (string, string) { pos := strings.LastIndex(svcAndMethod, "/") if pos < 0 { pos = strings.LastIndex(svcAndMethod, ".") if pos < 0 { return "", "" } } return svcAndMethod[:pos], svcAndMethod[pos+1:] } ================================================ FILE: mk-test-files.sh ================================================ #!/bin/bash set -e cd "$(dirname $0)" # Run this script to generate files used by tests. echo "Creating protosets..." protoc testing/test.proto \ --include_imports \ --descriptor_set_out=testing/test.protoset protoc testing/example.proto \ --include_imports \ --descriptor_set_out=testing/example.protoset protoc testing/jsonpb_test_proto/test_objects.proto \ --go_out=paths=source_relative:. echo "Creating certs for TLS testing..." if ! hash certstrap 2>/dev/null; then # certstrap not found: try to install it go get github.com/square/certstrap go install github.com/square/certstrap fi function cs() { certstrap --depot-path testing/tls "$@" --passphrase "" } rm -rf testing/tls # Create CA cs init --years 10 --common-name ca # Create client cert cs request-cert --common-name client cs sign client --years 10 --CA ca # Create server cert cs request-cert --common-name server --ip 127.0.0.1 --domain localhost cs sign server --years 10 --CA ca # Create another server cert for error testing cs request-cert --common-name other --ip 1.2.3.4 --domain foobar.com cs sign other --years 10 --CA ca # Create another CA and client cert for more # error testing cs init --years 10 --common-name wrong-ca cs request-cert --common-name wrong-client cs sign wrong-client --years 10 --CA wrong-ca # Create expired cert cs request-cert --common-name expired --ip 127.0.0.1 --domain localhost cs sign expired --years 0 --CA ca ================================================ FILE: releasing/README.md ================================================ # Releases of gRPCurl This document provides instructions for building a release of `grpcurl`. The release process consists of a handful of tasks: 1. Drop a release tag in git. 2. Build binaries for various platforms. This is done using the local `go` tool and uses `GOOS` and `GOARCH` environment variables to cross-compile for supported platforms. 3. Creates a release in GitHub, uploads the binaries, and creates provisional release notes (in the form of a change log). 4. Build a docker image for the new release. 5. Push the docker image to Docker Hub, with both a version tag and the "latest" tag. 6. Submits a PR to update the [Homebrew](https://brew.sh/) recipe with the latest version. Most of this is automated via a script in this same directory. The main thing you will need is a GitHub personal access token, which will be used for creating the release in GitHub (so you need write access to the fullstorydev/grpcurl repo) and to open a Homebrew pull request. ## Creating a new release So, to actually create a new release, just run the script in this directory. First, you need a version number for the new release, following sem-ver format: `v..`. Second, you need a personal access token for GitHub. We'll use `v2.3.4` as an example version and `abcdef0123456789abcdef` as an example GitHub token: ```sh # from the root of the repo GITHUB_TOKEN=abcdef0123456789abcd \ ./releasing/do-release.sh v2.3.4 ``` Wasn't that easy! There is one last step: update the release notes in GitHub. By default, the script just records a change log of commit descriptions. Use that log (and, if necessary, drill into individual PRs included in the release) to flesh out notes in the format of the `RELEASE_NOTES.md` file _in this directory_. Then login to GitHub, go to the new release, edit the notes, and paste in the markdown you just wrote. That should be all there is to it! If things go wrong and you have to re-do part of the process, see the sections below. ---- ### GitHub Releases The GitHub release is the first step performed by the `do-release.sh` script. So generally, if there is an issue with that step, you can re-try the whole script. Note, if running the script did something wrong, you may have to first login to GitHub and remove uploaded artifacts for a botched release attempt. In general, this is _very undesirable_. Releases should usually be considered immutable. Instead of removing uploaded assets and providing new ones, it is often better to remove uploaded assets (to make bad binaries no longer available) and then _release a new patch version_. (You can edit the release notes for the botched version explaining why there are no artifacts for it.) The steps to do a GitHub-only release (vs. running the entire script) are the following: ```sh # from the root of the repo git tag v2.3.4 GITHUB_TOKEN=abcdef0123456789abcdef \ GO111MODULE=on \ make release ``` The `git tag ...` step is necessary because the release target requires that the current SHA have a sem-ver tag. That's the version it will use when creating the release. This will create the release in GitHub with provisional release notes that just include a change log of commit messages. You still need to login to GitHub and revise those notes to adhere to the recommended format. (See `RELEASE_NOTES.md` in this directory.) ### Docker Hub Releases To re-run only the Docker Hub release steps, you can manually run through each step in the "Docker" section of `do_release.sh`. If the `docker push ...` steps fail, you may need to run `docker login`, enter your Docker Hub login credentials, and then try to push again. ### Homebrew Releases The last step is to update the Homebrew recipe to use the latest version. First, we need to compute the SHA256 checksum for the source archive: ```sh # download the source archive from GitHub URL=https://github.com/fullstorydev/grpcurl/archive/refs/tags/v2.3.4.tar.gz curl -L -o tmp.tgz $URL # and compute the SHA SHA="$(sha256sum < tmp.tgz | awk '{ print $1 }')" ``` To actually create the brew PR, you need your GitHub personal access token again, as well as the URL and SHA from the previous step: ```sh HOMEBREW_GITHUB_API_TOKEN=abcdef0123456789abcdef \ brew bump-formula-pr --url $URL --sha256 $SHA grpcurl ``` This creates a PR to bump the formula to the new version. When this PR is merged by brew maintainers, the new version becomes available! ================================================ FILE: releasing/RELEASE_NOTES.md ================================================ ## Changes ### Command-line tool * _In this list, describe the changes to the command-line tool._ * _Use one bullet per change. Include both bug-fixes and improvements. Omit this section if there are no changes that impact the command-line tool._ ### Go package "github.com/fullstorydev/grpcurl" * _In this list, describe the changes to exported API in the main package in this repo: "github.com/fullstorydev/grpcurl". These will often be closely related to changes to the command-line tool, though not always: changes that only impact the cmd/grpcurl directory of this repo do not impact exported API._ * _Use one bullet per change. Include both bug-fixes and improvements. Omit this section if there are no changes that impact the exported API._ ================================================ FILE: releasing/do-release.sh ================================================ #!/bin/bash # strict mode set -euo pipefail IFS=$'\n\t' if [[ -z ${DRY_RUN:-} ]]; then PREFIX="" else PREFIX="echo" fi # input validation if [[ -z ${GITHUB_TOKEN:-} ]]; then echo "GITHUB_TOKEN environment variable must be set before running." >&2 exit 1 fi if [[ $# -ne 1 || $1 == "" ]]; then echo "This program requires one argument: the version number, in 'vM.N.P' format." >&2 exit 1 fi VERSION=$1 # Change to root of the repo cd "$(dirname "$0")/.." # GitHub release $PREFIX git tag "$VERSION" # make sure GITHUB_TOKEN is exported, for the benefit of this next command export GITHUB_TOKEN GO111MODULE=on $PREFIX make release # if that was successful, it could have touched go.mod and go.sum, so revert those $PREFIX git checkout go.mod go.sum # Docker release # make sure credentials are valid for later push steps; this might # be interactive since this will prompt for username and password # if there are no valid current credentials. $PREFIX docker login echo "$VERSION" > VERSION # Docker Buildx support is included in Docker 19.03 # Below step installs emulators for different architectures on the host # This enables running and building containers for below architectures mentioned using --platforms $PREFIX docker run --privileged --rm tonistiigi/binfmt:qemu-v6.1.0 --install all # Create a new builder instance export DOCKER_CLI_EXPERIMENTAL=enabled $PREFIX docker buildx create --use --name multiarch-builder --node multiarch-builder0 # push to docker hub, both the given version as a tag and for "latest" tag $PREFIX docker buildx build --platform linux/amd64,linux/s390x,linux/arm64,linux/ppc64le --tag fullstorydev/grpcurl:${VERSION} --tag fullstorydev/grpcurl:latest --push --progress plain --no-cache . $PREFIX docker buildx build --platform linux/amd64,linux/s390x,linux/arm64,linux/ppc64le --tag fullstorydev/grpcurl:${VERSION}-alpine --tag fullstorydev/grpcurl:latest-alpine --push --progress plain --no-cache --target alpine . rm VERSION # Homebrew release URL="https://github.com/fullstorydev/grpcurl/archive/refs/tags/${VERSION}.tar.gz" curl -L -o tmp.tgz "$URL" SHA="$(sha256sum < tmp.tgz | awk '{ print $1 }')" rm tmp.tgz HOMEBREW_GITHUB_API_TOKEN="$GITHUB_TOKEN" $PREFIX brew bump-formula-pr --url "$URL" --sha256 "$SHA" grpcurl ================================================ FILE: snap/README.md ================================================ # packing and releasing To pack the current branch to a snap package: `snapcraft pack` To install the package locally: `snap install ./grpcurl_v[version tag]_amd64.snap --devmode` To upload the snap to the edge channel: `snapcraft upload --release edge ./grpcurl_v[version tag]_amd64.snap` (you need to own the package name registration for this!) # ownership The snap's current owner is `pietro.pasotti@canonical.com`; who is very happy to support with maintaining the snap distribution and/or transfer its ownership to the developers. Please reach out to me for questions regarding the snap; including: - adding support for other architectures - automating the release Cheers and thanks for the awesome tool! ================================================ FILE: snap/snapcraft.yaml ================================================ name: grpcurl base: core24 # allow grpcurl part to call craftctl set-version adopt-info: grpcurl summary: grpcurl is a command-line tool that lets you interact with gRPC servers. description: | grpcurl is a command-line tool that lets you interact with gRPC servers. It's basically curl for gRPC servers. grade: stable confinement: strict license: MIT platforms: amd64: build-on: - amd64 build-for: - amd64 arm64: build-on: - amd64 - arm64 build-for: - arm64 apps: grpcurl: command: grpcurl plugs: - network parts: grpcurl: plugin: go build-snaps: [go/latest/stable] source: https://github.com/fullstorydev/grpcurl source-type: git override-build: | tag="$(git describe --tags --abbrev=0)" craftctl set version="$tag" go build -o $CRAFT_PART_INSTALL/grpcurl ./cmd/grpcurl/grpcurl.go # adjust the permissions chmod 0755 $CRAFT_PART_INSTALL/grpcurl ================================================ FILE: tls_settings_test.go ================================================ package grpcurl_test import ( "context" "fmt" "net" "strings" "testing" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials" . "github.com/fullstorydev/grpcurl" grpcurl_testing "github.com/fullstorydev/grpcurl/internal/testing" ) func TestPlainText(t *testing.T) { e, err := createTestServerAndClient(nil, nil) if err != nil { t.Fatalf("failed to setup server and client: %v", err) } defer e.Close() simpleTest(t, e.cc) } func TestBasicTLS(t *testing.T) { serverCreds, err := ServerTransportCredentials("", "internal/testing/tls/server.crt", "internal/testing/tls/server.key", false) if err != nil { t.Fatalf("failed to create server creds: %v", err) } clientCreds, err := ClientTransportCredentials(false, "internal/testing/tls/ca.crt", "", "") if err != nil { t.Fatalf("failed to create server creds: %v", err) } e, err := createTestServerAndClient(serverCreds, clientCreds) if err != nil { t.Fatalf("failed to setup server and client: %v", err) } defer e.Close() simpleTest(t, e.cc) } func TestInsecureClientTLS(t *testing.T) { serverCreds, err := ServerTransportCredentials("", "internal/testing/tls/server.crt", "internal/testing/tls/server.key", false) if err != nil { t.Fatalf("failed to create server creds: %v", err) } clientCreds, err := ClientTransportCredentials(true, "", "", "") if err != nil { t.Fatalf("failed to create server creds: %v", err) } e, err := createTestServerAndClient(serverCreds, clientCreds) if err != nil { t.Fatalf("failed to setup server and client: %v", err) } defer e.Close() simpleTest(t, e.cc) } func TestClientCertTLS(t *testing.T) { serverCreds, err := ServerTransportCredentials("internal/testing/tls/ca.crt", "internal/testing/tls/server.crt", "internal/testing/tls/server.key", false) if err != nil { t.Fatalf("failed to create server creds: %v", err) } clientCreds, err := ClientTransportCredentials(false, "internal/testing/tls/ca.crt", "internal/testing/tls/client.crt", "internal/testing/tls/client.key") if err != nil { t.Fatalf("failed to create server creds: %v", err) } e, err := createTestServerAndClient(serverCreds, clientCreds) if err != nil { t.Fatalf("failed to setup server and client: %v", err) } defer e.Close() simpleTest(t, e.cc) } func TestRequireClientCertTLS(t *testing.T) { serverCreds, err := ServerTransportCredentials("internal/testing/tls/ca.crt", "internal/testing/tls/server.crt", "internal/testing/tls/server.key", true) if err != nil { t.Fatalf("failed to create server creds: %v", err) } clientCreds, err := ClientTransportCredentials(false, "internal/testing/tls/ca.crt", "internal/testing/tls/client.crt", "internal/testing/tls/client.key") if err != nil { t.Fatalf("failed to create server creds: %v", err) } e, err := createTestServerAndClient(serverCreds, clientCreds) if err != nil { t.Fatalf("failed to setup server and client: %v", err) } defer e.Close() simpleTest(t, e.cc) } func TestBrokenTLS_ClientPlainText(t *testing.T) { serverCreds, err := ServerTransportCredentials("", "internal/testing/tls/server.crt", "internal/testing/tls/server.key", false) if err != nil { t.Fatalf("failed to create server creds: %v", err) } // client connection (usually) succeeds since client is not waiting for TLS handshake // (we try several times, but if we never get a connection and the error message is // a known/expected possibility, we'll just bail) var e testEnv failCount := 0 for { e, err = createTestServerAndClient(serverCreds, nil) if err == nil { // success! defer e.Close() break } if strings.Contains(err.Error(), "deadline exceeded") || strings.Contains(err.Error(), "use of closed network connection") { // It is possible that the connection never becomes healthy: // 1) grpc connects successfully // 2) grpc client tries to send HTTP/2 preface and settings frame // 3) server, expecting handshake, closes the connection // 4) in the client, the write fails, so the connection never // becomes ready // The client will attempt to reconnect on transient errors, so // may eventually bump into the connect time limit. This used to // result in a "deadline exceeded" error, but more recent versions // of the grpc library report any underlying I/O error instead, so // we also check for "use of closed network connection". failCount++ if failCount > 5 { return // bail... } // we'll try again } else { // some other error occurred, so we'll consider that a test failure t.Fatalf("failed to setup server and client: %v", err) } } // but request fails because server closes connection upon seeing request // bytes that are not a TLS handshake cl := grpcurl_testing.NewTestServiceClient(e.cc) _, err = cl.UnaryCall(context.Background(), &grpcurl_testing.SimpleRequest{}) if err == nil { t.Fatal("expecting failure") } // various errors possible when server closes connection if !strings.Contains(err.Error(), "transport is closing") && !strings.Contains(err.Error(), "connection is unavailable") && !strings.Contains(err.Error(), "use of closed network connection") && !strings.Contains(err.Error(), "all SubConns are in TransientFailure") { t.Fatalf("expecting transport failure, got: %v", err) } } func TestBrokenTLS_ServerPlainText(t *testing.T) { clientCreds, err := ClientTransportCredentials(false, "internal/testing/tls/ca.crt", "", "") if err != nil { t.Fatalf("failed to create server creds: %v", err) } e, err := createTestServerAndClient(nil, clientCreds) if err == nil { e.Close() t.Fatal("expecting TLS failure setting up server and client") } if !strings.Contains(err.Error(), "first record does not look like a TLS handshake") { t.Fatalf("expecting TLS handshake failure, got: %v", err) } } func TestBrokenTLS_ServerUsesWrongCert(t *testing.T) { serverCreds, err := ServerTransportCredentials("", "internal/testing/tls/other.crt", "internal/testing/tls/other.key", false) if err != nil { t.Fatalf("failed to create server creds: %v", err) } clientCreds, err := ClientTransportCredentials(false, "internal/testing/tls/ca.crt", "", "") if err != nil { t.Fatalf("failed to create server creds: %v", err) } e, err := createTestServerAndClient(serverCreds, clientCreds) if err == nil { e.Close() t.Fatal("expecting TLS failure setting up server and client") } if !strings.Contains(err.Error(), "certificate is valid for") { t.Fatalf("expecting TLS certificate error, got: %v", err) } } func TestBrokenTLS_ClientHasExpiredCert(t *testing.T) { serverCreds, err := ServerTransportCredentials("internal/testing/tls/ca.crt", "internal/testing/tls/server.crt", "internal/testing/tls/server.key", false) if err != nil { t.Fatalf("failed to create server creds: %v", err) } clientCreds, err := ClientTransportCredentials(false, "internal/testing/tls/ca.crt", "internal/testing/tls/expired.crt", "internal/testing/tls/expired.key") if err != nil { t.Fatalf("failed to create server creds: %v", err) } e, err := createTestServerAndClient(serverCreds, clientCreds) if err == nil { e.Close() t.Fatal("expecting TLS failure setting up server and client") } if !strings.Contains(err.Error(), "certificate") { t.Fatalf("expecting TLS certificate error, got: %v", err) } } func TestBrokenTLS_ServerHasExpiredCert(t *testing.T) { serverCreds, err := ServerTransportCredentials("", "internal/testing/tls/expired.crt", "internal/testing/tls/expired.key", false) if err != nil { t.Fatalf("failed to create server creds: %v", err) } clientCreds, err := ClientTransportCredentials(false, "internal/testing/tls/ca.crt", "", "") if err != nil { t.Fatalf("failed to create server creds: %v", err) } e, err := createTestServerAndClient(serverCreds, clientCreds) if err == nil { e.Close() t.Fatal("expecting TLS failure setting up server and client") } if !strings.Contains(err.Error(), "certificate has expired or is not yet valid") { t.Fatalf("expecting TLS certificate expired, got: %v", err) } } func TestBrokenTLS_ClientNotTrusted(t *testing.T) { serverCreds, err := ServerTransportCredentials("internal/testing/tls/ca.crt", "internal/testing/tls/server.crt", "internal/testing/tls/server.key", true) if err != nil { t.Fatalf("failed to create server creds: %v", err) } clientCreds, err := ClientTransportCredentials(false, "internal/testing/tls/ca.crt", "internal/testing/tls/wrong-client.crt", "internal/testing/tls/wrong-client.key") if err != nil { t.Fatalf("failed to create server creds: %v", err) } e, err := createTestServerAndClient(serverCreds, clientCreds) if err == nil { e.Close() t.Fatal("expecting TLS failure setting up server and client") } // Check for either the old error (Go <=1.24) or the new one (Go 1.25+) // Go 1.24: "bad certificate" // Go 1.25: "handshake failure" errMsg := err.Error() if !strings.Contains(errMsg, "bad certificate") && !strings.Contains(errMsg, "handshake failure") { t.Fatalf("expecting a specific TLS certificate or handshake error, got: %v", err) } } func TestBrokenTLS_ServerNotTrusted(t *testing.T) { serverCreds, err := ServerTransportCredentials("", "internal/testing/tls/server.crt", "internal/testing/tls/server.key", false) if err != nil { t.Fatalf("failed to create server creds: %v", err) } clientCreds, err := ClientTransportCredentials(false, "", "internal/testing/tls/client.crt", "internal/testing/tls/client.key") if err != nil { t.Fatalf("failed to create server creds: %v", err) } e, err := createTestServerAndClient(serverCreds, clientCreds) if err == nil { e.Close() t.Fatal("expecting TLS failure setting up server and client") } if !strings.Contains(err.Error(), "certificate") { t.Fatalf("expecting TLS certificate error, got: %v", err) } } func TestBrokenTLS_RequireClientCertButNonePresented(t *testing.T) { serverCreds, err := ServerTransportCredentials("internal/testing/tls/ca.crt", "internal/testing/tls/server.crt", "internal/testing/tls/server.key", true) if err != nil { t.Fatalf("failed to create server creds: %v", err) } clientCreds, err := ClientTransportCredentials(false, "internal/testing/tls/ca.crt", "", "") if err != nil { t.Fatalf("failed to create server creds: %v", err) } e, err := createTestServerAndClient(serverCreds, clientCreds) if err == nil { e.Close() t.Fatal("expecting TLS failure setting up server and client") } // Check for either the old error (Go <=1.24) or the new one (Go 1.25+) // Go 1.24: "bad certificate" // Go 1.25: "handshake failure" errMsg := err.Error() if !strings.Contains(errMsg, "bad certificate") && !strings.Contains(errMsg, "handshake failure") { t.Fatalf("expecting a specific TLS certificate or handshake error, got: %v", err) } } func simpleTest(t *testing.T, cc *grpc.ClientConn) { cl := grpcurl_testing.NewTestServiceClient(cc) ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() _, err := cl.UnaryCall(ctx, &grpcurl_testing.SimpleRequest{}, grpc.WaitForReady(true)) if err != nil { t.Errorf("simple RPC failed: %v", err) } } func createTestServerAndClient(serverCreds, clientCreds credentials.TransportCredentials) (testEnv, error) { var e testEnv completed := false defer func() { if !completed { e.Close() } }() var svrOpts []grpc.ServerOption if serverCreds != nil { svrOpts = []grpc.ServerOption{grpc.Creds(serverCreds)} } svr := grpc.NewServer(svrOpts...) grpcurl_testing.RegisterTestServiceServer(svr, grpcurl_testing.TestServer{}) l, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { return e, err } port := l.Addr().(*net.TCPAddr).Port go svr.Serve(l) ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() cc, err := BlockingDial(ctx, "tcp", fmt.Sprintf("127.0.0.1:%d", port), clientCreds) if err != nil { return e, err } e.svr = svr e.cc = cc completed = true return e, nil } type testEnv struct { svr *grpc.Server cc *grpc.ClientConn } func (e *testEnv) Close() { if e.cc != nil { e.cc.Close() e.cc = nil } if e.svr != nil { e.svr.GracefulStop() e.svr = nil } }