github.com/nya3jp/tast@v0.0.0-20230601000426-85c8e4d83a9b/src/go.chromium.org/tast/core/internal/rpc/pipe.go (about) 1 // Copyright 2019 The ChromiumOS Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package rpc 6 7 import ( 8 "context" 9 "io" 10 "net" 11 "sync" 12 "time" 13 14 "google.golang.org/grpc" 15 16 "go.chromium.org/tast/core/errors" 17 ) 18 19 var ( 20 // fakeAddr is a fake IPv4 address. 21 fakeAddr = &net.IPAddr{IP: net.IPv4zero} 22 23 // errNotImpl is returned from unimplemented methods in pipeConn. 24 errNotImpl = errors.New("not implemented") 25 ) 26 27 // pipeConn is a pseudo net.Conn implementation based on io.Reader and io.Writer. 28 type pipeConn struct { 29 r io.Reader 30 w io.Writer 31 c func() error // if not nil, called on the first Close 32 33 closed bool // true after Close is called 34 mu sync.Mutex // protects closed 35 } 36 37 // Read reads data from the underlying io.Reader. 38 func (c *pipeConn) Read(b []byte) (n int, err error) { 39 return c.r.Read(b) 40 } 41 42 // Write writes data to the underlying io.Writer. 43 func (c *pipeConn) Write(b []byte) (n int, err error) { 44 return c.w.Write(b) 45 } 46 47 // Close calls c if it is not nil. 48 func (c *pipeConn) Close() error { 49 c.mu.Lock() 50 closed := c.closed 51 c.closed = true 52 c.mu.Unlock() 53 54 // Needs to protect from calling Close more than once. For example, grpc-go-1.25.0 calls Close twice. 55 if closed { 56 return errors.New("pipeConn: Close was already called") 57 } 58 59 if c.c == nil { 60 return nil 61 } 62 return c.c() 63 } 64 65 // LocalAddr returns a fake IPv4 address. 66 func (c *pipeConn) LocalAddr() net.Addr { 67 return fakeAddr 68 } 69 70 // RemoteAddr returns a fake IPv4 address. 71 func (c *pipeConn) RemoteAddr() net.Addr { 72 return fakeAddr 73 } 74 75 // SetDeadline always returns not implemented error. 76 func (c *pipeConn) SetDeadline(t time.Time) error { 77 return errNotImpl 78 } 79 80 // SetReadDeadline always returns not implemented error. 81 func (c *pipeConn) SetReadDeadline(t time.Time) error { 82 return errNotImpl 83 } 84 85 // SetWriteDeadline always returns not implemented error. 86 func (c *pipeConn) SetWriteDeadline(t time.Time) error { 87 return errNotImpl 88 } 89 90 var _ net.Conn = (*pipeConn)(nil) 91 92 // PipeListener is a pseudo net.Listener implementation based on io.Reader and 93 // io.Writer. PipeListener's Accept returns exactly one net.Conn that is based 94 // on the given io.Reader and io.Writer. When the connection is closed, Accept 95 // returns io.EOF. 96 // 97 // PipeListener is suitable for running a gRPC server over a bidirectional pipe. 98 type PipeListener struct { 99 ch chan *pipeConn 100 } 101 102 // NewPipeListener constructs a new PipeListener based on r and w. 103 func NewPipeListener(r io.Reader, w io.Writer) *PipeListener { 104 connCh := make(chan *pipeConn, 1) 105 lis := &PipeListener{ch: connCh} 106 conn := &pipeConn{ 107 r: r, 108 w: w, 109 c: func() error { 110 close(connCh) 111 return nil 112 }, 113 } 114 connCh <- conn 115 return lis 116 } 117 118 // Accept returns a connection. See the comment of PipeListener for its behavior. 119 func (l *PipeListener) Accept() (net.Conn, error) { 120 conn, ok := <-l.ch 121 if !ok { 122 return nil, io.EOF 123 } 124 return conn, nil 125 } 126 127 // Close closes the listener. 128 func (l *PipeListener) Close() error { 129 return nil 130 } 131 132 // Addr returns a fake IPv4 address. 133 func (l *PipeListener) Addr() net.Addr { 134 return fakeAddr 135 } 136 137 var _ net.Listener = (*PipeListener)(nil) 138 139 // NewPipeClientConn constructs ClientConn based on r and w. 140 // 141 // The returned ClientConn is suitable for talking with a gRPC server over a 142 // bidirectional pipe. 143 func NewPipeClientConn(ctx context.Context, r io.Reader, w io.Writer, extraOpts ...grpc.DialOption) (*grpc.ClientConn, error) { 144 opts := append([]grpc.DialOption{ 145 grpc.WithInsecure(), 146 // TODO(crbug.com/989419): Use grpc.WithContextDialer after updating grpc-go. 147 grpc.WithDialer(func(string, time.Duration) (net.Conn, error) { 148 return &pipeConn{r: r, w: w}, nil 149 }), 150 }, extraOpts...) 151 return grpc.DialContext(ctx, "", opts...) 152 }