python work

This commit is contained in:
John Smith 2023-06-13 23:17:45 -04:00
parent cd04a8a74c
commit df0b06bf3c
10 changed files with 1293 additions and 17 deletions

View File

@ -230,7 +230,7 @@ struct NodeInfo @0xe125d847e3f9f419 {
outboundProtocols @1 :ProtocolTypeSet; # protocols that can go outbound outboundProtocols @1 :ProtocolTypeSet; # protocols that can go outbound
addressTypes @2 :AddressTypeSet; # address types supported addressTypes @2 :AddressTypeSet; # address types supported
envelopeSupport @3 :List(UInt8); # supported rpc envelope/receipt versions envelopeSupport @3 :List(UInt8); # supported rpc envelope/receipt versions
cryptoSupport @4 :List(CryptoKind); # cryptography systems supported cryptoSupport @4 :List( ); # cryptography systems supported
dialInfoDetailList @5 :List(DialInfoDetail); # inbound dial info details for this node dialInfoDetailList @5 :List(DialInfoDetail); # inbound dial info details for this node
} }
@ -535,6 +535,7 @@ struct Answer @0xacacb8b6988c1058 {
getValueA @3 :OperationGetValueA; getValueA @3 :OperationGetValueA;
setValueA @4 :OperationSetValueA; setValueA @4 :OperationSetValueA;
watchValueA @5 :OperationWatchValueA; watchValueA @5 :OperationWatchValueA;
# #[cfg(feature="unstable-blockstore")] # #[cfg(feature="unstable-blockstore")]
#supplyBlockA @6 :OperationSupplyBlockA; #supplyBlockA @6 :OperationSupplyBlockA;
#findBlockA @7 :OperationFindBlockA; #findBlockA @7 :OperationFindBlockA;

View File

@ -1 +0,0 @@
from .error import *

View File

@ -1,11 +1,211 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from .state import VeilidState 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_values(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[str]:
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): class VeilidAPI(ABC):
@abstractmethod @abstractmethod
def control(self, args: list[str]) -> str: async def control(self, args: list[str]) -> str:
pass pass
@abstractmethod @abstractmethod
def get_state(self) -> VeilidState: 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 pass

View File

@ -1,5 +1,6 @@
from typing import Self, Optional from typing import Self, Optional
from enum import StrEnum from enum import StrEnum
from json import dumps
class VeilidConfigLogLevel(StrEnum): class VeilidConfigLogLevel(StrEnum):
OFF = 'Off' OFF = 'Off'
@ -38,6 +39,8 @@ class VeilidConfigCapabilities:
j['protocol_accept_ws'], j['protocol_accept_ws'],
j['protocol_connect_wss'], j['protocol_connect_wss'],
j['protocol_accept_wss']) j['protocol_accept_wss'])
def to_json(self) -> dict:
return self.__dict__
class VeilidConfigProtectedStore: class VeilidConfigProtectedStore:
allow_insecure_fallback: bool allow_insecure_fallback: bool
@ -61,6 +64,8 @@ class VeilidConfigProtectedStore:
def from_json(j: dict) -> Self: def from_json(j: dict) -> Self:
return VeilidConfigProtectedStore(j['allow_insecure_fallback'], j['always_use_insecure_storage'], 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']) 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: class VeilidConfigTableStore:
directory: str directory: str
@ -73,6 +78,8 @@ class VeilidConfigTableStore:
@staticmethod @staticmethod
def from_json(j: dict) -> Self: def from_json(j: dict) -> Self:
return VeilidConfigTableStore(j['directory'], j['delete']) return VeilidConfigTableStore(j['directory'], j['delete'])
def to_json(self) -> dict:
return self.__dict__
class VeilidConfigBlockStore: class VeilidConfigBlockStore:
directory: str directory: str
@ -85,6 +92,8 @@ class VeilidConfigBlockStore:
@staticmethod @staticmethod
def from_json(j: dict) -> Self: def from_json(j: dict) -> Self:
return VeilidConfigBlockStore(j['directory'], j['delete']) return VeilidConfigBlockStore(j['directory'], j['delete'])
def to_json(self) -> dict:
return self.__dict__
class VeilidConfigRoutingTable: class VeilidConfigRoutingTable:
node_id: list[str] node_id: list[str]
@ -119,6 +128,8 @@ class VeilidConfigRoutingTable:
j['limit_attached_strong'], j['limit_attached_strong'],
j['limit_attached_good'], j['limit_attached_good'],
j['limit_attached_weak']) j['limit_attached_weak'])
def to_json(self) -> dict:
return self.__dict__
class VeilidConfigRPC: class VeilidConfigRPC:
@ -151,6 +162,286 @@ class VeilidConfigRPC:
j['timeout_ms'], j['timeout_ms'],
j['max_route_hop_count'], j['max_route_hop_count'],
j['default_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: class VeilidConfigNetwork:
@ -221,6 +512,8 @@ class VeilidConfigNetwork:
VeilidConfigTLS.from_json(j['tls']), VeilidConfigTLS.from_json(j['tls']),
VeilidConfigApplication.from_json(j['application']), VeilidConfigApplication.from_json(j['application']),
VeilidConfigProtocol.from_json(j['protocol'])) VeilidConfigProtocol.from_json(j['protocol']))
def to_json(self) -> dict:
return self.__dict__
class VeilidConfig: class VeilidConfig:
program_name: str program_name: str
@ -245,10 +538,14 @@ class VeilidConfig:
@staticmethod @staticmethod
def from_json(j: dict) -> Self: def from_json(j: dict) -> Self:
'''JSON object hook'''
return VeilidConfig(j['program_name'], j['namespace'], return VeilidConfig(j['program_name'], j['namespace'],
VeilidConfigCapabilities.from_json(j['capabilities']), VeilidConfigCapabilities.from_json(j['capabilities']),
VeilidConfigProtectedStore.from_json(j['protected_store']), VeilidConfigProtectedStore.from_json(j['protected_store']),
VeilidConfigTableStore.from_json(j['table_store']), VeilidConfigTableStore.from_json(j['table_store']),
VeilidConfigBlockStore.from_json(j['block_store']), VeilidConfigBlockStore.from_json(j['block_store']),
VeilidConfigNetwork.from_json(j['network'])) VeilidConfigNetwork.from_json(j['network']))
def to_json(self) -> dict:
return self.__dict__

View File

@ -1,4 +1,4 @@
from typing import Self from typing import Self, Any
class VeilidAPIError(Exception): class VeilidAPIError(Exception):
"""Veilid API error exception base class""" """Veilid API error exception base class"""
@ -131,3 +131,12 @@ class VeilidAPIErrorGeneric(VeilidAPIError):
def __init__(self, message: str): def __init__(self, message: str):
super().__init__("Generic") super().__init__("Generic")
self.message = message 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")

View File

@ -0,0 +1,314 @@
import json;
import asyncio;
from typing import Callable, Awaitable
from .api import *;
from .state import *
from .config import *
from .error import *
from .types import *
from .operations import *
class _JsonRoutingContext(RoutingContext):
api: VeilidAPI
rc_id: int
def __init__(self, api: VeilidAPI, rc_id: int):
self.api = api
self.rc_id = rc_id
async def with_privacy(self) -> Self:
new_rc_id = raise_api_result(await self.send_ndjson_request(Operation.ROUTING_CONTEXT,
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.send_ndjson_request(Operation.ROUTING_CONTEXT,
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.send_ndjson_request(Operation.ROUTING_CONTEXT,
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.send_ndjson_request(Operation.ROUTING_CONTEXT,
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.send_ndjson_request(Operation.ROUTING_CONTEXT,
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.send_ndjson_request(Operation.ROUTING_CONTEXT,
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:
pass
async def close_dht_record(self, key: TypedKey):
pass
async def delete_dht_record(self, key: TypedKey):
pass
async def get_dht_value(self, key: TypedKey, subkey: ValueSubkey, force_refresh: bool) -> Optional[ValueData]:
pass
async def set_dht_value(self, key: TypedKey, subkey: ValueSubkey, data: bytes) -> Optional[ValueData]:
pass
async def watch_dht_values(self, key: TypedKey, subkeys: list[(ValueSubkey, ValueSubkey)], expiration: Timestamp, count: int) -> Timestamp:
pass
async def cancel_dht_values(self, key: TypedKey, subkeys: list[(ValueSubkey, ValueSubkey)]) -> bool:
pass
class _JsonTableDBTransaction(TableDBTransaction):
async def commit(self):
pass
async def rollback(self):
pass
async def store(self, col: int, key: bytes, value: bytes):
pass
async def delete(self, col: int, key: bytes):
pass
class _JsonTableDB(TableDB):
async def get_column_count(self) -> int:
pass
async def get_keys(self, col: int) -> list[str]:
pass
async def transact(self) -> TableDBTransaction:
pass
async def store(self, col: int, key: bytes, value: bytes):
pass
async def load(self, col: int, key: bytes) -> Optional[bytes]:
pass
async def delete(self, col: int, key: bytes) -> Optional[bytes]:
pass
class _JsonCryptoSystem(CryptoSystem):
async def cached_dh(self, key: PublicKey, secret: SecretKey) -> SharedSecret:
pass
async def compute_dh(self, key: PublicKey, secret: SecretKey) -> SharedSecret:
pass
async def random_bytes(self, len: int) -> bytes:
pass
async def default_salt_length(self) -> int:
pass
async def hash_password(self, password: bytes, salt: bytes) -> str:
pass
async def verify_password(self, password: bytes, password_hash: str) -> bool:
pass
async def derive_shared_secret(self, password: bytes, salt: bytes) -> SharedSecret:
pass
async def random_nonce(self) -> Nonce:
pass
async def random_shared_secret(self) -> SharedSecret:
pass
async def generate_key_pair(self) -> KeyPair:
pass
async def generate_hash(self, data: bytes) -> HashDigest:
pass
async def validate_key_pair(self, key: PublicKey, secret: SecretKey) -> bool:
pass
async def validate_hash(self, data: bytes, hash_digest: HashDigest) -> bool:
pass
async def distance(self, key1: CryptoKey, key2: CryptoKey) -> CryptoKeyDistance:
pass
async def sign(self, key: PublicKey, secret: SecretKey, data: bytes) -> Signature:
pass
async def verify(self, key: PublicKey, data: bytes, signature: Signature):
pass
async def aead_overhead(self) -> int:
pass
async def decrypt_aead(self, body: bytes, nonce: Nonce, shared_secret: SharedSecret, associated_data: Optional[bytes]) -> bytes:
pass
async def encrypt_aead(self, body: bytes, nonce: Nonce, shared_secret: SharedSecret, associated_data: Optional[bytes]) -> bytes:
pass
async def crypt_no_auth(self, body: bytes, nonce: Nonce, shared_secret: SharedSecret) -> bytes:
pass
class _JsonVeilidAPI(VeilidAPI):
reader: asyncio.StreamReader
writer: asyncio.StreamWriter
update_callback: Callable[[VeilidUpdate], Awaitable]
handle_recv_messages_task: Optional[asyncio.Task]
# Shared Mutable State
lock: asyncio.Lock
next_id: int
in_flight_requests: dict
def __init__(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter, update_callback: Callable[[VeilidUpdate], Awaitable]):
self.reader = reader
self.writer = writer
self.update_callback = update_callback
self.handle_recv_messages_task = None
self.lock = asyncio.Lock()
self.next_id = 1
self.in_flight_requests = dict()
@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
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())
# 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))
except:
pass
finally:
self.reader = None
self.writer.close()
await self.writer.wait_closed()
self.writer = None
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()
async def send_ndjson_request(self, op: Operation, **kwargs) -> dict:
# Get next id
await self.lock.acquire()
try:
id = self.next_id
self.next_id += 1
finally:
self.lock.release()
# Make NDJSON string for request
req = { "id": id, "op": op }
for k, v in kwargs.items():
setattr(req, k, v)
reqstr = VeilidJSONEncoder.dumps(req) + "\n"
reqbytes = reqstr.encode()
# Allocate future for request
reqfuture = await self.allocate_request_future(id)
# Send to socket
try:
self.writer.write(reqbytes)
await self.writer.drain()
finally:
# Send failed, release future
self.cancel_request_future(id)
# Wait for response
response = await reqfuture
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 RoutingContext(self, rc_id)
async def open_table_db(self, name: str, column_count: int) -> TableDB:
pass
async def delete_table_db(self, name: str):
pass
async def get_crypto_system(self, kind: CryptoKind) -> CryptoSystem:
pass
async def best_crypto_system(self) -> CryptoSystem:
pass
async def verify_signatures(self, node_ids: list[TypedKey], data: bytes, signatures: list[TypedSignature]) -> list[TypedKey]:
pass
async def generate_signatures(self, data: bytes, key_pairs: list[TypedKeyPair]) -> list[TypedSignature]:
pass
async def generate_key_pair(self, kind: CryptoKind) -> list[TypedKeyPair]:
pass
async def now(self) -> Timestamp:
pass
async def debug(self, command: str) -> str:
pass
async def veilid_version_string(self) -> str:
pass
async def veilid_version(self) -> VeilidVersion:
pass
def json_api_connect(host:str, port:int) -> VeilidAPI:
return _JsonVeilidAPI.connect(host, port)

View 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"

View File

@ -1,4 +0,0 @@
class Request:
def __init__(self, id: int):
self.id = id

View File

@ -1,7 +1,8 @@
from typing import Self, Optional from typing import Self, Optional
from enum import StrEnum from enum import StrEnum
from .types import Timestamp, TimestampDuration, ByteCount
from .config import VeilidConfig from .types import *
from .config import *
class AttachmentState(StrEnum): class AttachmentState(StrEnum):
DETACHED = 'Detached' DETACHED = 'Detached'
@ -25,6 +26,7 @@ class VeilidStateAttachment:
@staticmethod @staticmethod
def from_json(j: dict) -> Self: def from_json(j: dict) -> Self:
'''JSON object hook'''
return VeilidStateAttachment( return VeilidStateAttachment(
AttachmentState(j['state']), AttachmentState(j['state']),
j['public_internet_ready'], j['public_internet_ready'],
@ -54,6 +56,7 @@ class RPCStats:
@staticmethod @staticmethod
def from_json(j: dict) -> Self: def from_json(j: dict) -> Self:
'''JSON object hook'''
return RPCStats( return RPCStats(
j['messages_sent'], j['messages_sent'],
j['messages_rcvd'], j['messages_rcvd'],
@ -76,6 +79,7 @@ class LatencyStats:
@staticmethod @staticmethod
def from_json(j: dict) -> Self: def from_json(j: dict) -> Self:
'''JSON object hook'''
return LatencyStats( return LatencyStats(
TimestampDuration(j['fastest']), TimestampDuration(j['fastest']),
TimestampDuration(j['average']), TimestampDuration(j['average']),
@ -96,6 +100,7 @@ class TransferStats:
@staticmethod @staticmethod
def from_json(j: dict) -> Self: def from_json(j: dict) -> Self:
'''JSON object hook'''
return TransferStats( return TransferStats(
ByteCount(j['total']), ByteCount(j['total']),
ByteCount(j['maximum']), ByteCount(j['maximum']),
@ -113,6 +118,7 @@ class TransferStatsDownUp:
@staticmethod @staticmethod
def from_json(j: dict) -> Self: def from_json(j: dict) -> Self:
'''JSON object hook'''
return TransferStatsDownUp( return TransferStatsDownUp(
TransferStats.from_json(j['down']), TransferStats.from_json(j['down']),
TransferStats.from_json(j['up'])) TransferStats.from_json(j['up']))
@ -131,6 +137,7 @@ class PeerStats:
@staticmethod @staticmethod
def from_json(j: dict) -> Self: def from_json(j: dict) -> Self:
'''JSON object hook'''
return PeerStats( return PeerStats(
j['time_added'], j['time_added'],
RPCStats.from_json(j['rpc_stats']), RPCStats.from_json(j['rpc_stats']),
@ -149,6 +156,7 @@ class PeerTableData:
@staticmethod @staticmethod
def from_json(j: dict) -> Self: def from_json(j: dict) -> Self:
'''JSON object hook'''
return PeerTableData( return PeerTableData(
j['node_ids'], j['node_ids'],
j['peer_address'], j['peer_address'],
@ -168,6 +176,7 @@ class VeilidStateNetwork:
@staticmethod @staticmethod
def from_json(j: dict) -> Self: def from_json(j: dict) -> Self:
'''JSON object hook'''
return VeilidStateNetwork( return VeilidStateNetwork(
j['started'], j['started'],
ByteCount(j['bps_down']), ByteCount(j['bps_down']),
@ -182,10 +191,10 @@ class VeilidStateConfig:
@staticmethod @staticmethod
def from_json(j: dict) -> Self: def from_json(j: dict) -> Self:
'''JSON object hook'''
return VeilidStateConfig( return VeilidStateConfig(
j['config']) j['config'])
class VeilidState: class VeilidState:
attachment: VeilidStateAttachment attachment: VeilidStateAttachment
network: VeilidStateNetwork network: VeilidStateNetwork
@ -198,8 +207,143 @@ class VeilidState:
@staticmethod @staticmethod
def from_json(j: dict) -> Self: def from_json(j: dict) -> Self:
'''JSON object hook'''
return VeilidState( return VeilidState(
VeilidStateAttachment.from_json(j['attachment']), VeilidStateAttachment.from_json(j['attachment']),
VeilidStateNetwork.from_json(j['network']), VeilidStateNetwork.from_json(j['network']),
VeilidStateConfig.from_json(j['config'])) 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['attachment']),
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(
map(lambda x: RouteId(x), j['dead_routes']),
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']),
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")

View File

@ -1,4 +1,38 @@
import time import time
import json
import base64
from enum import StrEnum
from typing import Self, Optional, Any
def urlsafe_b64encode_no_pad(b: bytes) -> str:
"""
Removes any `=` used as padding from the encoded string.
"""
encoded = str(base64.urlsafe_b64encode(b))
return encoded.rstrip("=")
def urlsafe_b64decode_no_pad(s: str) -> bytes:
"""
Adds back in the required padding before decoding.
"""
padding = 4 - (len(s) % 4)
string = string + ("=" * 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 Timestamp(int): class Timestamp(int):
pass pass
@ -8,3 +42,194 @@ class TimestampDuration(int):
class ByteCount(int): class ByteCount(int):
pass pass
class OperationId(int):
pass
class RouteId(str):
pass
class TypedKey(str):
pass
class TypedKeyPair(str):
pass
class TypedSignature(str):
pass
class CryptoKey(str):
pass
class CryptoKeyDistance(str):
pass
class PublicKey(CryptoKey):
pass
class SecretKey(CryptoKey):
pass
class SharedSecret(CryptoKey):
pass
class Signature(str):
pass
class Nonce(str):
pass
class KeyPair(str):
pass
class HashDigest(CryptoKey):
pass
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 VeilidLogLevel(StrEnum):
ERROR = 'Error'
WARN = 'Warn'
INFO = 'Info'
DEBUG = 'Debug'
TRACE = 'Trace'
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 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 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'],
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__