1package main
2
3import (
4 "fmt"
5 "path/filepath"
6 "sort"
7 "strconv"
8 "strings"
9)
10
11// member is one wire member of a decoded container (reply or variable struct
12// view). Static is the offset from the container's byte 0, or -1 when it
13// follows a variable-length list and must be recomputed from earlier members.
14type member struct {
15 XName, Method, Kind, GoType string
16 Elem string // annotated element Go type for list views
17 Type *Type
18 Size, Align, Static int
19 Count *Node
20 ListKind string // bytes, scalar, struct, iter
21}
22
23// container is the lowered layout shared by replies and variable struct views.
24type container struct {
25 Name, Recv string
26 Members []member
27 Fixed int // minimum byte length (static prefix)
28 Dynamic int // index of the first variable list, -1 when none
29 Reply bool
30 Module *Module
31}
32
33const replyLifetime = "It borrows the reply bytes: valid only until the Handler callback returns, because the read ring is reused. Copy what you keep."
34
35func isByteElem(xml string) bool {
36 return xml == "void" || xml == "BYTE" || xml == "CARD8" || xml == "char"
37}
38
39func annotatedType(m *Module, n *Node, typ *Type) (string, error) {
40 goType := qualified(m, typ.Module, typ.GoType)
41 for _, attr := range []string{"enum", "mask"} {
42 if name := n.Attr[attr]; name != "" {
43 e, owner := findEnum(m, name)
44 if e == nil {
45 return "", n.fail("unknown %s %s", attr, name)
46 }
47 goType = qualified(m, owner, e.Name)
48 }
49 }
50 return goType, nil
51}
52
53// elemName names the per-package list view for an element type.
54func elemName(goType string) string {
55 if i := strings.LastIndex(goType, "."); i >= 0 {
56 goType = goType[i+1:]
57 }
58 return strings.ToUpper(goType[:1]) + goType[1:]
59}
60
61// layout selects where a container's XML members land on the wire.
62type layout int
63
64const (
65 layoutStruct layout = iota // members from byte 0
66 layoutReply // byte 1, then byte 8 (after seq and length)
67 layoutEvent // byte 1, then byte 4 (after seq)
68 layoutEventNoSeq // contiguous from byte 1
69 layoutXGE // from byte 10 (after extension, seq, length, evtype)
70)
71
72// headerJump returns the offset following the header data byte, or 0 when the
73// layout has no data byte.
74func (l layout) headerJump() int {
75 switch l {
76 case layoutReply:
77 return 8
78 case layoutEvent:
79 return 4
80 }
81 return 0
82}
83
84// buildContainer lowers XML members. Replies and events start at byte 1 (the
85// first XML member occupies the header data byte) and continue after the
86// sequence (and, for replies, length) header.
87func buildContainer(m *Module, name, recv string, nodes []*Node, lay layout, reserved string) (*container, error) {
88 reply := lay == layoutReply
89 c := &container{Name: name, Recv: recv, Reply: reply, Dynamic: -1, Module: m}
90 off := 0
91 switch lay {
92 case layoutReply, layoutEvent, layoutEventNoSeq:
93 off = 1
94 case layoutXGE:
95 off = 10
96 }
97 what := map[layout]string{layoutStruct: "struct", layoutReply: "reply", layoutEvent: "event", layoutEventNoSeq: "event", layoutXGE: "event"}[lay]
98 first := true
99 methods := map[string]bool{}
100 for _, n := range nodes {
101 if n.Kind == "doc" {
102 continue
103 }
104 mem := member{XName: n.Attr["name"], Kind: n.Kind, Static: off}
105 if c.Dynamic >= 0 {
106 mem.Static = -1
107 }
108 switch n.Kind {
109 case "pad":
110 if a := n.Attr["align"]; a != "" {
111 mem.Align, _ = strconv.Atoi(a)
112 if c.Dynamic < 0 {
113 mem.Size = (-off) & (mem.Align - 1)
114 }
115 } else {
116 mem.Size, _ = strconv.Atoi(n.Attr["bytes"])
117 }
118 case "field", "list":
119 typ, err := lookup(m, n.Attr["type"])
120 if err != nil {
121 return nil, n.fail("%v", err)
122 }
123 mem.Type = typ
124 mem.Method = goName(mem.XName)
125 if contains(reserved, mem.Method) {
126 mem.Method += "_"
127 }
128 if methods[mem.Method] {
129 return nil, n.fail("duplicate getter %s", mem.Method)
130 }
131 methods[mem.Method] = true
132 elem, err := annotatedType(m, n, typ)
133 if err != nil {
134 return nil, err
135 }
136 mem.Elem = elem
137 if n.Kind == "field" {
138 if typ.Variable {
139 return nil, n.fail("variable-size field outside a list is unsupported")
140 }
141 mem.GoType = elem
142 mem.Size = typ.Size
143 break
144 }
145 if len(n.Children) != 1 {
146 return nil, n.fail("reply-side list requires a length expression")
147 }
148 mem.Count = n.Children[0]
149 switch {
150 case isByteElem(n.Attr["type"]):
151 mem.ListKind, mem.GoType = "bytes", "[]byte"
152 case typ.Variable:
153 if typ.Size == 0 {
154 return nil, n.fail("variable struct %s has an empty fixed prefix", typ.Name)
155 }
156 mem.ListKind, mem.GoType = "iter", qualified(m, typ.Module, typ.GoType+"Iter")
157 case isScalar(typ):
158 mem.ListKind, mem.GoType = "scalar", elemName(elem)+"List"
159 default:
160 mem.ListKind, mem.GoType = "struct", elemName(elem)+"List"
161 }
162 constant := mem.Count.Kind == "value" && !typ.Variable
163 if constant {
164 count, _ := strconv.Atoi(mem.Count.Text)
165 mem.Size = count * typ.Size
166 } else if c.Dynamic < 0 {
167 c.Dynamic = len(c.Members)
168 }
169 default:
170 return nil, n.fail("unsupported %s member %s", what, n.Kind)
171 }
172 if jump := lay.headerJump(); jump > 0 && first {
173 if mem.Size != 1 {
174 return nil, n.fail("first %s member must occupy the header data byte", what)
175 }
176 off = jump
177 first = false
178 c.Members = append(c.Members, mem)
179 continue
180 }
181 first = false
182 if c.Dynamic < 0 {
183 off += mem.Size
184 }
185 c.Members = append(c.Members, mem)
186 }
187 if c.Dynamic >= 0 {
188 c.Fixed = c.Members[c.Dynamic].Static
189 } else {
190 c.Fixed = off
191 }
192 return c, nil
193}
194
195// ref renders a field reference as a getter call on the receiver. Replies may
196// refer to the implicit header length word.
197func (c *container) ref(s string) string {
198 for _, mem := range c.Members {
199 if mem.Kind == "field" && mem.XName == s {
200 return "int(" + c.Recv + "." + mem.Method + "())"
201 }
202 }
203 if c.Reply && s == "length" {
204 return "int(binary.LittleEndian.Uint32(" + c.Recv + ".raw[4:]))"
205 }
206 return "<unresolved " + s + ">"
207}
208
209func (c *container) count(mem member) (string, error) {
210 s, err := renderExpr(mem.Count, c.ref, c.Module)
211 if err != nil {
212 return "", err
213 }
214 if strings.Contains(s, "<unresolved") {
215 return "", mem.Count.fail("list length references an unknown field")
216 }
217 return s, nil
218}
219
220// advance emits code moving off past member i. With fail != "" every step is
221// bounds-checked against raw.
222func (c *container) advance(b *strings.Builder, i int, fail string) error {
223 mem := c.Members[i]
224 raw := c.Recv + ".raw"
225 switch mem.Kind {
226 case "pad":
227 if mem.Align > 0 {
228 fmt.Fprintf(b, "off=(off+%d)&^%d;", mem.Align-1, mem.Align-1)
229 } else {
230 fmt.Fprintf(b, "off+=%d;", mem.Size)
231 }
232 case "field":
233 fmt.Fprintf(b, "off+=%d;", mem.Size)
234 case "list":
235 n, err := c.count(mem)
236 if err != nil {
237 return err
238 }
239 if mem.ListKind == "iter" {
240 fmt.Fprintf(b, "{ n:=%s;", n)
241 if fail != "" {
242 fmt.Fprintf(b, "if off>len(%s) || n<0 || n>len(%s) { %s };", raw, raw, fail)
243 }
244 fmt.Fprintf(b, "it:=%s(%s[off:],n); for it.Next() { off+=it.Value().Size() };", qualified(c.Module, mem.Type.Module, "New"+mem.Type.GoType+"Iter"), raw)
245 if fail != "" {
246 fmt.Fprintf(b, "if it.Err() { %s };", fail)
247 }
248 b.WriteString("};")
249 return nil
250 }
251 fmt.Fprintf(b, "{ n:=%s;", n)
252 if fail != "" {
253 fmt.Fprintf(b, "if n<0 || n>len(%s) { %s };", raw, fail)
254 }
255 fmt.Fprintf(b, "off+=n*%d };", mem.Type.Size)
256 if fail != "" {
257 fmt.Fprintf(b, "if off>len(%s) { %s };", raw, fail)
258 }
259 }
260 return nil
261}
262
263// offset emits statements leaving member i's offset in off.
264func (c *container) offset(b *strings.Builder, i int) error {
265 if c.Members[i].Static >= 0 {
266 fmt.Fprintf(b, "off:=%d;", c.Members[i].Static)
267 return nil
268 }
269 fmt.Fprintf(b, "off:=%d;", c.Fixed)
270 for j := c.Dynamic; j < i; j++ {
271 if err := c.advance(b, j, ""); err != nil {
272 return err
273 }
274 }
275 return nil
276}
277
278// validate emits the full-length walk used by Parse and Iter.Next.
279func (c *container) validate(b *strings.Builder, fail string) error {
280 if c.Dynamic < 0 {
281 return nil
282 }
283 fmt.Fprintf(b, "off:=%d;", c.Fixed)
284 for j := c.Dynamic; j < len(c.Members); j++ {
285 if err := c.advance(b, j, fail); err != nil {
286 return err
287 }
288 }
289 fmt.Fprintf(b, "if off>len(%s.raw) { %s };", c.Recv, fail)
290 return nil
291}
292
293// getters emits one method per field and list.
294func (c *container) getters(b *strings.Builder, typ string, lists map[string]string) error {
295 r := c.Recv
296 for i, mem := range c.Members {
297 if mem.Kind == "pad" {
298 continue
299 }
300 fmt.Fprintf(b, "func (%s %s) %s() %s {", r, typ, mem.Method, mem.GoType)
301 static := mem.Static >= 0
302 at := fmt.Sprintf("%s.raw[%d:]", r, mem.Static)
303 if !static {
304 if err := c.offset(b, i); err != nil {
305 return err
306 }
307 at = r + ".raw[off:]"
308 }
309 if mem.Kind == "field" {
310 if isScalar(mem.Type) {
311 fmt.Fprintf(b, "return %s };\n", getScalar(mem.Type, mem.GoType, at))
312 } else {
313 fmt.Fprintf(b, "return %s(%s) };\n", qualified(c.Module, mem.Type.Module, "Get"+mem.Type.GoType), at)
314 }
315 continue
316 }
317 if static {
318 fmt.Fprintf(b, "off:=%d;", mem.Static)
319 }
320 n, err := c.count(mem)
321 if err != nil {
322 return err
323 }
324 switch mem.ListKind {
325 case "iter":
326 fmt.Fprintf(b, "return %s(%s.raw[off:],%s) };\n", qualified(c.Module, mem.Type.Module, "New"+mem.Type.GoType+"Iter"), r, n)
327 case "bytes":
328 fmt.Fprintf(b, "end:=off+%s; return %s.raw[off:end:end] };\n", n, r)
329 default:
330 fmt.Fprintf(b, "end:=off+(%s)*%d; return %s{b:%s.raw[off:end:end]} };\n", n, mem.Type.Size, mem.GoType, r)
331 lists[mem.GoType] = listDecl(c.Module, mem.GoType, mem.Elem, mem.Type)
332 }
333 }
334 return nil
335}
336
337func listDecl(m *Module, name, goType string, elem *Type) string {
338 var b strings.Builder
339 fmt.Fprintf(&b, "// %s is a borrowed view over a wire list of %s. %s\n", name, goType, "It shares the lifetime of the bytes it was taken from; copy what you keep.")
340 fmt.Fprintf(&b, "type %s struct { b []byte };\n", name)
341 fmt.Fprintf(&b, "func (l %s) Len() int { return len(l.b)/%d };\n", name, elem.Size)
342 src := fmt.Sprintf("l.b[i*%d:]", elem.Size)
343 if isScalar(elem) {
344 fmt.Fprintf(&b, "func (l %s) At(i int) %s { return %s };\n", name, goType, getScalar(elem, goType, src))
345 } else {
346 fmt.Fprintf(&b, "func (l %s) At(i int) %s { return %s(%s) };\n", name, goType, qualified(m, elem.Module, "Get"+elem.GoType), src)
347 }
348 return b.String()
349}
350
351type replyIR struct {
352 Req string
353 C *container
354}
355
356func resolveReplies(m *Module) ([]replyIR, []*container, error) {
357 var replies []replyIR
358 for _, n := range m.Root.Children {
359 if n.Kind != "request" {
360 continue
361 }
362 for _, r := range n.Children {
363 if r.Kind != "reply" {
364 continue
365 }
366 name := goName(n.Attr["name"])
367 for _, s := range []string{name + "Reply", "Parse" + name + "Reply"} {
368 if err := m.claim(s, r); err != nil {
369 return nil, nil, err
370 }
371 }
372 c, err := buildContainer(m, name+"Reply", "r", r.Children, layoutReply, "Raw")
373 if err != nil {
374 return nil, nil, err
375 }
376 replies = append(replies, replyIR{Req: name, C: c})
377 }
378 }
379 var views []*container
380 for _, t := range m.Types {
381 if t.Kind != "struct" || !t.Variable {
382 continue
383 }
384 for _, s := range []string{t.GoType + "View", t.GoType + "Iter", "New" + t.GoType + "Iter"} {
385 if err := m.claim(s, t.Node); err != nil {
386 return nil, nil, err
387 }
388 }
389 c, err := buildContainer(m, t.GoType+"View", "v", t.Node.Children, layoutStruct, "Raw Size")
390 if err != nil {
391 return nil, nil, err
392 }
393 views = append(views, c)
394 }
395 return replies, views, nil
396}
397
398func replyDecl(r replyIR, lists map[string]string) (string, error) {
399 c := r.C
400 var b strings.Builder
401 fmt.Fprintf(&b, "// %s is a zero-copy view over a %s reply; offsets count from reply byte 0.\n// %s\n", c.Name, r.Req, replyLifetime)
402 fmt.Fprintf(&b, "type %s struct { raw []byte };\n", c.Name)
403 fmt.Fprintf(&b, "// Parse%s validates raw (the whole reply, 32+4*length bytes) once; getters do not re-check.\n", c.Name)
404 fmt.Fprintf(&b, "func Parse%s(raw []byte) (%s, bool) {", c.Name, c.Name)
405 fixed := max(c.Fixed, 32)
406 fmt.Fprintf(&b, "if len(raw)<%d { return %s{}, false };", fixed, c.Name)
407 fmt.Fprintf(&b, "r:=%s{raw:raw};", c.Name)
408 if err := c.validate(&b, "return "+c.Name+"{}, false"); err != nil {
409 return "", err
410 }
411 b.WriteString("return r, true };\n")
412 fmt.Fprintf(&b, "func (r %s) Raw() []byte { return r.raw };\n", c.Name)
413 if err := c.getters(&b, c.Name, lists); err != nil {
414 return "", err
415 }
416 return b.String(), nil
417}
418
419func viewDecl(c *container, lists map[string]string) (string, error) {
420 base := strings.TrimSuffix(c.Name, "View")
421 iter := base + "Iter"
422 var b strings.Builder
423 fmt.Fprintf(&b, "// %s is a zero-copy view over one wire-encoded %s, yielded by %s.\n// It shares the lifetime of the bytes it was taken from (for replies: until the Handler callback returns). Copy what you keep.\n", c.Name, base, iter)
424 fmt.Fprintf(&b, "type %s struct { raw []byte };\n", c.Name)
425 fmt.Fprintf(&b, "func (v %s) Raw() []byte { return v.raw };\n", c.Name)
426 fmt.Fprintf(&b, "// Size is the element's wire size, including trailing alignment.\nfunc (v %s) Size() int { return len(v.raw) };\n", c.Name)
427 // size%s returns the element's wire size at the start of p, -1 when malformed.
428 fmt.Fprintf(&b, "func size%s(p []byte) int { if len(p)<%d { return -1 }; v:=%s{raw:p}; _=v;", base, c.Fixed, c.Name)
429 if c.Dynamic < 0 {
430 fmt.Fprintf(&b, "return %d };\n", c.Fixed)
431 } else {
432 if err := c.validate(&b, "return -1"); err != nil {
433 return "", err
434 }
435 b.WriteString("return off };\n")
436 }
437 if err := c.getters(&b, c.Name, lists); err != nil {
438 return "", err
439 }
440 fmt.Fprintf(&b, "// %s walks n consecutive %s elements; Err reports a malformed length.\n// It shares the lifetime of the bytes it walks.\n", iter, base)
441 fmt.Fprintf(&b, "type %s struct { b []byte; n int; v %s; err bool };\n", iter, c.Name)
442 fmt.Fprintf(&b, "// New%s iterates n elements encoded at the start of b.\nfunc New%s(b []byte, n int) %s { return %s{b:b, n:n} };\n", iter, iter, iter, iter)
443 fmt.Fprintf(&b, "func (it *%s) Next() bool { if it.n<=0 || it.err { return false }; s:=size%s(it.b); if s<0 { it.err=true; it.n=0; return false }; it.v=%s{raw:it.b[:s:s]}; it.b=it.b[s:]; it.n--; return true };\n", iter, base, c.Name)
444 fmt.Fprintf(&b, "func (it *%s) Value() %s { return it.v };\n", iter, c.Name)
445 fmt.Fprintf(&b, "func (it *%s) Err() bool { return it.err };\n", iter)
446 return b.String(), nil
447}
448
449// touch emits fuzz statements calling every getter and walking every list,
450// recursing into variable struct views.
451func touch(b *strings.Builder, c *container, expr string, depth int, all map[*Type]*container, failIter string) {
452 for _, mem := range c.Members {
453 switch {
454 case mem.Kind == "field":
455 fmt.Fprintf(b, "_ = %s.%s();", expr, mem.Method)
456 case mem.Kind == "list" && mem.ListKind == "bytes":
457 fmt.Fprintf(b, "_ = len(%s.%s());", expr, mem.Method)
458 case mem.Kind == "list" && mem.ListKind == "iter":
459 it := fmt.Sprintf("it%d", depth)
460 fmt.Fprintf(b, "{ %s := %s.%s(); for %s.Next() {", it, expr, mem.Method, it)
461 if child := all[mem.Type]; child != nil {
462 touch(b, child, it+".Value()", depth+1, all, failIter)
463 }
464 fmt.Fprintf(b, "}; if %s.Err() { %s } };", it, failIter)
465 case mem.Kind == "list":
466 l := fmt.Sprintf("l%d", depth)
467 fmt.Fprintf(b, "{ %s := %s.%s(); for i := 0; i < %s.Len(); i++ { _ = %s.At(i) } };", l, expr, mem.Method, l, l)
468 }
469 }
470}
471
472func emitReplies(modules []*Module) error {
473 type pending struct {
474 m *Module
475 replies []replyIR
476 views []*container
477 }
478 var all []pending
479 views := map[*Type]*container{}
480 for _, m := range modules {
481 replies, vs, err := resolveReplies(m)
482 if err != nil {
483 return err
484 }
485 for _, v := range vs {
486 for _, t := range m.Types {
487 if t.GoType+"View" == v.Name {
488 views[t] = v
489 }
490 }
491 }
492 all = append(all, pending{m, replies, vs})
493 }
494 for _, p := range all {
495 m := p.m
496 lists := map[string]string{}
497 var decls []string
498 for _, r := range p.replies {
499 d, err := replyDecl(r, lists)
500 if err != nil {
501 return err
502 }
503 decls = append(decls, d)
504 }
505 for _, v := range p.views {
506 d, err := viewDecl(v, lists)
507 if err != nil {
508 return err
509 }
510 decls = append(decls, d)
511 }
512 var names []string
513 for name := range lists {
514 if err := m.claim(name, m.Root); err != nil {
515 return err
516 }
517 names = append(names, name)
518 }
519 sort.Strings(names)
520 for _, name := range names {
521 decls = append(decls, lists[name])
522 }
523 if len(decls) == 0 {
524 continue
525 }
526 decls = append([]string{importsFor(m, strings.Join(decls, "\n"))}, decls...)
527 data := struct {
528 Pkg string
529 Decls []string
530 }{Pkg: m.Pkg, Decls: decls}
531 if err := writeTemplate(filepath.Join(m.Pkg, "replies_generated.go"), "replies", data); err != nil {
532 return err
533 }
534 if err := emitReplyFuzz(m, p.replies, views); err != nil {
535 return err
536 }
537 }
538 return nil
539}
540
541// emitReplyFuzz writes FuzzParse<Req>Reply for every reply. Seeds are the
542// zero-filled fixed prefix plus any captured goldens <Req>*.reply.hex.
543func emitReplyFuzz(m *Module, replies []replyIR, views map[*Type]*container) error {
544 var decls []string
545 usesGolden := false
546 for _, r := range replies {
547 var b strings.Builder
548 c := r.C
549 fmt.Fprintf(&b, "func FuzzParse%s(f *testing.F) {\n", c.Name)
550 fmt.Fprintf(&b, "f.Add(make([]byte, %d));\n", (max(c.Fixed, 32)+3)&^3)
551 goldens, _ := filepath.Glob(filepath.Join("testdata/golden", m.Pkg, r.Req+"*.reply.hex"))
552 sort.Strings(goldens)
553 for _, g := range goldens {
554 base := filepath.Base(g)
555 if strings.TrimSuffix(base, ".reply.hex") != r.Req && !startsUpper(strings.TrimPrefix(strings.TrimSuffix(base, ".reply.hex"), r.Req)) {
556 continue
557 }
558 usesGolden = true
559 fmt.Fprintf(&b, "if raw, err := xtest.LoadHex(%q); err != nil { f.Fatal(err) } else { f.Add(raw) };\n", "../"+filepath.ToSlash(g))
560 }
561 fmt.Fprintf(&b, "f.Fuzz(func(t *testing.T, raw []byte) {\nr, ok := Parse%s(raw);\nif !ok {", c.Name)
562 if c.Dynamic < 0 {
563 fmt.Fprintf(&b, "if len(raw) >= %d { t.Fatal(\"rejected a complete fixed-size reply\") };", max(c.Fixed, 32))
564 }
565 b.WriteString("return };\n")
566 b.WriteString("if len(r.Raw()) != len(raw) { t.Fatal(\"Raw must return the parsed bytes\") };\n")
567 touch(&b, c, "r", 0, views, `t.Fatal("iterator error after successful Parse")`)
568 b.WriteString("\n})\n}\n")
569 decls = append(decls, b.String())
570 }
571 imports := "import \"testing\""
572 if usesGolden {
573 imports += "\nimport \"bnema.dev/xturbo/internal/xtest\""
574 }
575 decls = append([]string{imports}, decls...)
576 data := struct {
577 Pkg string
578 Decls []string
579 }{Pkg: m.Pkg, Decls: decls}
580 return writeTemplate(filepath.Join(m.Pkg, "replies_fuzz_test.go"), "replies", data)
581}
582
583func startsUpper(s string) bool { return s != "" && s[0] >= 'A' && s[0] <= 'Z' }