atomicshop 2.4.0__py3-none-any.whl → 2.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.

Potentially problematic release.


This version of atomicshop might be problematic. Click here for more details.

atomicshop/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  """Atomic Basic functions and classes to make developer life easier"""
2
2
 
3
3
  __author__ = "Den Kras"
4
- __version__ = '2.4.0'
4
+ __version__ = '2.4.1'
@@ -1,15 +1,18 @@
1
1
  import json
2
+ from typing import Union
2
3
 
3
4
  from .file_io import read_file_decorator, write_file_decorator
4
5
 
5
6
 
6
7
  # noinspection PyUnusedLocal
7
8
  @read_file_decorator
8
- def read_json_file(file_path: str,
9
- file_mode: str = 'r',
10
- encoding=None,
11
- file_object=None,
12
- **kwargs) -> dict:
9
+ def read_json_file(
10
+ file_path: str,
11
+ file_mode: str = 'r',
12
+ encoding=None,
13
+ file_object=None,
14
+ **kwargs
15
+ ) -> dict:
13
16
  """
14
17
  Read the json file and return its content as dictionary.
15
18
 
@@ -27,13 +30,15 @@ def read_json_file(file_path: str,
27
30
 
28
31
  # noinspection PyUnusedLocal
29
32
  @write_file_decorator
30
- def write_json_file(json_content,
31
- file_path: str,
32
- file_mode: str = 'w',
33
- indent=None,
34
- use_default_indent=False,
35
- file_object=None,
36
- **kwargs) -> None:
33
+ def write_json_file(
34
+ json_content: Union[list, dict, str],
35
+ file_path: str,
36
+ file_mode: str = 'w',
37
+ indent=None,
38
+ use_default_indent=False,
39
+ file_object=None,
40
+ **kwargs
41
+ ) -> None:
37
42
  """
38
43
  Export list or dict to json file. If indent specified, the content will be beautified by the number of spaces
39
44
  specified in 'indent' integer.
@@ -69,3 +74,25 @@ def write_json_file(json_content,
69
74
  # If so, write it to file as regular string.
70
75
  # Getting the 'file_object' from the 'write_file_decorator'.
71
76
  file_object.write(json_content)
77
+
78
+
79
+ def convert_dict_to_json_string(
80
+ dict_or_list: Union[dict, list],
81
+ indent=None,
82
+ use_default_indent=False) -> str:
83
+ """
84
+ Convert dictionary or list of dictionaries to json formatted string.
85
+
86
+ :param dict_or_list: dictionary or list of dictionaries to convert.
87
+ :param indent: integer number of spaces for indentation.
88
+ If 'ident=0' new lines still will be created. The most compact is 'indent=None' (from documentation)
89
+ So, using default as 'None' and not something else.
90
+ :param use_default_indent: boolean. Default indent for 'json' format in many places is '2'. So, if you don't want
91
+ to set 'indent=2', just set this to 'True'.
92
+ :return: json formatted string.
93
+ """
94
+
95
+ if use_default_indent:
96
+ indent = 2
97
+
98
+ return json.dumps(dict_or_list, indent=indent)
@@ -1,7 +1,8 @@
1
1
  import ipaddress
2
+ from typing import Union, Literal
2
3
 
3
4
 
4
- def is_ip_address(string_value: str, ip_type: str = None) -> bool:
5
+ def is_ip_address(string_value: str, ip_type: Union[Literal['ipv4', 'ipv6'], None] = None) -> bool:
5
6
  """
6
7
  The function checks if the string is an IPv4 or IPv6 address.
7
8
 
atomicshop/urls.py CHANGED
@@ -1,5 +1,3 @@
1
- # v1.0.0
2
- # Basic imports.
3
1
  from urllib.parse import urlparse
4
2
 
5
3
 
@@ -3,3 +3,4 @@ FIRMWARE_ENDPOINT: str = '/rest/firmware'
3
3
  FILE_OBJECT_ENDPOINT: str = '/rest/file_object'
4
4
  STATUS_ENDPOINT: str = '/rest/status'
5
5
  STATISTICS_ENDPOINT: str = '/rest/statistics'
6
+ BINARY_SEARCH_ENDPOINT: str = '/rest/binary_search'
@@ -0,0 +1,29 @@
1
+ # noinspection PyPackageRequirements
2
+ import requests
3
+
4
+ from . import fact_config
5
+ from ... print_api import print_api
6
+
7
+
8
+ def search_string(string_to_search: str):
9
+ """
10
+ Get the binaries by searching for a string.
11
+ :return:
12
+ """
13
+
14
+ yara_rule = {
15
+ "rule_file": "rule rulename {strings: $a = \"" + string_to_search + "\" condition: $a }"
16
+ }
17
+
18
+ url: str = f'{fact_config.FACT_ADDRESS}{fact_config.BINARY_SEARCH_ENDPOINT}'
19
+ response: requests.Response = requests.get(url, json=yara_rule)
20
+
21
+ # Check response status code.
22
+ if response.status_code == 200:
23
+ # Print response.
24
+ print_api(response.json())
25
+ else:
26
+ # Print error.
27
+ print_api('Error: ' + str(response.status_code), error_type=True, logger_method='critical')
28
+
29
+ return response
@@ -2,14 +2,34 @@
2
2
  import requests
3
3
 
4
4
  from . import fact_config, get_file_data
5
- from ... print_api import print_status_of_list
5
+ from ... print_api import print_status_of_list, print_api
6
6
 
7
7
 
8
- def is_uid_exist(uid: str):
8
+ def get_all_file_objects():
9
9
  """
10
- Check if the specified FACT UID exists in the FILE_OBJECT database.
10
+ Get all file_object UIDs from the database.
11
+ :return:
12
+ """
13
+
14
+ url: str = f'{fact_config.FACT_ADDRESS}{fact_config.FILE_OBJECT_ENDPOINT}'
15
+ response: requests.Response = requests.get(url)
16
+
17
+ # Check response status code.
18
+ if response.status_code == 200:
19
+ # Print response.
20
+ print_api(response.json())
21
+ else:
22
+ # Print error.
23
+ print_api('Error: ' + str(response.status_code), error_type=True, logger_method='critical')
24
+
25
+ return response
26
+
27
+
28
+ def get_uid_data(uid: str):
29
+ """
30
+ Get file_object data by UID.
11
31
  :param uid: string, FACT UID.
12
- :return: boolean, True if exists, False if not.
32
+ :return:
13
33
  """
14
34
 
15
35
  url: str = f'{fact_config.FACT_ADDRESS}{fact_config.FILE_OBJECT_ENDPOINT}/{uid}'
@@ -17,6 +37,19 @@ def is_uid_exist(uid: str):
17
37
 
18
38
  # Check response status code.
19
39
  if response.status_code == 200:
40
+ return response.json()
41
+ else:
42
+ return None
43
+
44
+
45
+ def is_uid_exist(uid: str):
46
+ """
47
+ Check if the specified FACT UID exists in the FILE_OBJECT database.
48
+ :param uid: string, FACT UID.
49
+ :return: boolean, True if exists, False if not.
50
+ """
51
+
52
+ if get_uid_data(uid):
20
53
  return True
21
54
  else:
22
55
  return False
@@ -2,11 +2,112 @@
2
2
  import requests
3
3
  import base64
4
4
  import time
5
+ from typing import Union
6
+ import os
5
7
 
6
8
  from . import fact_config, get_file_data, rest_file_object
7
- from ... print_api import print_api, print_status_of_list
8
- from ... file_io import file_io
9
- from ... import filesystem
9
+ from ...print_api import print_api, print_status_of_list
10
+ from ...file_io import file_io, jsons, csvs
11
+ from ...basics import dicts
12
+ from ... import filesystem, ip_addresses
13
+
14
+
15
+ def get_uid_list(
16
+ config_data: dict,
17
+ query: Union[dict, str] = None,
18
+ url_parameters: dict = None,
19
+ fetch_uid_data: bool = False
20
+ ):
21
+ """
22
+ Get firmware UIDs by query.
23
+ :param config_data: dict, of Parameters to pass for REST API.
24
+ If query is specified, this parameter is ignored.
25
+ :param query: string, query.
26
+ Example:
27
+ {$and: [{"device_name": "test"}, {"device_name": "test222"}]}
28
+ Info:
29
+ Return UIDs of all firmwares with device_name "test" and device_name "test222".
30
+
31
+ Example2:
32
+ {"vendor": "AVM"}
33
+ Info:
34
+ Return UIDs of all firmwares with vendor "AVM".
35
+
36
+ Operators:
37
+ $and: AND operator.
38
+ $or: OR operator.
39
+ $ne: NOT EQUAL operator.
40
+ $like: LIKE operator.
41
+ $in: IN operator.
42
+ &lt: LESS THAN operator.
43
+ $gt: GREATER THAN operator.
44
+ $exists: EXISTS operator.
45
+ $regex: REGEX operator.
46
+ $contains: CONTAINS operator.
47
+ Basically all the operators that are supported by the MongoDB.
48
+
49
+ :param url_parameters: dict, of URL parameters. Available parameters:
50
+ {
51
+ 'limit': int - limit of results,
52
+ 'offset': int - offset of results (paging),
53
+ 'recursive': boolean - recursive search - only with query,
54
+ 'inverted': boolean - inverted search - only with query and recursive
55
+ }
56
+
57
+ :param fetch_uid_data: boolean, get data of the UIDs. This can take time, since each UID will be queried against the
58
+ database. Default is False.
59
+ :return: list, list of UIDs.
60
+ """
61
+
62
+ url: str = f'{fact_config.FACT_ADDRESS}{fact_config.FIRMWARE_ENDPOINT}'
63
+
64
+ if query is None:
65
+ if 'requested_analysis_systems' in config_data:
66
+ dicts.remove_keys(config_data, ['requested_analysis_systems'])
67
+
68
+ query = config_data
69
+
70
+ if isinstance(query, dict):
71
+ query = jsons.convert_dict_to_json_string(query)
72
+ elif isinstance(query, str):
73
+ pass
74
+ else:
75
+ raise TypeError(f'Query must be dict or string, not {type(query)}')
76
+
77
+ # If there are parameters to add to the URL, add the '?' to the URL.
78
+ if url_parameters or query:
79
+ url = f'{url}?'
80
+
81
+ # Add parameters to the URL.
82
+ if url_parameters and query:
83
+ for key, value in url_parameters.items():
84
+ url = f'{url}{key}={str(value)}&'
85
+ url = f'{url}query={query}'
86
+ if url_parameters and not query:
87
+ for parameter_index, (key, value) in enumerate(url_parameters.items()):
88
+ url = f'{url}{key}={str(value)}'
89
+ if parameter_index < len(url_parameters) - 1:
90
+ url = f'{url}&'
91
+ elif query and not url_parameters:
92
+ url = f'{url}query={query}'
93
+
94
+ response: requests.Response = requests.get(url)
95
+
96
+ uids: list = list()
97
+ # Check response status code.
98
+ if response.status_code == 200:
99
+ uids: list = response.json()['uids']
100
+ # Print response.
101
+ # print_api(response.json())
102
+ print_api(f'Found {len(uids)} UIDs.')
103
+ else:
104
+ # Print error.
105
+ print_api('Error: ' + str(response.status_code), error_type=True, logger_method='critical')
106
+
107
+ if fetch_uid_data:
108
+ return get_uid_list_data(uids)
109
+ else:
110
+ return uids
10
111
 
11
112
 
12
113
  def is_analysis_finished(uid: str) -> bool:
@@ -177,11 +278,11 @@ def upload_files(directory_path: str, json_data: dict):
177
278
  return None
178
279
 
179
280
 
180
- def is_uid_exist(uid: str):
281
+ def get_uid_data(uid: str):
181
282
  """
182
- Check if the specified FACT UID exists in the FIRMWARE database.
283
+ Get firmware data by UID.
183
284
  :param uid: string, FACT UID.
184
- :return: boolean, True if exists, False if not.
285
+ :return:
185
286
  """
186
287
 
187
288
  url: str = f'{fact_config.FACT_ADDRESS}{fact_config.FIRMWARE_ENDPOINT}/{uid}'
@@ -189,6 +290,35 @@ def is_uid_exist(uid: str):
189
290
 
190
291
  # Check response status code.
191
292
  if response.status_code == 200:
293
+ return response.json()
294
+ else:
295
+ return None
296
+
297
+
298
+ def get_uid_list_data(uid_list: list):
299
+ """
300
+ Get firmware data for each UID in the list.
301
+ :param uid_list: list, list of FACT UIDs.
302
+ :return:
303
+ """
304
+
305
+ uid_data_list: list = list()
306
+ for uid_index, uid in enumerate(uid_list):
307
+ print_status_of_list(
308
+ list_instance=uid_list, prefix_string='Getting UID Data: ', current_state=(uid_index + 1))
309
+ uid_data_list.append(get_uid_data(uid))
310
+
311
+ return uid_data_list
312
+
313
+
314
+ def is_uid_exist(uid: str):
315
+ """
316
+ Check if the specified FACT UID exists in the FIRMWARE database.
317
+ :param uid: string, FACT UID.
318
+ :return: boolean, True if exists, False if not.
319
+ """
320
+
321
+ if get_uid_data(uid):
192
322
  return True
193
323
  else:
194
324
  return False
@@ -245,3 +375,141 @@ def is_firmware_exist(directory_path: str, firmwares: list = None) -> list:
245
375
  raise ValueError(message)
246
376
 
247
377
  return firmwares
378
+
379
+
380
+ def find_analysis_recursively(uid: str, object_path: str = str()):
381
+ """
382
+ The function will find the analysis information like cve, OS type, etc. recursively and return it to
383
+ the asker firmware.
384
+
385
+ :param uid: string of the uid of the file_object.
386
+ :param object_path: string of the path of the file_object. Can be empty on the first iteration since it's the same
387
+ UID as the first asker function.
388
+ :return:
389
+ """
390
+
391
+ found_info: dict = dict()
392
+
393
+ # Get the data of the firmware.
394
+ file_data: dict = rest_file_object.get_uid_data(uid)
395
+
396
+ # Add current path to the object path to create a full path of the current object.
397
+ current_path: str = object_path + file_data['file_object']['meta_data']['hid']
398
+ print_api(f"Current File: {current_path}", print_end='\r')
399
+
400
+ cve_lookup_result: dict = file_data['file_object']['analysis']['cve_lookup']['result']
401
+ ip_and_uri_finder_result: dict = file_data['file_object']['analysis']['ip_and_uri_finder']['result']
402
+ software_components_result: dict = file_data['file_object']['analysis']['software_components']['result']
403
+
404
+ if cve_lookup_result and 'skipped' not in cve_lookup_result.keys():
405
+ if cve_lookup_result['cve_results']:
406
+ found_info['cve_lookup'] = file_data['file_object']['analysis']['cve_lookup']['result']
407
+ if ip_and_uri_finder_result and 'skipped' not in ip_and_uri_finder_result.keys():
408
+ if ip_and_uri_finder_result['ips_v4'] or ip_and_uri_finder_result['ips_v6'] or ip_and_uri_finder_result['uris']:
409
+ found_info['ip_and_uri_finder'] = file_data['file_object']['analysis']['ip_and_uri_finder']['result']
410
+ if software_components_result and 'skipped' not in software_components_result.keys():
411
+ found_info['software_components'] = file_data['file_object']['analysis']['software_components']['result']
412
+
413
+ if found_info:
414
+ found_info['path'] = current_path
415
+ found_files: list = [found_info]
416
+ else:
417
+ found_files: list = list()
418
+
419
+ for included_file_uid in file_data['file_object']['meta_data']['included_files']:
420
+ # Get the data of the included file.
421
+ included_found_files = find_analysis_recursively(included_file_uid, (
422
+ object_path + file_data['file_object']['meta_data']['hid']))
423
+
424
+ if included_found_files:
425
+ found_files.extend(included_found_files)
426
+
427
+ return found_files
428
+
429
+
430
+ def save_firmware_uids_as_csv(
431
+ directory_path: str,
432
+ config_data: dict = None,
433
+ query: Union[dict, str] = None,
434
+ url_parameters: dict = None,
435
+ get_analysis_data: bool = False
436
+ ):
437
+ """
438
+ Save firmware UIDs as CSV file.
439
+ :param directory_path: string, path to save the CSV file.
440
+ :param config_data: check get_uid_list() for more info.
441
+ :param query: check get_uid_list() for more info.
442
+ :param url_parameters: check get_uid_list() for more info.
443
+ :param get_analysis_data: boolean. If 'True', the function will get the analysis data of each file that is found
444
+ in the firmware results. This is needed in order to determine if the firmware is vulnerable to CVEs, OS type,
445
+ etc. Default is 'False'.
446
+ NOTE: This can take a lot of time, since each internal file will be queried against the database.
447
+
448
+ :return:
449
+ """
450
+
451
+ uids: list = get_uid_list(
452
+ config_data=config_data, query=query, url_parameters=url_parameters, fetch_uid_data=True)
453
+
454
+ export_list: list = list()
455
+ for uid_index, uid in enumerate(uids):
456
+ print_status_of_list(
457
+ list_instance=uids, prefix_string='Checking UID for analysis items: ', current_state=(uid_index + 1),
458
+ same_line=False)
459
+ export_entry: dict = dict()
460
+ for key, value in uid['firmware']['meta_data'].items():
461
+ if key == 'included_files' or key == 'total_files_in_firmware':
462
+ continue
463
+ export_entry[key] = value
464
+
465
+ export_entry['mime'] = uid['firmware']['analysis']['file_type']['result']['mime']
466
+ export_entry['sha256'] = uid['firmware']['analysis']['file_hashes']['result']['sha256']
467
+ export_entry['uid'] = uid['request']['uid']
468
+
469
+ # Check for CVEs and other info recursively.
470
+ if get_analysis_data:
471
+ analysis_data_list = find_analysis_recursively(uid['request']['uid'])
472
+ else:
473
+ analysis_data_list = list()
474
+
475
+ export_entry['urls_ips']: list = list()
476
+ export_entry['cves']: list = list()
477
+ export_entry['software']: list = list()
478
+
479
+ for analysis_data in analysis_data_list:
480
+ if 'cve_lookup' in analysis_data:
481
+ for key, value in analysis_data['cve_lookup']['cve_results'].items():
482
+ export_entry['cves'] = [key, jsons.convert_dict_to_json_string(value)]
483
+ if 'software_components' in analysis_data:
484
+ for key, value in analysis_data['software_components'].items():
485
+ export_entry['software'] = [key, jsons.convert_dict_to_json_string(value['meta'])]
486
+ if 'ip_and_uri_finder' in analysis_data:
487
+ for key, value in analysis_data['ip_and_uri_finder'].items():
488
+ if not value:
489
+ continue
490
+
491
+ if key == 'ips_v4':
492
+ for ipv4s in analysis_data['ip_and_uri_finder']['ips_v4']:
493
+ for ip_address in ipv4s:
494
+ if ip_addresses.is_ip_address(ip_address, ip_type='ipv4'):
495
+ if ip_address not in export_entry['urls_ips']:
496
+ export_entry['urls_ips'].append(ip_address)
497
+ elif key == 'ips_v6':
498
+ for ipv6s in analysis_data['ip_and_uri_finder']['ips_v6']:
499
+ for ip_address in ipv6s:
500
+ if ip_addresses.is_ip_address(ip_address, ip_type='ipv6'):
501
+ if ip_address not in export_entry['urls_ips']:
502
+ export_entry['urls_ips'].append(ip_address)
503
+ elif key == 'uris':
504
+ for address in value:
505
+ if address not in export_entry['urls_ips']:
506
+ export_entry['urls_ips'].append(address)
507
+
508
+ export_list.append(export_entry)
509
+ # break
510
+
511
+ # Save UIDs as CSV file.
512
+ file_path = directory_path + os.sep + 'uids.csv'
513
+ csvs.write_list_to_csv(export_list, file_path)
514
+
515
+ return None
@@ -1,4 +1,4 @@
1
- from .import rest_firmware, rest_statistics
1
+ from .import rest_firmware, rest_statistics, rest_binary_search
2
2
 
3
3
 
4
4
  def endpoint_router(config: dict):
@@ -10,5 +10,10 @@ def endpoint_router(config: dict):
10
10
 
11
11
  if config['method'] == 'upload_firmware':
12
12
  rest_firmware.upload_files(config['firmwares_path'], config['data'])
13
- elif config['method'] == 'stats':
13
+ elif config['method'] == 'firmware_csv':
14
+ rest_firmware.save_firmware_uids_as_csv(
15
+ directory_path=config['output_path'], config_data=config['data'], get_analysis_data=True)
16
+ elif config['method'] == 'get_statistics':
14
17
  rest_statistics.get_statistics()
18
+ elif config['method'] == 'binary_search':
19
+ rest_binary_search.search_string(config['data']['vendor'])
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: atomicshop
3
- Version: 2.4.0
3
+ Version: 2.4.1
4
4
  Summary: Atomic functions and classes to make developer life easier
5
5
  Author: Denis Kras
6
6
  License: MIT License
@@ -1,4 +1,4 @@
1
- atomicshop/__init__.py,sha256=EVyUsXjC3TAEyHm-m9w_eqPpPjqt2ZZwkU-s8sUyUAo,122
1
+ atomicshop/__init__.py,sha256=i3jQ-IHva0gZoh8TLttLwDYpRvGzWReWzwOG8PZkTyU,122
2
2
  atomicshop/_basics_temp.py,sha256=6cu2dd6r2dLrd1BRNcVDKTHlsHs_26Gpw8QS6v32lQ0,3699
3
3
  atomicshop/appointment_management.py,sha256=N3wVGJgrqJfsj_lqiRfaL3FxMEe57by5Stzanh189mk,7263
4
4
  atomicshop/archiver.py,sha256=E4dgAuh6ARtAWRW6Q0RdnMRMzsE_S1NjMiajHRIVG9s,5537
@@ -17,7 +17,7 @@ atomicshop/github_wrapper.py,sha256=7pZkhliP4vdcdeVtbgTDEzBS3lUw3-mp5PMWUDA19V0,
17
17
  atomicshop/hashing.py,sha256=k_HXR7FnPUzLUKk8EiewJ_gLFBlWncZluiBwzplFMWs,3548
18
18
  atomicshop/http_parse.py,sha256=nrf2rZcprLqtW8HVrV7TCZ1iTBcWRRy-mXIlAOzcaJs,9703
19
19
  atomicshop/inspect_wrapper.py,sha256=sGRVQhrJovNygHTydqJj0hxES-aB2Eg9KbIk3G31apw,11429
20
- atomicshop/ip_addresses.py,sha256=fvBwLFGbcNV87s_UzZZs0MO-TPwDEak_0SB4_syhefM,691
20
+ atomicshop/ip_addresses.py,sha256=GBG9YXEqHItmGEgKwqatx28CbWislFI2ZI2xVHGIqO4,759
21
21
  atomicshop/keyboard_press.py,sha256=1W5kRtOB75fulVx-uF2yarBhW0_IzdI1k73AnvXstk0,452
22
22
  atomicshop/pbtkmultifile_argparse.py,sha256=aEk8nhvoQVu-xyfZosK3ma17CwIgOjzO1erXXdjwtS4,4574
23
23
  atomicshop/permissions.py,sha256=CYTDVOI0jh9ks0ZLnnOuPzppgCszFEc9-92DTkVTYi4,522
@@ -36,7 +36,7 @@ atomicshop/ssh_remote.py,sha256=Sas3nrQv8ardxR51t59xZZsYm8nvUcA7tMSqEDViRLk,1715
36
36
  atomicshop/sys_functions.py,sha256=MTBxRve5bh58SPvhX3gMiGqHlSBuI_rdNN1NnnBBWqI,906
37
37
  atomicshop/tempfiles.py,sha256=uq1ve2WlWehZ3NOTXJnpBBMt6HyCdBufqedF0HyzA6k,2517
38
38
  atomicshop/timer.py,sha256=KxBBgVM8po6pUJDW8TgY1UXj0iiDmRmL5XDCq0VHAfU,1670
39
- atomicshop/urls.py,sha256=2vVWXeue6NyuW-RHORrkSeJEl9tQM71rW8DC7_SnR6c,625
39
+ atomicshop/urls.py,sha256=CQl1j1kjEVDlAuYJqYD9XxPF1SUSgrmG8PjlcXNEKsQ,597
40
40
  atomicshop/web.py,sha256=9cxzGhk16PU0daHi-mxSNH4r7LATDh527oN7fZ1dSMk,11003
41
41
  atomicshop/addons/PlayWrightCodegen.cmd,sha256=Z5cnllsyXD4F1W2h-WLEnyFkg5nZy0-hTGHRWXVOuW4,173
42
42
  atomicshop/addons/ScriptExecution.cmd,sha256=8iC-uHs9MX9qUD_C2M7n9Xw4MZvwOfxT8H5v3hluVps,93
@@ -78,7 +78,7 @@ atomicshop/etw/etw.py,sha256=xVJNbfCq4KgRfsDnul6CrIdAMl9xRBixZ-hUyqiB2g4,2403
78
78
  atomicshop/file_io/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
79
79
  atomicshop/file_io/csvs.py,sha256=4R4Kij8FmxNwXFjDtlF_A0flAk0Hj5nZKlEnqC5VxgQ,3125
80
80
  atomicshop/file_io/file_io.py,sha256=kaRMSm8sNrnos1gqgAcPVkUXjXgZE-uW_STueqsFZyw,5657
81
- atomicshop/file_io/jsons.py,sha256=sQjWtNXsM6HjtrSG2FIicT1Alp8ASC0yfY8VL-xoDR4,3354
81
+ atomicshop/file_io/jsons.py,sha256=4xCnC6MfajLouXUFl2aVXUPvftQVf2eS5DgydPZHF_c,4170
82
82
  atomicshop/file_io/tomls.py,sha256=T-K4l9FvkSN3eOBoAduPtlFkgW7JqUds6wDMRpZyG6U,1057
83
83
  atomicshop/file_io/xlsxs.py,sha256=v_dyg9GD4LqgWi6wA1QuWRZ8zG4ZwB6Dz52ytdcmmmI,2184
84
84
  atomicshop/file_io/xmls.py,sha256=sQvTKqUEAoMa9rxCvJJV2EiUE7UoTX6fa1REt-n6MVQ,1650
@@ -127,11 +127,12 @@ atomicshop/wrappers/certauthw/certauth.py,sha256=hKedW0DOWlEigSNm8wu4SqHkCQsGJ1t
127
127
  atomicshop/wrappers/certauthw/certauthw.py,sha256=4WvhjANI7Kzqrr_nKmtA8Kf7B6rute_5wfP65gwQrjw,8082
128
128
  atomicshop/wrappers/ctyping/process_winapi.py,sha256=QcXL-ETtlSSkoT8F7pYle97ubGWsjYp8cx8HxkVMgAc,2762
129
129
  atomicshop/wrappers/factw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
130
- atomicshop/wrappers/factw/fact_config.py,sha256=IbjM5MqjnTAi7iD--FJnC6pyNVrH9eruTRQFiideZY8,223
130
+ atomicshop/wrappers/factw/fact_config.py,sha256=Cxxg46cAgsquoaJiHLEmGy9GxqXTQhO_53fP32KDQ78,276
131
131
  atomicshop/wrappers/factw/get_file_data.py,sha256=ChKC0OjgjFlNubZQBwcGhRO3L2pccc27RLRlAMIUix4,1641
132
- atomicshop/wrappers/factw/rest_file_object.py,sha256=_utxe0-hQNKwBFFP3cmsLKmGlhU0UuJo67SCRfQ2aAk,2433
133
- atomicshop/wrappers/factw/rest_firmware.py,sha256=k2YvgH7qi_-oBDutoRUiEJ-2N_we2XOguCSmuC7UNDk,9486
134
- atomicshop/wrappers/factw/rest_router.py,sha256=xpVriLljpRAtUnEaxtVd5UDafHGJ1pFlMH8FqSl1pq8,402
132
+ atomicshop/wrappers/factw/rest_binary_search.py,sha256=k0O-fmBiFBQzCCyTjXdpSDKuegJPDJgZuE3GEmKK448,824
133
+ atomicshop/wrappers/factw/rest_file_object.py,sha256=4hk0XmwE1VVaiVoFTjnrRkmEQJPR3kWGEtj2ueex72Y,3216
134
+ atomicshop/wrappers/factw/rest_firmware.py,sha256=aE3laaM5oZJntqxbf_UPBL1yb_8d1VvicGxybMzOCVY,20383
135
+ atomicshop/wrappers/factw/rest_router.py,sha256=zrBDM66q9Q9BBuY8xFAL35IHmIOM5iPRuWFMn9lQ8QU,745
135
136
  atomicshop/wrappers/factw/rest_statistics.py,sha256=0no4yDZ5GSJbb8Hl8ZMyTahvNV1o7tilXJ9ifl7v4j4,651
136
137
  atomicshop/wrappers/factw/rest_status.py,sha256=iWpn6RpaQEKDavKTaTTsolCJ-HGzIEeNyQxVyMQRUYQ,639
137
138
  atomicshop/wrappers/loggingw/checks.py,sha256=AGFsTsLxHQd1yAraa5popqLaGO9VM0KpcPGuSLn5ptU,719
@@ -167,8 +168,8 @@ atomicshop/wrappers/socketw/socket_server_tester.py,sha256=VfNthyBvgI5tL9v3Qprh4
167
168
  atomicshop/wrappers/socketw/socket_wrapper.py,sha256=aXBwlEIJhFT0-c4i8iNlFx2It9VpCEpsv--5Oqcpxao,11624
168
169
  atomicshop/wrappers/socketw/ssl_base.py,sha256=k4V3gwkbq10MvOH4btU4onLX2GNOsSfUAdcHmL1rpVE,2274
169
170
  atomicshop/wrappers/socketw/statistics_csv.py,sha256=t3dtDEfN47CfYVi0CW6Kc2QHTEeZVyYhc57IYYh5nmA,826
170
- atomicshop-2.4.0.dist-info/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
171
- atomicshop-2.4.0.dist-info/METADATA,sha256=Ig9gE-rTTo9oTVxlzPiTaLPYLFaAc2R-jISyK_U1yJc,9585
172
- atomicshop-2.4.0.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
173
- atomicshop-2.4.0.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
174
- atomicshop-2.4.0.dist-info/RECORD,,
171
+ atomicshop-2.4.1.dist-info/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
172
+ atomicshop-2.4.1.dist-info/METADATA,sha256=O2JGo-QiTkcZ4fi_FinqLRM_zdsGIgbOLUau2-l9JI8,9585
173
+ atomicshop-2.4.1.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
174
+ atomicshop-2.4.1.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
175
+ atomicshop-2.4.1.dist-info/RECORD,,