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 }