1package zerobus
2
3import (
4 "errors"
5 "strings"
6 "testing"
7)
8
9func TestSignatures(t *testing.T) {
10 valid := []string{"", "s", "su", "a{sv}", "a(iiay)", "(s(ss))", "aa{s(ii)}", "a{oa{sa{sv}}}", "v", "g", "h"}
11 invalid := []string{"a", "(", "()", "{sv}", "a{vs}", "a{s}", "a{sss}", "(s", "z", "a{(s)s}", "a{as}"}
12 for _, s := range valid {
13 if !validSignature(s) {
14 t.Errorf("%q should be valid", s)
15 }
16 }
17 for _, s := range invalid {
18 if validSignature(s) {
19 t.Errorf("%q should be invalid", s)
20 }
21 }
22 // The spec allows 32 nested arrays and 32 nested structs.
23 nest := func(open, close string, n int) string {
24 return strings.Repeat(open, n) + "y" + strings.Repeat(close, n)
25 }
26 for _, c := range []struct {
27 sig string
28 ok bool
29 }{
30 {nest("a", "", 32), true},
31 {nest("a", "", 33), false},
32 {nest("(", ")", 32), true},
33 {nest("(", ")", 33), false},
34 {nest("a(", ")", 32), true},
35 {strings.Repeat("a{s", 33) + "y" + strings.Repeat("}", 33), false},
36 } {
37 if validSignature(c.sig) != c.ok {
38 t.Errorf("validSignature(%d bytes) = %v, want %v", len(c.sig), !c.ok, c.ok)
39 }
40 }
41}
42
43func TestNames(t *testing.T) {
44 for _, s := range []string{"org.example", "org.kde.StatusNotifierWatcher", "a_b.C1"} {
45 if !validInterface(s) {
46 t.Errorf("interface %q should be valid", s)
47 }
48 }
49 for _, s := range []string{"", "org", "org.", ".org.a", "org.1a", "org.a-b"} {
50 if validInterface(s) {
51 t.Errorf("interface %q should be invalid", s)
52 }
53 }
54 for _, s := range []string{":1.42", "org.freedesktop.StatusNotifierItem-799476-1", "org.a"} {
55 if !validBusName(s) {
56 t.Errorf("bus name %q should be valid", s)
57 }
58 }
59 for _, s := range []string{"", ":", "org", "org..a", "org.1a", "org.a/b"} {
60 if validBusName(s) {
61 t.Errorf("bus name %q should be invalid", s)
62 }
63 }
64 for _, s := range []string{"", "1a", "a.b", "a-b"} {
65 if validMember(s) {
66 t.Errorf("member %q should be invalid", s)
67 }
68 }
69 for name, h := range map[string][4]string{ // dest, path, iface, member
70 "bad destination": {"bad name", "/a", "a.b", "M"},
71 "call no path": {"a.b", "", "a.b", "M"},
72 "call no member": {"a.b", "/a", "a.b", ""},
73 } {
74 var e Encoder
75 e.begin(TypeMethodCall, 1, 0, h[0], h[1], h[2], h[3], "", "")
76 if _, err := e.finish(); !errors.Is(err, ErrInvalid) {
77 t.Errorf("%s accepted: %v", name, err)
78 }
79 }
80 var e Encoder
81 e.begin(TypeSignal, 1, 0, "", "/a", "", "C", "", "")
82 if _, err := e.finish(); !errors.Is(err, ErrInvalid) {
83 t.Errorf("signal without interface accepted: %v", err)
84 }
85 e.begin(TypeError, 1, 1, "", "", "", "", "", "")
86 if _, err := e.finish(); !errors.Is(err, ErrInvalid) {
87 t.Errorf("error without name accepted: %v", err)
88 }
89}
90
91func TestPaths(t *testing.T) {
92 for _, p := range []string{"/", "/a", "/org/kde/StatusNotifierItem", "/a_1/B2"} {
93 if !validPath(p) {
94 t.Errorf("%q should be valid", p)
95 }
96 }
97 for _, p := range []string{"", "a", "//", "/a/", "/a//b", "/a-b", "/a.b"} {
98 if validPath(p) {
99 t.Errorf("%q should be invalid", p)
100 }
101 }
102}
103
104// build encodes a method call with every type, the way Conn does.
105func build(e *Encoder) []byte {
106 const sig = "ybnqiuxtdsogva{sv}ay(si)"
107 e.begin(TypeMethodCall, 7, 0, "org.example", "/org/example", "org.example.I", "M", "", sig)
108 e.Byte(0xAB)
109 e.Bool(true)
110 e.Int16(-2)
111 e.Uint16(3)
112 e.Int32(-4)
113 e.Uint32(5)
114 e.Int64(-6)
115 e.Uint64(7)
116 e.Float64(8.5)
117 e.Str("héllo")
118 e.ObjectPath("/a/b")
119 e.Signature("a{sv}")
120 e.Variant("u")
121 e.Uint32(42)
122 d := e.BeginArray('{')
123 for _, k := range []string{"one", "two"} {
124 e.Struct()
125 e.Str(k)
126 e.Variant("s")
127 e.Str(k + "!")
128 }
129 e.EndArray(d)
130 e.ByteArray([]byte{1, 2, 3})
131 e.Struct()
132 e.Str("x")
133 e.Int32(9)
134 b, err := e.finish()
135 if err != nil {
136 panic(err)
137 }
138 return b
139}
140
141func TestRoundTrip(t *testing.T) {
142 var e Encoder
143 b := build(&e)
144 n, err := frameLen(b)
145 if err != nil || n != len(b) {
146 t.Fatalf("frameLen = %d, %v; want %d", n, err, len(b))
147 }
148 var m Message
149 if err := parse(b, &m); err != nil {
150 t.Fatal(err)
151 }
152 if m.Type != TypeMethodCall || m.Serial != 7 || m.Path != "/org/example" || m.Interface != "org.example.I" ||
153 m.Member != "M" || m.Destination != "org.example" || m.Signature != "ybnqiuxtdsogva{sv}ay(si)" {
154 t.Fatalf("header: %+v", m)
155 }
156 r := m.Body()
157 if r.Byte() != 0xAB || !r.Bool() || r.Int16() != -2 || r.Uint16() != 3 || r.Int32() != -4 || r.Uint32() != 5 ||
158 r.Int64() != -6 || r.Uint64() != 7 || r.Float64() != 8.5 || r.Str() != "héllo" || r.ObjectPath() != "/a/b" ||
159 r.Signature() != "a{sv}" {
160 t.Fatalf("basic values: %v", r.Err())
161 }
162 if sig := r.Variant(); sig != "u" || r.Uint32() != 42 {
163 t.Fatal("variant")
164 }
165 var keys []string
166 end := r.Array('{')
167 for r.More(end) {
168 r.Struct()
169 k := r.Str()
170 if r.Variant() != "s" || r.Str() != k+"!" {
171 t.Fatal("dict value")
172 }
173 keys = append(keys, k)
174 }
175 if len(keys) != 2 || keys[0] != "one" || keys[1] != "two" {
176 t.Fatalf("keys %v", keys)
177 }
178 if ay := r.ByteArray(); len(ay) != 3 || ay[2] != 3 {
179 t.Fatalf("ay %v", ay)
180 }
181 r.Struct()
182 if r.Str() != "x" || r.Int32() != 9 || !r.Done() {
183 t.Fatalf("struct: %v", r.Err())
184 }
185
186 // Skip the same body by its signature.
187 r = m.Body()
188 r.Skip(m.Signature)
189 if !r.Done() {
190 t.Fatalf("skip: %v off=%d len=%d", r.Err(), r.off, len(r.buf))
191 }
192}
193
194func TestEmptyArrayAlignment(t *testing.T) {
195 // An empty array of 8-aligned elements still pads to the element
196 // alignment, and its length excludes that padding.
197 var e Encoder
198 e.begin(TypeSignal, 1, 0, "", "/a", "a.b", "C", "", "a(ii)u")
199 a := e.BeginArray('(')
200 e.EndArray(a)
201 e.Uint32(1)
202 b, err := e.finish()
203 if err != nil {
204 t.Fatal(err)
205 }
206 var m Message
207 if err := parse(b, &m); err != nil {
208 t.Fatal(err)
209 }
210 r := m.Body()
211 end := r.Array('(')
212 if r.More(end) || r.Uint32() != 1 || !r.Done() {
213 t.Fatalf("got %v", r.Err())
214 }
215}
216
217func TestBigEndian(t *testing.T) {
218 // A signal from a big-endian peer: path /a, interface a.b, member C,
219 // body "u" = 0x01020304. The field array is 55 bytes, then 1 byte of
220 // padding before the body.
221 be := []byte{'B', 4, 0, 1, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 55,
222 1, 1, 'o', 0, 0, 0, 0, 2, '/', 'a', 0, 0, 0, 0, 0, 0,
223 2, 1, 's', 0, 0, 0, 0, 3, 'a', '.', 'b', 0, 0, 0, 0, 0,
224 3, 1, 's', 0, 0, 0, 0, 1, 'C', 0, 0, 0, 0, 0, 0, 0,
225 8, 1, 'g', 0, 1, 'u', 0, 0,
226 1, 2, 3, 4}
227 n, err := frameLen(be)
228 if err != nil || n != len(be) {
229 t.Fatalf("frameLen %d %v want %d", n, err, len(be))
230 }
231 var m Message
232 if err := parse(be, &m); err != nil {
233 t.Fatal(err)
234 }
235 if m.Path != "/a" || m.Interface != "a.b" || m.Member != "C" || m.Body().Uint32() != 0x01020304 {
236 t.Fatalf("%+v", m)
237 }
238}
239
240func TestEncoderRejects(t *testing.T) {
241 cases := map[string]func(e *Encoder){
242 "nul in string": func(e *Encoder) { e.Str("a\x00b") },
243 "bad utf-8": func(e *Encoder) { e.Str("\xff") },
244 "bad path": func(e *Encoder) { e.ObjectPath("a/b") },
245 "missing value": func(e *Encoder) {},
246 "wrong type": func(e *Encoder) { e.Byte(1) },
247 "extra value": func(e *Encoder) { e.Str("a"); e.Str("b") },
248 "bad variant sig": func(e *Encoder) { e.Variant("ss") },
249 "wrong element": func(e *Encoder) {
250 a := e.BeginArray('s')
251 e.Uint32(77)
252 e.EndArray(a)
253 },
254 "bad nested variant": func(e *Encoder) {
255 a := e.BeginArray('v')
256 e.Byte(1)
257 e.Str("z")
258 e.EndArray(a)
259 },
260 }
261 sigs := map[string]string{"wrong element": "as", "bad nested variant": "av"}
262 for name, f := range cases {
263 sig := sigs[name]
264 if sig == "" {
265 sig = "s"
266 }
267 var e Encoder
268 e.begin(TypeSignal, 1, 0, "", "/a", "a.b", "C", "", sig)
269 f(&e)
270 if _, err := e.finish(); !errors.Is(err, ErrInvalid) {
271 t.Errorf("%s: err = %v, want ErrInvalid", name, err)
272 }
273 }
274}
275
276func TestParseRejects(t *testing.T) {
277 var e Encoder
278 good := append([]byte(nil), build(&e)...)
279 mutate := map[string]func(b []byte) []byte{
280 "bad endian": func(b []byte) []byte { b[0] = 'x'; return b },
281 "bad version": func(b []byte) []byte { b[3] = 2; return b },
282 "zero serial": func(b []byte) []byte { b[8], b[9], b[10], b[11] = 0, 0, 0, 0; return b },
283 "short body": func(b []byte) []byte { return b[:len(b)-1] },
284 "huge body": func(b []byte) []byte { b[7] = 0xff; return b },
285 "no member": func(b []byte) []byte { return dropField(b, fieldMember) },
286 "bad padding": func(b []byte) []byte { return dirtyPadding(b) },
287 "no signature": func(b []byte) []byte { return dropField(b, fieldSignature) },
288 }
289 for name, f := range mutate {
290 b := f(append([]byte(nil), good...))
291 var m Message
292 n, err := frameLen(b)
293 if err == nil && n == len(b) {
294 err = parse(b, &m)
295 } else if err == nil {
296 err = ErrMalformed
297 }
298 if err == nil {
299 t.Errorf("%s: accepted", name)
300 }
301 }
302}
303
304// dropField renames the header field code to an unknown one, so parse
305// ignores it.
306func dropField(b []byte, code byte) []byte {
307 for i := 16; i < len(b)-1; i += 8 {
308 if b[i] == code && b[i+1] == 1 {
309 b[i] = 200
310 return b
311 }
312 }
313 return b
314}
315
316// dirtyPadding sets the last padding byte before the body to a non-zero
317// value.
318func dirtyPadding(b []byte) []byte {
319 fields := int(b[12]) | int(b[13])<<8
320 if end := headerLen + fields; end%8 != 0 {
321 b[pad(end, 8)-1] = 1
322 }
323 return b
324}
325
326func TestReaderStickyErrors(t *testing.T) {
327 r := Reader{buf: []byte{1, 0, 0}}
328 if r.Uint32() != 0 || r.Err() == nil {
329 t.Fatal("short read should fail")
330 }
331 if r.Byte() != 0 || r.Str() != "" {
332 t.Fatal("reads after an error should return zero values")
333 }
334 // A string without its NUL.
335 r = Reader{buf: []byte{1, 0, 0, 0, 'a', 'b'}}
336 if r.Str() != "" || r.Err() == nil {
337 t.Fatal("string without NUL should fail")
338 }
339 // An array longer than the buffer.
340 r = Reader{buf: []byte{0xff, 0, 0, 0}}
341 end := r.Array('y')
342 if r.More(end) || r.Err() == nil {
343 t.Fatal("oversized array should fail")
344 }
345}
346
347func TestEncodeParseNoAllocs(t *testing.T) {
348 var e Encoder
349 var m Message
350 build(&e) // grow the buffer once
351 allocs := testing.AllocsPerRun(100, func() {
352 b := build(&e)
353 if err := parse(b, &m); err != nil {
354 t.Fatal(err)
355 }
356 r := m.Body()
357 r.Skip(m.Signature)
358 if !r.Done() {
359 t.Fatal(r.Err())
360 }
361 })
362 if allocs != 0 {
363 t.Fatalf("%v allocations per message, want 0", allocs)
364 }
365}
366
367func FuzzParse(f *testing.F) {
368 var e Encoder
369 f.Add(append([]byte(nil), build(&e)...))
370 f.Fuzz(func(t *testing.T, b []byte) {
371 if len(b) < headerLen {
372 return
373 }
374 n, err := frameLen(b)
375 if err != nil || n > len(b) {
376 return
377 }
378 var m Message
379 if parse(b[:n], &m) != nil {
380 return
381 }
382 r := m.Body()
383 r.Skip(m.Signature)
384 })
385}
386
387// FuzzCheck checks that every body the encoder accepts parses back with
388// its signature.
389func FuzzCheck(f *testing.F) {
390 f.Add("a{sv}", []byte{8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 'a', 0, 1, 'u', 0, 0, 0, 0, 7, 0, 0, 0})
391 f.Add("as", []byte{4, 0, 0, 0, 77, 0, 0, 0})
392 f.Fuzz(func(t *testing.T, sig string, body []byte) {
393 if !validSignature(sig) {
394 return
395 }
396 var e Encoder
397 e.begin(TypeSignal, 1, 0, "", "/a", "a.b", "C", "", sig)
398 e.buf = append(e.buf, body...)
399 b, err := e.finish()
400 if err != nil {
401 return
402 }
403 var m Message
404 if err := parse(b, &m); err != nil {
405 t.Fatalf("accepted body does not parse: %v", err)
406 }
407 r := m.Body()
408 r.check(m.Signature)
409 if !r.Done() {
410 t.Fatalf("accepted body does not match its signature: %v", r.Err())
411 }
412 })
413}
414
415func FuzzSkip(f *testing.F) {
416 f.Add("a{sv}", []byte{8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 'a', 0, 1, 'u', 0, 0, 0, 0, 7, 0, 0, 0})
417 f.Add("(v)", []byte{1, 'v', 0})
418 f.Fuzz(func(t *testing.T, sig string, b []byte) {
419 if !validSignature(sig) {
420 return
421 }
422 r := Reader{buf: b}
423 r.Skip(sig)
424 })
425}
426
427func BenchmarkEncodeParse(b *testing.B) {
428 var e Encoder
429 var m Message
430 b.ReportAllocs()
431 for b.Loop() {
432 buf := build(&e)
433 if err := parse(buf, &m); err != nil {
434 b.Fatal(err)
435 }
436 }
437}