Nothing to see here, move along meow
0

Configure Feed

Select the types of activity you want to include in your feed.

at main 12 kB View raw
1use lancer_core::dns::{ 2 DNS_TYPE_A, DNS_TYPE_PTR, MDNS_TTL, build_mdns_a_response, build_mdns_ptr_response, 3 dns_encode_name, dns_name_eq, dns_skip_name, put_u16_be, put_u32_be, 4}; 5use proptest::prelude::*; 6 7fn encode_labels(labels: &[&[u8]]) -> ([u8; 256], usize) { 8 let mut buf = [0u8; 256]; 9 let len = dns_encode_name(&mut buf, labels).unwrap(); 10 (buf, len) 11} 12 13#[test] 14fn encode_single_label() { 15 let (buf, len) = encode_labels(&[b"lancer"]); 16 assert_eq!(len, 8); 17 assert_eq!(buf[0], 6); 18 assert_eq!(&buf[1..7], b"lancer"); 19 assert_eq!(buf[7], 0); 20} 21 22#[test] 23fn encode_multiple_labels() { 24 let (buf, len) = encode_labels(&[b"lancer", b"local"]); 25 assert_eq!(len, 14); 26 assert_eq!(buf[0], 6); 27 assert_eq!(&buf[1..7], b"lancer"); 28 assert_eq!(buf[7], 5); 29 assert_eq!(&buf[8..13], b"local"); 30 assert_eq!(buf[13], 0); 31} 32 33#[test] 34fn encode_empty_labels() { 35 let (buf, len) = encode_labels(&[]); 36 assert_eq!(len, 1); 37 assert_eq!(buf[0], 0); 38} 39 40#[test] 41fn encode_rejects_buffer_too_small() { 42 let mut buf = [0u8; 3]; 43 assert!(dns_encode_name(&mut buf, &[b"lancer"]).is_none()); 44} 45 46#[test] 47fn encode_rejects_label_over_63_bytes() { 48 let long_label = [b'a'; 64]; 49 let mut buf = [0u8; 256]; 50 assert!(dns_encode_name(&mut buf, &[&long_label]).is_none()); 51} 52 53#[test] 54fn encode_accepts_label_exactly_63_bytes() { 55 let label = [b'z'; 63]; 56 let mut buf = [0u8; 256]; 57 assert!(dns_encode_name(&mut buf, &[&label]).is_some()); 58} 59 60#[test] 61fn name_eq_matches_exact() { 62 let (buf, len) = encode_labels(&[b"lancer", b"local"]); 63 let result = dns_name_eq(&buf[..len], 0, &[b"lancer", b"local"]); 64 assert_eq!(result, Some(len)); 65} 66 67#[test] 68fn name_eq_case_insensitive() { 69 let (buf, len) = encode_labels(&[b"LANCER", b"LOCAL"]); 70 let result = dns_name_eq(&buf[..len], 0, &[b"lancer", b"local"]); 71 assert_eq!(result, Some(len)); 72} 73 74#[test] 75fn name_eq_rejects_wrong_labels() { 76 let (buf, len) = encode_labels(&[b"lancer", b"local"]); 77 assert_eq!(dns_name_eq(&buf[..len], 0, &[b"other", b"local"]), None); 78} 79 80#[test] 81fn name_eq_rejects_prefix_match() { 82 let (buf, len) = encode_labels(&[b"lancer", b"local"]); 83 assert_eq!(dns_name_eq(&buf[..len], 0, &[b"lancer"]), None); 84} 85 86#[test] 87fn name_eq_rejects_too_few_wire_labels() { 88 let (buf, len) = encode_labels(&[b"lancer"]); 89 assert_eq!(dns_name_eq(&buf[..len], 0, &[b"lancer", b"local"]), None); 90} 91 92#[test] 93fn name_eq_rejects_truncated_wire() { 94 let (buf, _len) = encode_labels(&[b"lancer", b"local"]); 95 assert_eq!(dns_name_eq(&buf[..5], 0, &[b"lancer", b"local"]), None); 96} 97 98#[test] 99fn name_eq_empty_target() { 100 let (buf, len) = encode_labels(&[]); 101 let result = dns_name_eq(&buf[..len], 0, &[]); 102 assert!(result.is_some()); 103} 104 105#[test] 106fn name_eq_with_offset() { 107 let mut buf = [0u8; 256]; 108 buf[0..4].copy_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]); 109 let written = dns_encode_name(&mut buf[4..], &[b"lancer", b"local"]).unwrap(); 110 let result = dns_name_eq(&buf[..4 + written], 4, &[b"lancer", b"local"]); 111 assert_eq!(result, Some(4 + written)); 112} 113 114#[test] 115fn name_eq_follows_compression_pointer() { 116 let mut buf = [0u8; 64]; 117 let name_len = dns_encode_name(&mut buf, &[b"lancer", b"local"]).unwrap(); 118 buf[name_len] = 0xC0; 119 buf[name_len + 1] = 0x00; 120 let wire_len = name_len + 2; 121 let result = dns_name_eq(&buf[..wire_len], name_len, &[b"lancer", b"local"]); 122 assert_eq!(result, Some(wire_len)); 123} 124 125#[test] 126fn name_eq_partial_compression() { 127 let mut buf = [0u8; 64]; 128 let local_offset = 0usize; 129 let local_len = dns_encode_name(&mut buf, &[b"local"]).unwrap(); 130 let name_start = local_len; 131 buf[name_start] = 6; 132 buf[name_start + 1..name_start + 7].copy_from_slice(b"lancer"); 133 buf[name_start + 7] = 0xC0; 134 buf[name_start + 8] = local_offset as u8; 135 let wire_len = name_start + 9; 136 let result = dns_name_eq(&buf[..wire_len], name_start, &[b"lancer", b"local"]); 137 assert_eq!(result, Some(wire_len)); 138} 139 140#[test] 141fn name_eq_rejects_infinite_pointer_loop() { 142 let buf = [0xC0, 0x00]; 143 assert_eq!(dns_name_eq(&buf, 0, &[b"lancer"]), None); 144} 145 146#[test] 147fn skip_name_regular() { 148 let (buf, len) = encode_labels(&[b"lancer", b"local"]); 149 assert_eq!(dns_skip_name(&buf[..len], 0), Some(len)); 150} 151 152#[test] 153fn skip_name_compression_pointer() { 154 let mut buf = [0u8; 32]; 155 let _ = dns_encode_name(&mut buf, &[b"lancer", b"local"]).unwrap(); 156 buf[20] = 0xC0; 157 buf[21] = 0x00; 158 assert_eq!(dns_skip_name(&buf[..22], 20), Some(22)); 159} 160 161#[test] 162fn skip_name_empty() { 163 let buf = [0u8; 1]; 164 assert_eq!(dns_skip_name(&buf, 0), Some(1)); 165} 166 167#[test] 168fn skip_name_truncated() { 169 let buf = [6u8, b'a']; 170 assert_eq!(dns_skip_name(&buf, 0), None); 171} 172 173#[test] 174fn put_u16_be_correct() { 175 let mut buf = [0u8; 4]; 176 assert!(put_u16_be(&mut buf, 1, 0xABCD).is_some()); 177 assert_eq!(buf[1], 0xAB); 178 assert_eq!(buf[2], 0xCD); 179} 180 181#[test] 182fn put_u16_be_rejects_overflow() { 183 let mut buf = [0u8; 2]; 184 assert!(put_u16_be(&mut buf, 1, 0xABCD).is_none()); 185} 186 187#[test] 188fn put_u32_be_correct() { 189 let mut buf = [0u8; 6]; 190 assert!(put_u32_be(&mut buf, 1, 0x12345678).is_some()); 191 assert_eq!(&buf[1..5], &[0x12, 0x34, 0x56, 0x78]); 192} 193 194#[test] 195fn put_u32_be_rejects_overflow() { 196 let mut buf = [0u8; 3]; 197 assert!(put_u32_be(&mut buf, 1, 0x12345678).is_none()); 198} 199 200#[test] 201fn build_mdns_a_response_structure() { 202 let ip = [10, 0, 0, 42]; 203 let mut buf = [0u8; 512]; 204 let len = build_mdns_a_response(&mut buf, ip).unwrap(); 205 206 assert!(len > 12); 207 208 let flags = ((buf[2] as u16) << 8) | buf[3] as u16; 209 assert_eq!(flags, 0x8400); 210 211 let ancount = ((buf[6] as u16) << 8) | buf[7] as u16; 212 assert_eq!(ancount, 1); 213 214 assert_eq!(&buf[len - 4..len], &ip); 215 216 let ttl_offset = len - 4 - 2 - 4; 217 let ttl = ((buf[ttl_offset] as u32) << 24) 218 | ((buf[ttl_offset + 1] as u32) << 16) 219 | ((buf[ttl_offset + 2] as u32) << 8) 220 | buf[ttl_offset + 3] as u32; 221 assert_eq!(ttl, MDNS_TTL); 222} 223 224#[test] 225fn build_mdns_a_response_type_is_a() { 226 let mut buf = [0u8; 512]; 227 let len = build_mdns_a_response(&mut buf, [192, 168, 1, 1]).unwrap(); 228 229 let name_end = dns_name_eq(&buf[12..len], 0, &[b"lancer", b"local"]) 230 .map(|off| 12 + off) 231 .unwrap(); 232 let qtype = ((buf[name_end] as u16) << 8) | buf[name_end + 1] as u16; 233 assert_eq!(qtype, DNS_TYPE_A); 234} 235 236#[test] 237fn build_mdns_a_response_rejects_tiny_buffer() { 238 let mut buf = [0u8; 10]; 239 assert!(build_mdns_a_response(&mut buf, [10, 0, 0, 1]).is_none()); 240} 241 242#[test] 243fn build_mdns_ptr_response_structure() { 244 let ip = [10, 0, 0, 99]; 245 let mut buf = [0u8; 512]; 246 let len = build_mdns_ptr_response(&mut buf, ip, 22).unwrap(); 247 248 let flags = ((buf[2] as u16) << 8) | buf[3] as u16; 249 assert_eq!(flags, 0x8400); 250 251 let ancount = ((buf[6] as u16) << 8) | buf[7] as u16; 252 assert_eq!(ancount, 1); 253 254 let arcount = ((buf[10] as u16) << 8) | buf[11] as u16; 255 assert_eq!(arcount, 2); 256 257 assert_eq!(&buf[len - 4..len], &ip); 258} 259 260#[test] 261fn build_mdns_ptr_response_contains_srv_port() { 262 let mut buf = [0u8; 512]; 263 let len = build_mdns_ptr_response(&mut buf, [10, 0, 0, 1], 22).unwrap(); 264 265 let has_port_22 = (12..len.saturating_sub(1)).any(|i| buf[i] == 0x00 && buf[i + 1] == 22); 266 assert!(has_port_22, "SRV record should contain port 22"); 267} 268 269#[test] 270fn build_mdns_ptr_response_custom_port() { 271 let mut buf = [0u8; 512]; 272 let len = build_mdns_ptr_response(&mut buf, [10, 0, 0, 1], 8080).unwrap(); 273 274 let has_port = (12..len.saturating_sub(1)).any(|i| buf[i] == 0x1F && buf[i + 1] == 0x90); 275 assert!(has_port, "SRV record should contain port 8080"); 276} 277 278#[test] 279fn build_mdns_ptr_response_has_ptr_type() { 280 let mut buf = [0u8; 512]; 281 let len = build_mdns_ptr_response(&mut buf, [10, 0, 0, 1], 22).unwrap(); 282 283 let svc_end = dns_name_eq(&buf[12..len], 0, &[b"_lancer", b"_tcp", b"local"]) 284 .map(|off| 12 + off) 285 .unwrap(); 286 let qtype = ((buf[svc_end] as u16) << 8) | buf[svc_end + 1] as u16; 287 assert_eq!(qtype, DNS_TYPE_PTR); 288} 289 290fn label_strategy() -> impl Strategy<Value = Vec<u8>> { 291 prop::collection::vec(b'a'..=b'z', 1..=10) 292} 293 294fn labels_strategy() -> impl Strategy<Value = Vec<Vec<u8>>> { 295 prop::collection::vec(label_strategy(), 1..=4) 296} 297 298proptest! { 299 #[test] 300 fn encode_decode_roundtrip(labels in labels_strategy()) { 301 let label_slices: Vec<&[u8]> = labels.iter().map(|l| l.as_slice()).collect(); 302 let mut buf = [0u8; 256]; 303 let len = dns_encode_name(&mut buf, &label_slices).unwrap(); 304 let result = dns_name_eq(&buf[..len], 0, &label_slices); 305 prop_assert!(result.is_some(), "round-trip failed for {:?}", labels); 306 prop_assert_eq!(result.unwrap(), len); 307 } 308 309 #[test] 310 fn encode_format_is_length_prefixed(labels in labels_strategy()) { 311 let label_slices: Vec<&[u8]> = labels.iter().map(|l| l.as_slice()).collect(); 312 let mut buf = [0u8; 256]; 313 let len = dns_encode_name(&mut buf, &label_slices).unwrap(); 314 315 prop_assert!(len > 0); 316 prop_assert_eq!(buf[len - 1], 0, "must end with null terminator"); 317 318 let mut pos = 0usize; 319 label_slices.iter().for_each(|label| { 320 assert_eq!(buf[pos] as usize, label.len()); 321 assert_eq!(&buf[pos + 1..pos + 1 + label.len()], *label); 322 pos += 1 + label.len(); 323 }); 324 prop_assert_eq!(buf[pos], 0); 325 } 326 327 #[test] 328 fn name_eq_case_insensitive_proptest(labels in labels_strategy()) { 329 let upper: Vec<Vec<u8>> = labels.iter().map(|l| { 330 l.iter().map(|b| b.to_ascii_uppercase()).collect() 331 }).collect(); 332 let upper_slices: Vec<&[u8]> = upper.iter().map(|l| l.as_slice()).collect(); 333 let lower_slices: Vec<&[u8]> = labels.iter().map(|l| l.as_slice()).collect(); 334 335 let mut buf = [0u8; 256]; 336 let len = dns_encode_name(&mut buf, &upper_slices).unwrap(); 337 let result = dns_name_eq(&buf[..len], 0, &lower_slices); 338 prop_assert!(result.is_some(), "case-insensitive match failed"); 339 } 340 341 #[test] 342 fn name_eq_rejects_extra_wire_labels(labels in labels_strategy()) { 343 let label_slices: Vec<&[u8]> = labels.iter().map(|l| l.as_slice()).collect(); 344 let mut extended = labels.clone(); 345 extended.push(b"extra".to_vec()); 346 let ext_slices: Vec<&[u8]> = extended.iter().map(|l| l.as_slice()).collect(); 347 let mut buf = [0u8; 256]; 348 let len = dns_encode_name(&mut buf, &ext_slices).unwrap(); 349 let result = dns_name_eq(&buf[..len], 0, &label_slices); 350 prop_assert!(result.is_none(), "prefix match should fail for {:?}", labels); 351 } 352 353 #[test] 354 fn skip_name_agrees_with_encode(labels in labels_strategy()) { 355 let label_slices: Vec<&[u8]> = labels.iter().map(|l| l.as_slice()).collect(); 356 let mut buf = [0u8; 256]; 357 let len = dns_encode_name(&mut buf, &label_slices).unwrap(); 358 let skipped = dns_skip_name(&buf[..len], 0); 359 prop_assert_eq!(skipped, Some(len)); 360 } 361 362 #[test] 363 fn a_response_always_has_valid_header( 364 a in 0u8..=255, 365 b in 0u8..=255, 366 c in 0u8..=255, 367 d in 0u8..=255, 368 ) { 369 let mut buf = [0u8; 512]; 370 let len = build_mdns_a_response(&mut buf, [a, b, c, d]).unwrap(); 371 prop_assert!(len > 12); 372 let flags = ((buf[2] as u16) << 8) | buf[3] as u16; 373 prop_assert_eq!(flags, 0x8400); 374 let ancount = ((buf[6] as u16) << 8) | buf[7] as u16; 375 prop_assert_eq!(ancount, 1); 376 prop_assert_eq!(&buf[len - 4..len], &[a, b, c, d]); 377 } 378}