core/digest.go (104 lines of code) (raw):

// Copyright (c) 2016-2019 Uber Technologies, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package core import ( _ "crypto/sha256" // For computing digest. "database/sql/driver" "encoding/hex" "encoding/json" "errors" "fmt" "strings" ) // DigestList is a list of digests. type DigestList []Digest // Value marshals a list of digests and returns []byte as driver.Value. func (l DigestList) Value() (driver.Value, error) { b, err := json.Marshal(l) if err != nil { return driver.Value([]byte{}), err } return driver.Value(b), nil } // Scan unmarshals []byte to a list of Digest. func (l *DigestList) Scan(src interface{}) error { return json.Unmarshal(src.([]byte), l) } // Digest can be represented in a string like "<algorithm>:<hex_digest_string>" // Example: // sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 type Digest struct { algo string hex string raw string } // NewSHA256DigestFromHex constructs a Digest from a sha256 in hexadecimal // format. Returns error if hex is not a valid sha256. func NewSHA256DigestFromHex(hex string) (Digest, error) { if err := ValidateSHA256(hex); err != nil { return Digest{}, fmt.Errorf("invalid sha256: %s", err) } return Digest{ algo: SHA256, hex: hex, raw: fmt.Sprintf("%s:%s", SHA256, hex), }, nil } // ParseSHA256Digest parses a raw "<algo>:<hex>" sha256 digest. Returns error if the // algo is not sha256 or the hex is not a valid sha256. func ParseSHA256Digest(raw string) (Digest, error) { if raw == "" { return Digest{}, errors.New("invalid digest: empty") } parts := strings.Split(raw, ":") if len(parts) != 2 { return Digest{}, errors.New("invalid digest: expected '<algo>:<hex>'") } algo := parts[0] hex := parts[1] if algo != SHA256 { return Digest{}, errors.New("invalid digest algo: expected sha256") } if err := ValidateSHA256(hex); err != nil { return Digest{}, fmt.Errorf("invalid sha256: %s", err) } return Digest{ algo: algo, hex: hex, raw: raw, }, nil } // Value marshals a digest and returns []byte as driver.Value. func (d Digest) Value() (driver.Value, error) { b, err := json.Marshal(d) if err != nil { return driver.Value([]byte{}), err } return driver.Value(b), nil } // Scan unmarshals []byte to a Digest. func (d *Digest) Scan(src interface{}) error { return json.Unmarshal(src.([]byte), d) } // UnmarshalJSON unmarshals "<algorithm>:<hex_digest_string>" to Digest. func (d *Digest) UnmarshalJSON(str []byte) error { var raw string if err := json.Unmarshal(str, &raw); err != nil { return err } digest, err := ParseSHA256Digest(raw) if err != nil { return err } *d = digest return nil } // MarshalJSON unmarshals hexBytes to Digest. func (d Digest) MarshalJSON() ([]byte, error) { return json.Marshal(d.raw) } // String returns digest in string format like "<algorithm>:<hex_digest_string>". func (d Digest) String() string { return d.raw } // Algo returns the algo part of the digest. // Example: // sha256 func (d Digest) Algo() string { return d.algo } // Hex returns the hex part of the digest. // Example: // e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 func (d Digest) Hex() string { return d.hex } // ShardID returns the shard id of the digest. func (d Digest) ShardID() string { return d.hex[:4] } // ValidateSHA256 returns error if s is not a valid SHA256 hex digest. func ValidateSHA256(s string) error { if len(s) != 64 { return fmt.Errorf("expected 64 characters, got %d from %q", len(s), s) } if _, err := hex.DecodeString(s); err != nil { return fmt.Errorf("hex: %s", err) } return nil }