func GetTLSCertHash(cert *tls.Certificate, hasher Hasher) ([]byte, error) {
	if cert == nil || len(cert.Certificate) == 0 {
		return nil, nil
	}
	tlsCertHash, err := hasher.Hash(cert.Certificate[0])
	if err != nil {
		return nil, errors.WithMessage(err, "failed to compute SHA256 on client certificate")
	}
	return tlsCertHash, nil
}
GetTLSCertHash computes SHA2-256 on tls certificate
package grpc
import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"io/ioutil"
	"sync"
	"time"
	"github.com/hyperledger-labs/fabric-smart-client/platform/view/services/flogging"
	"github.com/pkg/errors"
	"google.golang.org/grpc"
	"google.golang.org/grpc/keepalive"
)
var commLogger = flogging.MustGetLogger("view-sdk.comm")
type Client struct {
	tlsConfig *tls.Config
	dialOpts []grpc.DialOption
	timeout time.Duration
	maxRecvMsgSize int
	maxSendMsgSize int
	grpcConns []*grpc.ClientConn
	grpcCMux sync.Mutex
}
func NewGRPCClient(config ClientConfig) (*Client, error) {
	client := &Client{
		grpcConns: []*grpc.ClientConn{},
	}
	err := client.parseSecureOptions(config.SecOpts)
	if err != nil {
		return client, err
	}
	kap := keepalive.ClientParameters{
		Time:                config.KaOpts.ClientInterval,
		Timeout:             config.KaOpts.ClientTimeout,
		PermitWithoutStream: true,
	}
	client.dialOpts = append(client.dialOpts, grpc.WithKeepaliveParams(kap))
	if !config.AsyncConnect {
		client.dialOpts = append(client.dialOpts, grpc.WithBlock())
		client.dialOpts = append(client.dialOpts, grpc.FailOnNonTempDialError(true))
	}
	client.timeout = config.Timeout
	client.maxRecvMsgSize = MaxRecvMsgSize
	client.maxSendMsgSize = MaxSendMsgSize
	return client, nil
}
func (client *Client) parseSecureOptions(opts SecureOptions) error {
	if !opts.UseTLS {
		return nil
	}
	client.tlsConfig = &tls.Config{
		VerifyPeerCertificate: opts.VerifyCertificate,
		MinVersion:            tls.VersionTLS12}  
	if len(opts.ServerRootCAs) > 0 {
		client.tlsConfig.RootCAs = x509.NewCertPool()
		for _, certBytes := range opts.ServerRootCAs {
			err := AddPemToCertPool(certBytes, client.tlsConfig.RootCAs)
			if err != nil {
				commLogger.Debugf("error adding root certificate: %v", err)
				return errors.WithMessage(err,
					"error adding root certificate")
			}
		}
	}
	if opts.RequireClientCert {
		if opts.Key != nil &&
			opts.Certificate != nil {
			cert, err := tls.X509KeyPair(opts.Certificate,
				opts.Key)
			if err != nil {
				return errors.WithMessage(err, "failed to "+
					"load client certificate")
			}
			client.tlsConfig.Certificates = append(
				client.tlsConfig.Certificates, cert)
		} else {
			return errors.New("both Key and Certificate " +
				"are required when using mutual TLS")
		}
	}
	if opts.TimeShift > 0 {
		client.tlsConfig.Time = func() time.Time {
			return time.Now().Add((-1) * opts.TimeShift)
		}
	}
	return nil
}
func (client *Client) Certificate() tls.Certificate {
	cert := tls.Certificate{}
	if client.tlsConfig != nil && len(client.tlsConfig.Certificates) > 0 {
		cert = client.tlsConfig.Certificates[0]
	}
	return cert
}
func (client *Client) TLSEnabled() bool {
	return client.tlsConfig != nil
}
func (client *Client) MutualTLSRequired() bool {
	return client.tlsConfig != nil &&
		len(client.tlsConfig.Certificates) > 0
}
func (client *Client) SetMaxRecvMsgSize(size int) {
	client.maxRecvMsgSize = size
}
func (client *Client) SetMaxSendMsgSize(size int) {
	client.maxSendMsgSize = size
}
func (client *Client) SetServerRootCAs(serverRoots [][]byte) error {
	certPool := x509.NewCertPool()
	for _, root := range serverRoots {
		err := AddPemToCertPool(root, certPool)
		if err != nil {
			return errors.WithMessage(err, "error adding root certificate")
		}
	}
	client.tlsConfig.RootCAs = certPool
	return nil
}
type TLSOption func(tlsConfig *tls.Config)
func ServerNameOverride(name string) TLSOption {
	return func(tlsConfig *tls.Config) {
		tlsConfig.ServerName = name
	}
}
func CertPoolOverride(pool *x509.CertPool) TLSOption {
	return func(tlsConfig *tls.Config) {
		tlsConfig.RootCAs = pool
	}
}
func (client *Client) NewConnection(address string, tlsOptions ...TLSOption) (*grpc.ClientConn, error) {
	var dialOpts []grpc.DialOption
	dialOpts = append(dialOpts, client.dialOpts...)
	if client.tlsConfig != nil {
		dialOpts = append(dialOpts, grpc.WithTransportCredentials(
			&DynamicClientCredentials{
				TLSConfig:  client.tlsConfig,
				TLSOptions: tlsOptions,
			},
		))
	} else {
		dialOpts = append(dialOpts, grpc.WithInsecure())
	}
	dialOpts = append(dialOpts, grpc.WithDefaultCallOptions(
		grpc.MaxCallRecvMsgSize(client.maxRecvMsgSize),
		grpc.MaxCallSendMsgSize(client.maxSendMsgSize),
	))
	ctx, cancel := context.WithTimeout(context.Background(), client.timeout)
	defer cancel()
	conn, err := grpc.DialContext(ctx, address, dialOpts...)
	if err != nil {
		commLogger.Debugf("failed to create new connection to [%s][%v]: [%s]", address, dialOpts, errors.WithStack(err))
		return nil, errors.WithMessage(errors.WithStack(err), "failed to create new connection")
	}
	client.grpcCMux.Lock()
	client.grpcConns = append(client.grpcConns, conn)
	client.grpcCMux.Unlock()
	return conn, nil
}
func (client *Client) Close() {
	commLogger.Debugf("closing %d grpc connections", len(client.grpcConns))
	for _, grpcCon := range client.grpcConns {
		if err := grpcCon.Close(); err != nil {
			commLogger.Warningf("unable to close grpc conn but continue. Reason: %s", err.Error())
		}
	}
}
func CreateGRPCClient(config *ConnectionConfig) (*Client, error) {
	timeout := config.ConnectionTimeout
	if timeout <= 0 {
		timeout = DefaultConnectionTimeout
	}
	clientConfig := ClientConfig{
		KaOpts: KeepaliveOptions{
			ClientInterval:    60 * time.Second,
			ClientTimeout:     60 * time.Second,
			ServerInterval:    30 * time.Second,
			ServerTimeout:     60 * time.Second,
			ServerMinInterval: 60 * time.Second,
		},
		Timeout: timeout,
	}
	if config.TLSEnabled {
		var certs [][]byte
		switch {
		case len(config.TLSRootCertFile) != 0:
			caPEM, err := ioutil.ReadFile(config.TLSRootCertFile)
			if err != nil {
				return nil, errors.WithMessagef(err, "unable to load TLS cert from %s", config.TLSRootCertFile)
			}
			certs = append(certs, caPEM)
		case len(config.TLSRootCertBytes) != 0:
			certs = config.TLSRootCertBytes
		default:
			return nil, errors.New("missing TLSRootCertFile in client config")
		}
		secOpts := SecureOptions{
			UseTLS:            true,
			ServerRootCAs:     certs,
			RequireClientCert: false,
		}
		clientConfig.SecOpts = secOpts
	}
	return NewGRPCClient(clientConfig)
}
type Hasher interface {
	Hash(msg []byte) (hash []byte, err error)
}