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}