pax_global_header00006660000000000000000000000064146205161660014521gustar00rootroot0000000000000052 comment=196dbef62845f9e02f2492a4d4b7e977d4347e33 ttrpc-1.2.4/000077500000000000000000000000001462051616600126615ustar00rootroot00000000000000ttrpc-1.2.4/.gitattributes000066400000000000000000000000211462051616600155450ustar00rootroot00000000000000*.go text eol=lf ttrpc-1.2.4/.github/000077500000000000000000000000001462051616600142215ustar00rootroot00000000000000ttrpc-1.2.4/.github/workflows/000077500000000000000000000000001462051616600162565ustar00rootroot00000000000000ttrpc-1.2.4/.github/workflows/ci.yml000066400000000000000000000063271462051616600174040ustar00rootroot00000000000000name: CI on: push: branches: [ main ] pull_request: branches: [ main ] env: GO_VERSION: 1.20.x permissions: contents: read pull-requests: read jobs: # # golangci-lint # linters: name: Linters runs-on: ${{ matrix.os }} timeout-minutes: 10 strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - uses: actions/checkout@v4 with: path: src/github.com/containerd/ttrpc - uses: actions/setup-go@v5 with: go-version: ${{ env.GO_VERSION }} - name: golangci-lint uses: golangci/golangci-lint-action@v4 with: version: v1.51.2 args: --timeout=5m skip-cache: true working-directory: src/github.com/containerd/ttrpc - name: golangci-lint errors run: golangci-lint run working-directory: src/github.com/containerd/ttrpc if: ${{ failure() }} # # Project checks # project: name: Project Checks runs-on: ubuntu-22.04 timeout-minutes: 5 steps: - uses: actions/checkout@v4 with: path: src/github.com/containerd/ttrpc fetch-depth: 25 - uses: actions/setup-go@v5 with: go-version: ${{ env.GO_VERSION }} - uses: containerd/project-checks@v1.1.0 with: working-directory: src/github.com/containerd/ttrpc # # Build and Test project # build: strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] go: [1.19.x, 1.20.x] name: ${{ matrix.os }} / ${{ matrix.go }} runs-on: ${{ matrix.os }} timeout-minutes: 10 steps: - name: Check out code uses: actions/checkout@v4 with: path: src/github.com/containerd/ttrpc fetch-depth: 25 - uses: actions/setup-go@v5 with: go-version: ${{ matrix.go }} - name: Test working-directory: src/github.com/containerd/ttrpc run: | make test - name: Coverage working-directory: src/github.com/containerd/ttrpc run: | make coverage TESTFLAGS_RACE=-race - name: Integration Tests working-directory: src/github.com/containerd/ttrpc run: | make integration # # Run Protobuild # protobuild: name: Run Protobuild runs-on: ubuntu-22.04 timeout-minutes: 5 steps: - name: Check out code uses: actions/checkout@v4 with: path: src/github.com/containerd/ttrpc fetch-depth: 25 - uses: actions/setup-go@v5 with: go-version: ${{ env.GO_VERSION }} id: go - name: Setup Go binary path shell: bash run: | echo "GOPATH=${{ github.workspace }}" >> $GITHUB_ENV echo "${{ github.workspace }}/bin" >> $GITHUB_PATH - name: Install dependencies working-directory: src/github.com/containerd/ttrpc run: | sudo make install-protobuf make install-protobuild - name: Install protoc-gen-go-ttrpc working-directory: src/github.com/containerd/ttrpc run: | make install - name: Run Protobuild working-directory: src/github.com/containerd/ttrpc run: | make check-protos ttrpc-1.2.4/.gitignore000066400000000000000000000003141462051616600146470ustar00rootroot00000000000000# Binaries for programs and plugins /bin/ *.exe *.dll *.so *.dylib # Test binary, build with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out coverage.txt ttrpc-1.2.4/.golangci.yml000066400000000000000000000020401462051616600152410ustar00rootroot00000000000000linters: enable: - staticcheck - unconvert - gofmt - goimports - revive - ineffassign - vet - unused - misspell disable: - errcheck linters-settings: revive: ignore-generated-headers: true rules: - name: blank-imports - name: context-as-argument - name: context-keys-type - name: dot-imports - name: error-return - name: error-strings - name: error-naming - name: exported - name: if-return - name: increment-decrement - name: var-naming arguments: [["UID", "GID"], []] - name: var-declaration - name: package-comments - name: range - name: receiver-naming - name: time-naming - name: unexported-return - name: indent-error-flow - name: errorf - name: empty-block - name: superfluous-else - name: unused-parameter - name: unreachable-code - name: redefines-builtin-id issues: include: - EXC0002 run: timeout: 8m skip-dirs: - example ttrpc-1.2.4/LICENSE000066400000000000000000000261351462051616600136750ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. ttrpc-1.2.4/Makefile000066400000000000000000000135601462051616600143260ustar00rootroot00000000000000# Copyright The containerd 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. # Go command to use for build GO ?= go INSTALL ?= install # Root directory of the project (absolute path). ROOTDIR=$(dir $(abspath $(lastword $(MAKEFILE_LIST)))) WHALE = "🇩" ONI = "👹" # Project binaries. COMMANDS=protoc-gen-go-ttrpc protoc-gen-gogottrpc ifdef BUILDTAGS GO_BUILDTAGS = ${BUILDTAGS} endif GO_BUILDTAGS ?= GO_TAGS=$(if $(GO_BUILDTAGS),-tags "$(strip $(GO_BUILDTAGS))",) # Project packages. PACKAGES=$(shell $(GO) list ${GO_TAGS} ./... | grep -v /example) TESTPACKAGES=$(shell $(GO) list ${GO_TAGS} ./... | grep -v /cmd | grep -v /integration | grep -v /example) BINPACKAGES=$(addprefix ./cmd/,$(COMMANDS)) #Replaces ":" (*nix), ";" (windows) with newline for easy parsing GOPATHS=$(shell echo ${GOPATH} | tr ":" "\n" | tr ";" "\n") TESTFLAGS_RACE= GO_BUILD_FLAGS= # See Golang issue re: '-trimpath': https://github.com/golang/go/issues/13809 GO_GCFLAGS=$(shell \ set -- ${GOPATHS}; \ echo "-gcflags=-trimpath=$${1}/src"; \ ) BINARIES=$(addprefix bin/,$(COMMANDS)) # Flags passed to `go test` TESTFLAGS ?= $(TESTFLAGS_RACE) $(EXTRA_TESTFLAGS) TESTFLAGS_PARALLEL ?= 8 # Use this to replace `go test` with, for instance, `gotestsum` GOTEST ?= $(GO) test .PHONY: clean all AUTHORS build binaries test integration generate protos check-protos coverage ci check help install vendor install-protobuf install-protobuild .DEFAULT: default # Forcibly set the default goal to all, in case an include above brought in a rule definition. .DEFAULT_GOAL := all all: binaries check: proto-fmt ## run all linters @echo "$(WHALE) $@" GOGC=75 golangci-lint run ci: check binaries check-protos coverage # coverage-integration ## to be used by the CI AUTHORS: .mailmap .git/HEAD git log --format='%aN <%aE>' | sort -fu > $@ generate: protos @echo "$(WHALE) $@" @PATH="${ROOTDIR}/bin:${PATH}" $(GO) generate -x ${PACKAGES} protos: bin/protoc-gen-gogottrpc bin/protoc-gen-go-ttrpc ## generate protobuf @echo "$(WHALE) $@" @(PATH="${ROOTDIR}/bin:${PATH}" protobuild --quiet ${PACKAGES}) check-protos: protos ## check if protobufs needs to be generated again @echo "$(WHALE) $@" @test -z "$$(git status --short | grep ".pb.go" | tee /dev/stderr)" || \ ((git diff | cat) && \ (echo "$(ONI) please run 'make protos' when making changes to proto files" && false)) check-api-descriptors: protos ## check that protobuf changes aren't present. @echo "$(WHALE) $@" @test -z "$$(git status --short | grep ".pb.txt" | tee /dev/stderr)" || \ ((git diff $$(find . -name '*.pb.txt') | cat) && \ (echo "$(ONI) please run 'make protos' when making changes to proto files and check-in the generated descriptor file changes" && false)) proto-fmt: ## check format of proto files @echo "$(WHALE) $@" @test -z "$$(find . -name '*.proto' -type f -exec grep -Hn -e "^ " {} \; | tee /dev/stderr)" || \ (echo "$(ONI) please indent proto files with tabs only" && false) @test -z "$$(find . -name '*.proto' -type f -exec grep -Hn "Meta meta = " {} \; | grep -v '(gogoproto.nullable) = false' | tee /dev/stderr)" || \ (echo "$(ONI) meta fields in proto files must have option (gogoproto.nullable) = false" && false) build: ## build the go packages @echo "$(WHALE) $@" @$(GO) build ${DEBUG_GO_GCFLAGS} ${GO_GCFLAGS} ${GO_BUILD_FLAGS} ${EXTRA_FLAGS} ${PACKAGES} test: ## run tests, except integration tests and tests that require root @echo "$(WHALE) $@" @$(GOTEST) ${TESTFLAGS} ${TESTPACKAGES} integration: ## run integration tests @echo "$(WHALE) $@" @cd "${ROOTDIR}/integration" && $(GOTEST) -v ${TESTFLAGS} -parallel ${TESTFLAGS_PARALLEL} . benchmark: ## run benchmarks tests @echo "$(WHALE) $@" @$(GO) test ${TESTFLAGS} -bench . -run Benchmark FORCE: define BUILD_BINARY @echo "$(WHALE) $@" @$(GO) build ${DEBUG_GO_GCFLAGS} ${GO_GCFLAGS} ${GO_BUILD_FLAGS} -o $@ ${GO_TAGS} ./$< endef # Build a binary from a cmd. bin/%: cmd/% FORCE $(call BUILD_BINARY) binaries: $(BINARIES) ## build binaries @echo "$(WHALE) $@" clean: ## clean up binaries @echo "$(WHALE) $@" @rm -f $(BINARIES) install: ## install binaries @echo "$(WHALE) $@ $(BINPACKAGES)" @$(GO) install $(BINPACKAGES) install-protobuf: @echo "$(WHALE) $@" @script/install-protobuf install-protobuild: @echo "$(WHALE) $@" @$(GO) install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28.1 @$(GO) install github.com/containerd/protobuild@14832ccc41429f5c4f81028e5af08aa233a219cf coverage: ## generate coverprofiles from the unit tests, except tests that require root @echo "$(WHALE) $@" @rm -f coverage.txt @$(GO) test ${TESTFLAGS} ${TESTPACKAGES} 2> /dev/null @( for pkg in ${PACKAGES}; do \ $(GO) test ${TESTFLAGS} \ -cover \ -coverprofile=profile.out \ -covermode=atomic $$pkg || exit; \ if [ -f profile.out ]; then \ cat profile.out >> coverage.txt; \ rm profile.out; \ fi; \ done ) vendor: ## ensure all the go.mod/go.sum files are up-to-date @echo "$(WHALE) $@" @$(GO) mod tidy @$(GO) mod verify verify-vendor: ## verify if all the go.mod/go.sum files are up-to-date @echo "$(WHALE) $@" @$(GO) mod tidy @$(GO) mod verify @test -z "$$(git status --short | grep "go.sum" | tee /dev/stderr)" || \ ((git diff | cat) && \ (echo "$(ONI) make sure to checkin changes after go mod tidy" && false)) help: ## this help @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) | sort ttrpc-1.2.4/PROTOCOL.md000066400000000000000000000307651462051616600144570ustar00rootroot00000000000000# Protocol Specification The ttrpc protocol is client/server protocol to support multiple request streams over a single connection with lightweight framing. The client represents the process which initiated the underlying connection and the server is the process which accepted the connection. The protocol is currently defined as asymmetrical, with clients sending requests and servers sending responses. Both clients and servers are able to send stream data. The roles are also used in determining the stream identifiers, with client initiated streams using odd number identifiers and server initiated using even number. The protocol may be extended in the future to support server initiated streams, that is not supported in the latest version. ## Purpose The ttrpc protocol is designed to be lightweight and optimized for low latency and reliable connections between processes on the same host. The protocol does not include features for handling unreliable connections such as handshakes, resets, pings, or flow control. The protocol is designed to make low-overhead implementations as simple as possible. It is not intended as a suitable replacement for HTTP2/3 over the network. ## Message Frame Each Message Frame consists of a 10-byte message header followed by message data. The data length and stream ID are both big-endian 4-byte unsigned integers. The message type is an unsigned 1-byte integer. The flags are also an unsigned 1-byte integer and use is defined by the message type. +---------------------------------------------------------------+ | Data Length (32) | +---------------------------------------------------------------+ | Stream ID (32) | +---------------+-----------------------------------------------+ | Msg Type (8) | +---------------+ | Flags (8) | +---------------+-----------------------------------------------+ | Data (*) | +---------------------------------------------------------------+ The Data Length field represents the number of bytes in the Data field. The total frame size will always be Data Length + 10 bytes. The maximum data length is 4MB and any larger size should be rejected. Due to the maximum data size being less than 16MB, the first frame byte should always be zero. This first byte should be considered reserved for future use. The Stream ID must be odd for client initiated streams and even for server initiated streams. Server initiated streams are not currently supported. ## Mesage Types | Message Type | Name | Description | |--------------|----------|----------------------------------| | 0x01 | Request | Initiates stream | | 0x02 | Response | Final stream data and terminates | | 0x03 | Data | Stream data | ### Request The request message is used to initiate stream and send along request data for properly routing and handling the stream. The stream may indicate unary without any inbound or outbound stream data with only a response is expected on the stream. The request may also indicate the stream is still open for more data and no response is expected until data is finished. If the remote indicates the stream is closed, the request may be considered non-unary but without anymore stream data sent. In the case of `remote closed`, the remote still expects to receive a response or stream data. For compatibility with non streaming clients, a request with empty flags indicates a unary request. #### Request Flags | Flag | Name | Description | |------|-----------------|--------------------------------------------------| | 0x01 | `remote closed` | Non-unary, but no more data expected from remote | | 0x02 | `remote open` | Non-unary, remote is still sending data | ### Response The response message is used to end a stream with data, an empty response, or an error. A response message is the only expected message after a unary request. A non-unary request does not require a response message if the server is sending back stream data. A non-unary stream may return a single response message but no other stream data may follow. #### Response Flags No response flags are defined at this time, flags should be empty. ### Data The data message is used to send data on an already initialized stream. Either client or server may send data. A data message is not allowed on a unary stream. A data message should not be sent after indicating `remote closed` to the peer. The last data message on a stream must set the `remote closed` flag. The `no data` flag is used to indicate that the data message does not include any data. This is normally used with the `remote closed` flag to indicate the stream is now closed without transmitting any data. Since ttrpc normally transmits a single object per message, a zero length data message may be interpreted as an empty object. For example, transmitting the number zero as a protobuf message ends up with a data length of zero, but the message is still considered data and should be processed. #### Data Flags | Flag | Name | Description | |------|-----------------|-----------------------------------| | 0x01 | `remote closed` | No more data expected from remote | | 0x04 | `no data` | This message does not have data | ## Streaming All ttrpc requests use streams to transfer data. Unary streams will only have two messages sent per stream, a request from a client and a response from the server. Non-unary streams, however, may send any numbers of messages from the client and the server. This makes stream management more complicated than unary streams since both client and server need to track additional state. To keep this management as simple as possible, ttrpc minimizes the number of states and uses two flags instead of control frames. Each stream has two states while a stream is still alive: `local closed` and `remote closed`. Each peer considers local and remote from their own perspective and sets flags from the other peer's perspective. For example, if a client sends a data frame with the `remote closed` flag, that is indicating that the client is now `local closed` and the server will be `remote closed`. A unary operation does not need to send these flags since each received message always indicates `remote closed`. Once a peer is both `local closed` and `remote closed`, the stream is considered finished and may be cleaned up. Due to the asymmetric nature of the current protocol, a client should always be in the `local closed` state before `remote closed` and a server should always be in the `remote closed` state before `local closed`. This happens because the client is always initiating requests and a client always expects a final response back from a server to indicate the initiated request has been fulfilled. This may mean server sends a final empty response to finish a stream even after it has already completed sending data before the client. ### Unary State Diagram +--------+ +--------+ | Client | | Server | +---+----+ +----+---+ | +---------+ | local >---------------+ Request +--------------------> remote closed | +---------+ | closed | | | +----------+ | finished <--------------+ Response +--------------------< finished | +----------+ | | | ### Non-Unary State Diagrams RC: `remote closed` flag RO: `remote open` flag +--------+ +--------+ | Client | | Server | +---+----+ +----+---+ | +--------------+ | >-------------+ Request [RO] +-----------------> | +--------------+ | | | | +------+ | >-----------------+ Data +---------------------> | +------+ | | | | +-----------+ | local >---------------+ Data [RC] +------------------> remote closed | +-----------+ | closed | | | +----------+ | finished <--------------+ Response +--------------------< finished | +----------+ | | | +--------+ +--------+ | Client | | Server | +---+----+ +----+---+ | +--------------+ | local >-------------+ Request [RC] +-----------------> remote closed | +--------------+ | closed | | | +------+ | <-----------------+ Data +---------------------< | +------+ | | | | +-----------+ | finished <---------------+ Data [RC] +------------------< finished | +-----------+ | | | +--------+ +--------+ | Client | | Server | +---+----+ +----+---+ | +--------------+ | >-------------+ Request [RO] +-----------------> | +--------------+ | | | | +------+ | >-----------------+ Data +---------------------> | +------+ | | | | +------+ | <-----------------+ Data +---------------------< | +------+ | | | | +------+ | >-----------------+ Data +---------------------> | +------+ | | | | +-----------+ | local >---------------+ Data [RC] +------------------> remote closed | +-----------+ | closed | | | +------+ | <-----------------+ Data +---------------------< | +------+ | | | | +-----------+ | finished <---------------+ Data [RC] +------------------< finished | +-----------+ | | | ## RPC While this protocol is defined primarily to support Remote Procedure Calls, the protocol does not define the request and response types beyond the messages defined in the protocol. The implementation provides a default protobuf definition of request and response which may be used for cross language rpc. All implementations should at least define a request type which support routing by procedure name and a response type which supports call status. ## Version History | Version | Features | |---------|---------------------| | 1.0 | Unary requests only | | 1.2 | Streaming support | ttrpc-1.2.4/Protobuild.toml000066400000000000000000000020631462051616600157020ustar00rootroot00000000000000version = "2" generators = ["go"] # Control protoc include paths. Below are usually some good defaults, but feel # free to try it without them if it works for your project. [includes] # Include paths that will be added before all others. Typically, you want to # treat the root of the project as an include, but this may not be necessary. before = ["."] # Paths that will be added untouched to the end of the includes. We use # `/usr/local/include` to pickup the common install location of protobuf. # This is the default. after = ["/usr/local/include"] # This section maps protobuf imports to Go packages. These will become # `-M` directives in the call to the go protobuf generator. [packages] "google/protobuf/any.proto" = "github.com/gogo/protobuf/types" "proto/status.proto" = "google.golang.org/genproto/googleapis/rpc/status" [[overrides]] # enable ttrpc and disable fieldpath and grpc for the shim prefixes = ["github.com/containerd/ttrpc/integration/streaming"] generators = ["go", "go-ttrpc"] [overrides.parameters.go-ttrpc] prefix = "TTRPC" ttrpc-1.2.4/README.md000066400000000000000000000044631462051616600141470ustar00rootroot00000000000000# ttrpc [![Build Status](https://github.com/containerd/ttrpc/workflows/CI/badge.svg)](https://github.com/containerd/ttrpc/actions?query=workflow%3ACI) GRPC for low-memory environments. The existing grpc-go project requires a lot of memory overhead for importing packages and at runtime. While this is great for many services with low density requirements, this can be a problem when running a large number of services on a single machine or on a machine with a small amount of memory. Using the same GRPC definitions, this project reduces the binary size and protocol overhead required. We do this by eliding the `net/http`, `net/http2` and `grpc` package used by grpc replacing it with a lightweight framing protocol. The result are smaller binaries that use less resident memory with the same ease of use as GRPC. Please note that while this project supports generating either end of the protocol, the generated service definitions will be incompatible with regular GRPC services, as they do not speak the same protocol. # Protocol See the [protocol specification](./PROTOCOL.md). # Usage Create a gogo vanity binary (see [`cmd/protoc-gen-gogottrpc/main.go`](cmd/protoc-gen-gogottrpc/main.go) for an example with the ttrpc plugin enabled. It's recommended to use [`protobuild`](https://github.com/containerd/protobuild) to build the protobufs for this project, but this will work with protoc directly, if required. # Differences from GRPC - The protocol stack has been replaced with a lighter protocol that doesn't require http, http2 and tls. - The client and server interface are identical whereas in GRPC there is a client and server interface that are different. - The Go stdlib context package is used instead. # Status TODO: - [ ] Add testing under concurrent load to ensure - [ ] Verify connection error handling # Project details ttrpc is a containerd sub-project, licensed under the [Apache 2.0 license](./LICENSE). As a containerd sub-project, you will find the: * [Project governance](https://github.com/containerd/project/blob/main/GOVERNANCE.md), * [Maintainers](https://github.com/containerd/project/blob/main/MAINTAINERS), * and [Contributing guidelines](https://github.com/containerd/project/blob/main/CONTRIBUTING.md) information in our [`containerd/project`](https://github.com/containerd/project) repository. ttrpc-1.2.4/channel.go000066400000000000000000000112361462051616600146230ustar00rootroot00000000000000/* Copyright The containerd 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. */ package ttrpc import ( "bufio" "encoding/binary" "fmt" "io" "net" "sync" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) const ( messageHeaderLength = 10 messageLengthMax = 4 << 20 ) type messageType uint8 const ( messageTypeRequest messageType = 0x1 messageTypeResponse messageType = 0x2 messageTypeData messageType = 0x3 ) func (mt messageType) String() string { switch mt { case messageTypeRequest: return "request" case messageTypeResponse: return "response" case messageTypeData: return "data" default: return "unknown" } } const ( flagRemoteClosed uint8 = 0x1 flagRemoteOpen uint8 = 0x2 flagNoData uint8 = 0x4 ) // messageHeader represents the fixed-length message header of 10 bytes sent // with every request. type messageHeader struct { Length uint32 // length excluding this header. b[:4] StreamID uint32 // identifies which request stream message is a part of. b[4:8] Type messageType // message type b[8] Flags uint8 // type specific flags b[9] } func readMessageHeader(p []byte, r io.Reader) (messageHeader, error) { _, err := io.ReadFull(r, p[:messageHeaderLength]) if err != nil { return messageHeader{}, err } return messageHeader{ Length: binary.BigEndian.Uint32(p[:4]), StreamID: binary.BigEndian.Uint32(p[4:8]), Type: messageType(p[8]), Flags: p[9], }, nil } func writeMessageHeader(w io.Writer, p []byte, mh messageHeader) error { binary.BigEndian.PutUint32(p[:4], mh.Length) binary.BigEndian.PutUint32(p[4:8], mh.StreamID) p[8] = byte(mh.Type) p[9] = mh.Flags _, err := w.Write(p[:]) return err } var buffers sync.Pool type channel struct { conn net.Conn bw *bufio.Writer br *bufio.Reader hrbuf [messageHeaderLength]byte // avoid alloc when reading header hwbuf [messageHeaderLength]byte } func newChannel(conn net.Conn) *channel { return &channel{ conn: conn, bw: bufio.NewWriter(conn), br: bufio.NewReader(conn), } } // recv a message from the channel. The returned buffer contains the message. // // If a valid grpc status is returned, the message header // returned will be valid and caller should send that along to // the correct consumer. The bytes on the underlying channel // will be discarded. func (ch *channel) recv() (messageHeader, []byte, error) { mh, err := readMessageHeader(ch.hrbuf[:], ch.br) if err != nil { return messageHeader{}, nil, err } if mh.Length > uint32(messageLengthMax) { if _, err := ch.br.Discard(int(mh.Length)); err != nil { return mh, nil, fmt.Errorf("failed to discard after receiving oversized message: %w", err) } return mh, nil, status.Errorf(codes.ResourceExhausted, "message length %v exceed maximum message size of %v", mh.Length, messageLengthMax) } var p []byte if mh.Length > 0 { p = ch.getmbuf(int(mh.Length)) if _, err := io.ReadFull(ch.br, p); err != nil { return messageHeader{}, nil, fmt.Errorf("failed reading message: %w", err) } } return mh, p, nil } func (ch *channel) send(streamID uint32, t messageType, flags uint8, p []byte) error { // TODO: Error on send rather than on recv //if len(p) > messageLengthMax { // return status.Errorf(codes.InvalidArgument, "refusing to send, message length %v exceed maximum message size of %v", len(p), messageLengthMax) //} if err := writeMessageHeader(ch.bw, ch.hwbuf[:], messageHeader{Length: uint32(len(p)), StreamID: streamID, Type: t, Flags: flags}); err != nil { return err } if len(p) > 0 { _, err := ch.bw.Write(p) if err != nil { return err } } return ch.bw.Flush() } func (ch *channel) getmbuf(size int) []byte { // we can't use the standard New method on pool because we want to allocate // based on size. b, ok := buffers.Get().(*[]byte) if !ok || cap(*b) < size { // TODO(stevvooe): It may be better to allocate these in fixed length // buckets to reduce fragmentation but its not clear that would help // with performance. An ilogb approach or similar would work well. bb := make([]byte, size) b = &bb } else { *b = (*b)[:size] } return *b } func (ch *channel) putmbuf(p []byte) { buffers.Put(&p) } ttrpc-1.2.4/channel_test.go000066400000000000000000000046071462051616600156660ustar00rootroot00000000000000/* Copyright The containerd 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. */ package ttrpc import ( "bytes" "errors" "io" "net" "reflect" "testing" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) func TestReadWriteMessage(t *testing.T) { var ( w, r = net.Pipe() ch = newChannel(w) rch = newChannel(r) messages = [][]byte{ []byte("hello"), []byte("this is a test"), []byte("of message framing"), } received [][]byte errs = make(chan error, 1) ) go func() { for i, msg := range messages { if err := ch.send(uint32(i), 1, 0, msg); err != nil { errs <- err return } } w.Close() }() for { _, p, err := rch.recv() if err != nil { if !errors.Is(err, io.EOF) { t.Fatal(err) } break } received = append(received, p) // make sure we don't have send errors select { case err := <-errs: if err != nil { t.Fatal(err) } default: } } if !reflect.DeepEqual(received, messages) { t.Fatalf("didn't received expected set of messages: %v != %v", received, messages) } select { case err := <-errs: if err != nil { t.Fatal(err) } default: } } func TestMessageOversize(t *testing.T) { var ( w, r = net.Pipe() wch, rch = newChannel(w), newChannel(r) msg = bytes.Repeat([]byte("a message of massive length"), 512<<10) errs = make(chan error, 1) ) go func() { if err := wch.send(1, 1, 0, msg); err != nil { errs <- err } }() _, _, err := rch.recv() if err == nil { t.Fatalf("error expected reading with small buffer") } status, ok := status.FromError(err) if !ok { t.Fatalf("expected grpc status error: %v", err) } if status.Code() != codes.ResourceExhausted { t.Fatalf("expected grpc status code: %v != %v", status.Code(), codes.ResourceExhausted) } select { case err := <-errs: if err != nil { t.Fatal(err) } default: } } ttrpc-1.2.4/client.go000066400000000000000000000315031462051616600144700ustar00rootroot00000000000000/* Copyright The containerd 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. */ package ttrpc import ( "context" "errors" "fmt" "io" "net" "strings" "sync" "syscall" "time" "github.com/sirupsen/logrus" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" ) // Client for a ttrpc server type Client struct { codec codec conn net.Conn channel *channel streamLock sync.RWMutex streams map[streamID]*stream nextStreamID streamID sendLock sync.Mutex ctx context.Context closed func() closeOnce sync.Once userCloseFunc func() userCloseWaitCh chan struct{} interceptor UnaryClientInterceptor } // ClientOpts configures a client type ClientOpts func(c *Client) // WithOnClose sets the close func whenever the client's Close() method is called func WithOnClose(onClose func()) ClientOpts { return func(c *Client) { c.userCloseFunc = onClose } } // WithUnaryClientInterceptor sets the provided client interceptor func WithUnaryClientInterceptor(i UnaryClientInterceptor) ClientOpts { return func(c *Client) { c.interceptor = i } } // WithChainUnaryClientInterceptor sets the provided chain of client interceptors func WithChainUnaryClientInterceptor(interceptors ...UnaryClientInterceptor) ClientOpts { return func(c *Client) { if len(interceptors) == 0 { return } if c.interceptor != nil { interceptors = append([]UnaryClientInterceptor{c.interceptor}, interceptors...) } c.interceptor = func( ctx context.Context, req *Request, reply *Response, info *UnaryClientInfo, final Invoker, ) error { return interceptors[0](ctx, req, reply, info, chainUnaryInterceptors(interceptors[1:], final, info)) } } } func chainUnaryInterceptors(interceptors []UnaryClientInterceptor, final Invoker, info *UnaryClientInfo) Invoker { if len(interceptors) == 0 { return final } return func( ctx context.Context, req *Request, reply *Response, ) error { return interceptors[0](ctx, req, reply, info, chainUnaryInterceptors(interceptors[1:], final, info)) } } // NewClient creates a new ttrpc client using the given connection func NewClient(conn net.Conn, opts ...ClientOpts) *Client { ctx, cancel := context.WithCancel(context.Background()) channel := newChannel(conn) c := &Client{ codec: codec{}, conn: conn, channel: channel, streams: make(map[streamID]*stream), nextStreamID: 1, closed: cancel, ctx: ctx, userCloseFunc: func() {}, userCloseWaitCh: make(chan struct{}), } for _, o := range opts { o(c) } if c.interceptor == nil { c.interceptor = defaultClientInterceptor } go c.run() return c } func (c *Client) send(sid uint32, mt messageType, flags uint8, b []byte) error { c.sendLock.Lock() defer c.sendLock.Unlock() return c.channel.send(sid, mt, flags, b) } // Call makes a unary request and returns with response func (c *Client) Call(ctx context.Context, service, method string, req, resp interface{}) error { payload, err := c.codec.Marshal(req) if err != nil { return err } var ( creq = &Request{ Service: service, Method: method, Payload: payload, // TODO: metadata from context } cresp = &Response{} ) if metadata, ok := GetMetadata(ctx); ok { metadata.setRequest(creq) } if dl, ok := ctx.Deadline(); ok { creq.TimeoutNano = time.Until(dl).Nanoseconds() } info := &UnaryClientInfo{ FullMethod: fullPath(service, method), } if err := c.interceptor(ctx, creq, cresp, info, c.dispatch); err != nil { return err } if err := c.codec.Unmarshal(cresp.Payload, resp); err != nil { return err } if cresp.Status != nil && cresp.Status.Code != int32(codes.OK) { return status.ErrorProto(cresp.Status) } return nil } // StreamDesc describes the stream properties, whether the stream has // a streaming client, a streaming server, or both type StreamDesc struct { StreamingClient bool StreamingServer bool } // ClientStream is used to send or recv messages on the underlying stream type ClientStream interface { CloseSend() error SendMsg(m interface{}) error RecvMsg(m interface{}) error } type clientStream struct { ctx context.Context s *stream c *Client desc *StreamDesc localClosed bool remoteClosed bool } func (cs *clientStream) CloseSend() error { if !cs.desc.StreamingClient { return fmt.Errorf("%w: cannot close non-streaming client", ErrProtocol) } if cs.localClosed { return ErrStreamClosed } err := cs.s.send(messageTypeData, flagRemoteClosed|flagNoData, nil) if err != nil { return filterCloseErr(err) } cs.localClosed = true return nil } func (cs *clientStream) SendMsg(m interface{}) error { if !cs.desc.StreamingClient { return fmt.Errorf("%w: cannot send data from non-streaming client", ErrProtocol) } if cs.localClosed { return ErrStreamClosed } var ( payload []byte err error ) if m != nil { payload, err = cs.c.codec.Marshal(m) if err != nil { return err } } err = cs.s.send(messageTypeData, 0, payload) if err != nil { return filterCloseErr(err) } return nil } func (cs *clientStream) RecvMsg(m interface{}) error { if cs.remoteClosed { return io.EOF } var msg *streamMessage select { case <-cs.ctx.Done(): return cs.ctx.Err() case <-cs.s.recvClose: // If recv has a pending message, process that first select { case msg = <-cs.s.recv: default: return cs.s.recvErr } case msg = <-cs.s.recv: } if msg.header.Type == messageTypeResponse { resp := &Response{} err := proto.Unmarshal(msg.payload[:msg.header.Length], resp) // return the payload buffer for reuse cs.c.channel.putmbuf(msg.payload) if err != nil { return err } if err := cs.c.codec.Unmarshal(resp.Payload, m); err != nil { return err } if resp.Status != nil && resp.Status.Code != int32(codes.OK) { return status.ErrorProto(resp.Status) } cs.c.deleteStream(cs.s) cs.remoteClosed = true return nil } else if msg.header.Type == messageTypeData { if !cs.desc.StreamingServer { cs.c.deleteStream(cs.s) cs.remoteClosed = true return fmt.Errorf("received data from non-streaming server: %w", ErrProtocol) } if msg.header.Flags&flagRemoteClosed == flagRemoteClosed { cs.c.deleteStream(cs.s) cs.remoteClosed = true if msg.header.Flags&flagNoData == flagNoData { return io.EOF } } err := cs.c.codec.Unmarshal(msg.payload[:msg.header.Length], m) cs.c.channel.putmbuf(msg.payload) if err != nil { return err } return nil } return fmt.Errorf("unexpected %q message received: %w", msg.header.Type, ErrProtocol) } // Close closes the ttrpc connection and underlying connection func (c *Client) Close() error { c.closeOnce.Do(func() { c.closed() c.conn.Close() }) return nil } // UserOnCloseWait is used to block until the user's on-close callback // finishes. func (c *Client) UserOnCloseWait(ctx context.Context) error { select { case <-c.userCloseWaitCh: return nil case <-ctx.Done(): return ctx.Err() } } func (c *Client) run() { err := c.receiveLoop() c.Close() c.cleanupStreams(err) c.userCloseFunc() close(c.userCloseWaitCh) } func (c *Client) receiveLoop() error { for { select { case <-c.ctx.Done(): return ErrClosed default: var ( msg = &streamMessage{} err error ) msg.header, msg.payload, err = c.channel.recv() if err != nil { _, ok := status.FromError(err) if !ok { // treat all errors that are not an rpc status as terminal. // all others poison the connection. return filterCloseErr(err) } } sid := streamID(msg.header.StreamID) s := c.getStream(sid) if s == nil { logrus.WithField("stream", sid).Errorf("ttrpc: received message on inactive stream") continue } if err != nil { s.closeWithError(err) } else { if err := s.receive(c.ctx, msg); err != nil { logrus.WithError(err).WithField("stream", sid).Errorf("ttrpc: failed to handle message") } } } } } // createStream creates a new stream and registers it with the client // Introduce stream types for multiple or single response func (c *Client) createStream(flags uint8, b []byte) (*stream, error) { // sendLock must be held across both allocation of the stream ID and sending it across the wire. // This ensures that new stream IDs sent on the wire are always increasing, which is a // requirement of the TTRPC protocol. // This use of sendLock could be split into another mutex that covers stream creation + first send, // and just use sendLock to guard writing to the wire, but for now it seems simpler to have fewer mutexes. c.sendLock.Lock() defer c.sendLock.Unlock() // Check if closed since lock acquired to prevent adding // anything after cleanup completes select { case <-c.ctx.Done(): return nil, ErrClosed default: } var s *stream if err := func() error { // In the future this could be replaced with a sync.Map instead of streamLock+map. c.streamLock.Lock() defer c.streamLock.Unlock() // Check if closed since lock acquired to prevent adding // anything after cleanup completes select { case <-c.ctx.Done(): return ErrClosed default: } s = newStream(c.nextStreamID, c) c.streams[s.id] = s c.nextStreamID = c.nextStreamID + 2 return nil }(); err != nil { return nil, err } if err := c.channel.send(uint32(s.id), messageTypeRequest, flags, b); err != nil { return s, filterCloseErr(err) } return s, nil } func (c *Client) deleteStream(s *stream) { c.streamLock.Lock() delete(c.streams, s.id) c.streamLock.Unlock() s.closeWithError(nil) } func (c *Client) getStream(sid streamID) *stream { c.streamLock.RLock() s := c.streams[sid] c.streamLock.RUnlock() return s } func (c *Client) cleanupStreams(err error) { c.streamLock.Lock() defer c.streamLock.Unlock() for sid, s := range c.streams { s.closeWithError(err) delete(c.streams, sid) } } // filterCloseErr rewrites EOF and EPIPE errors to ErrClosed. Use when // returning from call or handling errors from main read loop. // // This purposely ignores errors with a wrapped cause. func filterCloseErr(err error) error { switch { case err == nil: return nil case err == io.EOF: return ErrClosed case errors.Is(err, io.ErrClosedPipe): return ErrClosed case errors.Is(err, io.EOF): return ErrClosed case strings.Contains(err.Error(), "use of closed network connection"): return ErrClosed default: // if we have an epipe on a write or econnreset on a read , we cast to errclosed var oerr *net.OpError if errors.As(err, &oerr) { if (oerr.Op == "write" && errors.Is(err, syscall.EPIPE)) || (oerr.Op == "read" && errors.Is(err, syscall.ECONNRESET)) { return ErrClosed } } } return err } // NewStream creates a new stream with the given stream descriptor to the // specified service and method. If not a streaming client, the request object // may be provided. func (c *Client) NewStream(ctx context.Context, desc *StreamDesc, service, method string, req interface{}) (ClientStream, error) { var payload []byte if req != nil { var err error payload, err = c.codec.Marshal(req) if err != nil { return nil, err } } request := &Request{ Service: service, Method: method, Payload: payload, // TODO: metadata from context } p, err := c.codec.Marshal(request) if err != nil { return nil, err } var flags uint8 if desc.StreamingClient { flags = flagRemoteOpen } else { flags = flagRemoteClosed } s, err := c.createStream(flags, p) if err != nil { return nil, err } return &clientStream{ ctx: ctx, s: s, c: c, desc: desc, }, nil } func (c *Client) dispatch(ctx context.Context, req *Request, resp *Response) error { p, err := c.codec.Marshal(req) if err != nil { return err } s, err := c.createStream(0, p) if err != nil { return err } defer c.deleteStream(s) var msg *streamMessage select { case <-ctx.Done(): return ctx.Err() case <-c.ctx.Done(): return ErrClosed case <-s.recvClose: // If recv has a pending message, process that first select { case msg = <-s.recv: default: return s.recvErr } case msg = <-s.recv: } if msg.header.Type == messageTypeResponse { err = proto.Unmarshal(msg.payload[:msg.header.Length], resp) } else { err = fmt.Errorf("unexpected %q message received: %w", msg.header.Type, ErrProtocol) } // return the payload buffer for reuse c.channel.putmbuf(msg.payload) return err } ttrpc-1.2.4/client_test.go000066400000000000000000000034041462051616600155260ustar00rootroot00000000000000/* Copyright The containerd 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. */ package ttrpc import ( "context" "testing" "time" "github.com/containerd/ttrpc/internal" ) func TestUserOnCloseWait(t *testing.T) { var ( ctx, cancel = context.WithDeadline(context.Background(), time.Now().Add(1*time.Minute)) server = mustServer(t)(NewServer()) testImpl = &testingServer{} addr, listener = newTestListener(t) ) defer cancel() defer listener.Close() registerTestingService(server, testImpl) go server.Serve(ctx, listener) defer server.Shutdown(ctx) var ( dataCh = make(chan string) client, cleanup = newTestClient(t, addr, WithOnClose(func() { dataCh <- time.Now().String() }), ) tp internal.TestPayload tclient = newTestingClient(client) ) if _, err := tclient.Test(ctx, &tp); err != nil { t.Fatal(err) } cleanup() fctx, fcancel := context.WithDeadline(ctx, time.Now().Add(1*time.Second)) defer fcancel() if err := client.UserOnCloseWait(fctx); err == nil || err != context.DeadlineExceeded { t.Fatalf("expected error %v, but got %v", context.DeadlineExceeded, err) } <-dataCh if err := client.UserOnCloseWait(ctx); err != nil { t.Fatalf("expected error nil , but got %v", err) } } ttrpc-1.2.4/cmd/000077500000000000000000000000001462051616600134245ustar00rootroot00000000000000ttrpc-1.2.4/cmd/protoc-gen-go-ttrpc/000077500000000000000000000000001462051616600172365ustar00rootroot00000000000000ttrpc-1.2.4/cmd/protoc-gen-go-ttrpc/generator.go000066400000000000000000000267461462051616600215720ustar00rootroot00000000000000/* Copyright The containerd 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. */ package main import ( "fmt" "strings" "google.golang.org/protobuf/compiler/protogen" ) // generator is a Go code generator that uses ttrpc.Server and ttrpc.Client. // Unlike the original gogo version, this doesn't generate serializers for message types and // let protoc-gen-go handle them. type generator struct { out *protogen.GeneratedFile ident struct { context string server string client string method string stream string serviceDesc string streamDesc string streamServerIdent protogen.GoIdent streamClientIdent protogen.GoIdent streamServer string streamClient string } } func newGenerator(out *protogen.GeneratedFile) *generator { gen := generator{out: out} gen.ident.context = out.QualifiedGoIdent(protogen.GoIdent{ GoImportPath: "context", GoName: "Context", }) gen.ident.server = out.QualifiedGoIdent(protogen.GoIdent{ GoImportPath: "github.com/containerd/ttrpc", GoName: "Server", }) gen.ident.client = out.QualifiedGoIdent(protogen.GoIdent{ GoImportPath: "github.com/containerd/ttrpc", GoName: "Client", }) gen.ident.method = out.QualifiedGoIdent(protogen.GoIdent{ GoImportPath: "github.com/containerd/ttrpc", GoName: "Method", }) gen.ident.stream = out.QualifiedGoIdent(protogen.GoIdent{ GoImportPath: "github.com/containerd/ttrpc", GoName: "Stream", }) gen.ident.serviceDesc = out.QualifiedGoIdent(protogen.GoIdent{ GoImportPath: "github.com/containerd/ttrpc", GoName: "ServiceDesc", }) gen.ident.streamDesc = out.QualifiedGoIdent(protogen.GoIdent{ GoImportPath: "github.com/containerd/ttrpc", GoName: "StreamDesc", }) gen.ident.streamServerIdent = protogen.GoIdent{ GoImportPath: "github.com/containerd/ttrpc", GoName: "StreamServer", } gen.ident.streamClientIdent = protogen.GoIdent{ GoImportPath: "github.com/containerd/ttrpc", GoName: "ClientStream", } gen.ident.streamServer = out.QualifiedGoIdent(gen.ident.streamServerIdent) gen.ident.streamClient = out.QualifiedGoIdent(gen.ident.streamClientIdent) return &gen } func generate(plugin *protogen.Plugin, input *protogen.File, servicePrefix string) error { if len(input.Services) == 0 { // Only generate a Go file if the file has some services. return nil } file := plugin.NewGeneratedFile(input.GeneratedFilenamePrefix+"_ttrpc.pb.go", input.GoImportPath) file.P("// Code generated by protoc-gen-go-ttrpc. DO NOT EDIT.") file.P("// source: ", input.Desc.Path()) file.P("package ", input.GoPackageName) gen := newGenerator(file) for _, service := range input.Services { service.GoName = servicePrefix + service.GoName gen.genService(service) } return nil } func (gen *generator) genService(service *protogen.Service) { fullName := service.Desc.FullName() p := gen.out var methods []*protogen.Method var streams []*protogen.Method serviceName := service.GoName + "Service" p.P("type ", serviceName, " interface{") for _, method := range service.Methods { var sendArgs, retArgs string if method.Desc.IsStreamingClient() || method.Desc.IsStreamingServer() { streams = append(streams, method) sendArgs = fmt.Sprintf("%s_%sServer", service.GoName, method.GoName) if !method.Desc.IsStreamingClient() { sendArgs = fmt.Sprintf("*%s, %s", p.QualifiedGoIdent(method.Input.GoIdent), sendArgs) } if method.Desc.IsStreamingServer() { retArgs = "error" } else { retArgs = fmt.Sprintf("(*%s, error)", p.QualifiedGoIdent(method.Output.GoIdent)) } } else { methods = append(methods, method) sendArgs = fmt.Sprintf("*%s", p.QualifiedGoIdent(method.Input.GoIdent)) retArgs = fmt.Sprintf("(*%s, error)", p.QualifiedGoIdent(method.Output.GoIdent)) } p.P(method.GoName, "(", gen.ident.context, ", ", sendArgs, ") ", retArgs) } p.P("}") p.P() for _, method := range streams { structName := strings.ToLower(service.GoName) + method.GoName + "Server" p.P("type ", service.GoName, "_", method.GoName, "Server interface {") if method.Desc.IsStreamingServer() { p.P("Send(*", method.Output.GoIdent, ") error") } if method.Desc.IsStreamingClient() { p.P("Recv() (*", method.Input.GoIdent, ", error)") } p.P(gen.ident.streamServer) p.P("}") p.P() p.P("type ", structName, " struct {") p.P(gen.ident.streamServer) p.P("}") p.P() if method.Desc.IsStreamingServer() { p.P("func (x *", structName, ") Send(m *", method.Output.GoIdent, ") error {") p.P("return x.StreamServer.SendMsg(m)") p.P("}") p.P() } if method.Desc.IsStreamingClient() { p.P("func (x *", structName, ") Recv() (*", method.Input.GoIdent, ", error) {") p.P("m := new(", method.Input.GoIdent, ")") p.P("if err := x.StreamServer.RecvMsg(m); err != nil {") p.P("return nil, err") p.P("}") p.P("return m, nil") p.P("}") p.P() } } // registration method p.P("func Register", serviceName, "(srv *", gen.ident.server, ", svc ", serviceName, "){") p.P(`srv.RegisterService("`, fullName, `", &`, gen.ident.serviceDesc, "{") if len(methods) > 0 { p.P(`Methods: map[string]`, gen.ident.method, "{") for _, method := range methods { p.P(`"`, method.GoName, `": func(ctx `, gen.ident.context, ", unmarshal func(interface{}) error)(interface{}, error){") p.P("var req ", method.Input.GoIdent) p.P("if err := unmarshal(&req); err != nil {") p.P("return nil, err") p.P("}") p.P("return svc.", method.GoName, "(ctx, &req)") p.P("},") } p.P("},") } if len(streams) > 0 { p.P(`Streams: map[string]`, gen.ident.stream, "{") for _, method := range streams { p.P(`"`, method.GoName, `": {`) p.P(`Handler: func(ctx `, gen.ident.context, ", stream ", gen.ident.streamServer, ") (interface{}, error) {") structName := strings.ToLower(service.GoName) + method.GoName + "Server" var sendArg string if !method.Desc.IsStreamingClient() { sendArg = "m, " p.P("m := new(", method.Input.GoIdent, ")") p.P("if err := stream.RecvMsg(m); err != nil {") p.P("return nil, err") p.P("}") } if method.Desc.IsStreamingServer() { p.P("return nil, svc.", method.GoName, "(ctx, ", sendArg, "&", structName, "{stream})") } else { p.P("return svc.", method.GoName, "(ctx, ", sendArg, "&", structName, "{stream})") } p.P("},") if method.Desc.IsStreamingClient() { p.P("StreamingClient: true,") } else { p.P("StreamingClient: false,") } if method.Desc.IsStreamingServer() { p.P("StreamingServer: true,") } else { p.P("StreamingServer: false,") } p.P("},") } p.P("},") } p.P("})") p.P("}") p.P() clientType := service.GoName + "Client" // For consistency with ttrpc 1.0 without streaming, just use // the service name if no streams are defined clientInterface := serviceName if len(streams) > 0 { clientInterface = clientType // Stream client interfaces are different than the server interface p.P("type ", clientInterface, " interface{") for _, method := range service.Methods { if method.Desc.IsStreamingClient() || method.Desc.IsStreamingServer() { streams = append(streams, method) var sendArg string if !method.Desc.IsStreamingClient() { sendArg = fmt.Sprintf("*%s, ", p.QualifiedGoIdent(method.Input.GoIdent)) } p.P(method.GoName, "(", gen.ident.context, ", ", sendArg, ") (", service.GoName, "_", method.GoName, "Client, error)") } else { methods = append(methods, method) p.P(method.GoName, "(", gen.ident.context, ", ", "*", method.Input.GoIdent, ")", "(*", method.Output.GoIdent, ", error)") } } p.P("}") p.P() } clientStructType := strings.ToLower(service.GoName) + "Client" p.P("type ", clientStructType, " struct{") p.P("client *", gen.ident.client) p.P("}") p.P("func New", clientType, "(client *", gen.ident.client, ")", clientInterface, "{") p.P("return &", clientStructType, "{") p.P("client:client,") p.P("}") p.P("}") p.P() for _, method := range service.Methods { var sendArg string if !method.Desc.IsStreamingClient() { sendArg = ", req *" + gen.out.QualifiedGoIdent(method.Input.GoIdent) } intName := service.GoName + "_" + method.GoName + "Client" var retArg string if method.Desc.IsStreamingClient() || method.Desc.IsStreamingServer() { retArg = intName } else { retArg = "*" + gen.out.QualifiedGoIdent(method.Output.GoIdent) } p.P("func (c *", clientStructType, ") ", method.GoName, "(ctx ", gen.ident.context, "", sendArg, ") ", "(", retArg, ", error) {") if method.Desc.IsStreamingClient() || method.Desc.IsStreamingServer() { var streamingClient, streamingServer, req string if method.Desc.IsStreamingClient() { streamingClient = "true" req = "nil" } else { streamingClient = "false" req = "req" } if method.Desc.IsStreamingServer() { streamingServer = "true" } else { streamingServer = "false" } p.P("stream, err := c.client.NewStream(ctx, &", gen.ident.streamDesc, "{") p.P("StreamingClient: ", streamingClient, ",") p.P("StreamingServer: ", streamingServer, ",") p.P("}, ", `"`+fullName+`", `, `"`+method.GoName+`", `, req, `)`) p.P("if err != nil {") p.P("return nil, err") p.P("}") structName := strings.ToLower(service.GoName) + method.GoName + "Client" p.P("x := &", structName, "{stream}") p.P("return x, nil") p.P("}") p.P() // Create interface p.P("type ", intName, " interface {") if method.Desc.IsStreamingClient() { p.P("Send(*", method.Input.GoIdent, ") error") } if method.Desc.IsStreamingServer() { p.P("Recv() (*", method.Output.GoIdent, ", error)") } else { p.P("CloseAndRecv() (*", method.Output.GoIdent, ", error)") } p.P(gen.ident.streamClient) p.P("}") p.P() // Create struct p.P("type ", structName, " struct {") p.P(gen.ident.streamClient) p.P("}") p.P() if method.Desc.IsStreamingClient() { p.P("func (x *", structName, ") Send(m *", method.Input.GoIdent, ") error {") p.P("return x.", gen.ident.streamClientIdent.GoName, ".SendMsg(m)") p.P("}") p.P() } if method.Desc.IsStreamingServer() { p.P("func (x *", structName, ") Recv() (*", method.Output.GoIdent, ", error) {") p.P("m := new(", method.Output.GoIdent, ")") p.P("if err := x.ClientStream.RecvMsg(m); err != nil {") p.P("return nil, err") p.P("}") p.P("return m, nil") p.P("}") p.P() } else { p.P("func (x *", structName, ") CloseAndRecv() (*", method.Output.GoIdent, ", error) {") p.P("if err := x.ClientStream.CloseSend(); err != nil {") p.P("return nil, err") p.P("}") p.P("m := new(", method.Output.GoIdent, ")") p.P("if err := x.ClientStream.RecvMsg(m); err != nil {") p.P("return nil, err") p.P("}") p.P("return m, nil") p.P("}") p.P() } } else { p.P("var resp ", method.Output.GoIdent) p.P(`if err := c.client.Call(ctx, "`, fullName, `", "`, method.Desc.Name(), `", req, &resp); err != nil {`) p.P("return nil, err") p.P("}") p.P("return &resp, nil") p.P("}") p.P() } } } ttrpc-1.2.4/cmd/protoc-gen-go-ttrpc/main.go000066400000000000000000000022561462051616600205160ustar00rootroot00000000000000/* Copyright The containerd 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. */ package main import ( "google.golang.org/protobuf/compiler/protogen" "google.golang.org/protobuf/types/pluginpb" ) func main() { var servicePrefix string protogen.Options{ ParamFunc: func(name, value string) error { if name == "prefix" { servicePrefix = value } return nil }, }.Run(func(gen *protogen.Plugin) error { gen.SupportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL) for _, f := range gen.Files { if !f.Generate { continue } if err := generate(gen, f, servicePrefix); err != nil { return err } } return nil }) } ttrpc-1.2.4/cmd/protoc-gen-gogottrpc/000077500000000000000000000000001462051616600175075ustar00rootroot00000000000000ttrpc-1.2.4/cmd/protoc-gen-gogottrpc/main.go000066400000000000000000000023651462051616600207700ustar00rootroot00000000000000/* Copyright The containerd 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. */ package main import ( _ "github.com/containerd/ttrpc/plugin" "github.com/gogo/protobuf/protoc-gen-gogo/descriptor" "github.com/gogo/protobuf/vanity" "github.com/gogo/protobuf/vanity/command" ) func main() { req := command.Read() files := req.GetProtoFile() files = vanity.FilterFiles(files, vanity.NotGoogleProtobufDescriptorProto) for _, opt := range []func(*descriptor.FileDescriptorProto){ vanity.TurnOffGoGettersAll, vanity.TurnOffGoStringerAll, vanity.TurnOnMarshalerAll, vanity.TurnOnStringerAll, vanity.TurnOnUnmarshalerAll, vanity.TurnOnSizerAll, } { vanity.ForEachFile(files, opt) } resp := command.Generate(req) command.Write(resp) } ttrpc-1.2.4/codec.go000066400000000000000000000021561462051616600142710ustar00rootroot00000000000000/* Copyright The containerd 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. */ package ttrpc import ( "fmt" "google.golang.org/protobuf/proto" ) type codec struct{} func (c codec) Marshal(msg interface{}) ([]byte, error) { switch v := msg.(type) { case proto.Message: return proto.Marshal(v) default: return nil, fmt.Errorf("ttrpc: cannot marshal unknown type: %T", msg) } } func (c codec) Unmarshal(p []byte, msg interface{}) error { switch v := msg.(type) { case proto.Message: return proto.Unmarshal(p, v) default: return fmt.Errorf("ttrpc: cannot unmarshal into unknown type: %T", msg) } } ttrpc-1.2.4/config.go000066400000000000000000000050361462051616600144610ustar00rootroot00000000000000/* Copyright The containerd 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. */ package ttrpc import ( "context" "errors" ) type serverConfig struct { handshaker Handshaker interceptor UnaryServerInterceptor } // ServerOpt for configuring a ttrpc server type ServerOpt func(*serverConfig) error // WithServerHandshaker can be passed to NewServer to ensure that the // handshaker is called before every connection attempt. // // Only one handshaker is allowed per server. func WithServerHandshaker(handshaker Handshaker) ServerOpt { return func(c *serverConfig) error { if c.handshaker != nil { return errors.New("only one handshaker allowed per server") } c.handshaker = handshaker return nil } } // WithUnaryServerInterceptor sets the provided interceptor on the server func WithUnaryServerInterceptor(i UnaryServerInterceptor) ServerOpt { return func(c *serverConfig) error { if c.interceptor != nil { return errors.New("only one unchained interceptor allowed per server") } c.interceptor = i return nil } } // WithChainUnaryServerInterceptor sets the provided chain of server interceptors func WithChainUnaryServerInterceptor(interceptors ...UnaryServerInterceptor) ServerOpt { return func(c *serverConfig) error { if len(interceptors) == 0 { return nil } if c.interceptor != nil { interceptors = append([]UnaryServerInterceptor{c.interceptor}, interceptors...) } c.interceptor = func( ctx context.Context, unmarshal Unmarshaler, info *UnaryServerInfo, method Method) (interface{}, error) { return interceptors[0](ctx, unmarshal, info, chainUnaryServerInterceptors(info, method, interceptors[1:])) } return nil } } func chainUnaryServerInterceptors(info *UnaryServerInfo, method Method, interceptors []UnaryServerInterceptor) Method { if len(interceptors) == 0 { return method } return func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { return interceptors[0](ctx, unmarshal, info, chainUnaryServerInterceptors(info, method, interceptors[1:])) } } ttrpc-1.2.4/doc.go000066400000000000000000000015541462051616600137620ustar00rootroot00000000000000/* Copyright The containerd 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. */ /* package ttrpc defines and implements a low level simple transfer protocol optimized for low latency and reliable connections between processes on the same host. The protocol uses simple framing for sending requests, responses, and data using multiple streams. */ package ttrpc ttrpc-1.2.4/errors.go000066400000000000000000000021461462051616600145270ustar00rootroot00000000000000/* Copyright The containerd 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. */ package ttrpc import "errors" var ( // ErrProtocol is a general error in the handling the protocol. ErrProtocol = errors.New("protocol error") // ErrClosed is returned by client methods when the underlying connection is // closed. ErrClosed = errors.New("ttrpc: closed") // ErrServerClosed is returned when the Server has closed its connection. ErrServerClosed = errors.New("ttrpc: server closed") // ErrStreamClosed is when the streaming connection is closed. ErrStreamClosed = errors.New("ttrpc: stream closed") ) ttrpc-1.2.4/example/000077500000000000000000000000001462051616600143145ustar00rootroot00000000000000ttrpc-1.2.4/example/Protobuild.toml000066400000000000000000000014651462051616600173420ustar00rootroot00000000000000version = "2" generators = ["go", "go-ttrpc"] # Control protoc include paths. Below are usually some good defaults, but feel # free to try it without them if it works for your project. [includes] # Include paths that will be added before all others. Typically, you want to # treat the root of the project as an include, but this may not be necessary. # before = ["./protobuf"] # Paths that will be added untouched to the end of the includes. We use # `/usr/local/include` to pickup the common install location of protobuf. # This is the default. after = ["/usr/local/include"] # This section maps protobuf imports to Go packages. These will become # `-M` directives in the call to the go protobuf generator. [packages] "google/rpc/status.proto" = "github.com/containerd/containerd/protobuf/google/rpc" ttrpc-1.2.4/example/cmd/000077500000000000000000000000001462051616600150575ustar00rootroot00000000000000ttrpc-1.2.4/example/cmd/handshaker_linux.go000066400000000000000000000013461462051616600207410ustar00rootroot00000000000000/* Copyright The containerd 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. */ package main import ttrpc "github.com/containerd/ttrpc" func defaultHandshaker() ttrpc.Handshaker { return ttrpc.UnixSocketRequireSameUser() } ttrpc-1.2.4/example/cmd/handshaker_other.go000066400000000000000000000013541462051616600207220ustar00rootroot00000000000000//go:build !linux // +build !linux /* Copyright The containerd 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. */ package main import ttrpc "github.com/containerd/ttrpc" func defaultHandshaker() ttrpc.Handshaker { return nil } ttrpc-1.2.4/example/cmd/main.go000066400000000000000000000057671462051616600163510ustar00rootroot00000000000000/* Copyright The containerd 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. */ package main import ( context "context" "encoding/json" "errors" "log" "net" "os" ttrpc "github.com/containerd/ttrpc" "github.com/containerd/ttrpc/example" "google.golang.org/protobuf/types/known/emptypb" ) const socket = "example-ttrpc-server" func main() { if err := handle(); err != nil { log.Fatal(err) } } func handle() error { command := os.Args[1] switch command { case "server": return server() case "client": return client() default: return errors.New("invalid command") } } func serverIntercept(ctx context.Context, um ttrpc.Unmarshaler, i *ttrpc.UnaryServerInfo, m ttrpc.Method) (interface{}, error) { log.Println("server interceptor") dumpMetadata(ctx) return m(ctx, um) } func clientIntercept(ctx context.Context, req *ttrpc.Request, resp *ttrpc.Response, i *ttrpc.UnaryClientInfo, invoker ttrpc.Invoker) error { log.Println("client interceptor") dumpMetadata(ctx) return invoker(ctx, req, resp) } func dumpMetadata(ctx context.Context) { md, ok := ttrpc.GetMetadata(ctx) if !ok { panic("no metadata") } if err := json.NewEncoder(os.Stdout).Encode(md); err != nil { panic(err) } } func server() error { s, err := ttrpc.NewServer( ttrpc.WithServerHandshaker(defaultHandshaker()), ttrpc.WithUnaryServerInterceptor(serverIntercept), ) if err != nil { return err } defer s.Close() example.RegisterExampleService(s, &exampleServer{}) l, err := net.Listen("unix", socket) if err != nil { return err } defer func() { l.Close() os.Remove(socket) }() return s.Serve(context.Background(), l) } func client() error { conn, err := net.Dial("unix", socket) if err != nil { return err } defer conn.Close() tc := ttrpc.NewClient(conn, ttrpc.WithUnaryClientInterceptor(clientIntercept)) client := example.NewExampleClient(tc) r := &example.Method1Request{ Foo: os.Args[2], Bar: os.Args[3], } ctx := context.Background() md := ttrpc.MD{} md.Set("name", "koye") ctx = ttrpc.WithMetadata(ctx, md) resp, err := client.Method1(ctx, r) if err != nil { return err } return json.NewEncoder(os.Stdout).Encode(resp) } type exampleServer struct { } func (s *exampleServer) Method1(ctx context.Context, r *example.Method1Request) (*example.Method1Response, error) { return &example.Method1Response{ Foo: r.Foo, Bar: r.Bar, }, nil } func (s *exampleServer) Method2(ctx context.Context, r *example.Method1Request) (*emptypb.Empty, error) { return &emptypb.Empty{}, nil } ttrpc-1.2.4/example/doc.go000066400000000000000000000012441462051616600154110ustar00rootroot00000000000000/* Copyright The containerd 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. */ // Package example demonstrates a lightweight protobuf service. package example ttrpc-1.2.4/example/example.pb.go000066400000000000000000000263541462051616600167100ustar00rootroot00000000000000// Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.27.1 // protoc v3.11.4 // source: github.com/containerd/ttrpc/example/example.proto package example import ( empty "github.com/golang/protobuf/ptypes/empty" 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) ) type Method1Request struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Foo string `protobuf:"bytes,1,opt,name=foo,proto3" json:"foo,omitempty"` Bar string `protobuf:"bytes,2,opt,name=bar,proto3" json:"bar,omitempty"` } func (x *Method1Request) Reset() { *x = Method1Request{} if protoimpl.UnsafeEnabled { mi := &file_github_com_containerd_ttrpc_example_example_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Method1Request) String() string { return protoimpl.X.MessageStringOf(x) } func (*Method1Request) ProtoMessage() {} func (x *Method1Request) ProtoReflect() protoreflect.Message { mi := &file_github_com_containerd_ttrpc_example_example_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 Method1Request.ProtoReflect.Descriptor instead. func (*Method1Request) Descriptor() ([]byte, []int) { return file_github_com_containerd_ttrpc_example_example_proto_rawDescGZIP(), []int{0} } func (x *Method1Request) GetFoo() string { if x != nil { return x.Foo } return "" } func (x *Method1Request) GetBar() string { if x != nil { return x.Bar } return "" } type Method1Response struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Foo string `protobuf:"bytes,1,opt,name=foo,proto3" json:"foo,omitempty"` Bar string `protobuf:"bytes,2,opt,name=bar,proto3" json:"bar,omitempty"` } func (x *Method1Response) Reset() { *x = Method1Response{} if protoimpl.UnsafeEnabled { mi := &file_github_com_containerd_ttrpc_example_example_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Method1Response) String() string { return protoimpl.X.MessageStringOf(x) } func (*Method1Response) ProtoMessage() {} func (x *Method1Response) ProtoReflect() protoreflect.Message { mi := &file_github_com_containerd_ttrpc_example_example_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 Method1Response.ProtoReflect.Descriptor instead. func (*Method1Response) Descriptor() ([]byte, []int) { return file_github_com_containerd_ttrpc_example_example_proto_rawDescGZIP(), []int{1} } func (x *Method1Response) GetFoo() string { if x != nil { return x.Foo } return "" } func (x *Method1Response) GetBar() string { if x != nil { return x.Bar } return "" } type Method2Request struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Action string `protobuf:"bytes,1,opt,name=action,proto3" json:"action,omitempty"` } func (x *Method2Request) Reset() { *x = Method2Request{} if protoimpl.UnsafeEnabled { mi := &file_github_com_containerd_ttrpc_example_example_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Method2Request) String() string { return protoimpl.X.MessageStringOf(x) } func (*Method2Request) ProtoMessage() {} func (x *Method2Request) ProtoReflect() protoreflect.Message { mi := &file_github_com_containerd_ttrpc_example_example_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 Method2Request.ProtoReflect.Descriptor instead. func (*Method2Request) Descriptor() ([]byte, []int) { return file_github_com_containerd_ttrpc_example_example_proto_rawDescGZIP(), []int{2} } func (x *Method2Request) GetAction() string { if x != nil { return x.Action } return "" } var File_github_com_containerd_ttrpc_example_example_proto protoreflect.FileDescriptor var file_github_com_containerd_ttrpc_example_example_proto_rawDesc = []byte{ 0x0a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x76, 0x31, 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, 0x22, 0x34, 0x0a, 0x0e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x31, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x6f, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x66, 0x6f, 0x6f, 0x12, 0x10, 0x0a, 0x03, 0x62, 0x61, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x62, 0x61, 0x72, 0x22, 0x35, 0x0a, 0x0f, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x31, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x6f, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x66, 0x6f, 0x6f, 0x12, 0x10, 0x0a, 0x03, 0x62, 0x61, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x62, 0x61, 0x72, 0x22, 0x28, 0x0a, 0x0e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x32, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x32, 0x9e, 0x01, 0x0a, 0x07, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x12, 0x4e, 0x0a, 0x07, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x31, 0x12, 0x20, 0x2e, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x31, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x31, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x07, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x32, 0x12, 0x20, 0x2e, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x31, 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, 0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x3b, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_github_com_containerd_ttrpc_example_example_proto_rawDescOnce sync.Once file_github_com_containerd_ttrpc_example_example_proto_rawDescData = file_github_com_containerd_ttrpc_example_example_proto_rawDesc ) func file_github_com_containerd_ttrpc_example_example_proto_rawDescGZIP() []byte { file_github_com_containerd_ttrpc_example_example_proto_rawDescOnce.Do(func() { file_github_com_containerd_ttrpc_example_example_proto_rawDescData = protoimpl.X.CompressGZIP(file_github_com_containerd_ttrpc_example_example_proto_rawDescData) }) return file_github_com_containerd_ttrpc_example_example_proto_rawDescData } var file_github_com_containerd_ttrpc_example_example_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_github_com_containerd_ttrpc_example_example_proto_goTypes = []interface{}{ (*Method1Request)(nil), // 0: ttrpc.example.v1.Method1Request (*Method1Response)(nil), // 1: ttrpc.example.v1.Method1Response (*Method2Request)(nil), // 2: ttrpc.example.v1.Method2Request (*empty.Empty)(nil), // 3: google.protobuf.Empty } var file_github_com_containerd_ttrpc_example_example_proto_depIdxs = []int32{ 0, // 0: ttrpc.example.v1.Example.Method1:input_type -> ttrpc.example.v1.Method1Request 0, // 1: ttrpc.example.v1.Example.Method2:input_type -> ttrpc.example.v1.Method1Request 1, // 2: ttrpc.example.v1.Example.Method1:output_type -> ttrpc.example.v1.Method1Response 3, // 3: ttrpc.example.v1.Example.Method2:output_type -> google.protobuf.Empty 2, // [2:4] is the sub-list for method output_type 0, // [0:2] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_github_com_containerd_ttrpc_example_example_proto_init() } func file_github_com_containerd_ttrpc_example_example_proto_init() { if File_github_com_containerd_ttrpc_example_example_proto != nil { return } if !protoimpl.UnsafeEnabled { file_github_com_containerd_ttrpc_example_example_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Method1Request); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_github_com_containerd_ttrpc_example_example_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Method1Response); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_github_com_containerd_ttrpc_example_example_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Method2Request); 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_github_com_containerd_ttrpc_example_example_proto_rawDesc, NumEnums: 0, NumMessages: 3, NumExtensions: 0, NumServices: 1, }, GoTypes: file_github_com_containerd_ttrpc_example_example_proto_goTypes, DependencyIndexes: file_github_com_containerd_ttrpc_example_example_proto_depIdxs, MessageInfos: file_github_com_containerd_ttrpc_example_example_proto_msgTypes, }.Build() File_github_com_containerd_ttrpc_example_example_proto = out.File file_github_com_containerd_ttrpc_example_example_proto_rawDesc = nil file_github_com_containerd_ttrpc_example_example_proto_goTypes = nil file_github_com_containerd_ttrpc_example_example_proto_depIdxs = nil } ttrpc-1.2.4/example/example.proto000066400000000000000000000007211462051616600170340ustar00rootroot00000000000000syntax = "proto3"; package ttrpc.example.v1; import "google/protobuf/empty.proto"; option go_package = "github.com/containerd/ttrpc/example;example"; service Example { rpc Method1(Method1Request) returns (Method1Response); rpc Method2(Method1Request) returns (google.protobuf.Empty); } message Method1Request { string foo = 1; string bar = 2; } message Method1Response { string foo = 1; string bar = 2; } message Method2Request { string action = 1; } ttrpc-1.2.4/example/example_ttrpc.pb.go000066400000000000000000000032671462051616600201220ustar00rootroot00000000000000// Code generated by protoc-gen-go-ttrpc. DO NOT EDIT. // source: github.com/containerd/ttrpc/example/example.proto package example import ( context "context" ttrpc "github.com/containerd/ttrpc" empty "github.com/golang/protobuf/ptypes/empty" ) type ExampleService interface { Method1(ctx context.Context, req *Method1Request) (*Method1Response, error) Method2(ctx context.Context, req *Method1Request) (*empty.Empty, error) } func RegisterExampleService(srv *ttrpc.Server, svc ExampleService) { srv.Register("ttrpc.example.v1.Example", map[string]ttrpc.Method{ "Method1": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { var req Method1Request if err := unmarshal(&req); err != nil { return nil, err } return svc.Method1(ctx, &req) }, "Method2": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { var req Method1Request if err := unmarshal(&req); err != nil { return nil, err } return svc.Method2(ctx, &req) }, }) } type exampleClient struct { client *ttrpc.Client } func NewExampleClient(client *ttrpc.Client) ExampleService { return &exampleClient{ client: client, } } func (c *exampleClient) Method1(ctx context.Context, req *Method1Request) (*Method1Response, error) { var resp Method1Response if err := c.client.Call(ctx, "ttrpc.example.v1.Example", "Method1", req, &resp); err != nil { return nil, err } return &resp, nil } func (c *exampleClient) Method2(ctx context.Context, req *Method1Request) (*empty.Empty, error) { var resp empty.Empty if err := c.client.Call(ctx, "ttrpc.example.v1.Example", "Method2", req, &resp); err != nil { return nil, err } return &resp, nil } ttrpc-1.2.4/go.mod000066400000000000000000000006371462051616600137750ustar00rootroot00000000000000module github.com/containerd/ttrpc go 1.19 require ( github.com/gogo/protobuf v1.3.2 github.com/golang/protobuf v1.5.3 github.com/prometheus/procfs v0.6.0 github.com/sirupsen/logrus v1.8.1 golang.org/x/sys v0.18.0 google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d google.golang.org/grpc v1.57.1 google.golang.org/protobuf v1.33.0 ) require golang.org/x/net v0.23.0 // indirect ttrpc-1.2.4/go.sum000066400000000000000000000135401462051616600140170ustar00rootroot00000000000000github.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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 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/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY= google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= google.golang.org/grpc v1.57.1 h1:upNTNqv0ES+2ZOOqACwVtS3Il8M12/+Hz41RCPzAjQg= google.golang.org/grpc v1.57.1/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= ttrpc-1.2.4/handshake.go000066400000000000000000000033121462051616600151350ustar00rootroot00000000000000/* Copyright The containerd 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. */ package ttrpc import ( "context" "net" ) // Handshaker defines the interface for connection handshakes performed on the // server or client when first connecting. type Handshaker interface { // Handshake should confirm or decorate a connection that may be incoming // to a server or outgoing from a client. // // If this returns without an error, the caller should use the connection // in place of the original connection. // // The second return value can contain credential specific data, such as // unix socket credentials or TLS information. // // While we currently only have implementations on the server-side, this // interface should be sufficient to implement similar handshakes on the // client-side. Handshake(ctx context.Context, conn net.Conn) (net.Conn, interface{}, error) } type handshakerFunc func(ctx context.Context, conn net.Conn) (net.Conn, interface{}, error) func (fn handshakerFunc) Handshake(ctx context.Context, conn net.Conn) (net.Conn, interface{}, error) { return fn(ctx, conn) } func noopHandshake(_ context.Context, conn net.Conn) (net.Conn, interface{}, error) { return conn, nil, nil } ttrpc-1.2.4/integration/000077500000000000000000000000001462051616600152045ustar00rootroot00000000000000ttrpc-1.2.4/integration/streaming/000077500000000000000000000000001462051616600171755ustar00rootroot00000000000000ttrpc-1.2.4/integration/streaming/doc.go000066400000000000000000000011461462051616600202730ustar00rootroot00000000000000/* Copyright The containerd 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. */ package streaming ttrpc-1.2.4/integration/streaming/test.pb.go000066400000000000000000000376741462051616600211240ustar00rootroot00000000000000// //Copyright The containerd 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. // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.28.1 // protoc v3.20.1 // source: github.com/containerd/ttrpc/integration/streaming/test.proto package streaming import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" emptypb "google.golang.org/protobuf/types/known/emptypb" 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) ) type EchoPayload struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Seq uint32 `protobuf:"varint,1,opt,name=seq,proto3" json:"seq,omitempty"` Msg string `protobuf:"bytes,2,opt,name=msg,proto3" json:"msg,omitempty"` } func (x *EchoPayload) Reset() { *x = EchoPayload{} if protoimpl.UnsafeEnabled { mi := &file_github_com_containerd_ttrpc_integration_streaming_test_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *EchoPayload) String() string { return protoimpl.X.MessageStringOf(x) } func (*EchoPayload) ProtoMessage() {} func (x *EchoPayload) ProtoReflect() protoreflect.Message { mi := &file_github_com_containerd_ttrpc_integration_streaming_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 EchoPayload.ProtoReflect.Descriptor instead. func (*EchoPayload) Descriptor() ([]byte, []int) { return file_github_com_containerd_ttrpc_integration_streaming_test_proto_rawDescGZIP(), []int{0} } func (x *EchoPayload) GetSeq() uint32 { if x != nil { return x.Seq } return 0 } func (x *EchoPayload) GetMsg() string { if x != nil { return x.Msg } return "" } type Part struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Add int32 `protobuf:"varint,1,opt,name=add,proto3" json:"add,omitempty"` } func (x *Part) Reset() { *x = Part{} if protoimpl.UnsafeEnabled { mi := &file_github_com_containerd_ttrpc_integration_streaming_test_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Part) String() string { return protoimpl.X.MessageStringOf(x) } func (*Part) ProtoMessage() {} func (x *Part) ProtoReflect() protoreflect.Message { mi := &file_github_com_containerd_ttrpc_integration_streaming_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 Part.ProtoReflect.Descriptor instead. func (*Part) Descriptor() ([]byte, []int) { return file_github_com_containerd_ttrpc_integration_streaming_test_proto_rawDescGZIP(), []int{1} } func (x *Part) GetAdd() int32 { if x != nil { return x.Add } return 0 } type Sum struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Sum int32 `protobuf:"varint,1,opt,name=sum,proto3" json:"sum,omitempty"` Num int32 `protobuf:"varint,2,opt,name=num,proto3" json:"num,omitempty"` } func (x *Sum) Reset() { *x = Sum{} if protoimpl.UnsafeEnabled { mi := &file_github_com_containerd_ttrpc_integration_streaming_test_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Sum) String() string { return protoimpl.X.MessageStringOf(x) } func (*Sum) ProtoMessage() {} func (x *Sum) ProtoReflect() protoreflect.Message { mi := &file_github_com_containerd_ttrpc_integration_streaming_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 Sum.ProtoReflect.Descriptor instead. func (*Sum) Descriptor() ([]byte, []int) { return file_github_com_containerd_ttrpc_integration_streaming_test_proto_rawDescGZIP(), []int{2} } func (x *Sum) GetSum() int32 { if x != nil { return x.Sum } return 0 } func (x *Sum) GetNum() int32 { if x != nil { return x.Num } return 0 } var File_github_com_containerd_ttrpc_integration_streaming_test_proto protoreflect.FileDescriptor var file_github_com_containerd_ttrpc_integration_streaming_test_proto_rawDesc = []byte{ 0x0a, 0x3c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1b, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 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, 0x22, 0x31, 0x0a, 0x0b, 0x45, 0x63, 0x68, 0x6f, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x65, 0x71, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x73, 0x65, 0x71, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x22, 0x18, 0x0a, 0x04, 0x50, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x64, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x61, 0x64, 0x64, 0x22, 0x29, 0x0a, 0x03, 0x53, 0x75, 0x6d, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x75, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x73, 0x75, 0x6d, 0x12, 0x10, 0x0a, 0x03, 0x6e, 0x75, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x6e, 0x75, 0x6d, 0x32, 0xfa, 0x04, 0x0a, 0x09, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x12, 0x5a, 0x0a, 0x04, 0x45, 0x63, 0x68, 0x6f, 0x12, 0x28, 0x2e, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x45, 0x63, 0x68, 0x6f, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x1a, 0x28, 0x2e, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x45, 0x63, 0x68, 0x6f, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x64, 0x0a, 0x0a, 0x45, 0x63, 0x68, 0x6f, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x28, 0x2e, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x45, 0x63, 0x68, 0x6f, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x1a, 0x28, 0x2e, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x45, 0x63, 0x68, 0x6f, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x28, 0x01, 0x30, 0x01, 0x12, 0x52, 0x0a, 0x09, 0x53, 0x75, 0x6d, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x21, 0x2e, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x61, 0x72, 0x74, 0x1a, 0x20, 0x2e, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x53, 0x75, 0x6d, 0x28, 0x01, 0x12, 0x55, 0x0a, 0x0c, 0x44, 0x69, 0x76, 0x69, 0x64, 0x65, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x20, 0x2e, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x53, 0x75, 0x6d, 0x1a, 0x21, 0x2e, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x61, 0x72, 0x74, 0x30, 0x01, 0x12, 0x4e, 0x0a, 0x08, 0x45, 0x63, 0x68, 0x6f, 0x4e, 0x75, 0x6c, 0x6c, 0x12, 0x28, 0x2e, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x45, 0x63, 0x68, 0x6f, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x28, 0x01, 0x12, 0x56, 0x0a, 0x0e, 0x45, 0x63, 0x68, 0x6f, 0x4e, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x28, 0x2e, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x45, 0x63, 0x68, 0x6f, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x28, 0x01, 0x30, 0x01, 0x12, 0x58, 0x0a, 0x12, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 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, 0x28, 0x2e, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x2e, 0x45, 0x63, 0x68, 0x6f, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x30, 0x01, 0x42, 0x3d, 0x5a, 0x3b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x3b, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_github_com_containerd_ttrpc_integration_streaming_test_proto_rawDescOnce sync.Once file_github_com_containerd_ttrpc_integration_streaming_test_proto_rawDescData = file_github_com_containerd_ttrpc_integration_streaming_test_proto_rawDesc ) func file_github_com_containerd_ttrpc_integration_streaming_test_proto_rawDescGZIP() []byte { file_github_com_containerd_ttrpc_integration_streaming_test_proto_rawDescOnce.Do(func() { file_github_com_containerd_ttrpc_integration_streaming_test_proto_rawDescData = protoimpl.X.CompressGZIP(file_github_com_containerd_ttrpc_integration_streaming_test_proto_rawDescData) }) return file_github_com_containerd_ttrpc_integration_streaming_test_proto_rawDescData } var file_github_com_containerd_ttrpc_integration_streaming_test_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_github_com_containerd_ttrpc_integration_streaming_test_proto_goTypes = []interface{}{ (*EchoPayload)(nil), // 0: ttrpc.integration.streaming.EchoPayload (*Part)(nil), // 1: ttrpc.integration.streaming.Part (*Sum)(nil), // 2: ttrpc.integration.streaming.Sum (*emptypb.Empty)(nil), // 3: google.protobuf.Empty } var file_github_com_containerd_ttrpc_integration_streaming_test_proto_depIdxs = []int32{ 0, // 0: ttrpc.integration.streaming.Streaming.Echo:input_type -> ttrpc.integration.streaming.EchoPayload 0, // 1: ttrpc.integration.streaming.Streaming.EchoStream:input_type -> ttrpc.integration.streaming.EchoPayload 1, // 2: ttrpc.integration.streaming.Streaming.SumStream:input_type -> ttrpc.integration.streaming.Part 2, // 3: ttrpc.integration.streaming.Streaming.DivideStream:input_type -> ttrpc.integration.streaming.Sum 0, // 4: ttrpc.integration.streaming.Streaming.EchoNull:input_type -> ttrpc.integration.streaming.EchoPayload 0, // 5: ttrpc.integration.streaming.Streaming.EchoNullStream:input_type -> ttrpc.integration.streaming.EchoPayload 3, // 6: ttrpc.integration.streaming.Streaming.EmptyPayloadStream:input_type -> google.protobuf.Empty 0, // 7: ttrpc.integration.streaming.Streaming.Echo:output_type -> ttrpc.integration.streaming.EchoPayload 0, // 8: ttrpc.integration.streaming.Streaming.EchoStream:output_type -> ttrpc.integration.streaming.EchoPayload 2, // 9: ttrpc.integration.streaming.Streaming.SumStream:output_type -> ttrpc.integration.streaming.Sum 1, // 10: ttrpc.integration.streaming.Streaming.DivideStream:output_type -> ttrpc.integration.streaming.Part 3, // 11: ttrpc.integration.streaming.Streaming.EchoNull:output_type -> google.protobuf.Empty 3, // 12: ttrpc.integration.streaming.Streaming.EchoNullStream:output_type -> google.protobuf.Empty 0, // 13: ttrpc.integration.streaming.Streaming.EmptyPayloadStream:output_type -> ttrpc.integration.streaming.EchoPayload 7, // [7:14] is the sub-list for method output_type 0, // [0:7] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_github_com_containerd_ttrpc_integration_streaming_test_proto_init() } func file_github_com_containerd_ttrpc_integration_streaming_test_proto_init() { if File_github_com_containerd_ttrpc_integration_streaming_test_proto != nil { return } if !protoimpl.UnsafeEnabled { file_github_com_containerd_ttrpc_integration_streaming_test_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*EchoPayload); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_github_com_containerd_ttrpc_integration_streaming_test_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Part); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_github_com_containerd_ttrpc_integration_streaming_test_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Sum); 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_github_com_containerd_ttrpc_integration_streaming_test_proto_rawDesc, NumEnums: 0, NumMessages: 3, NumExtensions: 0, NumServices: 1, }, GoTypes: file_github_com_containerd_ttrpc_integration_streaming_test_proto_goTypes, DependencyIndexes: file_github_com_containerd_ttrpc_integration_streaming_test_proto_depIdxs, MessageInfos: file_github_com_containerd_ttrpc_integration_streaming_test_proto_msgTypes, }.Build() File_github_com_containerd_ttrpc_integration_streaming_test_proto = out.File file_github_com_containerd_ttrpc_integration_streaming_test_proto_rawDesc = nil file_github_com_containerd_ttrpc_integration_streaming_test_proto_goTypes = nil file_github_com_containerd_ttrpc_integration_streaming_test_proto_depIdxs = nil } ttrpc-1.2.4/integration/streaming/test.proto000066400000000000000000000031301462051616600212360ustar00rootroot00000000000000/* Copyright The containerd 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. */ syntax = "proto3"; package ttrpc.integration.streaming; import "google/protobuf/empty.proto"; option go_package = "github.com/containerd/ttrpc/integration/streaming;streaming"; // Shim service is launched for each container and is responsible for owning the IO // for the container and its additional processes. The shim is also the parent of // each container and allows reattaching to the IO and receiving the exit status // for the container processes. service Streaming { rpc Echo(EchoPayload) returns (EchoPayload); rpc EchoStream(stream EchoPayload) returns (stream EchoPayload); rpc SumStream(stream Part) returns (Sum); rpc DivideStream(Sum) returns (stream Part); rpc EchoNull(stream EchoPayload) returns (google.protobuf.Empty); rpc EchoNullStream(stream EchoPayload) returns (stream google.protobuf.Empty); rpc EmptyPayloadStream(google.protobuf.Empty) returns (stream EchoPayload); } message EchoPayload { uint32 seq = 1; string msg = 2; } message Part { int32 add = 1; } message Sum { int32 sum = 1; int32 num = 2; } ttrpc-1.2.4/integration/streaming/test_ttrpc.pb.go000066400000000000000000000264151462051616600223270ustar00rootroot00000000000000// Code generated by protoc-gen-go-ttrpc. DO NOT EDIT. // source: github.com/containerd/ttrpc/integration/streaming/test.proto package streaming import ( context "context" ttrpc "github.com/containerd/ttrpc" emptypb "google.golang.org/protobuf/types/known/emptypb" ) type TTRPCStreamingService interface { Echo(context.Context, *EchoPayload) (*EchoPayload, error) EchoStream(context.Context, TTRPCStreaming_EchoStreamServer) error SumStream(context.Context, TTRPCStreaming_SumStreamServer) (*Sum, error) DivideStream(context.Context, *Sum, TTRPCStreaming_DivideStreamServer) error EchoNull(context.Context, TTRPCStreaming_EchoNullServer) (*emptypb.Empty, error) EchoNullStream(context.Context, TTRPCStreaming_EchoNullStreamServer) error EmptyPayloadStream(context.Context, *emptypb.Empty, TTRPCStreaming_EmptyPayloadStreamServer) error } type TTRPCStreaming_EchoStreamServer interface { Send(*EchoPayload) error Recv() (*EchoPayload, error) ttrpc.StreamServer } type ttrpcstreamingEchoStreamServer struct { ttrpc.StreamServer } func (x *ttrpcstreamingEchoStreamServer) Send(m *EchoPayload) error { return x.StreamServer.SendMsg(m) } func (x *ttrpcstreamingEchoStreamServer) Recv() (*EchoPayload, error) { m := new(EchoPayload) if err := x.StreamServer.RecvMsg(m); err != nil { return nil, err } return m, nil } type TTRPCStreaming_SumStreamServer interface { Recv() (*Part, error) ttrpc.StreamServer } type ttrpcstreamingSumStreamServer struct { ttrpc.StreamServer } func (x *ttrpcstreamingSumStreamServer) Recv() (*Part, error) { m := new(Part) if err := x.StreamServer.RecvMsg(m); err != nil { return nil, err } return m, nil } type TTRPCStreaming_DivideStreamServer interface { Send(*Part) error ttrpc.StreamServer } type ttrpcstreamingDivideStreamServer struct { ttrpc.StreamServer } func (x *ttrpcstreamingDivideStreamServer) Send(m *Part) error { return x.StreamServer.SendMsg(m) } type TTRPCStreaming_EchoNullServer interface { Recv() (*EchoPayload, error) ttrpc.StreamServer } type ttrpcstreamingEchoNullServer struct { ttrpc.StreamServer } func (x *ttrpcstreamingEchoNullServer) Recv() (*EchoPayload, error) { m := new(EchoPayload) if err := x.StreamServer.RecvMsg(m); err != nil { return nil, err } return m, nil } type TTRPCStreaming_EchoNullStreamServer interface { Send(*emptypb.Empty) error Recv() (*EchoPayload, error) ttrpc.StreamServer } type ttrpcstreamingEchoNullStreamServer struct { ttrpc.StreamServer } func (x *ttrpcstreamingEchoNullStreamServer) Send(m *emptypb.Empty) error { return x.StreamServer.SendMsg(m) } func (x *ttrpcstreamingEchoNullStreamServer) Recv() (*EchoPayload, error) { m := new(EchoPayload) if err := x.StreamServer.RecvMsg(m); err != nil { return nil, err } return m, nil } type TTRPCStreaming_EmptyPayloadStreamServer interface { Send(*EchoPayload) error ttrpc.StreamServer } type ttrpcstreamingEmptyPayloadStreamServer struct { ttrpc.StreamServer } func (x *ttrpcstreamingEmptyPayloadStreamServer) Send(m *EchoPayload) error { return x.StreamServer.SendMsg(m) } func RegisterTTRPCStreamingService(srv *ttrpc.Server, svc TTRPCStreamingService) { srv.RegisterService("ttrpc.integration.streaming.Streaming", &ttrpc.ServiceDesc{ Methods: map[string]ttrpc.Method{ "Echo": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { var req EchoPayload if err := unmarshal(&req); err != nil { return nil, err } return svc.Echo(ctx, &req) }, }, Streams: map[string]ttrpc.Stream{ "EchoStream": { Handler: func(ctx context.Context, stream ttrpc.StreamServer) (interface{}, error) { return nil, svc.EchoStream(ctx, &ttrpcstreamingEchoStreamServer{stream}) }, StreamingClient: true, StreamingServer: true, }, "SumStream": { Handler: func(ctx context.Context, stream ttrpc.StreamServer) (interface{}, error) { return svc.SumStream(ctx, &ttrpcstreamingSumStreamServer{stream}) }, StreamingClient: true, StreamingServer: false, }, "DivideStream": { Handler: func(ctx context.Context, stream ttrpc.StreamServer) (interface{}, error) { m := new(Sum) if err := stream.RecvMsg(m); err != nil { return nil, err } return nil, svc.DivideStream(ctx, m, &ttrpcstreamingDivideStreamServer{stream}) }, StreamingClient: false, StreamingServer: true, }, "EchoNull": { Handler: func(ctx context.Context, stream ttrpc.StreamServer) (interface{}, error) { return svc.EchoNull(ctx, &ttrpcstreamingEchoNullServer{stream}) }, StreamingClient: true, StreamingServer: false, }, "EchoNullStream": { Handler: func(ctx context.Context, stream ttrpc.StreamServer) (interface{}, error) { return nil, svc.EchoNullStream(ctx, &ttrpcstreamingEchoNullStreamServer{stream}) }, StreamingClient: true, StreamingServer: true, }, "EmptyPayloadStream": { Handler: func(ctx context.Context, stream ttrpc.StreamServer) (interface{}, error) { m := new(emptypb.Empty) if err := stream.RecvMsg(m); err != nil { return nil, err } return nil, svc.EmptyPayloadStream(ctx, m, &ttrpcstreamingEmptyPayloadStreamServer{stream}) }, StreamingClient: false, StreamingServer: true, }, }, }) } type TTRPCStreamingClient interface { Echo(context.Context, *EchoPayload) (*EchoPayload, error) EchoStream(context.Context) (TTRPCStreaming_EchoStreamClient, error) SumStream(context.Context) (TTRPCStreaming_SumStreamClient, error) DivideStream(context.Context, *Sum) (TTRPCStreaming_DivideStreamClient, error) EchoNull(context.Context) (TTRPCStreaming_EchoNullClient, error) EchoNullStream(context.Context) (TTRPCStreaming_EchoNullStreamClient, error) EmptyPayloadStream(context.Context, *emptypb.Empty) (TTRPCStreaming_EmptyPayloadStreamClient, error) } type ttrpcstreamingClient struct { client *ttrpc.Client } func NewTTRPCStreamingClient(client *ttrpc.Client) TTRPCStreamingClient { return &ttrpcstreamingClient{ client: client, } } func (c *ttrpcstreamingClient) Echo(ctx context.Context, req *EchoPayload) (*EchoPayload, error) { var resp EchoPayload if err := c.client.Call(ctx, "ttrpc.integration.streaming.Streaming", "Echo", req, &resp); err != nil { return nil, err } return &resp, nil } func (c *ttrpcstreamingClient) EchoStream(ctx context.Context) (TTRPCStreaming_EchoStreamClient, error) { stream, err := c.client.NewStream(ctx, &ttrpc.StreamDesc{ StreamingClient: true, StreamingServer: true, }, "ttrpc.integration.streaming.Streaming", "EchoStream", nil) if err != nil { return nil, err } x := &ttrpcstreamingEchoStreamClient{stream} return x, nil } type TTRPCStreaming_EchoStreamClient interface { Send(*EchoPayload) error Recv() (*EchoPayload, error) ttrpc.ClientStream } type ttrpcstreamingEchoStreamClient struct { ttrpc.ClientStream } func (x *ttrpcstreamingEchoStreamClient) Send(m *EchoPayload) error { return x.ClientStream.SendMsg(m) } func (x *ttrpcstreamingEchoStreamClient) Recv() (*EchoPayload, error) { m := new(EchoPayload) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func (c *ttrpcstreamingClient) SumStream(ctx context.Context) (TTRPCStreaming_SumStreamClient, error) { stream, err := c.client.NewStream(ctx, &ttrpc.StreamDesc{ StreamingClient: true, StreamingServer: false, }, "ttrpc.integration.streaming.Streaming", "SumStream", nil) if err != nil { return nil, err } x := &ttrpcstreamingSumStreamClient{stream} return x, nil } type TTRPCStreaming_SumStreamClient interface { Send(*Part) error CloseAndRecv() (*Sum, error) ttrpc.ClientStream } type ttrpcstreamingSumStreamClient struct { ttrpc.ClientStream } func (x *ttrpcstreamingSumStreamClient) Send(m *Part) error { return x.ClientStream.SendMsg(m) } func (x *ttrpcstreamingSumStreamClient) CloseAndRecv() (*Sum, error) { if err := x.ClientStream.CloseSend(); err != nil { return nil, err } m := new(Sum) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func (c *ttrpcstreamingClient) DivideStream(ctx context.Context, req *Sum) (TTRPCStreaming_DivideStreamClient, error) { stream, err := c.client.NewStream(ctx, &ttrpc.StreamDesc{ StreamingClient: false, StreamingServer: true, }, "ttrpc.integration.streaming.Streaming", "DivideStream", req) if err != nil { return nil, err } x := &ttrpcstreamingDivideStreamClient{stream} return x, nil } type TTRPCStreaming_DivideStreamClient interface { Recv() (*Part, error) ttrpc.ClientStream } type ttrpcstreamingDivideStreamClient struct { ttrpc.ClientStream } func (x *ttrpcstreamingDivideStreamClient) Recv() (*Part, error) { m := new(Part) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func (c *ttrpcstreamingClient) EchoNull(ctx context.Context) (TTRPCStreaming_EchoNullClient, error) { stream, err := c.client.NewStream(ctx, &ttrpc.StreamDesc{ StreamingClient: true, StreamingServer: false, }, "ttrpc.integration.streaming.Streaming", "EchoNull", nil) if err != nil { return nil, err } x := &ttrpcstreamingEchoNullClient{stream} return x, nil } type TTRPCStreaming_EchoNullClient interface { Send(*EchoPayload) error CloseAndRecv() (*emptypb.Empty, error) ttrpc.ClientStream } type ttrpcstreamingEchoNullClient struct { ttrpc.ClientStream } func (x *ttrpcstreamingEchoNullClient) Send(m *EchoPayload) error { return x.ClientStream.SendMsg(m) } func (x *ttrpcstreamingEchoNullClient) CloseAndRecv() (*emptypb.Empty, error) { if err := x.ClientStream.CloseSend(); err != nil { return nil, err } m := new(emptypb.Empty) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func (c *ttrpcstreamingClient) EchoNullStream(ctx context.Context) (TTRPCStreaming_EchoNullStreamClient, error) { stream, err := c.client.NewStream(ctx, &ttrpc.StreamDesc{ StreamingClient: true, StreamingServer: true, }, "ttrpc.integration.streaming.Streaming", "EchoNullStream", nil) if err != nil { return nil, err } x := &ttrpcstreamingEchoNullStreamClient{stream} return x, nil } type TTRPCStreaming_EchoNullStreamClient interface { Send(*EchoPayload) error Recv() (*emptypb.Empty, error) ttrpc.ClientStream } type ttrpcstreamingEchoNullStreamClient struct { ttrpc.ClientStream } func (x *ttrpcstreamingEchoNullStreamClient) Send(m *EchoPayload) error { return x.ClientStream.SendMsg(m) } func (x *ttrpcstreamingEchoNullStreamClient) Recv() (*emptypb.Empty, error) { m := new(emptypb.Empty) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func (c *ttrpcstreamingClient) EmptyPayloadStream(ctx context.Context, req *emptypb.Empty) (TTRPCStreaming_EmptyPayloadStreamClient, error) { stream, err := c.client.NewStream(ctx, &ttrpc.StreamDesc{ StreamingClient: false, StreamingServer: true, }, "ttrpc.integration.streaming.Streaming", "EmptyPayloadStream", req) if err != nil { return nil, err } x := &ttrpcstreamingEmptyPayloadStreamClient{stream} return x, nil } type TTRPCStreaming_EmptyPayloadStreamClient interface { Recv() (*EchoPayload, error) ttrpc.ClientStream } type ttrpcstreamingEmptyPayloadStreamClient struct { ttrpc.ClientStream } func (x *ttrpcstreamingEmptyPayloadStreamClient) Recv() (*EchoPayload, error) { m := new(EchoPayload) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } ttrpc-1.2.4/integration/streaming_test.go000066400000000000000000000241071462051616600205670ustar00rootroot00000000000000/* Copyright The containerd 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. */ package integration import ( "context" "errors" "fmt" "io" "math/rand" "net" "os" "sync" "testing" "time" "github.com/containerd/ttrpc" "github.com/containerd/ttrpc/integration/streaming" "github.com/golang/protobuf/ptypes/empty" "google.golang.org/protobuf/types/known/emptypb" ) func runService(ctx context.Context, t testing.TB, service streaming.TTRPCStreamingService) (streaming.TTRPCStreamingClient, func()) { server, err := ttrpc.NewServer() if err != nil { t.Fatal(err) } streaming.RegisterTTRPCStreamingService(server, service) addr := t.Name() + ".sock" if err := os.RemoveAll(addr); err != nil { t.Fatal(err) } listener, err := net.Listen("unix", addr) if err != nil { t.Fatal(err) } ctx, cancel := context.WithCancel(ctx) defer func() { if t.Failed() { cancel() server.Close() } }() go func() { err := server.Serve(ctx, listener) if err != nil && !errors.Is(err, ttrpc.ErrServerClosed) { t.Error(err) } }() conn, err := net.Dial("unix", addr) if err != nil { t.Fatal(err) } client := ttrpc.NewClient(conn) return streaming.NewTTRPCStreamingClient(client), func() { client.Close() server.Close() conn.Close() cancel() } } type testStreamingService struct { t testing.TB } func (tss *testStreamingService) Echo(_ context.Context, e *streaming.EchoPayload) (*streaming.EchoPayload, error) { e.Seq++ return e, nil } func (tss *testStreamingService) EchoStream(_ context.Context, es streaming.TTRPCStreaming_EchoStreamServer) error { for { var e streaming.EchoPayload if err := es.RecvMsg(&e); err != nil { if err == io.EOF { return nil } return err } e.Seq++ if err := es.SendMsg(&e); err != nil { return err } } } func (tss *testStreamingService) SumStream(_ context.Context, ss streaming.TTRPCStreaming_SumStreamServer) (*streaming.Sum, error) { var sum streaming.Sum for { var part streaming.Part if err := ss.RecvMsg(&part); err != nil { if err == io.EOF { break } return nil, err } sum.Sum = sum.Sum + part.Add sum.Num++ } return &sum, nil } func (tss *testStreamingService) DivideStream(_ context.Context, sum *streaming.Sum, ss streaming.TTRPCStreaming_DivideStreamServer) error { parts := divideSum(sum) for _, part := range parts { if err := ss.Send(part); err != nil { return err } } return nil } func (tss *testStreamingService) EchoNull(_ context.Context, es streaming.TTRPCStreaming_EchoNullServer) (*empty.Empty, error) { msg := "non-empty empty" for seq := uint32(0); ; seq++ { var e streaming.EchoPayload if err := es.RecvMsg(&e); err != nil { if err == io.EOF { break } return nil, err } if e.Seq != seq { return nil, fmt.Errorf("unexpected sequence %d, expected %d", e.Seq, seq) } if e.Msg != msg { return nil, fmt.Errorf("unexpected message %q, expected %q", e.Msg, msg) } } return &empty.Empty{}, nil } func (tss *testStreamingService) EchoNullStream(_ context.Context, es streaming.TTRPCStreaming_EchoNullStreamServer) error { msg := "non-empty empty" empty := &empty.Empty{} var wg sync.WaitGroup var sendErr error var errOnce sync.Once for seq := uint32(0); ; seq++ { var e streaming.EchoPayload if err := es.RecvMsg(&e); err != nil { if err == io.EOF { break } return err } if e.Seq != seq { return fmt.Errorf("unexpected sequence %d, expected %d", e.Seq, seq) } if e.Msg != msg { return fmt.Errorf("unexpected message %q, expected %q", e.Msg, msg) } for i := 0; i < 10; i++ { wg.Add(1) go func() { defer wg.Done() if err := es.SendMsg(empty); err != nil { errOnce.Do(func() { sendErr = err }) } }() } } wg.Wait() return sendErr } func (tss *testStreamingService) EmptyPayloadStream(_ context.Context, _ *emptypb.Empty, streamer streaming.TTRPCStreaming_EmptyPayloadStreamServer) error { if err := streamer.Send(&streaming.EchoPayload{Seq: 1}); err != nil { return err } return streamer.Send(&streaming.EchoPayload{Seq: 2}) } func TestStreamingService(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() client, cleanup := runService(ctx, t, &testStreamingService{t}) defer cleanup() t.Run("Echo", echoTest(ctx, client)) t.Run("EchoStream", echoStreamTest(ctx, client)) t.Run("SumStream", sumStreamTest(ctx, client)) t.Run("DivideStream", divideStreamTest(ctx, client)) t.Run("EchoNull", echoNullTest(ctx, client)) t.Run("EchoNullStream", echoNullStreamTest(ctx, client)) t.Run("EmptyPayloadStream", emptyPayloadStream(ctx, client)) } func echoTest(ctx context.Context, client streaming.TTRPCStreamingClient) func(t *testing.T) { return func(t *testing.T) { echo1 := &streaming.EchoPayload{ Seq: 1, Msg: "Echo Me", } resp, err := client.Echo(ctx, echo1) if err != nil { t.Fatal(err) } assertNextEcho(t, echo1, resp) } } func echoStreamTest(ctx context.Context, client streaming.TTRPCStreamingClient) func(t *testing.T) { return func(t *testing.T) { stream, err := client.EchoStream(ctx) if err != nil { t.Fatal(err) } for i := 0; i < 100; i = i + 2 { echoi := &streaming.EchoPayload{ Seq: uint32(i), Msg: fmt.Sprintf("%d: Echo in a stream", i), } if err := stream.Send(echoi); err != nil { t.Fatal(err) } resp, err := stream.Recv() if err != nil { t.Fatal(err) } assertNextEcho(t, echoi, resp) } if err := stream.CloseSend(); err != nil { t.Fatal(err) } if _, err := stream.Recv(); err != io.EOF { t.Fatalf("Expected io.EOF, got %v", err) } } } func sumStreamTest(ctx context.Context, client streaming.TTRPCStreamingClient) func(t *testing.T) { return func(t *testing.T) { stream, err := client.SumStream(ctx) if err != nil { t.Fatal(err) } var sum streaming.Sum if err := stream.Send(&streaming.Part{}); err != nil { t.Fatal(err) } sum.Num++ for i := -99; i <= 100; i++ { addi := &streaming.Part{ Add: int32(i), } if err := stream.Send(addi); err != nil { t.Fatal(err) } sum.Sum = sum.Sum + int32(i) sum.Num++ } if err := stream.Send(&streaming.Part{}); err != nil { t.Fatal(err) } sum.Num++ ssum, err := stream.CloseAndRecv() if err != nil { t.Fatal(err) } assertSum(t, ssum, &sum) } } func divideStreamTest(ctx context.Context, client streaming.TTRPCStreamingClient) func(t *testing.T) { return func(t *testing.T) { expected := &streaming.Sum{ Sum: 392, Num: 30, } stream, err := client.DivideStream(ctx, expected) if err != nil { t.Fatal(err) } var actual streaming.Sum for { part, err := stream.Recv() if err != nil { if err == io.EOF { break } t.Fatal(err) } actual.Sum = actual.Sum + part.Add actual.Num++ } assertSum(t, &actual, expected) } } func echoNullTest(ctx context.Context, client streaming.TTRPCStreamingClient) func(t *testing.T) { return func(t *testing.T) { stream, err := client.EchoNull(ctx) if err != nil { t.Fatal(err) } for i := 0; i < 100; i++ { echoi := &streaming.EchoPayload{ Seq: uint32(i), Msg: "non-empty empty", } if err := stream.Send(echoi); err != nil { t.Fatal(err) } } if _, err := stream.CloseAndRecv(); err != nil { t.Fatal(err) } } } func echoNullStreamTest(ctx context.Context, client streaming.TTRPCStreamingClient) func(t *testing.T) { return func(t *testing.T) { stream, err := client.EchoNullStream(ctx) if err != nil { t.Fatal(err) } var c int wait := make(chan error) go func() { defer close(wait) for { _, err := stream.Recv() if err != nil { if err != io.EOF { wait <- err } return } c++ } }() for i := 0; i < 100; i++ { echoi := &streaming.EchoPayload{ Seq: uint32(i), Msg: "non-empty empty", } if err := stream.Send(echoi); err != nil { t.Fatal(err) } } if err := stream.CloseSend(); err != nil { t.Fatal(err) } select { case err := <-wait: if err != nil { t.Fatal(err) } case <-time.After(time.Second * 10): t.Fatal("did not receive EOF within 10 seconds") } } } func emptyPayloadStream(ctx context.Context, client streaming.TTRPCStreamingClient) func(t *testing.T) { return func(t *testing.T) { ctx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() stream, err := client.EmptyPayloadStream(ctx, nil) if err != nil { t.Fatal(err) } for i := uint32(1); i < 3; i++ { first, err := stream.Recv() if err != nil { t.Fatal(err) } if first.Seq != i { t.Fatalf("unexpected seq: %d != %d", first.Seq, i) } } if _, err := stream.Recv(); err != io.EOF { t.Fatalf("Expected io.EOF, got %v", err) } } } func assertNextEcho(t testing.TB, a, b *streaming.EchoPayload) { t.Helper() if a.Msg != b.Msg { t.Fatalf("Mismatched messages: %q != %q", a.Msg, b.Msg) } if b.Seq != a.Seq+1 { t.Fatalf("Wrong sequence ID: got %d, expected %d", b.Seq, a.Seq+1) } } func assertSum(t testing.TB, a, b *streaming.Sum) { t.Helper() if a.Sum != b.Sum { t.Fatalf("Wrong sum %d, expected %d", a.Sum, b.Sum) } if a.Num != b.Num { t.Fatalf("Wrong num %d, expected %d", a.Num, b.Num) } } func divideSum(sum *streaming.Sum) []*streaming.Part { r := rand.New(rand.NewSource(14)) var total int32 parts := make([]*streaming.Part, sum.Num) for i := int32(1); i < sum.Num-2; i++ { add := r.Int31()%1000 - 500 parts[i] = &streaming.Part{ Add: add, } total = total + add } parts[0] = &streaming.Part{} parts[sum.Num-2] = &streaming.Part{ Add: sum.Sum - total, } parts[sum.Num-1] = &streaming.Part{} return parts } ttrpc-1.2.4/interceptor.go000066400000000000000000000045071462051616600155540ustar00rootroot00000000000000/* Copyright The containerd 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. */ package ttrpc import "context" // UnaryServerInfo provides information about the server request type UnaryServerInfo struct { FullMethod string } // UnaryClientInfo provides information about the client request type UnaryClientInfo struct { FullMethod string } // StreamServerInfo provides information about the server request type StreamServerInfo struct { FullMethod string StreamingClient bool StreamingServer bool } // Unmarshaler contains the server request data and allows it to be unmarshaled // into a concrete type type Unmarshaler func(interface{}) error // Invoker invokes the client's request and response from the ttrpc server type Invoker func(context.Context, *Request, *Response) error // UnaryServerInterceptor specifies the interceptor function for server request/response type UnaryServerInterceptor func(context.Context, Unmarshaler, *UnaryServerInfo, Method) (interface{}, error) // UnaryClientInterceptor specifies the interceptor function for client request/response type UnaryClientInterceptor func(context.Context, *Request, *Response, *UnaryClientInfo, Invoker) error func defaultServerInterceptor(ctx context.Context, unmarshal Unmarshaler, _ *UnaryServerInfo, method Method) (interface{}, error) { return method(ctx, unmarshal) } func defaultClientInterceptor(ctx context.Context, req *Request, resp *Response, _ *UnaryClientInfo, invoker Invoker) error { return invoker(ctx, req, resp) } type StreamServerInterceptor func(context.Context, StreamServer, *StreamServerInfo, StreamHandler) (interface{}, error) func defaultStreamServerInterceptor(ctx context.Context, ss StreamServer, _ *StreamServerInfo, stream StreamHandler) (interface{}, error) { return stream(ctx, ss) } type StreamClientInterceptor func(context.Context) ttrpc-1.2.4/interceptor_test.go000066400000000000000000000144731462051616600166160ustar00rootroot00000000000000/* Copyright The containerd 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. */ package ttrpc import ( "context" "reflect" "strings" "testing" "github.com/containerd/ttrpc/internal" ) func TestUnaryClientInterceptor(t *testing.T) { var ( intercepted = false interceptor = func(ctx context.Context, req *Request, reply *Response, ci *UnaryClientInfo, i Invoker) error { intercepted = true return i(ctx, req, reply) } ctx = context.Background() server = mustServer(t)(NewServer()) testImpl = &testingServer{} addr, listener = newTestListener(t) client, cleanup = newTestClient(t, addr, WithUnaryClientInterceptor(interceptor)) message = strings.Repeat("a", 16) reply = strings.Repeat(message, 2) ) defer listener.Close() defer cleanup() registerTestingService(server, testImpl) go server.Serve(ctx, listener) defer server.Shutdown(ctx) request := &internal.TestPayload{ Foo: message, } response := &internal.TestPayload{} if err := client.Call(ctx, serviceName, "Test", request, response); err != nil { t.Fatalf("unexpected error: %v", err) } if !intercepted { t.Fatalf("ttrpc client call not intercepted") } if response.Foo != reply { t.Fatalf("unexpected test service reply: %q != %q", response.Foo, reply) } } func TestChainUnaryClientInterceptor(t *testing.T) { var ( orderIdx = 0 recorded = []string{} intercept = func(idx int, tag string) UnaryClientInterceptor { return func(ctx context.Context, req *Request, reply *Response, ci *UnaryClientInfo, i Invoker) error { if idx != orderIdx { t.Fatalf("unexpected interceptor invocation order (%d != %d)", orderIdx, idx) } recorded = append(recorded, tag) orderIdx++ return i(ctx, req, reply) } } ctx = context.Background() server = mustServer(t)(NewServer()) testImpl = &testingServer{} addr, listener = newTestListener(t) client, cleanup = newTestClient(t, addr, WithChainUnaryClientInterceptor(), WithChainUnaryClientInterceptor( intercept(0, "seen it"), intercept(1, "been"), intercept(2, "there"), intercept(3, "done"), intercept(4, "that"), ), ) expected = []string{ "seen it", "been", "there", "done", "that", } message = strings.Repeat("a", 16) reply = strings.Repeat(message, 2) ) defer listener.Close() defer cleanup() registerTestingService(server, testImpl) go server.Serve(ctx, listener) defer server.Shutdown(ctx) request := &internal.TestPayload{ Foo: message, } response := &internal.TestPayload{} if err := client.Call(ctx, serviceName, "Test", request, response); err != nil { t.Fatalf("unexpected error: %v", err) } if !reflect.DeepEqual(recorded, expected) { t.Fatalf("unexpected ttrpc chained client unary interceptor order (%s != %s)", strings.Join(recorded, " "), strings.Join(expected, " ")) } if response.Foo != reply { t.Fatalf("unexpected test service reply: %q != %q", response.Foo, reply) } } func TestUnaryServerInterceptor(t *testing.T) { var ( intercepted = false interceptor = func(ctx context.Context, unmarshal Unmarshaler, _ *UnaryServerInfo, method Method) (interface{}, error) { intercepted = true return method(ctx, unmarshal) } ctx = context.Background() server = mustServer(t)(NewServer(WithUnaryServerInterceptor(interceptor))) testImpl = &testingServer{} addr, listener = newTestListener(t) client, cleanup = newTestClient(t, addr) message = strings.Repeat("a", 16) reply = strings.Repeat(message, 2) ) defer listener.Close() defer cleanup() registerTestingService(server, testImpl) go server.Serve(ctx, listener) defer server.Shutdown(ctx) request := &internal.TestPayload{ Foo: message, } response := &internal.TestPayload{} if err := client.Call(ctx, serviceName, "Test", request, response); err != nil { t.Fatalf("unexpected error: %v", err) } if !intercepted { t.Fatalf("ttrpc server call not intercepted") } if response.Foo != reply { t.Fatalf("unexpected test service reply: %q != %q", response.Foo, reply) } } func TestChainUnaryServerInterceptor(t *testing.T) { var ( orderIdx = 0 recorded = []string{} intercept = func(idx int, tag string) UnaryServerInterceptor { return func(ctx context.Context, unmarshal Unmarshaler, _ *UnaryServerInfo, method Method) (interface{}, error) { if orderIdx != idx { t.Fatalf("unexpected interceptor invocation order (%d != %d)", orderIdx, idx) } recorded = append(recorded, tag) orderIdx++ return method(ctx, unmarshal) } } ctx = context.Background() server = mustServer(t)(NewServer( WithUnaryServerInterceptor( intercept(0, "seen it"), ), WithChainUnaryServerInterceptor( intercept(1, "been"), intercept(2, "there"), intercept(3, "done"), intercept(4, "that"), ), )) expected = []string{ "seen it", "been", "there", "done", "that", } testImpl = &testingServer{} addr, listener = newTestListener(t) client, cleanup = newTestClient(t, addr) message = strings.Repeat("a", 16) reply = strings.Repeat(message, 2) ) defer listener.Close() defer cleanup() registerTestingService(server, testImpl) go server.Serve(ctx, listener) defer server.Shutdown(ctx) request := &internal.TestPayload{ Foo: message, } response := &internal.TestPayload{} if err := client.Call(ctx, serviceName, "Test", request, response); err != nil { t.Fatalf("unexpected error: %v", err) } if !reflect.DeepEqual(recorded, expected) { t.Fatalf("unexpected ttrpc chained server unary interceptor order (%s != %s)", strings.Join(recorded, " "), strings.Join(expected, " ")) } if response.Foo != reply { t.Fatalf("unexpected test service reply: %q != %q", response.Foo, reply) } } ttrpc-1.2.4/internal/000077500000000000000000000000001462051616600144755ustar00rootroot00000000000000ttrpc-1.2.4/internal/test.pb.go000066400000000000000000000171251462051616600164110ustar00rootroot00000000000000// Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.28.1 // protoc v3.20.1 // source: github.com/containerd/ttrpc/test.proto package internal import ( 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) ) type TestPayload struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Foo string `protobuf:"bytes,1,opt,name=foo,proto3" json:"foo,omitempty"` Deadline int64 `protobuf:"varint,2,opt,name=deadline,proto3" json:"deadline,omitempty"` Metadata string `protobuf:"bytes,3,opt,name=metadata,proto3" json:"metadata,omitempty"` } func (x *TestPayload) Reset() { *x = TestPayload{} if protoimpl.UnsafeEnabled { mi := &file_github_com_containerd_ttrpc_test_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *TestPayload) String() string { return protoimpl.X.MessageStringOf(x) } func (*TestPayload) ProtoMessage() {} func (x *TestPayload) ProtoReflect() protoreflect.Message { mi := &file_github_com_containerd_ttrpc_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 TestPayload.ProtoReflect.Descriptor instead. func (*TestPayload) Descriptor() ([]byte, []int) { return file_github_com_containerd_ttrpc_test_proto_rawDescGZIP(), []int{0} } func (x *TestPayload) GetFoo() string { if x != nil { return x.Foo } return "" } func (x *TestPayload) GetDeadline() int64 { if x != nil { return x.Deadline } return 0 } func (x *TestPayload) GetMetadata() string { if x != nil { return x.Metadata } return "" } type EchoPayload struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Seq int64 `protobuf:"varint,1,opt,name=seq,proto3" json:"seq,omitempty"` Msg string `protobuf:"bytes,2,opt,name=msg,proto3" json:"msg,omitempty"` } func (x *EchoPayload) Reset() { *x = EchoPayload{} if protoimpl.UnsafeEnabled { mi := &file_github_com_containerd_ttrpc_test_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *EchoPayload) String() string { return protoimpl.X.MessageStringOf(x) } func (*EchoPayload) ProtoMessage() {} func (x *EchoPayload) ProtoReflect() protoreflect.Message { mi := &file_github_com_containerd_ttrpc_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 EchoPayload.ProtoReflect.Descriptor instead. func (*EchoPayload) Descriptor() ([]byte, []int) { return file_github_com_containerd_ttrpc_test_proto_rawDescGZIP(), []int{1} } func (x *EchoPayload) GetSeq() int64 { if x != nil { return x.Seq } return 0 } func (x *EchoPayload) GetMsg() string { if x != nil { return x.Msg } return "" } var File_github_com_containerd_ttrpc_test_proto protoreflect.FileDescriptor var file_github_com_containerd_ttrpc_test_proto_rawDesc = []byte{ 0x0a, 0x26, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x74, 0x74, 0x72, 0x70, 0x63, 0x22, 0x57, 0x0a, 0x0b, 0x54, 0x65, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x6f, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x66, 0x6f, 0x6f, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x31, 0x0a, 0x0b, 0x45, 0x63, 0x68, 0x6f, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x65, 0x71, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x73, 0x65, 0x71, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_github_com_containerd_ttrpc_test_proto_rawDescOnce sync.Once file_github_com_containerd_ttrpc_test_proto_rawDescData = file_github_com_containerd_ttrpc_test_proto_rawDesc ) func file_github_com_containerd_ttrpc_test_proto_rawDescGZIP() []byte { file_github_com_containerd_ttrpc_test_proto_rawDescOnce.Do(func() { file_github_com_containerd_ttrpc_test_proto_rawDescData = protoimpl.X.CompressGZIP(file_github_com_containerd_ttrpc_test_proto_rawDescData) }) return file_github_com_containerd_ttrpc_test_proto_rawDescData } var file_github_com_containerd_ttrpc_test_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_github_com_containerd_ttrpc_test_proto_goTypes = []interface{}{ (*TestPayload)(nil), // 0: ttrpc.TestPayload (*EchoPayload)(nil), // 1: ttrpc.EchoPayload } var file_github_com_containerd_ttrpc_test_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type 0, // [0:0] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_github_com_containerd_ttrpc_test_proto_init() } func file_github_com_containerd_ttrpc_test_proto_init() { if File_github_com_containerd_ttrpc_test_proto != nil { return } if !protoimpl.UnsafeEnabled { file_github_com_containerd_ttrpc_test_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*TestPayload); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_github_com_containerd_ttrpc_test_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*EchoPayload); 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_github_com_containerd_ttrpc_test_proto_rawDesc, NumEnums: 0, NumMessages: 2, NumExtensions: 0, NumServices: 0, }, GoTypes: file_github_com_containerd_ttrpc_test_proto_goTypes, DependencyIndexes: file_github_com_containerd_ttrpc_test_proto_depIdxs, MessageInfos: file_github_com_containerd_ttrpc_test_proto_msgTypes, }.Build() File_github_com_containerd_ttrpc_test_proto = out.File file_github_com_containerd_ttrpc_test_proto_rawDesc = nil file_github_com_containerd_ttrpc_test_proto_goTypes = nil file_github_com_containerd_ttrpc_test_proto_depIdxs = nil } ttrpc-1.2.4/metadata.go000066400000000000000000000050751462051616600147770ustar00rootroot00000000000000/* Copyright The containerd 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. */ package ttrpc import ( "context" "strings" ) // MD is the user type for ttrpc metadata type MD map[string][]string // Get returns the metadata for a given key when they exist. // If there is no metadata, a nil slice and false are returned. func (m MD) Get(key string) ([]string, bool) { key = strings.ToLower(key) list, ok := m[key] if !ok || len(list) == 0 { return nil, false } return list, true } // Set sets the provided values for a given key. // The values will overwrite any existing values. // If no values provided, a key will be deleted. func (m MD) Set(key string, values ...string) { key = strings.ToLower(key) if len(values) == 0 { delete(m, key) return } m[key] = values } // Append appends additional values to the given key. func (m MD) Append(key string, values ...string) { key = strings.ToLower(key) if len(values) == 0 { return } current, ok := m[key] if ok { m.Set(key, append(current, values...)...) } else { m.Set(key, values...) } } func (m MD) setRequest(r *Request) { for k, values := range m { for _, v := range values { r.Metadata = append(r.Metadata, &KeyValue{ Key: k, Value: v, }) } } } func (m MD) fromRequest(r *Request) { for _, kv := range r.Metadata { m[kv.Key] = append(m[kv.Key], kv.Value) } } type metadataKey struct{} // GetMetadata retrieves metadata from context.Context (previously attached with WithMetadata) func GetMetadata(ctx context.Context) (MD, bool) { metadata, ok := ctx.Value(metadataKey{}).(MD) return metadata, ok } // GetMetadataValue gets a specific metadata value by name from context.Context func GetMetadataValue(ctx context.Context, name string) (string, bool) { metadata, ok := GetMetadata(ctx) if !ok { return "", false } if list, ok := metadata.Get(name); ok { return list[0], true } return "", false } // WithMetadata attaches metadata map to a context.Context func WithMetadata(ctx context.Context, md MD) context.Context { return context.WithValue(ctx, metadataKey{}, md) } ttrpc-1.2.4/metadata_test.go000066400000000000000000000053701462051616600160340ustar00rootroot00000000000000/* Copyright The containerd 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. */ package ttrpc import ( "context" "testing" ) func TestMetadataGet(t *testing.T) { metadata := make(MD) metadata.Set("foo", "1", "2") if list, ok := metadata.Get("foo"); !ok { t.Error("key not found") } else if len(list) != 2 { t.Errorf("unexpected number of values: %d", len(list)) } else if list[0] != "1" { t.Errorf("invalid metadata value at 0: %s", list[0]) } else if list[1] != "2" { t.Errorf("invalid metadata value at 1: %s", list[1]) } } func TestMetadataGetInvalidKey(t *testing.T) { metadata := make(MD) metadata.Set("foo", "1", "2") if _, ok := metadata.Get("invalid"); ok { t.Error("found invalid key") } } func TestMetadataUnset(t *testing.T) { metadata := make(MD) metadata.Set("foo", "1", "2") metadata.Set("foo") if _, ok := metadata.Get("foo"); ok { t.Error("key not deleted") } } func TestMetadataReplace(t *testing.T) { metadata := make(MD) metadata.Set("foo", "1", "2") metadata.Set("foo", "3", "4") if list, ok := metadata.Get("foo"); !ok { t.Error("key not found") } else if len(list) != 2 { t.Errorf("unexpected number of values: %d", len(list)) } else if list[0] != "3" { t.Errorf("invalid metadata value at 0: %s", list[0]) } else if list[1] != "4" { t.Errorf("invalid metadata value at 1: %s", list[1]) } } func TestMetadataAppend(t *testing.T) { metadata := make(MD) metadata.Set("foo", "1") metadata.Append("foo", "2") metadata.Append("bar", "3") if list, ok := metadata.Get("foo"); !ok { t.Error("key not found") } else if len(list) != 2 { t.Errorf("unexpected number of values: %d", len(list)) } else if list[0] != "1" { t.Errorf("invalid metadata value at 0: %s", list[0]) } else if list[1] != "2" { t.Errorf("invalid metadata value at 1: %s", list[1]) } if list, ok := metadata.Get("bar"); !ok { t.Error("key not found") } else if list[0] != "3" { t.Errorf("invalid value: %s", list[0]) } } func TestMetadataContext(t *testing.T) { metadata := make(MD) metadata.Set("foo", "bar") ctx := WithMetadata(context.Background(), metadata) if bar, ok := GetMetadataValue(ctx, "foo"); !ok { t.Error("metadata not found") } else if bar != "bar" { t.Errorf("invalid metadata value: %q", bar) } } ttrpc-1.2.4/plugin/000077500000000000000000000000001462051616600141575ustar00rootroot00000000000000ttrpc-1.2.4/plugin/generator.go000066400000000000000000000076371462051616600165110ustar00rootroot00000000000000/* Copyright The containerd 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. */ package plugin import ( "strings" "github.com/gogo/protobuf/protoc-gen-gogo/descriptor" "github.com/gogo/protobuf/protoc-gen-gogo/generator" ) type ttrpcGenerator struct { *generator.Generator generator.PluginImports typeurlPkg generator.Single ttrpcPkg generator.Single contextPkg generator.Single } func init() { generator.RegisterPlugin(new(ttrpcGenerator)) } func (p *ttrpcGenerator) Name() string { return "ttrpc" } func (p *ttrpcGenerator) Init(g *generator.Generator) { p.Generator = g } func (p *ttrpcGenerator) Generate(file *generator.FileDescriptor) { p.PluginImports = generator.NewPluginImports(p.Generator) p.contextPkg = p.NewImport("context") p.typeurlPkg = p.NewImport("github.com/containerd/typeurl") p.ttrpcPkg = p.NewImport("github.com/containerd/ttrpc") for _, service := range file.GetService() { serviceName := service.GetName() if pkg := file.GetPackage(); pkg != "" { serviceName = pkg + "." + serviceName } p.genService(serviceName, service) } } func (p *ttrpcGenerator) genService(fullName string, service *descriptor.ServiceDescriptorProto) { serviceName := service.GetName() + "Service" p.P() p.P("type ", serviceName, " interface{") p.In() for _, method := range service.Method { p.P(method.GetName(), "(ctx ", p.contextPkg.Use(), ".Context, ", "req *", p.typeName(method.GetInputType()), ") ", "(*", p.typeName(method.GetOutputType()), ", error)") } p.Out() p.P("}") p.P() // registration method p.P("func Register", serviceName, "(srv *", p.ttrpcPkg.Use(), ".Server, svc ", serviceName, ") {") p.In() p.P(`srv.Register("`, fullName, `", map[string]`, p.ttrpcPkg.Use(), ".Method{") p.In() for _, method := range service.Method { p.P(`"`, method.GetName(), `": `, `func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) {`) p.In() p.P("var req ", p.typeName(method.GetInputType())) p.P(`if err := unmarshal(&req); err != nil {`) p.In() p.P(`return nil, err`) p.Out() p.P(`}`) p.P("return svc.", method.GetName(), "(ctx, &req)") p.Out() p.P("},") } p.Out() p.P("})") p.Out() p.P("}") clientType := service.GetName() + "Client" clientStructType := strings.ToLower(clientType[:1]) + clientType[1:] p.P() p.P("type ", clientStructType, " struct{") p.In() p.P("client *", p.ttrpcPkg.Use(), ".Client") p.Out() p.P("}") p.P() p.P("func New", clientType, "(client *", p.ttrpcPkg.Use(), ".Client)", serviceName, "{") p.In() p.P("return &", clientStructType, "{") p.In() p.P("client: client,") p.Out() p.P("}") p.Out() p.P("}") p.P() for _, method := range service.Method { p.P() p.P("func (c *", clientStructType, ") ", method.GetName(), "(ctx ", p.contextPkg.Use(), ".Context, ", "req *", p.typeName(method.GetInputType()), ") ", "(*", p.typeName(method.GetOutputType()), ", error) {") p.In() p.P("var resp ", p.typeName(method.GetOutputType())) p.P("if err := c.client.Call(ctx, ", `"`+fullName+`", `, `"`+method.GetName()+`"`, ", req, &resp); err != nil {") p.In() p.P("return nil, err") p.Out() p.P("}") p.P("return &resp, nil") p.Out() p.P("}") } } func (p *ttrpcGenerator) objectNamed(name string) generator.Object { p.Generator.RecordTypeUse(name) return p.Generator.ObjectNamed(name) } func (p *ttrpcGenerator) typeName(str string) string { return p.Generator.TypeName(p.objectNamed(str)) } ttrpc-1.2.4/proto/000077500000000000000000000000001462051616600140245ustar00rootroot00000000000000ttrpc-1.2.4/proto/status.proto000066400000000000000000000000701462051616600164310ustar00rootroot00000000000000syntax = "proto3"; message Status { int32 code = 1; } ttrpc-1.2.4/request.pb.go000066400000000000000000000311501462051616600153000ustar00rootroot00000000000000// Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.28.1 // protoc v3.20.1 // source: github.com/containerd/ttrpc/request.proto package ttrpc import ( status "google.golang.org/genproto/googleapis/rpc/status" 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) ) type Request struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Service string `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"` Method string `protobuf:"bytes,2,opt,name=method,proto3" json:"method,omitempty"` Payload []byte `protobuf:"bytes,3,opt,name=payload,proto3" json:"payload,omitempty"` TimeoutNano int64 `protobuf:"varint,4,opt,name=timeout_nano,json=timeoutNano,proto3" json:"timeout_nano,omitempty"` Metadata []*KeyValue `protobuf:"bytes,5,rep,name=metadata,proto3" json:"metadata,omitempty"` } func (x *Request) Reset() { *x = Request{} if protoimpl.UnsafeEnabled { mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Request) String() string { return protoimpl.X.MessageStringOf(x) } func (*Request) ProtoMessage() {} func (x *Request) ProtoReflect() protoreflect.Message { mi := &file_github_com_containerd_ttrpc_request_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 Request.ProtoReflect.Descriptor instead. func (*Request) Descriptor() ([]byte, []int) { return file_github_com_containerd_ttrpc_request_proto_rawDescGZIP(), []int{0} } func (x *Request) GetService() string { if x != nil { return x.Service } return "" } func (x *Request) GetMethod() string { if x != nil { return x.Method } return "" } func (x *Request) GetPayload() []byte { if x != nil { return x.Payload } return nil } func (x *Request) GetTimeoutNano() int64 { if x != nil { return x.TimeoutNano } return 0 } func (x *Request) GetMetadata() []*KeyValue { if x != nil { return x.Metadata } return nil } type Response struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Status *status.Status `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"` } func (x *Response) Reset() { *x = Response{} if protoimpl.UnsafeEnabled { mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Response) String() string { return protoimpl.X.MessageStringOf(x) } func (*Response) ProtoMessage() {} func (x *Response) ProtoReflect() protoreflect.Message { mi := &file_github_com_containerd_ttrpc_request_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 Response.ProtoReflect.Descriptor instead. func (*Response) Descriptor() ([]byte, []int) { return file_github_com_containerd_ttrpc_request_proto_rawDescGZIP(), []int{1} } func (x *Response) GetStatus() *status.Status { if x != nil { return x.Status } return nil } func (x *Response) GetPayload() []byte { if x != nil { return x.Payload } return nil } type StringList struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields List []string `protobuf:"bytes,1,rep,name=list,proto3" json:"list,omitempty"` } func (x *StringList) Reset() { *x = StringList{} if protoimpl.UnsafeEnabled { mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *StringList) String() string { return protoimpl.X.MessageStringOf(x) } func (*StringList) ProtoMessage() {} func (x *StringList) ProtoReflect() protoreflect.Message { mi := &file_github_com_containerd_ttrpc_request_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 StringList.ProtoReflect.Descriptor instead. func (*StringList) Descriptor() ([]byte, []int) { return file_github_com_containerd_ttrpc_request_proto_rawDescGZIP(), []int{2} } func (x *StringList) GetList() []string { if x != nil { return x.List } return nil } type KeyValue struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` } func (x *KeyValue) Reset() { *x = KeyValue{} if protoimpl.UnsafeEnabled { mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *KeyValue) String() string { return protoimpl.X.MessageStringOf(x) } func (*KeyValue) ProtoMessage() {} func (x *KeyValue) ProtoReflect() protoreflect.Message { mi := &file_github_com_containerd_ttrpc_request_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 KeyValue.ProtoReflect.Descriptor instead. func (*KeyValue) Descriptor() ([]byte, []int) { return file_github_com_containerd_ttrpc_request_proto_rawDescGZIP(), []int{3} } func (x *KeyValue) GetKey() string { if x != nil { return x.Key } return "" } func (x *KeyValue) GetValue() string { if x != nil { return x.Value } return "" } var File_github_com_containerd_ttrpc_request_proto protoreflect.FileDescriptor var file_github_com_containerd_ttrpc_request_proto_rawDesc = []byte{ 0x0a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x74, 0x74, 0x72, 0x70, 0x63, 0x1a, 0x12, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa5, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x6e, 0x61, 0x6e, 0x6f, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x4e, 0x61, 0x6e, 0x6f, 0x12, 0x2b, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x45, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x20, 0x0a, 0x0a, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x22, 0x32, 0x0a, 0x08, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x1d, 0x5a, 0x1b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x74, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_github_com_containerd_ttrpc_request_proto_rawDescOnce sync.Once file_github_com_containerd_ttrpc_request_proto_rawDescData = file_github_com_containerd_ttrpc_request_proto_rawDesc ) func file_github_com_containerd_ttrpc_request_proto_rawDescGZIP() []byte { file_github_com_containerd_ttrpc_request_proto_rawDescOnce.Do(func() { file_github_com_containerd_ttrpc_request_proto_rawDescData = protoimpl.X.CompressGZIP(file_github_com_containerd_ttrpc_request_proto_rawDescData) }) return file_github_com_containerd_ttrpc_request_proto_rawDescData } var file_github_com_containerd_ttrpc_request_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_github_com_containerd_ttrpc_request_proto_goTypes = []interface{}{ (*Request)(nil), // 0: ttrpc.Request (*Response)(nil), // 1: ttrpc.Response (*StringList)(nil), // 2: ttrpc.StringList (*KeyValue)(nil), // 3: ttrpc.KeyValue (*status.Status)(nil), // 4: Status } var file_github_com_containerd_ttrpc_request_proto_depIdxs = []int32{ 3, // 0: ttrpc.Request.metadata:type_name -> ttrpc.KeyValue 4, // 1: ttrpc.Response.status:type_name -> Status 2, // [2:2] is the sub-list for method output_type 2, // [2:2] is the sub-list for method input_type 2, // [2:2] is the sub-list for extension type_name 2, // [2:2] is the sub-list for extension extendee 0, // [0:2] is the sub-list for field type_name } func init() { file_github_com_containerd_ttrpc_request_proto_init() } func file_github_com_containerd_ttrpc_request_proto_init() { if File_github_com_containerd_ttrpc_request_proto != nil { return } if !protoimpl.UnsafeEnabled { file_github_com_containerd_ttrpc_request_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Request); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_github_com_containerd_ttrpc_request_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Response); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_github_com_containerd_ttrpc_request_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*StringList); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_github_com_containerd_ttrpc_request_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*KeyValue); 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_github_com_containerd_ttrpc_request_proto_rawDesc, NumEnums: 0, NumMessages: 4, NumExtensions: 0, NumServices: 0, }, GoTypes: file_github_com_containerd_ttrpc_request_proto_goTypes, DependencyIndexes: file_github_com_containerd_ttrpc_request_proto_depIdxs, MessageInfos: file_github_com_containerd_ttrpc_request_proto_msgTypes, }.Build() File_github_com_containerd_ttrpc_request_proto = out.File file_github_com_containerd_ttrpc_request_proto_rawDesc = nil file_github_com_containerd_ttrpc_request_proto_goTypes = nil file_github_com_containerd_ttrpc_request_proto_depIdxs = nil } ttrpc-1.2.4/request.proto000066400000000000000000000006541462051616600154430ustar00rootroot00000000000000syntax = "proto3"; package ttrpc; import "proto/status.proto"; option go_package = "github.com/containerd/ttrpc"; message Request { string service = 1; string method = 2; bytes payload = 3; int64 timeout_nano = 4; repeated KeyValue metadata = 5; } message Response { Status status = 1; bytes payload = 2; } message StringList { repeated string list = 1; } message KeyValue { string key = 1; string value = 2; } ttrpc-1.2.4/script/000077500000000000000000000000001462051616600141655ustar00rootroot00000000000000ttrpc-1.2.4/script/install-protobuf000077500000000000000000000040031462051616600174140ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright The containerd 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. # # Downloads and installs protobuf # set -eu -o pipefail PROTOBUF_VERSION=3.20.1 GOARCH=$(go env GOARCH) GOOS=$(go env GOOS) PROTOBUF_DIR=$(mktemp -d) case $GOARCH in arm64) wget -O "$PROTOBUF_DIR/protobuf" "https://github.com/protocolbuffers/protobuf/releases/download/v$PROTOBUF_VERSION/protoc-$PROTOBUF_VERSION-linux-aarch64.zip" unzip "$PROTOBUF_DIR/protobuf" -d /usr/local ;; amd64|386) if [ "$GOOS" = windows ]; then wget -O "$PROTOBUF_DIR/protobuf" "https://github.com/protocolbuffers/protobuf/releases/download/v$PROTOBUF_VERSION/protoc-$PROTOBUF_VERSION-win32.zip" elif [ "$GOOS" = linux ]; then wget -O "$PROTOBUF_DIR/protobuf" "https://github.com/protocolbuffers/protobuf/releases/download/v$PROTOBUF_VERSION/protoc-$PROTOBUF_VERSION-linux-x86_64.zip" fi unzip "$PROTOBUF_DIR/protobuf" -d /usr/local ;; ppc64le) wget -O "$PROTOBUF_DIR/protobuf" "https://github.com/protocolbuffers/protobuf/releases/download/v$PROTOBUF_VERSION/protoc-$PROTOBUF_VERSION-linux-ppcle_64.zip" unzip "$PROTOBUF_DIR/protobuf" -d /usr/local ;; *) wget -O "$PROTOBUF_DIR/protobuf" "https://github.com/protocolbuffers/protobuf/releases/download/v$PROTOBUF_VERSION/protobuf-cpp-$PROTOBUF_VERSION.zip" unzip "$PROTOBUF_DIR/protobuf" -d /usr/src/protobuf cd "/usr/src/protobuf/protobuf-$PROTOBUF_VERSION" ./autogen.sh ./configure --disable-shared make make check make install ldconfig ;; esac rm -rf "$PROTOBUF_DIR" ttrpc-1.2.4/server.go000066400000000000000000000311571462051616600145250ustar00rootroot00000000000000/* Copyright The containerd 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. */ package ttrpc import ( "context" "errors" "io" "math/rand" "net" "sync" "sync/atomic" "syscall" "time" "github.com/sirupsen/logrus" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) type Server struct { config *serverConfig services *serviceSet codec codec mu sync.Mutex listeners map[net.Listener]struct{} connections map[*serverConn]struct{} // all connections to current state done chan struct{} // marks point at which we stop serving requests } func NewServer(opts ...ServerOpt) (*Server, error) { config := &serverConfig{} for _, opt := range opts { if err := opt(config); err != nil { return nil, err } } if config.interceptor == nil { config.interceptor = defaultServerInterceptor } return &Server{ config: config, services: newServiceSet(config.interceptor), done: make(chan struct{}), listeners: make(map[net.Listener]struct{}), connections: make(map[*serverConn]struct{}), }, nil } // Register registers a map of methods to method handlers // TODO: Remove in 2.0, does not support streams func (s *Server) Register(name string, methods map[string]Method) { s.services.register(name, &ServiceDesc{Methods: methods}) } func (s *Server) RegisterService(name string, desc *ServiceDesc) { s.services.register(name, desc) } func (s *Server) Serve(ctx context.Context, l net.Listener) error { s.addListener(l) defer s.closeListener(l) var ( backoff time.Duration handshaker = s.config.handshaker ) if handshaker == nil { handshaker = handshakerFunc(noopHandshake) } for { conn, err := l.Accept() if err != nil { select { case <-s.done: return ErrServerClosed default: } if terr, ok := err.(interface { Temporary() bool }); ok && terr.Temporary() { if backoff == 0 { backoff = time.Millisecond } else { backoff *= 2 } if max := time.Second; backoff > max { backoff = max } sleep := time.Duration(rand.Int63n(int64(backoff))) logrus.WithError(err).Errorf("ttrpc: failed accept; backoff %v", sleep) time.Sleep(sleep) continue } return err } backoff = 0 approved, handshake, err := handshaker.Handshake(ctx, conn) if err != nil { logrus.WithError(err).Error("ttrpc: refusing connection after handshake") conn.Close() continue } sc, err := s.newConn(approved, handshake) if err != nil { logrus.WithError(err).Error("ttrpc: create connection failed") conn.Close() continue } go sc.run(ctx) } } func (s *Server) Shutdown(ctx context.Context) error { s.mu.Lock() select { case <-s.done: default: // protected by mutex close(s.done) } lnerr := s.closeListeners() s.mu.Unlock() ticker := time.NewTicker(200 * time.Millisecond) defer ticker.Stop() for { s.closeIdleConns() if s.countConnection() == 0 { break } select { case <-ctx.Done(): return ctx.Err() case <-ticker.C: } } return lnerr } // Close the server without waiting for active connections. func (s *Server) Close() error { s.mu.Lock() defer s.mu.Unlock() select { case <-s.done: default: // protected by mutex close(s.done) } err := s.closeListeners() for c := range s.connections { c.close() delete(s.connections, c) } return err } func (s *Server) addListener(l net.Listener) { s.mu.Lock() defer s.mu.Unlock() s.listeners[l] = struct{}{} } func (s *Server) closeListener(l net.Listener) error { s.mu.Lock() defer s.mu.Unlock() return s.closeListenerLocked(l) } func (s *Server) closeListenerLocked(l net.Listener) error { defer delete(s.listeners, l) return l.Close() } func (s *Server) closeListeners() error { var err error for l := range s.listeners { if cerr := s.closeListenerLocked(l); cerr != nil && err == nil { err = cerr } } return err } func (s *Server) addConnection(c *serverConn) error { s.mu.Lock() defer s.mu.Unlock() select { case <-s.done: return ErrServerClosed default: } s.connections[c] = struct{}{} return nil } func (s *Server) delConnection(c *serverConn) { s.mu.Lock() defer s.mu.Unlock() delete(s.connections, c) } func (s *Server) countConnection() int { s.mu.Lock() defer s.mu.Unlock() return len(s.connections) } func (s *Server) closeIdleConns() { s.mu.Lock() defer s.mu.Unlock() for c := range s.connections { if st, ok := c.getState(); !ok || st == connStateActive { continue } c.close() delete(s.connections, c) } } type connState int const ( connStateActive = iota + 1 // outstanding requests connStateIdle // no requests connStateClosed // closed connection ) func (cs connState) String() string { switch cs { case connStateActive: return "active" case connStateIdle: return "idle" case connStateClosed: return "closed" default: return "unknown" } } func (s *Server) newConn(conn net.Conn, handshake interface{}) (*serverConn, error) { c := &serverConn{ server: s, conn: conn, handshake: handshake, shutdown: make(chan struct{}), } c.setState(connStateIdle) if err := s.addConnection(c); err != nil { c.close() return nil, err } return c, nil } type serverConn struct { server *Server conn net.Conn handshake interface{} // data from handshake, not used for now state atomic.Value shutdownOnce sync.Once shutdown chan struct{} // forced shutdown, used by close } func (c *serverConn) getState() (connState, bool) { cs, ok := c.state.Load().(connState) return cs, ok } func (c *serverConn) setState(newstate connState) { c.state.Store(newstate) } func (c *serverConn) close() error { c.shutdownOnce.Do(func() { close(c.shutdown) }) return nil } func (c *serverConn) run(sctx context.Context) { type ( response struct { id uint32 status *status.Status data []byte closeStream bool streaming bool } ) var ( ch = newChannel(c.conn) ctx, cancel = context.WithCancel(sctx) state connState = connStateIdle responses = make(chan response) recvErr = make(chan error, 1) done = make(chan struct{}) streams = sync.Map{} active int32 lastStreamID uint32 ) defer c.conn.Close() defer cancel() defer close(done) defer c.server.delConnection(c) sendStatus := func(id uint32, st *status.Status) bool { select { case responses <- response{ // even though we've had an invalid stream id, we send it // back on the same stream id so the client knows which // stream id was bad. id: id, status: st, closeStream: true, }: return true case <-c.shutdown: return false case <-done: return false } } go func(recvErr chan error) { defer close(recvErr) for { select { case <-c.shutdown: return case <-done: return default: // proceed } mh, p, err := ch.recv() if err != nil { status, ok := status.FromError(err) if !ok { recvErr <- err return } // in this case, we send an error for that particular message // when the status is defined. if !sendStatus(mh.StreamID, status) { return } continue } if mh.StreamID%2 != 1 { // enforce odd client initiated identifiers. if !sendStatus(mh.StreamID, status.Newf(codes.InvalidArgument, "StreamID must be odd for client initiated streams")) { return } continue } if mh.Type == messageTypeData { i, ok := streams.Load(mh.StreamID) if !ok { if !sendStatus(mh.StreamID, status.Newf(codes.InvalidArgument, "StreamID is no longer active")) { return } } sh := i.(*streamHandler) if mh.Flags&flagNoData != flagNoData { unmarshal := func(obj interface{}) error { err := protoUnmarshal(p, obj) ch.putmbuf(p) return err } if err := sh.data(unmarshal); err != nil { if !sendStatus(mh.StreamID, status.Newf(codes.InvalidArgument, "data handling error: %v", err)) { return } } } if mh.Flags&flagRemoteClosed == flagRemoteClosed { sh.closeSend() if len(p) > 0 { if !sendStatus(mh.StreamID, status.Newf(codes.InvalidArgument, "data close message cannot include data")) { return } } } } else if mh.Type == messageTypeRequest { if mh.StreamID <= lastStreamID { // enforce odd client initiated identifiers. if !sendStatus(mh.StreamID, status.Newf(codes.InvalidArgument, "StreamID cannot be re-used and must increment")) { return } continue } lastStreamID = mh.StreamID // TODO: Make request type configurable // Unmarshaller which takes in a byte array and returns an interface? var req Request if err := c.server.codec.Unmarshal(p, &req); err != nil { ch.putmbuf(p) if !sendStatus(mh.StreamID, status.Newf(codes.InvalidArgument, "unmarshal request error: %v", err)) { return } continue } ch.putmbuf(p) id := mh.StreamID respond := func(status *status.Status, data []byte, streaming, closeStream bool) error { select { case responses <- response{ id: id, status: status, data: data, closeStream: closeStream, streaming: streaming, }: case <-done: return ErrClosed } return nil } sh, err := c.server.services.handle(ctx, &req, respond) if err != nil { status, _ := status.FromError(err) if !sendStatus(mh.StreamID, status) { return } continue } streams.Store(id, sh) atomic.AddInt32(&active, 1) } // TODO: else we must ignore this for future compat. log this? } }(recvErr) for { var ( newstate connState shutdown chan struct{} ) activeN := atomic.LoadInt32(&active) if activeN > 0 { newstate = connStateActive shutdown = nil } else { newstate = connStateIdle shutdown = c.shutdown // only enable this branch in idle mode } if newstate != state { c.setState(newstate) state = newstate } select { case response := <-responses: if !response.streaming || response.status.Code() != codes.OK { p, err := c.server.codec.Marshal(&Response{ Status: response.status.Proto(), Payload: response.data, }) if err != nil { logrus.WithError(err).Error("failed marshaling response") return } if err := ch.send(response.id, messageTypeResponse, 0, p); err != nil { logrus.WithError(err).Error("failed sending message on channel") return } } else { var flags uint8 if response.closeStream { flags = flagRemoteClosed } if response.data == nil { flags = flags | flagNoData } if err := ch.send(response.id, messageTypeData, flags, response.data); err != nil { logrus.WithError(err).Error("failed sending message on channel") return } } if response.closeStream { // The ttrpc protocol currently does not support the case where // the server is localClosed but not remoteClosed. Once the server // is closing, the whole stream may be considered finished streams.Delete(response.id) atomic.AddInt32(&active, -1) } case err := <-recvErr: // TODO(stevvooe): Not wildly clear what we should do in this // branch. Basically, it means that we are no longer receiving // requests due to a terminal error. recvErr = nil // connection is now "closing" if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, syscall.ECONNRESET) { // The client went away and we should stop processing // requests, so that the client connection is closed return } logrus.WithError(err).Error("error receiving message") // else, initiate shutdown case <-shutdown: return } } } var noopFunc = func() {} func getRequestContext(ctx context.Context, req *Request) (retCtx context.Context, cancel func()) { if len(req.Metadata) > 0 { md := MD{} md.fromRequest(req) ctx = WithMetadata(ctx, md) } cancel = noopFunc if req.TimeoutNano == 0 { return ctx, cancel } ctx, cancel = context.WithTimeout(ctx, time.Duration(req.TimeoutNano)) return ctx, cancel } ttrpc-1.2.4/server_linux_test.go000066400000000000000000000072751462051616600170070ustar00rootroot00000000000000/* Copyright The containerd 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. */ package ttrpc import ( "context" "strings" "testing" "time" "github.com/containerd/ttrpc/internal" "github.com/prometheus/procfs" ) func TestUnixSocketHandshake(t *testing.T) { var ( ctx = context.Background() server = mustServer(t)(NewServer(WithServerHandshaker(UnixSocketRequireSameUser()))) addr, listener = newTestListener(t) errs = make(chan error, 1) client, cleanup = newTestClient(t, addr) ) defer cleanup() defer listener.Close() go func() { errs <- server.Serve(ctx, listener) }() registerTestingService(server, &testingServer{}) var tp internal.TestPayload // server shutdown, but we still make a call. if err := client.Call(ctx, serviceName, "Test", &tp, &tp); err != nil { t.Fatalf("unexpected error making call: %v", err) } } func BenchmarkRoundTripUnixSocketCreds(b *testing.B) { // TODO(stevvooe): Right now, there is a 5x performance decrease when using // unix socket credentials. See (UnixCredentialsFunc).Handshake for // details. var ( ctx = context.Background() server = mustServer(b)(NewServer(WithServerHandshaker(UnixSocketRequireSameUser()))) testImpl = &testingServer{} addr, listener = newTestListener(b) client, cleanup = newTestClient(b, addr) tclient = newTestingClient(client) ) defer listener.Close() defer cleanup() registerTestingService(server, testImpl) go server.Serve(ctx, listener) defer server.Shutdown(ctx) var tp internal.TestPayload b.ResetTimer() for i := 0; i < b.N; i++ { if _, err := tclient.Test(ctx, &tp); err != nil { b.Fatal(err) } } } func TestServerEOF(t *testing.T) { var ( ctx = context.Background() server = mustServer(t)(NewServer()) addr, listener = newTestListener(t) client, cleanup = newTestClient(t, addr) ) defer cleanup() defer listener.Close() socketCountBefore := socketCount(t) go server.Serve(ctx, listener) registerTestingService(server, &testingServer{}) tp := &internal.TestPayload{} // do a regular call if err := client.Call(ctx, serviceName, "Test", tp, tp); err != nil { t.Fatalf("unexpected error during test call: %v", err) } // close the client, so that server gets EOF if err := client.Close(); err != nil { t.Fatalf("unexpected error while closing client: %v", err) } // server should eventually close the client connection maxAttempts := 20 for i := 1; i <= maxAttempts; i++ { socketCountAfter := socketCount(t) if socketCountAfter < socketCountBefore { break } if i == maxAttempts { t.Fatalf("expected number of open sockets to be less than %d after client close, got %d open sockets", socketCountBefore, socketCountAfter) } time.Sleep(100 * time.Millisecond) } } func socketCount(t *testing.T) int { proc, err := procfs.Self() if err != nil { t.Fatalf("unexpected error while reading procfs: %v", err) } fds, err := proc.FileDescriptorTargets() if err != nil { t.Fatalf("unexpected error while listing open file descriptors: %v", err) } sockets := 0 for _, fd := range fds { if strings.Contains(fd, "socket") { sockets++ } } return sockets } ttrpc-1.2.4/server_test.go000066400000000000000000000340041462051616600155560ustar00rootroot00000000000000/* Copyright The containerd 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. */ package ttrpc import ( "bytes" "context" "errors" "fmt" "net" "runtime" "strings" "sync" "syscall" "testing" "time" "github.com/containerd/ttrpc/internal" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" ) const serviceName = "testService" // testingService is our prototype service definition for use in testing the full model. // // Typically, this is generated. We define it here to ensure that that package // primitive has what is required for generated code. type testingService interface { Test(ctx context.Context, req *internal.TestPayload) (*internal.TestPayload, error) } type testingClient struct { client *Client } func newTestingClient(client *Client) *testingClient { return &testingClient{ client: client, } } func (tc *testingClient) Test(ctx context.Context, req *internal.TestPayload) (*internal.TestPayload, error) { var tp internal.TestPayload return &tp, tc.client.Call(ctx, serviceName, "Test", req, &tp) } // testingServer is what would be implemented by the user of this package. type testingServer struct{} func (s *testingServer) Test(ctx context.Context, req *internal.TestPayload) (*internal.TestPayload, error) { tp := &internal.TestPayload{Foo: strings.Repeat(req.Foo, 2)} if dl, ok := ctx.Deadline(); ok { tp.Deadline = dl.UnixNano() } if v, ok := GetMetadataValue(ctx, "foo"); ok { tp.Metadata = v } return tp, nil } // registerTestingService mocks more of what is generated code. Unlike grpc, we // register with a closure so that the descriptor is allocated only on // registration. func registerTestingService(srv *Server, svc testingService) { srv.Register(serviceName, map[string]Method{ "Test": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { var req internal.TestPayload if err := unmarshal(&req); err != nil { return nil, err } return svc.Test(ctx, &req) }, }) } func protoEqual(a, b proto.Message) (bool, error) { ma, err := proto.Marshal(a) if err != nil { return false, err } mb, err := proto.Marshal(b) if err != nil { return false, err } return bytes.Equal(ma, mb), nil } func TestServer(t *testing.T) { var ( ctx = context.Background() server = mustServer(t)(NewServer()) testImpl = &testingServer{} addr, listener = newTestListener(t) client, cleanup = newTestClient(t, addr) tclient = newTestingClient(client) ) defer listener.Close() defer cleanup() registerTestingService(server, testImpl) go server.Serve(ctx, listener) defer server.Shutdown(ctx) testCases := []string{"bar", "baz"} results := make(chan callResult, len(testCases)) for _, tc := range testCases { go func(expected string) { results <- roundTrip(ctx, tclient, expected) }(tc) } for i := 0; i < len(testCases); { result := <-results if result.err != nil { t.Fatalf("(%s): %v", result.name, result.err) } equal, err := protoEqual(result.received, result.expected) if err != nil { t.Fatalf("failed to compare %s and %s: %s", result.received, result.expected, err) } if !equal { t.Fatalf("unexpected response: %+#v != %+#v", result.received, result.expected) } i++ } } func TestServerUnimplemented(t *testing.T) { var ( ctx = context.Background() server = mustServer(t)(NewServer()) addr, listener = newTestListener(t) errs = make(chan error, 1) client, cleanup = newTestClient(t, addr) ) defer cleanup() defer listener.Close() go func() { errs <- server.Serve(ctx, listener) }() var tp internal.TestPayload if err := client.Call(ctx, "Not", "Found", &tp, &tp); err == nil { t.Fatalf("expected error from non-existent service call") } else if status, ok := status.FromError(err); !ok { t.Fatalf("expected status present in error: %v", err) } else if status.Code() != codes.Unimplemented { t.Fatalf("expected not found for method") } if err := server.Shutdown(ctx); err != nil { t.Fatal(err) } if err := <-errs; err != ErrServerClosed { t.Fatal(err) } } func TestServerListenerClosed(t *testing.T) { var ( ctx = context.Background() server = mustServer(t)(NewServer()) _, listener = newTestListener(t) errs = make(chan error, 1) ) go func() { errs <- server.Serve(ctx, listener) }() if err := listener.Close(); err != nil { t.Fatal(err) } err := <-errs if err == nil { t.Fatal(err) } } func TestServerShutdown(t *testing.T) { const ncalls = 5 var ( ctx = context.Background() server = mustServer(t)(NewServer()) addr, listener = newTestListener(t) shutdownStarted = make(chan struct{}) shutdownFinished = make(chan struct{}) handlersStarted sync.WaitGroup proceed = make(chan struct{}) serveErrs = make(chan error, 1) callErrs = make(chan error, ncalls) shutdownErrs = make(chan error, 1) client, cleanup = newTestClient(t, addr) _, cleanup2 = newTestClient(t, addr) // secondary connection ) defer cleanup() defer cleanup2() // register a service that takes until we tell it to stop server.Register(serviceName, map[string]Method{ "Test": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { var req internal.TestPayload if err := unmarshal(&req); err != nil { return nil, err } handlersStarted.Done() <-proceed return &internal.TestPayload{Foo: "waited"}, nil }, }) go func() { serveErrs <- server.Serve(ctx, listener) }() // send a series of requests that will get blocked for i := 0; i < ncalls; i++ { handlersStarted.Add(1) go func(i int) { tp := internal.TestPayload{Foo: "half" + fmt.Sprint(i)} callErrs <- client.Call(ctx, serviceName, "Test", &tp, &tp) }(i) } handlersStarted.Wait() go func() { close(shutdownStarted) shutdownErrs <- server.Shutdown(ctx) close(shutdownFinished) }() <-shutdownStarted close(proceed) <-shutdownFinished for i := 0; i < ncalls; i++ { if err := <-callErrs; err != nil && err != ErrClosed { t.Fatal(err) } } if err := <-shutdownErrs; err != nil { t.Fatal(err) } if err := <-serveErrs; err != ErrServerClosed { t.Fatal(err) } checkServerShutdown(t, server) } func TestServerClose(t *testing.T) { var ( ctx = context.Background() server = mustServer(t)(NewServer()) _, listener = newTestListener(t) startClose = make(chan struct{}) errs = make(chan error, 1) ) go func() { close(startClose) errs <- server.Serve(ctx, listener) }() <-startClose if err := server.Close(); err != nil { t.Fatal(err) } err := <-errs if err != ErrServerClosed { t.Fatal("expected an error from a closed server", err) } checkServerShutdown(t, server) } func TestOversizeCall(t *testing.T) { var ( ctx = context.Background() server = mustServer(t)(NewServer()) addr, listener = newTestListener(t) errs = make(chan error, 1) client, cleanup = newTestClient(t, addr) ) defer cleanup() defer listener.Close() go func() { errs <- server.Serve(ctx, listener) }() registerTestingService(server, &testingServer{}) tp := &internal.TestPayload{ Foo: strings.Repeat("a", 1+messageLengthMax), } if err := client.Call(ctx, serviceName, "Test", tp, tp); err == nil { t.Fatalf("expected error from non-existent service call") } else if status, ok := status.FromError(err); !ok { t.Fatalf("expected status present in error: %v", err) } else if status.Code() != codes.ResourceExhausted { t.Fatalf("expected code: %v != %v", status.Code(), codes.ResourceExhausted) } if err := server.Shutdown(ctx); err != nil { t.Fatal(err) } if err := <-errs; err != ErrServerClosed { t.Fatal(err) } } func TestClientEOF(t *testing.T) { var ( ctx = context.Background() server = mustServer(t)(NewServer()) addr, listener = newTestListener(t) errs = make(chan error, 1) client, cleanup = newTestClient(t, addr) ) defer cleanup() defer listener.Close() go func() { errs <- server.Serve(ctx, listener) }() registerTestingService(server, &testingServer{}) tp := &internal.TestPayload{} // do a regular call if err := client.Call(ctx, serviceName, "Test", tp, tp); err != nil { t.Fatalf("unexpected error: %v", err) } // shutdown the server so the client stops receiving stuff. if err := server.Close(); err != nil { t.Fatal(err) } if err := <-errs; err != ErrServerClosed { t.Fatal(err) } client.UserOnCloseWait(ctx) // server shutdown, but we still make a call. if err := client.Call(ctx, serviceName, "Test", tp, tp); err == nil { t.Fatalf("expected error when calling against shutdown server") } else if !errors.Is(err, ErrClosed) { var errno syscall.Errno if errors.As(err, &errno) { t.Logf("errno=%d", errno) } t.Fatalf("expected to have a cause of ErrClosed, got %v", err) } } func TestServerRequestTimeout(t *testing.T) { var ( ctx, cancel = context.WithDeadline(context.Background(), time.Now().Add(10*time.Minute)) server = mustServer(t)(NewServer()) addr, listener = newTestListener(t) testImpl = &testingServer{} client, cleanup = newTestClient(t, addr) result internal.TestPayload ) defer cancel() defer cleanup() defer listener.Close() registerTestingService(server, testImpl) go server.Serve(ctx, listener) defer server.Shutdown(ctx) if err := client.Call(ctx, serviceName, "Test", &internal.TestPayload{}, &result); err != nil { t.Fatalf("unexpected error making call: %v", err) } dl, _ := ctx.Deadline() if result.Deadline != dl.UnixNano() { t.Fatalf("expected deadline %v, actual: %v", dl, time.Unix(0, result.Deadline)) } } func TestServerConnectionsLeak(t *testing.T) { var ( ctx = context.Background() server = mustServer(t)(NewServer()) addr, listener = newTestListener(t) client, cleanup = newTestClient(t, addr) ) defer cleanup() defer listener.Close() connectionCountBefore := server.countConnection() go server.Serve(ctx, listener) registerTestingService(server, &testingServer{}) tp := &internal.TestPayload{} // do a regular call if err := client.Call(ctx, serviceName, "Test", tp, tp); err != nil { t.Fatalf("unexpected error during test call: %v", err) } connectionCount := server.countConnection() if connectionCount != 1 { t.Fatalf("unexpected connection count: %d, expected: %d", connectionCount, 1) } // close the client, so that server gets EOF if err := client.Close(); err != nil { t.Fatalf("unexpected error while closing client: %v", err) } // server should eventually close the client connection maxAttempts := 20 for i := 1; i <= maxAttempts; i++ { connectionCountAfter := server.countConnection() if connectionCountAfter == connectionCountBefore { break } if i == maxAttempts { t.Fatalf("expected number of connections to be equal %d after client close, got %d connections", connectionCountBefore, connectionCountAfter) } time.Sleep(100 * time.Millisecond) } } func BenchmarkRoundTrip(b *testing.B) { var ( ctx = context.Background() server = mustServer(b)(NewServer()) testImpl = &testingServer{} addr, listener = newTestListener(b) client, cleanup = newTestClient(b, addr) tclient = newTestingClient(client) ) defer listener.Close() defer cleanup() registerTestingService(server, testImpl) go server.Serve(ctx, listener) defer server.Shutdown(ctx) var tp internal.TestPayload b.ResetTimer() for i := 0; i < b.N; i++ { if _, err := tclient.Test(ctx, &tp); err != nil { b.Fatal(err) } } } func checkServerShutdown(t *testing.T, server *Server) { t.Helper() server.mu.Lock() defer server.mu.Unlock() if len(server.listeners) > 0 { t.Errorf("expected listeners to be empty: %v", server.listeners) } for listener := range server.listeners { t.Logf("listener addr=%s", listener.Addr()) } if len(server.connections) > 0 { t.Errorf("expected connections to be empty: %v", server.connections) } for conn := range server.connections { state, ok := conn.getState() if !ok { t.Errorf("failed to get state from %v", conn) } t.Logf("conn state=%s", state) } } type callResult struct { name string err error input *internal.TestPayload expected *internal.TestPayload received *internal.TestPayload } func roundTrip(ctx context.Context, client *testingClient, name string) callResult { var ( tp = &internal.TestPayload{ Foo: name, } ) ctx = WithMetadata(ctx, MD{"foo": []string{name}}) resp, err := client.Test(ctx, tp) if err != nil { return callResult{ name: name, err: err, } } return callResult{ name: name, input: tp, expected: &internal.TestPayload{Foo: strings.Repeat(tp.Foo, 2), Metadata: name}, received: resp, } } func newTestClient(t testing.TB, addr string, opts ...ClientOpts) (*Client, func()) { conn, err := net.Dial("unix", addr) if err != nil { t.Fatal(err) } client := NewClient(conn, opts...) return client, func() { conn.Close() client.Close() } } func newTestListener(t testing.TB) (string, net.Listener) { var prefix string // Abstracts sockets are only available on Linux. if runtime.GOOS == "linux" { prefix = "\x00" } addr := prefix + t.Name() listener, err := net.Listen("unix", addr) if err != nil { t.Fatal(err) } return addr, listener } func mustServer(t testing.TB) func(server *Server, err error) *Server { return func(server *Server, err error) *Server { t.Helper() if err != nil { t.Fatal(err) } return server } } ttrpc-1.2.4/services.go000066400000000000000000000154241462051616600150410ustar00rootroot00000000000000/* Copyright The containerd 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. */ package ttrpc import ( "context" "errors" "fmt" "io" "os" "path" "unsafe" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" ) type Method func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) type StreamHandler func(context.Context, StreamServer) (interface{}, error) type Stream struct { Handler StreamHandler StreamingClient bool StreamingServer bool } type ServiceDesc struct { Methods map[string]Method Streams map[string]Stream } type serviceSet struct { services map[string]*ServiceDesc unaryInterceptor UnaryServerInterceptor streamInterceptor StreamServerInterceptor } func newServiceSet(interceptor UnaryServerInterceptor) *serviceSet { return &serviceSet{ services: make(map[string]*ServiceDesc), unaryInterceptor: interceptor, streamInterceptor: defaultStreamServerInterceptor, } } func (s *serviceSet) register(name string, desc *ServiceDesc) { if _, ok := s.services[name]; ok { panic(fmt.Errorf("duplicate service %v registered", name)) } s.services[name] = desc } func (s *serviceSet) unaryCall(ctx context.Context, method Method, info *UnaryServerInfo, data []byte) (p []byte, st *status.Status) { unmarshal := func(obj interface{}) error { return protoUnmarshal(data, obj) } resp, err := s.unaryInterceptor(ctx, unmarshal, info, method) if err == nil { if isNil(resp) { err = errors.New("ttrpc: marshal called with nil") } else { p, err = protoMarshal(resp) } } st, ok := status.FromError(err) if !ok { st = status.New(convertCode(err), err.Error()) } return p, st } func (s *serviceSet) streamCall(ctx context.Context, stream StreamHandler, info *StreamServerInfo, ss StreamServer) (p []byte, st *status.Status) { resp, err := s.streamInterceptor(ctx, ss, info, stream) if err == nil { p, err = protoMarshal(resp) } st, ok := status.FromError(err) if !ok { st = status.New(convertCode(err), err.Error()) } return } func (s *serviceSet) handle(ctx context.Context, req *Request, respond func(*status.Status, []byte, bool, bool) error) (*streamHandler, error) { srv, ok := s.services[req.Service] if !ok { return nil, status.Errorf(codes.Unimplemented, "service %v", req.Service) } if method, ok := srv.Methods[req.Method]; ok { go func() { ctx, cancel := getRequestContext(ctx, req) defer cancel() info := &UnaryServerInfo{ FullMethod: fullPath(req.Service, req.Method), } p, st := s.unaryCall(ctx, method, info, req.Payload) respond(st, p, false, true) }() return nil, nil } if stream, ok := srv.Streams[req.Method]; ok { ctx, cancel := getRequestContext(ctx, req) info := &StreamServerInfo{ FullMethod: fullPath(req.Service, req.Method), StreamingClient: stream.StreamingClient, StreamingServer: stream.StreamingServer, } sh := &streamHandler{ ctx: ctx, respond: respond, recv: make(chan Unmarshaler, 5), info: info, } go func() { defer cancel() p, st := s.streamCall(ctx, stream.Handler, info, sh) respond(st, p, stream.StreamingServer, true) }() // Empty proto messages serialized to 0 payloads, // so signatures like: rpc Stream(google.protobuf.Empty) returns (stream Data); // don't get invoked here, which causes hang on client side. // See https://github.com/containerd/ttrpc/issues/126 if req.Payload != nil || !info.StreamingClient { unmarshal := func(obj interface{}) error { return protoUnmarshal(req.Payload, obj) } if err := sh.data(unmarshal); err != nil { return nil, err } } return sh, nil } return nil, status.Errorf(codes.Unimplemented, "method %v", req.Method) } type streamHandler struct { ctx context.Context respond func(*status.Status, []byte, bool, bool) error recv chan Unmarshaler info *StreamServerInfo remoteClosed bool localClosed bool } func (s *streamHandler) closeSend() { if !s.remoteClosed { s.remoteClosed = true close(s.recv) } } func (s *streamHandler) data(unmarshal Unmarshaler) error { if s.remoteClosed { return ErrStreamClosed } select { case s.recv <- unmarshal: return nil case <-s.ctx.Done(): return s.ctx.Err() } } func (s *streamHandler) SendMsg(m interface{}) error { if s.localClosed { return ErrStreamClosed } p, err := protoMarshal(m) if err != nil { return err } return s.respond(nil, p, true, false) } func (s *streamHandler) RecvMsg(m interface{}) error { select { case unmarshal, ok := <-s.recv: if !ok { return io.EOF } return unmarshal(m) case <-s.ctx.Done(): return s.ctx.Err() } } func protoUnmarshal(p []byte, obj interface{}) error { switch v := obj.(type) { case proto.Message: if err := proto.Unmarshal(p, v); err != nil { return status.Errorf(codes.Internal, "ttrpc: error unmarshalling payload: %v", err.Error()) } default: return status.Errorf(codes.Internal, "ttrpc: error unsupported request type: %T", v) } return nil } func protoMarshal(obj interface{}) ([]byte, error) { if obj == nil { return nil, nil } switch v := obj.(type) { case proto.Message: r, err := proto.Marshal(v) if err != nil { return nil, status.Errorf(codes.Internal, "ttrpc: error marshaling payload: %v", err.Error()) } return r, nil default: return nil, status.Errorf(codes.Internal, "ttrpc: error unsupported response type: %T", v) } } // convertCode maps stdlib go errors into grpc space. // // This is ripped from the grpc-go code base. func convertCode(err error) codes.Code { switch err { case nil: return codes.OK case io.EOF: return codes.OutOfRange case io.ErrClosedPipe, io.ErrNoProgress, io.ErrShortBuffer, io.ErrShortWrite, io.ErrUnexpectedEOF: return codes.FailedPrecondition case os.ErrInvalid: return codes.InvalidArgument case context.Canceled: return codes.Canceled case context.DeadlineExceeded: return codes.DeadlineExceeded } switch { case os.IsExist(err): return codes.AlreadyExists case os.IsNotExist(err): return codes.NotFound case os.IsPermission(err): return codes.PermissionDenied } return codes.Unknown } func fullPath(service, method string) string { return "/" + path.Join(service, method) } func isNil(resp interface{}) bool { return (*[2]uintptr)(unsafe.Pointer(&resp))[1] == 0 } ttrpc-1.2.4/services_test.go000066400000000000000000000015711462051616600160760ustar00rootroot00000000000000/* Copyright The containerd 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. */ package ttrpc import ( "testing" ) func Test_MethodFullNameGeneration(t *testing.T) { name := fullPath("test.v1.service", "Foo") expectedName := "/test.v1.service/Foo" if name != expectedName { t.Fatalf("Service name does not match. Expected: %q, Actual: %q", expectedName, name) } } ttrpc-1.2.4/stream.go000066400000000000000000000032741462051616600145110ustar00rootroot00000000000000/* Copyright The containerd 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. */ package ttrpc import ( "context" "sync" ) type streamID uint32 type streamMessage struct { header messageHeader payload []byte } type stream struct { id streamID sender sender recv chan *streamMessage closeOnce sync.Once recvErr error recvClose chan struct{} } func newStream(id streamID, send sender) *stream { return &stream{ id: id, sender: send, recv: make(chan *streamMessage, 1), recvClose: make(chan struct{}), } } func (s *stream) closeWithError(err error) error { s.closeOnce.Do(func() { if err != nil { s.recvErr = err } else { s.recvErr = ErrClosed } close(s.recvClose) }) return nil } func (s *stream) send(mt messageType, flags uint8, b []byte) error { return s.sender.send(uint32(s.id), mt, flags, b) } func (s *stream) receive(ctx context.Context, msg *streamMessage) error { select { case <-s.recvClose: return s.recvErr default: } select { case <-s.recvClose: return s.recvErr case s.recv <- msg: return nil case <-ctx.Done(): return ctx.Err() } } type sender interface { send(uint32, messageType, uint8, []byte) error } ttrpc-1.2.4/stream_server.go000066400000000000000000000012771462051616600161000ustar00rootroot00000000000000/* Copyright The containerd 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. */ package ttrpc type StreamServer interface { SendMsg(m interface{}) error RecvMsg(m interface{}) error } ttrpc-1.2.4/stream_test.go000066400000000000000000000057231462051616600155510ustar00rootroot00000000000000/* Copyright The containerd 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. */ package ttrpc import ( "context" "io" "testing" "github.com/containerd/ttrpc/internal" ) func TestStreamClient(t *testing.T) { var ( ctx = context.Background() server = mustServer(t)(NewServer()) addr, listener = newTestListener(t) client, cleanup = newTestClient(t, addr) serviceName = "streamService" ) defer listener.Close() defer cleanup() desc := &ServiceDesc{ Methods: map[string]Method{ "Echo": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { var req internal.EchoPayload if err := unmarshal(&req); err != nil { return nil, err } req.Seq++ return &req, nil }, }, Streams: map[string]Stream{ "EchoStream": { Handler: func(ctx context.Context, ss StreamServer) (interface{}, error) { for { var req internal.EchoPayload if err := ss.RecvMsg(&req); err != nil { if err == io.EOF { err = nil } return nil, err } req.Seq++ if err := ss.SendMsg(&req); err != nil { return nil, err } } }, StreamingClient: true, StreamingServer: true, }, }, } server.RegisterService(serviceName, desc) go server.Serve(ctx, listener) defer server.Shutdown(ctx) //func (c *Client) NewStream(ctx context.Context, desc *StreamDesc, service, method string) (ClientStream, error) { var req, resp internal.EchoPayload if err := client.Call(ctx, serviceName, "Echo", &req, &resp); err != nil { t.Fatal(err) } stream, err := client.NewStream(ctx, &StreamDesc{true, true}, serviceName, "EchoStream", nil) if err != nil { t.Fatal(err) } for i := 1; i <= 100; i++ { req := internal.EchoPayload{ Seq: int64(i), Msg: "should be returned", } if err := stream.SendMsg(&req); err != nil { t.Fatalf("%d: %v", i, err) } var resp internal.EchoPayload if err := stream.RecvMsg(&resp); err != nil { t.Fatalf("%d: %v", i, err) } if resp.Seq != int64(i)+1 { t.Fatalf("%d: unexpected sequence value: %d, expected %d", i, resp.Seq, i+1) } if resp.Msg != req.Msg { t.Fatalf("%d: unexpected message: %q, expected %q", i, resp.Msg, req.Msg) } } if err := stream.CloseSend(); err != nil { t.Fatal(err) } err = stream.RecvMsg(&resp) if err == nil { t.Fatal("expected io.EOF after close send") } if err != io.EOF { t.Fatalf("expected io.EOF after close send, got %v", err) } } ttrpc-1.2.4/test.proto000066400000000000000000000003571462051616600147320ustar00rootroot00000000000000syntax = "proto3"; package ttrpc; option go_package = "github.com/containerd/ttrpc/internal"; message TestPayload { string foo = 1; int64 deadline = 2; string metadata = 3; } message EchoPayload { int64 seq = 1; string msg = 2; } ttrpc-1.2.4/unixcreds_linux.go000066400000000000000000000065241462051616600164420ustar00rootroot00000000000000/* Copyright The containerd 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. */ package ttrpc import ( "context" "errors" "fmt" "net" "os" "syscall" "golang.org/x/sys/unix" ) type UnixCredentialsFunc func(*unix.Ucred) error func (fn UnixCredentialsFunc) Handshake(_ context.Context, conn net.Conn) (net.Conn, interface{}, error) { uc, err := requireUnixSocket(conn) if err != nil { return nil, nil, fmt.Errorf("ttrpc.UnixCredentialsFunc: require unix socket: %w", err) } rs, err := uc.SyscallConn() if err != nil { return nil, nil, fmt.Errorf("ttrpc.UnixCredentialsFunc: (net.UnixConn).SyscallConn failed: %w", err) } var ( ucred *unix.Ucred ucredErr error ) if err := rs.Control(func(fd uintptr) { ucred, ucredErr = unix.GetsockoptUcred(int(fd), unix.SOL_SOCKET, unix.SO_PEERCRED) }); err != nil { return nil, nil, fmt.Errorf("ttrpc.UnixCredentialsFunc: (*syscall.RawConn).Control failed: %w", err) } if ucredErr != nil { return nil, nil, fmt.Errorf("ttrpc.UnixCredentialsFunc: failed to retrieve socket peer credentials: %w", ucredErr) } if err := fn(ucred); err != nil { return nil, nil, fmt.Errorf("ttrpc.UnixCredentialsFunc: credential check failed: %w", err) } return uc, ucred, nil } // UnixSocketRequireUidGid requires specific *effective* UID/GID, rather than the real UID/GID. // // For example, if a daemon binary is owned by the root (UID 0) with SUID bit but running as an // unprivileged user (UID 1001), the effective UID becomes 0, and the real UID becomes 1001. // So calling this function with uid=0 allows a connection from effective UID 0 but rejects // a connection from effective UID 1001. // // See socket(7), SO_PEERCRED: "The returned credentials are those that were in effect at the time of the call to connect(2) or socketpair(2)." func UnixSocketRequireUidGid(uid, gid int) UnixCredentialsFunc { return func(ucred *unix.Ucred) error { return requireUidGid(ucred, uid, gid) } } func UnixSocketRequireRoot() UnixCredentialsFunc { return UnixSocketRequireUidGid(0, 0) } // UnixSocketRequireSameUser resolves the current effective unix user and returns a // UnixCredentialsFunc that will validate incoming unix connections against the // current credentials. // // This is useful when using abstract sockets that are accessible by all users. func UnixSocketRequireSameUser() UnixCredentialsFunc { euid, egid := os.Geteuid(), os.Getegid() return UnixSocketRequireUidGid(euid, egid) } func requireUidGid(ucred *unix.Ucred, uid, gid int) error { if (uid != -1 && uint32(uid) != ucred.Uid) || (gid != -1 && uint32(gid) != ucred.Gid) { return fmt.Errorf("ttrpc: invalid credentials: %v", syscall.EPERM) } return nil } func requireUnixSocket(conn net.Conn) (*net.UnixConn, error) { uc, ok := conn.(*net.UnixConn) if !ok { return nil, errors.New("a unix socket connection is required") } return uc, nil }