arche / internal/object/issue_event.go

commit 154431fd
  1package object
  2
  3import (
  4	"bytes"
  5	"encoding/binary"
  6	"fmt"
  7
  8	"github.com/zeebo/blake3"
  9)
 10
 11type IssueEventObject struct {
 12	IssueID string
 13	Kind    string
 14	Payload []byte
 15	Author  string
 16	HLCMS   int64
 17	HLCSeq  int
 18	Parents [][32]byte
 19}
 20
 21func EncodeIssueEvent(w *bytes.Buffer, ev *IssueEventObject) {
 22	w.WriteString("arche-issue\x00")
 23	writeUint16(w, uint16(len(ev.IssueID)))
 24	w.WriteString(ev.IssueID)
 25	w.WriteByte(byte(len(ev.Kind)))
 26	w.WriteString(ev.Kind)
 27	writeUint32(w, uint32(len(ev.Payload)))
 28	w.Write(ev.Payload)
 29	w.WriteByte(byte(len(ev.Author)))
 30	w.WriteString(ev.Author)
 31	writeInt64(w, ev.HLCMS)
 32	var seqBuf [4]byte
 33	binary.BigEndian.PutUint32(seqBuf[:], uint32(ev.HLCSeq))
 34	w.Write(seqBuf[:])
 35	w.WriteByte(byte(len(ev.Parents)))
 36	for _, p := range ev.Parents {
 37		w.Write(p[:])
 38	}
 39}
 40
 41func HashIssueEvent(ev *IssueEventObject) [32]byte {
 42	var buf bytes.Buffer
 43	EncodeIssueEvent(&buf, ev)
 44	return blake3.Sum256(buf.Bytes())
 45}
 46
 47func DecodeIssueEvent(data []byte) (*IssueEventObject, error) {
 48	const prefix = "arche-issue\x00"
 49	if !bytes.HasPrefix(data, []byte(prefix)) {
 50		return nil, fmt.Errorf("invalid issue-event prefix")
 51	}
 52	d := data[len(prefix):]
 53
 54	readStr16 := func() (string, error) {
 55		if len(d) < 2 {
 56			return "", fmt.Errorf("issue-event: truncated string length")
 57		}
 58		l := int(binary.BigEndian.Uint16(d[:2]))
 59		d = d[2:]
 60		if len(d) < l {
 61			return "", fmt.Errorf("issue-event: string truncated")
 62		}
 63		s := string(d[:l])
 64		d = d[l:]
 65		return s, nil
 66	}
 67
 68	readStr8 := func() (string, error) {
 69		if len(d) < 1 {
 70			return "", fmt.Errorf("issue-event: truncated byte-length string")
 71		}
 72		l := int(d[0])
 73		d = d[1:]
 74		if len(d) < l {
 75			return "", fmt.Errorf("issue-event: byte-length string truncated")
 76		}
 77		s := string(d[:l])
 78		d = d[l:]
 79		return s, nil
 80	}
 81
 82	issueID, err := readStr16()
 83	if err != nil {
 84		return nil, fmt.Errorf("issue-event decode IssueID: %w", err)
 85	}
 86
 87	kind, err := readStr8()
 88	if err != nil {
 89		return nil, fmt.Errorf("issue-event decode Kind: %w", err)
 90	}
 91
 92	if len(d) < 4 {
 93		return nil, fmt.Errorf("issue-event: payload length truncated")
 94	}
 95	payLen := int(binary.BigEndian.Uint32(d[:4]))
 96	d = d[4:]
 97	if len(d) < payLen {
 98		return nil, fmt.Errorf("issue-event: payload truncated")
 99	}
100	payload := make([]byte, payLen)
101	copy(payload, d[:payLen])
102	d = d[payLen:]
103
104	author, err := readStr8()
105	if err != nil {
106		return nil, fmt.Errorf("issue-event decode Author: %w", err)
107	}
108
109	if len(d) < 12 {
110		return nil, fmt.Errorf("issue-event: HLC fields truncated")
111	}
112	hlcMS := int64(binary.BigEndian.Uint64(d[:8]))
113	hlcSeq := int(binary.BigEndian.Uint32(d[8:12]))
114	d = d[12:]
115
116	if len(d) < 1 {
117		return nil, fmt.Errorf("issue-event: numParents truncated")
118	}
119	numParents := int(d[0])
120	d = d[1:]
121	if len(d) < numParents*32 {
122		return nil, fmt.Errorf("issue-event: parents truncated")
123	}
124	parents := make([][32]byte, numParents)
125	for i := range parents {
126		copy(parents[i][:], d[:32])
127		d = d[32:]
128	}
129
130	return &IssueEventObject{
131		IssueID: issueID,
132		Kind:    kind,
133		Payload: payload,
134		Author:  author,
135		HLCMS:   hlcMS,
136		HLCSeq:  hlcSeq,
137		Parents: parents,
138	}, nil
139}