atomicshop 2.15.11__py3-none-any.whl → 3.10.5__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.
Files changed (221) hide show
  1. atomicshop/__init__.py +1 -1
  2. atomicshop/{addons/mains → a_mains}/FACT/update_extract.py +3 -2
  3. atomicshop/a_mains/dns_gateway_setting.py +11 -0
  4. atomicshop/a_mains/get_local_tcp_ports.py +85 -0
  5. atomicshop/a_mains/github_wrapper.py +11 -0
  6. atomicshop/a_mains/install_ca_certificate.py +172 -0
  7. atomicshop/a_mains/process_from_port.py +119 -0
  8. atomicshop/a_mains/set_default_dns_gateway.py +90 -0
  9. atomicshop/a_mains/update_config_toml.py +38 -0
  10. atomicshop/basics/ansi_escape_codes.py +3 -1
  11. atomicshop/basics/argparse_template.py +2 -0
  12. atomicshop/basics/booleans.py +27 -30
  13. atomicshop/basics/bytes_arrays.py +43 -0
  14. atomicshop/basics/classes.py +149 -1
  15. atomicshop/basics/enums.py +2 -2
  16. atomicshop/basics/exceptions.py +5 -1
  17. atomicshop/basics/list_of_classes.py +29 -0
  18. atomicshop/basics/multiprocesses.py +374 -50
  19. atomicshop/basics/strings.py +72 -3
  20. atomicshop/basics/threads.py +14 -0
  21. atomicshop/basics/tracebacks.py +13 -3
  22. atomicshop/certificates.py +153 -52
  23. atomicshop/config_init.py +11 -6
  24. atomicshop/console_user_response.py +7 -14
  25. atomicshop/consoles.py +9 -0
  26. atomicshop/datetimes.py +1 -1
  27. atomicshop/diff_check.py +3 -3
  28. atomicshop/dns.py +128 -3
  29. atomicshop/etws/_pywintrace_fix.py +17 -0
  30. atomicshop/etws/trace.py +40 -42
  31. atomicshop/etws/traces/trace_dns.py +56 -44
  32. atomicshop/etws/traces/trace_tcp.py +130 -0
  33. atomicshop/file_io/csvs.py +27 -5
  34. atomicshop/file_io/docxs.py +34 -17
  35. atomicshop/file_io/file_io.py +31 -17
  36. atomicshop/file_io/jsons.py +49 -0
  37. atomicshop/file_io/tomls.py +139 -0
  38. atomicshop/filesystem.py +616 -291
  39. atomicshop/get_process_list.py +3 -3
  40. atomicshop/http_parse.py +149 -93
  41. atomicshop/ip_addresses.py +6 -1
  42. atomicshop/mitm/centered_settings.py +132 -0
  43. atomicshop/mitm/config_static.py +207 -0
  44. atomicshop/mitm/config_toml_editor.py +55 -0
  45. atomicshop/mitm/connection_thread_worker.py +875 -357
  46. atomicshop/mitm/engines/__parent/parser___parent.py +4 -17
  47. atomicshop/mitm/engines/__parent/recorder___parent.py +108 -51
  48. atomicshop/mitm/engines/__parent/requester___parent.py +116 -0
  49. atomicshop/mitm/engines/__parent/responder___parent.py +75 -114
  50. atomicshop/mitm/engines/__reference_general/parser___reference_general.py +10 -7
  51. atomicshop/mitm/engines/__reference_general/recorder___reference_general.py +5 -5
  52. atomicshop/mitm/engines/__reference_general/requester___reference_general.py +47 -0
  53. atomicshop/mitm/engines/__reference_general/responder___reference_general.py +95 -13
  54. atomicshop/mitm/engines/create_module_template.py +58 -14
  55. atomicshop/mitm/import_config.py +359 -139
  56. atomicshop/mitm/initialize_engines.py +160 -80
  57. atomicshop/mitm/message.py +64 -23
  58. atomicshop/mitm/mitm_main.py +892 -0
  59. atomicshop/mitm/recs_files.py +183 -0
  60. atomicshop/mitm/shared_functions.py +4 -10
  61. atomicshop/mitm/ssh_tester.py +82 -0
  62. atomicshop/mitm/statistic_analyzer.py +136 -40
  63. atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +265 -83
  64. atomicshop/monitor/checks/dns.py +1 -1
  65. atomicshop/networks.py +671 -0
  66. atomicshop/on_exit.py +39 -9
  67. atomicshop/package_mains_processor.py +84 -0
  68. atomicshop/permissions/permissions.py +22 -0
  69. atomicshop/permissions/ubuntu_permissions.py +239 -0
  70. atomicshop/permissions/win_permissions.py +33 -0
  71. atomicshop/print_api.py +24 -42
  72. atomicshop/process.py +24 -6
  73. atomicshop/process_poller/process_pool.py +0 -1
  74. atomicshop/process_poller/simple_process_pool.py +204 -5
  75. atomicshop/python_file_patcher.py +1 -1
  76. atomicshop/python_functions.py +27 -75
  77. atomicshop/speech_recognize.py +8 -0
  78. atomicshop/ssh_remote.py +158 -172
  79. atomicshop/system_resource_monitor.py +61 -47
  80. atomicshop/system_resources.py +8 -8
  81. atomicshop/tempfiles.py +1 -2
  82. atomicshop/urls.py +6 -0
  83. atomicshop/venvs.py +28 -0
  84. atomicshop/versioning.py +27 -0
  85. atomicshop/web.py +98 -27
  86. atomicshop/web_apis/google_custom_search.py +44 -0
  87. atomicshop/web_apis/google_llm.py +188 -0
  88. atomicshop/websocket_parse.py +450 -0
  89. atomicshop/wrappers/certauthw/certauth.py +1 -0
  90. atomicshop/wrappers/cryptographyw.py +29 -8
  91. atomicshop/wrappers/ctyping/etw_winapi/const.py +97 -47
  92. atomicshop/wrappers/ctyping/etw_winapi/etw_functions.py +178 -49
  93. atomicshop/wrappers/ctyping/file_details_winapi.py +67 -0
  94. atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py +2 -1
  95. atomicshop/wrappers/ctyping/msi_windows_installer/extract_msi_main.py +2 -2
  96. atomicshop/wrappers/ctyping/setup_device.py +466 -0
  97. atomicshop/wrappers/ctyping/win_console.py +39 -0
  98. atomicshop/wrappers/dockerw/dockerw.py +113 -2
  99. atomicshop/wrappers/elasticsearchw/config_basic.py +0 -12
  100. atomicshop/wrappers/elasticsearchw/elastic_infra.py +75 -0
  101. atomicshop/wrappers/elasticsearchw/elasticsearchw.py +2 -20
  102. atomicshop/wrappers/factw/get_file_data.py +12 -5
  103. atomicshop/wrappers/factw/install/install_after_restart.py +89 -5
  104. atomicshop/wrappers/factw/install/pre_install_and_install_before_restart.py +20 -14
  105. atomicshop/wrappers/githubw.py +537 -54
  106. atomicshop/wrappers/loggingw/consts.py +1 -1
  107. atomicshop/wrappers/loggingw/filters.py +23 -0
  108. atomicshop/wrappers/loggingw/formatters.py +12 -0
  109. atomicshop/wrappers/loggingw/handlers.py +214 -107
  110. atomicshop/wrappers/loggingw/loggers.py +19 -0
  111. atomicshop/wrappers/loggingw/loggingw.py +860 -22
  112. atomicshop/wrappers/loggingw/reading.py +134 -112
  113. atomicshop/wrappers/mongodbw/mongo_infra.py +31 -0
  114. atomicshop/wrappers/mongodbw/mongodbw.py +1324 -36
  115. atomicshop/wrappers/netshw.py +271 -0
  116. atomicshop/wrappers/playwrightw/engine.py +34 -19
  117. atomicshop/wrappers/playwrightw/infra.py +5 -0
  118. atomicshop/wrappers/playwrightw/javascript.py +7 -3
  119. atomicshop/wrappers/playwrightw/keyboard.py +14 -0
  120. atomicshop/wrappers/playwrightw/scenarios.py +172 -5
  121. atomicshop/wrappers/playwrightw/waits.py +9 -7
  122. atomicshop/wrappers/powershell_networking.py +80 -0
  123. atomicshop/wrappers/psutilw/processes.py +37 -1
  124. atomicshop/wrappers/psutilw/psutil_networks.py +85 -0
  125. atomicshop/wrappers/pyopensslw.py +9 -2
  126. atomicshop/wrappers/pywin32w/cert_store.py +116 -0
  127. atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
  128. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +3 -105
  129. atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +3 -57
  130. atomicshop/wrappers/pywin32w/wmis/msft_netipaddress.py +113 -0
  131. atomicshop/wrappers/pywin32w/wmis/win32_networkadapterconfiguration.py +259 -0
  132. atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py +112 -0
  133. atomicshop/wrappers/pywin32w/wmis/wmi_helpers.py +236 -0
  134. atomicshop/wrappers/socketw/accepter.py +21 -7
  135. atomicshop/wrappers/socketw/certificator.py +216 -150
  136. atomicshop/wrappers/socketw/creator.py +190 -50
  137. atomicshop/wrappers/socketw/dns_server.py +491 -182
  138. atomicshop/wrappers/socketw/exception_wrapper.py +45 -52
  139. atomicshop/wrappers/socketw/process_getter.py +86 -0
  140. atomicshop/wrappers/socketw/receiver.py +144 -102
  141. atomicshop/wrappers/socketw/sender.py +65 -35
  142. atomicshop/wrappers/socketw/sni.py +334 -165
  143. atomicshop/wrappers/socketw/socket_base.py +134 -0
  144. atomicshop/wrappers/socketw/socket_client.py +137 -95
  145. atomicshop/wrappers/socketw/socket_server_tester.py +11 -7
  146. atomicshop/wrappers/socketw/socket_wrapper.py +717 -116
  147. atomicshop/wrappers/socketw/ssl_base.py +15 -14
  148. atomicshop/wrappers/socketw/statistics_csv.py +148 -17
  149. atomicshop/wrappers/sysmonw.py +1 -1
  150. atomicshop/wrappers/ubuntu_terminal.py +65 -26
  151. atomicshop/wrappers/win_auditw.py +189 -0
  152. atomicshop/wrappers/winregw/__init__.py +0 -0
  153. atomicshop/wrappers/winregw/winreg_installed_software.py +58 -0
  154. atomicshop/wrappers/winregw/winreg_network.py +232 -0
  155. {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/METADATA +31 -51
  156. atomicshop-3.10.5.dist-info/RECORD +306 -0
  157. {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/WHEEL +1 -1
  158. atomicshop/_basics_temp.py +0 -101
  159. atomicshop/a_installs/win/fibratus.py +0 -9
  160. atomicshop/a_installs/win/mongodb.py +0 -9
  161. atomicshop/a_installs/win/pycharm.py +0 -9
  162. atomicshop/addons/a_setup_scripts/install_psycopg2_ubuntu.sh +0 -3
  163. atomicshop/addons/a_setup_scripts/install_pywintrace_0.3.cmd +0 -2
  164. atomicshop/addons/mains/__pycache__/install_fibratus_windows.cpython-312.pyc +0 -0
  165. atomicshop/addons/mains/__pycache__/msi_unpacker.cpython-312.pyc +0 -0
  166. atomicshop/addons/mains/install_docker_rootless_ubuntu.py +0 -11
  167. atomicshop/addons/mains/install_docker_ubuntu_main_sudo.py +0 -11
  168. atomicshop/addons/mains/install_elastic_search_and_kibana_ubuntu.py +0 -10
  169. atomicshop/addons/mains/install_wsl_ubuntu_lts_admin.py +0 -9
  170. atomicshop/addons/package_setup/CreateWheel.cmd +0 -7
  171. atomicshop/addons/package_setup/Setup in Edit mode.cmd +0 -6
  172. atomicshop/addons/package_setup/Setup.cmd +0 -7
  173. atomicshop/archiver/_search_in_zip.py +0 -189
  174. atomicshop/archiver/archiver.py +0 -34
  175. atomicshop/archiver/search_in_archive.py +0 -250
  176. atomicshop/archiver/sevenz_app_w.py +0 -86
  177. atomicshop/archiver/sevenzs.py +0 -44
  178. atomicshop/archiver/zips.py +0 -293
  179. atomicshop/file_types.py +0 -24
  180. atomicshop/mitm/config_editor.py +0 -37
  181. atomicshop/mitm/engines/create_module_template_example.py +0 -13
  182. atomicshop/mitm/initialize_mitm_server.py +0 -268
  183. atomicshop/pbtkmultifile_argparse.py +0 -88
  184. atomicshop/permissions.py +0 -151
  185. atomicshop/script_as_string_processor.py +0 -38
  186. atomicshop/ssh_scripts/process_from_ipv4.py +0 -37
  187. atomicshop/ssh_scripts/process_from_port.py +0 -27
  188. atomicshop/wrappers/_process_wrapper_curl.py +0 -27
  189. atomicshop/wrappers/_process_wrapper_tar.py +0 -21
  190. atomicshop/wrappers/dockerw/install_docker.py +0 -209
  191. atomicshop/wrappers/elasticsearchw/infrastructure.py +0 -265
  192. atomicshop/wrappers/elasticsearchw/install_elastic.py +0 -232
  193. atomicshop/wrappers/ffmpegw.py +0 -125
  194. atomicshop/wrappers/fibratusw/install.py +0 -81
  195. atomicshop/wrappers/mongodbw/infrastructure.py +0 -53
  196. atomicshop/wrappers/mongodbw/install_mongodb.py +0 -190
  197. atomicshop/wrappers/msiw.py +0 -149
  198. atomicshop/wrappers/nodejsw/install_nodejs.py +0 -139
  199. atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
  200. atomicshop/wrappers/psutilw/networks.py +0 -45
  201. atomicshop/wrappers/pycharmw.py +0 -81
  202. atomicshop/wrappers/socketw/base.py +0 -59
  203. atomicshop/wrappers/socketw/get_process.py +0 -107
  204. atomicshop/wrappers/wslw.py +0 -191
  205. atomicshop-2.15.11.dist-info/RECORD +0 -302
  206. /atomicshop/{addons/mains → a_mains}/FACT/factw_fact_extractor_docker_image_main_sudo.py +0 -0
  207. /atomicshop/{addons → a_mains/addons}/PlayWrightCodegen.cmd +0 -0
  208. /atomicshop/{addons → a_mains/addons}/ScriptExecution.cmd +0 -0
  209. /atomicshop/{addons → a_mains/addons}/inits/init_to_import_all_modules.py +0 -0
  210. /atomicshop/{addons → a_mains/addons}/process_list/ReadMe.txt +0 -0
  211. /atomicshop/{addons → a_mains/addons}/process_list/compile.cmd +0 -0
  212. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.dll +0 -0
  213. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.exp +0 -0
  214. /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.lib +0 -0
  215. /atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +0 -0
  216. /atomicshop/{archiver → permissions}/__init__.py +0 -0
  217. /atomicshop/{wrappers/fibratusw → web_apis}/__init__.py +0 -0
  218. /atomicshop/wrappers/{nodejsw → pywin32w/wmis}/__init__.py +0 -0
  219. /atomicshop/wrappers/pywin32w/{wmi_win32process.py → wmis/win32process.py} +0 -0
  220. {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info/licenses}/LICENSE.txt +0 -0
  221. {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/top_level.txt +0 -0
@@ -1,29 +1,98 @@
1
1
  import statistics
2
+ from pathlib import Path
2
3
  from typing import Literal
4
+ import datetime
3
5
 
4
6
  from ...print_api import print_api
5
7
  from ...wrappers.loggingw import reading, consts
6
8
  from ...file_io import csvs
9
+ from ... import urls, filesystem
7
10
 
8
11
 
9
12
  def calculate_moving_average(
10
- file_path: str,
11
- moving_average_window_days,
12
- top_bottom_deviation_percentage: float,
13
+ file_path: str = None,
14
+ statistics_content: dict = None,
15
+ by_type: Literal['host', 'url'] = 'url',
16
+ moving_average_window_days: int = 5,
17
+ top_bottom_deviation_percentage: float = 0.25,
13
18
  get_deviation_for_last_day_only: bool = False,
19
+ get_deviation_for_date: str = None,
20
+ skip_total_count_less_than: int = None,
14
21
  print_kwargs: dict = None
15
22
  ) -> list:
16
23
  """
17
24
  This function calculates the moving average of the daily statistics.
18
25
 
19
26
  :param file_path: string, the path to the 'statistics.csv' file.
27
+ :param statistics_content: dict, the statistics content dictionary. If provided, 'file_path' will be ignored.
28
+ The dictionary should be in the format returned by 'get_all_files_content' function.
29
+ :param by_type: string, the type to calculate the moving average by. Can be 'host' or 'url'.
20
30
  :param moving_average_window_days: integer, the window size for the moving average.
21
31
  :param top_bottom_deviation_percentage: float, the percentage of deviation from the moving average to the top or
22
32
  bottom.
33
+ :param get_deviation_for_last_day_only: bool, check the 'get_all_files_content' function.
34
+ :param get_deviation_for_date: str, check the 'get_all_files_content' function.
35
+ :param skip_total_count_less_than: integer, if the total count is less than this number, skip the deviation.
36
+ :param print_kwargs: dict, the print_api arguments.
37
+ """
38
+
39
+ if not file_path and not statistics_content:
40
+ raise ValueError('Either file_path or statistics_content must be provided.')
41
+ if file_path and statistics_content:
42
+ raise ValueError('Only one of file_path or statistics_content must be provided.')
43
+
44
+ if get_deviation_for_last_day_only and get_deviation_for_date:
45
+ raise ValueError('Only one of get_deviation_for_last_day_only or get_deviation_for_date can be set.')
46
+
47
+ if not statistics_content:
48
+ statistics_content: dict = get_all_files_content(
49
+ file_path=file_path, moving_average_window_days=moving_average_window_days,
50
+ get_deviation_for_last_day_only=get_deviation_for_last_day_only,
51
+ get_deviation_for_date=get_deviation_for_date,
52
+ print_kwargs=print_kwargs)
53
+
54
+ for date_string, day_dict in statistics_content.items():
55
+ day_dict['content_no_useless'] = get_content_without_useless(day_dict['content'])
56
+
57
+ # Get the data dictionary from the statistics content.
58
+ day_dict['statistics_daily'] = compute_statistics_from_content(
59
+ day_dict['content_no_useless'], by_type)
60
+
61
+ moving_average_dict: dict = compute_moving_averages_from_average_statistics(
62
+ statistics_content,
63
+ moving_average_window_days
64
+ )
65
+
66
+ # Add the moving average to the statistics content.
67
+ for day, day_dict in statistics_content.items():
68
+ try:
69
+ day_dict['moving_average'] = moving_average_dict[day]
70
+ except KeyError:
71
+ day_dict['moving_average'] = {}
72
+
73
+ # Find deviation from the moving average to the bottom or top by specified percentage.
74
+ deviation_list: list = find_deviation_from_moving_average(
75
+ statistics_content, top_bottom_deviation_percentage, skip_total_count_less_than)
76
+
77
+ return deviation_list
78
+
79
+
80
+ def get_all_files_content(
81
+ file_path: str,
82
+ moving_average_window_days: int,
83
+ get_deviation_for_last_day_only: bool = False,
84
+ get_deviation_for_date: str = None,
85
+ print_kwargs: dict = None
86
+ ) -> dict:
87
+ """
88
+ Get the dictionary that will contain all the details of the file, like date, header and content, to prepare for the MA analysis.
89
+
90
+ :param file_path: string, the path to the 'statistics.csv' file.
91
+ :param moving_average_window_days: integer, the window size for the moving average.
23
92
  :param get_deviation_for_last_day_only: bool, if True, only the last day will be analyzed.
24
93
  Example: With 'moving_average_window_days=5', the last 6 days will be analyzed.
25
94
  5 days for moving average and the last day for deviation.
26
- File names example:
95
+ File names example the last day is 2021-01-06:
27
96
  statistics_2021-01-01.csv
28
97
  statistics_2021-01-02.csv
29
98
  statistics_2021-01-03.csv
@@ -33,61 +102,69 @@ def calculate_moving_average(
33
102
  Files 01 to 05 will be used for moving average and the file 06 for deviation.
34
103
  Meaning the average calculated for 2021-01-06 will be compared to the values moving average of 2021-01-01
35
104
  to 2021-01-05.
105
+ :param get_deviation_for_date: str, if set, the last day is considered the date that you set here.
106
+ The format should be the same as in the file names, e.g. 'YYYY-MM-DD'.
36
107
  :param print_kwargs: dict, the print_api arguments.
108
+ :return:
37
109
  """
38
110
 
39
- date_pattern: str = consts.DEFAULT_ROTATING_SUFFIXES_FROM_WHEN['midnight']
111
+ if get_deviation_for_last_day_only and get_deviation_for_date:
112
+ raise ValueError('Only one of get_deviation_for_last_day_only or get_deviation_for_date can be set.')
113
+
114
+ date_format: str = consts.DEFAULT_ROTATING_SUFFIXES_FROM_WHEN['midnight']
115
+
116
+ def is_valid_date(date_str: str) -> bool:
117
+ try:
118
+ datetime.datetime.strptime(date_str, date_format)
119
+ return True
120
+ except ValueError:
121
+ return False
40
122
 
41
123
  # Get all the file paths and their midnight rotations.
42
- logs_paths: list = reading.get_logs_paths(
124
+ logs_paths: list[filesystem.AtomicPath] = reading.get_logs_paths(
43
125
  log_file_path=file_path,
44
- date_pattern=date_pattern
126
+ date_format=date_format
45
127
  )
46
128
 
47
129
  if get_deviation_for_last_day_only:
48
130
  days_back_to_analyze: int = moving_average_window_days + 1
49
131
  logs_paths = logs_paths[-days_back_to_analyze:]
50
132
 
133
+ if get_deviation_for_date:
134
+ # Check if the date format is correct.
135
+ if not is_valid_date(get_deviation_for_date):
136
+ raise ValueError(f'Date [{get_deviation_for_date}] is not in the correct format: {date_format}')
137
+
138
+ # Find the index of the date in the logs_paths list.
139
+ date_index: int | None = None
140
+ for index, log_atomic_path in enumerate(logs_paths):
141
+ if log_atomic_path.datetime_string == get_deviation_for_date:
142
+ date_index = index
143
+ break
144
+
145
+ if date_index is None:
146
+ raise ValueError(f'Date {get_deviation_for_date} not found in the log files.')
147
+
148
+ start_index: int = max(0, date_index - moving_average_window_days)
149
+ logs_paths = logs_paths[start_index:date_index + 1]
150
+
51
151
  statistics_content: dict = {}
52
152
  # Read each file to its day.
53
- for log_path_dict in logs_paths:
54
- date_string = log_path_dict['date_string']
153
+ for log_atomic_path in logs_paths:
154
+ date_string: str = log_atomic_path.datetime_string
55
155
  statistics_content[date_string] = {}
56
156
 
57
- statistics_content[date_string]['file'] = log_path_dict
157
+ statistics_content[date_string]['file'] = log_atomic_path
58
158
 
59
159
  log_file_content, log_file_header = (
60
- csvs.read_csv_to_list_of_dicts_by_header(log_path_dict['file_path'], **(print_kwargs or {})))
160
+ csvs.read_csv_to_list_of_dicts_by_header(log_atomic_path.path, **(print_kwargs or {})))
61
161
  statistics_content[date_string]['content'] = log_file_content
62
162
  statistics_content[date_string]['header'] = log_file_header
63
163
 
64
- statistics_content[date_string]['content_no_errors'] = get_content_without_errors(log_file_content)
65
-
66
- # Get the data dictionary from the statistics content.
67
- statistics_content[date_string]['statistics_daily'] = compute_statistics_from_content(
68
- statistics_content[date_string]['content_no_errors']
69
- )
70
-
71
- moving_average_dict: dict = compute_moving_averages_from_average_statistics(
72
- statistics_content,
73
- moving_average_window_days
74
- )
75
-
76
- # Add the moving average to the statistics content.
77
- for day, day_dict in statistics_content.items():
78
- try:
79
- day_dict['moving_average'] = moving_average_dict[day]
80
- except KeyError:
81
- day_dict['moving_average'] = {}
82
-
83
- # Find deviation from the moving average to the bottom or top by specified percentage.
84
- deviation_list: list = find_deviation_from_moving_average(
85
- statistics_content, top_bottom_deviation_percentage)
86
-
87
- return deviation_list
164
+ return statistics_content
88
165
 
89
166
 
90
- def get_content_without_errors(content: list) -> list:
167
+ def get_content_without_useless(content: list) -> list:
91
168
  """
92
169
  This function gets the 'statistics.csv' file content without errors from the 'content' list.
93
170
 
@@ -98,7 +175,7 @@ def get_content_without_errors(content: list) -> list:
98
175
  traffic_statistics_without_errors: list = []
99
176
  for line in content:
100
177
  # Skip empty lines, headers and errors.
101
- if line['host'] == 'host' or line['command'] == '':
178
+ if line['host'] == 'host' or (line['request_size_bytes'] == '' and line['response_size_bytes'] == ''):
102
179
  continue
103
180
 
104
181
  traffic_statistics_without_errors.append(line)
@@ -106,19 +183,41 @@ def get_content_without_errors(content: list) -> list:
106
183
  return traffic_statistics_without_errors
107
184
 
108
185
 
109
- def get_data_dict_from_statistics_content(content: list) -> dict:
186
+ def get_data_dict_from_statistics_content(
187
+ content: list,
188
+ by_type: Literal['host', 'url']
189
+ ) -> dict:
110
190
  """
111
191
  This function gets the data dictionary from the 'statistics.csv' file content.
112
192
 
113
193
  :param content: list, the content list.
194
+ :param by_type: string, the type to calculate the moving average by. Can be 'host' or 'url'.
114
195
  :return: dict, the data dictionary.
115
196
  """
116
197
 
117
198
  hosts_requests_responses: dict = {}
118
199
  for line in content:
200
+ if by_type == 'host':
201
+ type_to_check: str = line['host']
202
+ elif by_type == 'url':
203
+ # Combine host and path to URL.
204
+ type_to_check: str = line['host'] + line['path']
205
+ # Remove the parameters from the URL.
206
+ url_parsed = urls.url_parser(type_to_check)
207
+
208
+ if url_parsed['file'] and Path(url_parsed['file']).suffix in ['.gz', '.gzip', '.zip']:
209
+ type_to_check = '/'.join(url_parsed['directories'][:-1])
210
+ else:
211
+ type_to_check = url_parsed['path']
212
+
213
+ # Remove the last slash from the URL.
214
+ type_to_check = type_to_check.removesuffix('/')
215
+ else:
216
+ raise ValueError(f'Invalid by_type: {by_type}')
217
+
119
218
  # If subdomain is not in the dictionary, add it.
120
- if line['host'] not in hosts_requests_responses:
121
- hosts_requests_responses[line['host']] = {
219
+ if type_to_check not in hosts_requests_responses:
220
+ hosts_requests_responses[type_to_check] = {
122
221
  'request_sizes': [],
123
222
  'response_sizes': []
124
223
  }
@@ -127,16 +226,13 @@ def get_data_dict_from_statistics_content(content: list) -> dict:
127
226
  try:
128
227
  request_size_bytes = line['request_size_bytes']
129
228
  response_size_bytes = line['response_size_bytes']
130
- if request_size_bytes == '':
131
- request_size_bytes = '0'
132
- if response_size_bytes == '':
133
- response_size_bytes = '0'
134
-
135
- hosts_requests_responses[line['host']]['request_sizes'].append(int(request_size_bytes))
136
- hosts_requests_responses[line['host']]['response_sizes'].append(int(response_size_bytes))
137
- except ValueError:
229
+ if request_size_bytes != '':
230
+ hosts_requests_responses[type_to_check]['request_sizes'].append(int(request_size_bytes))
231
+ if response_size_bytes != '':
232
+ hosts_requests_responses[type_to_check]['response_sizes'].append(int(response_size_bytes))
233
+ except ValueError as e:
138
234
  print_api(line, color='yellow')
139
- raise
235
+ raise e
140
236
 
141
237
  return hosts_requests_responses
142
238
 
@@ -150,31 +246,37 @@ def compute_statistics_from_data_dict(data_dict: dict):
150
246
  """
151
247
 
152
248
  for host, host_dict in data_dict.items():
153
- count = len(host_dict['request_sizes'])
154
- avg_request_size = statistics.mean(host_dict['request_sizes']) if count > 0 else 0
155
- median_request_size = statistics.median(host_dict['request_sizes']) if count > 0 else 0
156
- avg_response_size = statistics.mean(host_dict['response_sizes']) if count > 0 else 0
157
- median_response_size = statistics.median(host_dict['response_sizes']) if count > 0 else 0
158
-
159
- data_dict[host]['count'] = count
249
+ count_requests = len(host_dict['request_sizes'])
250
+ count_responses = len(host_dict['response_sizes'])
251
+ avg_request_size = statistics.mean(host_dict['request_sizes']) if count_requests > 0 else 0
252
+ median_request_size = statistics.median(host_dict['request_sizes']) if count_requests > 0 else 0
253
+ avg_response_size = statistics.mean(host_dict['response_sizes']) if count_responses > 0 else 0
254
+ median_response_size = statistics.median(host_dict['response_sizes']) if count_responses > 0 else 0
255
+
256
+ data_dict[host]['count_requests'] = count_requests
257
+ data_dict[host]['count_responses'] = count_responses
160
258
  data_dict[host]['avg_request_size'] = avg_request_size
161
259
  data_dict[host]['median_request_size'] = median_request_size
162
260
  data_dict[host]['avg_response_size'] = avg_response_size
163
261
  data_dict[host]['median_response_size'] = median_response_size
164
262
 
165
263
 
166
- def compute_statistics_from_content(content: list):
264
+ def compute_statistics_from_content(
265
+ content: list,
266
+ by_type: Literal['host', 'url']
267
+ ):
167
268
  """
168
269
  This function computes the statistics from the 'statistics.csv' file content.
169
270
 
170
271
  :param content: list, the content list.
272
+ :param by_type: string, the type to calculate the moving average by. Can be 'host' or 'url'.
171
273
  :return: dict, the statistics dictionary.
172
274
  """
173
275
 
174
- hosts_requests_responses: dict = get_data_dict_from_statistics_content(content)
175
- compute_statistics_from_data_dict(hosts_requests_responses)
276
+ requests_responses: dict = get_data_dict_from_statistics_content(content, by_type)
277
+ compute_statistics_from_data_dict(requests_responses)
176
278
 
177
- return hosts_requests_responses
279
+ return requests_responses
178
280
 
179
281
 
180
282
  def compute_moving_averages_from_average_statistics(
@@ -197,15 +299,18 @@ def compute_moving_averages_from_average_statistics(
197
299
 
198
300
  # Create list of the last 'moving_average_window_days' days, including the current day.
199
301
  last_x_window_days_content_list = (
200
- list(average_statistics_dict.values()))[current_day-moving_average_window_days:current_day]
302
+ list(average_statistics_dict.values()))[current_day - moving_average_window_days:current_day]
201
303
 
202
304
  # Compute the moving averages.
203
- moving_average[day] = compute_average_for_current_day_from_past_x_days(last_x_window_days_content_list)
305
+ moving_average[day] = compute_average_for_current_day_from_past_x_days(
306
+ last_x_window_days_content_list)
204
307
 
205
308
  return moving_average
206
309
 
207
310
 
208
- def compute_average_for_current_day_from_past_x_days(previous_days_content_list: list) -> dict:
311
+ def compute_average_for_current_day_from_past_x_days(
312
+ previous_days_content_list: list
313
+ ) -> dict:
209
314
  """
210
315
  This function computes the average for the current day from the past x days.
211
316
 
@@ -219,29 +324,48 @@ def compute_average_for_current_day_from_past_x_days(previous_days_content_list:
219
324
  for host, host_dict in statistics_daily.items():
220
325
  if host not in moving_average:
221
326
  moving_average[host] = {
222
- 'counts': [],
327
+ 'all_request_counts': [],
328
+ 'all_response_counts': [],
223
329
  'avg_request_sizes': [],
224
330
  'avg_response_sizes': [],
331
+ 'median_request_sizes': [],
332
+ 'median_response_sizes': []
225
333
  }
226
334
 
227
- moving_average[host]['counts'].append(int(host_dict['count']))
335
+ moving_average[host]['all_request_counts'].append(int(host_dict['count_requests']))
336
+ moving_average[host]['all_response_counts'].append(int(host_dict['count_responses']))
228
337
  moving_average[host]['avg_request_sizes'].append(float(host_dict['avg_request_size']))
229
338
  moving_average[host]['avg_response_sizes'].append(float(host_dict['avg_response_size']))
339
+ moving_average[host]['median_request_sizes'].append(float(host_dict['median_request_size']))
340
+ moving_average[host]['median_response_sizes'].append(float(host_dict['median_response_size']))
230
341
 
231
342
  # Compute the moving average.
232
343
  moving_average_results: dict = {}
233
344
  for host, host_dict in moving_average.items():
234
- ma_count = statistics.mean(host_dict['counts'])
345
+ ma_request_count = statistics.mean(host_dict['all_request_counts'])
346
+ ma_response_count = statistics.mean(host_dict['all_response_counts'])
235
347
  ma_request_size = statistics.mean(host_dict['avg_request_sizes'])
236
348
  ma_response_size = statistics.mean(host_dict['avg_response_sizes'])
349
+ mm_request_count = statistics.median(host_dict['all_request_counts'])
350
+ mm_response_count = statistics.median(host_dict['all_response_counts'])
351
+ mm_request_size = statistics.median(host_dict['median_request_sizes'])
352
+ mm_response_size = statistics.median(host_dict['median_response_sizes'])
237
353
 
238
354
  moving_average_results[host] = {
239
- 'ma_count': ma_count,
355
+ 'ma_request_count': ma_request_count,
356
+ 'ma_response_count': ma_response_count,
240
357
  'ma_request_size': ma_request_size,
241
358
  'ma_response_size': ma_response_size,
242
- 'counts': host_dict['counts'],
359
+ 'mm_request_count': mm_request_count,
360
+ 'mm_response_count': mm_response_count,
361
+ 'mm_request_size': mm_request_size,
362
+ 'mm_response_size': mm_response_size,
363
+ 'all_request_counts': host_dict['all_request_counts'],
364
+ 'all_response_counts': host_dict['all_response_counts'],
243
365
  'avg_request_sizes': host_dict['avg_request_sizes'],
244
- 'avg_response_sizes': host_dict['avg_response_sizes']
366
+ 'avg_response_sizes': host_dict['avg_response_sizes'],
367
+ 'median_request_sizes': host_dict['median_request_sizes'],
368
+ 'median_response_sizes': host_dict['median_response_sizes']
245
369
  }
246
370
 
247
371
  return moving_average_results
@@ -249,7 +373,8 @@ def compute_average_for_current_day_from_past_x_days(previous_days_content_list:
249
373
 
250
374
  def find_deviation_from_moving_average(
251
375
  statistics_content: dict,
252
- top_bottom_deviation_percentage: float
376
+ top_bottom_deviation_percentage: float,
377
+ skip_total_count_less_than: int = None
253
378
  ) -> list:
254
379
  """
255
380
  This function finds the deviation from the moving average to the bottom or top by specified percentage.
@@ -257,12 +382,13 @@ def find_deviation_from_moving_average(
257
382
  :param statistics_content: dict, the statistics content dictionary.
258
383
  :param top_bottom_deviation_percentage: float, the percentage of deviation from the moving average to the top or
259
384
  bottom.
385
+ :param skip_total_count_less_than: integer, if the total count is less than this number, skip the deviation.
260
386
  :return: list, the deviation list.
261
387
  """
262
388
 
263
389
  def _check_deviation(
264
- check_type: Literal['count', 'avg_request_size', 'avg_response_size'],
265
- ma_check_type: Literal['ma_count', 'ma_request_size', 'ma_response_size'],
390
+ check: Literal['count', 'avg'],
391
+ traffic_direction: Literal['request', 'response'],
266
392
  day_statistics_content_dict: dict,
267
393
  moving_averages_dict: dict
268
394
  ):
@@ -272,6 +398,19 @@ def find_deviation_from_moving_average(
272
398
 
273
399
  nonlocal message
274
400
 
401
+ if check == 'count':
402
+ check_type = f'{check}_{traffic_direction}s'
403
+ ma_check_type = f'ma_{traffic_direction}_{check}'
404
+ median_type_string = check_type
405
+ moving_median_type_string = f'mm_{traffic_direction}_{check}'
406
+ elif check == 'avg':
407
+ check_type = f'{check}_{traffic_direction}_size'
408
+ ma_check_type = f'ma_{traffic_direction}_size'
409
+ median_type_string = f'median_{traffic_direction}_size'
410
+ moving_median_type_string = f'mm_{traffic_direction}_size'
411
+ else:
412
+ raise ValueError(f'Invalid check: {check}')
413
+
275
414
  host_moving_average_by_type = moving_averages_dict[host][ma_check_type]
276
415
  check_type_moving_by_percent = (
277
416
  host_moving_average_by_type * top_bottom_deviation_percentage)
@@ -280,29 +419,59 @@ def find_deviation_from_moving_average(
280
419
 
281
420
  deviation_type = None
282
421
  deviation_percentage = None
422
+ error_message: str = str()
283
423
  if day_statistics_content_dict[check_type] > check_type_moving_above:
284
424
  deviation_type = 'above'
285
- deviation_percentage = (
286
- (day_statistics_content_dict[check_type] - host_moving_average_by_type) /
287
- host_moving_average_by_type)
425
+ try:
426
+ deviation_percentage = (
427
+ (day_statistics_content_dict[check_type] - host_moving_average_by_type) /
428
+ host_moving_average_by_type)
429
+ except ZeroDivisionError as e:
430
+ error_message = f' | Error: Division by 0, host_moving_average_by_type: {host_moving_average_by_type}'
288
431
  elif day_statistics_content_dict[check_type] < check_type_moving_below:
289
432
  deviation_type = 'below'
290
433
  deviation_percentage = (
291
434
  (host_moving_average_by_type - day_statistics_content_dict[check_type]) /
292
435
  host_moving_average_by_type)
436
+
293
437
  if deviation_type:
294
- message = f'[{check_type}] is [{deviation_type}] the moving average.'
438
+ message = f'[{check_type}] is [{deviation_type}] the moving average.' + error_message
439
+
440
+ # The median and the total count are None for the count, Since they are the count.
441
+ if 'count' in check_type:
442
+ total_entries_averaged = None
443
+ median_size = None
444
+ else:
445
+ total_entries_averaged = day_statistics_content_dict[f'count_{traffic_direction}s']
446
+ median_size = day_statistics_content_dict[median_type_string]
447
+
448
+ value = day_statistics_content_dict[check_type]
449
+
450
+ # If the total count is less than the specified number, skip the deviation.
451
+ if skip_total_count_less_than:
452
+ if total_entries_averaged:
453
+ if total_entries_averaged < skip_total_count_less_than:
454
+ return
455
+ else:
456
+ if value < skip_total_count_less_than:
457
+ return
458
+
459
+ moving_median_size = moving_averages_dict[host][moving_median_type_string]
460
+
295
461
  deviation_list.append({
296
462
  'day': day,
297
463
  'host': host,
298
464
  'message': message,
299
- 'value': day_statistics_content_dict[check_type],
465
+ 'value': value,
300
466
  'ma_value': host_moving_average_by_type,
301
467
  'check_type': check_type,
302
468
  'percentage': top_bottom_deviation_percentage,
303
469
  'ma_value_checked': check_type_moving_above,
304
470
  'deviation_percentage': deviation_percentage,
471
+ 'total_entries_averaged': total_entries_averaged,
305
472
  'deviation_type': deviation_type,
473
+ 'median_size': median_size,
474
+ 'mm_size': moving_median_size,
306
475
  'data': day_statistics_content_dict,
307
476
  'ma_data': moving_averages_dict[host]
308
477
  })
@@ -313,7 +482,8 @@ def find_deviation_from_moving_average(
313
482
  if day_index == 0:
314
483
  previous_day_moving_average_dict = {}
315
484
  else:
316
- previous_day_moving_average_dict = list(statistics_content.values())[day_index-1].get('moving_average', {})
485
+ previous_day_moving_average_dict = list(statistics_content.values())[day_index - 1].get('moving_average',
486
+ {})
317
487
 
318
488
  # If there is no moving average for previous day continue to the next day.
319
489
  if not previous_day_moving_average_dict:
@@ -327,17 +497,29 @@ def find_deviation_from_moving_average(
327
497
  deviation_list.append({
328
498
  'day': day,
329
499
  'host': host,
330
- 'data': host_dict,
331
500
  'message': message,
332
- 'type': 'clear'
501
+ 'value': None,
502
+ 'ma_value': None,
503
+ 'check_type': None,
504
+ 'percentage': None,
505
+ 'ma_value_checked': None,
506
+ 'deviation_percentage': None,
507
+ 'total_entries_averaged': None,
508
+ 'deviation_type': 'clear',
509
+ 'median_size': None,
510
+ 'mm_size': None,
511
+ 'data': host_dict,
512
+ 'ma_data': previous_day_moving_average_dict
333
513
  })
334
514
  continue
335
515
 
336
516
  _check_deviation(
337
- 'count', 'ma_count', host_dict, previous_day_moving_average_dict)
517
+ 'count', 'request', host_dict, previous_day_moving_average_dict)
518
+ _check_deviation(
519
+ 'count', 'response', host_dict, previous_day_moving_average_dict)
338
520
  _check_deviation(
339
- 'avg_request_size', 'ma_request_size', host_dict, previous_day_moving_average_dict)
521
+ 'avg', 'request', host_dict, previous_day_moving_average_dict)
340
522
  _check_deviation(
341
- 'avg_response_size', 'ma_response_size', host_dict, previous_day_moving_average_dict)
523
+ 'avg', 'response', host_dict, previous_day_moving_average_dict)
342
524
 
343
525
  return deviation_list
@@ -25,7 +25,7 @@ class DnsCheck:
25
25
 
26
26
  self.fetch_engine: trace_dns.DnsRequestResponseTrace = (
27
27
  trace_dns.DnsRequestResponseTrace(
28
- attrs=['name', 'cmdline', 'domain', 'query_type'],
28
+ attrs=['name', 'cmdline', 'query', 'query_type'],
29
29
  session_name=self.etw_session_name,
30
30
  close_existing_session_name=True,
31
31
  skip_record_list=self.settings['skip_record_list'],