huginn_net_db/
db_parse.rs

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>()), // olen
332        tag(":"),
333        alt((
334            tag("*").map(|_| None),
335            map_res(digit1, |s: &str| s.parse::<u16>().map(Some)),
336        )), // mss
337        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        )), // wscale
344        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}