blob: fa31f67bec2cfea64734cf2b3d86c3b6711cbb47 [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
2// SPDX-License-Identifier: Apache-2.0
3
Lorenz Brunc7b036b2023-06-01 12:23:57 +02004package kmod
5
6import (
7 "fmt"
8 "regexp"
9 "strings"
10 "testing"
11 "unicode"
12
13 "github.com/google/go-cmp/cmp"
14 "google.golang.org/protobuf/testing/protocmp"
15
Tim Windelschmidt9f21f532024-05-07 15:14:20 +020016 kmodpb "source.monogon.dev/osbase/kmod/spec"
Lorenz Brunc7b036b2023-06-01 12:23:57 +020017)
18
19func TestParsePattern(t *testing.T) {
20 cases := []struct {
21 name string
22 pattern string
23 expectedNodes []*kmodpb.RadixNode
24 }{
25 {"Empty", "", nil},
Tim Windelschmidta10d0cb2025-01-13 14:44:15 +010026 {"SingleLiteral", "asdf", []*kmodpb.RadixNode{{Type: kmodpb.RadixNode_TYPE_LITERAL, Literal: "asdf"}}},
Lorenz Brunc7b036b2023-06-01 12:23:57 +020027 {"SingleWildcard", "as*df", []*kmodpb.RadixNode{
Tim Windelschmidta10d0cb2025-01-13 14:44:15 +010028 {Type: kmodpb.RadixNode_TYPE_LITERAL, Literal: "as"},
29 {Type: kmodpb.RadixNode_TYPE_WILDCARD},
30 {Type: kmodpb.RadixNode_TYPE_LITERAL, Literal: "df"},
Lorenz Brunc7b036b2023-06-01 12:23:57 +020031 }},
Tim Windelschmidta10d0cb2025-01-13 14:44:15 +010032 {"EscapedWildcard", "a\\*", []*kmodpb.RadixNode{{Type: kmodpb.RadixNode_TYPE_LITERAL, Literal: "a*"}}},
33 {"SingleRange", "[y-z]", []*kmodpb.RadixNode{{Type: kmodpb.RadixNode_TYPE_BYTE_RANGE, StartByte: 121, EndByte: 122}}},
Lorenz Brunc7b036b2023-06-01 12:23:57 +020034 {"SingleWildcardChar", "a?c", []*kmodpb.RadixNode{
Tim Windelschmidta10d0cb2025-01-13 14:44:15 +010035 {Type: kmodpb.RadixNode_TYPE_LITERAL, Literal: "a"},
36 {Type: kmodpb.RadixNode_TYPE_SINGLE_WILDCARD},
37 {Type: kmodpb.RadixNode_TYPE_LITERAL, Literal: "c"},
Lorenz Brunc7b036b2023-06-01 12:23:57 +020038 }},
39 }
40 for _, c := range cases {
41 t.Run(c.name, func(t *testing.T) {
42 out, err := parsePattern(c.pattern)
43 if err != nil {
44 t.Fatal(err)
45 }
46 diff := cmp.Diff(c.expectedNodes, out, protocmp.Transform())
47 if diff != "" {
Tim Windelschmidtd0d5d9d2025-03-26 22:07:11 +010048 t.Fatal(diff)
Lorenz Brunc7b036b2023-06-01 12:23:57 +020049 }
50 })
51 }
52}
53
54func TestLookupComplex(t *testing.T) {
55 root := &kmodpb.RadixNode{
Tim Windelschmidta10d0cb2025-01-13 14:44:15 +010056 Type: kmodpb.RadixNode_TYPE_LITERAL,
Lorenz Brunc7b036b2023-06-01 12:23:57 +020057 }
58 if err := AddPattern(root, "usb:v0B95p1790d*dc*dsc*dp*icFFiscFFip00in*", 2); err != nil {
Tim Windelschmidtd0d5d9d2025-03-26 22:07:11 +010059 t.Fatal(err)
Lorenz Brunc7b036b2023-06-01 12:23:57 +020060 }
61 if err := AddPattern(root, "usb:v0B95p178Ad*dc*dsc*dp*icFFiscFFip00in*", 3); err != nil {
Tim Windelschmidtd0d5d9d2025-03-26 22:07:11 +010062 t.Fatal(err)
Lorenz Brunc7b036b2023-06-01 12:23:57 +020063 }
64 if err := AddPattern(root, "acpi*:PNP0C14:*", 10); err != nil {
Tim Windelschmidtd0d5d9d2025-03-26 22:07:11 +010065 t.Fatal(err)
Lorenz Brunc7b036b2023-06-01 12:23:57 +020066 }
67 matches := make(map[uint32]bool)
68 lookupModulesRec(root, "acpi:PNP0C14:asdf", matches)
69 if !matches[10] {
Tim Windelschmidtd0d5d9d2025-03-26 22:07:11 +010070 t.Fatal("value should match pattern 10")
Lorenz Brunc7b036b2023-06-01 12:23:57 +020071 }
72}
73
74func isASCII(s string) bool {
75 for i := 0; i < len(s); i++ {
76 if s[i] > unicode.MaxASCII {
77 return false
78 }
79 }
80 return true
81}
82
83func FuzzRadixImpl(f *testing.F) {
84 f.Add("acpi*:PNP0C14:*\x00usb:v0B95p1790d*dc*dsc*dp*icFFiscFFip00in*", "acpi:PNP0C14:asdf\x00usb:v0B95p1790d0dc0dsc0dp0icFFiscFFip00in")
85 f.Fuzz(func(t *testing.T, a string, b string) {
86 patternsRaw := strings.Split(a, "\x00")
87 values := strings.Split(b, "\x00")
88 var patternsRegexp []regexp.Regexp
89 root := &kmodpb.RadixNode{
Tim Windelschmidta10d0cb2025-01-13 14:44:15 +010090 Type: kmodpb.RadixNode_TYPE_LITERAL,
Lorenz Brunc7b036b2023-06-01 12:23:57 +020091 }
92 for i, p := range patternsRaw {
93 if !isASCII(p) {
94 // Ignore non-ASCII patterns, there are tons of edge cases with them
95 return
96 }
97 pp, err := parsePattern(p)
98 if err != nil {
99 // Bad pattern
100 return
101 }
102 if err := AddPattern(root, p, uint32(i)); err != nil {
103 t.Fatal(err)
104 }
105 var regexb strings.Builder
106 regexb.WriteString("(?s)^")
107 for _, part := range pp {
108 switch part.Type {
Tim Windelschmidta10d0cb2025-01-13 14:44:15 +0100109 case kmodpb.RadixNode_TYPE_LITERAL:
Lorenz Brunc7b036b2023-06-01 12:23:57 +0200110 regexb.WriteString(regexp.QuoteMeta(part.Literal))
Tim Windelschmidta10d0cb2025-01-13 14:44:15 +0100111 case kmodpb.RadixNode_TYPE_SINGLE_WILDCARD:
Lorenz Brunc7b036b2023-06-01 12:23:57 +0200112 regexb.WriteString(".")
Tim Windelschmidta10d0cb2025-01-13 14:44:15 +0100113 case kmodpb.RadixNode_TYPE_WILDCARD:
Lorenz Brunc7b036b2023-06-01 12:23:57 +0200114 regexb.WriteString(".*")
Tim Windelschmidta10d0cb2025-01-13 14:44:15 +0100115 case kmodpb.RadixNode_TYPE_BYTE_RANGE:
Lorenz Brunc7b036b2023-06-01 12:23:57 +0200116 regexb.WriteString(fmt.Sprintf("[%s-%s]", regexp.QuoteMeta(string([]rune{rune(part.StartByte)})), regexp.QuoteMeta(string([]rune{rune(part.EndByte)}))))
117 default:
118 t.Errorf("Unknown node type %v", part.Type)
119 }
120 }
121 regexb.WriteString("$")
122 patternsRegexp = append(patternsRegexp, *regexp.MustCompile(regexb.String()))
123 }
124 for _, v := range values {
125 if !isASCII(v) {
126 // Ignore non-ASCII values
127 return
128 }
129 if len(v) > 64 {
130 // Ignore big values as they are not realistic and cause the
131 // wildcard matches to be very expensive.
132 return
133 }
134 radixMatchesSet := make(map[uint32]bool)
135 lookupModulesRec(root, v, radixMatchesSet)
136 for i, re := range patternsRegexp {
137 if re.MatchString(v) {
138 if !radixMatchesSet[uint32(i)] {
139 t.Errorf("Pattern %q is expected to match %q but didn't", patternsRaw[i], v)
140 }
141 } else {
142 if radixMatchesSet[uint32(i)] {
143 t.Errorf("Pattern %q is not expected to match %q but did", patternsRaw[i], v)
144 }
145 }
146 }
147 }
148 })
149}