fork of https://github.com/sourcegraph/zoekt
1package zoekt
2
3import (
4 "bytes"
5 "encoding/binary"
6 "fmt"
7 "unsafe"
8)
9
10// Wire-format of map[uint32]MinimalRepoListEntry is pretty straightforward:
11//
12// byte(2) version
13// uvarint(len(minimal))
14// uvarint(sum(len(entry.Branches) for entry in minimal))
15// for repoID, entry in minimal:
16// uvarint(repoID)
17// byte(entry.HasSymbols)
18// uvarint(entry.IndexTimeUnix)
19// uvarint(len(entry.Branches))
20// for b in entry.Branches:
21// str(b.Name)
22// str(b.Version)
23//
24// Version 1 was the same, except it didn't have the IndexTimeUnix field.
25
26// reposMapEncode implements an efficient encoder for ReposMap.
27func reposMapEncode(minimal ReposMap) ([]byte, error) {
28 if minimal == nil {
29 return nil, nil
30 }
31
32 var b bytes.Buffer
33 var enc [binary.MaxVarintLen64]byte
34 varint := func(n int) {
35 m := binary.PutUvarint(enc[:], uint64(n))
36 b.Write(enc[:m])
37 }
38 str := func(s string) {
39 varint(len(s))
40 b.WriteString(s)
41 }
42 strSize := func(s string) int {
43 return binary.PutUvarint(enc[:], uint64(len(s))) + len(s)
44 }
45
46 // We calculate this up front so when decoding we only need to allocate the
47 // underlying array once.
48 allBranchesLen := 0
49 for _, entry := range minimal {
50 allBranchesLen += len(entry.Branches)
51 }
52
53 // Calculate size
54 size := 1 // version
55 size += binary.PutUvarint(enc[:], uint64(len(minimal)))
56 size += binary.PutUvarint(enc[:], uint64(allBranchesLen))
57 for repoID, entry := range minimal {
58 size += binary.PutUvarint(enc[:], uint64(repoID))
59 size += 1 // HasSymbols
60 size += binary.PutUvarint(enc[:], uint64(entry.IndexTimeUnix))
61 size += binary.PutUvarint(enc[:], uint64(len(entry.Branches)))
62 for _, b := range entry.Branches {
63 size += strSize(b.Name)
64 size += strSize(b.Version)
65 }
66 }
67 b.Grow(size)
68
69 // Version
70 b.WriteByte(2)
71
72 // Length
73 varint(len(minimal))
74
75 varint(allBranchesLen)
76
77 for repoID, entry := range minimal {
78 varint(int(repoID))
79
80 hasSymbols := byte(1)
81 if !entry.HasSymbols {
82 hasSymbols = 0
83 }
84 b.WriteByte(hasSymbols)
85
86 varint(int(entry.IndexTimeUnix))
87
88 varint(len(entry.Branches))
89 for _, b := range entry.Branches {
90 str(b.Name)
91 str(b.Version)
92 }
93 }
94
95 return b.Bytes(), nil
96}
97
98// reposMapDecode implements an efficient decoder for map[string]struct{}.
99func reposMapDecode(b []byte) (ReposMap, error) {
100 // nil input
101 if len(b) == 0 {
102 return nil, nil
103 }
104
105 // binaryReader returns strings pointing into b to avoid allocations. We
106 // don't own b, so we create a copy of it.
107 r := binaryReader{
108 typ: "ReposMap",
109 b: append([]byte{}, b...),
110 }
111
112 // Version
113 var readIndexTime bool
114 v := r.byt()
115 switch v {
116 case 1:
117 readIndexTime = false
118 case 2:
119 readIndexTime = true
120 default:
121 return nil, fmt.Errorf("unsupported stringSet encoding version %d", v)
122 }
123
124 // Length
125 l := r.uvarint()
126 m := make(map[uint32]MinimalRepoListEntry, l)
127
128 // Pre-allocate slice for all branches
129 allBranchesLen := r.uvarint()
130 allBranches := make([]RepositoryBranch, 0, allBranchesLen)
131
132 for i := 0; i < l; i++ {
133 repoID := r.uvarint()
134 hasSymbols := r.byt() == 1
135 var indexTimeUnix int64
136 if readIndexTime {
137 indexTimeUnix = int64(r.uvarint())
138 }
139 lb := r.uvarint()
140 for i := 0; i < lb; i++ {
141 allBranches = append(allBranches, RepositoryBranch{
142 Name: r.str(),
143 Version: r.str(),
144 })
145 }
146 branches := allBranches[len(allBranches)-lb:]
147 m[uint32(repoID)] = MinimalRepoListEntry{
148 HasSymbols: hasSymbols,
149 Branches: branches,
150 IndexTimeUnix: indexTimeUnix,
151 }
152 }
153
154 return m, r.err
155}
156
157type binaryReader struct {
158 typ string
159 b []byte
160 err error
161}
162
163func (b *binaryReader) uvarint() int {
164 x, n := binary.Uvarint(b.b)
165 if n < 0 {
166 b.b = nil
167 b.err = fmt.Errorf("malformed %s", b.typ)
168 return 0
169 }
170 b.b = b.b[n:]
171 return int(x)
172}
173
174func (b *binaryReader) str() string {
175 l := b.uvarint()
176 if l > len(b.b) {
177 b.b = nil
178 b.err = fmt.Errorf("malformed %s", b.typ)
179 return ""
180 }
181 s := b2s(b.b[:l])
182 b.b = b.b[l:]
183 return s
184}
185
186func (b *binaryReader) byt() byte {
187 if len(b.b) < 1 {
188 b.b = nil
189 b.err = fmt.Errorf("malformed %s", b.typ)
190 return 0
191 }
192 x := b.b[0]
193 b.b = b.b[1:]
194 return x
195}
196
197func b2s(b []byte) string {
198 return *(*string)(unsafe.Pointer(&b))
199}