1use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
8
9use compact_str::CompactString;
10
11#[derive(Debug, Clone)]
19pub enum FieldValue<'data> {
20 UInt8(u8),
23 UInt16(u16),
25 UInt32(u32),
27 UInt64(u64),
29 Int64(i64),
31 Bool(bool),
33
34 IpAddr(IpAddr),
37 MacAddr([u8; 6]),
39
40 Str(&'data str),
44 Bytes(&'data [u8]),
47
48 OwnedString(CompactString),
52 OwnedBytes(Vec<u8>),
54
55 List(Vec<FieldValue<'data>>),
59
60 Null,
62}
63
64pub type OwnedFieldValue = FieldValue<'static>;
67
68impl<'data> FieldValue<'data> {
69 pub fn mac(bytes: &[u8]) -> Self {
71 if bytes.len() >= 6 {
72 let mut mac = [0u8; 6];
73 mac.copy_from_slice(&bytes[..6]);
74 FieldValue::MacAddr(mac)
75 } else {
76 FieldValue::Null
77 }
78 }
79
80 pub fn ipv4(bytes: &[u8]) -> Self {
82 if bytes.len() >= 4 {
83 FieldValue::IpAddr(IpAddr::V4(Ipv4Addr::new(
84 bytes[0], bytes[1], bytes[2], bytes[3],
85 )))
86 } else {
87 FieldValue::Null
88 }
89 }
90
91 pub fn ipv6(bytes: &[u8]) -> Self {
93 if bytes.len() >= 16 {
94 let mut arr = [0u8; 16];
95 arr.copy_from_slice(&bytes[..16]);
96 FieldValue::IpAddr(IpAddr::V6(Ipv6Addr::from(arr)))
97 } else {
98 FieldValue::Null
99 }
100 }
101
102 pub fn format_mac(mac: &[u8; 6]) -> String {
104 format!(
105 "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
106 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]
107 )
108 }
109
110 pub fn is_null(&self) -> bool {
112 matches!(self, FieldValue::Null)
113 }
114
115 pub fn as_u64(&self) -> Option<u64> {
117 match self {
118 FieldValue::UInt8(v) => Some(*v as u64),
119 FieldValue::UInt16(v) => Some(*v as u64),
120 FieldValue::UInt32(v) => Some(*v as u64),
121 FieldValue::UInt64(v) => Some(*v),
122 _ => None,
123 }
124 }
125
126 pub fn as_i64(&self) -> Option<i64> {
128 match self {
129 FieldValue::Int64(v) => Some(*v),
130 FieldValue::UInt8(v) => Some(*v as i64),
131 FieldValue::UInt16(v) => Some(*v as i64),
132 FieldValue::UInt32(v) => Some(*v as i64),
133 _ => None,
134 }
135 }
136
137 pub fn as_u16(&self) -> Option<u16> {
139 match self {
140 FieldValue::UInt16(v) => Some(*v),
141 FieldValue::UInt8(v) => Some(*v as u16),
142 _ => None,
143 }
144 }
145
146 pub fn as_str(&self) -> Option<&str> {
148 match self {
149 FieldValue::Str(s) => Some(s),
150 FieldValue::OwnedString(s) => Some(s.as_str()),
151 _ => None,
152 }
153 }
154
155 pub fn as_bytes(&self) -> Option<&[u8]> {
157 match self {
158 FieldValue::Bytes(b) => Some(b),
159 FieldValue::OwnedBytes(b) => Some(b.as_slice()),
160 _ => None,
161 }
162 }
163
164 pub fn as_string(&self) -> Option<String> {
166 match self {
167 FieldValue::Str(s) => Some(s.to_string()),
168 FieldValue::OwnedString(s) => Some(s.to_string()),
169 FieldValue::IpAddr(addr) => Some(addr.to_string()),
170 FieldValue::MacAddr(mac) => Some(Self::format_mac(mac)),
171 FieldValue::UInt8(v) => Some(v.to_string()),
172 FieldValue::UInt16(v) => Some(v.to_string()),
173 FieldValue::UInt32(v) => Some(v.to_string()),
174 FieldValue::UInt64(v) => Some(v.to_string()),
175 FieldValue::Int64(v) => Some(v.to_string()),
176 FieldValue::Bool(v) => Some(v.to_string()),
177 _ => None,
178 }
179 }
180
181 pub fn as_list(&self) -> Option<&[FieldValue<'data>]> {
183 match self {
184 FieldValue::List(items) => Some(items.as_slice()),
185 _ => None,
186 }
187 }
188
189 pub fn list_len(&self) -> Option<usize> {
191 match self {
192 FieldValue::List(items) => Some(items.len()),
193 _ => None,
194 }
195 }
196
197 pub fn to_owned(&self) -> FieldValue<'static> {
200 match self {
201 FieldValue::UInt8(v) => FieldValue::UInt8(*v),
202 FieldValue::UInt16(v) => FieldValue::UInt16(*v),
203 FieldValue::UInt32(v) => FieldValue::UInt32(*v),
204 FieldValue::UInt64(v) => FieldValue::UInt64(*v),
205 FieldValue::Int64(v) => FieldValue::Int64(*v),
206 FieldValue::Bool(v) => FieldValue::Bool(*v),
207 FieldValue::IpAddr(v) => FieldValue::IpAddr(*v),
208 FieldValue::MacAddr(v) => FieldValue::MacAddr(*v),
209 FieldValue::Str(s) => FieldValue::OwnedString(CompactString::new(s)),
210 FieldValue::Bytes(b) => FieldValue::OwnedBytes(b.to_vec()),
211 FieldValue::OwnedString(s) => FieldValue::OwnedString(s.clone()),
212 FieldValue::OwnedBytes(b) => FieldValue::OwnedBytes(b.clone()),
213 FieldValue::List(items) => {
214 FieldValue::List(items.iter().map(|v| v.to_owned()).collect())
215 }
216 FieldValue::Null => FieldValue::Null,
217 }
218 }
219}
220
221impl<'data> std::fmt::Display for FieldValue<'data> {
222 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
223 match self {
224 FieldValue::UInt8(v) => write!(f, "{v}"),
225 FieldValue::UInt16(v) => write!(f, "{v}"),
226 FieldValue::UInt32(v) => write!(f, "{v}"),
227 FieldValue::UInt64(v) => write!(f, "{v}"),
228 FieldValue::Int64(v) => write!(f, "{v}"),
229 FieldValue::Bool(v) => write!(f, "{v}"),
230 FieldValue::Str(s) => write!(f, "{s}"),
231 FieldValue::OwnedString(s) => write!(f, "{s}"),
232 FieldValue::Bytes(b) => write!(f, "[{} bytes]", b.len()),
233 FieldValue::OwnedBytes(b) => write!(f, "[{} bytes]", b.len()),
234 FieldValue::IpAddr(addr) => write!(f, "{addr}"),
235 FieldValue::MacAddr(mac) => write!(f, "{}", Self::format_mac(mac)),
236 FieldValue::List(items) => {
237 write!(f, "[")?;
238 for (i, item) in items.iter().enumerate() {
239 if i > 0 {
240 write!(f, ", ")?;
241 }
242 write!(f, "{item}")?;
243 }
244 write!(f, "]")
245 }
246 FieldValue::Null => write!(f, "NULL"),
247 }
248 }
249}
250
251impl<'a, 'b> PartialEq<FieldValue<'b>> for FieldValue<'a> {
253 fn eq(&self, other: &FieldValue<'b>) -> bool {
254 match (self, other) {
255 (FieldValue::UInt8(a), FieldValue::UInt8(b)) => a == b,
256 (FieldValue::UInt16(a), FieldValue::UInt16(b)) => a == b,
257 (FieldValue::UInt32(a), FieldValue::UInt32(b)) => a == b,
258 (FieldValue::UInt64(a), FieldValue::UInt64(b)) => a == b,
259 (FieldValue::Int64(a), FieldValue::Int64(b)) => a == b,
260 (FieldValue::Bool(a), FieldValue::Bool(b)) => a == b,
261 (FieldValue::IpAddr(a), FieldValue::IpAddr(b)) => a == b,
262 (FieldValue::MacAddr(a), FieldValue::MacAddr(b)) => a == b,
263 (FieldValue::Str(a), FieldValue::Str(b)) => a == b,
265 (FieldValue::Str(a), FieldValue::OwnedString(b)) => *a == b.as_str(),
266 (FieldValue::OwnedString(a), FieldValue::Str(b)) => a.as_str() == *b,
267 (FieldValue::OwnedString(a), FieldValue::OwnedString(b)) => a == b,
268 (FieldValue::Bytes(a), FieldValue::Bytes(b)) => a == b,
270 (FieldValue::Bytes(a), FieldValue::OwnedBytes(b)) => *a == b.as_slice(),
271 (FieldValue::OwnedBytes(a), FieldValue::Bytes(b)) => a.as_slice() == *b,
272 (FieldValue::OwnedBytes(a), FieldValue::OwnedBytes(b)) => a == b,
273 (FieldValue::List(a), FieldValue::List(b)) => {
275 a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| x == y)
276 }
277 (FieldValue::Null, FieldValue::Null) => true,
278 _ => false,
279 }
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286
287 #[test]
288 fn test_zero_copy_str() {
289 let packet = b"GET /index.html HTTP/1.1\r\n";
290 let value = FieldValue::Str(std::str::from_utf8(&packet[4..15]).unwrap());
291
292 match value {
293 FieldValue::Str(s) => {
294 assert_eq!(s, "/index.html");
295 assert!(std::ptr::eq(s.as_ptr(), packet[4..].as_ptr()));
297 }
298 _ => panic!("Expected Str variant"),
299 }
300 }
301
302 #[test]
303 fn test_zero_copy_bytes() {
304 let packet = vec![0x45, 0x00, 0x00, 0x28, 0xde, 0xad, 0xbe, 0xef];
305 let payload = &packet[4..];
306 let value = FieldValue::Bytes(payload);
307
308 match value {
309 FieldValue::Bytes(b) => {
310 assert_eq!(b, &[0xde, 0xad, 0xbe, 0xef]);
311 assert!(std::ptr::eq(b.as_ptr(), packet[4..].as_ptr()));
312 }
313 _ => panic!("Expected Bytes variant"),
314 }
315 }
316
317 #[test]
318 fn test_owned_string() {
319 let domain = CompactString::new("www.example.com");
321 let value = FieldValue::OwnedString(domain);
322
323 match value {
324 FieldValue::OwnedString(s) => assert_eq!(s.as_str(), "www.example.com"),
325 _ => panic!("Expected OwnedString variant"),
326 }
327 }
328
329 #[test]
330 fn test_compact_string_inline() {
331 let small = CompactString::new("example.com"); assert!(!small.is_heap_allocated());
334
335 let large = CompactString::new("this-is-a-very-long-domain-name.example.com");
336 assert!(large.is_heap_allocated());
337 }
338
339 #[test]
340 fn test_str_owned_string_equality() {
341 let borrowed = FieldValue::Str("hello");
342 let owned = FieldValue::OwnedString(CompactString::new("hello"));
343
344 assert_eq!(borrowed, owned);
345 assert_eq!(owned, borrowed);
346 }
347
348 #[test]
349 fn test_bytes_owned_bytes_equality() {
350 let data = &[1u8, 2, 3, 4][..];
351 let borrowed = FieldValue::Bytes(data);
352 let owned = FieldValue::OwnedBytes(vec![1, 2, 3, 4]);
353
354 assert_eq!(borrowed, owned);
355 assert_eq!(owned, borrowed);
356 }
357
358 #[test]
359 fn test_to_owned() {
360 let packet = b"example.com";
361 let borrowed = FieldValue::Str(std::str::from_utf8(packet).unwrap());
362 let owned = borrowed.to_owned();
363
364 assert_eq!(borrowed, owned);
366
367 match owned {
369 FieldValue::OwnedString(s) => assert_eq!(s.as_str(), "example.com"),
370 _ => panic!("Expected OwnedString variant"),
371 }
372 }
373
374 #[test]
375 fn test_as_str() {
376 let str_val = FieldValue::Str("hello");
377 let owned_val = FieldValue::OwnedString(CompactString::new("world"));
378 let int_val = FieldValue::UInt32(42);
379
380 assert_eq!(str_val.as_str(), Some("hello"));
381 assert_eq!(owned_val.as_str(), Some("world"));
382 assert_eq!(int_val.as_str(), None);
383 }
384
385 #[test]
386 fn test_as_bytes() {
387 let bytes_val = FieldValue::Bytes(&[1, 2, 3]);
388 let owned_val = FieldValue::OwnedBytes(vec![4, 5, 6]);
389 let int_val = FieldValue::UInt32(42);
390
391 assert_eq!(bytes_val.as_bytes(), Some(&[1u8, 2, 3][..]));
392 assert_eq!(owned_val.as_bytes(), Some(&[4u8, 5, 6][..]));
393 assert_eq!(int_val.as_bytes(), None);
394 }
395
396 #[test]
397 fn test_list_basic() {
398 let list = FieldValue::List(vec![
399 FieldValue::UInt32(1),
400 FieldValue::UInt32(2),
401 FieldValue::UInt32(3),
402 ]);
403
404 assert_eq!(list.list_len(), Some(3));
405 assert!(list.as_list().is_some());
406
407 let items = list.as_list().unwrap();
408 assert_eq!(items[0], FieldValue::UInt32(1));
409 assert_eq!(items[1], FieldValue::UInt32(2));
410 assert_eq!(items[2], FieldValue::UInt32(3));
411 }
412
413 #[test]
414 fn test_list_display() {
415 let list = FieldValue::List(vec![FieldValue::UInt32(10), FieldValue::UInt32(20)]);
416 assert_eq!(format!("{}", list), "[10, 20]");
417
418 let empty: FieldValue = FieldValue::List(vec![]);
419 assert_eq!(format!("{}", empty), "[]");
420
421 let string_list = FieldValue::List(vec![
422 FieldValue::OwnedString(CompactString::new("hello")),
423 FieldValue::OwnedString(CompactString::new("world")),
424 ]);
425 assert_eq!(format!("{}", string_list), "[hello, world]");
426 }
427
428 #[test]
429 fn test_list_equality() {
430 let list1 = FieldValue::List(vec![FieldValue::UInt32(1), FieldValue::UInt32(2)]);
431 let list2 = FieldValue::List(vec![FieldValue::UInt32(1), FieldValue::UInt32(2)]);
432 let list3 = FieldValue::List(vec![FieldValue::UInt32(1), FieldValue::UInt32(3)]);
433
434 assert_eq!(list1, list2);
435 assert_ne!(list1, list3);
436 }
437
438 #[test]
439 fn test_list_to_owned() {
440 let packet = b"test";
441 let list_with_borrowed: FieldValue = FieldValue::List(vec![
442 FieldValue::Str(std::str::from_utf8(packet).unwrap()),
443 FieldValue::UInt32(42),
444 ]);
445
446 let owned = list_with_borrowed.to_owned();
447
448 assert_eq!(list_with_borrowed, owned);
450
451 match &owned {
453 FieldValue::List(items) => {
454 assert!(matches!(&items[0], FieldValue::OwnedString(_)));
455 assert!(matches!(&items[1], FieldValue::UInt32(42)));
456 }
457 _ => panic!("Expected List variant"),
458 }
459 }
460}