Merge branch 'tek/pythonification' into 'main'

Mold the new Python code into more idiomatic shape

See merge request veilid/veilid!26
This commit is contained in:
John Smith 2023-06-17 22:57:20 +00:00
commit b8e5039251
12 changed files with 1738 additions and 1331 deletions

View File

@ -1,34 +0,0 @@
from typing import Callable, Awaitable
import os
import pytest
pytest_plugins = ('pytest_asyncio',)
import veilid
##################################################################
VEILID_SERVER = os.getenv("VEILID_SERVER")
if VEILID_SERVER is not None:
vsparts = VEILID_SERVER.split(":")
VEILID_SERVER = vsparts[0]
if len(vsparts) == 2:
VEILID_SERVER_PORT = int(vsparts[1])
else:
VEILID_SERVER_PORT = 5959
else:
VEILID_SERVER = "localhost"
VEILID_SERVER_PORT = 5959
##################################################################
async def simple_connect_and_run(func: Callable[[veilid.VeilidAPI], Awaitable]):
api = await veilid.json_api_connect(VEILID_SERVER, VEILID_SERVER_PORT, simple_update_callback)
async with api:
# purge routes to ensure we start fresh
await api.debug("purge routes")
await func(api)
async def simple_update_callback(update: veilid.VeilidUpdate):
print("VeilidUpdate: {}".format(update))

View File

@ -0,0 +1,37 @@
import os
from functools import cache
from typing import AsyncGenerator
import pytest_asyncio
import veilid
from veilid.json_api import _JsonVeilidAPI
pytest_plugins = ("pytest_asyncio",)
@cache
def server_info() -> tuple[str, int]:
"""Return the hostname and port of the test server."""
VEILID_SERVER = os.getenv("VEILID_SERVER")
if VEILID_SERVER is None:
return "localhost", 5959
hostname, *rest = VEILID_SERVER.split(":")
if rest:
return hostname, int(rest[0])
return hostname, 5959
async def simple_update_callback(update: veilid.VeilidUpdate):
print(f"VeilidUpdate: {update}")
@pytest_asyncio.fixture
async def api_connection() -> AsyncGenerator[_JsonVeilidAPI, None]:
hostname, port = server_info()
api = await veilid.json_api_connect(hostname, port, simple_update_callback)
async with api:
# purge routes to ensure we start fresh
await api.debug("purge routes")
yield api

View File

@ -1,39 +1,44 @@
# Basic veilid tests # Basic veilid tests
import veilid import socket
import pytest import pytest
from . import * import veilid
from .conftest import simple_update_callback
##################################################################
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_connect(): async def test_connect(api_connection):
async def func(api: veilid.VeilidAPI):
pass pass
await simple_connect_and_run(func)
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_get_node_id(): async def test_get_node_id(api_connection):
async def func(api: veilid.VeilidAPI): state = await api_connection.get_state()
# get our own node id node_ids = state.config.config.network.routing_table.node_id
state = await api.get_state()
node_id = state.config.config.network.routing_table.node_id.pop() assert len(node_ids) >= 1
await simple_connect_and_run(func)
for node_id in node_ids:
assert node_id[4] == ":"
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_fail_connect(): async def test_fail_connect():
with pytest.raises(Exception): with pytest.raises(socket.gaierror) as exc:
api = await veilid.json_api_connect("fuahwelifuh32luhwafluehawea", 1, simple_update_callback) await veilid.json_api_connect(
async with api: "fuahwelifuh32luhwafluehawea", 1, simple_update_callback
pass )
assert exc.value.errno == socket.EAI_NONAME
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_version(): async def test_version(api_connection):
async def func(api: veilid.VeilidAPI): v = await api_connection.veilid_version()
v = await api.veilid_version() print(f"veilid_version: {v.__dict__}")
print("veilid_version: {}".format(v.__dict__)) assert v.__dict__.keys() >= {"_major", "_minor", "_patch"}
vstr = await api.veilid_version_string()
print("veilid_version_string: {}".format(vstr))
await simple_connect_and_run(func)
vstr = await api_connection.veilid_version_string()
print(f"veilid_version_string: {vstr}")

View File

@ -1,42 +1,49 @@
# Crypto veilid tests # Crypto veilid tests
import veilid
import pytest import pytest
from . import * import veilid
from veilid.api import CryptoSystem
##################################################################
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_best_crypto_system(): async def test_best_crypto_system(api_connection):
async def func(api: veilid.VeilidAPI): bcs: CryptoSystem = await api_connection.best_crypto_system()
bcs = await api.best_crypto_system()
await simple_connect_and_run(func) assert await bcs.default_salt_length() == 16
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_get_crypto_system(): async def test_get_crypto_system(api_connection):
async def func(api: veilid.VeilidAPI): cs: CryptoSystem = await api_connection.get_crypto_system(
cs = await api.get_crypto_system(veilid.CryptoKind.CRYPTO_KIND_VLD0) veilid.CryptoKind.CRYPTO_KIND_VLD0
)
assert await cs.default_salt_length() == 16
# clean up handle early # clean up handle early
del cs del cs
await simple_connect_and_run(func)
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_get_crypto_system_invalid(): async def test_get_crypto_system_invalid(api_connection):
async def func(api: veilid.VeilidAPI): with pytest.raises(veilid.VeilidAPIErrorInvalidArgument) as exc:
with pytest.raises(veilid.VeilidAPIError): await api_connection.get_crypto_system(veilid.CryptoKind.CRYPTO_KIND_NONE)
cs = await api.get_crypto_system(veilid.CryptoKind.CRYPTO_KIND_NONE)
await simple_connect_and_run(func) assert exc.value.context == "unsupported cryptosystem"
assert exc.value.argument == "kind"
assert exc.value.value == "NONE"
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_hash_and_verify_password(): async def test_hash_and_verify_password(api_connection):
async def func(api: veilid.VeilidAPI): bcs = await api_connection.best_crypto_system()
bcs = await api.best_crypto_system()
nonce = await bcs.random_nonce() nonce = await bcs.random_nonce()
salt = nonce.to_bytes() salt = nonce.to_bytes()
# Password match # Password match
phash = await bcs.hash_password(b"abc123", salt) phash = await bcs.hash_password(b"abc123", salt)
assert await bcs.verify_password(b"abc123", phash) assert await bcs.verify_password(b"abc123", phash)
# Password mismatch # Password mismatch
phash2 = await bcs.hash_password(b"abc1234", salt) phash2 = await bcs.hash_password(b"abc1234", salt)
assert not await bcs.verify_password(b"abc12345", phash) assert not await bcs.verify_password(b"abc12345", phash)
await simple_connect_and_run(func)

View File

@ -1,34 +1,38 @@
# Routing context veilid tests # Routing context veilid tests
import veilid
import pytest
import asyncio import asyncio
import json
from . import * import pytest
import veilid
from veilid.types import OperationId
from .conftest import server_info
################################################################## ##################################################################
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_routing_contexts(): async def test_routing_contexts(api_connection):
async def func(api: veilid.VeilidAPI): rc = await api_connection.new_routing_context()
rc = await api.new_routing_context()
rcp = await rc.with_privacy() rcp = await rc.with_privacy()
rcps = await rcp.with_sequencing(veilid.Sequencing.ENSURE_ORDERED) rcps = await rcp.with_sequencing(veilid.Sequencing.ENSURE_ORDERED)
rcpsr = await rcps.with_custom_privacy(veilid.Stability.RELIABLE) await rcps.with_custom_privacy(veilid.Stability.RELIABLE)
await simple_connect_and_run(func)
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_routing_context_app_message_loopback(): async def test_routing_context_app_message_loopback():
# Seriously, mypy?
app_message_queue = asyncio.Queue() app_message_queue: asyncio.Queue = asyncio.Queue()
async def app_message_queue_update_callback(update: veilid.VeilidUpdate): async def app_message_queue_update_callback(update: veilid.VeilidUpdate):
if update.kind == veilid.VeilidUpdateKind.APP_MESSAGE: if update.kind == veilid.VeilidUpdateKind.APP_MESSAGE:
await app_message_queue.put(update) await app_message_queue.put(update)
api = await veilid.json_api_connect(VEILID_SERVER, VEILID_SERVER_PORT, app_message_queue_update_callback) hostname, port = server_info()
api = await veilid.json_api_connect(
hostname, port, app_message_queue_update_callback
)
async with api: async with api:
# purge routes to ensure we start fresh # purge routes to ensure we start fresh
await api.debug("purge routes") await api.debug("purge routes")
@ -46,23 +50,25 @@ async def test_routing_context_app_message_loopback():
await rc.app_message(prr, message) await rc.app_message(prr, message)
# we should get the same message back # we should get the same message back
update: veilid.VeilidUpdate = await asyncio.wait_for(app_message_queue.get(), timeout=10) update: veilid.VeilidUpdate = await asyncio.wait_for(
appmsg: veilid.VeilidAppMessage = update.detail app_message_queue.get(), timeout=10
assert appmsg.message == message )
assert isinstance(update.detail, veilid.VeilidAppMessage)
assert update.detail.message == message
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_routing_context_app_call_loopback(): async def test_routing_context_app_call_loopback():
app_call_queue: asyncio.Queue = asyncio.Queue()
app_call_queue = asyncio.Queue()
async def app_call_queue_update_callback(update: veilid.VeilidUpdate): async def app_call_queue_update_callback(update: veilid.VeilidUpdate):
if update.kind == veilid.VeilidUpdateKind.APP_CALL: if update.kind == veilid.VeilidUpdateKind.APP_CALL:
await app_call_queue.put(update) await app_call_queue.put(update)
api = await veilid.json_api_connect(VEILID_SERVER, VEILID_SERVER_PORT, app_call_queue_update_callback) hostname, port = server_info()
api = await veilid.json_api_connect(hostname, port, app_call_queue_update_callback)
async with api: async with api:
# purge routes to ensure we start fresh # purge routes to ensure we start fresh
await api.debug("purge routes") await api.debug("purge routes")
@ -77,16 +83,27 @@ async def test_routing_context_app_call_loopback():
# send an app message to our own private route # send an app message to our own private route
request = b"abcd1234" request = b"abcd1234"
app_call_task = asyncio.create_task(rc.app_call(prr, request), name = "app call task") app_call_task = asyncio.create_task(
rc.app_call(prr, request), name="app call task"
)
# we should get the same request back # we should get the same request back
update: veilid.VeilidUpdate = await asyncio.wait_for(app_call_queue.get(), timeout=10) update: veilid.VeilidUpdate = await asyncio.wait_for(
appcall: veilid.VeilidAppCall = update.detail app_call_queue.get(), timeout=10
)
appcall = update.detail
assert isinstance(appcall, veilid.VeilidAppCall)
assert appcall.message == request assert appcall.message == request
# now we reply to the request # now we reply to the request
reply = b"qwer5678" reply = b"qwer5678"
await api.app_call_reply(appcall.call_id, reply) # TK: OperationId use to be a subclass of `int`. When I wrapped `appcall.call_id` in int(),
# this failed JSON schema validation, which defines `call_id` as a string. Maybe that was a
# typo, and OperationId is really *supposed* to be a str? Alternatively, perhaps the
# signature of `app_call_reply` is wrong and it's supposed to take a type other than
# OperationId?
await api.app_call_reply(OperationId(appcall.call_id), reply)
# now we should get the reply from the call # now we should get the reply from the call
result = await app_call_task result = await app_call_task

View File

@ -2,5 +2,5 @@ from .api import *
from .config import * from .config import *
from .error import * from .error import *
from .json_api import * from .json_api import *
from .error import * from .state import *
from .types import * from .types import *

View File

@ -1,50 +1,81 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import Self from typing import Optional, Self
from . import types
from .state import VeilidState
from .state import *
from .config import *
from .error import *
from .types import *
class RoutingContext(ABC): class RoutingContext(ABC):
@abstractmethod @abstractmethod
async def with_privacy(self) -> Self: async def with_privacy(self) -> Self:
pass pass
@abstractmethod @abstractmethod
async def with_custom_privacy(self, stability: Stability) -> Self: async def with_custom_privacy(self, stability: types.Stability) -> Self:
pass pass
@abstractmethod @abstractmethod
async def with_sequencing(self, sequencing: Sequencing) -> Self: async def with_sequencing(self, sequencing: types.Sequencing) -> Self:
pass pass
@abstractmethod @abstractmethod
async def app_call(self, target: TypedKey | RouteId, request: bytes) -> bytes: async def app_call(
self, target: types.TypedKey | types.RouteId, request: bytes
) -> bytes:
pass pass
@abstractmethod @abstractmethod
async def app_message(self, target: TypedKey | RouteId, message: bytes): async def app_message(self, target: types.TypedKey | types.RouteId, message: bytes):
pass pass
@abstractmethod @abstractmethod
async def create_dht_record(self, kind: CryptoKind, schema: DHTSchema) -> DHTRecordDescriptor: async def create_dht_record(
self, kind: types.CryptoKind, schema: types.DHTSchema
) -> types.DHTRecordDescriptor:
pass pass
@abstractmethod @abstractmethod
async def open_dht_record(self, key: TypedKey, writer: Optional[KeyPair]) -> DHTRecordDescriptor: async def open_dht_record(
self, key: types.TypedKey, writer: Optional[types.KeyPair]
) -> types.DHTRecordDescriptor:
pass pass
@abstractmethod @abstractmethod
async def close_dht_record(self, key: TypedKey): async def close_dht_record(self, key: types.TypedKey):
pass pass
@abstractmethod @abstractmethod
async def delete_dht_record(self, key: TypedKey): async def delete_dht_record(self, key: types.TypedKey):
pass pass
@abstractmethod @abstractmethod
async def get_dht_value(self, key: TypedKey, subkey: ValueSubkey, force_refresh: bool) -> Optional[ValueData]: async def get_dht_value(
self, key: types.TypedKey, subkey: types.ValueSubkey, force_refresh: bool
) -> Optional[types.ValueData]:
pass pass
@abstractmethod @abstractmethod
async def set_dht_value(self, key: TypedKey, subkey: ValueSubkey, data: bytes) -> Optional[ValueData]: async def set_dht_value(
self, key: types.TypedKey, subkey: types.ValueSubkey, data: bytes
) -> Optional[types.ValueData]:
pass pass
@abstractmethod @abstractmethod
async def watch_dht_values(self, key: TypedKey, subkeys: list[(ValueSubkey, ValueSubkey)], expiration: Timestamp, count: int) -> Timestamp: async def watch_dht_values(
self,
key: types.TypedKey,
subkeys: list[tuple[types.ValueSubkey, types.ValueSubkey]],
expiration: types.Timestamp,
count: int,
) -> types.Timestamp:
pass pass
@abstractmethod @abstractmethod
async def cancel_dht_watch(self, key: TypedKey, subkeys: list[(ValueSubkey, ValueSubkey)]) -> bool: async def cancel_dht_watch(
self,
key: types.TypedKey,
subkeys: list[tuple[types.ValueSubkey, types.ValueSubkey]],
) -> bool:
pass pass
@ -52,96 +83,153 @@ class TableDbTransaction(ABC):
@abstractmethod @abstractmethod
async def commit(self): async def commit(self):
pass pass
@abstractmethod @abstractmethod
async def rollback(self): async def rollback(self):
pass pass
@abstractmethod @abstractmethod
async def store(self, col: int, key: bytes, value: bytes): async def store(self, col: int, key: bytes, value: bytes):
pass pass
@abstractmethod @abstractmethod
async def delete(self, col: int, key: bytes): async def delete(self, col: int, key: bytes):
pass pass
class TableDb(ABC): class TableDb(ABC):
@abstractmethod @abstractmethod
async def get_column_count(self) -> int: async def get_column_count(self) -> int:
pass pass
@abstractmethod @abstractmethod
async def get_keys(self, col: int) -> list[bytes]: async def get_keys(self, col: int) -> list[bytes]:
pass pass
@abstractmethod @abstractmethod
async def transact(self) -> TableDbTransaction: async def transact(self) -> TableDbTransaction:
pass pass
@abstractmethod @abstractmethod
async def store(self, col: int, key: bytes, value: bytes): async def store(self, col: int, key: bytes, value: bytes):
pass pass
@abstractmethod @abstractmethod
async def load(self, col: int, key: bytes) -> Optional[bytes]: async def load(self, col: int, key: bytes) -> Optional[bytes]:
pass pass
@abstractmethod @abstractmethod
async def delete(self, col: int, key: bytes) -> Optional[bytes]: async def delete(self, col: int, key: bytes) -> Optional[bytes]:
pass pass
class CryptoSystem(ABC): class CryptoSystem(ABC):
@abstractmethod @abstractmethod
async def cached_dh(self, key: PublicKey, secret: SecretKey) -> SharedSecret: async def cached_dh(
self, key: types.PublicKey, secret: types.SecretKey
) -> types.SharedSecret:
pass pass
@abstractmethod @abstractmethod
async def compute_dh(self, key: PublicKey, secret: SecretKey) -> SharedSecret: async def compute_dh(
self, key: types.PublicKey, secret: types.SecretKey
) -> types.SharedSecret:
pass pass
@abstractmethod @abstractmethod
async def random_bytes(self, len: int) -> bytes: async def random_bytes(self, len: int) -> bytes:
pass pass
@abstractmethod @abstractmethod
async def default_salt_length(self) -> int: async def default_salt_length(self) -> int:
pass pass
@abstractmethod @abstractmethod
async def hash_password(self, password: bytes, salt: bytes) -> str: async def hash_password(self, password: bytes, salt: bytes) -> str:
pass pass
@abstractmethod @abstractmethod
async def verify_password(self, password: bytes, password_hash: str) -> bool: async def verify_password(self, password: bytes, password_hash: str) -> bool:
pass pass
@abstractmethod @abstractmethod
async def derive_shared_secret(self, password: bytes, salt: bytes) -> SharedSecret: async def derive_shared_secret(
self, password: bytes, salt: bytes
) -> types.SharedSecret:
pass pass
@abstractmethod @abstractmethod
async def random_nonce(self) -> Nonce: async def random_nonce(self) -> types.Nonce:
pass pass
@abstractmethod @abstractmethod
async def random_shared_secret(self) -> SharedSecret: async def random_shared_secret(self) -> types.SharedSecret:
pass pass
@abstractmethod @abstractmethod
async def generate_key_pair(self) -> KeyPair: async def generate_key_pair(self) -> types.KeyPair:
pass pass
@abstractmethod @abstractmethod
async def generate_hash(self, data: bytes) -> HashDigest: async def generate_hash(self, data: bytes) -> types.HashDigest:
pass pass
@abstractmethod @abstractmethod
async def validate_key_pair(self, key: PublicKey, secret: SecretKey) -> bool: async def validate_key_pair(
self, key: types.PublicKey, secret: types.SecretKey
) -> bool:
pass pass
@abstractmethod @abstractmethod
async def validate_hash(self, data: bytes, hash_digest: HashDigest) -> bool: async def validate_hash(self, data: bytes, hash_digest: types.HashDigest) -> bool:
pass pass
@abstractmethod @abstractmethod
async def distance(self, key1: CryptoKey, key2: CryptoKey) -> CryptoKeyDistance: async def distance(
self, key1: types.CryptoKey, key2: types.CryptoKey
) -> types.CryptoKeyDistance:
pass pass
@abstractmethod @abstractmethod
async def sign(self, key: PublicKey, secret: SecretKey, data: bytes) -> Signature: async def sign(
self, key: types.PublicKey, secret: types.SecretKey, data: bytes
) -> types.Signature:
pass pass
@abstractmethod @abstractmethod
async def verify(self, key: PublicKey, data: bytes, signature: Signature): async def verify(
self, key: types.PublicKey, data: bytes, signature: types.Signature
):
pass pass
@abstractmethod @abstractmethod
async def aead_overhead(self) -> int: async def aead_overhead(self) -> int:
pass pass
@abstractmethod @abstractmethod
async def decrypt_aead(self, body: bytes, nonce: Nonce, shared_secret: SharedSecret, associated_data: Optional[bytes]) -> bytes: async def decrypt_aead(
self,
body: bytes,
nonce: types.Nonce,
shared_secret: types.SharedSecret,
associated_data: Optional[bytes],
) -> bytes:
pass pass
@abstractmethod @abstractmethod
async def encrypt_aead(self, body: bytes, nonce: Nonce, shared_secret: SharedSecret, associated_data: Optional[bytes]) -> bytes: async def encrypt_aead(
self,
body: bytes,
nonce: types.Nonce,
shared_secret: types.SharedSecret,
associated_data: Optional[bytes],
) -> bytes:
pass pass
@abstractmethod @abstractmethod
async def crypt_no_auth(self, body: bytes, nonce: Nonce, shared_secret: SharedSecret) -> bytes: async def crypt_no_auth(
self, body: bytes, nonce: types.Nonce, shared_secret: types.SharedSecret
) -> bytes:
pass pass
@ -149,63 +237,95 @@ class VeilidAPI(ABC):
@abstractmethod @abstractmethod
async def control(self, args: list[str]) -> str: async def control(self, args: list[str]) -> str:
pass pass
@abstractmethod @abstractmethod
async def get_state(self) -> VeilidState: async def get_state(self) -> VeilidState:
pass pass
@abstractmethod @abstractmethod
async def attach(self): async def attach(self):
pass pass
@abstractmethod @abstractmethod
async def detach(self): async def detach(self):
pass pass
@abstractmethod @abstractmethod
async def new_private_route(self) -> Tuple[RouteId, bytes]: async def new_private_route(self) -> tuple[types.RouteId, bytes]:
pass pass
@abstractmethod @abstractmethod
async def new_custom_private_route(self, kinds: list[CryptoKind], stability: Stability, sequencing: Sequencing) -> Tuple[RouteId, bytes]: async def new_custom_private_route(
self,
kinds: list[types.CryptoKind],
stability: types.Stability,
sequencing: types.Sequencing,
) -> tuple[types.RouteId, bytes]:
pass pass
@abstractmethod @abstractmethod
async def import_remote_private_route(self, blob: bytes) -> RouteId: async def import_remote_private_route(self, blob: bytes) -> types.RouteId:
pass pass
@abstractmethod @abstractmethod
async def release_private_route(self, route_id: RouteId): async def release_private_route(self, route_id: types.RouteId):
pass pass
@abstractmethod @abstractmethod
async def app_call_reply(self, call_id: OperationId, message: bytes): async def app_call_reply(self, call_id: types.OperationId, message: bytes):
pass pass
@abstractmethod @abstractmethod
async def new_routing_context(self) -> RoutingContext: async def new_routing_context(self) -> RoutingContext:
pass pass
@abstractmethod @abstractmethod
async def open_table_db(self, name: str, column_count: int) -> TableDb: async def open_table_db(self, name: str, column_count: int) -> TableDb:
pass pass
@abstractmethod @abstractmethod
async def delete_table_db(self, name: str): async def delete_table_db(self, name: str):
pass pass
@abstractmethod @abstractmethod
async def get_crypto_system(self, kind: CryptoKind) -> CryptoSystem: async def get_crypto_system(self, kind: types.CryptoKind) -> CryptoSystem:
pass pass
@abstractmethod @abstractmethod
async def best_crypto_system(self) -> CryptoSystem: async def best_crypto_system(self) -> CryptoSystem:
pass pass
@abstractmethod @abstractmethod
async def verify_signatures(self, node_ids: list[TypedKey], data: bytes, signatures: list[TypedSignature]) -> list[TypedKey]: async def verify_signatures(
self,
node_ids: list[types.TypedKey],
data: bytes,
signatures: list[types.TypedSignature],
) -> list[types.TypedKey]:
pass pass
@abstractmethod @abstractmethod
async def generate_signatures(self, data: bytes, key_pairs: list[TypedKeyPair]) -> list[TypedSignature]: async def generate_signatures(
self, data: bytes, key_pairs: list[types.TypedKeyPair]
) -> list[types.TypedSignature]:
pass pass
@abstractmethod @abstractmethod
async def generate_key_pair(self, kind: CryptoKind) -> list[TypedKeyPair]: async def generate_key_pair(self, kind: types.CryptoKind) -> list[types.TypedKeyPair]:
pass pass
@abstractmethod @abstractmethod
async def now(self) -> Timestamp: async def now(self) -> types.Timestamp:
pass pass
@abstractmethod @abstractmethod
async def debug(self, command: str) -> str: async def debug(self, command: str) -> str:
pass pass
@abstractmethod @abstractmethod
async def veilid_version_string(self) -> str: async def veilid_version_string(self) -> str:
pass pass
@abstractmethod @abstractmethod
async def veilid_version(self) -> VeilidVersion: async def veilid_version(self) -> types.VeilidVersion:
pass pass

View File

@ -1,18 +1,46 @@
from typing import Self, Optional from dataclasses import dataclass, fields
from enum import StrEnum from enum import StrEnum
from json import dumps from typing import Optional, Self
from .types import TypedKey, TypedSecret
from .types import *
class VeilidConfigLogLevel(StrEnum): class VeilidConfigLogLevel(StrEnum):
OFF = 'Off' OFF = "Off"
ERROR = 'Error' ERROR = "Error"
WARN = 'Warn' WARN = "Warn"
INFO = 'Info' INFO = "Info"
DEBUG = 'Debug' DEBUG = "Debug"
TRACE = 'Trace' TRACE = "Trace"
class VeilidConfigCapabilities:
@dataclass
class ConfigBase:
@classmethod
def from_json(cls, json_data: dict) -> Self:
"""Return an instance of this type from the input data."""
args = {}
for field in fields(cls):
key = field.name
value = json_data[key]
try:
# See if this field's type knows how to load itself from JSON input.
loader = field.type.from_json
except AttributeError:
# No, it doesn't. Use the raw value.
args[key] = value
else:
# Yes, it does. Use the loading function's output.
args[key] = loader(value)
return cls(**args)
def to_json(self) -> dict:
return self.__dict__
@dataclass
class VeilidConfigCapabilities(ConfigBase):
protocol_udp: bool protocol_udp: bool
protocol_connect_tcp: bool protocol_connect_tcp: bool
protocol_accept_tcp: bool protocol_accept_tcp: bool
@ -21,30 +49,9 @@ class VeilidConfigCapabilities:
protocol_connect_wss: bool protocol_connect_wss: bool
protocol_accept_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 @dataclass
self.protocol_connect_tcp = protocol_connect_tcp class VeilidConfigProtectedStore(ConfigBase):
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 allow_insecure_fallback: bool
always_use_insecure_storage: bool always_use_insecure_storage: bool
directory: str directory: str
@ -52,52 +59,21 @@ class VeilidConfigProtectedStore:
device_encryption_key_password: str device_encryption_key_password: str
new_device_encryption_key_password: Optional[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 @dataclass
self.always_use_insecure_storage = always_use_insecure_storage class VeilidConfigTableStore(ConfigBase):
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 directory: str
delete: bool delete: bool
def __init__(self, directory: str, delete: bool):
self.directory = directory
self.delete = delete
@staticmethod @dataclass
def from_json(j: dict) -> Self: class VeilidConfigBlockStore(ConfigBase):
return VeilidConfigTableStore(j['directory'], j['delete'])
def to_json(self) -> dict:
return self.__dict__
class VeilidConfigBlockStore:
directory: str directory: str
delete: bool delete: bool
def __init__(self, directory: str, delete: bool):
self.directory = directory
self.delete = delete
@staticmethod @dataclass
def from_json(j: dict) -> Self: class VeilidConfigRoutingTable(ConfigBase):
return VeilidConfigBlockStore(j['directory'], j['delete'])
def to_json(self) -> dict:
return self.__dict__
class VeilidConfigRoutingTable:
node_id: list[TypedKey] node_id: list[TypedKey]
node_id_secret: list[TypedSecret] node_id_secret: list[TypedSecret]
bootstrap: list[str] bootstrap: list[str]
@ -107,34 +83,9 @@ class VeilidConfigRoutingTable:
limit_attached_good: int limit_attached_good: int
limit_attached_weak: 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 @dataclass
self.node_id_secret = node_id_secret class VeilidConfigRPC(ConfigBase):
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 concurrency: int
queue_size: int queue_size: int
max_timestamp_behind_ms: Optional[int] max_timestamp_behind_ms: Optional[int]
@ -143,31 +94,9 @@ class VeilidConfigRPC:
max_route_hop_count: int max_route_hop_count: int
default_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 @dataclass
self.queue_size = queue_size class VeilidConfigDHT(ConfigBase):
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 max_find_node_count: int
resolve_node_timeout_ms: int resolve_node_timeout_ms: int
resolve_node_count: int resolve_node_count: int
@ -188,186 +117,55 @@ class VeilidConfigDHT:
remote_max_subkey_cache_memory_mb: int remote_max_subkey_cache_memory_mb: int
remote_max_storage_space_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 @dataclass
self.resolve_node_timeout_ms =resolve_node_timeout_ms class VeilidConfigTLS(ConfigBase):
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 certificate_path: str
private_key_path: str private_key_path: str
connection_initial_timeout_ms: int 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 @dataclass
def from_json(j: dict) -> Self: class VeilidConfigHTTPS(ConfigBase):
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 enabled: bool
listen_address: str listen_address: str
path: str path: str
url: Optional[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 @dataclass
def from_json(j: dict) -> Self: class VeilidConfigHTTP(ConfigBase):
return VeilidConfigHTTPS(
j['enabled'],
j['listen_address'],
j['path'],
j['url'])
def to_json(self) -> dict:
return self.__dict__
class VeilidConfigHTTP:
enabled: bool enabled: bool
listen_address: str listen_address: str
path: str path: str
url: Optional[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 @dataclass
def from_json(j: dict) -> Self: class VeilidConfigApplication(ConfigBase):
return VeilidConfigHTTP(
j['enabled'],
j['listen_address'],
j['path'],
j['url'])
def to_json(self) -> dict:
return self.__dict__
class VeilidConfigApplication:
https: VeilidConfigHTTPS https: VeilidConfigHTTPS
http: VeilidConfigHTTP http: VeilidConfigHTTP
def __init__(self, https: VeilidConfigHTTPS, http: VeilidConfigHTTP):
self.https = https
self.http = http
@staticmethod @dataclass
def from_json(j: dict) -> Self: class VeilidConfigUDP(ConfigBase):
return VeilidConfigApplication(
VeilidConfigHTTPS.from_json(j['https']),
VeilidConfigHTTP.from_json(j['http']))
def to_json(self) -> dict:
return self.__dict__
class VeilidConfigUDP:
enabled: bool enabled: bool
socket_pool_size: int socket_pool_size: int
listen_address: str listen_address: str
public_address: Optional[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 @dataclass
def from_json(j: dict) -> Self: class VeilidConfigTCP(ConfigBase):
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 connect: bool
listen: bool listen: bool
max_connections: int max_connections: int
listen_address: str listen_address: str
public_address: Optional[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 @dataclass
def from_json(j: dict) -> Self: class VeilidConfigWS(ConfigBase):
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 connect: bool
listen: bool listen: bool
max_connections: int max_connections: int
@ -375,27 +173,9 @@ class VeilidConfigWS:
path: str path: str
url: Optional[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 @dataclass
def from_json(j: dict) -> Self: class VeilidConfigWSS(ConfigBase):
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 connect: bool
listen: bool listen: bool
max_connections: int max_connections: int
@ -403,50 +183,17 @@ class VeilidConfigWSS:
path: str path: str
url: Optional[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 @dataclass
def from_json(j: dict) -> Self: class VeilidConfigProtocol(ConfigBase):
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 udp: VeilidConfigUDP
tcp: VeilidConfigTCP tcp: VeilidConfigTCP
ws: VeilidConfigWS ws: VeilidConfigWS
wss: VeilidConfigWSS 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 @dataclass
def from_json(j: dict) -> Self: class VeilidConfigNetwork(ConfigBase):
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_initial_timeout_ms: int
connection_inactivity_timeout_ms: int connection_inactivity_timeout_ms: int
max_connections_per_ip4: int max_connections_per_ip4: int
@ -466,58 +213,9 @@ class VeilidConfigNetwork:
application: VeilidConfigApplication application: VeilidConfigApplication
protocol: VeilidConfigProtocol 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 @dataclass
self.connection_inactivity_timeout_ms = connection_inactivity_timeout_ms class VeilidConfig(ConfigBase):
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 program_name: str
namespace: str namespace: str
capabilities: VeilidConfigCapabilities capabilities: VeilidConfigCapabilities
@ -525,29 +223,3 @@ class VeilidConfig:
table_store: VeilidConfigTableStore table_store: VeilidConfigTableStore
block_store: VeilidConfigBlockStore block_store: VeilidConfigBlockStore
network: VeilidConfigNetwork 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__

View File

@ -1,142 +1,155 @@
from typing import Self, Any import inspect
from dataclasses import dataclass
from typing import Any, Self
_ERROR_REGISTRY: dict[str, type] = {}
class VeilidAPIError(Exception): class VeilidAPIError(Exception):
"""Veilid API error exception base class""" """Veilid API error exception base class"""
pass
@staticmethod label = "Base class"
def from_json(j: dict) -> Self:
match j['kind']: def __init__(self, *args, **kwargs):
case 'NotInitialized': super().__init__(self.label, *args, **kwargs)
return VeilidAPIErrorNotInitialized()
case 'AlreadyInitialized': @classmethod
return VeilidAPIErrorAlreadyInitialized() def from_json(cls, json: dict) -> Self:
case 'Timeout': kind = json["kind"]
return VeilidAPIErrorTimeout() try:
case 'TryAgain': error_class = _ERROR_REGISTRY[kind]
return VeilidAPIErrorTryAgain() except KeyError:
case 'Shutdown': return cls(f"Unknown exception type: {kind}")
return VeilidAPIErrorShutdown()
case 'InvalidTarget': args = {key: value for key, value in json.items() if key != "kind"}
return VeilidAPIErrorInvalidTarget() return error_class(**args)
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']))
@dataclass
class VeilidAPIErrorNotInitialized(VeilidAPIError): class VeilidAPIErrorNotInitialized(VeilidAPIError):
"""Veilid was not initialized""" """Veilid was not initialized"""
def __init__(self):
super().__init__("Not initialized")
label = "Not initialized"
@dataclass
class VeilidAPIErrorAlreadyInitialized(VeilidAPIError): class VeilidAPIErrorAlreadyInitialized(VeilidAPIError):
"""Veilid was already initialized""" """Veilid was already initialized"""
def __init__(self):
super().__init__("Already initialized")
label = "Already initialized"
@dataclass
class VeilidAPIErrorTimeout(VeilidAPIError): class VeilidAPIErrorTimeout(VeilidAPIError):
"""Veilid operation timed out""" """Veilid operation timed out"""
def __init__(self):
super().__init__("Timeout")
label = "Timeout"
@dataclass
class VeilidAPIErrorTryAgain(VeilidAPIError): class VeilidAPIErrorTryAgain(VeilidAPIError):
"""Operation could not be performed at this time, retry again later""" """Operation could not be performed at this time, retry again later"""
def __init__(self):
super().__init__("Try again")
label = "Try again"
@dataclass
class VeilidAPIErrorShutdown(VeilidAPIError): class VeilidAPIErrorShutdown(VeilidAPIError):
"""Veilid was already shut down""" """Veilid was already shut down"""
def __init__(self):
super().__init__("Shutdown")
label = "Shutdown"
@dataclass
class VeilidAPIErrorInvalidTarget(VeilidAPIError): class VeilidAPIErrorInvalidTarget(VeilidAPIError):
"""Target of operation is not valid""" """Target of operation is not valid"""
def __init__(self):
super().__init__("Invalid target")
label = "Invalid target"
@dataclass
class VeilidAPIErrorNoConnection(VeilidAPIError): class VeilidAPIErrorNoConnection(VeilidAPIError):
"""Connection could not be established""" """Connection could not be established"""
message: str
def __init__(self, message: str):
super().__init__("No connection")
self.message = message
label = "No connection"
message: str
@dataclass
class VeilidAPIErrorKeyNotFound(VeilidAPIError): class VeilidAPIErrorKeyNotFound(VeilidAPIError):
"""Key was not found""" """Key was not found"""
key: str
def __init__(self, key: str):
super().__init__("Key not found")
self.key = key
label = "Key not found"
key: str
@dataclass
class VeilidAPIErrorInternal(VeilidAPIError): class VeilidAPIErrorInternal(VeilidAPIError):
"""Veilid experienced an internal failure""" """Veilid experienced an internal failure"""
message: str
def __init__(self, message: str):
super().__init__("Internal")
self.message = message
label = "Internal"
message: str
@dataclass
class VeilidAPIErrorUnimplemented(VeilidAPIError): class VeilidAPIErrorUnimplemented(VeilidAPIError):
"""Functionality is not yet implemented""" """Functionality is not yet implemented"""
message: str
def __init__(self, message: str):
super().__init__("Unimplemented")
self.message = message
label = "Unimplemented"
message: str
@dataclass
class VeilidAPIErrorParseError(VeilidAPIError): class VeilidAPIErrorParseError(VeilidAPIError):
"""Value was not in a parseable format""" """Value was not in a parseable format"""
label = "Parse error"
message: str message: str
value: str value: str
def __init__(self, message: str, value: str):
super().__init__("Parse error")
self.message = message
self.value = value
@dataclass
class VeilidAPIErrorInvalidArgument(VeilidAPIError): class VeilidAPIErrorInvalidArgument(VeilidAPIError):
"""Argument is not valid in this context""" """Argument is not valid in this context"""
label = "Invalid argument"
context: str context: str
argument: str argument: str
value: 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
@dataclass
class VeilidAPIErrorMissingArgument(VeilidAPIError): class VeilidAPIErrorMissingArgument(VeilidAPIError):
"""Required argument was missing""" """Required argument was missing"""
label = "Missing argument"
context: str context: str
argument: str argument: str
def __init__(self, context: str, argument: str):
super().__init__("Missing argument")
self.context = context
self.argument = argument
@dataclass
class VeilidAPIErrorGeneric(VeilidAPIError): class VeilidAPIErrorGeneric(VeilidAPIError):
"""Generic error message""" """Generic error message"""
label = "Generic"
message: str message: str
def __init__(self, message: str):
super().__init__("Generic")
self.message = message # Build a mapping of canonicalized labels to their exception classes. Do this in-place to update
# the object inside the closure so VeilidAPIError.from_json can access the values.
_ERROR_REGISTRY.clear()
_ERROR_REGISTRY.update(
{
obj.label.title().replace(" ", ""): obj
for obj in vars().values()
if inspect.isclass(obj) and issubclass(obj, VeilidAPIError)
}
)
def raise_api_result(api_result: dict) -> Any: def raise_api_result(api_result: dict) -> Any:
if "value" in api_result: if "value" in api_result:
return api_result["value"] return api_result["value"]
elif "error" in api_result: if "error" in api_result:
raise VeilidAPIError.from_json(api_result["error"]) raise VeilidAPIError.from_json(api_result["error"])
else:
raise ValueError("Invalid format for ApiResult") raise ValueError("Invalid format for ApiResult")

File diff suppressed because it is too large Load Diff

View File

@ -1,36 +1,47 @@
from typing import Self, Optional
from enum import StrEnum from enum import StrEnum
from typing import Optional, Self
from .config import VeilidConfig
from .types import (ByteCount, RouteId, Timestamp, TimestampDuration, TypedKey,
ValueData, ValueSubkey, VeilidLogLevel,
urlsafe_b64decode_no_pad)
from .types import *
from .config import *
class AttachmentState(StrEnum): class AttachmentState(StrEnum):
DETACHED = 'Detached' DETACHED = "Detached"
ATTACHING = 'Attaching' ATTACHING = "Attaching"
ATTACHED_WEAK = 'AttachedWeak' ATTACHED_WEAK = "AttachedWeak"
ATTACHED_GOOD = 'AttachedGood' ATTACHED_GOOD = "AttachedGood"
ATTACHED_STRONG = 'AttachedStrong' ATTACHED_STRONG = "AttachedStrong"
FULLY_ATTACHED = 'FullyAttached' FULLY_ATTACHED = "FullyAttached"
OVER_ATTACHED = 'OverAttached' OVER_ATTACHED = "OverAttached"
DETACHING = 'Detaching' DETACHING = "Detaching"
class VeilidStateAttachment: class VeilidStateAttachment:
state: AttachmentState state: AttachmentState
public_internet_ready: bool public_internet_ready: bool
local_network_ready: bool local_network_ready: bool
def __init__(self, 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.state = state
self.public_internet_ready = public_internet_ready self.public_internet_ready = public_internet_ready
self.local_network_ready = local_network_ready self.local_network_ready = local_network_ready
@staticmethod @classmethod
def from_json(j: dict) -> Self: def from_json(cls, j: dict) -> Self:
'''JSON object hook''' """JSON object hook"""
return VeilidStateAttachment( return cls(
AttachmentState(j['state']), AttachmentState(j["state"]),
j['public_internet_ready'], j["public_internet_ready"],
j['local_network_ready']) j["local_network_ready"],
)
class RPCStats: class RPCStats:
messages_sent: int messages_sent: int
@ -42,9 +53,17 @@ class RPCStats:
recent_lost_answers: int recent_lost_answers: int
failed_to_send: int failed_to_send: int
def __init__(self, messages_sent: int, messages_rcvd: int, questions_in_flight: int, def __init__(
last_question_ts: Optional[Timestamp], last_seen_ts: Optional[Timestamp], self,
first_consecutive_seen_ts: Optional[Timestamp], recent_lost_answers: int, failed_to_send: int): 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_sent = messages_sent
self.messages_rcvd = messages_rcvd self.messages_rcvd = messages_rcvd
self.questions_in_flight = questions_in_flight self.questions_in_flight = questions_in_flight
@ -54,36 +73,46 @@ class RPCStats:
self.recent_lost_answers = recent_lost_answers self.recent_lost_answers = recent_lost_answers
self.failed_to_send = failed_to_send self.failed_to_send = failed_to_send
@staticmethod @classmethod
def from_json(j: dict) -> Self: def from_json(cls, j: dict) -> Self:
'''JSON object hook''' """JSON object hook"""
return RPCStats( return cls(
j['messages_sent'], j["messages_sent"],
j['messages_rcvd'], j["messages_rcvd"],
j['questions_in_flight'], j["questions_in_flight"],
None if j['last_question_ts'] is None else Timestamp(j['last_question_ts']), 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["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']), None
j['recent_lost_answers'], if j["first_consecutive_seen_ts"] is None
j['failed_to_send']) else Timestamp(j["first_consecutive_seen_ts"]),
j["recent_lost_answers"],
j["failed_to_send"],
)
class LatencyStats: class LatencyStats:
fastest: TimestampDuration fastest: TimestampDuration
average: TimestampDuration average: TimestampDuration
slowest: TimestampDuration slowest: TimestampDuration
def __init__(self, fastest: TimestampDuration, average: TimestampDuration, slowest: TimestampDuration): def __init__(
self,
fastest: TimestampDuration,
average: TimestampDuration,
slowest: TimestampDuration,
):
self.fastest = fastest self.fastest = fastest
self.average = average self.average = average
self.slowest = slowest self.slowest = slowest
@staticmethod @classmethod
def from_json(j: dict) -> Self: def from_json(cls, j: dict) -> Self:
'''JSON object hook''' """JSON object hook"""
return LatencyStats( return cls(
TimestampDuration(j['fastest']), TimestampDuration(j["fastest"]),
TimestampDuration(j['average']), TimestampDuration(j["average"]),
TimestampDuration(j['slowest'])) TimestampDuration(j["slowest"]),
)
class TransferStats: class TransferStats:
@ -92,20 +121,27 @@ class TransferStats:
average: ByteCount average: ByteCount
minimum: ByteCount minimum: ByteCount
def __init__(self, total: ByteCount, maximum: ByteCount, average: ByteCount, minimum: ByteCount): def __init__(
self,
total: ByteCount,
maximum: ByteCount,
average: ByteCount,
minimum: ByteCount,
):
self.total = total self.total = total
self.maximum = maximum self.maximum = maximum
self.average = average self.average = average
self.minimum = minimum self.minimum = minimum
@staticmethod @classmethod
def from_json(j: dict) -> Self: def from_json(cls, j: dict) -> Self:
'''JSON object hook''' """JSON object hook"""
return TransferStats( return cls(
ByteCount(j['total']), ByteCount(j["total"]),
ByteCount(j['maximum']), ByteCount(j["maximum"]),
ByteCount(j['average']), ByteCount(j["average"]),
ByteCount(j['minimum'])) ByteCount(j["minimum"]),
)
class TransferStatsDownUp: class TransferStatsDownUp:
@ -116,12 +152,11 @@ class TransferStatsDownUp:
self.down = down self.down = down
self.up = up self.up = up
@staticmethod @classmethod
def from_json(j: dict) -> Self: def from_json(cls, j: dict) -> Self:
'''JSON object hook''' """JSON object hook"""
return TransferStatsDownUp( return cls(TransferStats.from_json(j["down"]), TransferStats.from_json(j["up"]))
TransferStats.from_json(j['down']),
TransferStats.from_json(j['up']))
class PeerStats: class PeerStats:
time_added: Timestamp time_added: Timestamp
@ -129,20 +164,28 @@ class PeerStats:
latency: Optional[LatencyStats] latency: Optional[LatencyStats]
transfer: TransferStatsDownUp transfer: TransferStatsDownUp
def __init__(self, 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.time_added = time_added
self.rpc_stats = rpc_stats self.rpc_stats = rpc_stats
self.latency = latency self.latency = latency
self.transfer = transfer self.transfer = transfer
@staticmethod @classmethod
def from_json(j: dict) -> Self: def from_json(cls, j: dict) -> Self:
'''JSON object hook''' """JSON object hook"""
return PeerStats( return cls(
j['time_added'], j["time_added"],
RPCStats.from_json(j['rpc_stats']), RPCStats.from_json(j["rpc_stats"]),
None if j['latency'] is None else LatencyStats.from_json(j['latency']), None if j["latency"] is None else LatencyStats.from_json(j["latency"]),
TransferStatsDownUp.from_json(j['transfer'])) TransferStatsDownUp.from_json(j["transfer"]),
)
class PeerTableData: class PeerTableData:
node_ids: list[str] node_ids: list[str]
@ -154,13 +197,13 @@ class PeerTableData:
self.peer_address = peer_address self.peer_address = peer_address
self.peer_stats = peer_stats self.peer_stats = peer_stats
@staticmethod @classmethod
def from_json(j: dict) -> Self: def from_json(cls, j: dict) -> Self:
'''JSON object hook''' """JSON object hook"""
return PeerTableData( return cls(
j['node_ids'], j["node_ids"], j["peer_address"], PeerStats.from_json(j["peer_stats"])
j['peer_address'], )
PeerStats.from_json(j['peer_stats']))
class VeilidStateNetwork: class VeilidStateNetwork:
started: bool started: bool
@ -168,20 +211,28 @@ class VeilidStateNetwork:
bps_up: ByteCount bps_up: ByteCount
peers: list[PeerTableData] peers: list[PeerTableData]
def __init__(self, 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.started = started
self.bps_down = bps_down self.bps_down = bps_down
self.bps_up = bps_up self.bps_up = bps_up
self.peers = peers self.peers = peers
@staticmethod @classmethod
def from_json(j: dict) -> Self: def from_json(cls, j: dict) -> Self:
'''JSON object hook''' """JSON object hook"""
return VeilidStateNetwork( return cls(
j['started'], j["started"],
ByteCount(j['bps_down']), ByteCount(j["bps_down"]),
ByteCount(j['bps_up']), ByteCount(j["bps_up"]),
list(map(lambda x: PeerTableData.from_json(x), j['peers']))) [PeerTableData.from_json(peer) for peer in j["peers"]],
)
class VeilidStateConfig: class VeilidStateConfig:
config: VeilidConfig config: VeilidConfig
@ -189,48 +240,54 @@ class VeilidStateConfig:
def __init__(self, config: VeilidConfig): def __init__(self, config: VeilidConfig):
self.config = config self.config = config
@staticmethod @classmethod
def from_json(j: dict) -> Self: def from_json(cls, j: dict) -> Self:
'''JSON object hook''' """JSON object hook"""
return VeilidStateConfig( return cls(VeilidConfig.from_json(j["config"]))
VeilidConfig.from_json(j['config'])
)
class VeilidState: class VeilidState:
attachment: VeilidStateAttachment attachment: VeilidStateAttachment
network: VeilidStateNetwork network: VeilidStateNetwork
config: VeilidStateConfig config: VeilidStateConfig
def __init__(self, attachment: VeilidStateAttachment, network: VeilidStateNetwork, config: VeilidStateConfig): def __init__(
self,
attachment: VeilidStateAttachment,
network: VeilidStateNetwork,
config: VeilidStateConfig,
):
self.attachment = attachment self.attachment = attachment
self.network = network self.network = network
self.config = config self.config = config
@staticmethod @classmethod
def from_json(j: dict) -> Self: def from_json(cls, j: dict) -> Self:
'''JSON object hook''' """JSON object hook"""
return VeilidState( return cls(
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: class VeilidLog:
log_level: VeilidLogLevel log_level: VeilidLogLevel
message: str message: str
backtrace: Optional[str] backtrace: Optional[str]
def __init__(self, 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.log_level = log_level
self.message = message self.message = message
self.backtrace = backtrace self.backtrace = backtrace
@staticmethod @classmethod
def from_json(j: dict) -> Self: def from_json(cls, j: dict) -> Self:
'''JSON object hook''' """JSON object hook"""
return VeilidLog( return cls(VeilidLogLevel(j["log_level"]), j["message"], j["backtrace"])
VeilidLogLevel(j['log_level']),
j['message'],
j['backtrace'])
class VeilidAppMessage: class VeilidAppMessage:
sender: Optional[TypedKey] sender: Optional[TypedKey]
@ -240,12 +297,14 @@ class VeilidAppMessage:
self.sender = sender self.sender = sender
self.message = message self.message = message
@staticmethod @classmethod
def from_json(j: dict) -> Self: def from_json(cls, j: dict) -> Self:
'''JSON object hook''' """JSON object hook"""
return VeilidAppMessage( return cls(
None if j['sender'] is None else TypedKey(j['sender']), None if j["sender"] is None else TypedKey(j["sender"]),
urlsafe_b64decode_no_pad(j['message'])) urlsafe_b64decode_no_pad(j["message"]),
)
class VeilidAppCall: class VeilidAppCall:
sender: Optional[TypedKey] sender: Optional[TypedKey]
@ -257,13 +316,15 @@ class VeilidAppCall:
self.message = message self.message = message
self.call_id = call_id self.call_id = call_id
@staticmethod @classmethod
def from_json(j: dict) -> Self: def from_json(cls, j: dict) -> Self:
'''JSON object hook''' """JSON object hook"""
return VeilidAppCall( return cls(
None if j['sender'] is None else TypedKey(j['sender']), None if j["sender"] is None else TypedKey(j["sender"]),
urlsafe_b64decode_no_pad(j['message']), urlsafe_b64decode_no_pad(j["message"]),
j['call_id']) j["call_id"],
)
class VeilidRouteChange: class VeilidRouteChange:
dead_routes: list[RouteId] dead_routes: list[RouteId]
@ -273,12 +334,14 @@ class VeilidRouteChange:
self.dead_routes = dead_routes self.dead_routes = dead_routes
self.dead_remote_routes = dead_remote_routes self.dead_remote_routes = dead_remote_routes
@staticmethod @classmethod
def from_json(j: dict) -> Self: def from_json(cls, j: dict) -> Self:
'''JSON object hook''' """JSON object hook"""
return VeilidRouteChange( return cls(
list(map(lambda x: RouteId(x), j['dead_routes'])), [RouteId(route) for route in j["dead_routes"]],
list(map(lambda x: RouteId(x), j['dead_remote_routes']))) [RouteId(route) for route in j["dead_remote_routes"]],
)
class VeilidValueChange: class VeilidValueChange:
key: TypedKey key: TypedKey
@ -286,20 +349,23 @@ class VeilidValueChange:
count: int count: int
value: ValueData value: ValueData
def __init__(self, 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.key = key
self.subkeys = subkeys self.subkeys = subkeys
self.count = count self.count = count
self.value = value self.value = value
@staticmethod @classmethod
def from_json(j: dict) -> Self: def from_json(cls, j: dict) -> Self:
'''JSON object hook''' """JSON object hook"""
return VeilidValueChange( return cls(
TypedKey(j['key']), TypedKey(j["key"]),
list(map(lambda x: ValueSubkey(x), j['subkeys'])), [ValueSubkey(key) for key in j["subkeys"]],
j['count'], j["count"],
ValueData.from_json(j['value'])) ValueData.from_json(j["value"]),
)
class VeilidUpdateKind(StrEnum): class VeilidUpdateKind(StrEnum):
@ -313,19 +379,36 @@ class VeilidUpdateKind(StrEnum):
VALUE_CHANGE = "ValueChange" VALUE_CHANGE = "ValueChange"
SHUTDOWN = "Shutdown" SHUTDOWN = "Shutdown"
VeilidUpdateDetailType = Optional[
VeilidLog
| VeilidAppMessage
| VeilidAppCall
| VeilidStateAttachment
| VeilidStateNetwork
| VeilidStateConfig
| VeilidRouteChange
| VeilidValueChange
]
class VeilidUpdate: class VeilidUpdate:
kind: VeilidUpdateKind kind: VeilidUpdateKind
detail: Optional[VeilidLog | VeilidAppMessage | VeilidAppCall | VeilidStateAttachment | VeilidStateNetwork | VeilidStateConfig | VeilidRouteChange | VeilidValueChange] detail: VeilidUpdateDetailType
def __init__(self, kind: VeilidUpdateKind, detail: Optional[VeilidLog | VeilidAppMessage | VeilidAppCall | VeilidStateAttachment | VeilidStateNetwork | VeilidStateConfig | VeilidRouteChange | VeilidValueChange]): def __init__(
self,
kind: VeilidUpdateKind,
detail: VeilidUpdateDetailType,
):
self.kind = kind self.kind = kind
self.detail = detail self.detail = detail
@staticmethod @classmethod
def from_json(j: dict) -> Self: def from_json(cls, j: dict) -> Self:
'''JSON object hook''' """JSON object hook"""
kind = VeilidUpdateKind(j['kind']) kind = VeilidUpdateKind(j["kind"])
detail = None detail: VeilidUpdateDetailType = None
match kind: match kind:
case VeilidUpdateKind.LOG: case VeilidUpdateKind.LOG:
detail = VeilidLog.from_json(j) detail = VeilidLog.from_json(j)
@ -347,4 +430,4 @@ class VeilidUpdate:
detail = None detail = None
case _: case _:
raise ValueError("Unknown VeilidUpdateKind") raise ValueError("Unknown VeilidUpdateKind")
return VeilidUpdate(kind, detail) return cls(kind, detail)

View File

@ -1,12 +1,11 @@
import time
import json
import base64 import base64
import json
from enum import StrEnum from enum import StrEnum
from typing import Self, Optional, Any, Tuple from typing import Any, Optional, Self, Tuple
#################################################################### ####################################################################
def urlsafe_b64encode_no_pad(b: bytes) -> str: def urlsafe_b64encode_no_pad(b: bytes) -> str:
""" """
Removes any `=` used as padding from the encoded string. Removes any `=` used as padding from the encoded string.
@ -22,6 +21,7 @@ def urlsafe_b64decode_no_pad(s: str) -> bytes:
s = s + ("=" * padding) s = s + ("=" * padding)
return base64.urlsafe_b64decode(s) return base64.urlsafe_b64decode(s)
class VeilidJSONEncoder(json.JSONEncoder): class VeilidJSONEncoder(json.JSONEncoder):
def default(self, o): def default(self, o):
if isinstance(o, bytes): if isinstance(o, bytes):
@ -32,170 +32,203 @@ class VeilidJSONEncoder(json.JSONEncoder):
@staticmethod @staticmethod
def dumps(req: Any, *args, **kwargs) -> str: def dumps(req: Any, *args, **kwargs) -> str:
return json.dumps(req, cls = VeilidJSONEncoder, *args, **kwargs) return json.dumps(req, cls=VeilidJSONEncoder, *args, **kwargs)
#################################################################### ####################################################################
class VeilidLogLevel(StrEnum): class VeilidLogLevel(StrEnum):
ERROR = 'Error' ERROR = "Error"
WARN = 'Warn' WARN = "Warn"
INFO = 'Info' INFO = "Info"
DEBUG = 'Debug' DEBUG = "Debug"
TRACE = 'Trace' TRACE = "Trace"
class CryptoKind(StrEnum): class CryptoKind(StrEnum):
CRYPTO_KIND_NONE = "NONE" CRYPTO_KIND_NONE = "NONE"
CRYPTO_KIND_VLD0 = "VLD0" CRYPTO_KIND_VLD0 = "VLD0"
class Stability(StrEnum): class Stability(StrEnum):
LOW_LATENCY = "LowLatency" LOW_LATENCY = "LowLatency"
RELIABLE = "Reliable" RELIABLE = "Reliable"
class Sequencing(StrEnum): class Sequencing(StrEnum):
NO_PREFERENCE = "NoPreference" NO_PREFERENCE = "NoPreference"
PREFER_ORDERED = "PreferOrdered" PREFER_ORDERED = "PreferOrdered"
ENSURE_ORDERED = "EnsureOrdered" ENSURE_ORDERED = "EnsureOrdered"
class DHTSchemaKind(StrEnum): class DHTSchemaKind(StrEnum):
DFLT = "DFLT" DFLT = "DFLT"
SMPL = "SMPL" SMPL = "SMPL"
#################################################################### ####################################################################
class Timestamp(int): class Timestamp(int):
pass pass
class TimestampDuration(int): class TimestampDuration(int):
pass pass
class ByteCount(int): class ByteCount(int):
pass pass
class OperationId(int):
class OperationId(str):
pass pass
class RouteId(str): class RouteId(str):
pass pass
class CryptoKey:
class EncodedString(str):
def to_bytes(self) -> bytes: def to_bytes(self) -> bytes:
return urlsafe_b64decode_no_pad(self) return urlsafe_b64decode_no_pad(self)
class CryptoKeyDistance(CryptoKey, str): @classmethod
@staticmethod def from_bytes(cls, b: bytes) -> Self:
def from_bytes(b: bytes) -> Self: return cls(urlsafe_b64encode_no_pad(b))
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): class CryptoKey(EncodedString):
@staticmethod pass
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): class CryptoKeyDistance(CryptoKey):
@staticmethod pass
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): class PublicKey(CryptoKey):
@staticmethod pass
def from_bytes(b: bytes) -> Self:
return Signature(urlsafe_b64encode_no_pad(b))
def to_bytes(self) -> bytes: class SecretKey(CryptoKey):
return urlsafe_b64decode_no_pad(self) pass
class SharedSecret(CryptoKey):
pass
class HashDigest(CryptoKey):
pass
class Signature(EncodedString):
pass
class Nonce(EncodedString):
pass
class KeyPair(str): class KeyPair(str):
@staticmethod @classmethod
def from_parts(key: PublicKey, secret: SecretKey) -> Self: def from_parts(cls, key: PublicKey, secret: SecretKey) -> Self:
return KeyPair(key + ":" + secret) return cls(f"{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 key(self) -> PublicKey:
return PublicKey(self.split(":", 1)[0])
def secret(self) -> SecretKey:
return SecretKey(self.split(":", 1)[1])
def to_parts(self) -> Tuple[PublicKey, SecretKey]:
public, secret = self.split(":", 1)
return (PublicKey(public), SecretKey(secret))
class CryptoTyped(str):
def kind(self) -> CryptoKind: def kind(self) -> CryptoKind:
if self[4] != ':': if self[4] != ":":
raise ValueError("Not CryptoTyped") raise ValueError("Not CryptoTyped")
return CryptoKind(self[0:4]) return CryptoKind(self[0:4])
def _value(self) -> str: def _value(self) -> str:
if self[4] != ':': if self[4] != ":":
raise ValueError("Not CryptoTyped") raise ValueError("Not CryptoTyped")
return self[5:] return self[5:]
class TypedKey(CryptoTyped, str):
@staticmethod class TypedKey(CryptoTyped):
def from_value(kind: CryptoKind, value: PublicKey) -> Self: @classmethod
return TypedKey(kind + ":" + value) def from_value(cls, kind: CryptoKind, value: PublicKey) -> Self:
return cls(f"{kind}:{value}")
def value(self) -> PublicKey: def value(self) -> PublicKey:
PublicKey(self._value()) return PublicKey(self._value())
class TypedSecret(CryptoTyped):
@classmethod
def from_value(cls, kind: CryptoKind, value: SecretKey) -> Self:
return cls(f"{kind}:{value}")
class TypedSecret(CryptoTyped, str):
@staticmethod
def from_value(kind: CryptoKind, value: SecretKey) -> Self:
return TypedSecret(kind + ":" + value)
def value(self) -> SecretKey: def value(self) -> SecretKey:
SecretKey(self._value()) return SecretKey(self._value())
class TypedKeyPair(CryptoTyped):
@classmethod
def from_value(cls, kind: CryptoKind, value: KeyPair) -> Self:
return cls(f"{kind}:{value}")
class TypedKeyPair(CryptoTyped, str):
@staticmethod
def from_value(kind: CryptoKind, value: KeyPair) -> Self:
return TypedKeyPair(kind + ":" + value)
def value(self) -> KeyPair: def value(self) -> KeyPair:
KeyPair(self._value()) return KeyPair(self._value())
class TypedSignature(CryptoTyped):
@classmethod
def from_value(cls, kind: CryptoKind, value: Signature) -> Self:
return cls(f"{kind}:{value}")
class TypedSignature(CryptoTyped, str):
@staticmethod
def from_value(kind: CryptoKind, value: Signature) -> Self:
return TypedSignature(kind + ":" + value)
def value(self) -> Signature: def value(self) -> Signature:
Signature(self._value()) return Signature(self._value())
class ValueSubkey(int): class ValueSubkey(int):
pass pass
class ValueSeqNum(int): class ValueSeqNum(int):
pass pass
#################################################################### ####################################################################
class VeilidVersion: class VeilidVersion:
_major: int _major: int
_minor: int _minor: int
_patch: int _patch: int
def __init__(self, major: int, minor: int, patch: int): def __init__(self, major: int, minor: int, patch: int):
self._major = major self._major = major
self._minor = minor self._minor = minor
self._patch = patch self._patch = patch
@property @property
def major(self): def major(self):
return self._major return self._major
@property @property
def minor(self): def minor(self):
return self._minor return self._minor
@property @property
def patch(self): def patch(self):
return self._patch return self._patch
class NewPrivateRouteResult: class NewPrivateRouteResult:
route_id: RouteId route_id: RouteId
blob: bytes blob: bytes
@ -207,26 +240,27 @@ class NewPrivateRouteResult:
def to_tuple(self) -> Tuple[RouteId, bytes]: def to_tuple(self) -> Tuple[RouteId, bytes]:
return (self.route_id, self.blob) return (self.route_id, self.blob)
@staticmethod @classmethod
def from_json(j: dict) -> Self: def from_json(cls, j: dict) -> Self:
return NewPrivateRouteResult( return cls(RouteId(j["route_id"]), urlsafe_b64decode_no_pad(j["blob"]))
RouteId(j['route_id']),
urlsafe_b64decode_no_pad(j['blob']))
class DHTSchemaSMPLMember: class DHTSchemaSMPLMember:
m_key: PublicKey m_key: PublicKey
m_cnt: int m_cnt: int
def __init__(self, m_key: PublicKey, m_cnt: int): def __init__(self, m_key: PublicKey, m_cnt: int):
self.m_key = m_key self.m_key = m_key
self.m_cnt = m_cnt self.m_cnt = m_cnt
@staticmethod
def from_json(j: dict) -> Self: @classmethod
return DHTSchemaSMPLMember( def from_json(cls, j: dict) -> Self:
PublicKey(j['m_key']), return cls(PublicKey(j["m_key"]), j["m_cnt"])
j['m_cnt'])
def to_json(self) -> dict: def to_json(self) -> dict:
return self.__dict__ return self.__dict__
class DHTSchema: class DHTSchema:
kind: DHTSchemaKind kind: DHTSchemaKind
@ -235,50 +269,60 @@ class DHTSchema:
for k, v in kwargs.items(): for k, v in kwargs.items():
setattr(self, k, v) setattr(self, k, v)
@staticmethod @classmethod
def dflt(o_cnt: int) -> Self: def dflt(cls, o_cnt: int) -> Self:
Self(DHTSchemaKind.DFLT, o_cnt = o_cnt) return cls(DHTSchemaKind.DFLT, o_cnt=o_cnt)
@staticmethod @classmethod
def smpl(o_cnt: int, members: list[DHTSchemaSMPLMember]) -> Self: def smpl(cls, o_cnt: int, members: list[DHTSchemaSMPLMember]) -> Self:
Self(DHTSchemaKind.SMPL, o_cnt = o_cnt, members = members) return cls(DHTSchemaKind.SMPL, o_cnt=o_cnt, members=members)
@staticmethod @classmethod
def from_json(j: dict) -> Self: def from_json(cls, j: dict) -> Self:
if DHTSchemaKind(j['kind']) == DHTSchemaKind.DFLT: if DHTSchemaKind(j["kind"]) == DHTSchemaKind.DFLT:
return DHTSchema.dflt(j['o_cnt']) return cls.dflt(j["o_cnt"])
if DHTSchemaKind(j['kind']) == DHTSchemaKind.SMPL: if DHTSchemaKind(j["kind"]) == DHTSchemaKind.SMPL:
return DHTSchema.smpl( return cls.smpl(
j['o_cnt'], j["o_cnt"],
list(map(lambda x: DHTSchemaSMPLMember.from_json(x), j['members']))) [DHTSchemaSMPLMember.from_json(member) for member in j["members"]],
raise Exception("Unknown DHTSchema kind", j['kind']) )
raise Exception("Unknown DHTSchema kind", j["kind"])
def to_json(self) -> dict: def to_json(self) -> dict:
return self.__dict__ return self.__dict__
class DHTRecordDescriptor: class DHTRecordDescriptor:
key: TypedKey key: TypedKey
owner: PublicKey owner: PublicKey
owner_secret: Optional[SecretKey] owner_secret: Optional[SecretKey]
schema: DHTSchema schema: DHTSchema
def __init__(self, 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.key = key
self.owner = owner self.owner = owner
self.owner_secret = owner_secret self.owner_secret = owner_secret
self.schema = schema self.schema = schema
@staticmethod @classmethod
def from_json(j: dict) -> Self: def from_json(cls, j: dict) -> Self:
DHTRecordDescriptor( return cls(
TypedKey(j['key']), TypedKey(j["key"]),
PublicKey(j['owner']), PublicKey(j["owner"]),
None if j['owner_secret'] is None else SecretKey(j['owner_secret']), None if j["owner_secret"] is None else SecretKey(j["owner_secret"]),
DHTSchema.from_json(j['schema'])) DHTSchema.from_json(j["schema"]),
)
def to_json(self) -> dict: def to_json(self) -> dict:
return self.__dict__ return self.__dict__
class ValueData: class ValueData:
seq: ValueSeqNum seq: ValueSeqNum
data: bytes data: bytes
@ -289,13 +333,13 @@ class ValueData:
self.data = data self.data = data
self.writer = writer self.writer = writer
@staticmethod @classmethod
def from_json(j: dict) -> Self: def from_json(cls, j: dict) -> Self:
DHTRecordDescriptor( return cls(
ValueSeqNum(j['seq']), ValueSeqNum(j["seq"]),
urlsafe_b64decode_no_pad(j['data']), urlsafe_b64decode_no_pad(j["data"]),
PublicKey(j['writer'])) PublicKey(j["writer"]),
)
def to_json(self) -> dict: def to_json(self) -> dict:
return self.__dict__ return self.__dict__