1pub const IPV6_SHIM_FORMAT_COMPRESSED: u8 = 0x00;
21
22const IPV6_SHIM_RESIDUAL_SIZE: usize = 6;
24
25const IPV6_HEADER_SIZE: usize = 40;
27
28pub fn compress_ipv6(ipv6_packet: &[u8]) -> Option<Vec<u8>> {
37 if ipv6_packet.len() < IPV6_HEADER_SIZE || ipv6_packet[0] >> 4 != 6 {
38 return None;
39 }
40
41 let upper_payload = &ipv6_packet[IPV6_HEADER_SIZE..];
42 let mut out = Vec::with_capacity(1 + IPV6_SHIM_RESIDUAL_SIZE + upper_payload.len());
43
44 out.push(IPV6_SHIM_FORMAT_COMPRESSED);
46
47 out.extend_from_slice(&ipv6_packet[0..4]);
49
50 out.push(ipv6_packet[6]); out.push(ipv6_packet[7]); out.extend_from_slice(upper_payload);
56
57 Some(out)
58}
59
60pub fn decompress_ipv6(
69 shim_payload: &[u8],
70 src_ipv6: [u8; 16],
71 dst_ipv6: [u8; 16],
72) -> Option<Vec<u8>> {
73 if shim_payload.len() < 1 + IPV6_SHIM_RESIDUAL_SIZE {
74 return None;
75 }
76
77 let format = shim_payload[0];
78 if format != IPV6_SHIM_FORMAT_COMPRESSED {
79 return None;
80 }
81
82 let residual = &shim_payload[1..1 + IPV6_SHIM_RESIDUAL_SIZE];
83 let upper_payload = &shim_payload[1 + IPV6_SHIM_RESIDUAL_SIZE..];
84 let upper_len = upper_payload.len();
85
86 let mut ipv6 = Vec::with_capacity(IPV6_HEADER_SIZE + upper_len);
87
88 ipv6.push((residual[0] & 0x0F) | 0x60);
90 ipv6.extend_from_slice(&residual[1..4]);
91
92 ipv6.extend_from_slice(&(upper_len as u16).to_be_bytes());
94
95 ipv6.push(residual[4]);
97
98 ipv6.push(residual[5]);
100
101 ipv6.extend_from_slice(&src_ipv6);
103
104 ipv6.extend_from_slice(&dst_ipv6);
106
107 ipv6.extend_from_slice(upper_payload);
109
110 Some(ipv6)
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 fn build_ipv6_packet(
119 traffic_class: u8,
120 flow_label: u32,
121 next_header: u8,
122 hop_limit: u8,
123 src: [u8; 16],
124 dst: [u8; 16],
125 payload: &[u8],
126 ) -> Vec<u8> {
127 let mut pkt = Vec::with_capacity(IPV6_HEADER_SIZE + payload.len());
128
129 pkt.push(0x60 | (traffic_class >> 4));
131 pkt.push((traffic_class << 4) | ((flow_label >> 16) as u8 & 0x0F));
133 pkt.push((flow_label >> 8) as u8);
135 pkt.push(flow_label as u8);
136
137 pkt.extend_from_slice(&(payload.len() as u16).to_be_bytes());
139
140 pkt.push(next_header);
142
143 pkt.push(hop_limit);
145
146 pkt.extend_from_slice(&src);
148
149 pkt.extend_from_slice(&dst);
151
152 pkt.extend_from_slice(payload);
154
155 pkt
156 }
157
158 fn sample_src() -> [u8; 16] {
159 [
160 0xfd, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
161 0x0e, 0x0f,
162 ]
163 }
164
165 fn sample_dst() -> [u8; 16] {
166 [
167 0xfd, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d,
168 0x1e, 0x1f,
169 ]
170 }
171
172 #[test]
175 fn test_compress_decompress_roundtrip() {
176 let payload = vec![0xAA; 100];
177 let pkt = build_ipv6_packet(0, 0, 17, 64, sample_src(), sample_dst(), &payload);
178
179 let compressed = compress_ipv6(&pkt).unwrap();
180 let decompressed = decompress_ipv6(&compressed, sample_src(), sample_dst()).unwrap();
181
182 assert_eq!(decompressed, pkt);
183 }
184
185 #[test]
186 fn test_roundtrip_empty_payload() {
187 let pkt = build_ipv6_packet(0, 0, 59, 1, sample_src(), sample_dst(), &[]);
188
189 let compressed = compress_ipv6(&pkt).unwrap();
190 assert_eq!(compressed.len(), 1 + IPV6_SHIM_RESIDUAL_SIZE); let decompressed = decompress_ipv6(&compressed, sample_src(), sample_dst()).unwrap();
193 assert_eq!(decompressed, pkt);
194 }
195
196 #[test]
197 fn test_roundtrip_large_payload() {
198 let payload = vec![0x55; 1400];
199 let pkt = build_ipv6_packet(0, 0, 6, 128, sample_src(), sample_dst(), &payload);
200
201 let compressed = compress_ipv6(&pkt).unwrap();
202 let decompressed = decompress_ipv6(&compressed, sample_src(), sample_dst()).unwrap();
203
204 assert_eq!(decompressed, pkt);
205 }
206
207 #[test]
210 fn test_preserves_traffic_class() {
211 let pkt = build_ipv6_packet(0xAB, 0, 17, 64, sample_src(), sample_dst(), &[1, 2, 3]);
213
214 let compressed = compress_ipv6(&pkt).unwrap();
215 let decompressed = decompress_ipv6(&compressed, sample_src(), sample_dst()).unwrap();
216
217 assert_eq!(decompressed, pkt);
218 let tc = ((decompressed[0] & 0x0F) << 4) | (decompressed[1] >> 4);
220 assert_eq!(tc, 0xAB);
221 }
222
223 #[test]
224 fn test_preserves_flow_label() {
225 let pkt = build_ipv6_packet(0, 0xFEDCB, 17, 64, sample_src(), sample_dst(), &[1]);
226
227 let compressed = compress_ipv6(&pkt).unwrap();
228 let decompressed = decompress_ipv6(&compressed, sample_src(), sample_dst()).unwrap();
229
230 assert_eq!(decompressed, pkt);
231 }
232
233 #[test]
234 fn test_preserves_tc_and_flow_label_combined() {
235 let pkt = build_ipv6_packet(0xFF, 0xFFFFF, 17, 64, sample_src(), sample_dst(), &[1]);
237
238 let compressed = compress_ipv6(&pkt).unwrap();
239 let decompressed = decompress_ipv6(&compressed, sample_src(), sample_dst()).unwrap();
240
241 assert_eq!(decompressed, pkt);
242 }
243
244 #[test]
245 fn test_preserves_next_header_tcp() {
246 let pkt = build_ipv6_packet(0, 0, 6, 64, sample_src(), sample_dst(), &[0; 20]);
247
248 let compressed = compress_ipv6(&pkt).unwrap();
249 let decompressed = decompress_ipv6(&compressed, sample_src(), sample_dst()).unwrap();
250
251 assert_eq!(decompressed[6], 6); }
253
254 #[test]
255 fn test_preserves_next_header_icmpv6() {
256 let pkt = build_ipv6_packet(0, 0, 58, 255, sample_src(), sample_dst(), &[0; 8]);
257
258 let compressed = compress_ipv6(&pkt).unwrap();
259 let decompressed = decompress_ipv6(&compressed, sample_src(), sample_dst()).unwrap();
260
261 assert_eq!(decompressed[6], 58); assert_eq!(decompressed[7], 255); }
264
265 #[test]
266 fn test_preserves_hop_limit() {
267 for hop_limit in [0, 1, 64, 128, 255] {
268 let pkt = build_ipv6_packet(0, 0, 17, hop_limit, sample_src(), sample_dst(), &[1]);
269
270 let compressed = compress_ipv6(&pkt).unwrap();
271 let decompressed = decompress_ipv6(&compressed, sample_src(), sample_dst()).unwrap();
272
273 assert_eq!(decompressed[7], hop_limit);
274 }
275 }
276
277 #[test]
280 fn test_payload_length_reconstructed() {
281 let payload = vec![0xBB; 256];
282 let pkt = build_ipv6_packet(0, 0, 17, 64, sample_src(), sample_dst(), &payload);
283
284 let compressed = compress_ipv6(&pkt).unwrap();
285 let decompressed = decompress_ipv6(&compressed, sample_src(), sample_dst()).unwrap();
286
287 let payload_len = u16::from_be_bytes([decompressed[4], decompressed[5]]);
288 assert_eq!(payload_len, 256);
289 }
290
291 #[test]
294 fn test_compression_saves_bytes() {
295 let payload = vec![0; 100];
296 let pkt = build_ipv6_packet(0, 0, 17, 64, sample_src(), sample_dst(), &payload);
297
298 let compressed = compress_ipv6(&pkt).unwrap();
299
300 assert_eq!(pkt.len(), 140);
304 assert_eq!(compressed.len(), 107);
305 assert_eq!(pkt.len() - compressed.len(), 33);
306 }
307
308 #[test]
311 fn test_compress_rejects_non_ipv6() {
312 let mut pkt = build_ipv6_packet(0, 0, 17, 64, sample_src(), sample_dst(), &[1]);
313 pkt[0] = 0x40; assert!(compress_ipv6(&pkt).is_none());
315 }
316
317 #[test]
318 fn test_compress_rejects_short_packet() {
319 assert!(compress_ipv6(&[0x60; 39]).is_none());
320 assert!(compress_ipv6(&[]).is_none());
321 }
322
323 #[test]
324 fn test_decompress_rejects_unknown_format() {
325 let mut compressed = vec![0x01]; compressed.extend_from_slice(&[0; IPV6_SHIM_RESIDUAL_SIZE]);
327 assert!(decompress_ipv6(&compressed, sample_src(), sample_dst()).is_none());
328 }
329
330 #[test]
331 fn test_decompress_rejects_short_payload() {
332 assert!(decompress_ipv6(&[0x00; 6], sample_src(), sample_dst()).is_none());
334 assert!(decompress_ipv6(&[], sample_src(), sample_dst()).is_none());
335 }
336
337 #[test]
340 fn test_addresses_from_context() {
341 let original_src = [
342 0xfd, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
343 0xAA, 0xAA,
344 ];
345 let original_dst = [
346 0xfd, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
347 0xBB, 0xBB,
348 ];
349 let pkt = build_ipv6_packet(0, 0, 17, 64, original_src, original_dst, &[1, 2]);
350
351 let compressed = compress_ipv6(&pkt).unwrap();
352
353 let context_src = sample_src();
355 let context_dst = sample_dst();
356 let decompressed = decompress_ipv6(&compressed, context_src, context_dst).unwrap();
357
358 assert_eq!(&decompressed[8..24], &context_src);
360 assert_eq!(&decompressed[24..40], &context_dst);
361
362 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..]); }
368}