Reticulum-Go/bindings/python/rns/link.py

147 lines
4.3 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2024-2026 Quad4.io
from __future__ import annotations
import ctypes
from . import ffi
from .errors import Error, map_code
from .node import Node
class Link:
def __init__(self, handle: int) -> None:
self._handle = int(handle)
@classmethod
def open(cls, node: Node, dest_hash: bytes | bytearray | memoryview) -> Link:
data = bytes(dest_hash)
if len(data) != ffi.HASH_LEN:
raise Error(Error.INVALID_ARG)
buf = (ctypes.c_uint8 * ffi.HASH_LEN).from_buffer_copy(data)
h = ffi.lib.rns_link_open(node.handle, buf)
if h == 0:
raise Error(Error.INTERNAL)
return cls(h)
def send(self, data: bytes | bytearray | memoryview) -> None:
payload = bytes(data)
if not payload:
raise Error(Error.INVALID_ARG)
buf = (ctypes.c_uint8 * len(payload)).from_buffer_copy(payload)
map_code(ffi.lib.rns_link_send(self._handle, buf, len(payload)))
def send_resource(self, data: bytes | bytearray | memoryview, name: str = "") -> None:
payload = bytes(data)
name_ptr = name.encode("utf-8") if name else None
data_ptr = (ctypes.c_uint8 * len(payload)).from_buffer_copy(payload) if payload else None
map_code(
ffi.lib.rns_link_send_resource(
self._handle,
data_ptr,
len(payload),
name_ptr,
)
)
def close(self) -> None:
if self._handle:
ffi.lib.rns_link_close(self._handle)
self._handle = 0
def id(self) -> bytes:
out = (ctypes.c_uint8 * ffi.HASH_LEN)()
written = ctypes.c_size_t(0)
map_code(ffi.lib.rns_link_id(self._handle, out, ffi.HASH_LEN, ctypes.byref(written)))
if written.value != ffi.HASH_LEN:
raise Error(Error.TRUNCATED)
return bytes(out)
def request(
self,
node: Node,
path: str,
data: bytes | bytearray | memoryview = b"",
timeout_ms: int = 0,
) -> bytes:
if not path:
raise Error(Error.INVALID_ARG)
payload = bytes(data)
data_ptr = (ctypes.c_uint8 * len(payload)).from_buffer_copy(payload) if payload else None
request_id = (ctypes.c_uint8 * ffi.HASH_LEN)()
written = ctypes.c_size_t(0)
map_code(
ffi.lib.rns_link_request(
node.handle,
self._handle,
path.encode("utf-8"),
data_ptr,
len(payload),
int(timeout_ms),
request_id,
ffi.HASH_LEN,
ctypes.byref(written),
)
)
if written.value != ffi.HASH_LEN:
raise Error(Error.TRUNCATED)
return bytes(request_id)
@property
def handle(self) -> int:
return self._handle
def __enter__(self) -> Link:
return self
def __exit__(self, *args: object) -> None:
self.close()
def __del__(self) -> None:
try:
self.close()
except Exception:
pass
def request_respond(node: Node, request_id: bytes | bytearray | memoryview, data: bytes | bytearray | memoryview) -> None:
req = bytes(request_id)
if not req:
raise Error(Error.INVALID_ARG)
payload = bytes(data)
req_ptr = (ctypes.c_uint8 * len(req)).from_buffer_copy(req)
data_ptr = (ctypes.c_uint8 * len(payload)).from_buffer_copy(payload) if payload else None
map_code(
ffi.lib.rns_request_respond(
node.handle,
req_ptr,
len(req),
data_ptr,
len(payload),
)
)
def request_respond_file(
node: Node,
request_id: bytes | bytearray | memoryview,
filename: str,
data: bytes | bytearray | memoryview,
) -> None:
req = bytes(request_id)
if not req or not filename:
raise Error(Error.INVALID_ARG)
payload = bytes(data)
req_ptr = (ctypes.c_uint8 * len(req)).from_buffer_copy(req)
data_ptr = (ctypes.c_uint8 * len(payload)).from_buffer_copy(payload) if payload else None
map_code(
ffi.lib.rns_request_respond_file(
node.handle,
req_ptr,
len(req),
filename.encode("utf-8"),
data_ptr,
len(payload),
)
)