1use crate::transport::PacketBuffer;
20
21pub const IPV6_SHIM_FORMAT_COMPRESSED: u8 = 0x00;
23
24const IPV6_SHIM_RESIDUAL_SIZE: usize = 6;
26
27const IPV6_HEADER_SIZE: usize = 40;
29
30pub fn compress_ipv6(ipv6_packet: &[u8]) -> Option<Vec<u8>> {
39 if ipv6_packet.len() < IPV6_HEADER_SIZE || ipv6_packet[0] >> 4 != 6 {
40 return None;
41 }
42
43 let upper_payload = &ipv6_packet[IPV6_HEADER_SIZE..];
44 let mut out = Vec::with_capacity(1 + IPV6_SHIM_RESIDUAL_SIZE + upper_payload.len());
45
46 out.push(IPV6_SHIM_FORMAT_COMPRESSED);
48
49 out.extend_from_slice(&ipv6_packet[0..4]);
51
52 out.push(ipv6_packet[6]); out.push(ipv6_packet[7]); out.extend_from_slice(upper_payload);
58
59 Some(out)
60}
61
62pub fn compress_ipv6_with_port_header_in_place(
74 packet: &mut Vec<u8>,
75 src_port: u16,
76 dst_port: u16,
77) -> bool {
78 if packet.len() < IPV6_HEADER_SIZE || packet[0] >> 4 != 6 {
79 return false;
80 }
81
82 let residual_0_3 = [packet[0], packet[1], packet[2], packet[3]];
83 let next_header = packet[6];
84 let hop_limit = packet[7];
85 let upper_payload_len = packet.len() - IPV6_HEADER_SIZE;
86 const PORT_HEADER_SIZE: usize = 4;
87 let compressed_len = PORT_HEADER_SIZE + 1 + IPV6_SHIM_RESIDUAL_SIZE + upper_payload_len;
88
89 packet[0..2].copy_from_slice(&src_port.to_le_bytes());
90 packet[2..4].copy_from_slice(&dst_port.to_le_bytes());
91 packet[4] = IPV6_SHIM_FORMAT_COMPRESSED;
92 packet[5..9].copy_from_slice(&residual_0_3);
93 packet[9] = next_header;
94 packet[10] = hop_limit;
95 packet.copy_within(
96 IPV6_HEADER_SIZE..,
97 PORT_HEADER_SIZE + 1 + IPV6_SHIM_RESIDUAL_SIZE,
98 );
99 packet.truncate(compressed_len);
100 true
101}
102
103pub fn decompress_ipv6(
112 shim_payload: &[u8],
113 src_ipv6: [u8; 16],
114 dst_ipv6: [u8; 16],
115) -> Option<Vec<u8>> {
116 if shim_payload.len() < 1 + IPV6_SHIM_RESIDUAL_SIZE {
117 return None;
118 }
119
120 let format = shim_payload[0];
121 if format != IPV6_SHIM_FORMAT_COMPRESSED {
122 return None;
123 }
124
125 let residual = &shim_payload[1..1 + IPV6_SHIM_RESIDUAL_SIZE];
126 let upper_payload = &shim_payload[1 + IPV6_SHIM_RESIDUAL_SIZE..];
127 let upper_len = upper_payload.len();
128
129 let mut ipv6 = Vec::with_capacity(IPV6_HEADER_SIZE + upper_len);
130
131 ipv6.push((residual[0] & 0x0F) | 0x60);
133 ipv6.extend_from_slice(&residual[1..4]);
134
135 ipv6.extend_from_slice(&(upper_len as u16).to_be_bytes());
137
138 ipv6.push(residual[4]);
140
141 ipv6.push(residual[5]);
143
144 ipv6.extend_from_slice(&src_ipv6);
146
147 ipv6.extend_from_slice(&dst_ipv6);
149
150 ipv6.extend_from_slice(upper_payload);
152
153 Some(ipv6)
154}
155
156pub(crate) fn decompress_ipv6_packet_buffer(
161 packet: &mut PacketBuffer,
162 shim_offset: usize,
163 src_ipv6: [u8; 16],
164 dst_ipv6: [u8; 16],
165) -> bool {
166 let Some(prefix_len) = shim_offset.checked_add(1 + IPV6_SHIM_RESIDUAL_SIZE) else {
167 return false;
168 };
169 if packet.len() < prefix_len {
170 return false;
171 }
172
173 let visible = packet.as_slice();
174 if visible[shim_offset] != IPV6_SHIM_FORMAT_COMPRESSED {
175 return false;
176 }
177 let residual = &visible[shim_offset + 1..prefix_len];
178 let upper_len = visible.len() - prefix_len;
179 let Ok(upper_len) = u16::try_from(upper_len) else {
180 return false;
181 };
182
183 let mut ipv6_header = [0u8; IPV6_HEADER_SIZE];
184 ipv6_header[0] = (residual[0] & 0x0F) | 0x60;
185 ipv6_header[1..4].copy_from_slice(&residual[1..4]);
186 ipv6_header[4..6].copy_from_slice(&upper_len.to_be_bytes());
187 ipv6_header[6] = residual[4];
188 ipv6_header[7] = residual[5];
189 ipv6_header[8..24].copy_from_slice(&src_ipv6);
190 ipv6_header[24..40].copy_from_slice(&dst_ipv6);
191
192 packet.replace_visible_prefix(prefix_len, &ipv6_header)
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 fn build_ipv6_packet(
201 traffic_class: u8,
202 flow_label: u32,
203 next_header: u8,
204 hop_limit: u8,
205 src: [u8; 16],
206 dst: [u8; 16],
207 payload: &[u8],
208 ) -> Vec<u8> {
209 let mut pkt = Vec::with_capacity(IPV6_HEADER_SIZE + payload.len());
210
211 pkt.push(0x60 | (traffic_class >> 4));
213 pkt.push((traffic_class << 4) | ((flow_label >> 16) as u8 & 0x0F));
215 pkt.push((flow_label >> 8) as u8);
217 pkt.push(flow_label as u8);
218
219 pkt.extend_from_slice(&(payload.len() as u16).to_be_bytes());
221
222 pkt.push(next_header);
224
225 pkt.push(hop_limit);
227
228 pkt.extend_from_slice(&src);
230
231 pkt.extend_from_slice(&dst);
233
234 pkt.extend_from_slice(payload);
236
237 pkt
238 }
239
240 fn sample_src() -> [u8; 16] {
241 [
242 0xfd, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
243 0x0e, 0x0f,
244 ]
245 }
246
247 fn sample_dst() -> [u8; 16] {
248 [
249 0xfd, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d,
250 0x1e, 0x1f,
251 ]
252 }
253
254 #[test]
257 fn test_compress_decompress_roundtrip() {
258 let payload = vec![0xAA; 100];
259 let pkt = build_ipv6_packet(0, 0, 17, 64, sample_src(), sample_dst(), &payload);
260
261 let compressed = compress_ipv6(&pkt).unwrap();
262 let decompressed = decompress_ipv6(&compressed, sample_src(), sample_dst()).unwrap();
263
264 assert_eq!(decompressed, pkt);
265 }
266
267 #[test]
268 fn test_in_place_port_header_compression_matches_allocating_path() {
269 let payload = vec![0x5A; 256];
270 let pkt = build_ipv6_packet(0x24, 0x12345, 6, 32, sample_src(), sample_dst(), &payload);
271 let compressed = compress_ipv6(&pkt).unwrap();
272 let src_port = 0x0100u16;
273 let dst_port = 0x0200u16;
274 let mut expected = Vec::with_capacity(4 + compressed.len());
275 expected.extend_from_slice(&src_port.to_le_bytes());
276 expected.extend_from_slice(&dst_port.to_le_bytes());
277 expected.extend_from_slice(&compressed);
278
279 let mut in_place = pkt.clone();
280 assert!(compress_ipv6_with_port_header_in_place(
281 &mut in_place,
282 src_port,
283 dst_port
284 ));
285
286 assert_eq!(in_place, expected);
287 }
288
289 #[test]
290 fn test_packet_buffer_decompression_matches_allocating_path() {
291 let payload = vec![0x5A; 256];
292 let pkt = build_ipv6_packet(0x24, 0x12345, 6, 32, sample_src(), sample_dst(), &payload);
293 let compressed = compress_ipv6(&pkt).unwrap();
294
295 const HEADROOM: usize = 23;
296 const INNER_HEADER_LEN: usize = 6;
297 const PORT_HEADER_LEN: usize = 4;
298 let mut stored = vec![0xEE; HEADROOM];
299 stored.extend_from_slice(&[0, 0, 0, 0, 0x10, 0]);
300 stored.extend_from_slice(&0x0100u16.to_le_bytes());
301 stored.extend_from_slice(&0x0100u16.to_le_bytes());
302 stored.extend_from_slice(&compressed);
303 let mut packet = PacketBuffer::new(stored);
304 assert!(packet.trim_front(HEADROOM));
305
306 assert!(decompress_ipv6_packet_buffer(
307 &mut packet,
308 INNER_HEADER_LEN + PORT_HEADER_LEN,
309 sample_src(),
310 sample_dst()
311 ));
312
313 assert_eq!(packet.as_slice(), pkt.as_slice());
314 }
315
316 #[test]
317 fn test_roundtrip_empty_payload() {
318 let pkt = build_ipv6_packet(0, 0, 59, 1, sample_src(), sample_dst(), &[]);
319
320 let compressed = compress_ipv6(&pkt).unwrap();
321 assert_eq!(compressed.len(), 1 + IPV6_SHIM_RESIDUAL_SIZE); let decompressed = decompress_ipv6(&compressed, sample_src(), sample_dst()).unwrap();
324 assert_eq!(decompressed, pkt);
325 }
326
327 #[test]
328 fn test_roundtrip_large_payload() {
329 let payload = vec![0x55; 1400];
330 let pkt = build_ipv6_packet(0, 0, 6, 128, sample_src(), sample_dst(), &payload);
331
332 let compressed = compress_ipv6(&pkt).unwrap();
333 let decompressed = decompress_ipv6(&compressed, sample_src(), sample_dst()).unwrap();
334
335 assert_eq!(decompressed, pkt);
336 }
337
338 #[test]
341 fn test_preserves_traffic_class() {
342 let pkt = build_ipv6_packet(0xAB, 0, 17, 64, sample_src(), sample_dst(), &[1, 2, 3]);
344
345 let compressed = compress_ipv6(&pkt).unwrap();
346 let decompressed = decompress_ipv6(&compressed, sample_src(), sample_dst()).unwrap();
347
348 assert_eq!(decompressed, pkt);
349 let tc = ((decompressed[0] & 0x0F) << 4) | (decompressed[1] >> 4);
351 assert_eq!(tc, 0xAB);
352 }
353
354 #[test]
355 fn test_preserves_flow_label() {
356 let pkt = build_ipv6_packet(0, 0xFEDCB, 17, 64, sample_src(), sample_dst(), &[1]);
357
358 let compressed = compress_ipv6(&pkt).unwrap();
359 let decompressed = decompress_ipv6(&compressed, sample_src(), sample_dst()).unwrap();
360
361 assert_eq!(decompressed, pkt);
362 }
363
364 #[test]
365 fn test_preserves_tc_and_flow_label_combined() {
366 let pkt = build_ipv6_packet(0xFF, 0xFFFFF, 17, 64, sample_src(), sample_dst(), &[1]);
368
369 let compressed = compress_ipv6(&pkt).unwrap();
370 let decompressed = decompress_ipv6(&compressed, sample_src(), sample_dst()).unwrap();
371
372 assert_eq!(decompressed, pkt);
373 }
374
375 #[test]
376 fn test_preserves_next_header_tcp() {
377 let pkt = build_ipv6_packet(0, 0, 6, 64, sample_src(), sample_dst(), &[0; 20]);
378
379 let compressed = compress_ipv6(&pkt).unwrap();
380 let decompressed = decompress_ipv6(&compressed, sample_src(), sample_dst()).unwrap();
381
382 assert_eq!(decompressed[6], 6); }
384
385 #[test]
386 fn test_preserves_next_header_icmpv6() {
387 let pkt = build_ipv6_packet(0, 0, 58, 255, sample_src(), sample_dst(), &[0; 8]);
388
389 let compressed = compress_ipv6(&pkt).unwrap();
390 let decompressed = decompress_ipv6(&compressed, sample_src(), sample_dst()).unwrap();
391
392 assert_eq!(decompressed[6], 58); assert_eq!(decompressed[7], 255); }
395
396 #[test]
397 fn test_preserves_hop_limit() {
398 for hop_limit in [0, 1, 64, 128, 255] {
399 let pkt = build_ipv6_packet(0, 0, 17, hop_limit, sample_src(), sample_dst(), &[1]);
400
401 let compressed = compress_ipv6(&pkt).unwrap();
402 let decompressed = decompress_ipv6(&compressed, sample_src(), sample_dst()).unwrap();
403
404 assert_eq!(decompressed[7], hop_limit);
405 }
406 }
407
408 #[test]
411 fn test_payload_length_reconstructed() {
412 let payload = vec![0xBB; 256];
413 let pkt = build_ipv6_packet(0, 0, 17, 64, sample_src(), sample_dst(), &payload);
414
415 let compressed = compress_ipv6(&pkt).unwrap();
416 let decompressed = decompress_ipv6(&compressed, sample_src(), sample_dst()).unwrap();
417
418 let payload_len = u16::from_be_bytes([decompressed[4], decompressed[5]]);
419 assert_eq!(payload_len, 256);
420 }
421
422 #[test]
425 fn test_compression_saves_bytes() {
426 let payload = vec![0; 100];
427 let pkt = build_ipv6_packet(0, 0, 17, 64, sample_src(), sample_dst(), &payload);
428
429 let compressed = compress_ipv6(&pkt).unwrap();
430
431 assert_eq!(pkt.len(), 140);
435 assert_eq!(compressed.len(), 107);
436 assert_eq!(pkt.len() - compressed.len(), 33);
437 }
438
439 #[test]
442 fn test_compress_rejects_non_ipv6() {
443 let mut pkt = build_ipv6_packet(0, 0, 17, 64, sample_src(), sample_dst(), &[1]);
444 pkt[0] = 0x40; assert!(compress_ipv6(&pkt).is_none());
446 }
447
448 #[test]
449 fn test_compress_rejects_short_packet() {
450 assert!(compress_ipv6(&[0x60; 39]).is_none());
451 assert!(compress_ipv6(&[]).is_none());
452 }
453
454 #[test]
455 fn test_decompress_rejects_unknown_format() {
456 let mut compressed = vec![0x01]; compressed.extend_from_slice(&[0; IPV6_SHIM_RESIDUAL_SIZE]);
458 assert!(decompress_ipv6(&compressed, sample_src(), sample_dst()).is_none());
459 }
460
461 #[test]
462 fn test_decompress_rejects_short_payload() {
463 assert!(decompress_ipv6(&[0x00; 6], sample_src(), sample_dst()).is_none());
465 assert!(decompress_ipv6(&[], sample_src(), sample_dst()).is_none());
466 }
467
468 #[test]
471 fn test_addresses_from_context() {
472 let original_src = [
473 0xfd, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
474 0xAA, 0xAA,
475 ];
476 let original_dst = [
477 0xfd, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
478 0xBB, 0xBB,
479 ];
480 let pkt = build_ipv6_packet(0, 0, 17, 64, original_src, original_dst, &[1, 2]);
481
482 let compressed = compress_ipv6(&pkt).unwrap();
483
484 let context_src = sample_src();
486 let context_dst = sample_dst();
487 let decompressed = decompress_ipv6(&compressed, context_src, context_dst).unwrap();
488
489 assert_eq!(&decompressed[8..24], &context_src);
491 assert_eq!(&decompressed[24..40], &context_dst);
492
493 assert_eq!(&decompressed[0..4], &pkt[0..4]); assert_eq!(decompressed[6], pkt[6]); assert_eq!(decompressed[7], pkt[7]); assert_eq!(&decompressed[40..], &pkt[40..]); }
499}