arche / internal/object/encode.go

commit 154431fd
  1package object
  2
  3import (
  4	"bytes"
  5	"encoding/binary"
  6	"fmt"
  7	"sort"
  8	"time"
  9
 10	"github.com/zeebo/blake3"
 11)
 12
 13func HashBlob(b *Blob) [32]byte {
 14	var buf bytes.Buffer
 15	EncodeBlob(&buf, b)
 16	return blake3.Sum256(buf.Bytes())
 17}
 18
 19func HashTree(t *Tree) [32]byte {
 20	var buf bytes.Buffer
 21	EncodeTree(&buf, t)
 22	return blake3.Sum256(buf.Bytes())
 23}
 24
 25func HashCommit(c *Commit) [32]byte {
 26	var buf bytes.Buffer
 27	EncodeCommit(&buf, c)
 28	return blake3.Sum256(buf.Bytes())
 29}
 30
 31func HashConflict(c *Conflict) [32]byte {
 32	var buf bytes.Buffer
 33	EncodeConflict(&buf, c)
 34	return blake3.Sum256(buf.Bytes())
 35}
 36
 37func HashObsolete(o *ObsoleteMarker) [32]byte {
 38	var buf bytes.Buffer
 39	EncodeObsolete(&buf, o)
 40	return blake3.Sum256(buf.Bytes())
 41}
 42
 43func EncodeBlob(w *bytes.Buffer, b *Blob) {
 44	w.WriteString("arche-blob\x00")
 45	writeUint64(w, uint64(len(b.Content)))
 46	w.Write(b.Content)
 47}
 48
 49func EncodeTree(w *bytes.Buffer, t *Tree) {
 50	entries := make([]TreeEntry, len(t.Entries))
 51	copy(entries, t.Entries)
 52	sort.Slice(entries, func(i, j int) bool { return entries[i].Name < entries[j].Name })
 53
 54	w.WriteString("arche-tree\x00")
 55	writeUint32(w, uint32(len(entries)))
 56	for _, e := range entries {
 57		w.WriteByte(byte(e.Mode))
 58		writeUint16(w, uint16(len(e.Name)))
 59		w.WriteString(e.Name)
 60		w.Write(e.ObjectID[:])
 61
 62		keys := make([]string, 0, len(e.Props))
 63		for k := range e.Props {
 64			keys = append(keys, k)
 65		}
 66		sort.Strings(keys)
 67		writeUint16(w, uint16(len(keys)))
 68		for _, k := range keys {
 69			v := e.Props[k]
 70			writeUint16(w, uint16(len(k)))
 71			w.WriteString(k)
 72			writeUint16(w, uint16(len(v)))
 73			w.WriteString(v)
 74		}
 75	}
 76}
 77
 78func EncodeCommit(w *bytes.Buffer, c *Commit) {
 79	w.WriteString("arche-commit\x00")
 80	w.Write(c.TreeID[:])
 81	w.WriteByte(byte(len(c.Parents)))
 82	for _, p := range c.Parents {
 83		w.Write(p[:])
 84	}
 85	w.WriteByte(byte(len(c.ChangeID)))
 86	w.WriteString(c.ChangeID)
 87	w.WriteByte(byte(c.Phase))
 88	encodeSignature(w, c.Author)
 89	encodeSignature(w, c.Committer)
 90	writeUint32(w, uint32(len(c.Message)))
 91	w.WriteString(c.Message)
 92	if len(c.CommitSig) > 0 {
 93		w.WriteByte(1)
 94		writeUint16(w, uint16(len(c.CommitSig)))
 95		w.Write(c.CommitSig)
 96	}
 97}
 98
 99func CommitBodyForSigning(c *Commit) []byte {
100	tmp := *c
101	tmp.CommitSig = nil
102	var buf bytes.Buffer
103	EncodeCommit(&buf, &tmp)
104	return buf.Bytes()
105}
106
107func EncodeConflict(w *bytes.Buffer, c *Conflict) {
108	w.WriteString("arche-conflict\x00")
109	if c.Base != nil {
110		w.WriteByte(1)
111		w.Write(c.Base.CommitID[:])
112		w.Write(c.Base.BlobID[:])
113	} else {
114		w.WriteByte(0)
115	}
116	w.Write(c.Ours.CommitID[:])
117	w.Write(c.Ours.BlobID[:])
118	w.Write(c.Theirs.CommitID[:])
119	w.Write(c.Theirs.BlobID[:])
120}
121
122func EncodeObsolete(w *bytes.Buffer, o *ObsoleteMarker) {
123	w.WriteString("arche-obsolete\x00")
124	w.Write(o.Predecessor[:])
125	w.WriteByte(byte(len(o.Successors)))
126	for _, s := range o.Successors {
127		w.Write(s[:])
128	}
129	w.WriteByte(byte(len(o.Reason)))
130	w.WriteString(o.Reason)
131	writeInt64(w, o.Timestamp)
132}
133
134func DecodeBlob(data []byte) (*Blob, error) {
135	prefix := "arche-blob\x00"
136	if !bytes.HasPrefix(data, []byte(prefix)) {
137		return nil, fmt.Errorf("invalid blob prefix")
138	}
139	data = data[len(prefix):]
140	if len(data) < 8 {
141		return nil, fmt.Errorf("blob: length field truncated")
142	}
143	l := binary.BigEndian.Uint64(data[:8])
144	data = data[8:]
145	if uint64(len(data)) < l {
146		return nil, fmt.Errorf("blob: content truncated (want %d bytes, got %d)", l, len(data))
147	}
148	content := make([]byte, l)
149	copy(content, data[:l])
150	return &Blob{Content: content}, nil
151}
152
153func DecodeTree(data []byte) (*Tree, error) {
154	prefix := "arche-tree\x00"
155	if !bytes.HasPrefix(data, []byte(prefix)) {
156		return nil, fmt.Errorf("invalid tree prefix")
157	}
158	data = data[len(prefix):]
159	if len(data) < 4 {
160		return nil, fmt.Errorf("tree: entry count truncated")
161	}
162	n := binary.BigEndian.Uint32(data[:4])
163	data = data[4:]
164
165	entries := make([]TreeEntry, 0, n)
166	for i := uint32(0); i < n; i++ {
167		if len(data) < 1 {
168			return nil, fmt.Errorf("tree entry %d: truncated at mode", i)
169		}
170		mode := EntryMode(data[0])
171		data = data[1:]
172
173		if len(data) < 2 {
174			return nil, fmt.Errorf("tree entry %d: name len truncated", i)
175		}
176		nameLen := binary.BigEndian.Uint16(data[:2])
177		data = data[2:]
178		if len(data) < int(nameLen) {
179			return nil, fmt.Errorf("tree entry %d: name truncated", i)
180		}
181		name := string(data[:nameLen])
182		data = data[nameLen:]
183
184		if len(data) < 32 {
185			return nil, fmt.Errorf("tree entry %d: object ID truncated", i)
186		}
187		var oid [32]byte
188		copy(oid[:], data[:32])
189		data = data[32:]
190
191		if len(data) < 2 {
192			return nil, fmt.Errorf("tree entry %d: props count truncated", i)
193		}
194		numProps := binary.BigEndian.Uint16(data[:2])
195		data = data[2:]
196
197		props := make(map[string]string, numProps)
198		for j := uint16(0); j < numProps; j++ {
199			if len(data) < 2 {
200				return nil, fmt.Errorf("tree entry %d prop %d: key len truncated", i, j)
201			}
202			kl := binary.BigEndian.Uint16(data[:2])
203			data = data[2:]
204			if len(data) < int(kl) {
205				return nil, fmt.Errorf("tree entry %d prop %d: key truncated", i, j)
206			}
207			k := string(data[:kl])
208			data = data[kl:]
209
210			if len(data) < 2 {
211				return nil, fmt.Errorf("tree entry %d prop %d: val len truncated", i, j)
212			}
213			vl := binary.BigEndian.Uint16(data[:2])
214			data = data[2:]
215			if len(data) < int(vl) {
216				return nil, fmt.Errorf("tree entry %d prop %d: val truncated", i, j)
217			}
218			props[k] = string(data[:vl])
219			data = data[vl:]
220		}
221
222		entries = append(entries, TreeEntry{Name: name, Mode: mode, ObjectID: oid, Props: props})
223	}
224
225	for i := 1; i < len(entries); i++ {
226		if entries[i].Name <= entries[i-1].Name {
227			return nil, fmt.Errorf("tree entries not correctly sorted at index %d", i)
228		}
229	}
230	return &Tree{Entries: entries}, nil
231}
232
233func DecodeCommit(data []byte) (*Commit, error) {
234	prefix := "arche-commit\x00"
235	if !bytes.HasPrefix(data, []byte(prefix)) {
236		return nil, fmt.Errorf("invalid commit prefix")
237	}
238	data = data[len(prefix):]
239
240	if len(data) < 32 {
241		return nil, fmt.Errorf("commit: tree ID truncated")
242	}
243	var treeID [32]byte
244	copy(treeID[:], data[:32])
245	data = data[32:]
246
247	if len(data) < 1 {
248		return nil, fmt.Errorf("commit: parent count truncated")
249	}
250	np := int(data[0])
251	data = data[1:]
252	parents := make([][32]byte, np)
253	for i := range parents {
254		if len(data) < 32 {
255			return nil, fmt.Errorf("commit: parent %d truncated", i)
256		}
257		copy(parents[i][:], data[:32])
258		data = data[32:]
259	}
260
261	if len(data) < 1 {
262		return nil, fmt.Errorf("commit: change ID len truncated")
263	}
264	cidLen := int(data[0])
265	data = data[1:]
266	if len(data) < cidLen {
267		return nil, fmt.Errorf("commit: change ID truncated")
268	}
269	changeID := string(data[:cidLen])
270	data = data[cidLen:]
271
272	if len(data) < 1 {
273		return nil, fmt.Errorf("commit: phase truncated")
274	}
275	phase := Phase(data[0])
276	data = data[1:]
277
278	author, data, err := decodeSignature(data)
279	if err != nil {
280		return nil, fmt.Errorf("commit author: %w", err)
281	}
282	committer, data, err := decodeSignature(data)
283	if err != nil {
284		return nil, fmt.Errorf("commit committer: %w", err)
285	}
286
287	if len(data) < 4 {
288		return nil, fmt.Errorf("commit: message len truncated")
289	}
290	ml := binary.BigEndian.Uint32(data[:4])
291	data = data[4:]
292	if uint32(len(data)) < ml {
293		return nil, fmt.Errorf("commit: message truncated")
294	}
295	msg := string(data[:ml])
296	data = data[ml:]
297
298	var commitSig []byte
299	if len(data) >= 1 && data[0] == 1 {
300		data = data[1:]
301		if len(data) < 2 {
302			return nil, fmt.Errorf("commit: signature length truncated")
303		}
304		sigLen := binary.BigEndian.Uint16(data[:2])
305		data = data[2:]
306		if len(data) < int(sigLen) {
307			return nil, fmt.Errorf("commit: signature truncated")
308		}
309		commitSig = make([]byte, sigLen)
310		copy(commitSig, data[:sigLen])
311	}
312
313	return &Commit{
314		TreeID:    treeID,
315		Parents:   parents,
316		ChangeID:  changeID,
317		Author:    author,
318		Committer: committer,
319		Message:   msg,
320		Phase:     phase,
321		CommitSig: commitSig,
322	}, nil
323}
324
325func DecodeConflict(data []byte) (*Conflict, error) {
326	prefix := "arche-conflict\x00"
327	if !bytes.HasPrefix(data, []byte(prefix)) {
328		return nil, fmt.Errorf("invalid conflict prefix")
329	}
330	data = data[len(prefix):]
331
332	if len(data) < 1 {
333		return nil, fmt.Errorf("conflict: base flag truncated")
334	}
335	hasBase := data[0] == 1
336	data = data[1:]
337
338	c := &Conflict{}
339	if hasBase {
340		if len(data) < 64 {
341			return nil, fmt.Errorf("conflict: base side truncated")
342		}
343		var base ConflictSide
344		copy(base.CommitID[:], data[:32])
345		copy(base.BlobID[:], data[32:64])
346		data = data[64:]
347		c.Base = &base
348	}
349
350	if len(data) < 128 {
351		return nil, fmt.Errorf("conflict: ours/theirs truncated")
352	}
353	copy(c.Ours.CommitID[:], data[:32])
354	copy(c.Ours.BlobID[:], data[32:64])
355	copy(c.Theirs.CommitID[:], data[64:96])
356	copy(c.Theirs.BlobID[:], data[96:128])
357	return c, nil
358}
359
360func DecodeObsolete(data []byte) (*ObsoleteMarker, error) {
361	prefix := "arche-obsolete\x00"
362	if !bytes.HasPrefix(data, []byte(prefix)) {
363		return nil, fmt.Errorf("invalid obsolete prefix")
364	}
365	data = data[len(prefix):]
366
367	if len(data) < 32 {
368		return nil, fmt.Errorf("obsolete: predecessor truncated")
369	}
370	var pred [32]byte
371	copy(pred[:], data[:32])
372	data = data[32:]
373
374	if len(data) < 1 {
375		return nil, fmt.Errorf("obsolete: successor count truncated")
376	}
377	ns := int(data[0])
378	data = data[1:]
379	succs := make([][32]byte, ns)
380	for i := range succs {
381		if len(data) < 32 {
382			return nil, fmt.Errorf("obsolete: successor %d truncated", i)
383		}
384		copy(succs[i][:], data[:32])
385		data = data[32:]
386	}
387
388	if len(data) < 1 {
389		return nil, fmt.Errorf("obsolete: reason len truncated")
390	}
391	rl := int(data[0])
392	data = data[1:]
393	if len(data) < rl {
394		return nil, fmt.Errorf("obsolete: reason truncated")
395	}
396	reason := string(data[:rl])
397	data = data[rl:]
398
399	if len(data) < 8 {
400		return nil, fmt.Errorf("obsolete: timestamp truncated")
401	}
402	ts := int64(binary.BigEndian.Uint64(data[:8]))
403
404	return &ObsoleteMarker{
405		Predecessor: pred,
406		Successors:  succs,
407		Reason:      reason,
408		Timestamp:   ts,
409	}, nil
410}
411
412func encodeSignature(w *bytes.Buffer, s Signature) {
413	writeUint16(w, uint16(len(s.Name)))
414	w.WriteString(s.Name)
415	writeUint16(w, uint16(len(s.Email)))
416	w.WriteString(s.Email)
417	writeInt64(w, s.Timestamp.UnixNano())
418}
419
420func decodeSignature(data []byte) (Signature, []byte, error) {
421	if len(data) < 2 {
422		return Signature{}, nil, fmt.Errorf("name len truncated")
423	}
424	nl := binary.BigEndian.Uint16(data[:2])
425	data = data[2:]
426	if len(data) < int(nl) {
427		return Signature{}, nil, fmt.Errorf("name truncated")
428	}
429	name := string(data[:nl])
430	data = data[nl:]
431
432	if len(data) < 2 {
433		return Signature{}, nil, fmt.Errorf("email len truncated")
434	}
435	el := binary.BigEndian.Uint16(data[:2])
436	data = data[2:]
437	if len(data) < int(el) {
438		return Signature{}, nil, fmt.Errorf("email truncated")
439	}
440	email := string(data[:el])
441	data = data[el:]
442
443	if len(data) < 8 {
444		return Signature{}, nil, fmt.Errorf("timestamp truncated")
445	}
446	unixNs := int64(binary.BigEndian.Uint64(data[:8]))
447	data = data[8:]
448
449	return Signature{
450		Name:      name,
451		Email:     email,
452		Timestamp: time.Unix(0, unixNs).UTC(),
453	}, data, nil
454}
455
456func writeUint64(w *bytes.Buffer, v uint64) {
457	var b [8]byte
458	binary.BigEndian.PutUint64(b[:], v)
459	w.Write(b[:])
460}
461
462func writeUint32(w *bytes.Buffer, v uint32) {
463	var b [4]byte
464	binary.BigEndian.PutUint32(b[:], v)
465	w.Write(b[:])
466}
467
468func writeUint16(w *bytes.Buffer, v uint16) {
469	var b [2]byte
470	binary.BigEndian.PutUint16(b[:], v)
471	w.Write(b[:])
472}
473
474func writeInt64(w *bytes.Buffer, v int64) {
475	var b [8]byte
476	binary.BigEndian.PutUint64(b[:], uint64(v))
477	w.Write(b[:])
478}