1package main
2
3import (
4 "fmt"
5 "path/filepath"
6 "sort"
7 "strconv"
8 "strings"
9)
10
11// eventIR is one <event> or <eventcopy>. Copies point at their source IR and
12// share its container.
13type eventIR struct {
14 Name string // Go base name, e.g. MapRequest
15 Code int // core: wire code; extension: relative code; XGE: evtype
16 XGE bool
17 Copy *eventIR
18 C *container
19 Node *Node
20 KindExpr string // "" when the event has no Kind (core XGE envelope)
21}
22
23const eventLifetime = "Getters read the copied Event.Raw, so a view stays valid as long as the Event value it was taken from."
24
25func eventCode(n *Node) (int, error) {
26 code, err := strconv.Atoi(n.Attr["number"])
27 if err != nil || code < 0 || code > 255 {
28 return 0, n.fail("invalid event number %q", n.Attr["number"])
29 }
30 return code, nil
31}
32
33func kindExpr(m *Module, e *eventIR) (string, error) {
34 switch {
35 case m.ExtName == "" && e.XGE:
36 // The core XGE envelope is not an event type: DecodeEvent resolves
37 // every XGE packet to its extension's kind or KindUnknown.
38 return "", nil
39 case m.ExtName == "":
40 if e.Code < 2 || e.Code > 34 {
41 return "", e.Node.fail("core event code %d outside 2..34", e.Code)
42 }
43 return fmt.Sprintf("xturbo.EventKind(%d)", e.Code), nil
44 case e.XGE:
45 return fmt.Sprintf("xturbo.EventKind(xturbo.Ext%s)<<8 | 0x80 | %d", m.ExtName, e.Code), nil
46 }
47 if e.Code > 127 {
48 return "", e.Node.fail("extension event number %d above 127", e.Code)
49 }
50 if e.Code == 0 {
51 // "| 0" is a no-op that staticcheck rejects (SA4016).
52 return fmt.Sprintf("xturbo.EventKind(xturbo.Ext%s) << 8", m.ExtName), nil
53 }
54 return fmt.Sprintf("xturbo.EventKind(xturbo.Ext%s)<<8 | %d", m.ExtName, e.Code), nil
55}
56
57// xgeReserved are the implicit XGE header getters.
58const xgeReserved = "Raw Extension Length EventType Tail"
59
60func resolveEvents(m *Module) ([]*eventIR, error) {
61 var events []*eventIR
62 byXName := map[string]*eventIR{}
63 for _, n := range m.Root.Children {
64 if n.Kind != "event" && n.Kind != "eventcopy" {
65 continue
66 }
67 code, err := eventCode(n)
68 if err != nil {
69 return nil, err
70 }
71 e := &eventIR{Name: goName(n.Attr["name"]), Code: code, Node: n}
72 if n.Kind == "eventcopy" {
73 src := byXName[n.Attr["ref"]]
74 if src == nil {
75 return nil, n.fail("eventcopy of unknown event %s", n.Attr["ref"])
76 }
77 if src.Copy != nil {
78 src = src.Copy
79 }
80 e.Copy, e.C, e.XGE = src, src.C, src.XGE
81 } else {
82 e.XGE = n.Attr["xge"] == "true"
83 lay, reserved := layoutEvent, "Raw"
84 switch {
85 case e.XGE:
86 lay, reserved = layoutXGE, xgeReserved
87 if code > 127 {
88 return nil, n.fail("xge evtype %d >= 128", code)
89 }
90 case n.Attr["no-sequence-number"] == "true":
91 lay = layoutEventNoSeq
92 }
93 c, err := buildContainer(m, e.Name+"Event", "e", n.Children, lay, reserved)
94 if err != nil {
95 return nil, err
96 }
97 if c.Dynamic >= 0 || c.Fixed > 32 {
98 return nil, n.fail("event members must lie within the first 32 bytes")
99 }
100 for _, mem := range c.Members {
101 if mem.Kind == "list" && mem.ListKind == "iter" {
102 return nil, n.fail("variable-size struct list in event")
103 }
104 }
105 e.C = c
106 }
107 if e.KindExpr, err = kindExpr(m, e); err != nil {
108 return nil, err
109 }
110 names := []string{e.Name + "Event", e.Name + "Of"}
111 if e.KindExpr != "" {
112 names = append(names, "Kind"+e.Name)
113 }
114 if m.ExtName == "" && !e.XGE {
115 names = append(names, e.Name+"EventData")
116 }
117 for _, s := range names {
118 if err := m.claim(s, n); err != nil {
119 return nil, err
120 }
121 }
122 byXName[n.Attr["name"]] = e
123 events = append(events, e)
124 }
125 return events, nil
126}
127
128// dataField renders one EventData struct field and its Put statement.
129func dataField(c *container, mem member) (field, put string, err error) {
130 name := mem.Method
131 if name == "Put" {
132 name += "_"
133 }
134 src := "d." + name
135 at := fmt.Sprintf("tmp[%d:]", mem.Static)
136 switch {
137 case mem.Kind == "field" && isScalar(mem.Type):
138 return name + " " + mem.GoType, putScalar(mem.Type, src, at), nil
139 case mem.Kind == "field":
140 return name + " " + mem.GoType, fmt.Sprintf("%s.Put(%s);", src, at), nil
141 case mem.Kind == "list" && mem.Count.Kind == "value":
142 count := mem.Size / mem.Type.Size
143 elem := mem.Elem
144 var b strings.Builder
145 fmt.Fprintf(&b, "for i := range %s {", src)
146 target := fmt.Sprintf("tmp[%d+i*%d:]", mem.Static, mem.Type.Size)
147 if isScalar(mem.Type) {
148 b.WriteString(putScalar(mem.Type, src+"[i]", target))
149 } else {
150 fmt.Fprintf(&b, "%s[i].Put(%s);", src, target)
151 }
152 b.WriteString("};")
153 return fmt.Sprintf("%s [%d]%s", name, count, elem), b.String(), nil
154 }
155 return "", "", c.Module.Root.fail("unsupported event member %s for EventData", mem.XName)
156}
157
158func eventDecl(m *Module, e *eventIR, lists map[string]string) (string, error) {
159 var b strings.Builder
160 if e.KindExpr != "" {
161 fmt.Fprintf(&b, "// Kind%s identifies %s events in xturbo.Event.Kind.\nconst Kind%s = %s;\n", e.Name, e.Name, e.Name, e.KindExpr)
162 }
163 view := e.Name + "Event"
164 if e.Copy != nil {
165 src := e.Copy.Name
166 fmt.Fprintf(&b, "// %s shares %s's layout (eventcopy).\ntype %s = %sEvent;\n", view, src, view, src)
167 fmt.Fprintf(&b, "// %sOf views ev as %s without checking ev.Kind.\nfunc %sOf(ev *xturbo.Event) %s { return %sOf(ev) };\n", e.Name, e.Name, e.Name, view, src)
168 if m.ExtName == "" && !e.XGE {
169 fmt.Fprintf(&b, "// %sEventData encodes a %s event for SendEvent with %s's fields.\ntype %sEventData %sEventData;\n", e.Name, e.Name, src, e.Name, src)
170 fmt.Fprintf(&b, "// Put writes the event (code %d, pads and sequence zeroed) into dst.\nfunc (d *%sEventData) Put(dst *[32]byte) { (*%sEventData)(d).Put(dst); dst[0] = %d };\n", e.Code, e.Name, src, e.Code)
171 }
172 return b.String(), nil
173 }
174 c := e.C
175 if e.XGE {
176 fmt.Fprintf(&b, "// %s is a view over a %s generic event: the copied Event.Raw plus the borrowed Event.Tail.\n// Tail is valid only until the Handler callback returns; copy what you keep.\n", view, e.Name)
177 fmt.Fprintf(&b, "type %s struct { raw *[32]byte; tail []byte };\n", view)
178 fmt.Fprintf(&b, "// %sOf views ev as %s; false when ev is not a generic event or Tail does not match its length.\n", e.Name, e.Name)
179 fmt.Fprintf(&b, "func %sOf(ev *xturbo.Event) (%s, bool) { if ev.Raw[0]&0x7f != 35 || len(ev.Tail) != 4*int(binary.LittleEndian.Uint32(ev.Raw[4:])) { return %s{}, false }; return %s{raw: &ev.Raw, tail: ev.Tail}, true };\n", e.Name, view, view, view)
180 fmt.Fprintf(&b, "func (e %s) Extension() uint8 { return e.raw[1] };\n", view)
181 fmt.Fprintf(&b, "func (e %s) Length() uint32 { return binary.LittleEndian.Uint32(e.raw[4:]) };\n", view)
182 fmt.Fprintf(&b, "func (e %s) EventType() uint16 { return binary.LittleEndian.Uint16(e.raw[8:]) };\n", view)
183 fmt.Fprintf(&b, "func (e %s) Tail() []byte { return e.tail };\n", view)
184 } else {
185 fmt.Fprintf(&b, "// %s is a view over a %s event's 32 bytes. %s\n", view, e.Name, eventLifetime)
186 fmt.Fprintf(&b, "type %s struct { raw *[32]byte };\n", view)
187 fmt.Fprintf(&b, "// %sOf views ev as %s without checking ev.Kind: a wrong kind yields garbage, never a panic.\n", e.Name, e.Name)
188 fmt.Fprintf(&b, "func %sOf(ev *xturbo.Event) %s { return %s{raw: &ev.Raw} };\n", e.Name, view, view)
189 }
190 fmt.Fprintf(&b, "func (e %s) Raw() *[32]byte { return e.raw };\n", view)
191 if err := c.getters(&b, view, lists); err != nil {
192 return "", err
193 }
194 if m.ExtName != "" || e.XGE {
195 return b.String(), nil
196 }
197 var fields, puts []string
198 for _, mem := range c.Members {
199 if mem.Kind == "pad" {
200 continue
201 }
202 f, p, err := dataField(c, mem)
203 if err != nil {
204 return "", err
205 }
206 fields = append(fields, f)
207 puts = append(puts, p)
208 }
209 fmt.Fprintf(&b, "// %sEventData encodes a %s event for SendEvent.\ntype %sEventData struct {\n%s\n};\n", e.Name, e.Name, e.Name, strings.Join(fields, "\n"))
210 // Encode into a local array and store it last: borrowed fields (union
211 // views such as ClientMessageData) may alias dst, e.g. d.Put(&ev.Raw).
212 fmt.Fprintf(&b, "// Put writes the event (code %d, pads and sequence zeroed) into dst. dst may alias\n// memory that d's borrowed fields view, such as the Event.Raw they were read from.\nfunc (d *%sEventData) Put(dst *[32]byte) { var tmp [32]byte; tmp[0] = %d; %s; *dst = tmp };\n", e.Code, e.Name, e.Code, strings.Join(puts, ""))
213 return b.String(), nil
214}
215
216// errorIR is one <error> or <errorcopy>.
217type errorIR struct {
218 Const, Name string
219 Code int
220}
221
222func errorConst(xname string) string {
223 return "Bad" + goName(strings.TrimPrefix(xname, "Bad"))
224}
225
226func resolveErrors(m *Module) ([]errorIR, error) {
227 var errs []errorIR
228 seen := map[int]bool{}
229 for _, n := range m.Root.Children {
230 if n.Kind != "error" && n.Kind != "errorcopy" {
231 continue
232 }
233 code, err := strconv.Atoi(n.Attr["number"])
234 if err != nil || code < 0 || code > 255 || seen[code] {
235 return nil, n.fail("invalid or duplicate error number %q", n.Attr["number"])
236 }
237 seen[code] = true
238 e := errorIR{Const: errorConst(n.Attr["name"]), Code: code}
239 e.Name = e.Const
240 if err := m.claim(e.Const, n); err != nil {
241 return nil, err
242 }
243 errs = append(errs, e)
244 }
245 return errs, nil
246}
247
248// errorCount is one past the highest error number.
249func errorCount(m *Module) int {
250 count := 0
251 for _, n := range m.Root.Children {
252 if n.Kind == "error" || n.Kind == "errorcopy" {
253 code, _ := strconv.Atoi(n.Attr["number"])
254 count = max(count, code+1)
255 }
256 }
257 return count
258}
259
260// eventCount is one past the highest non-XGE event number.
261func eventCount(m *Module) (int, error) {
262 count := 0
263 for _, n := range m.Root.Children {
264 if (n.Kind == "event" || n.Kind == "eventcopy") && n.Attr["xge"] != "true" {
265 code, err := eventCode(n)
266 if err != nil {
267 return 0, err
268 }
269 count = max(count, code+1)
270 }
271 }
272 return count, nil
273}
274
275func errorsDecl(m *Module, errs []errorIR) string {
276 var b strings.Builder
277 what := "wire error codes"
278 if m.ExtName != "" {
279 what = "error codes relative to the extension's FirstError (XError.Rel)"
280 }
281 fmt.Fprintf(&b, "// Error codes: %s.\nconst (\n", what)
282 for _, e := range errs {
283 fmt.Fprintf(&b, "%s uint8 = %d\n", e.Const, e.Code)
284 }
285 b.WriteString(");\n")
286 names := make([]string, errorCount(m))
287 for _, e := range errs {
288 names[e.Code] = e.Name
289 }
290 b.WriteString("var errorNames = [...]string{")
291 for _, n := range names {
292 fmt.Fprintf(&b, "%q,", n)
293 }
294 b.WriteString("};\n")
295 fmt.Fprintf(&b, "// ErrorName returns the name of an error code (%s), or \"\" when unknown.\n", what)
296 b.WriteString("func ErrorName(code uint8) string { if int(code) < len(errorNames) { return errorNames[code] }; return \"\" };\n")
297 return b.String()
298}
299
300func emitEvents(modules []*Module) error {
301 for _, m := range modules {
302 events, err := resolveEvents(m)
303 if err != nil {
304 return err
305 }
306 lists := map[string]string{}
307 var decls []string
308 for _, e := range events {
309 d, err := eventDecl(m, e, lists)
310 if err != nil {
311 return err
312 }
313 decls = append(decls, d)
314 }
315 var names []string
316 for name := range lists {
317 // Reuse a list view already declared by replies_generated.go.
318 if old := m.Names[name]; old != nil && old == m.Root {
319 continue
320 }
321 if err := m.claim(name, m.Root); err != nil {
322 return err
323 }
324 names = append(names, name)
325 }
326 sort.Strings(names)
327 for _, name := range names {
328 decls = append(decls, lists[name])
329 }
330 errs, err := resolveErrors(m)
331 if err != nil {
332 return err
333 }
334 if len(errs) > 0 {
335 data := struct {
336 Pkg string
337 Decls []string
338 }{Pkg: m.Pkg, Decls: []string{errorsDecl(m, errs)}}
339 if err := writeTemplate(filepath.Join(m.Pkg, "errors_generated.go"), "errors", data); err != nil {
340 return err
341 }
342 }
343 if len(decls) == 0 {
344 continue
345 }
346 imports := importsFor(m, strings.Join(decls, "\n")) + "\nimport \"bnema.dev/xturbo\""
347 decls = append([]string{imports}, decls...)
348 data := struct {
349 Pkg string
350 Decls []string
351 }{Pkg: m.Pkg, Decls: decls}
352 if err := writeTemplate(filepath.Join(m.Pkg, "events_generated.go"), "events", data); err != nil {
353 return err
354 }
355 if err := emitEventFuzz(m, events); err != nil {
356 return err
357 }
358 }
359 return nil
360}
361
362// emitEventFuzz writes FuzzEventViews: every view getter runs on arbitrary
363// bytes, and for core events EventData built from the getters survives a
364// Put/view round trip. Seeds are the package's event goldens.
365func emitEventFuzz(m *Module, events []*eventIR) error {
366 var b strings.Builder
367 b.WriteString("func FuzzEventViews(f *testing.F) {\nf.Add(make([]byte, 32));\n")
368 goldens, _ := filepath.Glob(filepath.Join("testdata/golden", m.Pkg, "*.event.hex"))
369 sort.Strings(goldens)
370 for _, g := range goldens {
371 fmt.Fprintf(&b, "if raw, err := xtest.LoadHex(%q); err != nil { f.Fatal(err) } else { f.Add(raw) };\n", "../"+filepath.ToSlash(g))
372 }
373 b.WriteString("f.Fuzz(func(t *testing.T, raw []byte) {\nif len(raw) < 32 { return };\nvar ev xturbo.Event;\ncopy(ev.Raw[:], raw);\nev.Tail = raw[32:];\n")
374 for _, e := range events {
375 if e.Copy != nil {
376 fmt.Fprintf(&b, "_ = %sOf(&ev);\n", e.Name)
377 continue
378 }
379 v := "v" + e.Name
380 if e.XGE {
381 fmt.Fprintf(&b, "if %s, ok := %sOf(&ev); ok { _ = %s.Extension(); _ = %s.Length(); _ = %s.EventType(); _ = len(%s.Tail());", v, e.Name, v, v, v, v)
382 } else {
383 fmt.Fprintf(&b, "{ %s := %sOf(&ev);", v, e.Name)
384 }
385 touch(&b, e.C, v, 0, nil, `t.Fatal("iterator error")`)
386 if m.ExtName == "" && !e.XGE {
387 var fields, checks []string
388 for _, mem := range e.C.Members {
389 if mem.Kind == "pad" {
390 continue
391 }
392 name := mem.Method
393 if name == "Put" {
394 name += "_"
395 }
396 switch {
397 case mem.Kind == "list" && mem.ListKind == "bytes":
398 fields = append(fields, fmt.Sprintf("%s: [%d]%s(%s.%s())", name, mem.Size/mem.Type.Size, mem.Elem, v, mem.Method))
399 checks = append(checks, fmt.Sprintf("if !bytes.Equal(w.%s(), %s.%s()) { t.Fatal(%q) };", mem.Method, v, mem.Method, e.Name+"."+mem.Method))
400 case mem.Kind == "field" && isScalar(mem.Type):
401 fields = append(fields, fmt.Sprintf("%s: %s.%s()", name, v, mem.Method))
402 checks = append(checks, fmt.Sprintf("if w.%s() != %s.%s() { t.Fatal(%q) };", mem.Method, v, mem.Method, e.Name+"."+mem.Method))
403 case mem.Kind == "field":
404 fields = append(fields, fmt.Sprintf("%s: %s.%s()", name, v, mem.Method))
405 checks = append(checks, fmt.Sprintf("if !bytes.Equal(out[%d:%d], ev.Raw[%d:%d]) { t.Fatal(%q) };", mem.Static, mem.Static+mem.Size, mem.Static, mem.Static+mem.Size, e.Name+"."+mem.Method))
406 default:
407 return e.Node.fail("event member %s has no EventData round trip", mem.XName)
408 }
409 }
410 fmt.Fprintf(&b, "d := %sEventData{%s}; var out [32]byte; d.Put(&out);", e.Name, strings.Join(fields, ", "))
411 fmt.Fprintf(&b, "if out[0] != %d { t.Fatal(\"%s code\") };", e.Code, e.Name)
412 b.WriteString("back := xturbo.Event{Raw: out};")
413 fmt.Fprintf(&b, "w := %sOf(&back); %s", e.Name, strings.Join(checks, ""))
414 // In place: dst aliases the Raw that d's borrowed fields view.
415 fmt.Fprintf(&b, "in := xturbo.Event{Raw: ev.Raw}; vi := %sOf(&in); di := %sEventData{%s}; di.Put(&in.Raw); if in.Raw != out { t.Fatal(%q) };",
416 e.Name, e.Name, strings.ReplaceAll(strings.Join(fields, ", "), v+".", "vi."), e.Name+" in-place Put")
417 }
418 b.WriteString("};\n")
419 }
420 b.WriteString("})\n}\n")
421 body := b.String()
422 imports := "import \"testing\"\nimport \"bnema.dev/xturbo\""
423 if len(goldens) > 0 {
424 imports += "\nimport \"bnema.dev/xturbo/internal/xtest\""
425 }
426 if strings.Contains(body, "bytes.") {
427 imports += "\nimport \"bytes\""
428 }
429 data := struct {
430 Pkg string
431 Decls []string
432 }{Pkg: m.Pkg, Decls: []string{imports, body}}
433 return writeTemplate(filepath.Join(m.Pkg, "events_fuzz_test.go"), "events", data)
434}