1package magna
2
3import (
4 "testing"
5
6 "github.com/stretchr/testify/assert"
7)
8
9func TestQuestionDecode(t *testing.T) {
10 tests := []struct {
11 name string
12 input []byte
13 expectedOffset int
14 expected Question
15 expectedErr error
16 }{
17 {
18 name: "Valid question - example.com A IN",
19 input: []byte{7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0, 0, 1, 0, 1},
20 expectedOffset: 17,
21 expected: Question{
22 QName: "example.com",
23 QType: DNSType(1),
24 QClass: DNSClass(1),
25 },
26 expectedErr: nil,
27 },
28 {
29 name: "Valid question - example.com MX CH",
30 input: []byte{7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0, 0, 15, 0, 3},
31 expectedOffset: 17,
32 expected: Question{
33 QName: "example.com",
34 QType: DNSType(15),
35 QClass: DNSClass(3),
36 },
37 expectedErr: nil,
38 },
39 {
40 name: "Invalid domain name",
41 input: []byte{255, 'i', 'n', 'v', 'a', 'l', 'i', 'd', 0, 0, 1, 0, 1},
42 expectedOffset: 13,
43 expected: Question{},
44 expectedErr: &BufferOverflowError{},
45 },
46 {
47 name: "Insufficient buffer for QType",
48 input: []byte{7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0, 0},
49 expectedOffset: 14,
50 expected: Question{QName: "example.com"},
51 expectedErr: &BufferOverflowError{},
52 },
53 {
54 name: "Insufficient buffer for QClass",
55 input: []byte{7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0, 0, 1, 0},
56 expectedOffset: 16,
57 expected: Question{QName: "example.com", QType: DNSType(1)},
58 expectedErr: &BufferOverflowError{},
59 },
60 }
61
62 for _, tt := range tests {
63 t.Run(tt.name, func(t *testing.T) {
64 q := &Question{}
65 offset, err := q.Decode(tt.input, 0)
66
67 assert.Equal(t, tt.expectedOffset, offset)
68
69 if tt.expectedErr != nil {
70 assert.Error(t, err)
71 assert.IsType(t, tt.expectedErr, err)
72 } else {
73 assert.NoError(t, err)
74 assert.Equal(t, tt.expected, *q)
75 }
76 })
77 }
78}
79
80func TestQuestionEncode(t *testing.T) {
81 tests := []struct {
82 name string
83 question Question
84 offsets map[string]uint16
85 expected []byte
86 }{
87 {
88 name: "Simple domain - example.com A IN",
89 question: Question{
90 QName: "example.com",
91 QType: DNSType(1),
92 QClass: DNSClass(1),
93 },
94 offsets: make(map[string]uint16),
95 expected: []byte{7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0, 0, 1, 0, 1},
96 },
97 {
98 name: "Subdomain - subdomain.example.com AAAA IN",
99 question: Question{
100 QName: "subdomain.example.com",
101 QType: DNSType(28),
102 QClass: DNSClass(1),
103 },
104 offsets: make(map[string]uint16),
105 expected: []byte{9, 's', 'u', 'b', 'd', 'o', 'm', 'a', 'i', 'n', 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0, 0, 28, 0, 1},
106 },
107 {
108 name: "Different class - example.com MX CH",
109 question: Question{
110 QName: "example.com",
111 QType: DNSType(15),
112 QClass: DNSClass(3),
113 },
114 offsets: make(map[string]uint16),
115 expected: []byte{7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0, 0, 15, 0, 3},
116 },
117 {
118 name: "Domain compression - example.com after subdomain.example.com",
119 question: Question{
120 QName: "example.com",
121 QType: DNSType(1),
122 QClass: DNSClass(1),
123 },
124 offsets: map[string]uint16{
125 "com": 22,
126 "example.com": 19,
127 },
128 expected: []byte{0xC0, 0x13, 0x00, 0x01, 0x00, 0x01},
129 },
130 }
131
132 for _, tt := range tests {
133 t.Run(tt.name, func(t *testing.T) {
134 result := tt.question.Encode(nil, &tt.offsets)
135 assert.Equal(t, tt.expected, result)
136
137 if len(tt.offsets) == 0 {
138 expectedOffsets := map[string]uint16{
139 tt.question.QName: 0,
140 }
141 for i := 0; i < len(tt.question.QName); i++ {
142 if tt.question.QName[i] == '.' {
143 expectedOffsets[tt.question.QName[i+1:]] = uint16(i + 1)
144 }
145 }
146 assert.Equal(t, expectedOffsets, tt.offsets)
147 }
148 })
149 }
150}
151
152func TestQuestionEncodeDecodeRoundTrip(t *testing.T) {
153 tests := []struct {
154 name string
155 question Question
156 }{
157 {
158 name: "Simple domain - example.com A IN",
159 question: Question{
160 QName: "example.com",
161 QType: DNSType(1),
162 QClass: DNSClass(1),
163 },
164 },
165 {
166 name: "Subdomain - subdomain.example.com AAAA IN",
167 question: Question{
168 QName: "subdomain.example.com",
169 QType: DNSType(28),
170 QClass: DNSClass(1),
171 },
172 },
173 {
174 name: "Different class - example.com MX CH",
175 question: Question{
176 QName: "example.com",
177 QType: DNSType(15),
178 QClass: DNSClass(3),
179 },
180 },
181 }
182
183 for _, tt := range tests {
184 t.Run(tt.name, func(t *testing.T) {
185 offsets := make(map[string]uint16)
186 encoded := tt.question.Encode(nil, &offsets)
187
188 decodedQuestion := &Question{}
189 _, err := decodedQuestion.Decode(encoded, 0)
190
191 assert.NoError(t, err)
192 assert.Equal(t, tt.question, *decodedQuestion)
193 })
194 }
195}
196
197func TestQuestionEncodeWithExistingBuffer(t *testing.T) {
198 question := Question{
199 QName: "example.com",
200 QType: DNSType(1),
201 QClass: DNSClass(1),
202 }
203
204 existingBuffer := []byte{0xFF, 0xFF, 0xFF, 0xFF}
205 offsets := make(map[string]uint16)
206
207 result := question.Encode(existingBuffer, &offsets)
208
209 expected := append(
210 existingBuffer,
211 []byte{7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0, 0, 1, 0, 1}...,
212 )
213
214 assert.Equal(t, expected, result)
215}
216
217func TestQuestionEncodeLongDomainName(t *testing.T) {
218 longLabel := make([]byte, 63)
219 for i := range longLabel {
220 longLabel[i] = 'a'
221 }
222 longDomainName := string(longLabel) + "." + string(longLabel) + "." + string(longLabel) + "." + string(longLabel[:61])
223
224 question := Question{
225 QName: longDomainName,
226 QType: DNSType(1),
227 QClass: DNSClass(1),
228 }
229
230 offsets := make(map[string]uint16)
231 encoded := question.Encode(nil, &offsets)
232
233 assert.Equal(t, 259, len(encoded))
234
235 decodedQuestion := &Question{}
236 _, err := decodedQuestion.Decode(encoded, 0)
237
238 assert.NoError(t, err)
239 assert.Equal(t, question, *decodedQuestion)
240}