Skip to content

Commit c1d1c19

Browse files
committed
Update CA for Python 3.13+
1 parent 2511530 commit c1d1c19

10 files changed

Lines changed: 249 additions & 207 deletions

chaski/node.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -610,11 +610,13 @@ def __init__(
610610

611611
# Request an SSL certificate for secure communication if specified
612612
if request_ssl_certificate:
613-
asyncio.sleep(1)
614-
asyncio.create_task(self.request_ssl_certificate(request_ssl_certificate))
615-
616-
# if os.path.exists(self.ssl_context_client,
617-
# self.ssl_context_server)
613+
loop = asyncio.get_event_loop()
614+
loop.call_later(
615+
1,
616+
lambda: asyncio.create_task(
617+
self.request_ssl_certificate(request_ssl_certificate)
618+
),
619+
)
618620

619621
# ----------------------------------------------------------------------
620622
def __repr__(self) -> str:

chaski/scripts/remote_proxy.py

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,30 @@
66

77
logging.basicConfig(level=logging.DEBUG)
88

9-
parser = argparse.ArgumentParser(description='Chaski Remote Server')
9+
parser = argparse.ArgumentParser(description="Chaski Remote Server")
1010
parser.add_argument(
11-
'-p',
12-
'--port',
11+
"-i",
12+
"--ip",
1313
type=str,
14-
default='65434',
15-
help='Port number to run the server on',
14+
default="127.0.0.1",
15+
help="IP address to run the server on",
1616
)
1717
parser.add_argument(
18-
'-n',
19-
'--name',
18+
"-p",
19+
"--port",
2020
type=str,
21-
default='ChaskiRemote',
22-
help='Name of the server',
21+
default="65434",
22+
help="Port number to run the server on",
2323
)
2424
parser.add_argument(
25-
'modules', type=str, help='Comma-separated list of available modules'
25+
"-n",
26+
"--name",
27+
type=str,
28+
default="ChaskiRemote",
29+
help="Name of the server",
30+
)
31+
parser.add_argument(
32+
"modules", type=str, help="Comma-separated list of available modules"
2633
)
2734

2835
args = parser.parse_args()
@@ -32,9 +39,10 @@
3239
async def run():
3340
""""""
3441
server = ChaskiRemote(
42+
ip=args.ip,
3543
port=args.port,
3644
name=args.name,
37-
available=args.modules.split(','),
45+
available=args.modules.split(","),
3846
run=False,
3947
)
4048

@@ -48,5 +56,5 @@ def main():
4856
asyncio.run(run())
4957

5058

51-
if __name__ == '__main__':
59+
if __name__ == "__main__":
5260
main()

chaski/scripts/terminate_connections.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
# ----------------------------------------------------------------------
77
def close_connections(port):
88
""""""
9-
print(f"Cleaning connections on port {port}...")
109

1110
try:
1211
# Run the lsof command to get the PIDs of active connections on the specified port
@@ -17,9 +16,7 @@ def close_connections(port):
1716
)
1817
pids = result.stdout.strip().splitlines()
1918

20-
if not pids:
21-
print(f"No active connections on port {port}.")
22-
else:
19+
if pids:
2320
# Close all active connections
2421
for pid in pids:
2522
print(f"Closing connection with PID {pid}...")
@@ -37,18 +34,19 @@ def main():
3734

3835
try:
3936
port_range = sys.argv[1]
40-
START_PORT, END_PORT = map(int, port_range.split('-'))
37+
START_PORT, END_PORT = map(int, port_range.split("-"))
4138
if START_PORT > END_PORT:
4239
raise ValueError(
4340
"The start port must be less than or equal to the end port."
4441
)
4542
except ValueError as e:
46-
print(f"Error in port range: {e}")
47-
sys.exit(1)
43+
START_PORT = 65440
44+
END_PORT = 65500
4845

4946
# Iterate over the range of ports and close active connections
5047
for port in range(START_PORT, END_PORT + 1):
5148
close_connections(port)
5249

50+
5351
if __name__ == "__main__":
54-
main()
52+
main()

chaski/streamer.py

Lines changed: 53 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class ChaskiStreamer(ChaskiNode):
3939
# ----------------------------------------------------------------------
4040
def __init__(
4141
self,
42-
destination_folder: str = '.',
42+
destination_folder: str = ".",
4343
chunk_size: int = 8192,
4444
file_handling_callback: callable = None,
4545
allow_incoming_files: bool = False,
@@ -48,6 +48,8 @@ def __init__(
4848
*args: tuple,
4949
**kwargs: dict,
5050
):
51+
# Initialize lock for file transfer to prevent concurrent transfers
52+
self._file_transfer_lock = asyncio.Lock()
5153
"""
5254
Initialize a new instance of ChaskiStreamer.
5355
@@ -86,8 +88,8 @@ def __init__(
8688
self.terminate_stream_flag = False
8789

8890
self.enable_message_propagation()
89-
self.add_propagation_command('ChaskiMessage')
90-
self.add_propagation_command('ChaskiStorageRequest')
91+
self.add_propagation_command("ChaskiMessage")
92+
self.add_propagation_command("ChaskiStorageRequest")
9193

9294
if persistent_storage:
9395
self.persistent_storage = PersistentStorage()
@@ -101,7 +103,7 @@ def __repr__(self):
101103
such as the IP address and port. If the instance is a root node, it prepends an
102104
asterisk (*) to the string.
103105
"""
104-
h = '*' if self.paired else ''
106+
h = "*" if self.paired else ""
105107
return h + self.address
106108

107109
# ----------------------------------------------------------------------
@@ -122,7 +124,7 @@ def address(self) -> str:
122124

123125
# ----------------------------------------------------------------------
124126
@classmethod
125-
def get_hash(cls, file: str, algorithm: str = 'sha256') -> str:
127+
def get_hash(cls, file: str, algorithm: str = "sha256") -> str:
126128
"""
127129
Compute the hash of a file using the specified algorithm.
128130
@@ -143,7 +145,7 @@ def get_hash(cls, file: str, algorithm: str = 'sha256') -> str:
143145
The hexadecimal hash digest of the file.
144146
"""
145147
hash_func = hashlib.new(algorithm)
146-
with open(file, 'rb') as f:
148+
with open(file, "rb") as f:
147149
while chunk := f.read(8192): # Read the file in 8192 byte blocks
148150
hash_func.update(chunk)
149151
return hash_func.hexdigest()
@@ -188,7 +190,7 @@ def _get_status(self, **kwargs) -> dict:
188190
}
189191

190192
# ----------------------------------------------------------------------
191-
async def __aenter__(self) -> Generator['Message', None, None]:
193+
async def __aenter__(self) -> Generator["Message", None, None]:
192194
"""
193195
Enter the asynchronous context for streaming messages.
194196
@@ -208,7 +210,7 @@ async def __aexit__(
208210
self,
209211
exception_type: type,
210212
exception_value: BaseException,
211-
exception_traceback: 'TracebackType',
213+
exception_traceback: "TracebackType",
212214
) -> None:
213215
"""
214216
Exit the runtime context related to this object and stop the streamer.
@@ -250,10 +252,10 @@ async def push(self, topic: str, data: bytes = None) -> None:
250252
data : bytes, optional
251253
The byte-encoded data to be sent with the message. This could be any binary payload that subscribers are expected to process.
252254
"""
253-
await self._write('ChaskiMessage', data=data, topic=topic)
255+
await self._write("ChaskiMessage", data=data, topic=topic)
254256

255257
# ----------------------------------------------------------------------
256-
async def _process_ChaskiMessage(self, message: 'Message', edge: 'Edge') -> None:
258+
async def _process_ChaskiMessage(self, message: "Message", edge: "Edge") -> None:
257259
"""
258260
Process an incoming Chaski message and place it onto the message queue.
259261
@@ -307,7 +309,7 @@ def deactivate_file_transfer(self) -> None:
307309
self.allow_incoming_files = False
308310

309311
# ----------------------------------------------------------------------
310-
async def message_stream(self) -> Generator['Message', None, None]:
312+
async def message_stream(self) -> Generator["Message", None, None]:
311313
"""
312314
Asynchronously generate messages from the message queue.
313315
@@ -358,7 +360,7 @@ def terminate_stream(self) -> None:
358360
async def push_file(
359361
self,
360362
topic: str,
361-
file: 'IOBase',
363+
file: "IOBase",
362364
filename: str = None,
363365
data: dict = {},
364366
):
@@ -386,37 +388,44 @@ async def push_file(
386388
This method uses asynchronous I/O to read the file in chunks and send each chunk
387389
without blocking the event loop. It ensures that the entire file is processed and sent
388390
even if the process involves multiple chunks.
391+
392+
This method uses a lock to ensure only one file transfer happens at a time.
389393
"""
390-
size = 0
391-
# Initialize a SHA-256 hash function for computing the hash digest of the file chunks
392-
hash_func = hashlib.new('sha256')
393-
while True:
394-
# Read the next chunk of data from the file up to the specified chunk size
395-
chunk = file.read(self.chunk_size)
396-
# Increment the size by the length of the current chunk
397-
size += len(chunk)
398-
# Update the hash function with the current chunk of data.
399-
hash_func.update(chunk)
400-
# Package the chunked file data along with metadata such as filename, hash, and chunk size
401-
package_data = {
402-
'filename': (filename if filename else os.path.split(file.name)[-1]),
403-
'chunk': chunk,
404-
'hash': hash_func.hexdigest(),
405-
'data': data,
406-
'chunk_size': self.chunk_size,
407-
'size': size,
408-
}
409-
410-
# Send the chunked file data as a message to the specified topic and yield control to the event loop
411-
await self._write('ChaskiFile', data=package_data, topic=topic)
412-
await asyncio.sleep(0) # very important sleep
413-
414-
# If no more chunks are available to read, the file transfer is complete
415-
if not chunk:
416-
break
394+
395+
# Acquire the lock to ensure only one file transfer at a time
396+
async with self._file_transfer_lock:
397+
size = 0
398+
# Initialize a SHA-256 hash function for computing the hash digest of the file chunks
399+
hash_func = hashlib.new("sha256")
400+
401+
while True:
402+
# Read the next chunk of data from the file up to the specified chunk size
403+
chunk = file.read(self.chunk_size)
404+
# If no more chunks are available to read, the file transfer is complete
405+
if not chunk:
406+
break
407+
# Increment the size by the length of the current chunk
408+
size += len(chunk)
409+
# Update the hash function with the current chunk of data.
410+
hash_func.update(chunk)
411+
# Package the chunked file data along with metadata such as filename, hash, and chunk size
412+
package_data = {
413+
"filename": (
414+
filename if filename else os.path.split(file.name)[-1]
415+
),
416+
"chunk": chunk,
417+
"hash": hash_func.hexdigest(),
418+
"data": data,
419+
"chunk_size": self.chunk_size,
420+
"size": size,
421+
}
422+
423+
# Send the chunked file data as a message to the specified topic and yield control to the event loop
424+
await self._write("ChaskiFile", data=package_data, topic=topic)
425+
await asyncio.sleep(0) # very important sleep
417426

418427
# ----------------------------------------------------------------------
419-
async def _process_ChaskiFile(self, message: 'Message', edge: 'Edge') -> None:
428+
async def _process_ChaskiFile(self, message: "Message", edge: "Edge") -> None:
420429
"""
421430
Process an incoming ChaskiFile message and append each chunk of data to the target file.
422431
@@ -444,10 +453,10 @@ async def _process_ChaskiFile(self, message: 'Message', edge: 'Edge') -> None:
444453
return
445454

446455
# Append incoming file chunk data to the target file in append-binary mode
447-
if chunk := message.data.pop('chunk'):
456+
if chunk := message.data.pop("chunk"):
448457
with open(
449-
os.path.join(self.destination_folder, message.data['filename']),
450-
'ab',
458+
os.path.join(self.destination_folder, message.data["filename"]),
459+
"ab",
451460
) as file:
452461
# Write the current chunk to the target file in append-binary mode
453462
file.write(chunk)
@@ -459,7 +468,7 @@ async def _process_ChaskiFile(self, message: 'Message', edge: 'Edge') -> None:
459468
self.file_handling_callback(
460469
**{
461470
**message.data,
462-
'destiny_folder': self.destination_folder,
471+
"destiny_folder": self.destination_folder,
463472
}
464473
)
465474

test/test_connection.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import asyncio
2828
from chaski.utils.auto import create_nodes
2929
from typing import Optional
30+
from chaski.scripts import terminate_connections
3031

3132

3233
########################################################################
@@ -40,8 +41,11 @@ class _TestConnections:
4041
communication protocols, such as IPv4 and IPv6.
4142
"""
4243

44+
def tearDown(self):
45+
terminate_connections.main()
46+
4347
# ----------------------------------------------------------------------
44-
async def _close_nodes(self, nodes: list['ChaskiNode']):
48+
async def _close_nodes(self, nodes: list["ChaskiNode"]):
4549
"""
4650
Close all ChaskiNode instances in the provided list.
4751
@@ -54,11 +58,12 @@ async def _close_nodes(self, nodes: list['ChaskiNode']):
5458
A list containing instances of ChaskiNode that need to be stopped.
5559
"""
5660
for node in nodes:
61+
await asyncio.sleep(0.3)
5762
await node.stop()
5863

5964
# ----------------------------------------------------------------------
6065
def assertConnection(
61-
self, node1: 'ChaskiNode', node2: 'ChaskiNode', msg: Optional[str] = None
66+
self, node1: "ChaskiNode", node2: "ChaskiNode", msg: Optional[str] = None
6267
):
6368
"""
6469
Assert that two ChaskiNodes are connected to each other.
@@ -358,7 +363,7 @@ async def test_response_udp(self):
358363
}
359364
response_data = await nodes[0]._test_generic_request_udp(dummy_data)
360365
self.assertEqual(
361-
dummy_data, response_data, 'Mismatch between sent and received data'
366+
dummy_data, response_data, "Mismatch between sent and received data"
362367
)
363368

364369
await self._close_nodes(nodes)
@@ -414,7 +419,7 @@ async def asyncSetUp(self) -> None:
414419
-----
415420
This method is automatically invoked by the testing framework and typically does not need to be called explicitly.
416421
"""
417-
self.ip = '127.0.0.1'
422+
self.ip = "127.0.0.1"
418423
await asyncio.sleep(0)
419424

420425
async def test_single_connections(self):
@@ -489,7 +494,7 @@ async def asyncSetUp(self) -> None:
489494
-----
490495
This method is automatically invoked by the testing framework and typically does not need to be called explicitly.
491496
"""
492-
self.ip = '::1'
497+
self.ip = "::1"
493498
await asyncio.sleep(0)
494499

495500
async def test_single_connections(self):
@@ -514,5 +519,5 @@ async def test_response_udp(self):
514519
return await super().test_response_udp()
515520

516521

517-
if __name__ == '__main__':
522+
if __name__ == "__main__":
518523
unittest.main()

0 commit comments

Comments
 (0)