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  }