aboutsummaryrefslogtreecommitdiff
path: root/internal/format/packrev/packrev.go
blob: 3a6dc2de4fbd3b87a7848eedf6d1f4cca071a467 (about) (plain) (blame)
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
}