pax_global_header00006660000000000000000000000064144312577760014532gustar00rootroot0000000000000052 comment=20c493e60193d417ec0c6e795a4dcb8f25221f7d ttrpc-1.1.2/000077500000000000000000000000001443125777600126675ustar00rootroot00000000000000ttrpc-1.1.2/.github/000077500000000000000000000000001443125777600142275ustar00rootroot00000000000000ttrpc-1.1.2/.github/workflows/000077500000000000000000000000001443125777600162645ustar00rootroot00000000000000ttrpc-1.1.2/.github/workflows/ci.yml000066400000000000000000000067421443125777600174130ustar00rootroot00000000000000name: CI on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build: strategy: fail-fast: false matrix: os: [ubuntu-18.04, macos-10.15] name: ${{ matrix.os }} runs-on: ${{ matrix.os }} timeout-minutes: 5 steps: - name: Set up Go 1.15 uses: actions/setup-go@v2 with: go-version: 1.15 id: go - name: Setup Go binary path shell: bash run: | echo "GOPATH=${{ github.workspace }}" >> $GITHUB_ENV echo "${{ github.workspace }}/bin" >> $GITHUB_PATH - name: Check out code uses: actions/checkout@v2 with: path: src/github.com/containerd/ttrpc fetch-depth: 25 - name: Checkout project uses: actions/checkout@v2 with: repository: containerd/project path: src/github.com/containerd/project - name: Install dependencies env: GO111MODULE: off run: | go get -u github.com/vbatts/git-validation go get -u github.com/kunalkushwaha/ltag - name: Check DCO/whitespace/commit message env: GITHUB_COMMIT_URL: ${{ github.event.pull_request.commits_url }} DCO_VERBOSITY: "-q" DCO_RANGE: "" working-directory: src/github.com/containerd/ttrpc run: | if [ -z "${GITHUB_COMMIT_URL}" ]; then DCO_RANGE=$(jq -r '.before +".."+ .after' ${GITHUB_EVENT_PATH}) else DCO_RANGE=$(curl ${GITHUB_COMMIT_URL} | jq -r '.[0].parents[0].sha +".."+ .[-1].sha') fi ../project/script/validate/dco - name: Check file headers run: ../project/script/validate/fileheader ../project/ working-directory: src/github.com/containerd/ttrpc - name: Test working-directory: src/github.com/containerd/ttrpc run: | go test -v -race -coverprofile=coverage.txt -covermode=atomic ./... - name: Codecov run: bash <(curl -s https://codecov.io/bash) working-directory: src/github.com/containerd/ttrpc protobuild: name: Run Protobuild runs-on: ubuntu-20.04 timeout-minutes: 5 steps: - name: Set up Go 1.17 uses: actions/setup-go@v2 with: go-version: 1.17 id: go - name: Setup Go binary path shell: bash run: | echo "GOPATH=${{ github.workspace }}" >> $GITHUB_ENV echo "${{ github.workspace }}/bin" >> $GITHUB_PATH - name: Check out code uses: actions/checkout@v2 with: path: src/github.com/containerd/ttrpc fetch-depth: 25 - name: Install protoc run: | curl -LO https://github.com/protocolbuffers/protobuf/releases/download/v3.5.0/protoc-3.5.0-linux-x86_64.zip sudo unzip -x protoc-3.5.0-linux-x86_64.zip -d /usr/local sudo chmod -R go+rX /usr/local/include sudo chmod go+x /usr/local/bin/protoc - name: Install gogo/protobuf run: | cd $GOPATH/src mkdir -p github.com/gogo cd github.com/gogo git clone --depth 1 --branch v1.3.2 https://github.com/gogo/protobuf - name: Build protoc-gen-gogottrpc working-directory: src/github.com/containerd/ttrpc run: | go build ./cmd/protoc-gen-gogottrpc - name: Run Protobuild working-directory: src/github.com/containerd/ttrpc run: | export PATH=$GOPATH/bin:$PWD:$PATH go install github.com/containerd/protobuild@7e5ee24bc1f70e9e289fef15e2631eb3491320bf cd example protobuild git diff --exit-code ttrpc-1.1.2/.gitignore000066400000000000000000000002711443125777600146570ustar00rootroot00000000000000# Binaries for programs and plugins *.exe *.dll *.so *.dylib # Test binary, build with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out ttrpc-1.1.2/LICENSE000066400000000000000000000261351443125777600137030ustar00rootroot00000000000000 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.1.2/README.md000066400000000000000000000046521443125777600141550ustar00rootroot00000000000000# ttrpc [![Build Status](https://github.com/containerd/ttrpc/workflows/CI/badge.svg)](https://github.com/containerd/ttrpc/actions?query=workflow%3ACI) [![codecov](https://codecov.io/gh/containerd/ttrpc/branch/main/graph/badge.svg)](https://codecov.io/gh/containerd/ttrpc) 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. # 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//stevvooe/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. - No support for streams yet. # Status TODO: - [ ] Document protocol layout - [ ] 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.1.2/channel.go000066400000000000000000000077641443125777600146440ustar00rootroot00000000000000/* 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 ) // 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 // reserved 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) } 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, p []byte) error { if err := writeMessageHeader(ch.bw, ch.hwbuf[:], messageHeader{Length: uint32(len(p)), StreamID: streamID, Type: t}); err != nil { return err } _, 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.1.2/channel_test.go000066400000000000000000000046011443125777600156660ustar00rootroot00000000000000/* 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, 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, 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.1.2/client.go000066400000000000000000000220451443125777600144770ustar00rootroot00000000000000/* 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" "net" "os" "strings" "sync" "syscall" "time" "github.com/gogo/protobuf/proto" "github.com/sirupsen/logrus" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) // ErrClosed is returned by client methods when the underlying connection is // closed. var ErrClosed = errors.New("ttrpc: closed") // Client for a ttrpc server type Client struct { codec codec conn net.Conn channel *channel calls chan *callRequest ctx context.Context closed func() closeOnce sync.Once userCloseFunc func() userCloseWaitCh chan struct{} errOnce sync.Once err error 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 } } func NewClient(conn net.Conn, opts ...ClientOpts) *Client { ctx, cancel := context.WithCancel(context.Background()) c := &Client{ codec: codec{}, conn: conn, channel: newChannel(conn), calls: make(chan *callRequest), closed: cancel, ctx: ctx, userCloseFunc: func() {}, userCloseWaitCh: make(chan struct{}), interceptor: defaultClientInterceptor, } for _, o := range opts { o(c) } go c.run() return c } type callRequest struct { ctx context.Context req *Request resp *Response // response will be written back here errs chan error // error written here on completion } 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, } cresp = &Response{} ) if metadata, ok := GetMetadata(ctx); ok { metadata.setRequest(creq) } if dl, ok := ctx.Deadline(); ok { creq.TimeoutNano = dl.Sub(time.Now()).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 } func (c *Client) dispatch(ctx context.Context, req *Request, resp *Response) error { errs := make(chan error, 1) call := &callRequest{ ctx: ctx, req: req, resp: resp, errs: errs, } select { case <-ctx.Done(): return ctx.Err() case c.calls <- call: case <-c.ctx.Done(): return c.error() } select { case <-ctx.Done(): return ctx.Err() case err := <-errs: return filterCloseErr(err) case <-c.ctx.Done(): return c.error() } } func (c *Client) Close() error { c.closeOnce.Do(func() { c.closed() }) return nil } // UserOnCloseWait is used to blocks untils 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() } } type message struct { messageHeader p []byte err error } // callMap provides access to a map of active calls, guarded by a mutex. type callMap struct { m sync.Mutex activeCalls map[uint32]*callRequest closeErr error } // newCallMap returns a new callMap with an empty set of active calls. func newCallMap() *callMap { return &callMap{ activeCalls: make(map[uint32]*callRequest), } } // set adds a call entry to the map with the given streamID key. func (cm *callMap) set(streamID uint32, cr *callRequest) error { cm.m.Lock() defer cm.m.Unlock() if cm.closeErr != nil { return cm.closeErr } cm.activeCalls[streamID] = cr return nil } // get looks up the call entry for the given streamID key, then removes it // from the map and returns it. func (cm *callMap) get(streamID uint32) (cr *callRequest, ok bool, err error) { cm.m.Lock() defer cm.m.Unlock() if cm.closeErr != nil { return nil, false, cm.closeErr } cr, ok = cm.activeCalls[streamID] if ok { delete(cm.activeCalls, streamID) } return } // abort sends the given error to each active call, and clears the map. // Once abort has been called, any subsequent calls to the callMap will return the error passed to abort. func (cm *callMap) abort(err error) error { cm.m.Lock() defer cm.m.Unlock() if cm.closeErr != nil { return cm.closeErr } for streamID, call := range cm.activeCalls { call.errs <- err delete(cm.activeCalls, streamID) } cm.closeErr = err return nil } func (c *Client) run() { var ( waiters = newCallMap() receiverDone = make(chan struct{}) ) // Sender goroutine // Receives calls from dispatch, adds them to the set of active calls, and sends them // to the server. go func() { var streamID uint32 = 1 for { select { case <-c.ctx.Done(): return case call := <-c.calls: id := streamID streamID += 2 // enforce odd client initiated request ids if err := waiters.set(id, call); err != nil { call.errs <- err // errs is buffered so should not block. continue } if err := c.send(id, messageTypeRequest, call.req); err != nil { call.errs <- err // errs is buffered so should not block. waiters.get(id) // remove from waiters set } } } }() // Receiver goroutine // Receives responses from the server, looks up the call info in the set of active calls, // and notifies the caller of the response. go func() { defer close(receiverDone) for { select { case <-c.ctx.Done(): c.setError(c.ctx.Err()) return default: mh, p, 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. c.setError(filterCloseErr(err)) return } } msg := &message{ messageHeader: mh, p: p[:mh.Length], err: err, } call, ok, err := waiters.get(mh.StreamID) if err != nil { logrus.Errorf("ttrpc: failed to look up active call: %s", err) continue } if !ok { logrus.Errorf("ttrpc: received message for unknown channel %v", mh.StreamID) continue } call.errs <- c.recv(call.resp, msg) } } }() defer func() { c.conn.Close() c.userCloseFunc() close(c.userCloseWaitCh) }() for { select { case <-receiverDone: // The receiver has exited. // don't return out, let the close of the context trigger the abort of waiters c.Close() case <-c.ctx.Done(): // Abort all active calls. This will also prevent any new calls from being added // to waiters. waiters.abort(c.error()) return } } } func (c *Client) error() error { c.errOnce.Do(func() { if c.err == nil { c.err = ErrClosed } }) return c.err } func (c *Client) setError(err error) { c.errOnce.Do(func() { c.err = err }) } func (c *Client) send(streamID uint32, mtype messageType, msg interface{}) error { p, err := c.codec.Marshal(msg) if err != nil { return err } return c.channel.send(streamID, mtype, p) } func (c *Client) recv(resp *Response, msg *message) error { if msg.err != nil { return msg.err } if msg.Type != messageTypeResponse { return errors.New("unknown message type received") } defer c.channel.putmbuf(msg.p) return proto.Unmarshal(msg.p, resp) } // 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.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) && (oerr.Op == "write" || oerr.Op == "read") { serr, sok := oerr.Err.(*os.SyscallError) if sok && ((serr.Err == syscall.EPIPE && oerr.Op == "write") || (serr.Err == syscall.ECONNRESET && oerr.Op == "read")) { return ErrClosed } } } return err } ttrpc-1.1.2/client_test.go000066400000000000000000000033261443125777600155370ustar00rootroot00000000000000/* 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" ) 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 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.1.2/cmd/000077500000000000000000000000001443125777600134325ustar00rootroot00000000000000ttrpc-1.1.2/cmd/protoc-gen-go-ttrpc/000077500000000000000000000000001443125777600172445ustar00rootroot00000000000000ttrpc-1.1.2/cmd/protoc-gen-go-ttrpc/generator.go000066400000000000000000000074421443125777600215700ustar00rootroot00000000000000/* 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 ( "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 } } 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", }) return &gen } func generate(plugin *protogen.Plugin, input *protogen.File) error { 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 { gen.genService(service) } return nil } func (gen *generator) genService(service *protogen.Service) { fullName := service.Desc.FullName() p := gen.out serviceName := service.GoName + "Service" p.P("type ", serviceName, " interface{") for _, method := range service.Methods { p.P(method.GoName, "(ctx ", gen.ident.context, ",", "req *", method.Input.GoIdent, ")", "(*", method.Output.GoIdent, ", error)") } p.P("}") // registration method p.P("func Register", serviceName, "(srv *", gen.ident.server, ", svc ", serviceName, "){") p.P(`srv.Register("`, fullName, `", map[string]`, gen.ident.method, "{") for _, method := range service.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("})") p.P("}") clientType := service.GoName + "Client" clientStructType := strings.ToLower(clientType[:1]) + clientType[1:] p.P("type ", clientStructType, " struct{") p.P("client *", gen.ident.client) p.P("}") p.P("func New", clientType, "(client *", gen.ident.client, ")", serviceName, "{") p.P("return &", clientStructType, "{") p.P("client:client,") p.P("}") p.P("}") for _, method := range service.Methods { p.P("func (c *", clientStructType, ")", method.GoName, "(", "ctx ", gen.ident.context, ",", "req *", method.Input.GoIdent, ")", "(*", method.Output.GoIdent, ", error){") 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("}") } } ttrpc-1.1.2/cmd/protoc-gen-go-ttrpc/main.go000066400000000000000000000016041443125777600205200ustar00rootroot00000000000000/* 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" ) func main() { protogen.Options{}.Run(func(gen *protogen.Plugin) error { for _, f := range gen.Files { if !f.Generate { continue } if err := generate(gen, f); err != nil { return err } } return nil }) } ttrpc-1.1.2/cmd/protoc-gen-gogottrpc/000077500000000000000000000000001443125777600175155ustar00rootroot00000000000000ttrpc-1.1.2/cmd/protoc-gen-gogottrpc/main.go000066400000000000000000000023651443125777600207760ustar00rootroot00000000000000/* 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.1.2/codec.go000066400000000000000000000021541443125777600142750ustar00rootroot00000000000000/* 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" "github.com/gogo/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.1.2/config.go000066400000000000000000000030001443125777600144540ustar00rootroot00000000000000/* 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" 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 interceptor allowed per server") } c.interceptor = i return nil } } ttrpc-1.1.2/example/000077500000000000000000000000001443125777600143225ustar00rootroot00000000000000ttrpc-1.1.2/example/Protobuild.toml000066400000000000000000000030271443125777600173440ustar00rootroot00000000000000version = "unstable" generator = "gogottrpc" plugins = ["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 should be treated as include roots in relation to the vendor # directory. These will be calculated with the vendor directory nearest the # target package. packages = ["github.com/gogo/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] "gogoproto/gogo.proto" = "github.com/gogo/protobuf/gogoproto" "google/protobuf/any.proto" = "github.com/gogo/protobuf/types" "google/protobuf/empty.proto" = "github.com/gogo/protobuf/types" "google/protobuf/descriptor.proto" = "github.com/gogo/protobuf/protoc-gen-gogo/descriptor" "google/protobuf/field_mask.proto" = "github.com/gogo/protobuf/types" "google/protobuf/timestamp.proto" = "github.com/gogo/protobuf/types" "google/protobuf/duration.proto" = "github.com/gogo/protobuf/types" "google/rpc/status.proto" = "github.com/containerd/containerd/protobuf/google/rpc" ttrpc-1.1.2/example/cmd/000077500000000000000000000000001443125777600150655ustar00rootroot00000000000000ttrpc-1.1.2/example/cmd/handshaker_linux.go000066400000000000000000000013461443125777600207470ustar00rootroot00000000000000/* 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.1.2/example/cmd/handshaker_other.go000066400000000000000000000013321443125777600207240ustar00rootroot00000000000000// +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.1.2/example/cmd/main.go000066400000000000000000000057431443125777600163510ustar00rootroot00000000000000/* 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" "github.com/gogo/protobuf/types" ) 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) (*types.Empty, error) { return &types.Empty{}, nil } ttrpc-1.1.2/example/doc.go000066400000000000000000000012441443125777600154170ustar00rootroot00000000000000/* 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.1.2/example/example.pb.go000066400000000000000000000527051443125777600167150ustar00rootroot00000000000000// Code generated by protoc-gen-gogo. DO NOT EDIT. // source: github.com/containerd/ttrpc/example/example.proto package example import ( context "context" fmt "fmt" github_com_containerd_ttrpc "github.com/containerd/ttrpc" _ "github.com/gogo/protobuf/gogoproto" proto "github.com/gogo/protobuf/proto" types "github.com/gogo/protobuf/types" io "io" math "math" math_bits "math/bits" reflect "reflect" strings "strings" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package type Method1Request struct { Foo string `protobuf:"bytes,1,opt,name=foo,proto3" json:"foo,omitempty"` Bar string `protobuf:"bytes,2,opt,name=bar,proto3" json:"bar,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *Method1Request) Reset() { *m = Method1Request{} } func (*Method1Request) ProtoMessage() {} func (*Method1Request) Descriptor() ([]byte, []int) { return fileDescriptor_d5b23bbed948ff84, []int{0} } func (m *Method1Request) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *Method1Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { return xxx_messageInfo_Method1Request.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } return b[:n], nil } } func (m *Method1Request) XXX_Merge(src proto.Message) { xxx_messageInfo_Method1Request.Merge(m, src) } func (m *Method1Request) XXX_Size() int { return m.Size() } func (m *Method1Request) XXX_DiscardUnknown() { xxx_messageInfo_Method1Request.DiscardUnknown(m) } var xxx_messageInfo_Method1Request proto.InternalMessageInfo type Method1Response struct { Foo string `protobuf:"bytes,1,opt,name=foo,proto3" json:"foo,omitempty"` Bar string `protobuf:"bytes,2,opt,name=bar,proto3" json:"bar,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *Method1Response) Reset() { *m = Method1Response{} } func (*Method1Response) ProtoMessage() {} func (*Method1Response) Descriptor() ([]byte, []int) { return fileDescriptor_d5b23bbed948ff84, []int{1} } func (m *Method1Response) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *Method1Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { return xxx_messageInfo_Method1Response.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } return b[:n], nil } } func (m *Method1Response) XXX_Merge(src proto.Message) { xxx_messageInfo_Method1Response.Merge(m, src) } func (m *Method1Response) XXX_Size() int { return m.Size() } func (m *Method1Response) XXX_DiscardUnknown() { xxx_messageInfo_Method1Response.DiscardUnknown(m) } var xxx_messageInfo_Method1Response proto.InternalMessageInfo type Method2Request struct { Action string `protobuf:"bytes,1,opt,name=action,proto3" json:"action,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *Method2Request) Reset() { *m = Method2Request{} } func (*Method2Request) ProtoMessage() {} func (*Method2Request) Descriptor() ([]byte, []int) { return fileDescriptor_d5b23bbed948ff84, []int{2} } func (m *Method2Request) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } func (m *Method2Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { return xxx_messageInfo_Method2Request.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) if err != nil { return nil, err } return b[:n], nil } } func (m *Method2Request) XXX_Merge(src proto.Message) { xxx_messageInfo_Method2Request.Merge(m, src) } func (m *Method2Request) XXX_Size() int { return m.Size() } func (m *Method2Request) XXX_DiscardUnknown() { xxx_messageInfo_Method2Request.DiscardUnknown(m) } var xxx_messageInfo_Method2Request proto.InternalMessageInfo func init() { proto.RegisterType((*Method1Request)(nil), "ttrpc.example.v1.Method1Request") proto.RegisterType((*Method1Response)(nil), "ttrpc.example.v1.Method1Response") proto.RegisterType((*Method2Request)(nil), "ttrpc.example.v1.Method2Request") } func init() { proto.RegisterFile("github.com/containerd/ttrpc/example/example.proto", fileDescriptor_d5b23bbed948ff84) } var fileDescriptor_d5b23bbed948ff84 = []byte{ // 279 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x32, 0x4c, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xce, 0xcf, 0x2b, 0x49, 0xcc, 0xcc, 0x4b, 0x2d, 0x4a, 0xd1, 0x2f, 0x29, 0x29, 0x2a, 0x48, 0xd6, 0x4f, 0xad, 0x48, 0xcc, 0x2d, 0xc8, 0x49, 0x85, 0xd1, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x42, 0x02, 0x60, 0x49, 0x3d, 0x98, 0x60, 0x99, 0xa1, 0x94, 0x74, 0x7a, 0x7e, 0x7e, 0x7a, 0x4e, 0xaa, 0x3e, 0x58, 0x3e, 0xa9, 0x34, 0x4d, 0x3f, 0x35, 0xb7, 0xa0, 0xa4, 0x12, 0xa2, 0x5c, 0x4a, 0x24, 0x3d, 0x3f, 0x3d, 0x1f, 0xcc, 0xd4, 0x07, 0xb1, 0x20, 0xa2, 0x4a, 0x26, 0x5c, 0x7c, 0xbe, 0xa9, 0x25, 0x19, 0xf9, 0x29, 0x86, 0x41, 0xa9, 0x85, 0xa5, 0xa9, 0xc5, 0x25, 0x42, 0x02, 0x5c, 0xcc, 0x69, 0xf9, 0xf9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x20, 0x26, 0x48, 0x24, 0x29, 0xb1, 0x48, 0x82, 0x09, 0x22, 0x92, 0x94, 0x58, 0xa4, 0x64, 0xca, 0xc5, 0x0f, 0xd7, 0x55, 0x5c, 0x90, 0x9f, 0x57, 0x9c, 0x4a, 0x94, 0x36, 0x0d, 0x98, 0x65, 0x46, 0x30, 0xcb, 0xc4, 0xb8, 0xd8, 0x12, 0x93, 0x4b, 0x32, 0xf3, 0xf3, 0xa0, 0x1a, 0xa1, 0x3c, 0xa3, 0x79, 0x8c, 0x5c, 0xec, 0xae, 0x10, 0x8f, 0x09, 0xf9, 0x71, 0xb1, 0x43, 0x2d, 0x13, 0x52, 0xd0, 0x43, 0xf7, 0xb3, 0x1e, 0xaa, 0xeb, 0xa5, 0x14, 0xf1, 0xa8, 0x80, 0xba, 0xd4, 0x19, 0x66, 0x9e, 0x11, 0x11, 0xe6, 0x89, 0xe9, 0x41, 0xc2, 0x54, 0x0f, 0x16, 0xa6, 0x7a, 0xae, 0xa0, 0x30, 0x75, 0x72, 0x3d, 0xf1, 0x50, 0x8e, 0xe1, 0xc6, 0x43, 0x39, 0x86, 0x86, 0x47, 0x72, 0x8c, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, 0x1c, 0x63, 0x94, 0x36, 0x11, 0x31, 0x69, 0x0d, 0xa5, 0x93, 0xd8, 0xc0, 0xc6, 0x1a, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0x4a, 0xc9, 0x2c, 0xdc, 0xff, 0x01, 0x00, 0x00, } func (m *Method1Request) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } return dAtA[:n], nil } func (m *Method1Request) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *Method1Request) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if m.XXX_unrecognized != nil { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } if len(m.Bar) > 0 { i -= len(m.Bar) copy(dAtA[i:], m.Bar) i = encodeVarintExample(dAtA, i, uint64(len(m.Bar))) i-- dAtA[i] = 0x12 } if len(m.Foo) > 0 { i -= len(m.Foo) copy(dAtA[i:], m.Foo) i = encodeVarintExample(dAtA, i, uint64(len(m.Foo))) i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } func (m *Method1Response) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } return dAtA[:n], nil } func (m *Method1Response) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *Method1Response) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if m.XXX_unrecognized != nil { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } if len(m.Bar) > 0 { i -= len(m.Bar) copy(dAtA[i:], m.Bar) i = encodeVarintExample(dAtA, i, uint64(len(m.Bar))) i-- dAtA[i] = 0x12 } if len(m.Foo) > 0 { i -= len(m.Foo) copy(dAtA[i:], m.Foo) i = encodeVarintExample(dAtA, i, uint64(len(m.Foo))) i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } func (m *Method2Request) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) if err != nil { return nil, err } return dAtA[:n], nil } func (m *Method2Request) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } func (m *Method2Request) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l if m.XXX_unrecognized != nil { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } if len(m.Action) > 0 { i -= len(m.Action) copy(dAtA[i:], m.Action) i = encodeVarintExample(dAtA, i, uint64(len(m.Action))) i-- dAtA[i] = 0xa } return len(dAtA) - i, nil } func encodeVarintExample(dAtA []byte, offset int, v uint64) int { offset -= sovExample(v) base := offset for v >= 1<<7 { dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } dAtA[offset] = uint8(v) return base } func (m *Method1Request) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.Foo) if l > 0 { n += 1 + l + sovExample(uint64(l)) } l = len(m.Bar) if l > 0 { n += 1 + l + sovExample(uint64(l)) } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } return n } func (m *Method1Response) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.Foo) if l > 0 { n += 1 + l + sovExample(uint64(l)) } l = len(m.Bar) if l > 0 { n += 1 + l + sovExample(uint64(l)) } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } return n } func (m *Method2Request) Size() (n int) { if m == nil { return 0 } var l int _ = l l = len(m.Action) if l > 0 { n += 1 + l + sovExample(uint64(l)) } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } return n } func sovExample(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } func sozExample(x uint64) (n int) { return sovExample(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } func (this *Method1Request) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&Method1Request{`, `Foo:` + fmt.Sprintf("%v", this.Foo) + `,`, `Bar:` + fmt.Sprintf("%v", this.Bar) + `,`, `XXX_unrecognized:` + fmt.Sprintf("%v", this.XXX_unrecognized) + `,`, `}`, }, "") return s } func (this *Method1Response) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&Method1Response{`, `Foo:` + fmt.Sprintf("%v", this.Foo) + `,`, `Bar:` + fmt.Sprintf("%v", this.Bar) + `,`, `XXX_unrecognized:` + fmt.Sprintf("%v", this.XXX_unrecognized) + `,`, `}`, }, "") return s } func (this *Method2Request) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&Method2Request{`, `Action:` + fmt.Sprintf("%v", this.Action) + `,`, `XXX_unrecognized:` + fmt.Sprintf("%v", this.XXX_unrecognized) + `,`, `}`, }, "") return s } func valueToStringExample(v interface{}) string { rv := reflect.ValueOf(v) if rv.IsNil() { return "nil" } pv := reflect.Indirect(rv).Interface() return fmt.Sprintf("*%v", pv) } type ExampleService interface { Method1(ctx context.Context, req *Method1Request) (*Method1Response, error) Method2(ctx context.Context, req *Method1Request) (*types.Empty, error) } func RegisterExampleService(srv *github_com_containerd_ttrpc.Server, svc ExampleService) { srv.Register("ttrpc.example.v1.Example", map[string]github_com_containerd_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 *github_com_containerd_ttrpc.Client } func NewExampleClient(client *github_com_containerd_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) (*types.Empty, error) { var resp types.Empty if err := c.client.Call(ctx, "ttrpc.example.v1.Example", "Method2", req, &resp); err != nil { return nil, err } return &resp, nil } func (m *Method1Request) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowExample } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= uint64(b&0x7F) << shift if b < 0x80 { break } } fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { return fmt.Errorf("proto: Method1Request: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: Method1Request: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Foo", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowExample } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthExample } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthExample } if postIndex > l { return io.ErrUnexpectedEOF } m.Foo = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Bar", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowExample } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthExample } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthExample } if postIndex > l { return io.ErrUnexpectedEOF } m.Bar = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipExample(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthExample } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func (m *Method1Response) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowExample } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= uint64(b&0x7F) << shift if b < 0x80 { break } } fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { return fmt.Errorf("proto: Method1Response: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: Method1Response: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Foo", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowExample } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthExample } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthExample } if postIndex > l { return io.ErrUnexpectedEOF } m.Foo = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Bar", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowExample } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthExample } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthExample } if postIndex > l { return io.ErrUnexpectedEOF } m.Bar = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipExample(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthExample } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func (m *Method2Request) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowExample } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= uint64(b&0x7F) << shift if b < 0x80 { break } } fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { return fmt.Errorf("proto: Method2Request: wiretype end group for non-group") } if fieldNum <= 0 { return fmt.Errorf("proto: Method2Request: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Action", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowExample } if iNdEx >= l { return io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } intStringLen := int(stringLen) if intStringLen < 0 { return ErrInvalidLengthExample } postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthExample } if postIndex > l { return io.ErrUnexpectedEOF } m.Action = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipExample(dAtA[iNdEx:]) if err != nil { return err } if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthExample } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func skipExample(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 depth := 0 for iNdEx < l { var wire uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowExample } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { break } } wireType := int(wire & 0x7) switch wireType { case 0: for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowExample } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } iNdEx++ if dAtA[iNdEx-1] < 0x80 { break } } case 1: iNdEx += 8 case 2: var length int for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowExample } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ length |= (int(b) & 0x7F) << shift if b < 0x80 { break } } if length < 0 { return 0, ErrInvalidLengthExample } iNdEx += length case 3: depth++ case 4: if depth == 0 { return 0, ErrUnexpectedEndOfGroupExample } depth-- case 5: iNdEx += 4 default: return 0, fmt.Errorf("proto: illegal wireType %d", wireType) } if iNdEx < 0 { return 0, ErrInvalidLengthExample } if depth == 0 { return iNdEx, nil } } return 0, io.ErrUnexpectedEOF } var ( ErrInvalidLengthExample = fmt.Errorf("proto: negative length found during unmarshaling") ErrIntOverflowExample = fmt.Errorf("proto: integer overflow") ErrUnexpectedEndOfGroupExample = fmt.Errorf("proto: unexpected end of group") ) ttrpc-1.1.2/example/example.proto000066400000000000000000000007601443125777600170450ustar00rootroot00000000000000syntax = "proto3"; package ttrpc.example.v1; import "google/protobuf/empty.proto"; import "gogoproto/gogo.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.1.2/go.mod000066400000000000000000000005321443125777600137750ustar00rootroot00000000000000module github.com/containerd/ttrpc go 1.13 require ( github.com/gogo/protobuf v1.3.2 github.com/prometheus/procfs v0.6.0 github.com/sirupsen/logrus v1.8.1 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63 google.golang.org/grpc v1.27.1 google.golang.org/protobuf v1.27.1 ) ttrpc-1.1.2/go.sum000066400000000000000000000230671443125777600140320ustar00rootroot00000000000000cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 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/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 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-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 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 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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 h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63 h1:YzfoEYWbODU5Fbt37+h7X16BWQbad7Q4S6gclTKFXM8= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ttrpc-1.1.2/handshake.go000066400000000000000000000033141443125777600151450ustar00rootroot00000000000000/* 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(ctx context.Context, conn net.Conn) (net.Conn, interface{}, error) { return conn, nil, nil } ttrpc-1.1.2/interceptor.go000066400000000000000000000035161443125777600155610ustar00rootroot00000000000000/* 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 } // 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, info *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) } ttrpc-1.1.2/metadata.go000066400000000000000000000050751443125777600150050ustar00rootroot00000000000000/* 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.1.2/metadata_test.go000066400000000000000000000053701443125777600160420ustar00rootroot00000000000000/* 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.1.2/plugin/000077500000000000000000000000001443125777600141655ustar00rootroot00000000000000ttrpc-1.1.2/plugin/generator.go000066400000000000000000000076371443125777600165170ustar00rootroot00000000000000/* 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.1.2/server.go000066400000000000000000000242321443125777600145270ustar00rootroot00000000000000/* 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" ) var ( ErrServerClosed = errors.New("ttrpc: server closed") ) 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 } func (s *Server) Register(name string, methods map[string]Method) { s.services.register(name, methods) } 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).Errorf("ttrpc: refusing connection after handshake") conn.Close() continue } sc := s.newConn(approved, handshake) 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 { if s.closeIdleConns() { return lnerr } select { case <-ctx.Done(): return ctx.Err() case <-ticker.C: } } } // 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) { s.mu.Lock() defer s.mu.Unlock() s.connections[c] = struct{}{} } 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() bool { s.mu.Lock() defer s.mu.Unlock() quiescent := true for c := range s.connections { st, ok := c.getState() if !ok || st != connStateIdle { quiescent = false continue } c.close() delete(s.connections, c) } return quiescent } 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 { c := &serverConn{ server: s, conn: conn, handshake: handshake, shutdown: make(chan struct{}), } c.setState(connStateIdle) s.addConnection(c) return c } 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 ( request struct { id uint32 req *Request } response struct { id uint32 resp *Response } ) var ( ch = newChannel(c.conn) ctx, cancel = context.WithCancel(sctx) active int state connState = connStateIdle responses = make(chan response) requests = make(chan request) recvErr = make(chan error, 1) shutdown = c.shutdown done = make(chan struct{}) ) defer c.conn.Close() defer cancel() defer close(done) defer c.server.delConnection(c) go func(recvErr chan error) { defer close(recvErr) sendImmediate := 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, resp: &Response{ Status: st.Proto(), }, }: return true case <-c.shutdown: return false case <-done: return false } } 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 !sendImmediate(mh.StreamID, status) { return } continue } if mh.Type != messageTypeRequest { // we must ignore this for future compat. continue } var req Request if err := c.server.codec.Unmarshal(p, &req); err != nil { ch.putmbuf(p) if !sendImmediate(mh.StreamID, status.Newf(codes.InvalidArgument, "unmarshal request error: %v", err)) { return } continue } ch.putmbuf(p) if mh.StreamID%2 != 1 { // enforce odd client initiated identifiers. if !sendImmediate(mh.StreamID, status.Newf(codes.InvalidArgument, "StreamID must be odd for client initiated streams")) { return } continue } // Forward the request to the main loop. We don't wait on s.done // because we have already accepted the client request. select { case requests <- request{ id: mh.StreamID, req: &req, }: case <-done: return } } }(recvErr) for { newstate := state switch { case active > 0: newstate = connStateActive shutdown = nil case active == 0: newstate = connStateIdle shutdown = c.shutdown // only enable this branch in idle mode } if newstate != state { c.setState(newstate) state = newstate } select { case request := <-requests: active++ go func(id uint32) { ctx, cancel := getRequestContext(ctx, request.req) defer cancel() p, status := c.server.services.call(ctx, request.req.Service, request.req.Method, request.req.Payload) resp := &Response{ Status: status.Proto(), Payload: p, } select { case responses <- response{ id: id, resp: resp, }: case <-done: } }(request.id) case response := <-responses: p, err := c.server.codec.Marshal(response.resp) if err != nil { logrus.WithError(err).Error("failed marshaling response") return } if err := ch.send(response.id, messageTypeResponse, p); err != nil { logrus.WithError(err).Error("failed sending message on channel") return } active-- 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") 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.1.2/server_linux_test.go000066400000000000000000000071721443125777600170110ustar00rootroot00000000000000/* 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/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 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 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 := &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.1.2/server_test.go000066400000000000000000000331611443125777600155670ustar00rootroot00000000000000/* 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" "reflect" "runtime" "strings" "sync" "testing" "time" "github.com/gogo/protobuf/proto" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) 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 *testPayload) (*testPayload, error) } type testingClient struct { client *Client } func newTestingClient(client *Client) *testingClient { return &testingClient{ client: client, } } func (tc *testingClient) Test(ctx context.Context, req *testPayload) (*testPayload, error) { var tp testPayload return &tp, tc.client.Call(ctx, serviceName, "Test", req, &tp) } type testPayload struct { Foo string `protobuf:"bytes,1,opt,name=foo,proto3"` Deadline int64 `protobuf:"varint,2,opt,name=deadline,proto3"` Metadata string `protobuf:"bytes,3,opt,name=metadata,proto3"` } func (r *testPayload) Reset() { *r = testPayload{} } func (r *testPayload) String() string { return fmt.Sprintf("%+#v", r) } func (r *testPayload) ProtoMessage() {} // testingServer is what would be implemented by the user of this package. type testingServer struct{} func (s *testingServer) Test(ctx context.Context, req *testPayload) (*testPayload, error) { tp := &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 testPayload if err := unmarshal(&req); err != nil { return nil, err } return svc.Test(ctx, &req) }, }) } func init() { proto.RegisterType((*testPayload)(nil), "testPayload") proto.RegisterType((*Request)(nil), "Request") proto.RegisterType((*Response)(nil), "Response") } 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) const calls = 2 results := make(chan callResult, 2) go roundTrip(ctx, t, tclient, "bar", results) go roundTrip(ctx, t, tclient, "baz", results) for i := 0; i < calls; i++ { result := <-results if !reflect.DeepEqual(result.received, result.expected) { t.Fatalf("unexpected response: %+#v != %+#v", result.received, result.expected) } } } 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 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 = make(chan struct{}) handlersStartedCloseOnce sync.Once proceed = make(chan struct{}) serveErrs = make(chan error, 1) callwg sync.WaitGroup 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 testPayload if err := unmarshal(&req); err != nil { return nil, err } handlersStartedCloseOnce.Do(func() { close(handlersStarted) }) <-proceed return &testPayload{Foo: "waited"}, nil }, }) go func() { serveErrs <- server.Serve(ctx, listener) }() // send a series of requests that will get blocked for i := 0; i < 5; i++ { callwg.Add(1) go func(i int) { callwg.Done() tp := testPayload{Foo: "half" + fmt.Sprint(i)} callErrs <- client.Call(ctx, serviceName, "Test", &tp, &tp) }(i) } <-handlersStarted go func() { close(shutdownStarted) shutdownErrs <- server.Shutdown(ctx) // server.Close() 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 := &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 := &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) } // 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) { 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 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", &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, 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 := &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 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.Fatalf("expected listeners to be empty: %v", server.listeners) } if len(server.connections) > 0 { t.Fatalf("expected connections to be empty: %v", server.connections) } } type callResult struct { input *testPayload expected *testPayload received *testPayload } func roundTrip(ctx context.Context, t *testing.T, client *testingClient, value string, results chan callResult) { t.Helper() var ( tp = &testPayload{ Foo: "bar", } ) ctx = WithMetadata(ctx, MD{"foo": []string{"bar"}}) resp, err := client.Test(ctx, tp) if err != nil { t.Fatal(err) } results <- callResult{ input: tp, expected: &testPayload{Foo: strings.Repeat(tp.Foo, 2), Metadata: "bar"}, 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.1.2/services.go000066400000000000000000000100531443125777600150400ustar00rootroot00000000000000/* 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" "github.com/gogo/protobuf/proto" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) type Method func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) type ServiceDesc struct { Methods map[string]Method // TODO(stevvooe): Add stream support. } type serviceSet struct { services map[string]ServiceDesc interceptor UnaryServerInterceptor } func newServiceSet(interceptor UnaryServerInterceptor) *serviceSet { return &serviceSet{ services: make(map[string]ServiceDesc), interceptor: interceptor, } } func (s *serviceSet) register(name string, methods map[string]Method) { if _, ok := s.services[name]; ok { panic(fmt.Errorf("duplicate service %v registered", name)) } s.services[name] = ServiceDesc{ Methods: methods, } } func (s *serviceSet) call(ctx context.Context, serviceName, methodName string, p []byte) ([]byte, *status.Status) { p, err := s.dispatch(ctx, serviceName, methodName, p) st, ok := status.FromError(err) if !ok { st = status.New(convertCode(err), err.Error()) } return p, st } func (s *serviceSet) dispatch(ctx context.Context, serviceName, methodName string, p []byte) ([]byte, error) { method, err := s.resolve(serviceName, methodName) if err != nil { return nil, err } unmarshal := func(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 } info := &UnaryServerInfo{ FullMethod: fullPath(serviceName, methodName), } resp, err := s.interceptor(ctx, unmarshal, info, method) if err != nil { return nil, err } if isNil(resp) { return nil, errors.New("ttrpc: marshal called with nil") } switch v := resp.(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) } } func (s *serviceSet) resolve(service, method string) (Method, error) { srv, ok := s.services[service] if !ok { return nil, status.Errorf(codes.Unimplemented, "service %v", service) } mthd, ok := srv.Methods[method] if !ok { return nil, status.Errorf(codes.Unimplemented, "method %v", method) } return mthd, nil } // 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.1.2/services_test.go000066400000000000000000000015711443125777600161040ustar00rootroot00000000000000/* 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.1.2/types.go000066400000000000000000000042111443125777600143600ustar00rootroot00000000000000/* 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" spb "google.golang.org/genproto/googleapis/rpc/status" ) type Request struct { Service string `protobuf:"bytes,1,opt,name=service,proto3"` Method string `protobuf:"bytes,2,opt,name=method,proto3"` Payload []byte `protobuf:"bytes,3,opt,name=payload,proto3"` TimeoutNano int64 `protobuf:"varint,4,opt,name=timeout_nano,proto3"` Metadata []*KeyValue `protobuf:"bytes,5,rep,name=metadata,proto3"` } func (r *Request) Reset() { *r = Request{} } func (r *Request) String() string { return fmt.Sprintf("%+#v", r) } func (r *Request) ProtoMessage() {} type Response struct { Status *spb.Status `protobuf:"bytes,1,opt,name=status,proto3"` Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3"` } func (r *Response) Reset() { *r = Response{} } func (r *Response) String() string { return fmt.Sprintf("%+#v", r) } func (r *Response) ProtoMessage() {} type StringList struct { List []string `protobuf:"bytes,1,rep,name=list,proto3"` } func (r *StringList) Reset() { *r = StringList{} } func (r *StringList) String() string { return fmt.Sprintf("%+#v", r) } func (r *StringList) ProtoMessage() {} func makeStringList(item ...string) StringList { return StringList{List: item} } type KeyValue struct { Key string `protobuf:"bytes,1,opt,name=key,proto3"` Value string `protobuf:"bytes,2,opt,name=value,proto3"` } func (m *KeyValue) Reset() { *m = KeyValue{} } func (*KeyValue) ProtoMessage() {} func (m *KeyValue) String() string { return fmt.Sprintf("%+#v", m) } ttrpc-1.1.2/unixcreds_linux.go000066400000000000000000000066431443125777600164520ustar00rootroot00000000000000/* 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(ctx 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", err) } 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 requireRoot(ucred *unix.Ucred) error { return requireUidGid(ucred, 0, 0) } 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 }