better error handling
This commit is contained in:
6
veilid-python/veilid/__init__.py
Normal file
6
veilid-python/veilid/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from .api import *
|
||||
from .config import *
|
||||
from .error import *
|
||||
from .json_api import *
|
||||
from .error import *
|
||||
from .types import *
|
211
veilid-python/veilid/api.py
Normal file
211
veilid-python/veilid/api.py
Normal file
@@ -0,0 +1,211 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Self
|
||||
|
||||
from .state import *
|
||||
from .config import *
|
||||
from .error import *
|
||||
from .types import *
|
||||
|
||||
class RoutingContext(ABC):
|
||||
@abstractmethod
|
||||
async def with_privacy(self) -> Self:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def with_custom_privacy(self, stability: Stability) -> Self:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def with_sequencing(self, sequencing: Sequencing) -> Self:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def app_call(self, target: TypedKey | RouteId, request: bytes) -> bytes:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def app_message(self, target: TypedKey | RouteId, message: bytes):
|
||||
pass
|
||||
@abstractmethod
|
||||
async def create_dht_record(self, kind: CryptoKind, schema: DHTSchema) -> DHTRecordDescriptor:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def open_dht_record(self, key: TypedKey, writer: Optional[KeyPair]) -> DHTRecordDescriptor:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def close_dht_record(self, key: TypedKey):
|
||||
pass
|
||||
@abstractmethod
|
||||
async def delete_dht_record(self, key: TypedKey):
|
||||
pass
|
||||
@abstractmethod
|
||||
async def get_dht_value(self, key: TypedKey, subkey: ValueSubkey, force_refresh: bool) -> Optional[ValueData]:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def set_dht_value(self, key: TypedKey, subkey: ValueSubkey, data: bytes) -> Optional[ValueData]:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def watch_dht_values(self, key: TypedKey, subkeys: list[(ValueSubkey, ValueSubkey)], expiration: Timestamp, count: int) -> Timestamp:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def cancel_dht_watch(self, key: TypedKey, subkeys: list[(ValueSubkey, ValueSubkey)]) -> bool:
|
||||
pass
|
||||
|
||||
|
||||
class TableDbTransaction(ABC):
|
||||
@abstractmethod
|
||||
async def commit(self):
|
||||
pass
|
||||
@abstractmethod
|
||||
async def rollback(self):
|
||||
pass
|
||||
@abstractmethod
|
||||
async def store(self, col: int, key: bytes, value: bytes):
|
||||
pass
|
||||
@abstractmethod
|
||||
async def delete(self, col: int, key: bytes):
|
||||
pass
|
||||
|
||||
class TableDb(ABC):
|
||||
@abstractmethod
|
||||
async def get_column_count(self) -> int:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def get_keys(self, col: int) -> list[bytes]:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def transact(self) -> TableDbTransaction:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def store(self, col: int, key: bytes, value: bytes):
|
||||
pass
|
||||
@abstractmethod
|
||||
async def load(self, col: int, key: bytes) -> Optional[bytes]:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def delete(self, col: int, key: bytes) -> Optional[bytes]:
|
||||
pass
|
||||
|
||||
class CryptoSystem(ABC):
|
||||
@abstractmethod
|
||||
async def cached_dh(self, key: PublicKey, secret: SecretKey) -> SharedSecret:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def compute_dh(self, key: PublicKey, secret: SecretKey) -> SharedSecret:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def random_bytes(self, len: int) -> bytes:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def default_salt_length(self) -> int:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def hash_password(self, password: bytes, salt: bytes) -> str:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def verify_password(self, password: bytes, password_hash: str) -> bool:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def derive_shared_secret(self, password: bytes, salt: bytes) -> SharedSecret:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def random_nonce(self) -> Nonce:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def random_shared_secret(self) -> SharedSecret:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def generate_key_pair(self) -> KeyPair:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def generate_hash(self, data: bytes) -> HashDigest:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def validate_key_pair(self, key: PublicKey, secret: SecretKey) -> bool:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def validate_hash(self, data: bytes, hash_digest: HashDigest) -> bool:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def distance(self, key1: CryptoKey, key2: CryptoKey) -> CryptoKeyDistance:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def sign(self, key: PublicKey, secret: SecretKey, data: bytes) -> Signature:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def verify(self, key: PublicKey, data: bytes, signature: Signature):
|
||||
pass
|
||||
@abstractmethod
|
||||
async def aead_overhead(self) -> int:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def decrypt_aead(self, body: bytes, nonce: Nonce, shared_secret: SharedSecret, associated_data: Optional[bytes]) -> bytes:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def encrypt_aead(self, body: bytes, nonce: Nonce, shared_secret: SharedSecret, associated_data: Optional[bytes]) -> bytes:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def crypt_no_auth(self, body: bytes, nonce: Nonce, shared_secret: SharedSecret) -> bytes:
|
||||
pass
|
||||
|
||||
|
||||
class VeilidAPI(ABC):
|
||||
@abstractmethod
|
||||
async def control(self, args: list[str]) -> str:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def get_state(self) -> VeilidState:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def attach(self):
|
||||
pass
|
||||
@abstractmethod
|
||||
async def detach(self):
|
||||
pass
|
||||
@abstractmethod
|
||||
async def new_private_route(self) -> NewPrivateRouteResult:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def new_custom_private_route(self, kinds: list[CryptoKind], stability: Stability, sequencing: Sequencing) -> NewPrivateRouteResult:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def import_remote_private_route(self, blob: bytes) -> RouteId:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def release_private_route(self, route_id: RouteId):
|
||||
pass
|
||||
@abstractmethod
|
||||
async def app_call_reply(self, call_id: OperationId, message: bytes):
|
||||
pass
|
||||
@abstractmethod
|
||||
async def new_routing_context(self) -> RoutingContext:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def open_table_db(self, name: str, column_count: int) -> TableDb:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def delete_table_db(self, name: str):
|
||||
pass
|
||||
@abstractmethod
|
||||
async def get_crypto_system(self, kind: CryptoKind) -> CryptoSystem:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def best_crypto_system(self) -> CryptoSystem:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def verify_signatures(self, node_ids: list[TypedKey], data: bytes, signatures: list[TypedSignature]) -> list[TypedKey]:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def generate_signatures(self, data: bytes, key_pairs: list[TypedKeyPair]) -> list[TypedSignature]:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def generate_key_pair(self, kind: CryptoKind) -> list[TypedKeyPair]:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def now(self) -> Timestamp:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def debug(self, command: str) -> str:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def veilid_version_string(self) -> str:
|
||||
pass
|
||||
@abstractmethod
|
||||
async def veilid_version(self) -> VeilidVersion:
|
||||
pass
|
553
veilid-python/veilid/config.py
Normal file
553
veilid-python/veilid/config.py
Normal file
@@ -0,0 +1,553 @@
|
||||
from typing import Self, Optional
|
||||
from enum import StrEnum
|
||||
from json import dumps
|
||||
|
||||
from .types import *
|
||||
|
||||
class VeilidConfigLogLevel(StrEnum):
|
||||
OFF = 'Off'
|
||||
ERROR = 'Error'
|
||||
WARN = 'Warn'
|
||||
INFO = 'Info'
|
||||
DEBUG = 'Debug'
|
||||
TRACE = 'Trace'
|
||||
|
||||
class VeilidConfigCapabilities:
|
||||
protocol_udp: bool
|
||||
protocol_connect_tcp: bool
|
||||
protocol_accept_tcp: bool
|
||||
protocol_connect_ws: bool
|
||||
protocol_accept_ws: bool
|
||||
protocol_connect_wss: bool
|
||||
protocol_accept_wss: bool
|
||||
|
||||
def __init__(self, protocol_udp: bool, protocol_connect_tcp: bool, protocol_accept_tcp: bool,
|
||||
protocol_connect_ws: bool, protocol_accept_ws: bool, protocol_connect_wss: bool, protocol_accept_wss: bool):
|
||||
|
||||
self.protocol_udp = protocol_udp
|
||||
self.protocol_connect_tcp = protocol_connect_tcp
|
||||
self.protocol_accept_tcp = protocol_accept_tcp
|
||||
self.protocol_connect_ws = protocol_connect_ws
|
||||
self.protocol_accept_ws = protocol_accept_ws
|
||||
self.protocol_connect_wss = protocol_connect_wss
|
||||
self.protocol_accept_wss = protocol_accept_wss
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
return VeilidConfigCapabilities(j['protocol_udp'],
|
||||
j['protocol_connect_tcp'],
|
||||
j['protocol_accept_tcp'],
|
||||
j['protocol_connect_ws'],
|
||||
j['protocol_accept_ws'],
|
||||
j['protocol_connect_wss'],
|
||||
j['protocol_accept_wss'])
|
||||
def to_json(self) -> dict:
|
||||
return self.__dict__
|
||||
|
||||
class VeilidConfigProtectedStore:
|
||||
allow_insecure_fallback: bool
|
||||
always_use_insecure_storage: bool
|
||||
directory: str
|
||||
delete: bool
|
||||
device_encryption_key_password: str
|
||||
new_device_encryption_key_password: Optional[str]
|
||||
|
||||
def __init__(self, allow_insecure_fallback: bool, always_use_insecure_storage: bool,
|
||||
directory: str, delete: bool, device_encryption_key_password: str, new_device_encryption_key_password: Optional[str]):
|
||||
|
||||
self.allow_insecure_fallback = allow_insecure_fallback
|
||||
self.always_use_insecure_storage = always_use_insecure_storage
|
||||
self.directory = directory
|
||||
self.delete = delete
|
||||
self.device_encryption_key_password = device_encryption_key_password
|
||||
self.new_device_encryption_key_password = new_device_encryption_key_password
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
return VeilidConfigProtectedStore(j['allow_insecure_fallback'], j['always_use_insecure_storage'],
|
||||
j['directory'], j['delete'], j['device_encryption_key_password'], j['new_device_encryption_key_password'])
|
||||
def to_json(self) -> dict:
|
||||
return self.__dict__
|
||||
|
||||
class VeilidConfigTableStore:
|
||||
directory: str
|
||||
delete: bool
|
||||
|
||||
def __init__(self, directory: str, delete: bool):
|
||||
self.directory = directory
|
||||
self.delete = delete
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
return VeilidConfigTableStore(j['directory'], j['delete'])
|
||||
def to_json(self) -> dict:
|
||||
return self.__dict__
|
||||
|
||||
class VeilidConfigBlockStore:
|
||||
directory: str
|
||||
delete: bool
|
||||
|
||||
def __init__(self, directory: str, delete: bool):
|
||||
self.directory = directory
|
||||
self.delete = delete
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
return VeilidConfigBlockStore(j['directory'], j['delete'])
|
||||
def to_json(self) -> dict:
|
||||
return self.__dict__
|
||||
|
||||
class VeilidConfigRoutingTable:
|
||||
node_id: list[TypedKey]
|
||||
node_id_secret: list[TypedSecret]
|
||||
bootstrap: list[str]
|
||||
limit_over_attached: int
|
||||
limit_fully_attached: int
|
||||
limit_attached_strong: int
|
||||
limit_attached_good: int
|
||||
limit_attached_weak: int
|
||||
|
||||
def __init__(self, node_id: list[TypedKey], node_id_secret: list[TypedSecret], bootstrap: list[str], limit_over_attached: int,
|
||||
limit_fully_attached: int, limit_attached_strong: int, limit_attached_good: int, limit_attached_weak: int):
|
||||
|
||||
self.node_id = node_id
|
||||
self.node_id_secret = node_id_secret
|
||||
self.bootstrap = bootstrap
|
||||
self.limit_over_attached = limit_over_attached
|
||||
self.limit_fully_attached = limit_fully_attached
|
||||
self.limit_attached_strong = limit_attached_strong
|
||||
self.limit_attached_good = limit_attached_good
|
||||
self.limit_attached_weak = limit_attached_weak
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
return VeilidConfigRoutingTable(
|
||||
list(map(lambda x: TypedKey(x), j['node_id'])),
|
||||
list(map(lambda x: TypedSecret(x), j['node_id_secret'])),
|
||||
j['bootstrap'],
|
||||
j['limit_over_attached'],
|
||||
j['limit_fully_attached'],
|
||||
j['limit_attached_strong'],
|
||||
j['limit_attached_good'],
|
||||
j['limit_attached_weak'])
|
||||
def to_json(self) -> dict:
|
||||
return self.__dict__
|
||||
|
||||
|
||||
class VeilidConfigRPC:
|
||||
concurrency: int
|
||||
queue_size: int
|
||||
max_timestamp_behind_ms: Optional[int]
|
||||
max_timestamp_ahead_ms: Optional[int]
|
||||
timeout_ms: int
|
||||
max_route_hop_count: int
|
||||
default_route_hop_count: int
|
||||
|
||||
def __init__(self, concurrency: int, queue_size: int, max_timestamp_behind_ms: Optional[int], max_timestamp_ahead_ms: Optional[int],
|
||||
timeout_ms: int, max_route_hop_count: int, default_route_hop_count: int):
|
||||
|
||||
self.concurrency = concurrency
|
||||
self.queue_size = queue_size
|
||||
self.max_timestamp_behind_ms = max_timestamp_behind_ms
|
||||
self.max_timestamp_ahead_ms = max_timestamp_ahead_ms
|
||||
self.timeout_ms = timeout_ms
|
||||
self.max_route_hop_count = max_route_hop_count
|
||||
self.default_route_hop_count = default_route_hop_count
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
return VeilidConfigRPC(
|
||||
j['concurrency'],
|
||||
j['queue_size'],
|
||||
j['max_timestamp_behind_ms'],
|
||||
j['max_timestamp_ahead_ms'],
|
||||
j['timeout_ms'],
|
||||
j['max_route_hop_count'],
|
||||
j['default_route_hop_count'])
|
||||
def to_json(self) -> dict:
|
||||
return self.__dict__
|
||||
|
||||
class VeilidConfigDHT:
|
||||
max_find_node_count: int
|
||||
resolve_node_timeout_ms: int
|
||||
resolve_node_count: int
|
||||
resolve_node_fanout: int
|
||||
get_value_timeout_ms: int
|
||||
get_value_count: int
|
||||
get_value_fanout: int
|
||||
set_value_timeout_ms: int
|
||||
set_value_count: int
|
||||
set_value_fanout: int
|
||||
min_peer_count: int
|
||||
min_peer_refresh_time_ms: int
|
||||
validate_dial_info_receipt_time_ms: int
|
||||
local_subkey_cache_size: int
|
||||
local_max_subkey_cache_memory_mb: int
|
||||
remote_subkey_cache_size: int
|
||||
remote_max_records: int
|
||||
remote_max_subkey_cache_memory_mb: int
|
||||
remote_max_storage_space_mb: int
|
||||
|
||||
def __init__(self, max_find_node_count: int, resolve_node_timeout_ms: int, resolve_node_count: int,
|
||||
resolve_node_fanout: int, get_value_timeout_ms: int, get_value_count: int, get_value_fanout: int,
|
||||
set_value_timeout_ms: int, set_value_count: int, set_value_fanout: int,
|
||||
min_peer_count: int, min_peer_refresh_time_ms: int, validate_dial_info_receipt_time_ms: int,
|
||||
local_subkey_cache_size: int, local_max_subkey_cache_memory_mb: int,
|
||||
remote_subkey_cache_size: int, remote_max_records: int, remote_max_subkey_cache_memory_mb: int, remote_max_storage_space_mb: int):
|
||||
|
||||
self.max_find_node_count = max_find_node_count
|
||||
self.resolve_node_timeout_ms =resolve_node_timeout_ms
|
||||
self.resolve_node_count = resolve_node_count
|
||||
self.resolve_node_fanout = resolve_node_fanout
|
||||
self.get_value_timeout_ms = get_value_timeout_ms
|
||||
self.get_value_count = get_value_count
|
||||
self.get_value_fanout = get_value_fanout
|
||||
self.set_value_timeout_ms = set_value_timeout_ms
|
||||
self.set_value_count = set_value_count
|
||||
self.set_value_fanout = set_value_fanout
|
||||
self.min_peer_count = min_peer_count
|
||||
self.min_peer_refresh_time_ms = min_peer_refresh_time_ms
|
||||
self.validate_dial_info_receipt_time_ms = validate_dial_info_receipt_time_ms
|
||||
self.local_subkey_cache_size = local_subkey_cache_size
|
||||
self.local_max_subkey_cache_memory_mb = local_max_subkey_cache_memory_mb
|
||||
self.remote_subkey_cache_size = remote_subkey_cache_size
|
||||
self.remote_max_records = remote_max_records
|
||||
self.remote_max_subkey_cache_memory_mb = remote_max_subkey_cache_memory_mb
|
||||
self.remote_max_storage_space_mb = remote_max_storage_space_mb
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
return VeilidConfigDHT(
|
||||
j['max_find_node_count'],
|
||||
j['resolve_node_timeout_ms'],
|
||||
j['resolve_node_count'],
|
||||
j['resolve_node_fanout'],
|
||||
j['get_value_timeout_ms'],
|
||||
j['get_value_count'],
|
||||
j['get_value_fanout'],
|
||||
j['set_value_timeout_ms'],
|
||||
j['set_value_count'],
|
||||
j['set_value_fanout'],
|
||||
j['min_peer_count'],
|
||||
j['min_peer_refresh_time_ms'],
|
||||
j['validate_dial_info_receipt_time_ms'],
|
||||
j['local_subkey_cache_size'],
|
||||
j['local_max_subkey_cache_memory_mb'],
|
||||
j['remote_subkey_cache_size'],
|
||||
j['remote_max_records'],
|
||||
j['remote_max_subkey_cache_memory_mb'],
|
||||
j['remote_max_storage_space_mb'])
|
||||
def to_json(self) -> dict:
|
||||
return self.__dict__
|
||||
|
||||
class VeilidConfigTLS:
|
||||
certificate_path: str
|
||||
private_key_path: str
|
||||
connection_initial_timeout_ms: int
|
||||
|
||||
def __init__(self, certificate_path: str, private_key_path: str, connection_initial_timeout_ms: int):
|
||||
self.certificate_path = certificate_path
|
||||
self.private_key_path = private_key_path
|
||||
self.connection_initial_timeout_ms = connection_initial_timeout_ms
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
return VeilidConfigTLS(
|
||||
j['certificate_path'],
|
||||
j['private_key_path'],
|
||||
j['connection_initial_timeout_ms'])
|
||||
def to_json(self) -> dict:
|
||||
return self.__dict__
|
||||
|
||||
class VeilidConfigHTTPS:
|
||||
enabled: bool
|
||||
listen_address: str
|
||||
path: str
|
||||
url: Optional[str]
|
||||
|
||||
def __init__(self, enabled: bool, listen_address: str, path: str, url: Optional[str]):
|
||||
self.enabled = enabled
|
||||
self.listen_address = listen_address
|
||||
self.path = path
|
||||
self.url = url
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
return VeilidConfigHTTPS(
|
||||
j['enabled'],
|
||||
j['listen_address'],
|
||||
j['path'],
|
||||
j['url'])
|
||||
def to_json(self) -> dict:
|
||||
return self.__dict__
|
||||
|
||||
class VeilidConfigHTTP:
|
||||
enabled: bool
|
||||
listen_address: str
|
||||
path: str
|
||||
url: Optional[str]
|
||||
|
||||
def __init__(self, enabled: bool, listen_address: str, path: str, url: Optional[str]):
|
||||
self.enabled = enabled
|
||||
self.listen_address = listen_address
|
||||
self.path = path
|
||||
self.url = url
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
return VeilidConfigHTTP(
|
||||
j['enabled'],
|
||||
j['listen_address'],
|
||||
j['path'],
|
||||
j['url'])
|
||||
def to_json(self) -> dict:
|
||||
return self.__dict__
|
||||
|
||||
class VeilidConfigApplication:
|
||||
https: VeilidConfigHTTPS
|
||||
http: VeilidConfigHTTP
|
||||
|
||||
def __init__(self, https: VeilidConfigHTTPS, http: VeilidConfigHTTP):
|
||||
self.https = https
|
||||
self.http = http
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
return VeilidConfigApplication(
|
||||
VeilidConfigHTTPS.from_json(j['https']),
|
||||
VeilidConfigHTTP.from_json(j['http']))
|
||||
def to_json(self) -> dict:
|
||||
return self.__dict__
|
||||
|
||||
|
||||
class VeilidConfigUDP:
|
||||
enabled: bool
|
||||
socket_pool_size: int
|
||||
listen_address: str
|
||||
public_address: Optional[str]
|
||||
|
||||
def __init__(self, enabled: bool, socket_pool_size: int, listen_address: str, public_address: Optional[str]):
|
||||
self.enabled = enabled
|
||||
self.socket_pool_size = socket_pool_size
|
||||
self.listen_address = listen_address
|
||||
self.public_address = public_address
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
return VeilidConfigUDP(
|
||||
j['enabled'],
|
||||
j['socket_pool_size'],
|
||||
j['listen_address'],
|
||||
j['public_address'])
|
||||
def to_json(self) -> dict:
|
||||
return self.__dict__
|
||||
|
||||
class VeilidConfigTCP:
|
||||
connect: bool
|
||||
listen: bool
|
||||
max_connections: int
|
||||
listen_address: str
|
||||
public_address: Optional[str]
|
||||
|
||||
def __init__(self, connect: bool, listen: bool, max_connections: int, listen_address: str, public_address: Optional[str]):
|
||||
self.connect = connect
|
||||
self.listen = listen
|
||||
self.max_connections = max_connections
|
||||
self.listen_address = listen_address
|
||||
self.public_address = public_address
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
return VeilidConfigTCP(
|
||||
j['connect'],
|
||||
j['listen'],
|
||||
j['max_connections'],
|
||||
j['listen_address'],
|
||||
j['public_address'])
|
||||
def to_json(self) -> dict:
|
||||
return self.__dict__
|
||||
|
||||
class VeilidConfigWS:
|
||||
connect: bool
|
||||
listen: bool
|
||||
max_connections: int
|
||||
listen_address: str
|
||||
path: str
|
||||
url: Optional[str]
|
||||
|
||||
def __init__(self, connect: bool, listen: bool, max_connections: int, listen_address: str, path: str, url: Optional[str]):
|
||||
self.connect = connect
|
||||
self.listen = listen
|
||||
self.max_connections = max_connections
|
||||
self.listen_address = listen_address
|
||||
self.path = path
|
||||
self.url = url
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
return VeilidConfigWS(
|
||||
j['connect'],
|
||||
j['listen'],
|
||||
j['max_connections'],
|
||||
j['listen_address'],
|
||||
j['path'],
|
||||
j['url'])
|
||||
def to_json(self) -> dict:
|
||||
return self.__dict__
|
||||
|
||||
class VeilidConfigWSS:
|
||||
connect: bool
|
||||
listen: bool
|
||||
max_connections: int
|
||||
listen_address: str
|
||||
path: str
|
||||
url: Optional[str]
|
||||
|
||||
def __init__(self, connect: bool, listen: bool, max_connections: int, listen_address: str, path: str, url: Optional[str]):
|
||||
self.connect = connect
|
||||
self.listen = listen
|
||||
self.max_connections = max_connections
|
||||
self.listen_address = listen_address
|
||||
self.path = path
|
||||
self.url = url
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
return VeilidConfigWSS(
|
||||
j['connect'],
|
||||
j['listen'],
|
||||
j['max_connections'],
|
||||
j['listen_address'],
|
||||
j['path'],
|
||||
j['url'])
|
||||
def to_json(self) -> dict:
|
||||
return self.__dict__
|
||||
|
||||
class VeilidConfigProtocol:
|
||||
udp: VeilidConfigUDP
|
||||
tcp: VeilidConfigTCP
|
||||
ws: VeilidConfigWS
|
||||
wss: VeilidConfigWSS
|
||||
|
||||
def __init__(self, udp: VeilidConfigUDP, tcp: VeilidConfigTCP, ws: VeilidConfigWS, wss: VeilidConfigWSS):
|
||||
self.udp = udp
|
||||
self.tcp = tcp
|
||||
self.ws = ws
|
||||
self.wss = wss
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
return VeilidConfigProtocol(
|
||||
VeilidConfigUDP.from_json(j['udp']),
|
||||
VeilidConfigTCP.from_json(j['tcp']),
|
||||
VeilidConfigWS.from_json(j['ws']),
|
||||
VeilidConfigWSS.from_json(j['wss']))
|
||||
def to_json(self) -> dict:
|
||||
return self.__dict__
|
||||
|
||||
|
||||
class VeilidConfigNetwork:
|
||||
connection_initial_timeout_ms: int
|
||||
connection_inactivity_timeout_ms: int
|
||||
max_connections_per_ip4: int
|
||||
max_connections_per_ip6_prefix: int
|
||||
max_connections_per_ip6_prefix_size: int
|
||||
max_connection_frequency_per_min: int
|
||||
client_whitelist_timeout_ms: int
|
||||
reverse_connection_receipt_time_ms: int
|
||||
hole_punch_receipt_time_ms: int
|
||||
routing_table: VeilidConfigRoutingTable
|
||||
rpc: VeilidConfigRPC
|
||||
dht: VeilidConfigDHT
|
||||
upnp: bool
|
||||
detect_address_changes: bool
|
||||
restricted_nat_retries: int
|
||||
tls: VeilidConfigTLS
|
||||
application: VeilidConfigApplication
|
||||
protocol: VeilidConfigProtocol
|
||||
|
||||
def __init__(self, connection_initial_timeout_ms: int, connection_inactivity_timeout_ms: int,
|
||||
max_connections_per_ip4: int, max_connections_per_ip6_prefix: int,
|
||||
max_connections_per_ip6_prefix_size: int, max_connection_frequency_per_min: int,
|
||||
client_whitelist_timeout_ms: int, reverse_connection_receipt_time_ms: int,
|
||||
hole_punch_receipt_time_ms: int, routing_table: VeilidConfigRoutingTable,
|
||||
rpc: VeilidConfigRPC, dht: VeilidConfigDHT, upnp: bool, detect_address_changes: bool,
|
||||
restricted_nat_retries: int, tls: VeilidConfigTLS, application: VeilidConfigApplication, protocol: VeilidConfigProtocol):
|
||||
|
||||
self.connection_initial_timeout_ms = connection_initial_timeout_ms
|
||||
self.connection_inactivity_timeout_ms = connection_inactivity_timeout_ms
|
||||
self.max_connections_per_ip4 = max_connections_per_ip4
|
||||
self.max_connections_per_ip6_prefix = max_connections_per_ip6_prefix
|
||||
self.max_connections_per_ip6_prefix_size = max_connections_per_ip6_prefix_size
|
||||
self.max_connection_frequency_per_min = max_connection_frequency_per_min
|
||||
self.client_whitelist_timeout_ms = client_whitelist_timeout_ms
|
||||
self.reverse_connection_receipt_time_ms = reverse_connection_receipt_time_ms
|
||||
self.hole_punch_receipt_time_ms = hole_punch_receipt_time_ms
|
||||
self.routing_table = routing_table
|
||||
self.rpc = rpc
|
||||
self.dht = dht
|
||||
self.upnp = upnp
|
||||
self.detect_address_changes = detect_address_changes
|
||||
self.restricted_nat_retries = restricted_nat_retries
|
||||
self.tls = tls
|
||||
self.application = application
|
||||
self.protocol = protocol
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
return VeilidConfigNetwork(
|
||||
j['connection_initial_timeout_ms'],
|
||||
j['connection_inactivity_timeout_ms'],
|
||||
j['max_connections_per_ip4'],
|
||||
j['max_connections_per_ip6_prefix'],
|
||||
j['max_connections_per_ip6_prefix_size'],
|
||||
j['max_connection_frequency_per_min'],
|
||||
j['client_whitelist_timeout_ms'],
|
||||
j['reverse_connection_receipt_time_ms'],
|
||||
j['hole_punch_receipt_time_ms'],
|
||||
VeilidConfigRoutingTable.from_json(j['routing_table']),
|
||||
VeilidConfigRPC.from_json(j['rpc']),
|
||||
VeilidConfigDHT.from_json(j['dht']),
|
||||
j['upnp'],
|
||||
j['detect_address_changes'],
|
||||
j['restricted_nat_retries'],
|
||||
VeilidConfigTLS.from_json(j['tls']),
|
||||
VeilidConfigApplication.from_json(j['application']),
|
||||
VeilidConfigProtocol.from_json(j['protocol']))
|
||||
def to_json(self) -> dict:
|
||||
return self.__dict__
|
||||
|
||||
class VeilidConfig:
|
||||
program_name: str
|
||||
namespace: str
|
||||
capabilities: VeilidConfigCapabilities
|
||||
protected_store: VeilidConfigProtectedStore
|
||||
table_store: VeilidConfigTableStore
|
||||
block_store: VeilidConfigBlockStore
|
||||
network: VeilidConfigNetwork
|
||||
|
||||
def __init__(self, program_name: str, namespace: str, capabilities: VeilidConfigCapabilities,
|
||||
protected_store: VeilidConfigProtectedStore, table_store: VeilidConfigTableStore,
|
||||
block_store: VeilidConfigBlockStore, network: VeilidConfigNetwork):
|
||||
|
||||
self.program_name = program_name
|
||||
self.namespace = namespace
|
||||
self.capabilities = capabilities
|
||||
self.protected_store = protected_store
|
||||
self.table_store = table_store
|
||||
self.block_store = block_store
|
||||
self.network = network
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
'''JSON object hook'''
|
||||
return VeilidConfig(j['program_name'], j['namespace'],
|
||||
VeilidConfigCapabilities.from_json(j['capabilities']),
|
||||
VeilidConfigProtectedStore.from_json(j['protected_store']),
|
||||
VeilidConfigTableStore.from_json(j['table_store']),
|
||||
VeilidConfigBlockStore.from_json(j['block_store']),
|
||||
VeilidConfigNetwork.from_json(j['network']))
|
||||
def to_json(self) -> dict:
|
||||
return self.__dict__
|
||||
|
||||
|
142
veilid-python/veilid/error.py
Normal file
142
veilid-python/veilid/error.py
Normal file
@@ -0,0 +1,142 @@
|
||||
from typing import Self, Any
|
||||
|
||||
class VeilidAPIError(Exception):
|
||||
"""Veilid API error exception base class"""
|
||||
pass
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
match j['kind']:
|
||||
case 'NotInitialized':
|
||||
return VeilidAPIErrorNotInitialized()
|
||||
case 'AlreadyInitialized':
|
||||
return VeilidAPIErrorAlreadyInitialized()
|
||||
case 'Timeout':
|
||||
return VeilidAPIErrorTimeout()
|
||||
case 'TryAgain':
|
||||
return VeilidAPIErrorTryAgain()
|
||||
case 'Shutdown':
|
||||
return VeilidAPIErrorShutdown()
|
||||
case 'InvalidTarget':
|
||||
return VeilidAPIErrorInvalidTarget()
|
||||
case 'NoConnection':
|
||||
return VeilidAPIErrorNoConnection(j['message'])
|
||||
case 'KeyNotFound':
|
||||
return VeilidAPIErrorKeyNotFound(j['key'])
|
||||
case 'Internal':
|
||||
return VeilidAPIErrorInternal(j['message'])
|
||||
case 'Unimplemented':
|
||||
return VeilidAPIErrorUnimplemented(j['message'])
|
||||
case 'ParseError':
|
||||
return VeilidAPIErrorParseError(j['message'], j['value'])
|
||||
case 'InvalidArgument':
|
||||
return VeilidAPIErrorInvalidArgument(j['context'], j['argument'], j['value'])
|
||||
case 'MissingArgument':
|
||||
return VeilidAPIErrorMissingArgument(j['context'], j['argument'])
|
||||
case 'Generic':
|
||||
return VeilidAPIErrorGeneric(j['message'])
|
||||
case _:
|
||||
return VeilidAPIError("Unknown exception type: {}".format(j['kind']))
|
||||
|
||||
|
||||
class VeilidAPIErrorNotInitialized(VeilidAPIError):
|
||||
"""Veilid was not initialized"""
|
||||
def __init__(self):
|
||||
super().__init__("Not initialized")
|
||||
|
||||
class VeilidAPIErrorAlreadyInitialized(VeilidAPIError):
|
||||
"""Veilid was already initialized"""
|
||||
def __init__(self):
|
||||
super().__init__("Already initialized")
|
||||
|
||||
class VeilidAPIErrorTimeout(VeilidAPIError):
|
||||
"""Veilid operation timed out"""
|
||||
def __init__(self):
|
||||
super().__init__("Timeout")
|
||||
|
||||
class VeilidAPIErrorTryAgain(VeilidAPIError):
|
||||
"""Operation could not be performed at this time, retry again later"""
|
||||
def __init__(self):
|
||||
super().__init__("Try again")
|
||||
|
||||
class VeilidAPIErrorShutdown(VeilidAPIError):
|
||||
"""Veilid was already shut down"""
|
||||
def __init__(self):
|
||||
super().__init__("Shutdown")
|
||||
|
||||
class VeilidAPIErrorInvalidTarget(VeilidAPIError):
|
||||
"""Target of operation is not valid"""
|
||||
def __init__(self):
|
||||
super().__init__("Invalid target")
|
||||
|
||||
class VeilidAPIErrorNoConnection(VeilidAPIError):
|
||||
"""Connection could not be established"""
|
||||
message: str
|
||||
def __init__(self, message: str):
|
||||
super().__init__("No connection")
|
||||
self.message = message
|
||||
|
||||
class VeilidAPIErrorKeyNotFound(VeilidAPIError):
|
||||
"""Key was not found"""
|
||||
key: str
|
||||
def __init__(self, key: str):
|
||||
super().__init__("Key not found")
|
||||
self.key = key
|
||||
|
||||
class VeilidAPIErrorInternal(VeilidAPIError):
|
||||
"""Veilid experienced an internal failure"""
|
||||
message: str
|
||||
def __init__(self, message: str):
|
||||
super().__init__("Internal")
|
||||
self.message = message
|
||||
|
||||
class VeilidAPIErrorUnimplemented(VeilidAPIError):
|
||||
"""Functionality is not yet implemented"""
|
||||
message: str
|
||||
def __init__(self, message: str):
|
||||
super().__init__("Unimplemented")
|
||||
self.message = message
|
||||
|
||||
class VeilidAPIErrorParseError(VeilidAPIError):
|
||||
"""Value was not in a parseable format"""
|
||||
message: str
|
||||
value: str
|
||||
def __init__(self, message: str, value: str):
|
||||
super().__init__("Parse error")
|
||||
self.message = message
|
||||
self.value = value
|
||||
|
||||
class VeilidAPIErrorInvalidArgument(VeilidAPIError):
|
||||
"""Argument is not valid in this context"""
|
||||
context: str
|
||||
argument: str
|
||||
value: str
|
||||
def __init__(self, context: str, argument: str, value: str):
|
||||
super().__init__("Invalid argument")
|
||||
self.context = context
|
||||
self.argument = argument
|
||||
self.value = value
|
||||
|
||||
class VeilidAPIErrorMissingArgument(VeilidAPIError):
|
||||
"""Required argument was missing"""
|
||||
context: str
|
||||
argument: str
|
||||
def __init__(self, context: str, argument: str):
|
||||
super().__init__("Missing argument")
|
||||
self.context = context
|
||||
self.argument = argument
|
||||
|
||||
class VeilidAPIErrorGeneric(VeilidAPIError):
|
||||
"""Generic error message"""
|
||||
message: str
|
||||
def __init__(self, message: str):
|
||||
super().__init__("Generic")
|
||||
self.message = message
|
||||
|
||||
|
||||
def raise_api_result(api_result: dict) -> Any:
|
||||
if "value" in api_result:
|
||||
return api_result["value"]
|
||||
elif "error" in api_result:
|
||||
raise VeilidAPIError.from_json(api_result["error"])
|
||||
else:
|
||||
raise ValueError("Invalid format for ApiResult")
|
636
veilid-python/veilid/json_api.py
Normal file
636
veilid-python/veilid/json_api.py
Normal file
@@ -0,0 +1,636 @@
|
||||
import json
|
||||
import asyncio
|
||||
from jsonschema import validators, exceptions
|
||||
|
||||
from typing import Callable, Awaitable, Mapping
|
||||
|
||||
from .api import *
|
||||
from .state import *
|
||||
from .config import *
|
||||
from .error import *
|
||||
from .types import *
|
||||
from .operations import *
|
||||
|
||||
##############################################################
|
||||
|
||||
import importlib.resources as importlib_resources
|
||||
from . import schema
|
||||
|
||||
def _get_schema_validator(schema):
|
||||
cls = validators.validator_for(schema)
|
||||
cls.check_schema(schema)
|
||||
validator = cls(schema)
|
||||
return validator
|
||||
|
||||
def _schema_validate(validator, instance):
|
||||
error = exceptions.best_match(validator.iter_errors(instance))
|
||||
if error is not None:
|
||||
raise error
|
||||
|
||||
_VALIDATOR_REQUEST = _get_schema_validator(json.loads((importlib_resources.files(schema) / 'Request.json').read_text()))
|
||||
_VALIDATOR_RECV_MESSAGE = _get_schema_validator(json.loads((importlib_resources.files(schema) / 'RecvMessage.json').read_text()))
|
||||
|
||||
|
||||
##############################################################
|
||||
|
||||
class _JsonVeilidAPI(VeilidAPI):
|
||||
reader: asyncio.StreamReader
|
||||
writer: asyncio.StreamWriter
|
||||
update_callback: Callable[[VeilidUpdate], Awaitable]
|
||||
handle_recv_messages_task: Optional[asyncio.Task]
|
||||
validate_schemas: bool
|
||||
# Shared Mutable State
|
||||
lock: asyncio.Lock
|
||||
next_id: int
|
||||
in_flight_requests: Mapping[str, asyncio.Future]
|
||||
|
||||
def __init__(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter, update_callback: Callable[[VeilidUpdate], Awaitable], validate_schema: bool = True):
|
||||
self.reader = reader
|
||||
self.writer = writer
|
||||
self.update_callback = update_callback
|
||||
self.validate_schema = validate_schema
|
||||
self.handle_recv_messages_task = None
|
||||
self.lock = asyncio.Lock()
|
||||
self.next_id = 1
|
||||
self.in_flight_requests = dict()
|
||||
|
||||
async def __aenter__(self) -> Self:
|
||||
return self
|
||||
|
||||
async def __aexit__(self, *excinfo):
|
||||
await self.close()
|
||||
|
||||
async def _cleanup_close(self):
|
||||
await self.lock.acquire()
|
||||
try:
|
||||
self.reader = None
|
||||
self.writer.close()
|
||||
await self.writer.wait_closed()
|
||||
self.writer = None
|
||||
|
||||
for (reqid, reqfuture) in self.in_flight_requests.items():
|
||||
reqfuture.cancel()
|
||||
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
async def close(self):
|
||||
# Take the task
|
||||
await self.lock.acquire()
|
||||
try:
|
||||
if self.handle_recv_messages_task is None:
|
||||
return
|
||||
handle_recv_messages_task = self.handle_recv_messages_task
|
||||
self.handle_recv_messages_task = None
|
||||
finally:
|
||||
self.lock.release()
|
||||
# Cancel it
|
||||
handle_recv_messages_task.cancel()
|
||||
try:
|
||||
await handle_recv_messages_task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
async def connect(host: str, port: int, update_callback: Callable[[VeilidUpdate], Awaitable]) -> Self:
|
||||
reader, writer = await asyncio.open_connection(host, port)
|
||||
veilid_api = _JsonVeilidAPI(reader, writer, update_callback)
|
||||
veilid_api.handle_recv_messages_task = asyncio.create_task(veilid_api.handle_recv_messages(), name = "JsonVeilidAPI.handle_recv_messages")
|
||||
return veilid_api
|
||||
|
||||
async def handle_recv_message_response(self, j: dict):
|
||||
id = j['id']
|
||||
await self.lock.acquire()
|
||||
try:
|
||||
# Get and remove the in-flight request
|
||||
reqfuture = self.in_flight_requests.pop(id, None)
|
||||
finally:
|
||||
self.lock.release()
|
||||
# Resolve the request's future to the response json
|
||||
if reqfuture is not None:
|
||||
reqfuture.set_result(j)
|
||||
|
||||
|
||||
async def handle_recv_messages(self):
|
||||
# Read lines until we're done
|
||||
try:
|
||||
while True:
|
||||
linebytes = await self.reader.readline()
|
||||
if not linebytes.endswith(b'\n'):
|
||||
break
|
||||
|
||||
# Parse line as ndjson
|
||||
j = json.loads(linebytes.strip())
|
||||
|
||||
if self.validate_schema:
|
||||
_schema_validate(_VALIDATOR_RECV_MESSAGE, j)
|
||||
|
||||
# Process the message
|
||||
if j['type'] == "Response":
|
||||
await self.handle_recv_message_response(j)
|
||||
elif j['type'] == "Update":
|
||||
await self.update_callback(VeilidUpdate.from_json(j))
|
||||
finally:
|
||||
await self._cleanup_close()
|
||||
|
||||
async def allocate_request_future(self, id: int) -> asyncio.Future:
|
||||
reqfuture = asyncio.get_running_loop().create_future()
|
||||
|
||||
await self.lock.acquire()
|
||||
try:
|
||||
self.in_flight_requests[id] = reqfuture
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
return reqfuture
|
||||
|
||||
async def cancel_request_future(self, id: int):
|
||||
await self.lock.acquire()
|
||||
try:
|
||||
reqfuture = self.in_flight_requests.pop(id, None)
|
||||
reqfuture.cancel()
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
def send_one_way_ndjson_request(self, op: Operation, **kwargs):
|
||||
|
||||
if self.writer is None:
|
||||
return
|
||||
|
||||
# Make NDJSON string for request
|
||||
# Always use id 0 because no reply will be received for one-way requests
|
||||
req = { "id": 0, "op": op }
|
||||
for k, v in kwargs.items():
|
||||
req[k] = v
|
||||
reqstr = VeilidJSONEncoder.dumps(req) + "\n"
|
||||
reqbytes = reqstr.encode()
|
||||
|
||||
if self.validate_schema:
|
||||
_schema_validate(_VALIDATOR_REQUEST, json.loads(reqbytes))
|
||||
|
||||
# Send to socket without waitings
|
||||
self.writer.write(reqbytes)
|
||||
|
||||
async def send_ndjson_request(self, op: Operation, validate: Optional[Callable[[dict, dict], None]] = None, **kwargs) -> dict:
|
||||
|
||||
# Get next id
|
||||
await self.lock.acquire()
|
||||
try:
|
||||
id = self.next_id
|
||||
self.next_id += 1
|
||||
writer = self.writer
|
||||
finally:
|
||||
self.lock.release()
|
||||
|
||||
# Make NDJSON string for request
|
||||
req = { "id": id, "op": op }
|
||||
for k, v in kwargs.items():
|
||||
req[k] = v
|
||||
reqstr = VeilidJSONEncoder.dumps(req) + "\n"
|
||||
reqbytes = reqstr.encode()
|
||||
|
||||
if self.validate_schema:
|
||||
_schema_validate(_VALIDATOR_REQUEST, json.loads(reqbytes))
|
||||
|
||||
# Allocate future for request
|
||||
reqfuture = await self.allocate_request_future(id)
|
||||
|
||||
# Send to socket
|
||||
try:
|
||||
writer.write(reqbytes)
|
||||
await writer.drain()
|
||||
except:
|
||||
# Send failed, release future
|
||||
await self.cancel_request_future(id)
|
||||
raise
|
||||
|
||||
# Wait for response
|
||||
response = await reqfuture
|
||||
|
||||
# Validate if we have a validator
|
||||
if response["op"] != req["op"]:
|
||||
raise ValueError("Response op does not match request op")
|
||||
if validate is not None:
|
||||
validate(req, response)
|
||||
|
||||
return response
|
||||
|
||||
async def control(self, args: list[str]) -> str:
|
||||
return raise_api_result(await self.send_ndjson_request(Operation.CONTROL, args = args))
|
||||
async def get_state(self) -> VeilidState:
|
||||
return VeilidState.from_json(raise_api_result(await self.send_ndjson_request(Operation.GET_STATE)))
|
||||
async def attach(self):
|
||||
raise_api_result(await self.send_ndjson_request(Operation.ATTACH))
|
||||
async def detach(self):
|
||||
raise_api_result(await self.send_ndjson_request(Operation.DETACH))
|
||||
async def new_private_route(self) -> NewPrivateRouteResult:
|
||||
return NewPrivateRouteResult.from_json(raise_api_result(await self.send_ndjson_request(Operation.NEW_PRIVATE_ROUTE)))
|
||||
async def new_custom_private_route(self, kinds: list[CryptoKind], stability: Stability, sequencing: Sequencing) -> NewPrivateRouteResult:
|
||||
return NewPrivateRouteResult.from_json(raise_api_result(
|
||||
await self.send_ndjson_request(Operation.NEW_CUSTOM_PRIVATE_ROUTE,
|
||||
kinds = kinds,
|
||||
stability = stability,
|
||||
sequencing = sequencing)
|
||||
))
|
||||
async def import_remote_private_route(self, blob: bytes) -> RouteId:
|
||||
return RouteId(raise_api_result(
|
||||
await self.send_ndjson_request(Operation.IMPORT_REMOTE_PRIVATE_ROUTE,
|
||||
blob = blob)
|
||||
))
|
||||
async def release_private_route(self, route_id: RouteId):
|
||||
raise_api_result(
|
||||
await self.send_ndjson_request(Operation.RELEASE_PRIVATE_ROUTE,
|
||||
route_id = route_id)
|
||||
)
|
||||
async def app_call_reply(self, call_id: OperationId, message: bytes):
|
||||
raise_api_result(
|
||||
await self.send_ndjson_request(Operation.APP_CALL_REPLY,
|
||||
call_id = call_id,
|
||||
message = message)
|
||||
)
|
||||
async def new_routing_context(self) -> RoutingContext:
|
||||
rc_id = raise_api_result(await self.send_ndjson_request(Operation.NEW_ROUTING_CONTEXT))
|
||||
return _JsonRoutingContext(self, rc_id)
|
||||
async def open_table_db(self, name: str, column_count: int) -> TableDb:
|
||||
db_id = raise_api_result(await self.send_ndjson_request(Operation.OPEN_TABLE_DB,
|
||||
name = name,
|
||||
column_count = column_count))
|
||||
return _JsonTableDb(self, db_id)
|
||||
async def delete_table_db(self, name: str):
|
||||
return raise_api_result(await self.send_ndjson_request(Operation.DELETE_TABLE_DB,
|
||||
name = name))
|
||||
async def get_crypto_system(self, kind: CryptoKind) -> CryptoSystem:
|
||||
cs_id = raise_api_result(await self.send_ndjson_request(Operation.GET_CRYPTO_SYSTEM,
|
||||
kind = kind))
|
||||
return _JsonCryptoSystem(self, cs_id)
|
||||
async def best_crypto_system(self) -> CryptoSystem:
|
||||
cs_id = raise_api_result(await self.send_ndjson_request(Operation.BEST_CRYPTO_SYSTEM))
|
||||
return _JsonCryptoSystem(self, cs_id)
|
||||
async def verify_signatures(self, node_ids: list[TypedKey], data: bytes, signatures: list[TypedSignature]) -> list[TypedKey]:
|
||||
return list(map(lambda x: TypedKey(x), raise_api_result(await self.send_ndjson_request(Operation.VERIFY_SIGNATURES,
|
||||
node_ids = node_ids,
|
||||
data = data,
|
||||
signatures = signatures))))
|
||||
async def generate_signatures(self, data: bytes, key_pairs: list[TypedKeyPair]) -> list[TypedSignature]:
|
||||
return list(map(lambda x: TypedSignature(x), raise_api_result(await self.send_ndjson_request(Operation.GENERATE_SIGNATURES,
|
||||
data = data,
|
||||
key_pairs = key_pairs))))
|
||||
async def generate_key_pair(self, kind: CryptoKind) -> list[TypedKeyPair]:
|
||||
return list(map(lambda x: TypedKeyPair(x), raise_api_result(await self.send_ndjson_request(Operation.GENERATE_KEY_PAIR,
|
||||
kind = kind))))
|
||||
async def now(self) -> Timestamp:
|
||||
return Timestamp(raise_api_result(await self.send_ndjson_request(Operation.NOW)))
|
||||
async def debug(self, command: str) -> str:
|
||||
return raise_api_result(await self.send_ndjson_request(Operation.DEBUG,
|
||||
command = command
|
||||
))
|
||||
async def veilid_version_string(self) -> str:
|
||||
return raise_api_result(await self.send_ndjson_request(Operation.VEILID_VERSION_STRING))
|
||||
async def veilid_version(self) -> VeilidVersion:
|
||||
v = await self.send_ndjson_request(Operation.VEILID_VERSION)
|
||||
return VeilidVersion(v['major'], v['minor'], v['patch'])
|
||||
|
||||
######################################################
|
||||
|
||||
def validate_rc_op(request: dict, response: dict):
|
||||
if response['rc_op'] != request['rc_op']:
|
||||
raise ValueError("Response rc_op does not match request rc_op")
|
||||
|
||||
class _JsonRoutingContext(RoutingContext):
|
||||
api: _JsonVeilidAPI
|
||||
rc_id: int
|
||||
|
||||
def __init__(self, api: _JsonVeilidAPI, rc_id: int):
|
||||
self.api = api
|
||||
self.rc_id = rc_id
|
||||
|
||||
def __del__(self):
|
||||
self.api.send_one_way_ndjson_request(Operation.ROUTING_CONTEXT,
|
||||
rc_id = self.rc_id,
|
||||
rc_op = RoutingContextOperation.RELEASE)
|
||||
|
||||
async def with_privacy(self) -> Self:
|
||||
new_rc_id = raise_api_result(await self.api.send_ndjson_request(Operation.ROUTING_CONTEXT, validate=validate_rc_op,
|
||||
rc_id = self.rc_id,
|
||||
rc_op = RoutingContextOperation.WITH_PRIVACY))
|
||||
return _JsonRoutingContext(self.api, new_rc_id)
|
||||
|
||||
async def with_custom_privacy(self, stability: Stability) -> Self:
|
||||
new_rc_id = raise_api_result(await self.api.send_ndjson_request(Operation.ROUTING_CONTEXT, validate=validate_rc_op,
|
||||
rc_id = self.rc_id,
|
||||
rc_op = RoutingContextOperation.WITH_CUSTOM_PRIVACY,
|
||||
stability = stability))
|
||||
return _JsonRoutingContext(self.api, new_rc_id)
|
||||
async def with_sequencing(self, sequencing: Sequencing) -> Self:
|
||||
new_rc_id = raise_api_result(await self.api.send_ndjson_request(Operation.ROUTING_CONTEXT, validate=validate_rc_op,
|
||||
rc_id = self.rc_id,
|
||||
rc_op = RoutingContextOperation.WITH_SEQUENCING,
|
||||
sequencing = sequencing))
|
||||
return _JsonRoutingContext(self.api, new_rc_id)
|
||||
async def app_call(self, target: TypedKey | RouteId, request: bytes) -> bytes:
|
||||
return urlsafe_b64decode_no_pad(raise_api_result(await self.api.send_ndjson_request(Operation.ROUTING_CONTEXT, validate=validate_rc_op,
|
||||
rc_id = self.rc_id,
|
||||
rc_op = RoutingContextOperation.APP_CALL,
|
||||
target = target,
|
||||
request = request)))
|
||||
async def app_message(self, target: TypedKey | RouteId, message: bytes):
|
||||
raise_api_result(await self.api.send_ndjson_request(Operation.ROUTING_CONTEXT, validate=validate_rc_op,
|
||||
rc_id = self.rc_id,
|
||||
rc_op = RoutingContextOperation.APP_MESSAGE,
|
||||
target = target,
|
||||
message = message))
|
||||
async def create_dht_record(self, kind: CryptoKind, schema: DHTSchema) -> DHTRecordDescriptor:
|
||||
return DHTRecordDescriptor.from_json(raise_api_result(await self.api.send_ndjson_request(Operation.ROUTING_CONTEXT, validate=validate_rc_op,
|
||||
rc_id = self.rc_id,
|
||||
rc_op = RoutingContextOperation.CREATE_DHT_RECORD,
|
||||
kind = kind,
|
||||
schema = schema)))
|
||||
async def open_dht_record(self, key: TypedKey, writer: Optional[KeyPair]) -> DHTRecordDescriptor:
|
||||
return DHTRecordDescriptor.from_json(raise_api_result(await self.api.send_ndjson_request(Operation.ROUTING_CONTEXT, validate=validate_rc_op,
|
||||
rc_id = self.rc_id,
|
||||
rc_op = RoutingContextOperation.OPEN_DHT_RECORD,
|
||||
key = key,
|
||||
writer = writer)))
|
||||
async def close_dht_record(self, key: TypedKey):
|
||||
raise_api_result(await self.api.send_ndjson_request(Operation.ROUTING_CONTEXT, validate=validate_rc_op,
|
||||
rc_id = self.rc_id,
|
||||
rc_op = RoutingContextOperation.CLOSE_DHT_RECORD,
|
||||
key = key))
|
||||
async def delete_dht_record(self, key: TypedKey):
|
||||
raise_api_result(await self.api.send_ndjson_request(Operation.ROUTING_CONTEXT, validate=validate_rc_op,
|
||||
rc_id = self.rc_id,
|
||||
rc_op = RoutingContextOperation.DELETE_DHT_RECORD,
|
||||
key = key))
|
||||
async def get_dht_value(self, key: TypedKey, subkey: ValueSubkey, force_refresh: bool) -> Optional[ValueData]:
|
||||
ret = raise_api_result(await self.api.send_ndjson_request(Operation.ROUTING_CONTEXT, validate=validate_rc_op,
|
||||
rc_id = self.rc_id,
|
||||
rc_op = RoutingContextOperation.GET_DHT_VALUE,
|
||||
key = key,
|
||||
subkey = subkey,
|
||||
force_refresh = force_refresh))
|
||||
return None if ret is None else ValueData.from_json(ret)
|
||||
async def set_dht_value(self, key: TypedKey, subkey: ValueSubkey, data: bytes) -> Optional[ValueData]:
|
||||
ret = raise_api_result(await self.api.send_ndjson_request(Operation.ROUTING_CONTEXT, validate=validate_rc_op,
|
||||
rc_id = self.rc_id,
|
||||
rc_op = RoutingContextOperation.SET_DHT_VALUE,
|
||||
key = key,
|
||||
subkey = subkey,
|
||||
data = data))
|
||||
return None if ret is None else ValueData.from_json(ret)
|
||||
async def watch_dht_values(self, key: TypedKey, subkeys: list[(ValueSubkey, ValueSubkey)], expiration: Timestamp, count: int) -> Timestamp:
|
||||
return Timestamp(raise_api_result(await self.api.send_ndjson_request(Operation.ROUTING_CONTEXT, validate=validate_rc_op,
|
||||
rc_id = self.rc_id,
|
||||
rc_op = RoutingContextOperation.WATCH_DHT_VALUES,
|
||||
key = key,
|
||||
subkeys = subkeys,
|
||||
expiration = expiration,
|
||||
count = count)))
|
||||
async def cancel_dht_watch(self, key: TypedKey, subkeys: list[(ValueSubkey, ValueSubkey)]) -> bool:
|
||||
return raise_api_result(await self.api.send_ndjson_request(Operation.ROUTING_CONTEXT, validate=validate_rc_op,
|
||||
rc_id = self.rc_id,
|
||||
rc_op = RoutingContextOperation.CANCEL_DHT_WATCH,
|
||||
key = key,
|
||||
subkeys = subkeys))
|
||||
|
||||
######################################################
|
||||
|
||||
def validate_tx_op(request: dict, response: dict):
|
||||
if response['tx_op'] != request['tx_op']:
|
||||
raise ValueError("Response tx_op does not match request tx_op")
|
||||
|
||||
class _JsonTableDbTransaction(TableDbTransaction):
|
||||
api: _JsonVeilidAPI
|
||||
tx_id: int
|
||||
done: bool
|
||||
|
||||
def __init__(self, api: _JsonVeilidAPI, tx_id: int):
|
||||
self.api = api
|
||||
self.tx_id = tx_id
|
||||
self.done = False
|
||||
|
||||
def __del__(self):
|
||||
if not self.done:
|
||||
raise AssertionError("Should have committed or rolled back transaction")
|
||||
|
||||
async def commit(self):
|
||||
raise_api_result(await self.api.send_ndjson_request(Operation.TABLE_DB_TRANSACTION, validate=validate_tx_op,
|
||||
tx_id = self.tx_id,
|
||||
tx_op = TableDbTransactionOperation.COMMIT))
|
||||
self.done = True
|
||||
async def rollback(self):
|
||||
raise_api_result(await self.api.send_ndjson_request(Operation.TABLE_DB_TRANSACTION, validate=validate_tx_op,
|
||||
tx_id = self.tx_id,
|
||||
tx_op = TableDbTransactionOperation.ROLLBACK))
|
||||
self.done = True
|
||||
async def store(self, col: int, key: bytes, value: bytes):
|
||||
await self.api.send_ndjson_request(Operation.TABLE_DB_TRANSACTION, validate=validate_tx_op,
|
||||
tx_id = self.tx_id,
|
||||
tx_op = TableDbTransactionOperation.STORE,
|
||||
col = col,
|
||||
key = key,
|
||||
value = value)
|
||||
async def delete(self, col: int, key: bytes):
|
||||
await self.api.send_ndjson_request(Operation.TABLE_DB_TRANSACTION, validate=validate_tx_op,
|
||||
tx_id = self.tx_id,
|
||||
tx_op = TableDbTransactionOperation.DELETE,
|
||||
col = col,
|
||||
key = key)
|
||||
|
||||
######################################################
|
||||
|
||||
def validate_db_op(request: dict, response: dict):
|
||||
if response['db_op'] != request['db_op']:
|
||||
raise ValueError("Response db_op does not match request db_op")
|
||||
|
||||
class _JsonTableDb(TableDb):
|
||||
api: _JsonVeilidAPI
|
||||
db_id: int
|
||||
|
||||
def __init__(self, api: _JsonVeilidAPI, db_id: int):
|
||||
self.api = api
|
||||
self.db_id = db_id
|
||||
|
||||
def __del__(self):
|
||||
self.api.send_one_way_ndjson_request(Operation.TABLE_DB,
|
||||
db_id = self.db_id,
|
||||
rc_op = TableDbOperation.RELEASE)
|
||||
|
||||
async def get_column_count(self) -> int:
|
||||
return raise_api_result(await self.api.send_ndjson_request(Operation.TABLE_DB, validate=validate_db_op,
|
||||
db_id = self.db_id,
|
||||
db_op = TableDbOperation.GET_COLUMN_COUNT))
|
||||
async def get_keys(self, col: int) -> list[bytes]:
|
||||
return list(map(lambda x: urlsafe_b64decode_no_pad(x), raise_api_result(await self.api.send_ndjson_request(Operation.TABLE_DB, validate=validate_db_op,
|
||||
db_id = self.db_id,
|
||||
db_op = TableDbOperation.GET_KEYS,
|
||||
col = col))))
|
||||
async def transact(self) -> TableDbTransaction:
|
||||
tx_id = raise_api_result(await self.api.send_ndjson_request(Operation.TABLE_DB, validate=validate_db_op,
|
||||
db_id = self.db_id,
|
||||
db_op = TableDbOperation.TRANSACT))
|
||||
return _JsonTableDbTransaction(self.api, tx_id)
|
||||
async def store(self, col: int, key: bytes, value: bytes):
|
||||
return raise_api_result(await self.api.send_ndjson_request(Operation.TABLE_DB, validate=validate_db_op,
|
||||
db_id = self.db_id,
|
||||
db_op = TableDbOperation.STORE,
|
||||
col = col,
|
||||
key = key,
|
||||
value = value))
|
||||
async def load(self, col: int, key: bytes) -> Optional[bytes]:
|
||||
res = raise_api_result(await self.api.send_ndjson_request(Operation.TABLE_DB, validate=validate_db_op,
|
||||
db_id = self.db_id,
|
||||
db_op = TableDbOperation.LOAD,
|
||||
col = col,
|
||||
key = key))
|
||||
return None if res is None else urlsafe_b64decode_no_pad(res)
|
||||
async def delete(self, col: int, key: bytes) -> Optional[bytes]:
|
||||
res = raise_api_result(await self.api.send_ndjson_request(Operation.TABLE_DB, validate=validate_db_op,
|
||||
db_id = self.db_id,
|
||||
db_op = TableDbOperation.DELETE,
|
||||
col = col,
|
||||
key = key))
|
||||
return None if res is None else urlsafe_b64decode_no_pad(res)
|
||||
|
||||
######################################################
|
||||
|
||||
|
||||
|
||||
def validate_cs_op(request: dict, response: dict):
|
||||
if response['cs_op'] != request['cs_op']:
|
||||
raise ValueError("Response cs_op does not match request cs_op")
|
||||
|
||||
class _JsonCryptoSystem(CryptoSystem):
|
||||
api: _JsonVeilidAPI
|
||||
cs_id: int
|
||||
|
||||
def __init__(self, api: _JsonVeilidAPI, cs_id: int):
|
||||
self.api = api
|
||||
self.cs_id = cs_id
|
||||
|
||||
def __del__(self):
|
||||
self.api.send_one_way_ndjson_request(Operation.CRYPTO_SYSTEM,
|
||||
cs_id = self.cs_id,
|
||||
cs_op = CryptoSystemOperation.RELEASE)
|
||||
|
||||
async def cached_dh(self, key: PublicKey, secret: SecretKey) -> SharedSecret:
|
||||
return SharedSecret(raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op,
|
||||
cs_id = self.cs_id,
|
||||
cs_op = CryptoSystemOperation.CACHED_DH,
|
||||
key = key,
|
||||
secret = secret)))
|
||||
async def compute_dh(self, key: PublicKey, secret: SecretKey) -> SharedSecret:
|
||||
return SharedSecret(raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op,
|
||||
cs_id = self.cs_id,
|
||||
cs_op = CryptoSystemOperation.COMPUTE_DH,
|
||||
key = key,
|
||||
secret = secret)))
|
||||
async def random_bytes(self, len: int) -> bytes:
|
||||
return urlsafe_b64decode_no_pad(raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op,
|
||||
cs_id = self.cs_id,
|
||||
cs_op = CryptoSystemOperation.RANDOM_BYTES,
|
||||
len = len)))
|
||||
async def default_salt_length(self) -> int:
|
||||
return raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op,
|
||||
cs_id = self.cs_id,
|
||||
cs_op = CryptoSystemOperation.DEFAULT_SALT_LENGTH))
|
||||
async def hash_password(self, password: bytes, salt: bytes) -> str:
|
||||
return raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op,
|
||||
cs_id = self.cs_id,
|
||||
cs_op = CryptoSystemOperation.HASH_PASSWORD,
|
||||
password = password,
|
||||
salt = salt))
|
||||
async def verify_password(self, password: bytes, password_hash: str) -> bool:
|
||||
return raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op,
|
||||
cs_id = self.cs_id,
|
||||
cs_op = CryptoSystemOperation.VERIFY_PASSWORD,
|
||||
password = password,
|
||||
password_hash = password_hash))
|
||||
async def derive_shared_secret(self, password: bytes, salt: bytes) -> SharedSecret:
|
||||
return SharedSecret(raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op,
|
||||
cs_id = self.cs_id,
|
||||
cs_op = CryptoSystemOperation.DERIVE_SHARED_SECRET,
|
||||
password = password,
|
||||
salt = salt)))
|
||||
async def random_nonce(self) -> Nonce:
|
||||
return Nonce(raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op,
|
||||
cs_id = self.cs_id,
|
||||
cs_op = CryptoSystemOperation.RANDOM_NONCE)))
|
||||
async def random_shared_secret(self) -> SharedSecret:
|
||||
return SharedSecret(raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op,
|
||||
cs_id = self.cs_id,
|
||||
cs_op = CryptoSystemOperation.RANDOM_SHARED_SECRET)))
|
||||
async def generate_key_pair(self) -> KeyPair:
|
||||
return KeyPair(raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op,
|
||||
cs_id = self.cs_id,
|
||||
cs_op = CryptoSystemOperation.GENERATE_KEY_PAIR)))
|
||||
async def generate_hash(self, data: bytes) -> HashDigest:
|
||||
return HashDigest(raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op,
|
||||
cs_id = self.cs_id,
|
||||
cs_op = CryptoSystemOperation.GENERATE_HASH,
|
||||
data = data)))
|
||||
async def validate_key_pair(self, key: PublicKey, secret: SecretKey) -> bool:
|
||||
return raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op,
|
||||
cs_id = self.cs_id,
|
||||
cs_op = CryptoSystemOperation.VALIDATE_KEY_PAIR,
|
||||
key = key,
|
||||
secret = secret))
|
||||
async def validate_hash(self, data: bytes, hash_digest: HashDigest) -> bool:
|
||||
return raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op,
|
||||
cs_id = self.cs_id,
|
||||
cs_op = CryptoSystemOperation.VALIDATE_HASH,
|
||||
data = data,
|
||||
hash_digest = hash_digest))
|
||||
async def distance(self, key1: CryptoKey, key2: CryptoKey) -> CryptoKeyDistance:
|
||||
return CryptoKeyDistance(raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op,
|
||||
cs_id = self.cs_id,
|
||||
cs_op = CryptoSystemOperation.DISTANCE,
|
||||
key1 = key1,
|
||||
key2 = key2)))
|
||||
async def sign(self, key: PublicKey, secret: SecretKey, data: bytes) -> Signature:
|
||||
return Signature(raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op,
|
||||
cs_id = self.cs_id,
|
||||
cs_op = CryptoSystemOperation.SIGN,
|
||||
key = key,
|
||||
secret = secret,
|
||||
data = data)))
|
||||
async def verify(self, key: PublicKey, data: bytes, signature: Signature):
|
||||
raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op,
|
||||
cs_id = self.cs_id,
|
||||
cs_op = CryptoSystemOperation.VERIFY,
|
||||
key = key,
|
||||
data = data,
|
||||
signature = signature))
|
||||
async def aead_overhead(self) -> int:
|
||||
return raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op,
|
||||
cs_id = self.cs_id,
|
||||
cs_op = CryptoSystemOperation.AEAD_OVERHEAD))
|
||||
async def decrypt_aead(self, body: bytes, nonce: Nonce, shared_secret: SharedSecret, associated_data: Optional[bytes]) -> bytes:
|
||||
return urlsafe_b64decode_no_pad(raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op,
|
||||
cs_id = self.cs_id,
|
||||
cs_op = CryptoSystemOperation.DECRYPT_AEAD,
|
||||
body = body,
|
||||
nonce = nonce,
|
||||
shared_secret = shared_secret,
|
||||
associated_data = associated_data)))
|
||||
async def encrypt_aead(self, body: bytes, nonce: Nonce, shared_secret: SharedSecret, associated_data: Optional[bytes]) -> bytes:
|
||||
return urlsafe_b64decode_no_pad(raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op,
|
||||
cs_id = self.cs_id,
|
||||
cs_op = CryptoSystemOperation.ENCRYPT_AEAD,
|
||||
body = body,
|
||||
nonce = nonce,
|
||||
shared_secret = shared_secret,
|
||||
associated_data = associated_data)))
|
||||
async def crypt_no_auth(self, body: bytes, nonce: Nonce, shared_secret: SharedSecret) -> bytes:
|
||||
return urlsafe_b64decode_no_pad(raise_api_result(await self.api.send_ndjson_request(Operation.CRYPTO_SYSTEM, validate=validate_cs_op,
|
||||
cs_id = self.cs_id,
|
||||
cs_op = CryptoSystemOperation.CRYPT_NO_AUTH,
|
||||
body = body,
|
||||
nonce = nonce,
|
||||
shared_secret = shared_secret)))
|
||||
|
||||
|
||||
######################################################
|
||||
|
||||
async def json_api_connect(host:str, port:int, update_callback: Callable[[VeilidUpdate], Awaitable]) -> VeilidAPI:
|
||||
return await _JsonVeilidAPI.connect(host, port, update_callback)
|
||||
|
91
veilid-python/veilid/operations.py
Normal file
91
veilid-python/veilid/operations.py
Normal file
@@ -0,0 +1,91 @@
|
||||
from enum import StrEnum
|
||||
from typing import Self
|
||||
|
||||
class Operation(StrEnum):
|
||||
CONTROL = "Control"
|
||||
GET_STATE = "GetState"
|
||||
ATTACH = "Attach"
|
||||
DETACH = "Detach"
|
||||
NEW_PRIVATE_ROUTE = "NewPrivateRoute"
|
||||
NEW_CUSTOM_PRIVATE_ROUTE = "NewCustomPrivateRoute"
|
||||
IMPORT_REMOTE_PRIVATE_ROUTE = "ImportRemotePrivateRoute"
|
||||
RELEASE_PRIVATE_ROUTE = "ReleasePrivateRoute"
|
||||
APP_CALL_REPLY = "AppCallReply"
|
||||
NEW_ROUTING_CONTEXT = "NewRoutingContext"
|
||||
ROUTING_CONTEXT = "RoutingContext"
|
||||
OPEN_TABLE_DB = "OpenTableDb"
|
||||
DELETE_TABLE_DB = "DeleteTableDb"
|
||||
TABLE_DB = "TableDb"
|
||||
TABLE_DB_TRANSACTION = "TableDbTransaction"
|
||||
GET_CRYPTO_SYSTEM = "GetCryptoSystem"
|
||||
BEST_CRYPTO_SYSTEM = "BestCryptoSystem"
|
||||
CRYPTO_SYSTEM = "CryptoSystem"
|
||||
VERIFY_SIGNATURES = "VerifySignatures"
|
||||
GENERATE_SIGNATURES = "GenerateSignatures"
|
||||
GENERATE_KEY_PAIR = "GenerateKeyPair"
|
||||
NOW = "Now"
|
||||
DEBUG = "Debug"
|
||||
VEILID_VERSION_STRING = "VeilidVersionString"
|
||||
VEILID_VERSION = "VeilidVersion"
|
||||
|
||||
class RoutingContextOperation(StrEnum):
|
||||
INVALID_ID = "InvalidId"
|
||||
RELEASE = "Release"
|
||||
WITH_PRIVACY = "WithPrivacy"
|
||||
WITH_CUSTOM_PRIVACY = "WithCustomPrivacy"
|
||||
WITH_SEQUENCING = "WithSequencing"
|
||||
APP_CALL = "AppCall"
|
||||
APP_MESSAGE = "AppMessage"
|
||||
CREATE_DHT_RECORD = "CreateDhtRecord"
|
||||
OPEN_DHT_RECORD = "OpenDhtRecord"
|
||||
CLOSE_DHT_RECORD = "CloseDhtRecord"
|
||||
DELETE_DHT_RECORD = "DeleteDhtRecord"
|
||||
GET_DHT_VALUE = "GetDhtValue"
|
||||
SET_DHT_VALUE = "SetDhtValue"
|
||||
WATCH_DHT_VALUES = "WatchDhtValues"
|
||||
CANCEL_DHT_WATCH = "CancelDhtWatch"
|
||||
|
||||
class TableDbOperation(StrEnum):
|
||||
INVALID_ID = "InvalidId"
|
||||
RELEASE = "Release"
|
||||
GET_COLUMN_COUNT = "GetColumnCount"
|
||||
GET_KEYS = "GetKeys"
|
||||
TRANSACT = "Transact"
|
||||
STORE = "Store"
|
||||
LOAD = "Load"
|
||||
DELETE = "Delete"
|
||||
|
||||
class TableDbTransactionOperation(StrEnum):
|
||||
INVALID_ID = "InvalidId"
|
||||
COMMIT = "Commit"
|
||||
ROLLBACK = "Rollback"
|
||||
STORE = "Store"
|
||||
DELETE = "Delete"
|
||||
|
||||
class CryptoSystemOperation(StrEnum):
|
||||
INVALID_ID = "InvalidId"
|
||||
RELEASE = "Release"
|
||||
CACHED_DH = "CachedDh"
|
||||
COMPUTE_DH = "ComputeDh"
|
||||
RANDOM_BYTES = "RandomBytes"
|
||||
DEFAULT_SALT_LENGTH = "DefaultSaltLength"
|
||||
HASH_PASSWORD = "HashPassword"
|
||||
VERIFY_PASSWORD = "VerifyPassword"
|
||||
DERIVE_SHARED_SECRET = "DeriveSharedSecret"
|
||||
RANDOM_NONCE = "RandomNonce"
|
||||
RANDOM_SHARED_SECRET = "RandomSharedSecret"
|
||||
GENERATE_KEY_PAIR = "GenerateKeyPair"
|
||||
GENERATE_HASH = "GenerateHash"
|
||||
VALIDATE_KEY_PAIR = "ValidateKeyPair"
|
||||
VALIDATE_HASH = "ValidateHash"
|
||||
DISTANCE = "Distance"
|
||||
SIGN = "Sign"
|
||||
VERIFY = "Verify"
|
||||
AEAD_OVERHEAD = "AeadOverhead"
|
||||
DECRYPT_AEAD = "DecryptAead"
|
||||
ENCRYPT_AEAD = "EncryptAead"
|
||||
CRYPT_NO_AUTH = "CryptNoAuth"
|
||||
|
||||
class RecvMessageType(StrEnum):
|
||||
RESPONSE = "Response"
|
||||
UPDATE = "Update"
|
3924
veilid-python/veilid/schema/RecvMessage.json
Normal file
3924
veilid-python/veilid/schema/RecvMessage.json
Normal file
File diff suppressed because it is too large
Load Diff
1585
veilid-python/veilid/schema/Request.json
Normal file
1585
veilid-python/veilid/schema/Request.json
Normal file
File diff suppressed because it is too large
Load Diff
350
veilid-python/veilid/state.py
Normal file
350
veilid-python/veilid/state.py
Normal file
@@ -0,0 +1,350 @@
|
||||
from typing import Self, Optional
|
||||
from enum import StrEnum
|
||||
|
||||
from .types import *
|
||||
from .config import *
|
||||
|
||||
class AttachmentState(StrEnum):
|
||||
DETACHED = 'Detached'
|
||||
ATTACHING = 'Attaching'
|
||||
ATTACHED_WEAK = 'AttachedWeak'
|
||||
ATTACHED_GOOD = 'AttachedGood'
|
||||
ATTACHED_STRONG = 'AttachedStrong'
|
||||
FULLY_ATTACHED = 'FullyAttached'
|
||||
OVER_ATTACHED = 'OverAttached'
|
||||
DETACHING = 'Detaching'
|
||||
|
||||
class VeilidStateAttachment:
|
||||
state: AttachmentState
|
||||
public_internet_ready: bool
|
||||
local_network_ready: bool
|
||||
|
||||
def __init__(self, state: AttachmentState, public_internet_ready: bool, local_network_ready: bool):
|
||||
self.state = state
|
||||
self.public_internet_ready = public_internet_ready
|
||||
self.local_network_ready = local_network_ready
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
'''JSON object hook'''
|
||||
return VeilidStateAttachment(
|
||||
AttachmentState(j['state']),
|
||||
j['public_internet_ready'],
|
||||
j['local_network_ready'])
|
||||
|
||||
class RPCStats:
|
||||
messages_sent: int
|
||||
messages_rcvd: int
|
||||
questions_in_flight: int
|
||||
last_question_ts: Optional[Timestamp]
|
||||
last_seen_ts: Optional[Timestamp]
|
||||
first_consecutive_seen_ts: Optional[Timestamp]
|
||||
recent_lost_answers: int
|
||||
failed_to_send: int
|
||||
|
||||
def __init__(self, messages_sent: int, messages_rcvd: int, questions_in_flight: int,
|
||||
last_question_ts: Optional[Timestamp], last_seen_ts: Optional[Timestamp],
|
||||
first_consecutive_seen_ts: Optional[Timestamp], recent_lost_answers: int, failed_to_send: int):
|
||||
self.messages_sent = messages_sent
|
||||
self.messages_rcvd = messages_rcvd
|
||||
self.questions_in_flight = questions_in_flight
|
||||
self.last_question_ts = last_question_ts
|
||||
self.last_seen_ts = last_seen_ts
|
||||
self.first_consecutive_seen_ts = first_consecutive_seen_ts
|
||||
self.recent_lost_answers = recent_lost_answers
|
||||
self.failed_to_send = failed_to_send
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
'''JSON object hook'''
|
||||
return RPCStats(
|
||||
j['messages_sent'],
|
||||
j['messages_rcvd'],
|
||||
j['questions_in_flight'],
|
||||
None if j['last_question_ts'] is None else Timestamp(j['last_question_ts']),
|
||||
None if j['last_seen_ts'] is None else Timestamp(j['last_seen_ts']),
|
||||
None if j['first_consecutive_seen_ts'] is None else Timestamp(j['first_consecutive_seen_ts']),
|
||||
j['recent_lost_answers'],
|
||||
j['failed_to_send'])
|
||||
|
||||
class LatencyStats:
|
||||
fastest: TimestampDuration
|
||||
average: TimestampDuration
|
||||
slowest: TimestampDuration
|
||||
|
||||
def __init__(self, fastest: TimestampDuration, average: TimestampDuration, slowest: TimestampDuration):
|
||||
self.fastest = fastest
|
||||
self.average = average
|
||||
self.slowest = slowest
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
'''JSON object hook'''
|
||||
return LatencyStats(
|
||||
TimestampDuration(j['fastest']),
|
||||
TimestampDuration(j['average']),
|
||||
TimestampDuration(j['slowest']))
|
||||
|
||||
|
||||
class TransferStats:
|
||||
total: ByteCount
|
||||
maximum: ByteCount
|
||||
average: ByteCount
|
||||
minimum: ByteCount
|
||||
|
||||
def __init__(self, total: ByteCount, maximum: ByteCount, average: ByteCount, minimum: ByteCount):
|
||||
self.total = total
|
||||
self.maximum = maximum
|
||||
self.average = average
|
||||
self.minimum = minimum
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
'''JSON object hook'''
|
||||
return TransferStats(
|
||||
ByteCount(j['total']),
|
||||
ByteCount(j['maximum']),
|
||||
ByteCount(j['average']),
|
||||
ByteCount(j['minimum']))
|
||||
|
||||
|
||||
class TransferStatsDownUp:
|
||||
down: TransferStats
|
||||
up: TransferStats
|
||||
|
||||
def __init__(self, down: TransferStats, up: TransferStats):
|
||||
self.down = down
|
||||
self.up = up
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
'''JSON object hook'''
|
||||
return TransferStatsDownUp(
|
||||
TransferStats.from_json(j['down']),
|
||||
TransferStats.from_json(j['up']))
|
||||
|
||||
class PeerStats:
|
||||
time_added: Timestamp
|
||||
rpc_stats: RPCStats
|
||||
latency: Optional[LatencyStats]
|
||||
transfer: TransferStatsDownUp
|
||||
|
||||
def __init__(self, time_added: Timestamp, rpc_stats: RPCStats, latency: Optional[LatencyStats], transfer: TransferStatsDownUp):
|
||||
self.time_added = time_added
|
||||
self.rpc_stats = rpc_stats
|
||||
self.latency = latency
|
||||
self.transfer = transfer
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
'''JSON object hook'''
|
||||
return PeerStats(
|
||||
j['time_added'],
|
||||
RPCStats.from_json(j['rpc_stats']),
|
||||
None if j['latency'] is None else LatencyStats.from_json(j['latency']),
|
||||
TransferStatsDownUp.from_json(j['transfer']))
|
||||
|
||||
class PeerTableData:
|
||||
node_ids: list[str]
|
||||
peer_address: str
|
||||
peer_stats: PeerStats
|
||||
|
||||
def __init__(self, node_ids: list[str], peer_address: str, peer_stats: PeerStats):
|
||||
self.node_ids = node_ids
|
||||
self.peer_address = peer_address
|
||||
self.peer_stats = peer_stats
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
'''JSON object hook'''
|
||||
return PeerTableData(
|
||||
j['node_ids'],
|
||||
j['peer_address'],
|
||||
PeerStats.from_json(j['peer_stats']))
|
||||
|
||||
class VeilidStateNetwork:
|
||||
started: bool
|
||||
bps_down: ByteCount
|
||||
bps_up: ByteCount
|
||||
peers: list[PeerTableData]
|
||||
|
||||
def __init__(self, started: bool, bps_down: ByteCount, bps_up: ByteCount, peers: list[PeerTableData]):
|
||||
self.started = started
|
||||
self.bps_down = bps_down
|
||||
self.bps_up = bps_up
|
||||
self.peers = peers
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
'''JSON object hook'''
|
||||
return VeilidStateNetwork(
|
||||
j['started'],
|
||||
ByteCount(j['bps_down']),
|
||||
ByteCount(j['bps_up']),
|
||||
list(map(lambda x: PeerTableData.from_json(x), j['peers'])))
|
||||
|
||||
class VeilidStateConfig:
|
||||
config: VeilidConfig
|
||||
|
||||
def __init__(self, config: VeilidConfig):
|
||||
self.config = config
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
'''JSON object hook'''
|
||||
return VeilidStateConfig(
|
||||
VeilidConfig.from_json(j['config'])
|
||||
)
|
||||
|
||||
class VeilidState:
|
||||
attachment: VeilidStateAttachment
|
||||
network: VeilidStateNetwork
|
||||
config: VeilidStateConfig
|
||||
|
||||
def __init__(self, attachment: VeilidStateAttachment, network: VeilidStateNetwork, config: VeilidStateConfig):
|
||||
self.attachment = attachment
|
||||
self.network = network
|
||||
self.config = config
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
'''JSON object hook'''
|
||||
return VeilidState(
|
||||
VeilidStateAttachment.from_json(j['attachment']),
|
||||
VeilidStateNetwork.from_json(j['network']),
|
||||
VeilidStateConfig.from_json(j['config']))
|
||||
|
||||
class VeilidLog:
|
||||
log_level: VeilidLogLevel
|
||||
message: str
|
||||
backtrace: Optional[str]
|
||||
|
||||
def __init__(self, log_level: VeilidLogLevel, message: str, backtrace: Optional[str]):
|
||||
self.log_level = log_level
|
||||
self.message = message
|
||||
self.backtrace = backtrace
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
'''JSON object hook'''
|
||||
return VeilidLog(
|
||||
VeilidLogLevel(j['log_level']),
|
||||
j['message'],
|
||||
j['backtrace'])
|
||||
|
||||
class VeilidAppMessage:
|
||||
sender: Optional[TypedKey]
|
||||
message: bytes
|
||||
|
||||
def __init__(self, sender: Optional[TypedKey], message: bytes):
|
||||
self.sender = sender
|
||||
self.message = message
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
'''JSON object hook'''
|
||||
return VeilidAppMessage(
|
||||
None if j['sender'] is None else TypedKey(j['sender']),
|
||||
urlsafe_b64decode_no_pad(j['message']))
|
||||
|
||||
class VeilidAppCall:
|
||||
sender: Optional[TypedKey]
|
||||
message: bytes
|
||||
operation_id: str
|
||||
|
||||
def __init__(self, sender: Optional[TypedKey], message: bytes, operation_id: str):
|
||||
self.sender = sender
|
||||
self.message = message
|
||||
self.operation_id = operation_id
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
'''JSON object hook'''
|
||||
return VeilidAppCall(
|
||||
None if j['sender'] is None else TypedKey(j['sender']),
|
||||
urlsafe_b64decode_no_pad(j['message']),
|
||||
j['operation_id'])
|
||||
|
||||
class VeilidRouteChange:
|
||||
dead_routes: list[RouteId]
|
||||
dead_remote_routes: list[RouteId]
|
||||
|
||||
def __init__(self, dead_routes: list[RouteId], dead_remote_routes: list[RouteId]):
|
||||
self.dead_routes = dead_routes
|
||||
self.dead_remote_routes = dead_remote_routes
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
'''JSON object hook'''
|
||||
return VeilidRouteChange(
|
||||
list(map(lambda x: RouteId(x), j['dead_routes'])),
|
||||
list(map(lambda x: RouteId(x), j['dead_remote_routes'])))
|
||||
|
||||
class VeilidValueChange:
|
||||
key: TypedKey
|
||||
subkeys: list[ValueSubkey]
|
||||
count: int
|
||||
value: ValueData
|
||||
|
||||
def __init__(self, key: TypedKey, subkeys: list[ValueSubkey], count: int, value: ValueData):
|
||||
self.key = key
|
||||
self.subkeys = subkeys
|
||||
self.count = count
|
||||
self.value = value
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
'''JSON object hook'''
|
||||
return VeilidValueChange(
|
||||
TypedKey(j['key']),
|
||||
list(map(lambda x: ValueSubkey(x), j['subkeys'])),
|
||||
j['count'],
|
||||
ValueData.from_json(j['value']))
|
||||
|
||||
|
||||
class VeilidUpdateKind(StrEnum):
|
||||
LOG = "Log"
|
||||
APP_MESSAGE = "AppMessage"
|
||||
APP_CALL = "AppCall"
|
||||
ATTACHMENT = "Attachment"
|
||||
NETWORK = "Network"
|
||||
CONFIG = "Config"
|
||||
ROUTE_CHANGE = "RouteChange"
|
||||
VALUE_CHANGE = "ValueChange"
|
||||
SHUTDOWN = "Shutdown"
|
||||
|
||||
class VeilidUpdate:
|
||||
kind: VeilidUpdateKind
|
||||
detail: Optional[VeilidLog | VeilidAppMessage | VeilidAppCall | VeilidStateAttachment | VeilidStateNetwork | VeilidStateConfig | VeilidRouteChange | VeilidValueChange]
|
||||
|
||||
def __init__(self, kind: VeilidUpdateKind, detail: Optional[VeilidLog | VeilidAppMessage | VeilidAppCall | VeilidStateAttachment | VeilidStateNetwork | VeilidStateConfig | VeilidRouteChange | VeilidValueChange]):
|
||||
self.kind = kind
|
||||
self.detail = detail
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
'''JSON object hook'''
|
||||
kind = VeilidUpdateKind(j['kind'])
|
||||
detail = None
|
||||
match kind:
|
||||
case VeilidUpdateKind.LOG:
|
||||
detail = VeilidLog.from_json(j)
|
||||
case VeilidUpdateKind.APP_MESSAGE:
|
||||
detail = VeilidAppMessage.from_json(j)
|
||||
case VeilidUpdateKind.APP_CALL:
|
||||
detail = VeilidAppCall.from_json(j)
|
||||
case VeilidUpdateKind.ATTACHMENT:
|
||||
detail = VeilidStateAttachment.from_json(j)
|
||||
case VeilidUpdateKind.NETWORK:
|
||||
detail = VeilidStateNetwork.from_json(j)
|
||||
case VeilidUpdateKind.CONFIG:
|
||||
detail = VeilidStateConfig.from_json(j)
|
||||
case VeilidUpdateKind.ROUTE_CHANGE:
|
||||
detail = VeilidRouteChange.from_json(j)
|
||||
case VeilidUpdateKind.VALUE_CHANGE:
|
||||
detail = VeilidValueChange.from_json(j)
|
||||
case VeilidUpdateKind.SHUTDOWN:
|
||||
detail = None
|
||||
case _:
|
||||
raise ValueError("Unknown VeilidUpdateKind")
|
||||
return VeilidUpdate(kind, detail)
|
298
veilid-python/veilid/types.py
Normal file
298
veilid-python/veilid/types.py
Normal file
@@ -0,0 +1,298 @@
|
||||
import time
|
||||
import json
|
||||
import base64
|
||||
|
||||
from enum import StrEnum
|
||||
from typing import Self, Optional, Any, Tuple
|
||||
|
||||
####################################################################
|
||||
|
||||
def urlsafe_b64encode_no_pad(b: bytes) -> str:
|
||||
"""
|
||||
Removes any `=` used as padding from the encoded string.
|
||||
"""
|
||||
return base64.urlsafe_b64encode(b).decode().rstrip("=")
|
||||
|
||||
|
||||
def urlsafe_b64decode_no_pad(s: str) -> bytes:
|
||||
"""
|
||||
Adds back in the required padding before decoding.
|
||||
"""
|
||||
padding = 4 - (len(s) % 4)
|
||||
s = s + ("=" * padding)
|
||||
return base64.urlsafe_b64decode(s)
|
||||
|
||||
class VeilidJSONEncoder(json.JSONEncoder):
|
||||
def default(self, o):
|
||||
if isinstance(o, bytes):
|
||||
return urlsafe_b64encode_no_pad(o)
|
||||
if hasattr(o, "to_json") and callable(o.to_json):
|
||||
return o.to_json()
|
||||
return json.JSONEncoder.default(self, o)
|
||||
|
||||
@staticmethod
|
||||
def dumps(req: Any, *args, **kwargs) -> str:
|
||||
return json.dumps(req, cls = VeilidJSONEncoder, *args, **kwargs)
|
||||
|
||||
####################################################################
|
||||
|
||||
class VeilidLogLevel(StrEnum):
|
||||
ERROR = 'Error'
|
||||
WARN = 'Warn'
|
||||
INFO = 'Info'
|
||||
DEBUG = 'Debug'
|
||||
TRACE = 'Trace'
|
||||
|
||||
class CryptoKind(StrEnum):
|
||||
CRYPTO_KIND_NONE = "NONE"
|
||||
CRYPTO_KIND_VLD0 = "VLD0"
|
||||
|
||||
class Stability(StrEnum):
|
||||
LOW_LATENCY = "LowLatency"
|
||||
RELIABLE = "Reliable"
|
||||
|
||||
class Sequencing(StrEnum):
|
||||
NO_PREFERENCE = "NoPreference"
|
||||
PREFER_ORDERED = "PreferOrdered"
|
||||
ENSURE_ORDERED = "EnsureOrdered"
|
||||
|
||||
class DHTSchemaKind(StrEnum):
|
||||
DFLT = "DFLT"
|
||||
SMPL = "SMPL"
|
||||
|
||||
####################################################################
|
||||
|
||||
class Timestamp(int):
|
||||
pass
|
||||
|
||||
class TimestampDuration(int):
|
||||
pass
|
||||
|
||||
class ByteCount(int):
|
||||
pass
|
||||
|
||||
class OperationId(int):
|
||||
pass
|
||||
|
||||
class RouteId(str):
|
||||
pass
|
||||
|
||||
class CryptoKey:
|
||||
def to_bytes(self) -> bytes:
|
||||
return urlsafe_b64decode_no_pad(self)
|
||||
|
||||
class CryptoKeyDistance(CryptoKey, str):
|
||||
@staticmethod
|
||||
def from_bytes(b: bytes) -> Self:
|
||||
return CryptoKeyDistance(urlsafe_b64encode_no_pad(b))
|
||||
|
||||
class PublicKey(CryptoKey, str):
|
||||
@staticmethod
|
||||
def from_bytes(b: bytes) -> Self:
|
||||
return PublicKey(urlsafe_b64encode_no_pad(b))
|
||||
|
||||
class SecretKey(CryptoKey, str):
|
||||
@staticmethod
|
||||
def from_bytes(b: bytes) -> Self:
|
||||
return SecretKey(urlsafe_b64encode_no_pad(b))
|
||||
|
||||
class SharedSecret(CryptoKey, str):
|
||||
@staticmethod
|
||||
def from_bytes(b: bytes) -> Self:
|
||||
return SharedSecret(urlsafe_b64encode_no_pad(b))
|
||||
|
||||
class HashDigest(CryptoKey, str):
|
||||
@staticmethod
|
||||
def from_bytes(b: bytes) -> Self:
|
||||
return HashDigest(urlsafe_b64encode_no_pad(b))
|
||||
|
||||
class Signature(str):
|
||||
@staticmethod
|
||||
def from_bytes(b: bytes) -> Self:
|
||||
return Signature(urlsafe_b64encode_no_pad(b))
|
||||
def to_bytes(self) -> bytes:
|
||||
return urlsafe_b64decode_no_pad(self)
|
||||
|
||||
class Nonce(str):
|
||||
@staticmethod
|
||||
def from_bytes(b: bytes) -> Self:
|
||||
return Signature(urlsafe_b64encode_no_pad(b))
|
||||
def to_bytes(self) -> bytes:
|
||||
return urlsafe_b64decode_no_pad(self)
|
||||
|
||||
class KeyPair(str):
|
||||
@staticmethod
|
||||
def from_parts(key: PublicKey, secret: SecretKey) -> Self:
|
||||
return KeyPair(key + ":" + secret)
|
||||
def key(self) -> PublicKey:
|
||||
return PublicKey(str.split(":", 1)[0])
|
||||
def secret(self) -> SecretKey:
|
||||
return SecretKey(str.split(":", 1)[1])
|
||||
def to_parts(self) -> Tuple[PublicKey, SecretKey]:
|
||||
parts = str.split(":", 1)
|
||||
return (PublicKey(parts[0]), SecretKey(parts[1]))
|
||||
|
||||
class CryptoTyped:
|
||||
def kind(self) -> CryptoKind:
|
||||
if self[4] != ':':
|
||||
raise ValueError("Not CryptoTyped")
|
||||
return CryptoKind(self[0:4])
|
||||
def _value(self) -> str:
|
||||
if self[4] != ':':
|
||||
raise ValueError("Not CryptoTyped")
|
||||
return self[5:]
|
||||
|
||||
class TypedKey(CryptoTyped, str):
|
||||
@staticmethod
|
||||
def from_value(kind: CryptoKind, value: PublicKey) -> Self:
|
||||
return TypedKey(kind + ":" + value)
|
||||
def value(self) -> PublicKey:
|
||||
PublicKey(self._value())
|
||||
|
||||
class TypedSecret(CryptoTyped, str):
|
||||
@staticmethod
|
||||
def from_value(kind: CryptoKind, value: SecretKey) -> Self:
|
||||
return TypedSecret(kind + ":" + value)
|
||||
def value(self) -> SecretKey:
|
||||
SecretKey(self._value())
|
||||
|
||||
class TypedKeyPair(CryptoTyped, str):
|
||||
@staticmethod
|
||||
def from_value(kind: CryptoKind, value: KeyPair) -> Self:
|
||||
return TypedKeyPair(kind + ":" + value)
|
||||
def value(self) -> KeyPair:
|
||||
KeyPair(self._value())
|
||||
|
||||
class TypedSignature(CryptoTyped, str):
|
||||
@staticmethod
|
||||
def from_value(kind: CryptoKind, value: Signature) -> Self:
|
||||
return TypedSignature(kind + ":" + value)
|
||||
def value(self) -> Signature:
|
||||
Signature(self._value())
|
||||
|
||||
class ValueSubkey(int):
|
||||
pass
|
||||
|
||||
class ValueSeqNum(int):
|
||||
pass
|
||||
|
||||
####################################################################
|
||||
|
||||
class VeilidVersion:
|
||||
_major: int
|
||||
_minor: int
|
||||
_patch: int
|
||||
def __init__(self, major: int, minor: int, patch: int):
|
||||
self._major = major
|
||||
self._minor = minor
|
||||
self._patch = patch
|
||||
@property
|
||||
def major(self):
|
||||
return self._major
|
||||
@property
|
||||
def minor(self):
|
||||
return self._minor
|
||||
@property
|
||||
def patch(self):
|
||||
return self._patch
|
||||
|
||||
class NewPrivateRouteResult:
|
||||
route_id: RouteId
|
||||
blob: bytes
|
||||
|
||||
def __init__(self, route_id: RouteId, blob: bytes):
|
||||
self.route_id = route_id
|
||||
self.blob = blob
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
return NewPrivateRouteResult(
|
||||
RouteId(j['route_id']),
|
||||
urlsafe_b64decode_no_pad(j['blob']))
|
||||
|
||||
class DHTSchemaSMPLMember:
|
||||
m_key: PublicKey
|
||||
m_cnt: int
|
||||
def __init__(self, m_key: PublicKey, m_cnt: int):
|
||||
self.m_key = m_key
|
||||
self.m_cnt = m_cnt
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
return DHTSchemaSMPLMember(
|
||||
PublicKey(j['m_key']),
|
||||
j['m_cnt'])
|
||||
def to_json(self) -> dict:
|
||||
return self.__dict__
|
||||
|
||||
class DHTSchema:
|
||||
kind: DHTSchemaKind
|
||||
|
||||
def __init__(self, kind: DHTSchemaKind, **kwargs):
|
||||
self.kind = kind
|
||||
for k, v in kwargs.items():
|
||||
setattr(self, k, v)
|
||||
|
||||
@staticmethod
|
||||
def dflt(o_cnt: int) -> Self:
|
||||
Self(DHTSchemaKind.DFLT, o_cnt = o_cnt)
|
||||
|
||||
@staticmethod
|
||||
def smpl(o_cnt: int, members: list[DHTSchemaSMPLMember]) -> Self:
|
||||
Self(DHTSchemaKind.SMPL, o_cnt = o_cnt, members = members)
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
if DHTSchemaKind(j['kind']) == DHTSchemaKind.DFLT:
|
||||
return DHTSchema.dflt(j['o_cnt'])
|
||||
if DHTSchemaKind(j['kind']) == DHTSchemaKind.SMPL:
|
||||
return DHTSchema.smpl(
|
||||
j['o_cnt'],
|
||||
list(map(lambda x: DHTSchemaSMPLMember.from_json(x), j['members'])))
|
||||
raise Exception("Unknown DHTSchema kind", j['kind'])
|
||||
|
||||
def to_json(self) -> dict:
|
||||
return self.__dict__
|
||||
|
||||
class DHTRecordDescriptor:
|
||||
key: TypedKey
|
||||
owner: PublicKey
|
||||
owner_secret: Optional[SecretKey]
|
||||
schema: DHTSchema
|
||||
|
||||
def __init__(self, key: TypedKey, owner: PublicKey, owner_secret: Optional[SecretKey], schema: DHTSchema):
|
||||
self.key = key
|
||||
self.owner = owner
|
||||
self.owner_secret = owner_secret
|
||||
self.schema = schema
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
DHTRecordDescriptor(
|
||||
TypedKey(j['key']),
|
||||
PublicKey(j['owner']),
|
||||
None if j['owner_secret'] is None else SecretKey(j['owner_secret']),
|
||||
DHTSchema.from_json(j['schema']))
|
||||
|
||||
def to_json(self) -> dict:
|
||||
return self.__dict__
|
||||
|
||||
class ValueData:
|
||||
seq: ValueSeqNum
|
||||
data: bytes
|
||||
writer: PublicKey
|
||||
|
||||
def __init__(self, seq: ValueSeqNum, data: bytes, writer: PublicKey):
|
||||
self.seq = seq
|
||||
self.data = data
|
||||
self.writer = writer
|
||||
|
||||
@staticmethod
|
||||
def from_json(j: dict) -> Self:
|
||||
DHTRecordDescriptor(
|
||||
ValueSeqNum(j['seq']),
|
||||
urlsafe_b64decode_no_pad(j['data']),
|
||||
PublicKey(j['writer']))
|
||||
|
||||
def to_json(self) -> dict:
|
||||
return self.__dict__
|
||||
|
Reference in New Issue
Block a user