scanoss 1.20.5__py3-none-any.whl → 1.22.0__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 (48) hide show
  1. protoc_gen_swagger/options/annotations_pb2.py +9 -12
  2. protoc_gen_swagger/options/annotations_pb2_grpc.py +1 -1
  3. protoc_gen_swagger/options/openapiv2_pb2.py +96 -98
  4. protoc_gen_swagger/options/openapiv2_pb2_grpc.py +1 -1
  5. scanoss/__init__.py +1 -1
  6. scanoss/api/common/v2/scanoss_common_pb2.py +20 -18
  7. scanoss/api/common/v2/scanoss_common_pb2_grpc.py +1 -1
  8. scanoss/api/components/v2/scanoss_components_pb2.py +38 -48
  9. scanoss/api/components/v2/scanoss_components_pb2_grpc.py +96 -142
  10. scanoss/api/cryptography/v2/scanoss_cryptography_pb2.py +42 -22
  11. scanoss/api/cryptography/v2/scanoss_cryptography_pb2_grpc.py +185 -75
  12. scanoss/api/dependencies/v2/scanoss_dependencies_pb2.py +32 -30
  13. scanoss/api/dependencies/v2/scanoss_dependencies_pb2_grpc.py +83 -75
  14. scanoss/api/provenance/v2/scanoss_provenance_pb2.py +20 -21
  15. scanoss/api/provenance/v2/scanoss_provenance_pb2_grpc.py +1 -1
  16. scanoss/api/scanning/v2/scanoss_scanning_pb2.py +20 -10
  17. scanoss/api/scanning/v2/scanoss_scanning_pb2_grpc.py +70 -40
  18. scanoss/api/semgrep/v2/scanoss_semgrep_pb2.py +18 -22
  19. scanoss/api/semgrep/v2/scanoss_semgrep_pb2_grpc.py +49 -71
  20. scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py +27 -37
  21. scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py +72 -109
  22. scanoss/cli.py +417 -74
  23. scanoss/components.py +5 -3
  24. scanoss/constants.py +12 -0
  25. scanoss/data/build_date.txt +1 -1
  26. scanoss/file_filters.py +272 -57
  27. scanoss/results.py +92 -109
  28. scanoss/scanner.py +25 -20
  29. scanoss/scanners/__init__.py +23 -0
  30. scanoss/scanners/container_scanner.py +474 -0
  31. scanoss/scanners/folder_hasher.py +302 -0
  32. scanoss/scanners/scanner_config.py +73 -0
  33. scanoss/scanners/scanner_hfh.py +172 -0
  34. scanoss/scanoss_settings.py +9 -5
  35. scanoss/scanossapi.py +29 -7
  36. scanoss/scanossbase.py +9 -3
  37. scanoss/scanossgrpc.py +145 -13
  38. scanoss/threadedscanning.py +6 -6
  39. scanoss/utils/abstract_presenter.py +103 -0
  40. scanoss/utils/crc64.py +96 -0
  41. scanoss/utils/simhash.py +198 -0
  42. {scanoss-1.20.5.dist-info → scanoss-1.22.0.dist-info}/METADATA +4 -2
  43. scanoss-1.22.0.dist-info/RECORD +83 -0
  44. {scanoss-1.20.5.dist-info → scanoss-1.22.0.dist-info}/WHEEL +1 -1
  45. scanoss-1.20.5.dist-info/RECORD +0 -74
  46. {scanoss-1.20.5.dist-info → scanoss-1.22.0.dist-info}/entry_points.txt +0 -0
  47. {scanoss-1.20.5.dist-info → scanoss-1.22.0.dist-info/licenses}/LICENSE +0 -0
  48. {scanoss-1.20.5.dist-info → scanoss-1.22.0.dist-info}/top_level.txt +0 -0
scanoss/scanossbase.py CHANGED
@@ -80,20 +80,26 @@ class ScanossBase:
80
80
  **kwargs,
81
81
  )
82
82
 
83
- def print_to_file_or_stdout(self, msg: str, file: str = None):
83
+ def print_to_file_or_stdout(self, content: str, file: str = None):
84
84
  """
85
85
  Print message to file if provided or stdout
86
86
  """
87
+ if not content:
88
+ return
89
+
87
90
  if file:
88
91
  with open(file, 'w') as f:
89
- f.write(msg)
92
+ f.write(content)
90
93
  else:
91
- self.print_stdout(msg)
94
+ self.print_stdout(content)
92
95
 
93
96
  def print_to_file_or_stderr(self, msg: str, file: str = None):
94
97
  """
95
98
  Print message to file if provided or stderr
96
99
  """
100
+ if not msg:
101
+ return
102
+
97
103
  if file:
98
104
  with open(file, 'w') as f:
99
105
  f.write(msg)
scanoss/scanossgrpc.py CHANGED
@@ -26,6 +26,9 @@ import concurrent.futures
26
26
  import json
27
27
  import os
28
28
  import uuid
29
+ from dataclasses import dataclass
30
+ from enum import IntEnum
31
+ from typing import Dict, Optional
29
32
  from urllib.parse import urlparse
30
33
 
31
34
  import grpc
@@ -33,6 +36,10 @@ from google.protobuf.json_format import MessageToDict, ParseDict
33
36
  from pypac.parser import PACFile
34
37
  from pypac.resolver import ProxyResolver
35
38
 
39
+ from scanoss.api.provenance.v2.scanoss_provenance_pb2_grpc import ProvenanceStub
40
+ from scanoss.api.scanning.v2.scanoss_scanning_pb2_grpc import ScanningStub
41
+ from scanoss.constants import DEFAULT_TIMEOUT
42
+
36
43
  from . import __version__
37
44
  from .api.common.v2.scanoss_common_pb2 import (
38
45
  EchoRequest,
@@ -53,7 +60,7 @@ from .api.cryptography.v2.scanoss_cryptography_pb2_grpc import CryptographyStub
53
60
  from .api.dependencies.v2.scanoss_dependencies_pb2 import DependencyRequest
54
61
  from .api.dependencies.v2.scanoss_dependencies_pb2_grpc import DependenciesStub
55
62
  from .api.provenance.v2.scanoss_provenance_pb2 import ProvenanceResponse
56
- from .api.provenance.v2.scanoss_provenance_pb2_grpc import ProvenanceStub
63
+ from .api.scanning.v2.scanoss_scanning_pb2 import HFHRequest
57
64
  from .api.semgrep.v2.scanoss_semgrep_pb2 import SemgrepResponse
58
65
  from .api.semgrep.v2.scanoss_semgrep_pb2_grpc import SemgrepStub
59
66
  from .api.vulnerabilities.v2.scanoss_vulnerabilities_pb2 import VulnerabilityResponse
@@ -68,12 +75,29 @@ SCANOSS_API_KEY = os.environ.get('SCANOSS_API_KEY') if os.environ.get('SCANOSS_A
68
75
  MAX_CONCURRENT_REQUESTS = 5
69
76
 
70
77
 
78
+ class ScanossGrpcError(Exception):
79
+ """
80
+ Custom exception for SCANOSS gRPC errors
81
+ """
82
+
83
+ pass
84
+
85
+
86
+ class ScanossGrpcStatusCode(IntEnum):
87
+ """Status codes for SCANOSS gRPC responses"""
88
+
89
+ SUCCESS = 1
90
+ SUCCESS_WITH_WARNINGS = 2
91
+ FAILED_WITH_WARNINGS = 3
92
+ FAILED = 4
93
+
94
+
71
95
  class ScanossGrpc(ScanossBase):
72
96
  """
73
97
  Client for gRPC functionality
74
98
  """
75
99
 
76
- def __init__( # noqa: PLR0913
100
+ def __init__( # noqa: PLR0913, PLR0915
77
101
  self,
78
102
  url: str = None,
79
103
  debug: bool = False,
@@ -86,6 +110,7 @@ class ScanossGrpc(ScanossBase):
86
110
  proxy: str = None,
87
111
  grpc_proxy: str = None,
88
112
  pac: PACFile = None,
113
+ req_headers: dict = None,
89
114
  ):
90
115
  """
91
116
 
@@ -103,22 +128,28 @@ class ScanossGrpc(ScanossBase):
103
128
  grpc_proxy='http://<ip>:<port>'
104
129
  """
105
130
  super().__init__(debug, trace, quiet)
106
- self.url = url if url else SCANOSS_GRPC_URL
131
+ self.url = url
107
132
  self.api_key = api_key if api_key else SCANOSS_API_KEY
108
- if self.api_key and not url and not os.environ.get('SCANOSS_GRPC_URL'):
109
- self.url = DEFAULT_URL2 # API key specific and no alternative URL, so use the default premium
110
- self.url = self.url.lower()
111
- self.orig_url = self.url # Used for proxy lookup
112
133
  self.timeout = timeout
113
134
  self.proxy = proxy
114
135
  self.grpc_proxy = grpc_proxy
115
136
  self.pac = pac
137
+ self.req_headers = req_headers
116
138
  self.metadata = []
139
+
117
140
  if self.api_key:
118
141
  self.metadata.append(('x-api-key', api_key)) # Set API key if we have one
119
142
  if ver_details:
120
143
  self.metadata.append(('x-scanoss-client', ver_details))
121
144
  self.metadata.append(('user-agent', f'scanoss-py/{__version__}'))
145
+ self.load_generic_headers()
146
+
147
+ self.url = url if url else SCANOSS_GRPC_URL
148
+ if self.api_key and not url and not os.environ.get('SCANOSS_GRPC_URL'):
149
+ self.url = DEFAULT_URL2 # API key specific and no alternative URL, so use the default premium
150
+ self.url = self.url.lower()
151
+ self.orig_url = self.url # Used for proxy lookup
152
+
122
153
  secure = True if self.url.startswith('https:') else False # Is it a secure connection?
123
154
  if self.url.startswith('http'):
124
155
  u = urlparse(self.url)
@@ -139,6 +170,7 @@ class ScanossGrpc(ScanossBase):
139
170
  self.semgrep_stub = SemgrepStub(grpc.insecure_channel(self.url))
140
171
  self.vuln_stub = VulnerabilitiesStub(grpc.insecure_channel(self.url))
141
172
  self.provenance_stub = ProvenanceStub(grpc.insecure_channel(self.url))
173
+ self.scanning_stub = ScanningStub(grpc.insecure_channel(self.url))
142
174
  else:
143
175
  if ca_cert is not None:
144
176
  credentials = grpc.ssl_channel_credentials(cert_data) # secure with specified certificate
@@ -150,6 +182,7 @@ class ScanossGrpc(ScanossBase):
150
182
  self.semgrep_stub = SemgrepStub(grpc.secure_channel(self.url, credentials))
151
183
  self.vuln_stub = VulnerabilitiesStub(grpc.secure_channel(self.url, credentials))
152
184
  self.provenance_stub = ProvenanceStub(grpc.secure_channel(self.url, credentials))
185
+ self.scanning_stub = ScanningStub(grpc.secure_channel(self.url, credentials))
153
186
 
154
187
  @classmethod
155
188
  def _load_cert(cls, cert_file: str) -> bytes:
@@ -429,6 +462,62 @@ class ScanossGrpc(ScanossBase):
429
462
  return resp_dict
430
463
  return None
431
464
 
465
+ def folder_hash_scan(self, request: Dict) -> Dict:
466
+ """
467
+ Client function to call the rpc for Folder Hashing Scan
468
+
469
+ Args:
470
+ request (Dict): Folder Hash Request
471
+
472
+ Returns:
473
+ Dict: Folder Hash Response
474
+ """
475
+ return self._call_rpc(
476
+ self.scanning_stub.FolderHashScan,
477
+ request,
478
+ HFHRequest,
479
+ 'Sending folder hash scan data (rqId: {rqId})...',
480
+ )
481
+
482
+ def _call_rpc(self, rpc_method, request_input, request_type, debug_msg: Optional[str] = None) -> dict:
483
+ """
484
+ Call a gRPC method and return the response as a dictionary
485
+
486
+ Args:
487
+ rpc_method (): The gRPC stub method
488
+ request_input (): Either a dict or a gRPC request object.
489
+ request_type (): The type of the gRPC request object.
490
+ debug_msg (str, optional): Debug message template that can include {rqId} placeholder.
491
+
492
+ Returns:
493
+ dict: The parsed gRPC response as a dictionary, or None if an error occurred.
494
+ """
495
+
496
+ request_id = str(uuid.uuid4())
497
+
498
+ if isinstance(request_input, dict):
499
+ request_obj = ParseDict(request_input, request_type())
500
+ else:
501
+ request_obj = request_input
502
+
503
+ metadata = self.metadata[:] + [('x-request-id', request_id)]
504
+
505
+ self.print_debug(debug_msg.format(rqId=request_id))
506
+
507
+ try:
508
+ resp = rpc_method(request_obj, metadata=metadata, timeout=self.timeout)
509
+ except grpc.RpcError as e:
510
+ raise ScanossGrpcError(
511
+ f'{e.__class__.__name__} while sending gRPC message (rqId: {request_id}): {e.details()}'
512
+ )
513
+
514
+ if resp and not self._check_status_response(resp.status, request_id):
515
+ raise ScanossGrpcError(f'Unsuccessful status response (rqId: {request_id}).')
516
+
517
+ resp_dict = MessageToDict(resp, preserving_proto_field_name=True)
518
+ resp_dict.pop('status', None)
519
+ return resp_dict
520
+
432
521
  def _check_status_response(self, status_response: StatusResponse, request_id: str = None) -> bool:
433
522
  """
434
523
  Check the response object to see if the command was successful or not
@@ -436,21 +525,18 @@ class ScanossGrpc(ScanossBase):
436
525
  :return: True if successful, False otherwise
437
526
  """
438
527
 
439
- SUCCEDED_WITH_WARNINGS_STATUS_CODE = 2
440
- FAILED_STATUS_CODE = 3
441
-
442
528
  if not status_response:
443
529
  self.print_stderr(f'Warning: No status response supplied (rqId: {request_id}). Assuming it was ok.')
444
530
  return True
445
531
  self.print_debug(f'Checking response status (rqId: {request_id}): {status_response}')
446
532
  status_code: StatusCode = status_response.status
447
- if status_code > 1:
533
+ if status_code > ScanossGrpcStatusCode.SUCCESS:
448
534
  ret_val = False # default to failed
449
535
  msg = 'Unsuccessful'
450
- if status_code == SUCCEDED_WITH_WARNINGS_STATUS_CODE:
536
+ if status_code == ScanossGrpcStatusCode.SUCCESS_WITH_WARNINGS:
451
537
  msg = 'Succeeded with warnings'
452
538
  ret_val = True # No need to fail as it succeeded with warnings
453
- elif status_code == FAILED_STATUS_CODE:
539
+ elif status_code == ScanossGrpcStatusCode.FAILED_WITH_WARNINGS:
454
540
  msg = 'Failed with warnings'
455
541
  self.print_stderr(f'{msg} (rqId: {request_id} - status: {status_code}): {status_response.message}')
456
542
  return ret_val
@@ -507,7 +593,53 @@ class ScanossGrpc(ScanossBase):
507
593
  return resp_dict
508
594
  return None
509
595
 
596
+ def load_generic_headers(self):
597
+ """
598
+ Adds custom headers from req_headers to metadata.
599
+
600
+ If x-api-key is present and no URL is configured (directly or via
601
+ environment), sets URL to the premium endpoint (DEFAULT_URL2).
602
+ """
603
+ if self.req_headers: # Load generic headers
604
+ for key, value in self.req_headers.items():
605
+ if key == 'x-api-key': # Set premium URL if x-api-key header is set
606
+ if not self.url and not os.environ.get('SCANOSS_GRPC_URL'):
607
+ self.url = DEFAULT_URL2 # API key specific and no alternative URL, so use the default premium
608
+ self.api_key = value
609
+ self.metadata.append((key, value))
610
+
510
611
 
511
612
  #
512
613
  # End of ScanossGrpc Class
513
614
  #
615
+
616
+
617
+ @dataclass
618
+ class GrpcConfig:
619
+ url: str = DEFAULT_URL
620
+ api_key: Optional[str] = SCANOSS_API_KEY
621
+ debug: Optional[bool] = False
622
+ trace: Optional[bool] = False
623
+ quiet: Optional[bool] = False
624
+ ver_details: Optional[str] = None
625
+ ca_cert: Optional[str] = None
626
+ pac: Optional[PACFile] = None
627
+ timeout: Optional[int] = DEFAULT_TIMEOUT
628
+ proxy: Optional[str] = None
629
+ grpc_proxy: Optional[str] = None
630
+
631
+
632
+ def create_grpc_config_from_args(args) -> GrpcConfig:
633
+ return GrpcConfig(
634
+ url=getattr(args, 'api2url', DEFAULT_URL),
635
+ api_key=getattr(args, 'key', SCANOSS_API_KEY),
636
+ debug=getattr(args, 'debug', False),
637
+ trace=getattr(args, 'trace', False),
638
+ quiet=getattr(args, 'quiet', False),
639
+ ver_details=getattr(args, 'ver_details', None),
640
+ ca_cert=getattr(args, 'ca_cert', None),
641
+ pac=getattr(args, 'pac', None),
642
+ timeout=getattr(args, 'timeout', DEFAULT_TIMEOUT),
643
+ proxy=getattr(args, 'proxy', None),
644
+ grpc_proxy=getattr(args, 'grpc_proxy', None),
645
+ )
@@ -23,13 +23,13 @@ SPDX-License-Identifier: MIT
23
23
  """
24
24
 
25
25
  import os
26
+ import queue
26
27
  import sys
27
28
  import threading
28
- import queue
29
29
  import time
30
-
31
- from typing import Dict, List
32
30
  from dataclasses import dataclass
31
+ from typing import Dict, List
32
+
33
33
  from progress.bar import Bar
34
34
 
35
35
  from .scanossapi import ScanossApi
@@ -49,8 +49,6 @@ class ThreadedScanning(ScanossBase):
49
49
  Multiple threads pull messages off this queue, process the request and put the results into an output queue
50
50
  """
51
51
 
52
- inputs: queue.Queue = queue.Queue()
53
- output: queue.Queue = queue.Queue()
54
52
  bar: Bar = None
55
53
 
56
54
  def __init__(
@@ -65,6 +63,8 @@ class ThreadedScanning(ScanossBase):
65
63
  :param nb_threads: Number of thread to run (default 5)
66
64
  """
67
65
  super().__init__(debug, trace, quiet)
66
+ self.inputs = queue.Queue()
67
+ self.output = queue.Queue()
68
68
  self.scanapi = scanapi
69
69
  self.nb_threads = nb_threads
70
70
  self._isatty = sys.stderr.isatty()
@@ -134,7 +134,7 @@ class ThreadedScanning(ScanossBase):
134
134
  :param wfp: WFP to add to queue
135
135
  """
136
136
  if wfp is None or wfp == '':
137
- self.print_stderr(f'Warning: empty WFP. Skipping from scan...')
137
+ self.print_stderr('Warning: empty WFP. Skipping from scan...')
138
138
  else:
139
139
  self.inputs.put(wfp)
140
140
 
@@ -0,0 +1,103 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from scanoss.scanossbase import ScanossBase
4
+
5
+
6
+ class AbstractPresenter(ABC):
7
+ """
8
+ Abstract presenter class for presenting output in a given format.
9
+ Subclasses must implement the _format_json_output and _format_plain_output methods.
10
+ """
11
+
12
+ def __init__(
13
+ self,
14
+ debug: bool = False,
15
+ trace: bool = False,
16
+ quiet: bool = False,
17
+ output_file: str = None,
18
+ output_format: str = None,
19
+ ):
20
+ """
21
+ Initialize the presenter with the given output file and format.
22
+ """
23
+ self.AVAILABLE_OUTPUT_FORMATS = ['json', 'plain', 'cyclonedx', 'spdxlite', 'csv', 'raw']
24
+ self.base = ScanossBase(debug=debug, trace=trace, quiet=quiet)
25
+ self.output_file = output_file
26
+ self.output_format = output_format
27
+
28
+ def present(self, output_format: str = None, output_file: str = None):
29
+ """
30
+ Present the formatted output to a file if provided; otherwise, print to stdout.
31
+ """
32
+ file_path = output_file or self.output_file
33
+ fmt = output_format or self.output_format
34
+
35
+ if fmt and fmt not in self.AVAILABLE_OUTPUT_FORMATS:
36
+ raise ValueError(
37
+ f"ERROR: Invalid output format '{fmt}'. Valid values are: {', '.join(self.AVAILABLE_OUTPUT_FORMATS)}"
38
+ )
39
+
40
+ if fmt == 'json':
41
+ content = self._format_json_output()
42
+ elif fmt == 'plain':
43
+ content = self._format_plain_output()
44
+ elif fmt == 'cyclonedx':
45
+ content = self._format_cyclonedx_output()
46
+ elif fmt == 'spdxlite':
47
+ content = self._format_spdxlite_output()
48
+ elif fmt == 'csv':
49
+ content = self._format_csv_output()
50
+ elif fmt == 'raw':
51
+ content = self._format_raw_output()
52
+ else:
53
+ content = self._format_plain_output()
54
+
55
+ self._present_output(content, file_path)
56
+
57
+ def _present_output(self, content: str, file_path: str = None):
58
+ """
59
+ If a file path is provided, write to that file; otherwise, print the content to stdout.
60
+ """
61
+ self.base.print_to_file_or_stdout(content, file_path)
62
+
63
+ @abstractmethod
64
+ def _format_cyclonedx_output(self) -> str:
65
+ """
66
+ Return a CycloneDX string representation of the data.
67
+ """
68
+ pass
69
+
70
+ @abstractmethod
71
+ def _format_spdxlite_output(self) -> str:
72
+ """
73
+ Return a SPDX-Lite string representation of the data.
74
+ """
75
+ pass
76
+
77
+ @abstractmethod
78
+ def _format_csv_output(self) -> str:
79
+ """
80
+ Return a CSV string representation of the data.
81
+ """
82
+ pass
83
+
84
+ @abstractmethod
85
+ def _format_json_output(self) -> str:
86
+ """
87
+ Return a JSON string representation of the data.
88
+ """
89
+ pass
90
+
91
+ @abstractmethod
92
+ def _format_plain_output(self) -> str:
93
+ """
94
+ Return a plain text string representation of the data.
95
+ """
96
+ pass
97
+
98
+ @abstractmethod
99
+ def _format_raw_output(self) -> str:
100
+ """
101
+ Return a raw string representation of the data.
102
+ """
103
+ pass
scanoss/utils/crc64.py ADDED
@@ -0,0 +1,96 @@
1
+ """
2
+ SPDX-License-Identifier: MIT
3
+
4
+ Copyright (c) 2025, SCANOSS
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ THE SOFTWARE.
23
+ """
24
+
25
+ import struct
26
+ from typing import List
27
+
28
+
29
+ class CRC64:
30
+ """
31
+ CRC64 ECMA implementation matching Go's hash/crc64 package.
32
+ Uses polynomial: 0xC96C5795D7870F42
33
+ """
34
+
35
+ POLY = 0xC96C5795D7870F42
36
+ _TABLE = None
37
+
38
+ def __init__(self):
39
+ if CRC64._TABLE is None:
40
+ CRC64._TABLE = self._make_table()
41
+ self.crc = 0xFFFFFFFFFFFFFFFF # Initial value
42
+
43
+ def _make_table(self) -> list:
44
+ """Generate the CRC64 lookup table."""
45
+ table = []
46
+ for i in range(256):
47
+ crc = i
48
+ for _ in range(8):
49
+ if crc & 1:
50
+ crc = (crc >> 1) ^ self.POLY
51
+ else:
52
+ crc >>= 1
53
+ table.append(crc)
54
+ return table
55
+
56
+ def update(self, data: bytes) -> None:
57
+ """Update the CRC with new data."""
58
+ if isinstance(data, str):
59
+ data = data.encode('utf-8')
60
+
61
+ crc = self.crc
62
+ for b in data:
63
+ crc = (crc >> 8) ^ CRC64._TABLE[(crc ^ b) & 0xFF] # Use class-level table
64
+ self.crc = crc
65
+
66
+ def digest(self) -> int:
67
+ """Get the current CRC value."""
68
+ return self.crc ^ 0xFFFFFFFFFFFFFFFF # Final XOR value
69
+
70
+ def hexdigest(self):
71
+ """Get the current CRC value as a hexadecimal string."""
72
+ return format(self.digest(), '016x')
73
+
74
+ @classmethod
75
+ def checksum(cls, data: bytes) -> int:
76
+ """Calculate CRC64 checksum for the given data."""
77
+ crc = cls()
78
+ crc.update(data)
79
+ return crc.digest()
80
+
81
+ @classmethod
82
+ def get_hash_buff(cls, buff: bytes) -> List[bytes]:
83
+ """
84
+ Get the hash value of the given buffer, and converts it to 8 bytes in big-endian order.
85
+
86
+ Args:
87
+ buff (bytes): The buffer to get the hash value of.
88
+
89
+ Returns:
90
+ bytes: The hash value of the given buffer, and converts it to 8 bytes in big-endian order.
91
+ """
92
+ crc = cls()
93
+ crc.update(buff)
94
+ hash_val = crc.digest()
95
+
96
+ return list(struct.pack('>Q', hash_val))