distributed-state-network 0.1.3__tar.gz → 0.2.0__tar.gz
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.
- {distributed_state_network-0.1.3 → distributed_state_network-0.2.0}/PKG-INFO +5 -2
- {distributed_state_network-0.1.3 → distributed_state_network-0.2.0}/README.md +4 -1
- {distributed_state_network-0.1.3 → distributed_state_network-0.2.0}/documentation/ds-node-config.md +14 -8
- {distributed_state_network-0.1.3 → distributed_state_network-0.2.0}/documentation/ds-node-server.md +2 -2
- distributed_state_network-0.2.0/documentation/protocol.md +65 -0
- distributed_state_network-0.2.0/documentation/usage.md +159 -0
- {distributed_state_network-0.1.3 → distributed_state_network-0.2.0}/pyproject.toml +1 -1
- {distributed_state_network-0.1.3 → distributed_state_network-0.2.0}/src/distributed_state_network/dsnode.py +149 -61
- distributed_state_network-0.2.0/src/distributed_state_network/handler.py +143 -0
- {distributed_state_network-0.1.3 → distributed_state_network-0.2.0}/src/distributed_state_network/objects/config.py +0 -4
- {distributed_state_network-0.1.3 → distributed_state_network-0.2.0}/src/distributed_state_network/objects/hello_packet.py +10 -6
- distributed_state_network-0.2.0/src/distributed_state_network/objects/msg_types.py +4 -0
- {distributed_state_network-0.1.3 → distributed_state_network-0.2.0}/technical.md +6 -9
- {distributed_state_network-0.1.3 → distributed_state_network-0.2.0}/test.py +60 -56
- distributed_state_network-0.1.3/documentation/protocol.md +0 -37
- distributed_state_network-0.1.3/documentation/usage.md +0 -84
- distributed_state_network-0.1.3/src/distributed_state_network/handler.py +0 -117
- {distributed_state_network-0.1.3 → distributed_state_network-0.2.0}/.github/workflows/publish.yml +0 -0
- {distributed_state_network-0.1.3 → distributed_state_network-0.2.0}/.gitignore +0 -0
- {distributed_state_network-0.1.3 → distributed_state_network-0.2.0}/LICENSE +0 -0
- {distributed_state_network-0.1.3 → distributed_state_network-0.2.0}/coverage.sh +0 -0
- {distributed_state_network-0.1.3 → distributed_state_network-0.2.0}/documentation/ds-node.md +0 -0
- {distributed_state_network-0.1.3 → distributed_state_network-0.2.0}/src/distributed_state_network/__init__.py +0 -0
- {distributed_state_network-0.1.3 → distributed_state_network-0.2.0}/src/distributed_state_network/create_key.py +0 -0
- {distributed_state_network-0.1.3 → distributed_state_network-0.2.0}/src/distributed_state_network/objects/endpoint.py +0 -0
- {distributed_state_network-0.1.3 → distributed_state_network-0.2.0}/src/distributed_state_network/objects/peers_packet.py +0 -0
- {distributed_state_network-0.1.3 → distributed_state_network-0.2.0}/src/distributed_state_network/objects/signed_packet.py +0 -0
- {distributed_state_network-0.1.3 → distributed_state_network-0.2.0}/src/distributed_state_network/objects/state_packet.py +0 -0
- {distributed_state_network-0.1.3 → distributed_state_network-0.2.0}/src/distributed_state_network/util/__init__.py +0 -0
- {distributed_state_network-0.1.3 → distributed_state_network-0.2.0}/src/distributed_state_network/util/aes.py +0 -0
- {distributed_state_network-0.1.3 → distributed_state_network-0.2.0}/src/distributed_state_network/util/byte_helper.py +0 -0
- {distributed_state_network-0.1.3 → distributed_state_network-0.2.0}/src/distributed_state_network/util/ecdsa.py +0 -0
- {distributed_state_network-0.1.3 → distributed_state_network-0.2.0}/src/distributed_state_network/util/https.py +0 -0
- {distributed_state_network-0.1.3 → distributed_state_network-0.2.0}/src/distributed_state_network/util/key_manager.py +0 -0
- {distributed_state_network-0.1.3 → distributed_state_network-0.2.0}/tests/connections.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: distributed-state-network
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: A tool to distribute the state of a network device to other devices on the network
|
|
5
5
|
Project-URL: Homepage, https://github.com/erinclemmer/distributed_state_network
|
|
6
6
|
Project-URL: Issues, https://github.com/erinclemmer/distributed_state_network/issues
|
|
@@ -77,7 +77,7 @@ DSN creates a peer-to-peer network where each node maintains its own state datab
|
|
|
77
77
|
- Each node owns its state and is the only one who can modify it
|
|
78
78
|
- State changes are automatically broadcast to all connected nodes
|
|
79
79
|
- Any node can read any other node's state instantly
|
|
80
|
-
- All communication is encrypted with AES
|
|
80
|
+
- All communication is encrypted with AES
|
|
81
81
|
|
|
82
82
|
## Example: Distributed Temperature Monitoring
|
|
83
83
|
|
|
@@ -110,6 +110,9 @@ for node_id in monitor.node.peers():
|
|
|
110
110
|
|
|
111
111
|
### Documentation
|
|
112
112
|
* [Usage Examples](./documentation/usage.md)
|
|
113
|
+
* [Configuration Class](./documentation/ds-node-config.md)
|
|
114
|
+
* [Server Class](./documentation/ds-node-server.md)
|
|
115
|
+
* [Protocol Class](./documentation/ds-node.md)
|
|
113
116
|
* [Protocol](./documentation/protocol.md)
|
|
114
117
|
|
|
115
118
|
#### API Reference
|
|
@@ -52,7 +52,7 @@ DSN creates a peer-to-peer network where each node maintains its own state datab
|
|
|
52
52
|
- Each node owns its state and is the only one who can modify it
|
|
53
53
|
- State changes are automatically broadcast to all connected nodes
|
|
54
54
|
- Any node can read any other node's state instantly
|
|
55
|
-
- All communication is encrypted with AES
|
|
55
|
+
- All communication is encrypted with AES
|
|
56
56
|
|
|
57
57
|
## Example: Distributed Temperature Monitoring
|
|
58
58
|
|
|
@@ -85,6 +85,9 @@ for node_id in monitor.node.peers():
|
|
|
85
85
|
|
|
86
86
|
### Documentation
|
|
87
87
|
* [Usage Examples](./documentation/usage.md)
|
|
88
|
+
* [Configuration Class](./documentation/ds-node-config.md)
|
|
89
|
+
* [Server Class](./documentation/ds-node-server.md)
|
|
90
|
+
* [Protocol Class](./documentation/ds-node.md)
|
|
88
91
|
* [Protocol](./documentation/protocol.md)
|
|
89
92
|
|
|
90
93
|
#### API Reference
|
{distributed_state_network-0.1.3 → distributed_state_network-0.2.0}/documentation/ds-node-config.md
RENAMED
|
@@ -12,20 +12,18 @@ from distributed_state_network import DSNodeConfig
|
|
|
12
12
|
class DSNodeConfig:
|
|
13
13
|
node_id: str
|
|
14
14
|
port: int
|
|
15
|
-
https: bool
|
|
16
|
-
network_ip: str
|
|
17
15
|
aes_key_file: str
|
|
18
16
|
bootstrap_nodes: List[Endpoint]
|
|
19
17
|
```
|
|
20
18
|
|
|
21
19
|
### Attributes
|
|
22
20
|
- **node_id** (`str`): Unique identifier for the node
|
|
23
|
-
- **port** (`int`): Port number for the node to listen on
|
|
21
|
+
- **port** (`int`): Port number for the node to listen on (UDP)
|
|
24
22
|
- **aes_key_file** (`str`): Path to the AES key file for encryption/decryption
|
|
25
|
-
- **https** (`bool`): whether to use HTTPS network requests (true) or http (false)
|
|
26
|
-
- **network_ip** (`str`): Must match network address of the current computer (only if https is on)
|
|
27
23
|
- **bootstrap_nodes** (`List[Endpoint]`): List of initial nodes to connect to when joining the network
|
|
28
24
|
|
|
25
|
+
**Note:** The node's public IP address is automatically detected by the bootstrap server during the initial handshake.
|
|
26
|
+
|
|
29
27
|
### Methods
|
|
30
28
|
|
|
31
29
|
### `from_dict(data: Dict) -> DSNodeConfig`
|
|
@@ -43,11 +41,19 @@ config_dict = {
|
|
|
43
41
|
"node_id": "node1",
|
|
44
42
|
"port": 8000,
|
|
45
43
|
"aes_key_file": "/path/to/key.aes",
|
|
46
|
-
"https": false,
|
|
47
|
-
"network_ip": "192.168.0.1",
|
|
48
44
|
"bootstrap_nodes": [
|
|
49
45
|
{"address": "127.0.0.1", "port": 8001}
|
|
50
46
|
]
|
|
51
47
|
}
|
|
52
48
|
config = DSNodeConfig.from_dict(config_dict)
|
|
53
|
-
```
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Example for bootstrap node (first node in network):**
|
|
52
|
+
```python
|
|
53
|
+
config_dict = {
|
|
54
|
+
"node_id": "bootstrap",
|
|
55
|
+
"port": 8000,
|
|
56
|
+
"aes_key_file": "/path/to/key.aes",
|
|
57
|
+
"bootstrap_nodes": [] # Empty for first node
|
|
58
|
+
}
|
|
59
|
+
config = DSNodeConfig.from_dict(config_dict)
|
{distributed_state_network-0.1.3 → distributed_state_network-0.2.0}/documentation/ds-node-server.md
RENAMED
|
@@ -30,8 +30,8 @@ server = DSNodeServer.start(config)
|
|
|
30
30
|
|
|
31
31
|
**Parameters:**
|
|
32
32
|
- `config` (`DSNodeConfig`): Node configuration
|
|
33
|
-
- `disconnect_callback` (`Optional[Callable]`): Callback for disconnect events
|
|
34
|
-
- `update_callback` (`Optional[Callable]`): Callback for state update events
|
|
33
|
+
- `disconnect_callback` (`Optional[Callable]`): Callback for disconnect events (no parameters)
|
|
34
|
+
- `update_callback` (`Optional[Callable]`): Callback for state update events (no parameters)
|
|
35
35
|
|
|
36
36
|
**Returns:**
|
|
37
37
|
- `DSNodeServer`: Running server instance
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
## Network Protocol
|
|
2
|
+
|
|
3
|
+
### Transport Layer
|
|
4
|
+
The network now uses **UDP (User Datagram Protocol)** for all communication instead of HTTP/HTTPS. This provides:
|
|
5
|
+
- Lower latency for peer-to-peer communication
|
|
6
|
+
- Reduced overhead compared to HTTP
|
|
7
|
+
- Simpler connection management
|
|
8
|
+
- More suitable for real-time distributed systems
|
|
9
|
+
|
|
10
|
+
### Message Types
|
|
11
|
+
All UDP packets use a message type byte prefix to identify the packet type:
|
|
12
|
+
|
|
13
|
+
- **Type 1 (HELLO)**: Exchange node information and credentials
|
|
14
|
+
- **Type 2 (PEERS)**: Request/share peer list
|
|
15
|
+
- **Type 3 (UPDATE)**: Send/receive state updates
|
|
16
|
+
- **Type 4 (PING)**: Connectivity check
|
|
17
|
+
|
|
18
|
+
### Packet Structure
|
|
19
|
+
Each UDP packet follows this structure:
|
|
20
|
+
1. **AES Encrypted Payload** containing:
|
|
21
|
+
- Message type (1 byte)
|
|
22
|
+
- Message payload (variable length)
|
|
23
|
+
|
|
24
|
+
### Security
|
|
25
|
+
- All communication is encrypted using AES with a shared key
|
|
26
|
+
- Messages are signed using ECDSA for authentication
|
|
27
|
+
- UDP packets are encrypted end-to-end
|
|
28
|
+
- HTTPS/TLS is not supported with UDP (certificates are no longer used)
|
|
29
|
+
|
|
30
|
+
### State Synchronization
|
|
31
|
+
- Nodes maintain a copy of all peers' states
|
|
32
|
+
- Updates are broadcast to all connected peers
|
|
33
|
+
- Timestamps prevent older updates from overwriting newer ones
|
|
34
|
+
|
|
35
|
+
### Socket Management
|
|
36
|
+
- You can provide a custom UDP socket when starting a server
|
|
37
|
+
- If no socket is provided, one will be created and bound automatically
|
|
38
|
+
- Socket timeout is set to 2 seconds for requests
|
|
39
|
+
- Maximum UDP packet size is 65507 bytes
|
|
40
|
+
|
|
41
|
+
## Important Notes
|
|
42
|
+
|
|
43
|
+
1. **Shared AES Key**: All nodes in the network must use the same AES key file
|
|
44
|
+
2. **Unique Node IDs**: Each node must have a unique node_id
|
|
45
|
+
3. **Port Availability**: Ensure the specified UDP port is available before starting
|
|
46
|
+
4. **Bootstrap Nodes**: At least one bootstrap node is required to join an existing network
|
|
47
|
+
5. **Network Tick**: The network performs maintenance checks every 3 seconds
|
|
48
|
+
6. **Credential Management**: ECDSA keys are automatically generated and stored in `credentials/` directory
|
|
49
|
+
7. **UDP Reliability**: The protocol implements retry logic (up to 3 attempts) for failed requests
|
|
50
|
+
8. **HTTPS Not Supported**: The `https` configuration option is ignored when using UDP
|
|
51
|
+
|
|
52
|
+
## Error Handling
|
|
53
|
+
|
|
54
|
+
Common error codes (embedded in exception messages):
|
|
55
|
+
- **401**: Not Authorized (signature verification failed)
|
|
56
|
+
- **406**: Not Acceptable (invalid data, stale update, or version mismatch)
|
|
57
|
+
- **505**: Version not supported
|
|
58
|
+
|
|
59
|
+
## Migration from HTTP
|
|
60
|
+
|
|
61
|
+
If you're migrating from the HTTP-based version:
|
|
62
|
+
1. Remove any HTTPS certificate dependencies
|
|
63
|
+
2. Update firewall rules to allow UDP traffic on your ports
|
|
64
|
+
3. The API remains largely the same, but transport is now UDP
|
|
65
|
+
4. The `https` flag in configuration is now ignored
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
## Usage Examples
|
|
2
|
+
|
|
3
|
+
### Basic Setup
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
from distributed_state_network import DSNodeServer, DSNodeConfig, Endpoint
|
|
7
|
+
|
|
8
|
+
# Create configuration
|
|
9
|
+
config = DSNodeConfig(
|
|
10
|
+
node_id="node1",
|
|
11
|
+
port=8000,
|
|
12
|
+
aes_key_file="/path/to/shared.key",
|
|
13
|
+
bootstrap_nodes=[] # Empty for first node
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
# Start server (UDP socket will be created automatically)
|
|
17
|
+
server = DSNodeServer.start(config)
|
|
18
|
+
|
|
19
|
+
# Update state
|
|
20
|
+
server.node.update_data("status", "online")
|
|
21
|
+
server.node.update_data("version", "1.0.0")
|
|
22
|
+
|
|
23
|
+
# Read own state
|
|
24
|
+
my_status = server.node.read_data("node1", "status")
|
|
25
|
+
|
|
26
|
+
# Get connected peers
|
|
27
|
+
peers = server.node.peers()
|
|
28
|
+
print(f"Connected peers: {peers}")
|
|
29
|
+
|
|
30
|
+
# Shutdown
|
|
31
|
+
server.stop()
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Joining Existing Network
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
# Node 2 configuration with bootstrap
|
|
38
|
+
config2 = DSNodeConfig(
|
|
39
|
+
node_id="node2",
|
|
40
|
+
port=8001,
|
|
41
|
+
aes_key_file="/path/to/shared.key", # Same key file as network
|
|
42
|
+
bootstrap_nodes=[
|
|
43
|
+
Endpoint("127.0.0.1", 8000) # Node 1's endpoint
|
|
44
|
+
]
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
server2 = DSNodeServer.start(config2)
|
|
48
|
+
|
|
49
|
+
# Read state from peer
|
|
50
|
+
peer_status = server2.node.read_data("node1", "status")
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### With Custom Socket
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
import socket
|
|
57
|
+
|
|
58
|
+
# Create a custom UDP socket
|
|
59
|
+
custom_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
60
|
+
custom_socket.bind(("0.0.0.0", 8003))
|
|
61
|
+
|
|
62
|
+
# You can configure socket options before passing it to the server
|
|
63
|
+
custom_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
64
|
+
|
|
65
|
+
config = DSNodeConfig(
|
|
66
|
+
node_id="node3",
|
|
67
|
+
port=8003, # Should match the bound port
|
|
68
|
+
aes_key_file="/path/to/shared.key",
|
|
69
|
+
bootstrap_nodes=[Endpoint("127.0.0.1", 8000)]
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Pass the custom socket when starting the server
|
|
73
|
+
server = DSNodeServer.start(config, sock=custom_socket)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### With Disconnect Callback
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
def handle_disconnect():
|
|
80
|
+
print("A peer has disconnected!")
|
|
81
|
+
# Handle reconnection logic
|
|
82
|
+
|
|
83
|
+
config = DSNodeConfig(
|
|
84
|
+
node_id="node4",
|
|
85
|
+
port=8004,
|
|
86
|
+
aes_key_file="/path/to/shared.key",
|
|
87
|
+
bootstrap_nodes=[Endpoint("127.0.0.1", 8000)]
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
server = DSNodeServer.start(config, disconnect_callback=handle_disconnect)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### With Update Callback
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
def handle_update():
|
|
97
|
+
print("A peer has updated their state!")
|
|
98
|
+
# React to state changes
|
|
99
|
+
|
|
100
|
+
config = DSNodeConfig(
|
|
101
|
+
node_id="node5",
|
|
102
|
+
port=8005,
|
|
103
|
+
aes_key_file="/path/to/shared.key",
|
|
104
|
+
bootstrap_nodes=[Endpoint("127.0.0.1", 8000)]
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
server = DSNodeServer.start(config, update_callback=handle_update)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Complete Example with All Options
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
import socket
|
|
114
|
+
from distributed_state_network import DSNodeServer, DSNodeConfig, Endpoint
|
|
115
|
+
|
|
116
|
+
def on_peer_disconnect():
|
|
117
|
+
print("Peer disconnected from network")
|
|
118
|
+
|
|
119
|
+
def on_state_update():
|
|
120
|
+
print("Peer state was updated")
|
|
121
|
+
|
|
122
|
+
# Create custom socket (optional)
|
|
123
|
+
custom_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
124
|
+
custom_sock.bind(("0.0.0.0", 8006))
|
|
125
|
+
|
|
126
|
+
config = DSNodeConfig(
|
|
127
|
+
node_id="node6",
|
|
128
|
+
port=8006,
|
|
129
|
+
aes_key_file="/path/to/shared.key",
|
|
130
|
+
bootstrap_nodes=[
|
|
131
|
+
Endpoint("127.0.0.1", 8000),
|
|
132
|
+
Endpoint("127.0.0.1", 8001) # Multiple bootstrap options
|
|
133
|
+
]
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Start with all options
|
|
137
|
+
server = DSNodeServer.start(
|
|
138
|
+
config,
|
|
139
|
+
sock=custom_sock, # Optional custom socket
|
|
140
|
+
disconnect_callback=on_peer_disconnect,
|
|
141
|
+
update_callback=on_state_update
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Use the server
|
|
145
|
+
server.node.update_data("role", "worker")
|
|
146
|
+
peers = server.node.peers()
|
|
147
|
+
|
|
148
|
+
# When done
|
|
149
|
+
server.stop()
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Network Protocol Notes
|
|
153
|
+
|
|
154
|
+
- All communication uses **UDP** instead of HTTP/HTTPS
|
|
155
|
+
- Packets are encrypted with AES using the shared key
|
|
156
|
+
- Messages are signed with ECDSA for authentication
|
|
157
|
+
- Maximum UDP packet size is 65507 bytes
|
|
158
|
+
- Request timeout is 2 seconds with up to 3 retry attempts
|
|
159
|
+
- Network health checks occur every 3 seconds
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "distributed-state-network"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
authors = [{name="Erin Clemmer", email="erin.c.clemmer@gmail.com"}]
|
|
9
9
|
description = "A tool to distribute the state of a network device to other devices on the network"
|
|
10
10
|
keywords = ["distributed", "networking"]
|