1use std::str::FromStr;
2
3use crate::db::{Database, FingerprintCollection, Label, Type};
4use crate::error::DatabaseError;
5use crate::{
6 http::{Header as HttpHeader, Signature as HttpSignature, Version as HttpVersion},
7 tcp::{IpVersion, PayloadSize, Quirk, Signature as TcpSignature, TcpOption, Ttl, WindowSize},
8};
9use nom::branch::alt;
10use nom::bytes::complete::{take_until, take_while};
11use nom::character::complete::{alpha1, char, digit1};
12use nom::combinator::{map, map_res, opt};
13use nom::multi::{separated_list0, separated_list1};
14use nom::sequence::{pair, separated_pair, terminated};
15use nom::*;
16use nom::{
17 bytes::complete::tag,
18 character::complete::{alphanumeric1, space0},
19 combinator::rest,
20 sequence::preceded,
21 IResult,
22};
23use tracing::{trace, warn};
24
25impl FromStr for Database {
26 type Err = DatabaseError;
27
28 fn from_str(s: &str) -> Result<Self, Self::Err> {
29 let mut classes = vec![];
30 let mut mtu_entries = vec![];
31 let mut ua_os_entries = vec![];
32
33 let mut temp_tcp_request_entries: Vec<(Label, Vec<TcpSignature>)> = vec![];
34 let mut temp_tcp_response_entries: Vec<(Label, Vec<TcpSignature>)> = vec![];
35 let mut temp_http_request_entries: Vec<(Label, Vec<HttpSignature>)> = vec![];
36 let mut temp_http_response_entries: Vec<(Label, Vec<HttpSignature>)> = vec![];
37
38 let mut cur_mod = None;
39
40 for line in s.lines() {
41 let line = line.trim();
42
43 if line.is_empty() || line.starts_with(';') {
44 continue;
45 }
46
47 if line.starts_with("classes") {
48 classes.append(
49 &mut parse_classes(line)
50 .map_err(|err| {
51 DatabaseError::Parse(format!("fail to parse `classes`: {line}, {err}"))
52 })?
53 .1,
54 );
55 } else if line.starts_with("ua_os") {
56 ua_os_entries.append(
57 &mut parse_ua_os(line)
58 .map_err(|err| {
59 DatabaseError::Parse(format!("fail to parse `ua_os`: {line}, {err}"))
60 })?
61 .1,
62 );
63 } else if line.starts_with('[') && line.ends_with(']') {
64 cur_mod = Some(
65 parse_module(line)
66 .map_err(|err| {
67 DatabaseError::Parse(format!("fail to parse `module`: {line}, {err}"))
68 })?
69 .1,
70 );
71 } else if let Some((module, direction)) = cur_mod.as_ref() {
72 let (_, (name, value)) = parse_named_value(line).map_err(|err| {
73 DatabaseError::Parse(format!("fail to parse named value: {line}, {err}"))
74 })?;
75
76 match name {
77 "label" if module == "mtu" => {
78 mtu_entries.push((value.to_string(), vec![]));
79 }
80 "sig" if module == "mtu" => {
81 if let Some((_label, values)) = mtu_entries.last_mut() {
82 let sig = value.parse::<u16>().map_err(|err| {
83 DatabaseError::Parse(format!(
84 "fail to parse `mtu` value: {value}, {err}"
85 ))
86 })?;
87 values.push(sig);
88 } else {
89 return Err(DatabaseError::Parse(format!(
90 "`mtu` value without `label`: {value}"
91 )));
92 }
93 }
94 "label" => {
95 let (_, label) = parse_label(value).map_err(|err| {
96 DatabaseError::Parse(format!("fail to parse `label`: {value}, {err}"))
97 })?;
98
99 match (module.as_str(), direction.as_ref().map(|s| s.as_ref())) {
100 ("tcp", Some("request")) => {
101 temp_tcp_request_entries.push((label, vec![]))
102 }
103 ("tcp", Some("response")) => {
104 temp_tcp_response_entries.push((label, vec![]))
105 }
106 ("http", Some("request")) => {
107 temp_http_request_entries.push((label, vec![]))
108 }
109 ("http", Some("response")) => {
110 temp_http_response_entries.push((label, vec![]))
111 }
112 _ => {
113 warn!("skip `label` in unknown module `{}`: {}", module, value);
114 }
115 }
116 }
117 "sig" => match (module.as_str(), direction.as_ref().map(|s| s.as_ref())) {
118 ("tcp", Some("request")) => {
119 if let Some((label, values)) = temp_tcp_request_entries.last_mut() {
120 let sig = value.parse()?;
121 trace!("sig for `{}` tcp request: {}", label, sig);
122 values.push(sig);
123 } else {
124 return Err(DatabaseError::Parse(format!(
125 "tcp signature without `label`: {value}"
126 )));
127 }
128 }
129 ("tcp", Some("response")) => {
130 if let Some((label, values)) = temp_tcp_response_entries.last_mut() {
131 let sig = value.parse()?;
132 trace!("sig for `{}` tcp response: {}", label, sig);
133 values.push(sig);
134 } else {
135 return Err(DatabaseError::Parse(format!(
136 "tcp signature without `label`: {value}"
137 )));
138 }
139 }
140 ("http", Some("request")) => {
141 if let Some((label, values)) = temp_http_request_entries.last_mut() {
142 let sig = value.parse()?;
143 trace!("sig for `{}` http request: {}", label, sig);
144 values.push(sig);
145 } else {
146 return Err(DatabaseError::Parse(format!(
147 "http signature without `label`: {value}"
148 )));
149 }
150 }
151 ("http", Some("response")) => {
152 if let Some((label, values)) = temp_http_response_entries.last_mut() {
153 let sig = value.parse()?;
154 trace!("sig for `{}` http response: {}", label, sig);
155 values.push(sig);
156 } else {
157 return Err(DatabaseError::Parse(format!(
158 "http signature without `label`: {value}"
159 )));
160 }
161 }
162 _ => {
163 warn!("skip `sig` in unknown module `{}`: {}", module, value);
164 }
165 },
166 "sys" if module != "mtu" => {}
167 _ => {
168 warn!("skip unknown named value: {} = {}", name, value);
169 }
170 }
171 } else {
172 return Err(DatabaseError::Parse(format!(
173 "unexpected line outside the module: {line}"
174 )));
175 }
176 }
177
178 Ok(Database {
179 classes,
180 mtu: mtu_entries,
181 ua_os: ua_os_entries,
182 tcp_request: FingerprintCollection::new(temp_tcp_request_entries),
183 tcp_response: FingerprintCollection::new(temp_tcp_response_entries),
184 http_request: FingerprintCollection::new(temp_http_request_entries),
185 http_response: FingerprintCollection::new(temp_http_response_entries),
186 })
187 }
188}
189
190macro_rules! impl_from_str {
191 ($ty:ty, $parse:ident) => {
192 impl FromStr for $ty {
193 type Err = DatabaseError;
194
195 fn from_str(s: &str) -> Result<Self, Self::Err> {
196 let (remaining, res) = $parse(s).map_err(|err| {
197 DatabaseError::Parse(format!(
198 "parse {} failed: {}, {}",
199 stringify!($ty),
200 s,
201 err
202 ))
203 })?;
204
205 if !remaining.is_empty() {
206 Err(DatabaseError::Parse(format!(
207 "parse {} failed, remaining: {}",
208 stringify!($ty),
209 remaining
210 )))
211 } else {
212 Ok(res)
213 }
214 }
215 }
216 };
217}
218
219impl_from_str!(Label, parse_label);
220impl_from_str!(Type, parse_type);
221impl_from_str!(TcpSignature, parse_tcp_signature);
222impl_from_str!(IpVersion, parse_ip_version);
223impl_from_str!(Ttl, parse_ttl);
224impl_from_str!(WindowSize, parse_window_size);
225impl_from_str!(TcpOption, parse_tcp_option);
226impl_from_str!(Quirk, parse_quirk);
227impl_from_str!(PayloadSize, parse_payload_size);
228impl_from_str!(HttpSignature, parse_http_signature);
229impl_from_str!(HttpHeader, parse_http_header);
230
231fn parse_named_value(input: &str) -> IResult<&str, (&str, &str)> {
232 let (input, (name, _, _, _, value)) =
233 (alphanumeric1, space0, tag("="), space0, rest).parse(input)?;
234 Ok((input, (name, value)))
235}
236
237fn parse_classes(input: &str) -> IResult<&str, Vec<String>> {
238 let (input, (_, _, _, _, classes)) = (
239 tag("classes"),
240 space0,
241 tag("="),
242 space0,
243 separated_list0(tag(","), alphanumeric1),
244 )
245 .parse(input)?;
246
247 let class_vec = classes.into_iter().map(|s| s.to_string()).collect();
248 Ok((input, class_vec))
249}
250
251fn parse_module(input: &str) -> IResult<&str, (String, Option<String>)> {
252 let (input, (_, module, direction, _)) =
253 (tag("["), alpha1, opt(preceded(tag(":"), alpha1)), tag("]")).parse(input)?;
254 let module_str = module.to_string();
255 let direction_str = direction.map(|s| s.to_string());
256
257 Ok((input, (module_str, direction_str)))
258}
259
260fn parse_ua_os(input: &str) -> IResult<&str, Vec<(String, Option<String>)>> {
261 let (input, (_, _, _, _, values)) = (
262 tag("ua_os"),
263 space0,
264 tag("="),
265 space0,
266 separated_list0(tag(","), parse_key_value),
267 )
268 .parse(input)?;
269
270 let result = values
271 .into_iter()
272 .map(|(name, value)| (name.to_string(), value.map(|s| s.to_string())))
273 .collect();
274
275 Ok((input, result))
276}
277
278fn parse_key_value(input: &str) -> IResult<&str, (&str, Option<&str>)> {
279 let (input, (name, _, value)) = (
280 alphanumeric1,
281 space0,
282 opt(preceded((space0, tag("="), space0), alphanumeric1)),
283 )
284 .parse(input)?;
285
286 Ok((input, (name, value)))
287}
288
289fn parse_label(input: &str) -> IResult<&str, Label> {
290 let (input, (ty, _, class, _, name, flavor)) = (
291 parse_type,
292 tag(":"),
293 alt((
294 map(tag("!"), |_| None),
295 map(take_until(":"), |s: &str| Some(s.to_string())),
296 )),
297 tag(":"),
298 take_until(":"),
299 opt(preceded(tag(":"), rest)),
300 )
301 .parse(input)?;
302
303 Ok((
304 input,
305 Label {
306 ty,
307 class,
308 name: name.to_string(),
309 flavor: flavor.filter(|f| !f.is_empty()).map(String::from),
310 },
311 ))
312}
313
314fn parse_type(input: &str) -> IResult<&str, Type> {
315 alt((
316 tag("s").map(|_| Type::Specified),
317 tag("g").map(|_| Type::Generic),
318 ))
319 .parse(input)
320}
321
322fn parse_tcp_signature(input: &str) -> IResult<&str, TcpSignature> {
323 let (
324 input,
325 (version, _, ittl, _, olen, _, mss, _, wsize, _, wscale, _, olayout, _, quirks, _, pclass),
326 ) = (
327 parse_ip_version,
328 tag(":"),
329 parse_ttl,
330 tag(":"),
331 map_res(digit1, |s: &str| s.parse::<u8>()), tag(":"),
333 alt((
334 tag("*").map(|_| None),
335 map_res(digit1, |s: &str| s.parse::<u16>().map(Some)),
336 )), tag(":"),
338 parse_window_size,
339 tag(","),
340 alt((
341 tag("*").map(|_| None),
342 map_res(digit1, |s: &str| s.parse::<u8>().map(Some)),
343 )), tag(":"),
345 separated_list1(tag(","), parse_tcp_option),
346 tag(":"),
347 separated_list0(tag(","), parse_quirk),
348 tag(":"),
349 parse_payload_size,
350 )
351 .parse(input)?;
352
353 Ok((
354 input,
355 TcpSignature {
356 version,
357 ittl,
358 olen,
359 mss,
360 wsize,
361 wscale,
362 olayout,
363 quirks,
364 pclass,
365 },
366 ))
367}
368
369fn parse_ip_version(input: &str) -> IResult<&str, IpVersion> {
370 alt((
371 map(tag("4"), |_| IpVersion::V4),
372 map(tag("6"), |_| IpVersion::V6),
373 map(tag("*"), |_| IpVersion::Any),
374 ))
375 .parse(input)
376}
377
378fn parse_ttl(input: &str) -> IResult<&str, Ttl> {
379 alt((
380 map_res(terminated(digit1, tag("-")), |s: &str| {
381 s.parse::<u8>().map(Ttl::Bad)
382 }),
383 map_res(terminated(digit1, tag("+?")), |s: &str| {
384 s.parse::<u8>().map(Ttl::Guess)
385 }),
386 map_res(
387 separated_pair(digit1, tag("+"), digit1),
388 |(ttl_str, distance_str): (&str, &str)| match (
389 ttl_str.parse::<u8>(),
390 distance_str.parse::<u8>(),
391 ) {
392 (Ok(ttl), Ok(distance)) => Ok(Ttl::Distance(ttl, distance)),
393 (Err(_), _) => Err("Failed to parse ttl"),
394 (_, Err(_)) => Err("Failed to parse distance"),
395 },
396 ),
397 map_res(digit1, |s: &str| s.parse::<u8>().map(Ttl::Value)),
398 ))
399 .parse(input)
400}
401
402fn parse_window_size(input: &str) -> IResult<&str, WindowSize> {
403 alt((
404 map(tag("*"), |_| WindowSize::Any),
405 map_res(preceded(tag("mss*"), digit1), |s: &str| {
406 s.parse::<u8>().map(WindowSize::Mss)
407 }),
408 map_res(preceded(tag("mtu*"), digit1), |s: &str| {
409 s.parse::<u8>().map(WindowSize::Mtu)
410 }),
411 map_res(preceded(tag("%"), digit1), |s: &str| {
412 s.parse::<u16>().map(WindowSize::Mod)
413 }),
414 map_res(digit1, |s: &str| s.parse::<u16>().map(WindowSize::Value)),
415 ))
416 .parse(input)
417}
418
419fn parse_tcp_option(input: &str) -> IResult<&str, TcpOption> {
420 alt((
421 map_res(preceded(tag("eol+"), digit1), |s: &str| {
422 s.parse::<u8>().map(TcpOption::Eol)
423 }),
424 tag("nop").map(|_| TcpOption::Nop),
425 tag("mss").map(|_| TcpOption::Mss),
426 tag("ws").map(|_| TcpOption::Ws),
427 tag("sok").map(|_| TcpOption::Sok),
428 tag("sack").map(|_| TcpOption::Sack),
429 tag("ts").map(|_| TcpOption::TS),
430 preceded(
431 tag("?"),
432 map(digit1, |s: &str| s.parse::<u8>().unwrap_or(0)),
433 )
434 .map(TcpOption::Unknown),
435 ))
436 .parse(input)
437}
438
439fn parse_quirk(input: &str) -> IResult<&str, Quirk> {
440 alt((
441 map(tag("df"), |_| Quirk::Df),
442 map(tag("id+"), |_| Quirk::NonZeroID),
443 map(tag("id-"), |_| Quirk::ZeroID),
444 map(tag("ecn"), |_| Quirk::Ecn),
445 map(tag("0+"), |_| Quirk::MustBeZero),
446 map(tag("flow"), |_| Quirk::FlowID),
447 map(tag("seq-"), |_| Quirk::SeqNumZero),
448 map(tag("ack+"), |_| Quirk::AckNumNonZero),
449 map(tag("ack-"), |_| Quirk::AckNumZero),
450 map(tag("uptr+"), |_| Quirk::NonZeroURG),
451 map(tag("urgf+"), |_| Quirk::Urg),
452 map(tag("pushf+"), |_| Quirk::Push),
453 map(tag("ts1-"), |_| Quirk::OwnTimestampZero),
454 map(tag("ts2+"), |_| Quirk::PeerTimestampNonZero),
455 map(tag("opt+"), |_| Quirk::TrailinigNonZero),
456 map(tag("exws"), |_| Quirk::ExcessiveWindowScaling),
457 map(tag("bad"), |_| Quirk::OptBad),
458 ))
459 .parse(input)
460}
461
462fn parse_payload_size(input: &str) -> IResult<&str, PayloadSize> {
463 alt((
464 map(tag("0"), |_| PayloadSize::Zero),
465 map(tag("+"), |_| PayloadSize::NonZero),
466 map(tag("*"), |_| PayloadSize::Any),
467 ))
468 .parse(input)
469}
470
471fn parse_http_signature(input: &str) -> IResult<&str, HttpSignature> {
472 let (input, (version, _, horder, _, habsent, _, expsw)) = (
473 parse_http_version,
474 tag(":"),
475 separated_list1(tag(","), parse_http_header),
476 tag(":"),
477 opt(separated_list0(tag(","), parse_http_header)),
478 tag(":"),
479 rest,
480 )
481 .parse(input)?;
482
483 let habsent = habsent
484 .unwrap_or_default()
485 .into_iter()
486 .filter(|h| !h.name.is_empty())
487 .collect();
488
489 Ok((
490 input,
491 HttpSignature {
492 version,
493 horder,
494 habsent,
495 expsw: expsw.to_string(),
496 },
497 ))
498}
499
500fn parse_http_version(input: &str) -> IResult<&str, HttpVersion> {
501 alt((
502 map(tag("0"), |_| HttpVersion::V10),
503 map(tag("1"), |_| HttpVersion::V11),
504 map(tag("*"), |_| HttpVersion::Any),
505 ))
506 .parse(input)
507}
508
509fn parse_header_key_value(input: &str) -> IResult<&str, (&str, Option<&str>)> {
510 pair(
511 take_while(|c: char| (c.is_ascii_alphanumeric() || c == '-') && c != ':' && c != '='),
512 opt(preceded(tag("=["), terminated(take_until("]"), char(']')))),
513 )
514 .parse(input)
515}
516
517fn parse_http_header(input: &str) -> IResult<&str, HttpHeader> {
518 let (input, optional) = opt(char('?')).parse(input)?;
519 let (input, (name, value)) = parse_header_key_value(input)?;
520
521 Ok((
522 input,
523 HttpHeader {
524 optional: optional.is_some(),
525 name: name.to_string(),
526 value: value.map(|s| s.to_string()),
527 },
528 ))
529}
530
531#[cfg(test)]
532mod tests {
533 use lazy_static::lazy_static;
534
535 use super::*;
536 use crate::http::header;
537 use crate::tcp::{Quirk::*, TcpOption::*};
538
539 lazy_static! {
540 static ref LABELS: Vec<(&'static str, Label)> = vec![
541 (
542 "s:!:Uncle John's Networked ls Utility:2.3.0.1",
543 Label {
544 ty: Type::Specified,
545 class: None,
546 name: "Uncle John's Networked ls Utility".to_owned(),
547 flavor: Some("2.3.0.1".to_owned()),
548 },
549 ),
550 (
551 "s:unix:Linux:3.11 and newer",
552 Label {
553 ty: Type::Specified,
554 class: Some("unix".to_owned()),
555 name: "Linux".to_owned(),
556 flavor: Some("3.11 and newer".to_owned()),
557 },
558 ),
559 (
560 "s:!:Chrome:11.x to 26.x",
561 Label {
562 ty: Type::Specified,
563 class: None,
564 name: "Chrome".to_owned(),
565 flavor: Some("11.x to 26.x".to_owned()),
566 },
567 ),
568 (
569 "s:!:curl:",
570 Label {
571 ty: Type::Specified,
572 class: None,
573 name: "curl".to_owned(),
574 flavor: None,
575 },
576 )
577 ];
578 static ref TCP_SIGNATURES: Vec<(&'static str, TcpSignature)> = vec![
579 (
580 "*:64:0:*:mss*20,10:mss,sok,ts,nop,ws:df,id+:0",
581 TcpSignature {
582 version: IpVersion::Any,
583 ittl: Ttl::Value(64),
584 olen: 0,
585 mss: None,
586 wsize: WindowSize::Mss(20),
587 wscale: Some(10),
588 olayout: vec![Mss, Sok, TS, Nop, Ws],
589 quirks: vec![Df, NonZeroID],
590 pclass: PayloadSize::Zero,
591 }
592 ),
593 (
594 "*:64:0:*:16384,0:mss::0",
595 TcpSignature {
596 version: IpVersion::Any,
597 ittl: Ttl::Value(64),
598 olen: 0,
599 mss: None,
600 wsize: WindowSize::Value(16384),
601 wscale: Some(0),
602 olayout: vec![Mss],
603 quirks: vec![],
604 pclass: PayloadSize::Zero,
605 }
606 ),
607 (
608 "4:128:0:1460:mtu*2,0:mss,nop,ws::0",
609 TcpSignature {
610 version: IpVersion::V4,
611 ittl: Ttl::Value(128),
612 olen: 0,
613 mss: Some(1460),
614 wsize: WindowSize::Mtu(2),
615 wscale: Some(0),
616 olayout: vec![Mss, Nop, Ws],
617 quirks: vec![],
618 pclass: PayloadSize::Zero,
619 }
620 ),
621 (
622 "*:64-:0:265:%512,0:mss,sok,ts:ack+:0",
623 TcpSignature {
624 version: IpVersion::Any,
625 ittl: Ttl::Bad(64),
626 olen: 0,
627 mss: Some(265),
628 wsize: WindowSize::Mod(512),
629 wscale: Some(0),
630 olayout: vec![Mss, Sok, TS],
631 quirks: vec![AckNumNonZero],
632 pclass: PayloadSize::Zero,
633 }
634 ),
635 (
636 "*:64:0:*:mss*44,1:mss,sok,ts,nop,ws:df,id+:0",
637 TcpSignature {
638 version: IpVersion::Any,
639 ittl: Ttl::Value(64),
640 olen: 0,
641 mss: None,
642 wsize: WindowSize::Mss(44),
643 wscale: Some(1),
644 olayout: vec![Mss, Sok, TS, Nop, Ws],
645 quirks: vec![Df, NonZeroID],
646 pclass: PayloadSize::Zero,
647 }
648 ),
649 (
650 "*:64:0:*:*,*:mss,sok,ts,nop,ws:df,id+:0",
651 TcpSignature {
652 version: IpVersion::Any,
653 ittl: Ttl::Value(64),
654 olen: 0,
655 mss: None,
656 wsize: WindowSize::Any,
657 wscale: None,
658 olayout: vec![Mss, Sok, TS, Nop, Ws],
659 quirks: vec![Df, NonZeroID],
660 pclass: PayloadSize::Zero,
661 }
662
663 )
664 ];
665 static ref TTLS: Vec<(&'static str, Ttl)> = vec![
666 (
667 "64",
668 Ttl::Value(64)
669 ),
670 (
671 "54+10",
672 Ttl::Distance(54, 10)
673 ),
674 (
675 "64-",
676 Ttl::Bad(64)
677 ),
678 (
679 "54+?",
680 Ttl::Guess(54)
681 )
682 ];
683 static ref HTTP_SIGNATURES: Vec<(&'static str, HttpSignature)> = vec![
684 (
685 "*:Host,User-Agent,Accept=[,*/*;q=],?Accept-Language,Accept-Encoding=[gzip,deflate],Accept-Charset=[utf-8;q=0.7,*;q=0.7],Keep-Alive=[300],Connection=[keep-alive]::Firefox/",
686 HttpSignature {
687 version: HttpVersion::Any,
688 horder: vec![
689 header("Host"),
690 header("User-Agent"),
691 header("Accept").with_value(",*/*;q="),
692 header("Accept-Language").optional(),
693 header("Accept-Encoding").with_value("gzip,deflate"),
694 header("Accept-Charset").with_value("utf-8;q=0.7,*;q=0.7"),
695 header("Keep-Alive").with_value("300"),
696 header("Connection").with_value("keep-alive"),
697 ],
698 habsent: vec![],
699 expsw: "Firefox/".to_owned(),
700 }
701 )
702 ];
703 static ref HTTP_HEADERS: Vec<(&'static str, HttpHeader)> = vec![
704 ("Host", HttpHeader{ optional: false, name: "Host".to_owned(), value: None}),
705 ("User-Agent", HttpHeader{ optional: false, name: "User-Agent".to_owned(), value: None}),
706 ("Accept=[,*/*;q=]", HttpHeader{ optional: false, name: "Accept".to_owned(), value: Some(",*/*;q=".to_owned())}),
707 ("?Accept-Language", HttpHeader{ optional: true, name: "Accept-Language".to_owned(), value: None}),
708 ];
709 }
710
711 #[test]
712 fn test_label() {
713 for (s, l) in LABELS.iter() {
714 let result = s.parse::<Label>();
715 assert!(result.is_ok(), "Failed to parse label: {s}");
716 if let Ok(ref parsed) = result {
717 assert_eq!(parsed, l);
718 }
719 }
720 }
721
722 #[test]
723 fn test_tcp_signature() {
724 for (s, sig) in TCP_SIGNATURES.iter() {
725 let result = s.parse::<TcpSignature>();
726 assert!(result.is_ok(), "Failed to parse TCP signature: {s}");
727 if let Ok(ref parsed) = result {
728 assert_eq!(parsed, sig);
729 }
730 assert_eq!(&sig.to_string(), s);
731 }
732 }
733
734 #[test]
735 fn test_ttl() {
736 for (s, ttl) in TTLS.iter() {
737 let result = s.parse::<Ttl>();
738 assert!(result.is_ok(), "Failed to parse TTL: {s}");
739 if let Ok(ref parsed) = result {
740 assert_eq!(parsed, ttl);
741 }
742 assert_eq!(&ttl.to_string(), s);
743 }
744 }
745
746 #[test]
747 fn test_http_signature() {
748 for (s, sig) in HTTP_SIGNATURES.iter() {
749 let result = s.parse::<HttpSignature>();
750 assert!(result.is_ok(), "Failed to parse HTTP signature: {s}");
751 if let Ok(ref parsed) = result {
752 assert_eq!(parsed, sig);
753 }
754 assert_eq!(&sig.to_string(), s);
755 }
756 }
757
758 #[test]
759 fn test_http_header() {
760 for (s, h) in HTTP_HEADERS.iter() {
761 let result = s.parse::<HttpHeader>();
762 assert!(result.is_ok(), "Failed to parse HTTP header: {s}");
763 if let Ok(ref parsed) = result {
764 assert_eq!(parsed, h);
765 }
766 assert_eq!(&h.to_string(), s);
767 }
768 }
769}