pax_global_header00006660000000000000000000000064137276664610014534gustar00rootroot0000000000000052 comment=bfba540dc45464586c106b1f31c8547933c1eb41 ttrpc-1.0.2/000077500000000000000000000000001372766646100126705ustar00rootroot00000000000000ttrpc-1.0.2/.gitignore000066400000000000000000000004231372766646100146570ustar00rootroot00000000000000# 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 # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 .glide/ ttrpc-1.0.2/.travis.yml000066400000000000000000000011661372766646100150050ustar00rootroot00000000000000dist: bionic language: go go: - "1.13.x" - "1.15.x" install: # Don't change local go.{mod, sum} by go get tools. # # ref: https://github.com/golang/go/issues/27643 - pushd ..; go get -u github.com/vbatts/git-validation; popd - pushd ..; go get -u github.com/kunalkushwaha/ltag; popd before_script: - pushd ..; git clone https://github.com/containerd/project; popd script: - DCO_VERBOSITY=-q ../project/script/validate/dco - ../project/script/validate/fileheader ../project/ - go test -v -race -covermode=atomic -coverprofile=coverage.txt ./... after_success: - bash <(curl -s https://codecov.io/bash) ttrpc-1.0.2/LICENSE000066400000000000000000000261351372766646100137040ustar00rootroot00000000000000 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.0.2/README.md000066400000000000000000000047231372766646100141550ustar00rootroot00000000000000# ttrpc [![Build Status](https://travis-ci.org/containerd/ttrpc.svg?branch=master)](https://travis-ci.org/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 Very new. YMMV. TODO: - [X] Plumb error codes and GRPC status - [X] Remove use of any type and dependency on typeurl package - [X] Ensure that protocol can support streaming in the future - [ ] 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/master/GOVERNANCE.md), * [Maintainers](https://github.com/containerd/project/blob/master/MAINTAINERS), * and [Contributing guidelines](https://github.com/containerd/project/blob/master/CONTRIBUTING.md) information in our [`containerd/project`](https://github.com/containerd/project) repository. ttrpc-1.0.2/channel.go000066400000000000000000000100021372766646100146200ustar00rootroot00000000000000/* 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" "io" "net" "sync" "github.com/pkg/errors" "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, errors.Wrapf(err, "failed to discard after receiving oversized message") } 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, errors.Wrapf(err, "failed reading message") } 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.0.2/channel_test.go000066400000000000000000000046241372766646100156740ustar00rootroot00000000000000/* 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" "io" "net" "reflect" "testing" "github.com/pkg/errors" "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.Cause(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.0.2/client.go000066400000000000000000000171751372766646100145100ustar00rootroot00000000000000/* Copyright The containerd Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package ttrpc import ( "context" "io" "net" "os" "strings" "sync" "syscall" "time" "github.com/gogo/protobuf/proto" "github.com/pkg/errors" "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 } type receiver struct { wg *sync.WaitGroup messages chan *message err error } func (r *receiver) run(ctx context.Context, c *channel) { defer r.wg.Done() for { select { case <-ctx.Done(): r.err = ctx.Err() return default: mh, p, err := c.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. r.err = filterCloseErr(err) return } } select { case r.messages <- &message{ messageHeader: mh, p: p[:mh.Length], err: err, }: case <-ctx.Done(): r.err = ctx.Err() return } } } } func (c *Client) run() { var ( streamID uint32 = 1 waiters = make(map[uint32]*callRequest) calls = c.calls incoming = make(chan *message) receiversDone = make(chan struct{}) wg sync.WaitGroup ) // broadcast the shutdown error to the remaining waiters. abortWaiters := func(wErr error) { for _, waiter := range waiters { waiter.errs <- wErr } } recv := &receiver{ wg: &wg, messages: incoming, } wg.Add(1) go func() { wg.Wait() close(receiversDone) }() go recv.run(c.ctx, c.channel) defer func() { c.conn.Close() c.userCloseFunc() close(c.userCloseWaitCh) }() for { select { case call := <-calls: if err := c.send(streamID, messageTypeRequest, call.req); err != nil { call.errs <- err continue } waiters[streamID] = call streamID += 2 // enforce odd client initiated request ids case msg := <-incoming: call, ok := waiters[msg.StreamID] if !ok { logrus.Errorf("ttrpc: received message for unknown channel %v", msg.StreamID) continue } call.errs <- c.recv(call.resp, msg) delete(waiters, msg.StreamID) case <-receiversDone: // all the receivers have exited if recv.err != nil { c.setError(recv.err) } // don't return out, let the close of the context trigger the abort of waiters c.Close() case <-c.ctx.Done(): abortWaiters(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.Cause(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.0.2/client_test.go000066400000000000000000000033261372766646100155400ustar00rootroot00000000000000/* 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.0.2/cmd/000077500000000000000000000000001372766646100134335ustar00rootroot00000000000000ttrpc-1.0.2/cmd/protoc-gen-gogottrpc/000077500000000000000000000000001372766646100175165ustar00rootroot00000000000000ttrpc-1.0.2/cmd/protoc-gen-gogottrpc/main.go000066400000000000000000000023651372766646100207770ustar00rootroot00000000000000/* 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.0.2/codec.go000066400000000000000000000022031372766646100142710ustar00rootroot00000000000000/* 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 ( "github.com/gogo/protobuf/proto" "github.com/pkg/errors" ) 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, errors.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 errors.Errorf("ttrpc: cannot unmarshal into unknown type: %T", msg) } } ttrpc-1.0.2/config.go000066400000000000000000000030171372766646100144650ustar00rootroot00000000000000/* 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 "github.com/pkg/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.0.2/example/000077500000000000000000000000001372766646100143235ustar00rootroot00000000000000ttrpc-1.0.2/example/Protobuild.toml000066400000000000000000000030271372766646100173450ustar00rootroot00000000000000version = "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.0.2/example/cmd/000077500000000000000000000000001372766646100150665ustar00rootroot00000000000000ttrpc-1.0.2/example/cmd/main.go000066400000000000000000000057611372766646100163520ustar00rootroot00000000000000/* 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(ttrpc.UnixSocketRequireSameUser()), 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.0.2/example/doc.go000066400000000000000000000012441372766646100154200ustar00rootroot00000000000000/* 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.0.2/example/example.pb.go000066400000000000000000000430271372766646100167130ustar00rootroot00000000000000// Code generated by protoc-gen-gogo. DO NOT EDIT. // source: github.com/containerd/ttrpc/example/example.proto /* Package example is a generated protocol buffer package. It is generated from these files: github.com/containerd/ttrpc/example/example.proto It has these top-level messages: Method1Request Method1Response Method2Request */ package example import proto "github.com/gogo/protobuf/proto" import fmt "fmt" import math "math" import google_protobuf "github.com/gogo/protobuf/types" import _ "github.com/gogo/protobuf/gogoproto" import strings "strings" import reflect "reflect" import context "context" import ttrpc "github.com/containerd/ttrpc" import io "io" // 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.GoGoProtoPackageIsVersion2 // 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"` } func (m *Method1Request) Reset() { *m = Method1Request{} } func (*Method1Request) ProtoMessage() {} func (*Method1Request) Descriptor() ([]byte, []int) { return fileDescriptorExample, []int{0} } 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"` } func (m *Method1Response) Reset() { *m = Method1Response{} } func (*Method1Response) ProtoMessage() {} func (*Method1Response) Descriptor() ([]byte, []int) { return fileDescriptorExample, []int{1} } type Method2Request struct { Action string `protobuf:"bytes,1,opt,name=action,proto3" json:"action,omitempty"` } func (m *Method2Request) Reset() { *m = Method2Request{} } func (*Method2Request) ProtoMessage() {} func (*Method2Request) Descriptor() ([]byte, []int) { return fileDescriptorExample, []int{2} } 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 (m *Method1Request) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } return dAtA[:n], nil } func (m *Method1Request) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Foo) > 0 { dAtA[i] = 0xa i++ i = encodeVarintExample(dAtA, i, uint64(len(m.Foo))) i += copy(dAtA[i:], m.Foo) } if len(m.Bar) > 0 { dAtA[i] = 0x12 i++ i = encodeVarintExample(dAtA, i, uint64(len(m.Bar))) i += copy(dAtA[i:], m.Bar) } return i, nil } func (m *Method1Response) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } return dAtA[:n], nil } func (m *Method1Response) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Foo) > 0 { dAtA[i] = 0xa i++ i = encodeVarintExample(dAtA, i, uint64(len(m.Foo))) i += copy(dAtA[i:], m.Foo) } if len(m.Bar) > 0 { dAtA[i] = 0x12 i++ i = encodeVarintExample(dAtA, i, uint64(len(m.Bar))) i += copy(dAtA[i:], m.Bar) } return i, nil } func (m *Method2Request) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } return dAtA[:n], nil } func (m *Method2Request) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Action) > 0 { dAtA[i] = 0xa i++ i = encodeVarintExample(dAtA, i, uint64(len(m.Action))) i += copy(dAtA[i:], m.Action) } return i, nil } func encodeVarintExample(dAtA []byte, offset int, v uint64) int { for v >= 1<<7 { dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } dAtA[offset] = uint8(v) return offset + 1 } func (m *Method1Request) Size() (n int) { 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)) } return n } func (m *Method1Response) Size() (n int) { 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)) } return n } func (m *Method2Request) Size() (n int) { var l int _ = l l = len(m.Action) if l > 0 { n += 1 + l + sovExample(uint64(l)) } return n } func sovExample(x uint64) (n int) { for { n++ x >>= 7 if x == 0 { break } } return n } 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) + `,`, `}`, }, "") 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) + `,`, `}`, }, "") return s } func (this *Method2Request) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&Method2Request{`, `Action:` + fmt.Sprintf("%v", this.Action) + `,`, `}`, }, "") 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) (*google_protobuf.Empty, error) } func RegisterExampleService(srv *ttrpc.Server, svc ExampleService) { srv.Register("ttrpc.example.v1.Example", map[string]ttrpc.Method{ "Method1": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { var req Method1Request if err := unmarshal(&req); err != nil { return nil, err } return svc.Method1(ctx, &req) }, "Method2": func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error) { var req Method1Request if err := unmarshal(&req); err != nil { return nil, err } return svc.Method2(ctx, &req) }, }) } type exampleClient struct { client *ttrpc.Client } func NewExampleClient(client *ttrpc.Client) ExampleService { return &exampleClient{ client: client, } } func (c *exampleClient) Method1(ctx context.Context, req *Method1Request) (*Method1Response, error) { var resp Method1Response if err := c.client.Call(ctx, "ttrpc.example.v1.Example", "Method1", req, &resp); err != nil { return nil, err } return &resp, nil } func (c *exampleClient) Method2(ctx context.Context, req *Method1Request) (*google_protobuf.Empty, error) { var resp google_protobuf.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 > 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 > 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 { return ErrInvalidLengthExample } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } 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 > 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 > 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 { return ErrInvalidLengthExample } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } 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 > 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 { return ErrInvalidLengthExample } if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } iNdEx += skippy } } if iNdEx > l { return io.ErrUnexpectedEOF } return nil } func skipExample(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 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 } } return iNdEx, nil case 1: iNdEx += 8 return iNdEx, nil 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 } } iNdEx += length if length < 0 { return 0, ErrInvalidLengthExample } return iNdEx, nil case 3: for { var innerWire uint64 var start int = iNdEx for shift := uint(0); ; shift += 7 { if shift >= 64 { return 0, ErrIntOverflowExample } if iNdEx >= l { return 0, io.ErrUnexpectedEOF } b := dAtA[iNdEx] iNdEx++ innerWire |= (uint64(b) & 0x7F) << shift if b < 0x80 { break } } innerWireType := int(innerWire & 0x7) if innerWireType == 4 { break } next, err := skipExample(dAtA[start:]) if err != nil { return 0, err } iNdEx = start + next } return iNdEx, nil case 4: return iNdEx, nil case 5: iNdEx += 4 return iNdEx, nil default: return 0, fmt.Errorf("proto: illegal wireType %d", wireType) } } panic("unreachable") } var ( ErrInvalidLengthExample = fmt.Errorf("proto: negative length found during unmarshaling") ErrIntOverflowExample = fmt.Errorf("proto: integer overflow") ) func init() { proto.RegisterFile("github.com/containerd/ttrpc/example/example.proto", fileDescriptorExample) } var fileDescriptorExample = []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, } ttrpc-1.0.2/example/example.proto000066400000000000000000000007601372766646100170460ustar00rootroot00000000000000syntax = "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.0.2/go.mod000066400000000000000000000006671372766646100140070ustar00rootroot00000000000000module github.com/containerd/ttrpc go 1.13 require ( github.com/gogo/protobuf v1.3.1 github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/pkg/errors v0.9.1 github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1 github.com/sirupsen/logrus v1.4.2 golang.org/x/sys v0.0.0-20200120151820-655fe14d7479 google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24 google.golang.org/grpc v1.26.0 ) ttrpc-1.0.2/go.sum000066400000000000000000000203071372766646100140250ustar00rootroot00000000000000cloud.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.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 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.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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.0.0-20190522114515-bc1a522cf7b1 h1:Lo6mRUjdS99f3zxYOUalftWHUoOGaDRqFk1+j0Q57/I= github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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/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/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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/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-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5 h1:f005F/Jl5JLP036x7QIvUVhNTqxvSYwFIiyOh2q12iU= golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200120151820-655fe14d7479 h1:LhLiKguPgZL+Tglay4GhVtfF0kb8cvOJ0dHTCBO8YNI= golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/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= 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-20190522204451-c2c4e71fbf69 h1:4rNOqY4ULrKzS6twXa619uQgI7h9PaVd4ZhjFQ7C5zs= google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24 h1:wDju+RU97qa0FZT0QnZDg9Uc2dH0Ql513kFvHocz+WM= google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.21.0 h1:G+97AoqBnmZIT91cLG/EkCoK9NSelj64P8bOHHNmGn0= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 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.0.2/handshake.go000066400000000000000000000033141372766646100151460ustar00rootroot00000000000000/* 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.0.2/interceptor.go000066400000000000000000000035161372766646100155620ustar00rootroot00000000000000/* 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.0.2/metadata.go000066400000000000000000000050751372766646100150060ustar00rootroot00000000000000/* 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.0.2/metadata_test.go000066400000000000000000000053701372766646100160430ustar00rootroot00000000000000/* 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.0.2/plugin/000077500000000000000000000000001372766646100141665ustar00rootroot00000000000000ttrpc-1.0.2/plugin/generator.go000066400000000000000000000076371372766646100165200ustar00rootroot00000000000000/* 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.0.2/server.go000066400000000000000000000241771372766646100145400ustar00rootroot00000000000000/* Copyright The containerd Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package ttrpc import ( "context" "io" "math/rand" "net" "sync" "sync/atomic" "time" "github.com/pkg/errors" "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 err == io.EOF || err == io.ErrUnexpectedEOF { // The client went away and we should stop processing // requests, so that the client connection is closed return } if err != nil { 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.0.2/server_test.go000066400000000000000000000407451372766646100155760ustar00rootroot00000000000000/* 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" "fmt" "net" "reflect" "strings" "sync" "testing" "time" "github.com/gogo/protobuf/proto" "github.com/pkg/errors" "github.com/prometheus/procfs" "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 TestServerNotFound(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.NotFound { 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.Cause(err) != ErrClosed { t.Fatalf("expected to have a cause of ErrClosed, got %v", errors.Cause(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 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 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 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 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) { addr := "\x00" + 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 } } 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.0.2/services.go000066400000000000000000000100541372766646100150420ustar00rootroot00000000000000/* Copyright The containerd Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package ttrpc import ( "context" "io" "os" "path" "unsafe" "github.com/gogo/protobuf/proto" "github.com/pkg/errors" "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(errors.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.NotFound, "service %v", service) } mthd, ok := srv.Methods[method] if !ok { return nil, status.Errorf(codes.NotFound, "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.0.2/services_test.go000066400000000000000000000015711372766646100161050ustar00rootroot00000000000000/* 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.0.2/types.go000066400000000000000000000042111372766646100143610ustar00rootroot00000000000000/* 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.0.2/unixcreds_linux.go000066400000000000000000000066341372766646100164530ustar00rootroot00000000000000/* 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" "os" "syscall" "github.com/pkg/errors" "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, errors.Wrap(err, "ttrpc.UnixCredentialsFunc: require unix socket") } rs, err := uc.SyscallConn() if err != nil { return nil, nil, errors.Wrap(err, "ttrpc.UnixCredentialsFunc: (net.UnixConn).SyscallConn failed") } 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, errors.Wrapf(err, "ttrpc.UnixCredentialsFunc: (*syscall.RawConn).Control failed") } if ucredErr != nil { return nil, nil, errors.Wrapf(err, "ttrpc.UnixCredentialsFunc: failed to retrieve socket peer credentials") } if err := fn(ucred); err != nil { return nil, nil, errors.Wrapf(err, "ttrpc.UnixCredentialsFunc: credential check failed") } 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 errors.Wrap(syscall.EPERM, "ttrpc: invalid credentials") } 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 }