File: suffix.go

package info (click to toggle)
trillian 1.7.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 6,600 kB
  • sloc: sh: 1,181; javascript: 474; sql: 330; makefile: 39
file content (148 lines) | stat: -rw-r--r-- 4,734 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// Copyright 2017 Google LLC. All Rights Reserved.
//
// 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 cache

import (
	"encoding/base64"
	"errors"
	"fmt"
)

// key is used to look up variable length suffix values in the map.
type key struct {
	// The depth of this entry in bits (must be > 0).
	depth uint8
	// The value of the path at this depth.
	value byte
}

var (
	// emptySuffix is a reusable suffix of zero bits. To avoid special cases
	// there is a single byte path attached to it and there is no way to create
	// a suffix with a nil or empty path.
	emptySuffix = newSuffix(0, []byte{0})
	// fromRaw maps a bit length and single byte path to a suffix.
	fromRaw = make(map[key]*suffix)
	// fromString maps a base64 encoded string representation to a suffix.
	fromString = make(map[string]*suffix)
)

// suffix represents the tail of a NodeID. It is the path within the subtree.
// The portion of the path that extends beyond the subtree is not part of this suffix.
// We keep a cache of the suffix values use by log trees, which will have a
// depth between 1 and 8 bits. These are reused to avoid constant reallocation
// and base64 conversion overhead.
//
// TODO(pavelkalinnikov, v2): This type is specific to SubtreeProto. Move it.
type suffix struct {
	// bits is the number of bits in the node ID suffix.
	bits uint8
	// path is the suffix itself.
	path []byte
	// asString is the string representation of the suffix.
	asString string
}

// newSuffix creates a new suffix. The primary use for them is to get their
// String value to use as a key so we compute that once up front.
//
// TODO(pavelkalinnikov): Mask the last byte of path.
func newSuffix(bits uint8, path []byte) *suffix {
	// Use a shared value for a short suffix if we have one, they're immutable.
	if bits <= 8 {
		if sfx, ok := fromRaw[key{depth: bits, value: path[0]}]; ok {
			return sfx
		}
	}

	r := make([]byte, 1, len(path)+1)
	r[0] = bits
	r = append(r, path...)
	s := base64.StdEncoding.EncodeToString(r)

	return &suffix{bits: bits, path: r[1:], asString: s}
}

// Bits returns the number of significant bits in the suffix path.
func (s suffix) Bits() uint8 {
	return s.bits
}

// Path returns a copy of the suffix path.
func (s suffix) Path() []byte {
	return append(make([]byte, 0, len(s.path)), s.path...)
}

// String returns a string that represents suffix.
// This is a base64 encoding of the following format:
// [ 1 byte for depth || path bytes ]
func (s suffix) String() string {
	return s.asString
}

// parseSuffix converts a suffix string back into a suffix.
func parseSuffix(s string) (*suffix, error) {
	if sfx, ok := fromString[s]; ok {
		// Matches a precalculated value, use that.
		return sfx, nil
	}

	b, err := base64.StdEncoding.DecodeString(s)
	if err != nil {
		return nil, err
	}
	if len(b) == 0 {
		return nil, errors.New("empty bytes")
	}
	bits, b := b[0], b[1:]
	if got, want := len(b), bytesForBits(int(bits)); got != want {
		return nil, fmt.Errorf("unexpected length %d, need %d", got, want)
	}

	return newSuffix(bits, b), nil
}

// bytesForBits returns the number of bytes required to store numBits bits.
func bytesForBits(numBits int) int {
	return (numBits + 7) >> 3
}

// Precalculate all the one byte suffix values (from depths 1-8) so they can be
// reused either on construction or parsing.
func init() {
	path := make([]byte, 1)
	// There are 8 levels of depth to process.
	for d := 8; d >= 1; d-- {
		// And at each depth there's 2^d valid combinations of bits. E.g at
		// depth 1 there's one valid bit so two possibilities.
		for i := 0; i < 1<<uint(d); i++ {
			// Don't need to mask off lower bits outside the valid ones because we
			// know they're already zero.
			path[0] = byte(i << uint(8-d))
			sfx := newSuffix(byte(d), path)
			// As an extra check there should be no collisions in the suffix values
			// that we build so map entries should not be overwritten.
			k := key{depth: uint8(d), value: path[0]}
			if _, ok := fromRaw[k]; ok {
				panic(fmt.Errorf("cache collision for: %v", k))
			}
			fromRaw[k] = sfx
			if _, ok := fromString[sfx.String()]; ok {
				panic(fmt.Errorf("cache collision for: %s", sfx.String()))
			}
			fromString[sfx.String()] = sfx
		}
	}
}