Skip to content

Commit

Permalink
Merge pull request #26 from solo-io/tjons/restore-legacy-hashing
Browse files Browse the repository at this point in the history
  • Loading branch information
tjons authored Jun 26, 2024
2 parents d9c967c + edbcede commit 08ea32b
Show file tree
Hide file tree
Showing 7 changed files with 881 additions and 100 deletions.
3 changes: 2 additions & 1 deletion templates/hash/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ var (
)
{{ range .AllMessages }}
{{ template "msg" . }}
{{ template "hash" . }}
{{ template "uniquehash" . }}
{{ end }}
`
10 changes: 10 additions & 0 deletions templates/hash/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,32 @@ const messageTpl = `
`

const primitiveTmpl = `
{{ if .UniqueHash }}
if _, err = {{ .Hasher }}.Write([]byte("{{ .FieldName }}")); err != nil {
return 0, err
}
{{ end }}
err = binary.Write({{ .Hasher }}, binary.LittleEndian, {{ .FieldAccessor }} )
if err != nil {return 0, err}
`

const bytesTpl = `
{{ if .UniqueHash }}
if _, err = {{ .Hasher }}.Write([]byte("{{ .FieldName }}")); err != nil {
return 0, err
}
{{ end }}
if _, err = {{ .Hasher }}.Write({{ .FieldAccessor }}); err != nil {
return 0, err
}
`

const stringTpl = `
{{ if .UniqueHash }}
if _, err = {{ .Hasher }}.Write([]byte("{{ .FieldName }}")); err != nil {
return 0, err
}
{{ end }}
if _, err = {{ .Hasher }}.Write([]byte({{ .FieldAccessor }})); err != nil {
return 0, err
}
Expand All @@ -69,13 +75,17 @@ const mapTpl = `
`

const repeatedTpl = `
{{ if .UniqueHash }}
if _, err = hasher.Write([]byte("{{ .FieldName }}")); err != nil {
return 0, err
}
for i, v := range {{ .FieldAccessor }} {
if _, err = hasher.Write([]byte(strconv.Itoa(i))); err != nil {
return 0, err
}
{{ else }}
for _, v := range {{ .FieldAccessor }} {
{{ end }}
{{ .InnerTemplates.Value }}
}
`
47 changes: 43 additions & 4 deletions templates/hash/msg.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package hash

const msgTpl = `
const hashTpl = `
// Hash function
//
// Deprecated: due to hashing implemention only using field values. The omission
// of the field name in the hash calculation can lead to hash collisions.
// Prefer the HashUnique function instead.
func (m {{ (msgTyp .).Pointer }}) Hash(hasher hash.Hash64) (uint64, error) {
if m == nil { return 0, nil }
if hasher == nil { hasher = fnv.New64() }
Expand All @@ -11,17 +15,52 @@ func (m {{ (msgTyp .).Pointer }}) Hash(hasher hash.Hash64) (uint64, error) {
}
{{ range .NonOneOfFields }}
{{ render . }}
{{ render . false }}
{{ end }}
{{ range .SyntheticOneOfFields }}
{{ render . }}
{{ render . false }}
{{ end }}
{{ range .RealOneOfs }}
switch m.{{ name . }}.(type) {
{{ range .Fields }}
case {{ oneof . }}:
{{ render . }}
{{ render . false }}
{{ end }}
}
{{ end }}
return hasher.Sum64(), nil
}
`

const uniqueHashTpl = `
// HashUnique function generates a hash of the object that is unique to the object by
// hashing field name and value pairs.
// Replaces Hash due to original hashing implemention only using field values. The omission
// of the field name in the hash calculation can lead to hash collisions.
func (m {{ (msgTyp .).Pointer }}) HashUnique(hasher hash.Hash64) (uint64, error) {
if m == nil { return 0, nil }
if hasher == nil { hasher = fnv.New64() }
var err error
if _, err = hasher.Write([]byte("{{ fullPackageName . }}")); err != nil {
return 0, err
}
{{ range .NonOneOfFields }}
{{ render . true }}
{{ end }}
{{ range .SyntheticOneOfFields }}
{{ render . true }}
{{ end }}
{{ range .RealOneOfs }}
switch m.{{ name . }}.(type) {
{{ range .Fields }}
case {{ oneof . }}:
{{ render . true }}
{{ end }}
}
{{ end }}
Expand Down
3 changes: 2 additions & 1 deletion templates/hash/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ import (
func Register(tpl *template.Template, params pgs.Parameters) {
register(tpl, params)
template.Must(tpl.Parse(fileTpl))
template.Must(tpl.New("msg").Parse(msgTpl))
template.Must(tpl.New("hash").Parse(hashTpl))
template.Must(tpl.New("uniquehash").Parse(uniqueHashTpl))
}
24 changes: 14 additions & 10 deletions templates/hash/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ type Value struct {
Key string
Value string
}
UniqueHash bool
}

func (fns goSharedFuncs) render(field pgs.Field) (string, error) {
func (fns goSharedFuncs) render(field pgs.Field, hashUnique bool) (string, error) {
var tpl *template.Template

// check if skip hash is set on a given field
Expand All @@ -38,9 +39,9 @@ func (fns goSharedFuncs) render(field pgs.Field) (string, error) {

tpl = template.Must(fns.tpl.New("primitive").Parse(primitiveTmpl))
} else if field.Type().IsMap() {
return fns.renderMap(field)
return fns.renderMap(field, hashUnique)
} else if field.Type().IsRepeated() {
return fns.renderRepeated(field)
return fns.renderRepeated(field, hashUnique)
} else {
switch field.Type().ProtoType() {
case pgs.BytesT:
Expand All @@ -59,25 +60,26 @@ func (fns goSharedFuncs) render(field pgs.Field) (string, error) {
FieldAccessor: fns.accessor(field),
FieldName: fns.fieldName(field),
Hasher: "hasher",
UniqueHash: hashUnique,
})
return b.String(), err
}

func (fns goSharedFuncs) renderMap(field pgs.Field) (string, error) {

func (fns goSharedFuncs) renderMap(field pgs.Field, uniqueHash bool) (string, error) {
var b bytes.Buffer
valueTemplate, err := fns.simpleRender(field.Type().Element(), "v", "innerHash")
valueTemplate, err := fns.simpleRender(field.Type().Element(), "v", "innerHash", uniqueHash)
if err != nil {
return "", err
}
keyTemplate, err := fns.simpleRender(field.Type().Key(), "k", "innerHash")
keyTemplate, err := fns.simpleRender(field.Type().Key(), "k", "innerHash", uniqueHash)
if err != nil {
return "", err
}
values := Value{
FieldAccessor: fns.accessor(field),
FieldName: fns.fieldName(field),
Hasher: "innerHash",
UniqueHash: uniqueHash,
InnerTemplates: struct {
Key string
Value string
Expand All @@ -88,16 +90,17 @@ func (fns goSharedFuncs) renderMap(field pgs.Field) (string, error) {
return b.String(), err
}

func (fns goSharedFuncs) renderRepeated(field pgs.Field) (string, error) {
func (fns goSharedFuncs) renderRepeated(field pgs.Field, hashUnique bool) (string, error) {
var b bytes.Buffer
innerTemplate, err := fns.simpleRender(field.Type().Element(), "v", "hasher")
innerTemplate, err := fns.simpleRender(field.Type().Element(), "v", "hasher", hashUnique)
if err != nil {
return "", err
}
values := Value{
FieldName: fns.fieldName(field),
FieldAccessor: fns.accessor(field),
Hasher: "innerHash",
UniqueHash: hashUnique,
InnerTemplates: struct {
Key string
Value string
Expand All @@ -108,7 +111,7 @@ func (fns goSharedFuncs) renderRepeated(field pgs.Field) (string, error) {
return b.String(), err
}

func (fns goSharedFuncs) simpleRender(field pgs.FieldTypeElem, valueName, hasherName string) (string, error) {
func (fns goSharedFuncs) simpleRender(field pgs.FieldTypeElem, valueName, hasherName string, uniqueHash bool) (string, error) {
var tpl *template.Template
if field.ProtoType().IsNumeric() ||
field.ProtoType() == pgs.BoolT ||
Expand All @@ -132,6 +135,7 @@ func (fns goSharedFuncs) simpleRender(field pgs.FieldTypeElem, valueName, hasher
FieldName: valueName,
FieldAccessor: valueName,
Hasher: hasherName,
UniqueHash: uniqueHash,
})
return b.String(), err
}
Loading

0 comments on commit 08ea32b

Please sign in to comment.