diff --git a/options.go b/options.go deleted file mode 100644 index 4f24fcb..0000000 --- a/options.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright © 2015 Michal Witkowski -// -// 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 proxy -import ( - "google.golang.org/grpc/credentials" - "google.golang.org/grpc/grpclog" -) - -type options struct { - creds credentials.TransportCredentials - logger grpclog.Logger - maxConcurrentStreams uint32 -} - -// A ProxyOption sets options. -type ProxyOption func(*options) - - -// UsingLogger returns a ProxyOption that makes use of a logger other than the default `grpclogger`. -func UsingLogger(logger grpclog.Logger) ProxyOption { - return func(o *options) { - o.logger = logger - } -} - -// MaxConcurrentStreams returns a ProxyOption that will apply a limit on the number -// of concurrent streams to each ServerTransport. -func MaxConcurrentStreams(n uint32) ProxyOption { - return func(o *options) { - o.maxConcurrentStreams = n - } -} - -// Creds returns a ProxyOption that sets credentials for server connections. -func Creds(c credentials.TransportCredentials) ProxyOption { - return func(o *options) { - o.creds = c - } -} - - -type defaultLogger struct{} - -func (g *defaultLogger) Fatal(args ...interface{}) { - grpclog.Fatal(args...) -} - -func (g *defaultLogger) Fatalf(format string, args ...interface{}) { - grpclog.Fatalf(format, args...) -} - -func (g *defaultLogger) Fatalln(args ...interface{}) { - grpclog.Fatalln(args...) -} - -func (g *defaultLogger) Print(args ...interface{}) { - grpclog.Print(args...) -} - -func (g *defaultLogger) Printf(format string, args ...interface{}) { - grpclog.Printf(format, args...) -} - -func (g *defaultLogger) Println(args ...interface{}) { - grpclog.Println(args...) -} diff --git a/proxy.go b/proxy.go index dacac2a..f674d77 100644 --- a/proxy.go +++ b/proxy.go @@ -1,244 +1,117 @@ -// Copyright © 2015 Michal Witkowski -// Copyright 2014, Google Inc. - server parts, licensed under MIT license. -// -// 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 proxy import ( - "fmt" "io" - "net" - "strings" - "sync" - "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" - "google.golang.org/grpc/credentials" - "google.golang.org/grpc/grpclog" "google.golang.org/grpc/transport" ) -// transportWriter is a common interface between gRPC transport.ServerTransport and transport.ClientTransport. -type transportWriter interface { - Write(s *transport.Stream, data []byte, opts *transport.Options) error -} - -type Proxy struct { - mu sync.Mutex - lis map[net.Listener]bool - conns map[transport.ServerTransport]bool - logger grpclog.Logger - director StreamDirector - opts *options -} - -// NewServer creates a gRPC proxy which will use the `StreamDirector` for making routing decisions. -func NewServer(director StreamDirector, opt ...ProxyOption) *Proxy { - s := &Proxy{ - lis: make(map[net.Listener]bool), - conns: make(map[transport.ServerTransport]bool), - opts: &options{}, - director: director, - logger: &defaultLogger{}, +var ( + clientStreamDescForProxying = &grpc.StreamDesc{ + ServerStreams: true, + ClientStreams: true, } - for _, o := range opt { - o(s.opts) - } - if s.opts.logger != nil { - s.logger = s.opts.logger - } - return s -} +) -// Serve handles the serving path of the grpc. -func (s *Proxy) Serve(lis net.Listener) error { - s.mu.Lock() - if s.lis == nil { - s.mu.Unlock() - return grpc.ErrServerStopped - } - s.lis[lis] = true - s.mu.Unlock() - defer func() { - lis.Close() - s.mu.Lock() - delete(s.lis, lis) - s.mu.Unlock() - }() - for { - c, err := lis.Accept() - if err != nil { - s.mu.Lock() - s.mu.Unlock() - return err +func RegisterProxyStreams(server *grpc.Server, director StreamDirector, serviceName string, methodNames ...string) { + streamer := &proxyStreamer{director} + fakeDesc := &grpc.ServiceDesc{ + ServiceName: serviceName, + HandlerType: (*interface{})(nil), + } + for _, m := range methodNames { + streamDesc := grpc.StreamDesc{ + StreamName: m, + Handler: streamer.handler, + ServerStreams: true, + ClientStreams: true, } - var authInfo credentials.AuthInfo = nil - if creds, ok := s.opts.creds.(credentials.TransportCredentials); ok { - var conn net.Conn - conn, authInfo, err = creds.ServerHandshake(c) - if err != nil { - s.mu.Lock() - s.mu.Unlock() - s.logger.Println("grpc: Proxy.Serve failed to complete security handshake.") - continue - } - c = conn - } - s.mu.Lock() - if s.conns == nil { - s.mu.Unlock() - c.Close() - return nil - } - st, err := transport.NewServerTransport("http2", c, s.opts.maxConcurrentStreams, authInfo) - if err != nil { - s.mu.Unlock() - c.Close() - s.logger.Println("grpc: Proxy.Serve failed to create ServerTransport: ", err) - continue - } - s.conns[st] = true - s.mu.Unlock() - - var wg sync.WaitGroup - st.HandleStreams(func(stream *transport.Stream) { - wg.Add(1) - go func() { - s.handleStream(st, stream) - wg.Done() - }() - }) - wg.Wait() - s.mu.Lock() - delete(s.conns, st) - s.mu.Unlock() + fakeDesc.Streams = append(fakeDesc.Streams, streamDesc) } + server.RegisterService(fakeDesc, streamer) } -func (s *Proxy) handleStream(frontTrans transport.ServerTransport, frontStream *transport.Stream) { - sm := frontStream.Method() - if sm != "" && sm[0] == '/' { - sm = sm[1:] - } - pos := strings.LastIndex(sm, "/") - if pos == -1 { - if err := frontTrans.WriteStatus(frontStream, codes.InvalidArgument, fmt.Sprintf("malformed method name: %q", frontStream.Method())); err != nil { - s.logger.Printf("proxy: Proxy.handleStream failed to write status: %v", err) - } - return - } - ProxyStream(s.director, s.logger, frontTrans, frontStream) - +type proxyStreamer struct { + director StreamDirector } -// Stop stops the gRPC server. Once Stop returns, the server stops accepting -// connection requests and closes all the connected connections. -func (s *Proxy) Stop() { - s.mu.Lock() - listeners := s.lis - s.lis = nil - cs := s.conns - s.conns = nil - s.mu.Unlock() - for lis := range listeners { - lis.Close() +// proxyStreamHandler is where the real magic of proxying happens. +// It is invoked like any gRPC server stream and uses the gRPC server framing to get and receive bytes from the wire, +// forwarding it to a ClientStream established against the relevant ClientConn. +func (s *proxyStreamer) handler(srv interface{}, serverStream grpc.ServerStream) error { + backendConn, err := s.director(serverStream.Context()) + if err != nil { + return err } - for c := range cs { - c.Close() + // little bit of gRPC internals never hurt anyone + lowLevelServerStream, ok := transport.StreamFromContext(serverStream.Context()) + if !ok { + return grpc.Errorf(codes.Internal, "lowLevelServerStream not exists in context") } -} - -// ProxyStream performs a forward of a gRPC frontend stream to a backend. -func ProxyStream(director StreamDirector, logger grpclog.Logger, frontTrans transport.ServerTransport, frontStream *transport.Stream) { - backendTrans, backendStream, err := backendTransportStream(director, frontStream.Context()) + // TODO(mwitkow): Add a `forwarded` header to metadata, https://en.wikipedia.org/wiki/X-Forwarded-For. + clientStream, err := grpc.NewClientStream(serverStream.Context(), clientStreamDescForProxying, backendConn, lowLevelServerStream.Method()) if err != nil { - frontTrans.WriteStatus(frontStream, grpc.Code(err), grpc.ErrorDesc(err)) - logger.Printf("proxy: Proxy.handleStream %v failed to allocate backend: %v", frontStream.Method(), err) - return - } - defer backendTrans.CloseStream(backendStream, nil) - - // data coming from client call to backend - ingressPathChan := forwardDataFrames(frontStream, backendStream, backendTrans) - - // custom header handling *must* be after some data is processed by the backend, otherwise there's a deadlock - headerMd, err := backendStream.Header() - if err == nil && len(headerMd) > 0 { - frontTrans.WriteHeader(frontStream, headerMd) + return err } - // data coming from backend back to client call - egressPathChan := forwardDataFrames(backendStream, frontStream, frontTrans) - - // wait for both data streams to complete. - egressErr := <-egressPathChan - ingressErr := <-ingressPathChan - if egressErr != io.EOF || ingressErr != io.EOF { - logger.Printf("proxy: Proxy.handleStream %v failure during transfer ingres: %v egress: %v", frontStream.Method(), ingressErr, egressErr) - frontTrans.WriteStatus(frontStream, codes.Unavailable, fmt.Sprintf("problem in transfer ingress: %v egress: %v", ingressErr, egressErr)) - return + defer clientStream.CloseSend() // always close this! + s2cErr := <-s.forwardServerToClient(serverStream, clientStream) + c2sErr := <-s.forwardClientToServer(clientStream, serverStream) + if s2cErr != io.EOF { + return grpc.Errorf(codes.Internal, "failed proxying s2c: %v", s2cErr, c2sErr) } - // handle trailing metadata - trailingMd := backendStream.Trailer() - if len(trailingMd) > 0 { - frontStream.SetTrailer(trailingMd) + serverStream.SetTrailer(clientStream.Trailer()) + // c2sErr will contain RPC error from client code. If not io.EOF return the RPC error as server stream error. + if c2sErr != io.EOF { + return c2sErr } - frontTrans.WriteStatus(frontStream, backendStream.StatusCode(), backendStream.StatusDesc()) + return nil } -// backendTransportStream picks and establishes a Stream to the backend. -func backendTransportStream(director StreamDirector, ctx context.Context) (transport.ClientTransport, *transport.Stream, error) { - grpcConn, err := director(ctx) - if err != nil { - if grpc.Code(err) != codes.Unknown { // rpcError check - return nil, nil, err - } else { - return nil, nil, grpc.Errorf(codes.Aborted, "cant dial to backend: %v", err) +func (s *proxyStreamer) forwardClientToServer(src grpc.ClientStream, dst grpc.ServerStream) chan error { + ret := make(chan error, 1) + go func() { + f := &frame{} + for i := 0; ; i++ { + if err := src.RecvMsg(f); err != nil { + ret <- err // this can be io.EOF which is happy case + break + } + if i == 0 { + // This is a bit of a hack, but client to server headers are only readable after first client msg is + // received but must be written to server stream before the first msg is flushed. + // This is the only place to do it nicely. + md, err := src.Header() + if err != nil { + ret <- err + break + } + if err := dst.SendHeader(md); err != nil { + ret <- err + break + } + } + if err := dst.SendMsg(f); err != nil { + ret <- err + break + } } - } - // TODO(michal): ClientConn.GetTransport() IS NOT IN UPSTREAM GRPC! - // To make this work, copy patch/get_transport.go to google.golang.org/grpc/ - backendTrans, _, err := grpcConn.GetTransport(ctx) - frontendStream, _ := transport.StreamFromContext(ctx) - callHdr := &transport.CallHdr{ - Method: frontendStream.Method(), - Host: "TODOFIXTLS", // TODO(michal): This can fail if the backend server is using TLS Hostname verification. Use conn.authority, once it's public? - } - backendStream, err := backendTrans.NewStream(ctx, callHdr) - if err != nil { - return nil, nil, grpc.Errorf(codes.Unknown, "cant establish stream to backend: %v", err) - } - return backendTrans, backendStream, nil + close(ret) + }() + return ret } -// forwardDataFrames moves data from one gRPC transport `Stream` to another in async fashion. -// It returns an error channel. `nil` on it signifies everything was fine, anything else is a serious problem. -func forwardDataFrames(srcStream *transport.Stream, dstStream *transport.Stream, dstTransport transportWriter) chan error { +func (s *proxyStreamer) forwardServerToClient(src grpc.ServerStream, dst grpc.ClientStream) chan error { ret := make(chan error, 1) - go func() { - data := make([]byte, 4096) - opt := &transport.Options{} - for { - n, err := srcStream.Read(data) - if err != nil { // including io.EOF - // Send nil to terminate the stream. - opt.Last = true - dstTransport.Write(dstStream, nil, opt) - ret <- err + f := &frame{} + for i := 0; ; i++ { + if err := src.RecvMsg(f); err != nil { + ret <- err // this can be io.EOF which is happy case break } - if err := dstTransport.Write(dstStream, data[:n], opt); err != nil { + if err := dst.SendMsg(f); err != nil { ret <- err break } @@ -247,4 +120,3 @@ func forwardDataFrames(srcStream *transport.Stream, dstStream *transport.Stream, }() return ret } - diff --git a/proxy_codec.go b/proxy_codec.go new file mode 100644 index 0000000..abe969c --- /dev/null +++ b/proxy_codec.go @@ -0,0 +1,63 @@ +package proxy + +import ( + "fmt" + "google.golang.org/grpc" + "github.com/golang/protobuf/proto" +) + +// ProxyCodec is custom codec for gRPC server that a no-op codec if the unmarshalling is done to/from bytes. +// This is required for proxy functionality (as the proxy doesn't know the types). But in case of methods implemented +// on the server, it falls back to the proto codec. +func ProxyCodec() grpc.ServerOption { + return grpc.CustomCodec(&codec{&protoCodec{}}) +} + +func WithProxyCodec() grpc.DialOption { + return grpc.WithCodec(&codec{&protoCodec{}}) +} + +type codec struct { + parentCodec grpc.Codec +} + +type frame struct { + payload []byte +} + +func (c *codec) Marshal(v interface{}) ([]byte, error) { + out, ok := v.(*frame) + if !ok { + return c.parentCodec.Marshal(v) + } + return out.payload, nil + +} + +func (c *codec) Unmarshal(data []byte, v interface{}) error { + dst, ok := v.(*frame) + if !ok { + return c.parentCodec.Unmarshal(data, v) + } + dst.payload = data + return nil +} + +func (c *codec) String() string { + return fmt.Sprintf("proxy>%s", c.parentCodec.String()) +} + +// protoCodec is a Codec implementation with protobuf. It is the default codec for gRPC. +type protoCodec struct{} + +func (protoCodec) Marshal(v interface{}) ([]byte, error) { + return proto.Marshal(v.(proto.Message)) +} + +func (protoCodec) Unmarshal(data []byte, v interface{}) error { + return proto.Unmarshal(data, v.(proto.Message)) +} + +func (protoCodec) String() string { + return "proto" +} diff --git a/proxy_codec_test.go b/proxy_codec_test.go new file mode 100644 index 0000000..cc2900e --- /dev/null +++ b/proxy_codec_test.go @@ -0,0 +1,23 @@ +package proxy + +import ( + "testing" + "github.com/stretchr/testify/require" +) + +func TestProxyCodec_ReadYourWrites(t *testing.T) { + framePtr := &frame{} + data := []byte{0xDE, 0xAD, 0xBE, 0xEF} + codec := codec{} + require.NoError(t, codec.Unmarshal(data, framePtr), "unmarshalling must go ok") + out, err := codec.Marshal(framePtr) + require.NoError(t, err, "no marshal error") + require.Equal(t, data, out, "output and data must be the same") + + // reuse + require.NoError(t, codec.Unmarshal([]byte{0x55}, framePtr), "unmarshalling must go ok") + out, err = codec.Marshal(framePtr) + require.NoError(t, err, "no marshal error") + require.Equal(t, []byte{0x55}, out, "output and data must be the same") + +} diff --git a/proxy_test.go b/proxy_test.go index 8f55a30..4cdd3d8 100644 --- a/proxy_test.go +++ b/proxy_test.go @@ -1,43 +1,45 @@ -// Copyright © 2015 Michal Witkowski +//// Copyright © 2015 Michal Witkowski +//// +//// 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. // -// 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 proxy_test + import ( - "strings" - "time" "net" + "strings" "testing" - "io" - + "time" "github.com/mwitkow/grpc-proxy" pb "github.com/mwitkow/grpc-proxy/testservice" - "github.com/stretchr/testify/suite" + "io" + "log" + "os" + + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "golang.org/x/net/context" "google.golang.org/grpc" - "google.golang.org/grpc/grpclog" "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" "google.golang.org/grpc/metadata" - "golang.org/x/net/context" - "github.com/stretchr/testify/assert" ) - const ( - pingDefaultValue = "I like kittens." - clientMdKey = "test-client-header" - serverHeaderMdKey = "test-client-header" + pingDefaultValue = "I like kittens." + clientMdKey = "test-client-header" + serverHeaderMdKey = "test-client-header" serverTrailerMdKey = "test-client-trailer" rejectingMdKey = "test-reject-rpc-if-in-context" @@ -81,7 +83,6 @@ func (s *assertingService) PingList(ping *pb.PingRequest, stream pb.TestService_ return nil } - // ProxyHappySuite tests the "happy" path of handling: that everything works in absence of connection issues. type ProxyHappySuite struct { suite.Suite @@ -89,16 +90,20 @@ type ProxyHappySuite struct { serverListener net.Listener server *grpc.Server proxyListener net.Listener - proxy *proxy.Proxy + proxy *grpc.Server - client *grpc.ClientConn - testClient pb.TestServiceClient + client *grpc.ClientConn + testClient pb.TestServiceClient +} - ctx context.Context +func (s *ProxyHappySuite) ctx() context.Context { + // Make all RPC calls last at most 1 sec, meaning all async issues or deadlock will not kill tests. + ctx, _ := context.WithTimeout(context.TODO(), 120*time.Second) + return ctx } func (s *ProxyHappySuite) TestPingEmptyCarriesClientMetadata() { - ctx := metadata.NewContext(s.ctx, metadata.Pairs(clientMdKey, "true")) + ctx := metadata.NewContext(s.ctx(), metadata.Pairs(clientMdKey, "true")) out, err := s.testClient.PingEmpty(ctx, &pb.Empty{}) require.NoError(s.T(), err, "PingEmpty should succeed without errors") require.Equal(s.T(), &pb.PingResponse{Value: pingDefaultValue, Counter: 42}, out) @@ -108,7 +113,7 @@ func (s *ProxyHappySuite) TestPingCarriesServerHeadersAndTrailers() { headerMd := make(metadata.MD) trailerMd := make(metadata.MD) // This is an awkward calling convention... but meh. - out, err := s.testClient.Ping(s.ctx, &pb.PingRequest{Value: "foo"}, grpc.Header(&headerMd), grpc.Trailer(&trailerMd)) + out, err := s.testClient.Ping(s.ctx(), &pb.PingRequest{Value: "foo"}, grpc.Header(&headerMd), grpc.Trailer(&trailerMd)) require.NoError(s.T(), err, "Ping should succeed without errors") require.Equal(s.T(), &pb.PingResponse{Value: "foo", Counter: 42}, out) assert.Len(s.T(), headerMd, 1, "server response headers must contain server data") @@ -116,7 +121,7 @@ func (s *ProxyHappySuite) TestPingCarriesServerHeadersAndTrailers() { } func (s *ProxyHappySuite) TestPingErrorPropagatesAppError() { - _, err := s.testClient.PingError(s.ctx, &pb.PingRequest{Value: "foo"}) + _, err := s.testClient.PingError(s.ctx(), &pb.PingRequest{Value: "foo"}) require.Error(s.T(), err, "PingError should never succeed") assert.Equal(s.T(), codes.FailedPrecondition, grpc.Code(err)) assert.Equal(s.T(), "Userspace error.", grpc.ErrorDesc(err)) @@ -124,7 +129,7 @@ func (s *ProxyHappySuite) TestPingErrorPropagatesAppError() { func (s *ProxyHappySuite) TestDirectorErrorIsPropagated() { // See SetupSuite where the StreamDirector has a special case. - ctx := metadata.NewContext(s.ctx, metadata.Pairs(rejectingMdKey, "true")) + ctx := metadata.NewContext(s.ctx(), metadata.Pairs(rejectingMdKey, "true")) _, err := s.testClient.Ping(ctx, &pb.PingRequest{Value: "foo"}) require.Error(s.T(), err, "Director should reject this RPC") assert.Equal(s.T(), codes.PermissionDenied, grpc.Code(err)) @@ -132,7 +137,7 @@ func (s *ProxyHappySuite) TestDirectorErrorIsPropagated() { } func (s *ProxyHappySuite) TestPingListStreamsAll() { - stream, err := s.testClient.PingList(s.ctx, &pb.PingRequest{Value: "foo"}) + stream, err := s.testClient.PingList(s.ctx(), &pb.PingRequest{Value: "foo"}) require.NoError(s.T(), err, "PingList request should be successful.") // Check that the header arrives before all entries. headerMd, err := stream.Header() @@ -156,20 +161,21 @@ func (s *ProxyHappySuite) TestPingListStreamsAll() { func (s *ProxyHappySuite) SetupSuite() { var err error - logger := &testingLog{(*s.T())} s.proxyListener, err = net.Listen("tcp", "127.0.0.1:0") require.NoError(s.T(), err, "must be able to allocate a port for proxyListener") s.serverListener, err = net.Listen("tcp", "127.0.0.1:0") require.NoError(s.T(), err, "must be able to allocate a port for serverListener") + grpclog.SetLogger(log.New(os.Stderr, "grpc: ", log.LstdFlags)) + s.server = grpc.NewServer() pb.RegisterTestServiceServer(s.server, &assertingService{t: s.T()}) // Setup of the proxy's Director. - proxyClientConn, err := grpc.Dial(s.serverListener.Addr().String(), grpc.WithInsecure()) + proxyClientConn, err := grpc.Dial(s.serverListener.Addr().String(), grpc.WithInsecure(), proxy.WithProxyCodec()) require.NoError(s.T(), err, "must not error on deferred client Dial") - proxyServer := proxy.NewServer(func(ctx context.Context) (*grpc.ClientConn, error) { + director := func(ctx context.Context) (*grpc.ClientConn, error) { md, ok := metadata.FromContext(ctx) if ok { if _, exists := md[rejectingMdKey]; exists { @@ -177,7 +183,13 @@ func (s *ProxyHappySuite) SetupSuite() { } } return proxyClientConn, nil - }, proxy.UsingLogger(logger)) + } + s.proxy = grpc.NewServer( + proxy.ProxyCodec(), + ) + proxy.RegisterProxyStreams(s.proxy, director, + "mwitkow.testproto.TestService", + "PingEmpty", "Ping", "PingError", "PingList") // Start the serving loops. go func() { @@ -186,14 +198,12 @@ func (s *ProxyHappySuite) SetupSuite() { }() go func() { s.T().Logf("starting grpc.Proxy at: %v", s.proxyListener.Addr().String()) - proxyServer.Serve(s.proxyListener) + s.proxy.Serve(s.proxyListener) }() - clientConn, err := grpc.Dial(strings.Replace(s.proxyListener.Addr().String(), "127.0.0.1", "localhost", 1), grpc.WithInsecure()) + clientConn, err := grpc.Dial(strings.Replace(s.proxyListener.Addr().String(), "127.0.0.1", "localhost", 1), grpc.WithInsecure(), grpc.WithTimeout(1*time.Second)) require.NoError(s.T(), err, "must not error on deferred client Dial") s.testClient = pb.NewTestServiceClient(clientConn) - // Make all RPC calls last at most 1 sec, meaning all async issues or deadlock will not kill tests. - s.ctx, _ = context.WithTimeout(context.TODO(), 1 * time.Second) } func (s *ProxyHappySuite) TearDownSuite() { @@ -231,7 +241,6 @@ func (t *testingLog) Printf(format string, args ...interface{}) { t.T.Logf(format, args...) } - func (t *testingLog) Println(args ...interface{}) { t.T.Log(args...) } diff --git a/testservice/test.pb.go b/testservice/test.pb.go index 0d5d9e3..acc40a2 100644 --- a/testservice/test.pb.go +++ b/testservice/test.pb.go @@ -1,38 +1,29 @@ -// Code generated by protoc-gen-gogo. +// Code generated by protoc-gen-go. // source: test.proto // DO NOT EDIT! /* - Package mwitkow_testproto is a generated protocol buffer package. +Package mwitkow_testproto is a generated protocol buffer package. - It is generated from these files: - test.proto +It is generated from these files: + test.proto - It has these top-level messages: - Empty - PingRequest - PingResponse +It has these top-level messages: + Empty + PingRequest + PingResponse */ package mwitkow_testproto import proto "github.com/golang/protobuf/proto" - -import "math" - -import "fmt" -import "strings" -import github_com_golang_protobuf_proto "github.com/golang/protobuf/proto" -import "sort" -import "strconv" -import "reflect" +import fmt "fmt" +import math "math" import ( context "golang.org/x/net/context" grpc "google.golang.org/grpc" ) -import "io" - // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf @@ -40,7 +31,9 @@ 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. -const _ = proto.ProtoPackageIsVersion1 +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package type Empty struct { } @@ -48,176 +41,52 @@ type Empty struct { func (m *Empty) Reset() { *m = Empty{} } func (m *Empty) String() string { return proto.CompactTextString(m) } func (*Empty) ProtoMessage() {} -func (*Empty) Descriptor() ([]byte, []int) { return fileDescriptorTest, []int{0} } +func (*Empty) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } type PingRequest struct { - Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` + Value string `protobuf:"bytes,1,opt,name=value" json:"value,omitempty"` } func (m *PingRequest) Reset() { *m = PingRequest{} } func (m *PingRequest) String() string { return proto.CompactTextString(m) } func (*PingRequest) ProtoMessage() {} -func (*PingRequest) Descriptor() ([]byte, []int) { return fileDescriptorTest, []int{1} } +func (*PingRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } + +func (m *PingRequest) GetValue() string { + if m != nil { + return m.Value + } + return "" +} type PingResponse struct { - Value string `protobuf:"bytes,1,opt,name=Value,json=value,proto3" json:"Value,omitempty"` - Counter int32 `protobuf:"varint,2,opt,name=counter,proto3" json:"counter,omitempty"` + Value string `protobuf:"bytes,1,opt,name=Value,json=value" json:"Value,omitempty"` + Counter int32 `protobuf:"varint,2,opt,name=counter" json:"counter,omitempty"` } func (m *PingResponse) Reset() { *m = PingResponse{} } func (m *PingResponse) String() string { return proto.CompactTextString(m) } func (*PingResponse) ProtoMessage() {} -func (*PingResponse) Descriptor() ([]byte, []int) { return fileDescriptorTest, []int{2} } - -func init() { - proto.RegisterType((*Empty)(nil), "mwitkow.testproto.Empty") - proto.RegisterType((*PingRequest)(nil), "mwitkow.testproto.PingRequest") - proto.RegisterType((*PingResponse)(nil), "mwitkow.testproto.PingResponse") -} -func (this *Empty) Equal(that interface{}) bool { - if that == nil { - if this == nil { - return true - } - return false - } +func (*PingResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } - that1, ok := that.(*Empty) - if !ok { - that2, ok := that.(Empty) - if ok { - that1 = &that2 - } else { - return false - } - } - if that1 == nil { - if this == nil { - return true - } - return false - } else if this == nil { - return false +func (m *PingResponse) GetValue() string { + if m != nil { + return m.Value } - return true + return "" } -func (this *PingRequest) Equal(that interface{}) bool { - if that == nil { - if this == nil { - return true - } - return false - } - that1, ok := that.(*PingRequest) - if !ok { - that2, ok := that.(PingRequest) - if ok { - that1 = &that2 - } else { - return false - } +func (m *PingResponse) GetCounter() int32 { + if m != nil { + return m.Counter } - if that1 == nil { - if this == nil { - return true - } - return false - } else if this == nil { - return false - } - if this.Value != that1.Value { - return false - } - return true + return 0 } -func (this *PingResponse) Equal(that interface{}) bool { - if that == nil { - if this == nil { - return true - } - return false - } - that1, ok := that.(*PingResponse) - if !ok { - that2, ok := that.(PingResponse) - if ok { - that1 = &that2 - } else { - return false - } - } - if that1 == nil { - if this == nil { - return true - } - return false - } else if this == nil { - return false - } - if this.Value != that1.Value { - return false - } - if this.Counter != that1.Counter { - return false - } - return true -} -func (this *Empty) GoString() string { - if this == nil { - return "nil" - } - s := make([]string, 0, 4) - s = append(s, "&mwitkow_testproto.Empty{") - s = append(s, "}") - return strings.Join(s, "") -} -func (this *PingRequest) GoString() string { - if this == nil { - return "nil" - } - s := make([]string, 0, 5) - s = append(s, "&mwitkow_testproto.PingRequest{") - s = append(s, "Value: "+fmt.Sprintf("%#v", this.Value)+",\n") - s = append(s, "}") - return strings.Join(s, "") -} -func (this *PingResponse) GoString() string { - if this == nil { - return "nil" - } - s := make([]string, 0, 6) - s = append(s, "&mwitkow_testproto.PingResponse{") - s = append(s, "Value: "+fmt.Sprintf("%#v", this.Value)+",\n") - s = append(s, "Counter: "+fmt.Sprintf("%#v", this.Counter)+",\n") - s = append(s, "}") - return strings.Join(s, "") -} -func valueToGoStringTest(v interface{}, typ string) string { - rv := reflect.ValueOf(v) - if rv.IsNil() { - return "nil" - } - pv := reflect.Indirect(rv).Interface() - return fmt.Sprintf("func(v %v) *%v { return &v } ( %#v )", typ, typ, pv) -} -func extensionToGoStringTest(e map[int32]github_com_golang_protobuf_proto.Extension) string { - if e == nil { - return "nil" - } - s := "map[int32]proto.Extension{" - keys := make([]int, 0, len(e)) - for k := range e { - keys = append(keys, int(k)) - } - sort.Ints(keys) - ss := []string{} - for _, k := range keys { - ss = append(ss, strconv.Itoa(k)+": "+e[int32(k)].GoString()) - } - s += strings.Join(ss, ",") + "}" - return s +func init() { + proto.RegisterType((*Empty)(nil), "mwitkow.testproto.Empty") + proto.RegisterType((*PingRequest)(nil), "mwitkow.testproto.PingRequest") + proto.RegisterType((*PingResponse)(nil), "mwitkow.testproto.PingResponse") } // Reference imports to suppress errors if they are not otherwise used. @@ -226,7 +95,7 @@ var _ grpc.ClientConn // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion3 +const _ = grpc.SupportPackageIsVersion4 // Client API for TestService service @@ -273,7 +142,7 @@ func (c *testServiceClient) PingError(ctx context.Context, in *PingRequest, opts } func (c *testServiceClient) PingList(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (TestService_PingListClient, error) { - stream, err := grpc.NewClientStream(ctx, &TestService_serviceDesc.Streams[0], c.cc, "/mwitkow.testproto.TestService/PingList", opts...) + stream, err := grpc.NewClientStream(ctx, &_TestService_serviceDesc.Streams[0], c.cc, "/mwitkow.testproto.TestService/PingList", opts...) if err != nil { return nil, err } @@ -314,7 +183,7 @@ type TestServiceServer interface { } func RegisterTestServiceServer(s *grpc.Server, srv TestServiceServer) { - s.RegisterService(&TestService_serviceDesc, srv) + s.RegisterService(&_TestService_serviceDesc, srv) } func _TestService_PingEmpty_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { @@ -392,7 +261,7 @@ func (x *testServicePingListServer) Send(m *PingResponse) error { return x.ServerStream.SendMsg(m) } -var TestService_serviceDesc = grpc.ServiceDesc{ +var _TestService_serviceDesc = grpc.ServiceDesc{ ServiceName: "mwitkow.testproto.TestService", HandlerType: (*TestServiceServer)(nil), Methods: []grpc.MethodDesc{ @@ -416,497 +285,25 @@ var TestService_serviceDesc = grpc.ServiceDesc{ ServerStreams: true, }, }, - Metadata: fileDescriptorTest, -} - -func (m *Empty) 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 *Empty) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - return i, nil -} - -func (m *PingRequest) 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 *PingRequest) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.Value) > 0 { - data[i] = 0xa - i++ - i = encodeVarintTest(data, i, uint64(len(m.Value))) - i += copy(data[i:], m.Value) - } - return i, nil -} - -func (m *PingResponse) 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 *PingResponse) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.Value) > 0 { - data[i] = 0xa - i++ - i = encodeVarintTest(data, i, uint64(len(m.Value))) - i += copy(data[i:], m.Value) - } - if m.Counter != 0 { - data[i] = 0x10 - i++ - i = encodeVarintTest(data, i, uint64(m.Counter)) - } - return i, nil -} - -func encodeFixed64Test(data []byte, offset int, v uint64) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - data[offset+4] = uint8(v >> 32) - data[offset+5] = uint8(v >> 40) - data[offset+6] = uint8(v >> 48) - data[offset+7] = uint8(v >> 56) - return offset + 8 -} -func encodeFixed32Test(data []byte, offset int, v uint32) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - return offset + 4 -} -func encodeVarintTest(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 *Empty) Size() (n int) { - var l int - _ = l - return n -} - -func (m *PingRequest) Size() (n int) { - var l int - _ = l - l = len(m.Value) - if l > 0 { - n += 1 + l + sovTest(uint64(l)) - } - return n + Metadata: "test.proto", } -func (m *PingResponse) Size() (n int) { - var l int - _ = l - l = len(m.Value) - if l > 0 { - n += 1 + l + sovTest(uint64(l)) - } - if m.Counter != 0 { - n += 1 + sovTest(uint64(m.Counter)) - } - return n -} - -func sovTest(x uint64) (n int) { - for { - n++ - x >>= 7 - if x == 0 { - break - } - } - return n -} -func sozTest(x uint64) (n int) { - return sovTest(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (m *Empty) 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 ErrIntOverflowTest - } - 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: Empty: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: Empty: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipTest(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthTest - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *PingRequest) 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 ErrIntOverflowTest - } - 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: PingRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: PingRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTest - } - 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 ErrInvalidLengthTest - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Value = string(data[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipTest(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthTest - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *PingResponse) 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 ErrIntOverflowTest - } - 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: PingResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: PingResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTest - } - 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 ErrInvalidLengthTest - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Value = string(data[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Counter", wireType) - } - m.Counter = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTest - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.Counter |= (int32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - default: - iNdEx = preIndex - skippy, err := skipTest(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthTest - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func skipTest(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, ErrIntOverflowTest - } - 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, ErrIntOverflowTest - } - 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, ErrIntOverflowTest - } - 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, ErrInvalidLengthTest - } - return iNdEx, nil - case 3: - for { - var innerWire uint64 - var start int = iNdEx - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowTest - } - 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 := skipTest(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 ( - ErrInvalidLengthTest = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowTest = fmt.Errorf("proto: integer overflow") -) +func init() { proto.RegisterFile("test.proto", fileDescriptor0) } -var fileDescriptorTest = []byte{ - // 244 bytes of a gzipped FileDescriptorProto +var fileDescriptor0 = []byte{ + // 218 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x2a, 0x49, 0x2d, 0x2e, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0xcc, 0x2d, 0xcf, 0x2c, 0xc9, 0xce, 0x2f, 0xd7, 0x03, 0x89, 0x81, 0x85, 0x94, 0xd8, 0xb9, 0x58, 0x5d, 0x73, 0x0b, 0x4a, 0x2a, 0x95, 0x94, 0xb9, - 0xb8, 0x03, 0x32, 0xf3, 0xd2, 0x83, 0x52, 0x0b, 0x4b, 0x81, 0x92, 0x42, 0x22, 0x5c, 0xac, 0x65, - 0x89, 0x39, 0xa5, 0xa9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x10, 0x8e, 0x92, 0x1d, 0x17, - 0x0f, 0x44, 0x51, 0x71, 0x41, 0x7e, 0x5e, 0x71, 0x2a, 0x48, 0x55, 0x18, 0x86, 0x2a, 0x21, 0x09, - 0x2e, 0xf6, 0xe4, 0xfc, 0xd2, 0xbc, 0x92, 0xd4, 0x22, 0x09, 0x26, 0xa0, 0x38, 0x6b, 0x10, 0x8c, - 0x6b, 0xb4, 0x87, 0x89, 0x8b, 0x3b, 0x04, 0x68, 0x7c, 0x70, 0x6a, 0x51, 0x59, 0x66, 0x72, 0xaa, - 0x90, 0x07, 0x17, 0x27, 0xc8, 0x3c, 0xb0, 0x0b, 0x84, 0x24, 0xf4, 0x30, 0x9c, 0xa7, 0x07, 0x96, - 0x91, 0x92, 0xc7, 0x22, 0x83, 0xec, 0x0e, 0x25, 0x06, 0x21, 0x4f, 0x2e, 0x16, 0x90, 0x88, 0x90, - 0x1c, 0x4e, 0xa5, 0x60, 0x7f, 0x11, 0x63, 0x94, 0x3b, 0xd4, 0x51, 0x45, 0x45, 0xf9, 0x45, 0x04, - 0xcd, 0xc3, 0xe9, 0x68, 0xa0, 0x41, 0xfe, 0x5c, 0x1c, 0x20, 0xa5, 0x3e, 0x99, 0xc0, 0xf0, 0xa4, - 0xdc, 0x5d, 0x06, 0x8c, 0x4e, 0x0a, 0x17, 0x1e, 0xca, 0x31, 0x7c, 0x78, 0x28, 0xc7, 0xb8, 0xe2, - 0x91, 0x1c, 0xe3, 0x09, 0x20, 0xbe, 0x00, 0xc4, 0x0f, 0x80, 0x78, 0xc2, 0x63, 0x39, 0x86, 0x19, - 0x40, 0x9c, 0xc4, 0x06, 0xd6, 0x69, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0xcf, 0x30, 0xf3, 0xf0, - 0xf6, 0x01, 0x00, 0x00, + 0xb8, 0x03, 0x32, 0xf3, 0xd2, 0x83, 0x52, 0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x84, 0x44, 0xb8, 0x58, + 0xcb, 0x12, 0x73, 0x4a, 0x53, 0x25, 0x18, 0x15, 0x18, 0x35, 0x38, 0x83, 0x20, 0x1c, 0x25, 0x3b, + 0x2e, 0x1e, 0x88, 0xa2, 0xe2, 0x82, 0xfc, 0xbc, 0xe2, 0x54, 0x90, 0xaa, 0x30, 0x0c, 0x55, 0x42, + 0x12, 0x5c, 0xec, 0xc9, 0xf9, 0xa5, 0x79, 0x25, 0xa9, 0x45, 0x12, 0x4c, 0x0a, 0x8c, 0x1a, 0xac, + 0x41, 0x30, 0xae, 0xd1, 0x1e, 0x26, 0x2e, 0xee, 0x90, 0xd4, 0xe2, 0x92, 0xe0, 0xd4, 0xa2, 0xb2, + 0xcc, 0xe4, 0x54, 0x21, 0x0f, 0x2e, 0x4e, 0x90, 0x79, 0x60, 0x17, 0x08, 0x49, 0xe8, 0x61, 0x38, + 0x4f, 0x0f, 0x2c, 0x23, 0x25, 0x8f, 0x45, 0x06, 0xd9, 0x1d, 0x4a, 0x0c, 0x42, 0x9e, 0x5c, 0x2c, + 0x20, 0x11, 0x21, 0x39, 0x9c, 0x4a, 0xc1, 0xfe, 0x22, 0xc6, 0x28, 0x77, 0xa8, 0xa3, 0x8a, 0x8a, + 0xf2, 0x8b, 0x08, 0x9a, 0x87, 0xd3, 0xd1, 0x4a, 0x0c, 0x42, 0xfe, 0x5c, 0x1c, 0x20, 0xa5, 0x3e, + 0x99, 0xc5, 0x25, 0x54, 0x70, 0x97, 0x01, 0x63, 0x12, 0x1b, 0x58, 0xdc, 0x18, 0x10, 0x00, 0x00, + 0xff, 0xff, 0x7b, 0xc9, 0x16, 0xf1, 0xd4, 0x01, 0x00, 0x00, }