traffic-taffy 0.5.8__py3-none-any.whl → 0.6.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,25 +1,40 @@
1
+ """Base module for output classes."""
2
+
3
+ from __future__ import annotations
4
+ from typing import TYPE_CHECKING
5
+
6
+ if TYPE_CHECKING:
7
+ from traffic_taffy.comparison import Comparison
8
+
9
+
1
10
  class Output:
2
- def __init__(self, report, options={}):
11
+ """Base class for outputting reports."""
12
+
13
+ def __init__(self, report: Comparison, options: dict | None = None):
14
+ """Initialize the base."""
3
15
  self.report = report
4
- self.output_options = options
16
+ self.output_options = options or {}
5
17
 
6
18
  @property
7
- def report(self):
19
+ def report(self) -> Comparison:
20
+ """The report itself."""
8
21
  return self._report
9
22
 
10
23
  @report.setter
11
- def report(self, new_report):
24
+ def report(self, new_report: Comparison) -> None:
12
25
  self._report = new_report
13
26
 
14
27
  @property
15
- def output_options(self):
28
+ def output_options(self) -> dict:
29
+ """A list of output options."""
16
30
  return self._output_options
17
31
 
18
32
  @output_options.setter
19
- def output_options(self, new_output_options):
33
+ def output_options(self, new_output_options: dict) -> None:
20
34
  self._output_options = new_output_options
21
35
 
22
- def output(self, report=None):
36
+ def output(self, report: Comparison | None = None) -> None:
37
+ """Dump a report to the output stream."""
23
38
  if not report:
24
39
  report = self.report
25
40
  contents = report.contents
@@ -55,12 +70,12 @@ class Output:
55
70
  ):
56
71
  continue
57
72
 
58
- # TODO: we don't do match_value here?
73
+ # TODO(hardaker): we don't do match_value here?
59
74
 
60
75
  record_count = 0
61
76
  for subkey, data in sorted(
62
77
  contents[key].items(),
63
- key=lambda x: x[1][sort_by],
78
+ key=lambda x: getattr(x[1], sort_by),
64
79
  reverse=sort_order,
65
80
  ):
66
81
  if not self.filter_check(data):
@@ -84,16 +99,18 @@ class Output:
84
99
 
85
100
  self.output_close()
86
101
 
87
- def output_new_section(self, key):
102
+ def output_new_section(self, key: str) -> None:
103
+ """Create a new section header."""
88
104
  return
89
105
 
90
- def output_close(self):
106
+ def output_close(self) -> None:
107
+ """Close the output stream."""
91
108
  return
92
109
 
93
110
  def filter_check(self, data: dict) -> bool:
94
- "Return true if we should include it."
95
- delta: float = data["delta_percentage"]
96
- total: int = data["total"]
111
+ """Return true if we should include it."""
112
+ delta: float = data.delta_percentage
113
+ total: int = data.total
97
114
 
98
115
  if self.output_options["only_positive"] and delta <= 0:
99
116
  return False
@@ -122,12 +139,11 @@ class Output:
122
139
  # just check output_options["minimum_count"]
123
140
  if total > self.output_options["minimum_count"]:
124
141
  return True
125
- else:
142
+ elif (
143
+ total > self.output_options["minimum_count"]
144
+ and abs(delta) > self.output_options["print_threshold"]
145
+ ):
126
146
  # require both
127
- if (
128
- total > self.output_options["minimum_count"]
129
- and abs(delta) > self.output_options["print_threshold"]
130
- ):
131
- return True
147
+ return True
132
148
 
133
149
  return False
@@ -1,21 +1,36 @@
1
+ """A module to output comparison results to the console."""
2
+
3
+ from __future__ import annotations
4
+ from typing import Dict, Any, TYPE_CHECKING
5
+ from rich.console import Console as RichConsole
6
+
1
7
  from traffic_taffy.output import Output
2
8
  from traffic_taffy.dissection import Dissection
3
- from rich.console import Console as RichConsole
9
+
10
+ if TYPE_CHECKING:
11
+ from traffic_taffy.comparison import Comparison
4
12
 
5
13
 
6
14
  class Console(Output):
7
- def __init__(self, *args, **kwargs):
15
+ """An output class for reporting to a console."""
16
+
17
+ BOLD_LIMIT = 0.5
18
+ POSITIVE = 0.0
19
+
20
+ def __init__(self, *args: list, **kwargs: Dict[str, Any]):
21
+ """Create a console reporting object."""
8
22
  super().__init__(*args, **kwargs)
9
23
  self.console = None
10
24
  self.have_done_header = False
11
25
 
12
26
  # actual routines to print stuff
13
- def init_console(self):
27
+ def init_console(self) -> None:
28
+ """Initialize the rich console object."""
14
29
  if not self.console:
15
30
  self.console = RichConsole()
16
31
 
17
- def output_start(self, report):
18
- "Prints the header about columns being displayed"
32
+ def output_start(self, report: Comparison) -> None:
33
+ """Print the header about columns being displayed."""
19
34
  # This should match the spacing in print_contents()
20
35
  self.init_console()
21
36
 
@@ -42,31 +57,31 @@ class Console(Output):
42
57
 
43
58
  self.console.print(line)
44
59
 
45
- def output_new_section(self, key):
46
- print(f"----- {key}")
47
-
48
- def output_record(self, key, subkey, data) -> None:
49
- "prints a report to the console"
60
+ def output_new_section(self, key: str) -> None:
61
+ """Print a new section border."""
62
+ self.console.print(f"----- {key}")
50
63
 
51
- delta_percentage: float = data["delta_percentage"]
64
+ def output_record(self, key: str, subkey: Any, data: Dict[str, Any]) -> None:
65
+ """Print a report to the console."""
66
+ delta_percentage: float = data.delta_percentage
52
67
 
53
68
  # apply some fancy styling
54
69
  style = ""
55
- if delta_percentage < -0.5:
70
+ if delta_percentage < -Console.BOLD_LIMIT:
56
71
  style = "[bold red]"
57
- elif delta_percentage < 0.0:
72
+ elif delta_percentage < Console.POSITIVE:
58
73
  style = "[red]"
59
- elif delta_percentage > 0.5:
74
+ elif delta_percentage > Console.BOLD_LIMIT:
60
75
  style = "[bold green]"
61
- elif delta_percentage > 0.0:
76
+ elif delta_percentage > Console.POSITIVE:
62
77
  style = "[green]"
63
78
  endstyle = style.replace("[", "[/")
64
79
 
65
80
  # construct the output line with styling
66
81
  subkey = Dissection.make_printable(key, subkey)
67
82
  line = f" {style}{subkey:<50}{endstyle}"
68
- line += f" {data['left_count']:>8} {data['right_count']:>8} {data['delta_absolute']:>8}"
69
- line += f" {100*data['left_percentage']:>7.2f} {100*data['right_percentage']:>7.2f} {100*delta_percentage:>7.2f}"
83
+ line += f" {data.left_count:>8} {data.right_count:>8} {data.delta_absolute:>8}"
84
+ line += f" {100*data.left_percentage:>7.2f} {100*data.right_percentage:>7.2f} {100*delta_percentage:>7.2f}"
70
85
 
71
86
  # print it to the rich console
72
87
  self.console.print(line)
@@ -1,12 +1,18 @@
1
+ """A module to output comparison results to an FSDB output."""
1
2
  import sys
2
3
  import pyfsdb
4
+ from typing import Any
3
5
 
4
6
  from traffic_taffy.output import Output
5
7
  from traffic_taffy.dissection import Dissection
8
+ from traffic_taffy.comparison import Comparison
6
9
 
7
10
 
8
11
  class Fsdb(Output):
9
- def __init__(self, *args, **kwargs):
12
+ """An FSDB report generator."""
13
+
14
+ def __init__(self, *args: list, **kwargs: dict):
15
+ """Create an FSDB report generator."""
10
16
  super().__init__(*args, **kwargs)
11
17
  self.console = None
12
18
  self.have_done_header = False
@@ -26,25 +32,24 @@ class Fsdb(Output):
26
32
  ]
27
33
  self.fsdb.converters = [str, str, str, int, int, int, float, float, float]
28
34
 
29
- def output_start(self, report):
30
- "Prints the header about columns being displayed"
35
+ def output_start(self, report: Comparison) -> None:
36
+ """Print the header about columns being displayed."""
31
37
  # This should match the spacing in print_contents()
32
38
  self.in_report = report.title
33
39
 
34
- def output_record(self, key, subkey, data) -> None:
35
- "prints a report to the console"
36
-
40
+ def output_record(self, key: str, subkey: Any, data: dict) -> None:
41
+ """Print a report to the console."""
37
42
  subkey = Dissection.make_printable(key, subkey)
38
43
  self.fsdb.append(
39
44
  [
40
45
  self.in_report,
41
46
  key,
42
47
  subkey,
43
- data["left_count"],
44
- data["right_count"],
45
- data["delta_absolute"],
46
- data["left_percentage"],
47
- data["right_percentage"],
48
- data["delta_percentage"],
48
+ data.left_count,
49
+ data.right_count,
50
+ data.delta_absolute,
51
+ data.left_percentage,
52
+ data.right_percentage,
53
+ data.delta_percentage,
49
54
  ]
50
55
  )
@@ -1,11 +1,17 @@
1
+ """Store contents of a report in memory."""
1
2
  from collections import defaultdict
3
+ from typing import Any
2
4
 
3
5
  from traffic_taffy.output import Output
4
6
  from traffic_taffy.dissection import Dissection
7
+ from traffic_taffy.comparison import Comparison
5
8
 
6
9
 
7
10
  class Memory(Output):
8
- def __init__(self, *args, **kwargs):
11
+ """A class for storing report contents in memory."""
12
+
13
+ def __init__(self, *args: list, **kwargs: dict):
14
+ """Create a Memory object."""
9
15
  super().__init__(*args, **kwargs)
10
16
  self.console = None
11
17
  self.have_done_header = False
@@ -13,39 +19,40 @@ class Memory(Output):
13
19
  self.memory = None
14
20
 
15
21
  @property
16
- def title(self):
22
+ def title(self) -> str:
23
+ """The title of the report."""
17
24
  return self._title
18
25
 
19
26
  @title.setter
20
- def title(self, new_title):
27
+ def title(self, new_title: str) -> None:
21
28
  self._title = new_title
22
29
 
23
30
  @property
24
- def memory(self):
31
+ def memory(self) -> dict:
32
+ """The data for the report."""
25
33
  return self._memory
26
34
 
27
35
  @memory.setter
28
- def memory(self, new_memory):
36
+ def memory(self, new_memory: dict) -> None:
29
37
  self._memory = new_memory
30
38
 
31
- def output_start(self, report):
32
- "Prints the header about columns being displayed"
39
+ def output_start(self, report: Comparison) -> None:
40
+ """Print the header about columns being displayed."""
33
41
  # This should match the spacing in print_contents()
34
42
  self.title = report.title
35
43
  self.memory = defaultdict(list)
36
44
 
37
- def output_record(self, key, subkey, data) -> None:
38
- "prints a report to the console"
39
-
45
+ def output_record(self, key: str, subkey: Any, data: dict) -> None:
46
+ """Print a report to the console."""
40
47
  subkey = Dissection.make_printable(key, subkey)
41
48
  self.memory[key].append(
42
49
  {
43
50
  "subkey": subkey,
44
- "left_count": data["left_count"],
45
- "right_count": data["right_count"],
46
- "delta_absolute": data["delta_absolute"],
47
- "left_percentage": data["left_percentage"],
48
- "right_percentage": data["right_percentage"],
49
- "delta_percentage": data["delta_percentage"],
51
+ "left_count": data.left_count,
52
+ "right_count": data.right_count,
53
+ "delta_absolute": data.delta_absolute,
54
+ "left_percentage": data.left_percentage,
55
+ "right_percentage": data.right_percentage,
56
+ "delta_percentage": data.delta_percentage,
50
57
  }
51
58
  )
@@ -1,5 +1,5 @@
1
1
  from collections import Counter
2
- from traffic_taffy.compare import PcapCompare
2
+ from traffic_taffy.compare import PcapCompare, Report
3
3
 
4
4
 
5
5
  def test_compare_results():
@@ -9,42 +9,42 @@ def test_compare_results():
9
9
  # this should be positive when right_data is larger
10
10
  expected = {
11
11
  "src": {
12
- "a": {
13
- "total": 20,
14
- "left_count": 5,
15
- "right_count": 15,
16
- "delta_absolute": 15 - 5,
17
- "left_percentage": 5.0 / 15.0,
18
- "right_percentage": 15.0 / 30.0,
19
- "delta_percentage": 15.0 / 30.0 - 5.0 / 15.0,
20
- },
21
- "b": {
22
- "total": -10, # only in 1
23
- "left_count": 10,
24
- "right_count": 0,
25
- "delta_absolute": 0 - 10,
26
- "left_percentage": 10.0 / 15.0,
27
- "right_percentage": 0.0,
28
- "delta_percentage": -1.0,
29
- },
30
- "c": {
31
- "total": 15, # only in 2
32
- "left_count": 0,
33
- "right_count": 15,
34
- "delta_absolute": 15 - 0,
35
- "left_percentage": 0.0,
36
- "right_percentage": 15.0 / 30.0,
37
- "delta_percentage": 1.0,
38
- },
39
- "__NEW_VALUES__": {
40
- "total": 2, # 1 on each side
41
- "left_count": 1, # b
42
- "right_count": 1, # c
43
- "delta_absolute": 1 - 1,
44
- "left_percentage": 1.0 / 15.0, # TODO: nuke this
45
- "right_percentage": 1.0 / 30.0, # TODO: nuke this
46
- "delta_percentage": 1.0 / 30.0 - 1.0 / 15.0,
47
- },
12
+ "a": Report(
13
+ total=20,
14
+ left_count=5,
15
+ right_count=15,
16
+ delta_absolute=15 - 5,
17
+ left_percentage=5.0 / 15.0,
18
+ right_percentage=15.0 / 30.0,
19
+ delta_percentage=15.0 / 30.0 - 5.0 / 15.0,
20
+ ),
21
+ "b": Report(
22
+ total=-10, # only in 1
23
+ left_count=10,
24
+ right_count=0,
25
+ delta_absolute=0 - 10,
26
+ left_percentage=10.0 / 15.0,
27
+ right_percentage=0.0,
28
+ delta_percentage=-1.0,
29
+ ),
30
+ "c": Report(
31
+ total=15, # only in 2
32
+ left_count=0,
33
+ right_count=15,
34
+ delta_absolute=15 - 0,
35
+ left_percentage=0.0,
36
+ right_percentage=15.0 / 30.0,
37
+ delta_percentage=1.0,
38
+ ),
39
+ "__NEW_VALUES__": Report(
40
+ total=2, # 1 on each side
41
+ left_count=1, # b
42
+ right_count=1, # c
43
+ delta_absolute=1 - 1,
44
+ left_percentage=1.0 / 15.0, # TODO: nuke this
45
+ right_percentage=1.0 / 30.0, # TODO: nuke this
46
+ delta_percentage=1.0 / 30.0 - 1.0 / 15.0,
47
+ ),
48
48
  }
49
49
  }
50
50
 
@@ -0,0 +1,15 @@
1
+ import os
2
+ from traffic_taffy.dissection import PCAPDissectorLevel
3
+ from traffic_taffy.dissector_engine.dpkt import DissectionEngineDpkt
4
+
5
+ def test_dpkt_engine():
6
+ test_pcap = "dns.pcap"
7
+ test_pcap = "port53-2023-30-31_20.pcap"
8
+ test_pcap = "airplane-wireless.pcap"
9
+ if not os.path.exists(test_pcap):
10
+ return
11
+
12
+ engine = DissectionEngineDpkt(test_pcap,
13
+ dissector_level = PCAPDissectorLevel.COMMON_LAYERS)
14
+ dissection = engine.load()
15
+
@@ -4,7 +4,7 @@ from traffic_taffy.dissection import Dissection
4
4
  from traffic_taffy.dissector import PCAPDissector, PCAPDissectorLevel
5
5
 
6
6
 
7
- def test_dissector_load():
7
+ def test_dissector_load() -> None:
8
8
  from traffic_taffy.dissector import PCAPDissector
9
9
 
10
10
  pd = PCAPDissector("bogus")
@@ -12,7 +12,7 @@ def test_dissector_load():
12
12
  assert pd.pcap_file == "bogus"
13
13
 
14
14
 
15
- def test_dissector_simple_callback():
15
+ def test_dissector_simple_callback() -> None:
16
16
  base_pcap = "/tmp/dissector-test.pcap" # doesn't need to exist
17
17
  save_file = base_pcap + ".taffy"
18
18
 
@@ -52,7 +52,7 @@ def test_dissector_simple_callback():
52
52
  # create a new one to make sure it's blank
53
53
  pd = PCAPDissector(
54
54
  base_pcap,
55
- dissector_level=PCAPDissectorLevel.DETAILED.value,
55
+ dissector_level=PCAPDissectorLevel.COUNT_ONLY.value,
56
56
  cache_results=True,
57
57
  )
58
58
 
@@ -67,5 +67,5 @@ def test_dissector_simple_callback():
67
67
  os.unlink(save_file)
68
68
 
69
69
 
70
- def test_dissector_scapy_callback():
70
+ def test_dissector_scapy_callback() -> None:
71
71
  assert True
@@ -31,7 +31,6 @@ def test_pcap_splitter():
31
31
  ]:
32
32
  debug(f"===== trying to load {test_pcap} ====")
33
33
  if not os.path.exists(test_pcap):
34
- print(f"a test requires a {test_pcap} file to read and parse")
35
34
  continue
36
35
 
37
36
  # clean up previous runs
@@ -1,13 +1,14 @@
1
- """Loads the cached data for a file to display the results about it"""
1
+ """Loads the cached data for a file to display the results about it."""
2
2
 
3
- from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
3
+ from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter, Namespace
4
+ from pathlib import Path
4
5
  from rich import print
5
6
  import logging
6
7
  import msgpack
7
8
 
8
9
 
9
- def parse_args():
10
- "Parse the command line arguments."
10
+ def parse_args() -> Namespace:
11
+ """Parse the command line arguments."""
11
12
  parser = ArgumentParser(
12
13
  formatter_class=ArgumentDefaultsHelpFormatter,
13
14
  description=__doc__,
@@ -34,15 +35,17 @@ def parse_args():
34
35
  return args
35
36
 
36
37
 
37
- def main():
38
+ def main() -> None:
39
+ """Run taffy-cache-info."""
38
40
  args = parse_args()
39
41
 
40
42
  for cache_file in args.cache_file:
41
43
  print(f"===== {cache_file} ======")
42
- contents = msgpack.load(open(cache_file, "rb"), strict_map_key=False)
44
+ with Path(cache_file).open("rb") as fh:
45
+ contents = msgpack.load(fh, strict_map_key=False)
43
46
 
44
47
  # play the major keys
45
- for key in contents.keys():
48
+ for key in contents:
46
49
  if key != "dissection" and key != "parameters":
47
50
  print(f"{key:<20} {contents[key]}")
48
51
 
@@ -1,6 +1,7 @@
1
- """Takes a set of pcap files to compare and creates a report"""
1
+ """Takes a set of pcap files to compare and creates a report."""
2
2
 
3
- from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
3
+ import sys
4
+ from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter, Namespace
4
5
  import logging
5
6
  from traffic_taffy.output.console import Console
6
7
  from traffic_taffy.output.fsdb import Fsdb
@@ -14,8 +15,8 @@ from traffic_taffy.dissector import (
14
15
  from traffic_taffy.compare import PcapCompare
15
16
 
16
17
 
17
- def parse_args():
18
- "Parse the command line arguments."
18
+ def parse_args() -> Namespace:
19
+ """Parse the command line arguments."""
19
20
  parser = ArgumentParser(
20
21
  formatter_class=ArgumentDefaultsHelpFormatter,
21
22
  description=__doc__,
@@ -31,7 +32,7 @@ def parse_args():
31
32
  )
32
33
 
33
34
  limitor_parser = limitor_add_parseargs(parser)
34
- compare_add_parseargs(limitor_parser, False)
35
+ compare_add_parseargs(limitor_parser, add_subgroup=False)
35
36
  dissector_add_parseargs(parser)
36
37
 
37
38
  debugging_group = parser.add_argument_group("Debugging options")
@@ -43,14 +44,6 @@ def parse_args():
43
44
  help="Define the logging verbosity level (debug, info, warning, error, ...).",
44
45
  )
45
46
 
46
- parser.add_argument(
47
- "-s",
48
- "--sort-by",
49
- default="delta%",
50
- type=str,
51
- help="Sort report entries by this column",
52
- )
53
-
54
47
  parser.add_argument("pcap_files", type=str, nargs="*", help="PCAP files to analyze")
55
48
 
56
49
  args = parser.parse_args()
@@ -62,7 +55,8 @@ def parse_args():
62
55
  return args
63
56
 
64
57
 
65
- def main():
58
+ def main() -> None:
59
+ """Run taffy-compare."""
66
60
  args = parse_args()
67
61
 
68
62
  # setup output options
@@ -88,7 +82,7 @@ def main():
88
82
  cache_file_suffix=args.cache_file_suffix,
89
83
  maximum_count=printing_arguments["maximum_count"],
90
84
  dissection_level=args.dissection_level,
91
- between_times=args.between_times,
85
+ # between_times=args.between_times, # TODO(hardaker): TBD
92
86
  bin_size=args.bin_size,
93
87
  ignore_list=args.ignore_list,
94
88
  pcap_filter=args.filter,
@@ -101,7 +95,7 @@ def main():
101
95
  try:
102
96
  reports = pc.compare()
103
97
  except ValueError:
104
- exit()
98
+ sys.exit()
105
99
 
106
100
  if args.fsdb:
107
101
  output = Fsdb(None, printing_arguments)
@@ -1,3 +1,5 @@
1
+ """Performs generic dissection of a PCAP file."""
2
+ import logging
1
3
  from traffic_taffy.dissector import (
2
4
  dissector_add_parseargs,
3
5
  limitor_add_parseargs,
@@ -5,14 +7,14 @@ from traffic_taffy.dissector import (
5
7
  PCAPDissector,
6
8
  )
7
9
  from traffic_taffy.dissectmany import PCAPDissectMany
10
+ from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter, Namespace
8
11
 
9
12
 
10
- def main():
11
- from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
12
- import logging
13
+ def main() -> None:
14
+ """Dissect a pcap file and report contents."""
13
15
 
14
- def parse_args():
15
- "Parse the command line arguments."
16
+ def parse_args() -> Namespace:
17
+ """Parse the command line arguments."""
16
18
  parser = ArgumentParser(
17
19
  formatter_class=ArgumentDefaultsHelpFormatter,
18
20
  description=__doc__,
@@ -67,7 +69,7 @@ def main():
67
69
  force_overwrite=args.force_overwrite,
68
70
  force_load=args.force_load,
69
71
  )
70
- dissections = pdm.load_all(True, dont_fork=args.dont_fork)
72
+ dissections = pdm.load_all(return_as_list=True, dont_fork=args.dont_fork)
71
73
 
72
74
  # merge them into a single dissection
73
75
  dissection = dissections.pop(0)