traffic-taffy 0.3.6__py3-none-any.whl → 0.4.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.
- traffic_taffy/cache_info.py +0 -6
- traffic_taffy/compare.py +154 -250
- traffic_taffy/comparison.py +26 -0
- traffic_taffy/dissection.py +383 -0
- traffic_taffy/dissectmany.py +20 -18
- traffic_taffy/dissector.py +128 -476
- traffic_taffy/dissector_engine/__init__.py +35 -0
- traffic_taffy/dissector_engine/dpkt.py +98 -0
- traffic_taffy/dissector_engine/scapy.py +98 -0
- traffic_taffy/graph.py +23 -90
- traffic_taffy/graphdata.py +35 -20
- traffic_taffy/output/__init__.py +118 -0
- traffic_taffy/output/console.py +72 -0
- traffic_taffy/output/fsdb.py +50 -0
- traffic_taffy/output/memory.py +51 -0
- traffic_taffy/pcap_splitter.py +17 -36
- traffic_taffy/tools/cache_info.py +65 -0
- traffic_taffy/tools/compare.py +110 -0
- traffic_taffy/tools/dissect.py +77 -0
- traffic_taffy/tools/explore.py +686 -0
- traffic_taffy/tools/graph.py +85 -0
- {traffic_taffy-0.3.6.dist-info → traffic_taffy-0.4.1.dist-info}/METADATA +1 -1
- traffic_taffy-0.4.1.dist-info/RECORD +29 -0
- traffic_taffy-0.4.1.dist-info/entry_points.txt +6 -0
- pcap_compare/cache_info.py +0 -46
- pcap_compare/compare.py +0 -288
- pcap_compare/dissectmany.py +0 -21
- pcap_compare/dissector.py +0 -512
- pcap_compare/dissectorresults.py +0 -21
- pcap_compare/graph.py +0 -210
- traffic_taffy/explore.py +0 -221
- traffic_taffy-0.3.6.dist-info/RECORD +0 -22
- traffic_taffy-0.3.6.dist-info/entry_points.txt +0 -5
- {pcap_compare → traffic_taffy/tools}/__init__.py +0 -0
- {traffic_taffy-0.3.6.dist-info → traffic_taffy-0.4.1.dist-info}/WHEEL +0 -0
- {traffic_taffy-0.3.6.dist-info → traffic_taffy-0.4.1.dist-info}/top_level.txt +0 -0
pcap_compare/graph.py
DELETED
@@ -1,210 +0,0 @@
|
|
1
|
-
"""Read a PCAP file and graph it or parts of it"""
|
2
|
-
|
3
|
-
import os
|
4
|
-
import seaborn as sns
|
5
|
-
import matplotlib.pyplot as plt
|
6
|
-
import pandas
|
7
|
-
from pandas import DataFrame, to_datetime
|
8
|
-
from pcap_compare.dissector import (
|
9
|
-
PCAPDissectorType,
|
10
|
-
dissector_add_parseargs,
|
11
|
-
limitor_add_parseargs,
|
12
|
-
check_dissector_level,
|
13
|
-
)
|
14
|
-
from pcap_compare.dissectmany import PCAPDissectMany, PCAPDissector
|
15
|
-
|
16
|
-
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
17
|
-
from logging import debug, info
|
18
|
-
import logging
|
19
|
-
|
20
|
-
|
21
|
-
def parse_args():
|
22
|
-
"Parse the command line arguments."
|
23
|
-
parser = ArgumentParser(
|
24
|
-
formatter_class=ArgumentDefaultsHelpFormatter,
|
25
|
-
description=__doc__,
|
26
|
-
epilog="Exmaple Usage: ",
|
27
|
-
)
|
28
|
-
|
29
|
-
parser.add_argument(
|
30
|
-
"-g",
|
31
|
-
"--graph-elements",
|
32
|
-
default=None,
|
33
|
-
type=str,
|
34
|
-
help="Graph these particular elements; the default is packet counts",
|
35
|
-
)
|
36
|
-
|
37
|
-
parser.add_argument(
|
38
|
-
"-o",
|
39
|
-
"--output-file",
|
40
|
-
default=None,
|
41
|
-
type=str,
|
42
|
-
help="Where to save the output (png)",
|
43
|
-
)
|
44
|
-
|
45
|
-
parser.add_argument(
|
46
|
-
"--log-level",
|
47
|
-
"--ll",
|
48
|
-
default="info",
|
49
|
-
help="Define verbosity level (debug, info, warning, error, fotal, critical).",
|
50
|
-
)
|
51
|
-
|
52
|
-
parser.add_argument(
|
53
|
-
"-b",
|
54
|
-
"--bin-size",
|
55
|
-
type=int,
|
56
|
-
default=1,
|
57
|
-
help="Bin results into this many seconds",
|
58
|
-
)
|
59
|
-
|
60
|
-
dissector_add_parseargs(parser)
|
61
|
-
limitor_add_parseargs(parser)
|
62
|
-
|
63
|
-
parser.add_argument("input_file", type=str, help="PCAP file to graph", nargs="+")
|
64
|
-
|
65
|
-
args = parser.parse_args()
|
66
|
-
log_level = args.log_level.upper()
|
67
|
-
logging.basicConfig(level=log_level, format="%(levelname)-10s:\t%(message)s")
|
68
|
-
logging.getLogger("matplotlib.font_manager").setLevel(logging.ERROR)
|
69
|
-
return args
|
70
|
-
|
71
|
-
|
72
|
-
class PcapGraph:
|
73
|
-
def __init__(
|
74
|
-
self,
|
75
|
-
pcap_files: str,
|
76
|
-
output_file: str,
|
77
|
-
maximum_count: int = None,
|
78
|
-
minimum_count: int = None,
|
79
|
-
bin_size: int = None,
|
80
|
-
match_key: str = None,
|
81
|
-
match_value: str = None,
|
82
|
-
cache_pcap_results: bool = False,
|
83
|
-
dissector_level: PCAPDissectorType = PCAPDissectorType.COUNT_ONLY,
|
84
|
-
):
|
85
|
-
self.pcap_files = pcap_files
|
86
|
-
self.output_file = output_file
|
87
|
-
self.maximum_count = maximum_count
|
88
|
-
self.minimum_count = minimum_count
|
89
|
-
self.bin_size = bin_size
|
90
|
-
self.subsections = None
|
91
|
-
self.pkt_filter = None
|
92
|
-
self.match_key = match_key
|
93
|
-
self.match_value = match_value
|
94
|
-
self.cache_pcap_results = cache_pcap_results
|
95
|
-
self.dissector_level = dissector_level
|
96
|
-
|
97
|
-
def load_pcaps(self):
|
98
|
-
"loads the pcap and counts things into bins"
|
99
|
-
self.data = {}
|
100
|
-
|
101
|
-
info("reading pcap files")
|
102
|
-
pdm = PCAPDissectMany(
|
103
|
-
self.pcap_files,
|
104
|
-
bin_size=self.bin_size,
|
105
|
-
maximum_count=self.maximum_count,
|
106
|
-
dissector_level=self.dissector_level,
|
107
|
-
pcap_filter=self.pkt_filter,
|
108
|
-
cache_results=self.cache_pcap_results,
|
109
|
-
)
|
110
|
-
results = pdm.load_all()
|
111
|
-
|
112
|
-
for result in results:
|
113
|
-
self.data[result["file"]] = result["data"]
|
114
|
-
info("done reading pcap files")
|
115
|
-
|
116
|
-
def normalize_bins(self, counters):
|
117
|
-
results = {}
|
118
|
-
time_keys = list(counters.keys())
|
119
|
-
if time_keys[0] == 0: # likely always
|
120
|
-
time_keys.pop(0)
|
121
|
-
time_keys[0]
|
122
|
-
time_keys[-1]
|
123
|
-
|
124
|
-
results = {"time": [], "count": [], "index": []}
|
125
|
-
|
126
|
-
# TODO: this could likely be made much more efficient and needs hole-filling
|
127
|
-
info(f"match value: {self.match_value}")
|
128
|
-
for (timestamp, key, subkey, value) in PCAPDissector.find_data(
|
129
|
-
counters,
|
130
|
-
timestamps=time_keys,
|
131
|
-
match_string=self.match_key,
|
132
|
-
match_value=self.match_value,
|
133
|
-
minimum_count=self.minimum_count,
|
134
|
-
make_printable=True,
|
135
|
-
):
|
136
|
-
index = key + "=" + subkey
|
137
|
-
results["count"].append(int(value))
|
138
|
-
results["index"].append(index)
|
139
|
-
results["time"].append(timestamp)
|
140
|
-
|
141
|
-
return results
|
142
|
-
|
143
|
-
def merge_datasets(self):
|
144
|
-
datasets = []
|
145
|
-
for dataset in self.data:
|
146
|
-
data = self.normalize_bins(self.data[dataset])
|
147
|
-
data = DataFrame.from_records(data)
|
148
|
-
data["filename"] = os.path.basename(dataset)
|
149
|
-
data["time"] = to_datetime(data["time"], unit="s")
|
150
|
-
datasets.append(data)
|
151
|
-
datasets = pandas.concat(datasets)
|
152
|
-
return datasets
|
153
|
-
|
154
|
-
def create_graph(self):
|
155
|
-
"Graph the results of the data collection"
|
156
|
-
debug("creating the graph")
|
157
|
-
sns.set_theme()
|
158
|
-
|
159
|
-
df = self.merge_datasets()
|
160
|
-
debug(df)
|
161
|
-
|
162
|
-
hue_variable = "index"
|
163
|
-
if df[hue_variable].nunique() == 1:
|
164
|
-
hue_variable = None
|
165
|
-
|
166
|
-
ax = sns.relplot(
|
167
|
-
data=df,
|
168
|
-
kind="line",
|
169
|
-
x="time",
|
170
|
-
y="count",
|
171
|
-
hue=hue_variable,
|
172
|
-
aspect=1.77,
|
173
|
-
)
|
174
|
-
ax.set(xlabel="time", ylabel="count")
|
175
|
-
plt.xticks(rotation=45)
|
176
|
-
|
177
|
-
info(f"saving graph to {self.output_file}")
|
178
|
-
if self.output_file:
|
179
|
-
plt.savefig(self.output_file)
|
180
|
-
else:
|
181
|
-
plt.show()
|
182
|
-
|
183
|
-
def graph_it(self):
|
184
|
-
debug("--- loading pcaps")
|
185
|
-
self.load_pcaps()
|
186
|
-
debug("--- creating graph")
|
187
|
-
self.create_graph()
|
188
|
-
|
189
|
-
|
190
|
-
def main():
|
191
|
-
args = parse_args()
|
192
|
-
|
193
|
-
check_dissector_level(args.dissection_level)
|
194
|
-
|
195
|
-
pc = PcapGraph(
|
196
|
-
args.input_file,
|
197
|
-
args.output_file,
|
198
|
-
maximum_count=args.packet_count,
|
199
|
-
minimum_count=args.minimum_count,
|
200
|
-
bin_size=args.bin_size,
|
201
|
-
match_key=args.match_string,
|
202
|
-
match_value=args.match_value,
|
203
|
-
cache_pcap_results=args.cache_pcap_results,
|
204
|
-
dissector_level=args.dissection_level,
|
205
|
-
)
|
206
|
-
pc.graph_it()
|
207
|
-
|
208
|
-
|
209
|
-
if __name__ == "__main__":
|
210
|
-
main()
|
traffic_taffy/explore.py
DELETED
@@ -1,221 +0,0 @@
|
|
1
|
-
import sys
|
2
|
-
import logging
|
3
|
-
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
4
|
-
from traffic_taffy.dissector import (
|
5
|
-
dissector_add_parseargs,
|
6
|
-
limitor_add_parseargs,
|
7
|
-
check_dissector_level,
|
8
|
-
)
|
9
|
-
from traffic_taffy.graphdata import PcapGraphData
|
10
|
-
from traffic_taffy.compare import PcapCompare
|
11
|
-
from PyQt6.QtCharts import QLineSeries, QChart, QChartView
|
12
|
-
|
13
|
-
# https://stackoverflow.com/questions/32476006/how-to-make-an-expandable-collapsable-section-widget-in-qt
|
14
|
-
|
15
|
-
# class Widget(QWidget):
|
16
|
-
# def __init__(self):
|
17
|
-
# super().__init__()
|
18
|
-
# self.__initUi()
|
19
|
-
|
20
|
-
# def __initUi(self):
|
21
|
-
# addBtn = QPushButton('Add')
|
22
|
-
# addBtn.clicked.connect(self.__add)
|
23
|
-
# self.__foldableListWidget = FoldableListWidget()
|
24
|
-
# lay = QVBoxLayout()
|
25
|
-
# lay.addWidget(addBtn)
|
26
|
-
# lay.addWidget(self.__foldableListWidget)
|
27
|
-
# self.setLayout(lay)
|
28
|
-
|
29
|
-
# def __add(self):
|
30
|
-
# foldedItem = QLabel("folded")
|
31
|
-
# # foldedItem.setPlaceholderText('Input...')
|
32
|
-
|
33
|
-
# sublist = FoldableListWidget()
|
34
|
-
# subitem1 = QLabel("main item")
|
35
|
-
# subitem2 = QLabel("sub item")
|
36
|
-
# sublist.setFoldableListWidgetItem(subitem1, subitem2)
|
37
|
-
|
38
|
-
# self.__foldableListWidget.setFoldableListWidgetItem(foldedItem, sublist)
|
39
|
-
|
40
|
-
|
41
|
-
from PyQt6.QtWidgets import (
|
42
|
-
QPushButton,
|
43
|
-
QDialog,
|
44
|
-
QGridLayout,
|
45
|
-
QVBoxLayout,
|
46
|
-
QHBoxLayout,
|
47
|
-
QApplication,
|
48
|
-
QWidget,
|
49
|
-
)
|
50
|
-
|
51
|
-
|
52
|
-
class TaffyExplorer(QDialog, PcapGraphData):
|
53
|
-
"""Explore PCAP files by comparison slices"""
|
54
|
-
|
55
|
-
def __init__(self, args):
|
56
|
-
super().__init__()
|
57
|
-
|
58
|
-
# TODO: allow varying
|
59
|
-
self.minimum_count = 2
|
60
|
-
|
61
|
-
self.mainLayout = QVBoxLayout()
|
62
|
-
self.setLayout(self.mainLayout)
|
63
|
-
|
64
|
-
# create the graph at the top
|
65
|
-
self.detail_graph = QChart()
|
66
|
-
self.detail_graph_view = QChartView(self.detail_graph)
|
67
|
-
self.mainLayout.addWidget(self.detail_graph_view)
|
68
|
-
|
69
|
-
# create the mini graph next
|
70
|
-
self.traffic_graph = QChart()
|
71
|
-
self.traffic_graph.legend().hide()
|
72
|
-
self.traffic_graph.setTitle("All Traffic")
|
73
|
-
self.traffic_graph_view = QChartView(self.traffic_graph)
|
74
|
-
self.mainLayout.addWidget(self.traffic_graph_view)
|
75
|
-
|
76
|
-
# create the traffic source menu bar
|
77
|
-
self.source_menus = QHBoxLayout() # TODO: line graph
|
78
|
-
self.source_menus_w = QWidget()
|
79
|
-
self.source_menus_w.setLayout(self.source_menus)
|
80
|
-
self.mainLayout.addWidget(self.source_menus_w)
|
81
|
-
|
82
|
-
# the comparison panel contains deltas between them
|
83
|
-
self.comparison_panel = QGridLayout()
|
84
|
-
self.comparison_panel_w = QWidget()
|
85
|
-
self.comparison_panel_w.setLayout(self.comparison_panel)
|
86
|
-
self.mainLayout.addWidget(self.comparison_panel_w)
|
87
|
-
|
88
|
-
self.quit_button = QPushButton("Quit")
|
89
|
-
self.mainLayout.addWidget(self.quit_button)
|
90
|
-
self.quit_button.clicked.connect(self.quit)
|
91
|
-
|
92
|
-
# self.tree = QTreeWidget()
|
93
|
-
# self.tree.setHeaderHidden(True)
|
94
|
-
# self.tree.setIndentation(0)
|
95
|
-
|
96
|
-
self.args = args
|
97
|
-
|
98
|
-
def quit(self):
|
99
|
-
exit()
|
100
|
-
|
101
|
-
def create_comparison(self):
|
102
|
-
self.pc = PcapCompare(
|
103
|
-
self.args.pcap_files,
|
104
|
-
maximum_count=self.args.packet_count,
|
105
|
-
print_threshold=float(self.args.print_threshold) / 100.0,
|
106
|
-
print_minimum_count=self.args.minimum_count,
|
107
|
-
print_match_string=self.args.match_string,
|
108
|
-
only_positive=self.args.only_positive,
|
109
|
-
only_negative=self.args.only_negative,
|
110
|
-
cache_results=self.args.cache_pcap_results,
|
111
|
-
dissection_level=self.args.dissection_level,
|
112
|
-
between_times=self.args.between_times,
|
113
|
-
bin_size=self.args.bin_size,
|
114
|
-
)
|
115
|
-
|
116
|
-
# create the graph data storage
|
117
|
-
# and load everything in
|
118
|
-
datasets = list(self.pc.load_pcaps())
|
119
|
-
|
120
|
-
self.data = {}
|
121
|
-
for dataset in datasets:
|
122
|
-
self.data[dataset["file"]] = dataset["data"]
|
123
|
-
|
124
|
-
def update_chart(
|
125
|
-
self, chart: QChart, match_key: str, match_value: str | None = None
|
126
|
-
):
|
127
|
-
self.match_key = match_key
|
128
|
-
self.match_value = match_value
|
129
|
-
|
130
|
-
df = self.merge_datasets()
|
131
|
-
|
132
|
-
series = QLineSeries()
|
133
|
-
|
134
|
-
# TODO: there must be a better way!
|
135
|
-
for index in df.index:
|
136
|
-
series.append(
|
137
|
-
df["time"][index].to_pydatetime().timestamp(), df["count"][index]
|
138
|
-
)
|
139
|
-
|
140
|
-
chart.addSeries(series)
|
141
|
-
|
142
|
-
def update_detail_chart(
|
143
|
-
self, match_key: str = "__TOTAL__", match_value: str | None = None
|
144
|
-
):
|
145
|
-
self.update_chart(self.detail_graph, match_key, match_value)
|
146
|
-
|
147
|
-
def update_traffic_chart(self):
|
148
|
-
self.update_chart(self.traffic_graph, "__TOTAL__")
|
149
|
-
|
150
|
-
# def show_comparison(self, pcap_one, timestamp_one, pcap_two, timestamp_two):
|
151
|
-
|
152
|
-
|
153
|
-
def parse_args():
|
154
|
-
"Parse the command line arguments."
|
155
|
-
parser = ArgumentParser(
|
156
|
-
formatter_class=ArgumentDefaultsHelpFormatter,
|
157
|
-
description=__doc__,
|
158
|
-
epilog="Exmaple Usage: ",
|
159
|
-
)
|
160
|
-
|
161
|
-
limiting_parser = limitor_add_parseargs(parser)
|
162
|
-
|
163
|
-
limiting_parser.add_argument(
|
164
|
-
"-t",
|
165
|
-
"--print-threshold",
|
166
|
-
default=0.0,
|
167
|
-
type=float,
|
168
|
-
help="Don't print results with abs(percent) less than this threshold",
|
169
|
-
)
|
170
|
-
|
171
|
-
limiting_parser.add_argument(
|
172
|
-
"-P", "--only-positive", action="store_true", help="Only show positive entries"
|
173
|
-
)
|
174
|
-
|
175
|
-
limiting_parser.add_argument(
|
176
|
-
"-N", "--only-negative", action="store_true", help="Only show negative entries"
|
177
|
-
)
|
178
|
-
|
179
|
-
limiting_parser.add_argument(
|
180
|
-
"-T",
|
181
|
-
"--between-times",
|
182
|
-
nargs=2,
|
183
|
-
type=int,
|
184
|
-
help="For single files, only display results between these timestamps",
|
185
|
-
)
|
186
|
-
|
187
|
-
dissector_add_parseargs(parser)
|
188
|
-
|
189
|
-
debugging_group = parser.add_argument_group("Debugging options")
|
190
|
-
|
191
|
-
debugging_group.add_argument(
|
192
|
-
"--log-level",
|
193
|
-
"--ll",
|
194
|
-
default="info",
|
195
|
-
help="Define the logging verbosity level (debug, info, warning, error, ...).",
|
196
|
-
)
|
197
|
-
|
198
|
-
parser.add_argument("pcap_files", type=str, nargs="+", help="PCAP files to analyze")
|
199
|
-
|
200
|
-
args = parser.parse_args()
|
201
|
-
log_level = args.log_level.upper()
|
202
|
-
logging.basicConfig(level=log_level, format="%(levelname)-10s:\t%(message)s")
|
203
|
-
|
204
|
-
check_dissector_level(args.dissection_level)
|
205
|
-
|
206
|
-
return args
|
207
|
-
|
208
|
-
|
209
|
-
def main():
|
210
|
-
args = parse_args()
|
211
|
-
|
212
|
-
app = QApplication(sys.argv)
|
213
|
-
window = TaffyExplorer(args)
|
214
|
-
window.create_comparison()
|
215
|
-
window.update_traffic_chart()
|
216
|
-
window.show()
|
217
|
-
sys.exit(app.exec())
|
218
|
-
|
219
|
-
|
220
|
-
if __name__ == "__main__":
|
221
|
-
main()
|
@@ -1,22 +0,0 @@
|
|
1
|
-
pcap_compare/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
pcap_compare/cache_info.py,sha256=22Fs5fNU_GzWwcAua_XfEFswRmlM8TmTbHXe2CYKQDE,1468
|
3
|
-
pcap_compare/compare.py,sha256=xVRNmCsdUp1mEZub0SN2CW7osOZMgJuW__Cj9UA2DRE,9169
|
4
|
-
pcap_compare/dissectmany.py,sha256=dxxChV0Qq9LeoPXiS0cCIEKFtO_RJwHxwOoPOZ3rwU8,681
|
5
|
-
pcap_compare/dissector.py,sha256=iEZzLHJ7HubM3-UbZR461irXkZcpM8u_Aoa7GzWUMEY,17571
|
6
|
-
pcap_compare/dissectorresults.py,sha256=LKoyX04Qjc6B7RnqtJgIWsyVnselJ9CygLkMAp3lhw0,647
|
7
|
-
pcap_compare/graph.py,sha256=cioVq9JYNRVqzlRJSKLveGnGSEL9FkXDUyp-Y7osSkM,5935
|
8
|
-
traffic_taffy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
-
traffic_taffy/cache_info.py,sha256=zpuuJcPzJzyma37YJE4FwKSaOBAmzImDj0Khdn2x1FI,1623
|
10
|
-
traffic_taffy/compare.py,sha256=IDcO2rzYoVs_9NrUTF5TaJHzrETOw0eheu98vu_h8O4,12359
|
11
|
-
traffic_taffy/dissectmany.py,sha256=DDz2Kv_0TIuSRwb2jgl2ffHEHWhwTrnhkIyzDH8pJvA,2514
|
12
|
-
traffic_taffy/dissector.py,sha256=qYsSGTuxT0bYy5WW1EZaAua2guPBetEjunJk-ZMTVWU,21484
|
13
|
-
traffic_taffy/dissectorresults.py,sha256=LKoyX04Qjc6B7RnqtJgIWsyVnselJ9CygLkMAp3lhw0,647
|
14
|
-
traffic_taffy/explore.py,sha256=O4C5X9yKUbu0LZNKcBhwpEEoLomHWH7MqMUm2ike_Lc,6595
|
15
|
-
traffic_taffy/graph.py,sha256=-fluyt-REEIzM9h3uoExrJT0K5s1LUTdcHllrr_quoM,4952
|
16
|
-
traffic_taffy/graphdata.py,sha256=mXzRWAReV_oDfnRIVlxqaOCk5nZKZ07mutHPdnX5eeA,1583
|
17
|
-
traffic_taffy/pcap_splitter.py,sha256=qDZ0Nd81fIteQRVElyKsD1ncBi4lg07cmx8KPl_q3rY,4585
|
18
|
-
traffic_taffy-0.3.6.dist-info/METADATA,sha256=qC5mASfshWYRXyplqLMoE_yxAUdyx_H2CVn77cw118k,1014
|
19
|
-
traffic_taffy-0.3.6.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
20
|
-
traffic_taffy-0.3.6.dist-info/entry_points.txt,sha256=xabsmMHgr0Pek8ccdQoW6iAKqljueozDZW-q1hJ79eU,194
|
21
|
-
traffic_taffy-0.3.6.dist-info/top_level.txt,sha256=wjeQaxXSKqUzrRtfbUlbSn8SoaL4VC73yudFAEtnIuo,14
|
22
|
-
traffic_taffy-0.3.6.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|