a go dns packet parser
1package magna 2 3import ( 4 "errors" 5 "testing" 6 7 "github.com/stretchr/testify/assert" 8 "github.com/stretchr/testify/require" 9) 10 11func TestQuestionDecode(t *testing.T) { 12 tests := []struct { 13 name string 14 input []byte 15 expectedOffset int 16 expected Question 17 expectedErr error 18 wantErrMsg string 19 }{ 20 { 21 name: "Valid question - example.com A IN", 22 input: []byte{7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0, 0, 1, 0, 1}, 23 expectedOffset: 17, 24 expected: Question{ 25 QName: "example.com", 26 QType: DNSType(1), 27 QClass: DNSClass(1), 28 }, 29 expectedErr: nil, 30 }, 31 { 32 name: "Valid question - example.com MX CH", 33 input: []byte{7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0, 0, 15, 0, 3}, 34 expectedOffset: 17, 35 expected: Question{ 36 QName: "example.com", 37 QType: DNSType(15), 38 QClass: DNSClass(3), 39 }, 40 expectedErr: nil, 41 }, 42 { 43 name: "Invalid domain name - label too long", 44 input: []byte{64, 'i', 'n', 'v', 'a', 'l', 'i', 'd', 0, 0, 1, 0, 1}, 45 expectedOffset: 13, 46 expected: Question{}, 47 expectedErr: &InvalidLabelError{}, 48 wantErrMsg: "question decode: failed to decode QName: invalid domain label: length 64 exceeds maximum 63", 49 }, 50 { 51 name: "Invalid domain name - compression loop", 52 input: []byte{0xC0, 0x00, 0, 1, 0, 1}, 53 expectedOffset: 6, 54 expected: Question{}, 55 expectedErr: &DomainCompressionError{}, 56 wantErrMsg: "question decode: failed to decode QName: invalid domain compression: pointer loop detected", 57 }, 58 { 59 name: "Insufficient buffer for QType", 60 input: []byte{7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0, 0}, 61 expectedOffset: 14, 62 expected: Question{QName: "example.com"}, 63 expectedErr: &BufferOverflowError{}, 64 wantErrMsg: "question decode: failed to decode QType for example.com: buffer overflow", 65 }, 66 { 67 name: "Insufficient buffer for QClass", 68 input: []byte{7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0, 0, 1, 0}, 69 expectedOffset: 16, 70 expected: Question{QName: "example.com", QType: DNSType(1)}, 71 expectedErr: &BufferOverflowError{}, 72 wantErrMsg: "question decode: failed to decode QClass for example.com: buffer overflow", 73 }, 74 } 75 76 for _, tt := range tests { 77 t.Run(tt.name, func(t *testing.T) { 78 q := &Question{} 79 offset, err := q.Decode(tt.input, 0) 80 81 if tt.expectedErr != nil { 82 assert.Error(t, err, "Expected an error but got nil") 83 assert.True(t, errors.Is(err, tt.expectedErr), "Error type mismatch. Got %T, expected %T", err, tt.expectedErr) 84 if tt.wantErrMsg != "" { 85 assert.ErrorContains(t, err, tt.wantErrMsg, "Wrapped error message mismatch") 86 } 87 assert.Equal(t, tt.expectedOffset, offset, "Offset mismatch on error") 88 } else { 89 assert.NoError(t, err, "Expected no error but got one") 90 assert.Equal(t, tt.expected, *q, "Decoded question mismatch") 91 assert.Equal(t, tt.expectedOffset, offset, "Offset mismatch on success") 92 } 93 }) 94 } 95} 96 97func TestQuestionEncode(t *testing.T) { 98 tests := []struct { 99 name string 100 question Question 101 initialBuf []byte 102 offsets map[string]uint16 103 expected []byte 104 expectedErr error 105 wantErrMsg string 106 newOffsets map[string]uint16 107 }{ 108 { 109 name: "Simple domain - example.com A IN", 110 question: Question{ 111 QName: "example.com", 112 QType: DNSType(1), 113 QClass: DNSClass(1), 114 }, 115 initialBuf: nil, 116 offsets: make(map[string]uint16), 117 expected: []byte{7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0, 0, 1, 0, 1}, 118 newOffsets: map[string]uint16{"example.com": 0, "com": 8}, 119 }, 120 { 121 name: "Subdomain - subdomain.example.com AAAA IN", 122 question: Question{ 123 QName: "subdomain.example.com", 124 QType: DNSType(28), 125 QClass: DNSClass(1), 126 }, 127 initialBuf: nil, 128 offsets: make(map[string]uint16), 129 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}, 130 newOffsets: map[string]uint16{"subdomain.example.com": 0, "example.com": 10, "com": 18}, 131 }, 132 { 133 name: "Different class - example.com MX CH", 134 question: Question{ 135 QName: "example.com", 136 QType: DNSType(15), 137 QClass: DNSClass(3), 138 }, 139 initialBuf: nil, 140 offsets: make(map[string]uint16), 141 expected: []byte{7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0, 0, 15, 0, 3}, 142 newOffsets: map[string]uint16{"example.com": 0, "com": 8}, 143 }, 144 { 145 name: "Domain compression - example.com after subdomain.example.com", 146 question: Question{ 147 QName: "example.com", 148 QType: DNSType(1), 149 QClass: DNSClass(1), 150 }, 151 initialBuf: nil, 152 offsets: map[string]uint16{ 153 "subdomain.example.com": 0, 154 "example.com": 10, 155 "com": 18, 156 }, 157 expected: []byte{0xC0, 0x0a, 0x00, 0x01, 0x00, 0x01}, 158 newOffsets: map[string]uint16{ 159 "subdomain.example.com": 0, 160 "example.com": 10, 161 "com": 18, 162 }, 163 }, 164 { 165 name: "Encode with initial buffer", 166 question: Question{ 167 QName: "test.org", 168 QType: AType, 169 QClass: IN, 170 }, 171 initialBuf: []byte{0xAA, 0xBB}, 172 offsets: make(map[string]uint16), 173 expected: []byte{0xAA, 0xBB, 4, 't', 'e', 's', 't', 3, 'o', 'r', 'g', 0, 0, 1, 0, 1}, 174 newOffsets: map[string]uint16{"test.org": 2, "org": 7}, 175 }, 176 { 177 name: "Encode invalid domain - label too long", 178 question: Question{ 179 QName: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com", 180 QType: AType, 181 QClass: IN, 182 }, 183 initialBuf: nil, 184 offsets: make(map[string]uint16), 185 expected: nil, 186 expectedErr: &InvalidLabelError{}, 187 wantErrMsg: "question encode: failed to encode QName", 188 newOffsets: map[string]uint16{}, 189 }, 190 } 191 192 for _, tt := range tests { 193 t.Run(tt.name, func(t *testing.T) { 194 currentOffsets := make(map[string]uint16) 195 for k, v := range tt.offsets { 196 currentOffsets[k] = v 197 } 198 199 result, err := tt.question.Encode(tt.initialBuf, &currentOffsets) 200 201 if tt.expectedErr != nil { 202 assert.Error(t, err, "Expected an error but got nil") 203 assert.True(t, errors.Is(err, tt.expectedErr), "Error type mismatch. Got %T, expected %T", err, tt.expectedErr) 204 if tt.wantErrMsg != "" { 205 assert.ErrorContains(t, err, tt.wantErrMsg, "Wrapped error message mismatch") 206 } 207 } else { 208 assert.NoError(t, err, "Expected no error but got one") 209 assert.Equal(t, tt.expected, result, "Encoded question mismatch") 210 assert.Equal(t, tt.newOffsets, currentOffsets, "Final offsets mismatch") 211 } 212 }) 213 } 214} 215 216func TestQuestionEncodeDecodeRoundTrip(t *testing.T) { 217 tests := []struct { 218 name string 219 question Question 220 }{ 221 { 222 name: "Simple domain - example.com A IN", 223 question: Question{ 224 QName: "example.com", 225 QType: DNSType(1), 226 QClass: DNSClass(1), 227 }, 228 }, 229 { 230 name: "Subdomain - subdomain.example.com AAAA IN", 231 question: Question{ 232 QName: "subdomain.example.com", 233 QType: DNSType(28), 234 QClass: DNSClass(1), 235 }, 236 }, 237 { 238 name: "Different class - example.com MX CH", 239 question: Question{ 240 QName: "example.com", 241 QType: DNSType(15), 242 QClass: DNSClass(3), 243 }, 244 }, 245 } 246 247 for _, tt := range tests { 248 t.Run(tt.name, func(t *testing.T) { 249 offsets := make(map[string]uint16) 250 encoded, err := tt.question.Encode(nil, &offsets) 251 require.NoError(t, err, "Encoding failed") 252 253 decodedQuestion := &Question{} 254 offset, err := decodedQuestion.Decode(encoded, 0) 255 256 assert.NoError(t, err, "Decoding failed") 257 assert.Equal(t, len(encoded), offset, "Offset after decoding should match encoded length") 258 assert.Equal(t, tt.question, *decodedQuestion, "Decoded question does not match original") 259 }) 260 } 261}