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
|
package packrev
import (
"encoding/binary"
"errors"
"fmt"
"lindenii.org/go/furgit/object/id"
"lindenii.org/go/lgo/intconv"
)
// ErrMalformedReverseIndex reports that
// a pack reverse index is truncated,
// has a bad signature, version, or hash function,
// or contains invalid index positions.
var ErrMalformedReverseIndex = errors.New("internal/format/packrev: malformed pack reverse index")
const (
signature = 0x52494458 // "RIDX"
version = 1
headerLen = 12
)
// hashFunctionID returns the on-disk hash function identifier
// for one object format.
func hashFunctionID(objectFormat id.ObjectFormat) (uint32, error) {
switch objectFormat {
case id.ObjectFormatSHA1:
return 1, nil
case id.ObjectFormatSHA256:
return 2, nil
case id.ObjectFormatUnknown:
}
return 0, id.ErrInvalidObjectFormat
}
// Packrev is a parsed pack reverse index view over borrowed bytes.
//
// Labels: Deps-Borrowed, Life-Parent, MT-Safe.
type Packrev struct {
// data is the entire pack reverse index payload.
data []byte
// hashSize is the object ID size of the object format.
hashSize int
// numObjects is the number of index position entries.
numObjects int
}
// Parse parses a pack reverse index from data.
func Parse(data []byte, objectFormat id.ObjectFormat) (Packrev, error) {
var zero Packrev
wantHashID, err := hashFunctionID(objectFormat)
if err != nil {
return zero, err
}
hashSize := objectFormat.Size()
if len(data) < headerLen+2*hashSize {
return zero, fmt.Errorf("%w: truncated", ErrMalformedReverseIndex)
}
if binary.BigEndian.Uint32(data) != signature {
return zero, fmt.Errorf("%w: bad signature", ErrMalformedReverseIndex)
}
if binary.BigEndian.Uint32(data[4:]) != version {
return zero, fmt.Errorf("%w: unsupported version", ErrMalformedReverseIndex)
}
if binary.BigEndian.Uint32(data[8:]) != wantHashID {
return zero, fmt.Errorf("%w: hash function mismatch", ErrMalformedReverseIndex)
}
positionBytes := len(data) - headerLen - 2*hashSize
if positionBytes%4 != 0 {
return zero, fmt.Errorf("%w: position table size not a 32-bit multiple", ErrMalformedReverseIndex)
}
return Packrev{
data: data,
hashSize: hashSize,
numObjects: positionBytes / 4,
}, nil
}
// NumObjects returns the number of index position entries.
func (rev *Packrev) NumObjects() int {
return rev.numObjects
}
// PackHash returns the pack hash recorded in the trailer.
//
// Labels: Life-Parent, Mut-No.
func (rev *Packrev) PackHash() []byte {
return rev.data[len(rev.data)-2*rev.hashSize : len(rev.data)-rev.hashSize]
}
// PositionAt returns the pack index position
// of the object at a pack offset order position.
//
// PositionAt panics when packOrder is out of range,
// and errors when the stored position is not a valid index position.
func (rev *Packrev) PositionAt(packOrder int) (int, error) {
if packOrder < 0 || packOrder >= rev.numObjects {
panic("internal/format/packrev: pack order position out of range")
}
stored := binary.BigEndian.Uint32(rev.data[headerLen+4*packOrder:])
position, err := intconv.Uint32ToInt(stored)
if err != nil {
return 0, fmt.Errorf("%w: %w", ErrMalformedReverseIndex, err)
}
if position >= rev.numObjects {
return 0, fmt.Errorf("%w: index position out of range", ErrMalformedReverseIndex)
}
return position, nil
}
|