github.com/lyft/flytestdlib@v0.3.12-0.20210213045714-8cdd111ecda1/pbhash/pbhash.go (about)

     1  // This is a package that provides hashing utilities for Protobuf objects.
     2  package pbhash
     3  
     4  import (
     5  	"context"
     6  	"encoding/base64"
     7  
     8  	goObjectHash "github.com/benlaurie/objecthash/go/objecthash"
     9  	"github.com/golang/protobuf/jsonpb"
    10  	"github.com/golang/protobuf/proto"
    11  	"github.com/lyft/flytestdlib/logger"
    12  )
    13  
    14  var marshaller = &jsonpb.Marshaler{}
    15  
    16  func fromHashToByteArray(input [32]byte) []byte {
    17  	output := make([]byte, 32)
    18  	for idx, val := range input {
    19  		output[idx] = val
    20  	}
    21  	return output
    22  }
    23  
    24  // Generate a deterministic hash in bytes for the pb object
    25  func ComputeHash(ctx context.Context, pb proto.Message) ([]byte, error) {
    26  	// We marshal the pb object to JSON first which should provide a consistent mapping of pb to json fields as stated
    27  	// here: https://developers.google.com/protocol-buffers/docs/proto3#json
    28  	// jsonpb marshalling includes:
    29  	// - sorting map values to provide a stable output
    30  	// - omitting empty values which supports backwards compatibility of old protobuf definitions
    31  	// We do not use protobuf marshalling because it does not guarantee stable output because of how it handles
    32  	// unknown fields and ordering of fields. https://github.com/protocolbuffers/protobuf/issues/2830
    33  	pbJSON, err := marshaller.MarshalToString(pb)
    34  	if err != nil {
    35  		logger.Warning(ctx, "failed to marshal pb [%+v] to JSON with err %v", pb, err)
    36  		return nil, err
    37  	}
    38  
    39  	// Deterministically hash the JSON object to a byte array. The library will sort the map keys of the JSON object
    40  	// so that we do not run into the issues from pb marshalling.
    41  	hash, err := goObjectHash.CommonJSONHash(pbJSON)
    42  	if err != nil {
    43  		logger.Warning(ctx, "failed to hash JSON for pb [%+v] with err %v", pb, err)
    44  		return nil, err
    45  	}
    46  
    47  	return fromHashToByteArray(hash), err
    48  }
    49  
    50  // Generate a deterministic hash as a base64 encoded string for the pb object.
    51  func ComputeHashString(ctx context.Context, pb proto.Message) (string, error) {
    52  	hashBytes, err := ComputeHash(ctx, pb)
    53  	if err != nil {
    54  		return "", err
    55  	}
    56  
    57  	return base64.StdEncoding.EncodeToString(hashBytes), err
    58  }