blob: 65b7c18ad32fafb4487763cc892071b0cc4ec393 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package build
import (
"bufio"
"encoding/hex"
"errors"
"fmt"
"io"
"os"
"sort"
"strings"
)
// MerkleRoot is the root hash of a merkle tree
type MerkleRoot [32]byte
// ErrInvalidMerkleRootLength indicates a MerkleRoot string did not contain 32 hex bytes
var ErrInvalidMerkleRootLength = errors.New("decoded merkle root does not contain 32 bytes")
// ErrInvalidMetaContentsLine indicates that a line in a meta/contents file was not valid
type ErrInvalidMetaContentsLine struct {
Line string
}
func (e ErrInvalidMetaContentsLine) Error() string {
return fmt.Sprintf("invalid line: %s", e.Line)
}
// MustDecodeMerkleRoot parses a MerkleRoot from a string, or panics
func MustDecodeMerkleRoot(s string) MerkleRoot {
m, err := DecodeMerkleRoot([]byte(s))
if err != nil {
panic(err)
}
return m
}
// DecodeMerkleRoot attempts to parse a MerkleRoot from a string
func DecodeMerkleRoot(text []byte) (MerkleRoot, error) {
var m MerkleRoot
if err := m.UnmarshalText(text); err != nil {
return m, err
}
return m, nil
}
// String encodes a MerkleRoot as a lower case 32 byte hex string
func (m MerkleRoot) String() string {
return hex.EncodeToString(m[:])
}
// MarshalText implements encoding.TextMarshaler
func (m MerkleRoot) MarshalText() ([]byte, error) {
return []byte(m.String()), nil
}
// UnmarshalText implements encoding.TextUnmarshaler
func (m *MerkleRoot) UnmarshalText(text []byte) error {
n, err := hex.Decode(m[:], text)
if err != nil {
return err
} else if n != 32 {
return ErrInvalidMerkleRootLength
}
return nil
}
// LessThan provides a sort ordering for MerkleRoot
func (m MerkleRoot) LessThan(other MerkleRoot) bool {
for i := 0; i < len(m); i++ {
if m[i] < other[i] {
return true
} else if m[i] > other[i] {
return false
}
}
return false
}
// MetaContents maps file paths within a package to their content IDs
type MetaContents map[string]MerkleRoot
// LoadMetaContents attempts to parse a meta/contents file from disk
func LoadMetaContents(path string) (MetaContents, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
return ParseMetaContents(f)
}
// ParseMetaContents attempts to parse a meta/contents file from an io.Reader
func ParseMetaContents(r io.Reader) (MetaContents, error) {
res := make(map[string]MerkleRoot)
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := scanner.Text()
parts := strings.SplitN(line, "=", 2)
if len(parts) < 2 {
return nil, ErrInvalidMetaContentsLine{line}
}
key := strings.TrimSpace(parts[0])
var value MerkleRoot
if err := value.UnmarshalText([]byte(strings.TrimSpace(parts[1]))); err != nil {
return nil, err
}
res[key] = value
}
if err := scanner.Err(); err != nil {
return nil, err
}
return res, nil
}
// String serializes the instance in the manifest file format, which could be
// parsed by ParseMetaContents.
func (m MetaContents) String() string {
paths := make([]string, 0, len(m))
for path := range m {
paths = append(paths, path)
}
sort.Strings(paths)
contentLines := make([]string, 0, len(m))
for _, path := range paths {
root := m[path]
line := fmt.Sprintf("%s=%s\n", path, root)
contentLines = append(contentLines, line)
}
return strings.Join(contentLines, "")
}