aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDimitri Staessens <dimitri@ouroboros.rocks>2026-03-04 21:26:43 +0100
committerDimitri Staessens <dimitri@ouroboros.rocks>2026-03-07 15:27:04 +0100
commit7a4c37e8b673328dda59cec11ab9dce66c22a312 (patch)
treed2a05d2fa022d97ba79d8af2d32ec9e7cc3bd9e1
parent62924a033cb2a0130cc6a072e03590f8eec5ac72 (diff)
downloadpyouroboros-7a4c37e8b673328dda59cec11ab9dce66c22a312.tar.gz
pyouroboros-7a4c37e8b673328dda59cec11ab9dce66c22a312.zip
ouroboros: Add IRM wrapper
Add ouroboros.irm module wrapping the Ouroboros IRM C API, providing Python interfaces for IPCP lifecycle (create, destroy, bootstrap, enroll, connect), name management (create, destroy, register, list), and program/process binding. Split the monolithic CFFI build into separate _ouroboros_dev_cffi and _ouroboros_irm_cffi modules, each linking only its required library. Also includes: - ouroboros.cli module with higher-level wrappers mirroring CLI tools - FRCT flag support (set/get) in the Flow API - FlowPeer event type in FEventType - QoS defaults updated to match ouroboros source - Bug fixes: flow_set_snd_timeout typo, flow_set_flags calling convention, FlowSet name mangling, fqueue_type return type - .gitignore, copyright updates, version bump to 0.23.0
-rw-r--r--.gitignore7
-rw-r--r--README.md200
-rwxr-xr-xexamples/oecho.py2
-rwxr-xr-xexamples/oecho_set.py2
-rw-r--r--ffi/fccntl_wrap.h21
-rw-r--r--ffi/irm_wrap.h90
-rw-r--r--ffi/pyouroboros_build_dev.py (renamed from ffi/pyouroboros_build.py)52
-rw-r--r--ffi/pyouroboros_build_irm.py291
-rw-r--r--ouroboros/cli.py543
-rw-r--r--ouroboros/dev.py102
-rw-r--r--ouroboros/event.py9
-rw-r--r--ouroboros/irm.py662
-rw-r--r--ouroboros/qos.py78
-rwxr-xr-xsetup.py5
14 files changed, 1956 insertions, 108 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c363780
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+__pycache__/
+*.pyc
+*.pyo
+build/
+dist/
+*.egg-info/
+*.so
diff --git a/README.md b/README.md
index 36c3613..96e6151 100644
--- a/README.md
+++ b/README.md
@@ -153,6 +153,7 @@ class FEventType(IntFlag):
FlowUp
FlowAlloc
FlowDealloc
+ FlowPeer
```
and can be obtained by calling the next method:
@@ -186,9 +187,206 @@ if t == FEventType.FlowPkt:
line = f2.readline()
```
+## IRM API
+
+The IRM (IPC Resource Manager) module allows managing IPCPs, names,
+and bindings programmatically.
+
+```Python
+from ouroboros.irm import *
+```
+
+### IPCP Management
+
+Creating, bootstrapping, enrolling, and destroying IPCPs:
+
+```Python
+# Create a local IPCP
+pid = create_ipcp("my_ipcp", IpcpType.LOCAL)
+
+# Bootstrap it into a layer
+conf = IpcpConfig(ipcp_type=IpcpType.LOCAL, layer_name="my_layer")
+bootstrap_ipcp(pid, conf)
+
+# List all running IPCPs
+for ipcp in list_ipcps():
+ print(ipcp)
+
+# Enroll an IPCP
+enroll_ipcp(pid, "enrollment_dst")
+
+# Destroy an IPCP
+destroy_ipcp(pid)
+```
+
+IPCP types: `LOCAL`, `UNICAST`, `BROADCAST`, `ETH_LLC`, `ETH_DIX`,
+`UDP4`, `UDP6`.
+
+### IPCP Configuration
+
+The `IpcpConfig` class is used to bootstrap an IPCP. It takes
+the following parameters:
+
+```Python
+IpcpConfig(
+ ipcp_type, # IpcpType (required)
+ layer_name="", # Layer name (string)
+ dir_hash_algo=DirectoryHashAlgo.SHA3_256, # Hash algorithm
+ unicast=None, eth=None, udp4=None, udp6=None # Type-specific config
+)
+```
+
+The `dir_hash_algo` can be set to `SHA3_224`, `SHA3_256`, `SHA3_384`,
+or `SHA3_512`.
+
+#### Local and Broadcast IPCPs
+
+Local and Broadcast IPCPs need no type-specific configuration:
+
+```Python
+conf = IpcpConfig(ipcp_type=IpcpType.LOCAL, layer_name="local_layer")
+conf = IpcpConfig(ipcp_type=IpcpType.BROADCAST, layer_name="bc_layer")
+```
+
+#### Unicast IPCPs
+
+Unicast IPCPs have the most detailed configuration, structured as
+follows:
+
+```Python
+conf = IpcpConfig(
+ ipcp_type=IpcpType.UNICAST,
+ layer_name="my_layer",
+ unicast=UnicastConfig(
+ dt=DtConfig(
+ addr_size=4, # Address size in bytes (default: 4)
+ eid_size=8, # Endpoint ID size in bytes (default: 8)
+ max_ttl=60, # Maximum time-to-live (default: 60)
+ routing=RoutingConfig(
+ pol=RoutingPolicy.LINK_STATE,
+ ls=LinkStateConfig(
+ pol=LinkStatePolicy.SIMPLE, # SIMPLE, LFA, or ECMP
+ t_recalc=4, # Recalculation interval (s)
+ t_update=15, # Update interval (s)
+ t_timeo=60 # Timeout (s)
+ )
+ )
+ ),
+ dir=DirConfig(
+ pol=DirectoryPolicy.DHT,
+ dht=DhtConfig(
+ alpha=3, # Concurrency parameter
+ k=8, # Replication factor
+ t_expire=86400, # Entry expiry time (s)
+ t_refresh=900, # Refresh interval (s)
+ t_replicate=900 # Replication interval (s)
+ )
+ ),
+ addr_auth=AddressAuthPolicy.FLAT_RANDOM,
+ cong_avoid=CongestionAvoidPolicy.MB_ECN # or CA_NONE
+ )
+)
+```
+
+All sub-configs have sensible defaults, so for most cases a simpler
+form suffices:
+
+```Python
+conf = IpcpConfig(
+ ipcp_type=IpcpType.UNICAST,
+ layer_name="my_layer",
+ unicast=UnicastConfig()
+)
+```
+
+#### Ethernet IPCPs (LLC and DIX)
+
+```Python
+conf = IpcpConfig(
+ ipcp_type=IpcpType.ETH_LLC, # or IpcpType.ETH_DIX
+ layer_name="eth_layer",
+ eth=EthConfig(
+ dev="eth0", # Network device name
+ ethertype=0xA000 # Ethertype (mainly for DIX)
+ )
+)
+```
+
+#### UDP IPCPs
+
+For UDP over IPv4:
+
+```Python
+conf = IpcpConfig(
+ ipcp_type=IpcpType.UDP4,
+ layer_name="udp4_layer",
+ udp4=Udp4Config(
+ ip_addr="192.168.1.1", # Local IP address
+ dns_addr="192.168.1.254", # DNS server address
+ port=3435 # UDP port (default: 3435)
+ )
+)
+```
+
+For UDP over IPv6:
+
+```Python
+conf = IpcpConfig(
+ ipcp_type=IpcpType.UDP6,
+ layer_name="udp6_layer",
+ udp6=Udp6Config(
+ ip_addr="fd00::1", # Local IPv6 address
+ dns_addr="fd00::fe", # DNS server address
+ port=3435 # UDP port (default: 3435)
+ )
+)
+```
+
+### Connecting IPCP Components
+
+Connecting and disconnecting IPCP components:
+
+```Python
+connect_ipcp(pid, DT_COMP, "destination")
+disconnect_ipcp(pid, DT_COMP, "destination")
+```
+
+### Name Management
+
+Creating, destroying, and listing names:
+
+```Python
+# Create a name
+info = NameInfo(name="my_name", pol_lb=LoadBalancePolicy.ROUND_ROBIN)
+create_name(info)
+
+# Register/unregister an IPCP to a name
+reg_name("my_name", pid)
+unreg_name("my_name", pid)
+
+# List all registered names
+for name in list_names():
+ print(name.name)
+
+# Destroy a name
+destroy_name("my_name")
+```
+
+### Binding Programs and Processes
+
+```Python
+# Bind a program to a name (auto-start on flow allocation)
+bind_program("/usr/bin/my_server", "my_name", BIND_AUTO)
+unbind_program("/usr/bin/my_server", "my_name")
+
+# Bind a running process to a name
+bind_process(pid, "my_name")
+unbind_process(pid, "my_name")
+```
+
## Examples
Some example code is in the examples folder.
## License
-pyOuorboros is LGPLv2.1. The examples are 3-clause BSD.
+pyOuroboros is LGPLv2.1. The examples are 3-clause BSD.
diff --git a/examples/oecho.py b/examples/oecho.py
index 395930c..d3fccfe 100755
--- a/examples/oecho.py
+++ b/examples/oecho.py
@@ -1,6 +1,6 @@
#!/bin/python
-# Ouroboros - Copyright (C) 2016 - 2020
+# Ouroboros - Copyright (C) 2016 - 2026
#
# A simple echo application
#
diff --git a/examples/oecho_set.py b/examples/oecho_set.py
index 4d9d1f7..a389cc3 100755
--- a/examples/oecho_set.py
+++ b/examples/oecho_set.py
@@ -1,6 +1,6 @@
#!/bin/python
-# Ouroboros - Copyright (C) 2016 - 2020
+# Ouroboros - Copyright (C) 2016 - 2026
#
# A simple echo application
#
diff --git a/ffi/fccntl_wrap.h b/ffi/fccntl_wrap.h
index ab227ea..f9a137e 100644
--- a/ffi/fccntl_wrap.h
+++ b/ffi/fccntl_wrap.h
@@ -1,10 +1,10 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2020
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* An fccntl wrapper
*
- * Dimitri Staessens <dimitri.staessens@ugent.be>
- * Sander Vrijders <sander.vrijders@ugent.be>
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ * Sander Vrijders <sander@ouroboros.rocks>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
@@ -71,3 +71,18 @@ int flow_get_flags(int fd)
return (int) flags;
}
+
+int flow_set_frct_flags(int fd, uint16_t flags)
+{
+ return fccntl(fd, FRCTSFLAGS, flags);
+}
+
+int flow_get_frct_flags(int fd)
+{
+ uint16_t flags;
+
+ if (fccntl(fd, FRCTGFLAGS, &flags))
+ return -EPERM;
+
+ return (int) flags;
+}
diff --git a/ffi/irm_wrap.h b/ffi/irm_wrap.h
new file mode 100644
index 0000000..23b64a0
--- /dev/null
+++ b/ffi/irm_wrap.h
@@ -0,0 +1,90 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2026
+ *
+ * An IRM wrapper for Python bindings
+ *
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#include <ouroboros/irm.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <stdlib.h>
+
+static int ipcp_config_udp4_set_ip(struct ipcp_config * conf,
+ const char * ip_str)
+{
+ return inet_pton(AF_INET, ip_str, &conf->udp4.ip_addr) == 1 ? 0 : -1;
+}
+
+static int ipcp_config_udp4_set_dns(struct ipcp_config * conf,
+ const char * dns_str)
+{
+ return inet_pton(AF_INET, dns_str, &conf->udp4.dns_addr) == 1 ? 0 : -1;
+}
+
+static int ipcp_config_udp6_set_ip(struct ipcp_config * conf,
+ const char * ip_str)
+{
+ return inet_pton(AF_INET6, ip_str, &conf->udp6.ip_addr) == 1 ? 0 : -1;
+}
+
+static int ipcp_config_udp6_set_dns(struct ipcp_config * conf,
+ const char * dns_str)
+{
+ return inet_pton(AF_INET6, dns_str, &conf->udp6.dns_addr) == 1 ? 0 : -1;
+}
+
+static void ipcp_config_init_uni(struct ipcp_config * conf,
+ uint8_t addr_size, uint8_t eid_size,
+ uint8_t max_ttl,
+ enum pol_link_state ls_pol,
+ long t_recalc, long t_update, long t_timeo,
+ enum pol_dir dir_pol,
+ uint32_t dht_alpha, uint32_t dht_k,
+ uint32_t dht_t_expire, uint32_t dht_t_refresh,
+ uint32_t dht_t_replicate,
+ enum pol_addr_auth addr_auth,
+ enum pol_cong_avoid cong_avoid)
+{
+ memset(conf, 0, sizeof(*conf));
+ conf->type = IPCP_UNICAST;
+ conf->unicast.dt.addr_size = addr_size;
+ conf->unicast.dt.eid_size = eid_size;
+ conf->unicast.dt.max_ttl = max_ttl;
+ conf->unicast.dt.routing.pol = ROUTING_LINK_STATE;
+ conf->unicast.dt.routing.ls.pol = ls_pol;
+ conf->unicast.dt.routing.ls.t_recalc = t_recalc;
+ conf->unicast.dt.routing.ls.t_update = t_update;
+ conf->unicast.dt.routing.ls.t_timeo = t_timeo;
+ conf->unicast.dir.pol = dir_pol;
+ conf->unicast.dir.dht.params.alpha = dht_alpha;
+ conf->unicast.dir.dht.params.k = dht_k;
+ conf->unicast.dir.dht.params.t_expire = dht_t_expire;
+ conf->unicast.dir.dht.params.t_refresh = dht_t_refresh;
+ conf->unicast.dir.dht.params.t_replicate = dht_t_replicate;
+ conf->unicast.addr_auth_type = addr_auth;
+ conf->unicast.cong_avoid = cong_avoid;
+}
+
+static void ipcp_config_init_eth(struct ipcp_config * conf,
+ const char * dev,
+ uint16_t ethertype)
+{
+ memset(conf, 0, sizeof(*conf));
+ strncpy(conf->eth.dev, dev, DEV_NAME_SIZE);
+ conf->eth.ethertype = ethertype;
+}
diff --git a/ffi/pyouroboros_build.py b/ffi/pyouroboros_build_dev.py
index a7a6a0d..751b492 100644
--- a/ffi/pyouroboros_build.py
+++ b/ffi/pyouroboros_build_dev.py
@@ -1,7 +1,7 @@
#
-# Ouroboros - Copyright (C) 2016 - 2020
+# Ouroboros - Copyright (C) 2016 - 2026
#
-# Python API for applications
+# Python API for Ouroboros
#
# Dimitri Staessens <dimitri@ouroboros.rocks>
#
@@ -24,15 +24,24 @@ from cffi import FFI
ffibuilder: FFI = FFI()
ffibuilder.cdef("""
+/* System types */
+typedef long... time_t;
+
+struct timespec {
+ time_t tv_sec;
+ long tv_nsec;
+ ...;
+};
+
/* OUROBOROS QOS.H */
typedef struct qos_spec {
- uint32_t delay; /* In ms */
- uint64_t bandwidth; /* In bits/s */
- uint8_t availability; /* Class of 9s */
- uint32_t loss; /* Packet loss */
- uint32_t ber; /* Bit error rate, errors per billion bits */
- uint8_t in_order; /* In-order delivery, enables FRCT */
- uint32_t max_gap; /* In ms */
+ uint32_t delay;
+ uint64_t bandwidth;
+ uint8_t availability;
+ uint32_t loss;
+ uint32_t ber;
+ uint8_t in_order;
+ uint32_t max_gap;
uint32_t timeout; /* Timeout in ms */
} qosspec_t;
@@ -46,7 +55,7 @@ int flow_alloc(const char * dst_name,
int flow_accept(qosspec_t * qs,
const struct timespec * timeo);
-/* Returns flow descriptor, qs updates to supplied QoS. */
+/* Returns flow descriptor. */
int flow_join(const char * bc,
const struct timespec * timeo);
@@ -60,7 +69,7 @@ ssize_t flow_read(int fd,
void * buf,
size_t count);
-/*OUROBOROS FCCNTL.H, VIA WRAPPER */
+/* OUROBOROS FCCNTL.H, VIA WRAPPER */
int flow_set_snd_timeout(int fd, struct timespec * ts);
int flow_set_rcv_timeout(int fd, struct timespec * ts);
@@ -79,13 +88,18 @@ int flow_set_flags(int fd, uint32_t flags);
int flow_get_flags(int fd);
-/*OUROBOROS FQUEUE.H */
+int flow_set_frct_flags(int fd, uint16_t flags);
+
+int flow_get_frct_flags(int fd);
+
+/* OUROBOROS FQUEUE.H */
enum fqtype {
- FLOW_PKT = (1 << 0),
- FLOW_DOWN = (1 << 1),
- FLOW_UP = (1 << 2),
- FLOW_ALLOC = (1 << 3),
- FLOW_DEALLOC = (1 << 4)
+ FLOW_PKT = ...,
+ FLOW_DOWN = ...,
+ FLOW_UP = ...,
+ FLOW_ALLOC = ...,
+ FLOW_DEALLOC = ...,
+ FLOW_PEER = ...
};
struct flow_set;
@@ -116,14 +130,14 @@ void fset_del(fset_t * set,
int fqueue_next(fqueue_t * fq);
-int fqueue_type(fqueue_t * fq);
+enum fqtype fqueue_type(fqueue_t * fq);
ssize_t fevent(fset_t * set,
fqueue_t * fq,
const struct timespec * timeo);
""")
-ffibuilder.set_source("_ouroboros_cffi",
+ffibuilder.set_source("_ouroboros_dev_cffi",
"""
#include "ouroboros/qos.h"
#include "ouroboros/dev.h"
diff --git a/ffi/pyouroboros_build_irm.py b/ffi/pyouroboros_build_irm.py
new file mode 100644
index 0000000..e29287e
--- /dev/null
+++ b/ffi/pyouroboros_build_irm.py
@@ -0,0 +1,291 @@
+#
+# Ouroboros - Copyright (C) 2016 - 2026
+#
+# Python API for Ouroboros
+#
+# Dimitri Staessens <dimitri@ouroboros.rocks>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public License
+# version 2.1 as published by the Free Software Foundation.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., http://www.fsf.org/about/contact/.
+#
+
+from cffi import FFI
+
+ffibuilder: FFI = FFI()
+
+ffibuilder.cdef("""
+/* System types */
+typedef int... pid_t;
+typedef long... time_t;
+
+struct timespec {
+ time_t tv_sec;
+ long tv_nsec;
+ ...;
+};
+
+/* Network types */
+struct in_addr { ...; };
+struct in6_addr { ...; };
+
+/* OUROBOROS QOS.H */
+typedef struct qos_spec {
+ uint32_t delay;
+ uint64_t bandwidth;
+ uint8_t availability;
+ uint32_t loss;
+ uint32_t ber;
+ uint8_t in_order;
+ uint32_t max_gap;
+ uint32_t timeout; /* Timeout in ms */
+} qosspec_t;
+
+/* OUROBOROS IPCP.H */
+enum ipcp_type {
+ IPCP_LOCAL = ...,
+ IPCP_UNICAST = ...,
+ IPCP_BROADCAST = ...,
+ IPCP_ETH_LLC = ...,
+ IPCP_ETH_DIX = ...,
+ IPCP_UDP4 = ...,
+ IPCP_UDP6 = ...,
+ IPCP_INVALID = ...
+};
+
+/* Unicast IPCP policies */
+enum pol_addr_auth {
+ ADDR_AUTH_FLAT_RANDOM = ...,
+ ADDR_AUTH_INVALID = ...
+};
+
+enum pol_link_state {
+ LS_SIMPLE = ...,
+ LS_LFA = ...,
+ LS_ECMP = ...,
+ LS_INVALID = ...
+};
+
+struct ls_config {
+ enum pol_link_state pol;
+ time_t t_recalc;
+ time_t t_update;
+ time_t t_timeo;
+};
+
+enum pol_routing {
+ ROUTING_LINK_STATE = ...,
+ ROUTING_INVALID = ...
+};
+
+struct routing_config {
+ enum pol_routing pol;
+ union {
+ struct ls_config ls;
+ };
+};
+
+enum pol_cong_avoid {
+ CA_NONE = ...,
+ CA_MB_ECN = ...,
+ CA_INVALID = ...
+};
+
+struct dt_config {
+ struct {
+ uint8_t addr_size;
+ uint8_t eid_size;
+ uint8_t max_ttl;
+ };
+ struct routing_config routing;
+};
+
+enum pol_dir {
+ DIR_DHT = ...,
+ DIR_INVALID = ...
+};
+
+enum pol_dir_hash {
+ DIR_HASH_SHA3_224 = ...,
+ DIR_HASH_SHA3_256 = ...,
+ DIR_HASH_SHA3_384 = ...,
+ DIR_HASH_SHA3_512 = ...,
+ DIR_HASH_INVALID = ...
+};
+
+struct dir_dht_config {
+ struct {
+ uint32_t alpha;
+ uint32_t k;
+ uint32_t t_expire;
+ uint32_t t_refresh;
+ uint32_t t_replicate;
+ } params;
+ uint64_t peer;
+};
+
+struct dir_config {
+ enum pol_dir pol;
+ union {
+ struct dir_dht_config dht;
+ };
+};
+
+struct uni_config {
+ struct dt_config dt;
+ struct dir_config dir;
+ enum pol_addr_auth addr_auth_type;
+ enum pol_cong_avoid cong_avoid;
+};
+
+struct eth_config {
+ char dev[256]; /* DEV_NAME_SIZE + 1 */
+ uint16_t ethertype;
+};
+
+struct udp4_config {
+ struct in_addr ip_addr;
+ struct in_addr dns_addr;
+ uint16_t port;
+};
+
+struct udp6_config {
+ struct in6_addr ip_addr;
+ struct in6_addr dns_addr;
+ uint16_t port;
+};
+
+struct layer_info {
+ char name[256]; /* LAYER_NAME_SIZE + 1 */
+ enum pol_dir_hash dir_hash_algo;
+};
+
+struct ipcp_config {
+ struct layer_info layer_info;
+ enum ipcp_type type;
+
+ union {
+ struct uni_config unicast;
+ struct udp4_config udp4;
+ struct udp6_config udp6;
+ struct eth_config eth;
+ };
+};
+
+/* OUROBOROS NAME.H */
+#define BIND_AUTO ...
+
+enum pol_balance {
+ LB_RR = ...,
+ LB_SPILL = ...,
+ LB_INVALID = ...
+};
+
+struct name_sec_paths {
+ char enc[512]; /* NAME_PATH_SIZE + 1 */
+ char key[512]; /* NAME_PATH_SIZE + 1 */
+ char crt[512]; /* NAME_PATH_SIZE + 1 */
+};
+
+struct name_info {
+ char name[256]; /* NAME_SIZE + 1 */
+ enum pol_balance pol_lb;
+
+ struct name_sec_paths s;
+ struct name_sec_paths c;
+};
+
+/* OUROBOROS IRM.H */
+
+struct ipcp_list_info {
+ pid_t pid;
+ enum ipcp_type type;
+ char name[255]; /* NAME_SIZE */
+ char layer[255]; /* LAYER_NAME_SIZE */
+};
+
+pid_t irm_create_ipcp(const char * name,
+ enum ipcp_type type);
+
+int irm_destroy_ipcp(pid_t pid);
+
+ssize_t irm_list_ipcps(struct ipcp_list_info ** ipcps);
+
+int irm_enroll_ipcp(pid_t pid,
+ const char * dst);
+
+int irm_bootstrap_ipcp(pid_t pid,
+ const struct ipcp_config * conf);
+
+int irm_connect_ipcp(pid_t pid,
+ const char * component,
+ const char * dst,
+ qosspec_t qs);
+
+int irm_disconnect_ipcp(pid_t pid,
+ const char * component,
+ const char * dst);
+
+int irm_bind_program(const char * prog,
+ const char * name,
+ uint16_t opts,
+ int argc,
+ char ** argv);
+
+int irm_unbind_program(const char * progr,
+ const char * name);
+
+int irm_bind_process(pid_t pid,
+ const char * name);
+
+int irm_unbind_process(pid_t pid,
+ const char * name);
+
+int irm_create_name(struct name_info * info);
+
+int irm_destroy_name(const char * name);
+
+ssize_t irm_list_names(struct name_info ** names);
+
+int irm_reg_name(const char * name,
+ pid_t pid);
+
+int irm_unreg_name(const char * name,
+ pid_t pid);
+
+/* IRM WRAPPER HELPERS */
+int ipcp_config_udp4_set_ip(struct ipcp_config * conf,
+ const char * ip_str);
+
+int ipcp_config_udp4_set_dns(struct ipcp_config * conf,
+ const char * dns_str);
+
+int ipcp_config_udp6_set_ip(struct ipcp_config * conf,
+ const char * ip_str);
+
+int ipcp_config_udp6_set_dns(struct ipcp_config * conf,
+ const char * dns_str);
+
+/* libc */
+void free(void *ptr);
+""")
+
+ffibuilder.set_source("_ouroboros_irm_cffi",
+ """
+#include "ouroboros/qos.h"
+#include "irm_wrap.h"
+ """,
+ libraries=['ouroboros-irm'],
+ extra_compile_args=["-I./ffi/"])
+
+if __name__ == "__main__":
+ ffibuilder.compile(verbose=True)
diff --git a/ouroboros/cli.py b/ouroboros/cli.py
new file mode 100644
index 0000000..7f07e56
--- /dev/null
+++ b/ouroboros/cli.py
@@ -0,0 +1,543 @@
+#
+# Ouroboros - Copyright (C) 2016 - 2026
+#
+# Python API for Ouroboros - CLI equivalents
+#
+# Dimitri Staessens <dimitri@ouroboros.rocks>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public License
+# version 2.1 as published by the Free Software Foundation.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., http://www.fsf.org/about/contact/.
+#
+
+"""
+Higher-level wrappers that mirror CLI tool behaviour.
+
+The ``irm`` CLI tools perform extra steps that the raw C library API
+does not, such as resolving program names via ``realpath``, looking up
+IPCP pids, and the ``autobind`` flag for bootstrapping/enrolling.
+This module exposes those same patterns as a Python API so that
+callers do not need to re-implement them.
+
+Each wrapper corresponds to a specific ``irm`` sub-command:
+
+========================= ====================================
+Python CLI equivalent
+========================= ====================================
+``create_ipcp`` ``irm ipcp create``
+``destroy_ipcp`` ``irm ipcp destroy``
+``bootstrap_ipcp`` ``irm ipcp bootstrap [autobind]``
+``enroll_ipcp`` ``irm ipcp enroll [autobind]``
+``connect_ipcp`` ``irm ipcp connect``
+``disconnect_ipcp`` ``irm ipcp disconnect``
+``list_ipcps`` ``irm ipcp list``
+``bind_program`` ``irm bind program``
+``bind_process`` ``irm bind process``
+``bind_ipcp`` ``irm bind ipcp``
+``unbind_program`` ``irm unbind program``
+``unbind_process`` ``irm unbind process``
+``unbind_ipcp`` ``irm unbind ipcp``
+``create_name`` ``irm name create``
+``destroy_name`` ``irm name destroy``
+``reg_name`` ``irm name register``
+``unreg_name`` ``irm name unregister``
+``list_names`` ``irm name list``
+``autoboot`` ``irm ipcp bootstrap autobind``
+ (with implicit ``create``)
+========================= ====================================
+
+Usage::
+
+ from ouroboros.cli import create_ipcp, bootstrap_ipcp, enroll_ipcp
+ from ouroboros.cli import bind_program, autoboot
+"""
+
+import shutil
+from typing import List, Optional
+
+from ouroboros.irm import (
+ DT_COMP,
+ MGMT_COMP,
+ IpcpType,
+ IpcpConfig,
+ IpcpInfo,
+ NameInfo,
+ BindError,
+ IrmError,
+ UnicastConfig,
+ DtConfig,
+ RoutingConfig,
+ LinkStateConfig,
+ LinkStatePolicy,
+ EthConfig,
+ Udp4Config,
+ Udp6Config,
+ bind_program as _irm_bind_program,
+ bind_process as _irm_bind_process,
+ bootstrap_ipcp as _irm_bootstrap_ipcp,
+ connect_ipcp as _irm_connect_ipcp,
+ create_ipcp,
+ create_name as _irm_create_name,
+ destroy_ipcp as _irm_destroy_ipcp,
+ destroy_name,
+ disconnect_ipcp as _irm_disconnect_ipcp,
+ enroll_ipcp as _irm_enroll_ipcp,
+ list_ipcps as _irm_list_ipcps,
+ list_names as _irm_list_names,
+ reg_name as _irm_reg_name,
+ unbind_process as _irm_unbind_process,
+ unbind_program as _irm_unbind_program,
+ unreg_name as _irm_unreg_name,
+)
+from ouroboros.qos import QoSSpec
+
+
+def _pid_of(ipcp_name: str) -> int:
+ """Look up the pid of a running IPCP by its name."""
+ for info in _irm_list_ipcps():
+ if info.name == ipcp_name:
+ return info.pid
+ raise ValueError(f"No IPCP named {ipcp_name!r}")
+
+
+def destroy_ipcp(name: str) -> None:
+ """
+ Destroy an IPCP by name.
+
+ Mirrors ``irm ipcp destroy name <name>``.
+
+ Resolves the IPCP name to a pid, then destroys the IPCP.
+
+ :param name: Name of the IPCP to destroy.
+ :raises ValueError: If no IPCP with *name* exists.
+ """
+ _irm_destroy_ipcp(_pid_of(name))
+
+
+def list_ipcps(name: Optional[str] = None,
+ layer: Optional[str] = None,
+ ipcp_type: Optional[IpcpType] = None) -> List[IpcpInfo]:
+ """
+ List running IPCPs, optionally filtered.
+
+ Mirrors ``irm ipcp list [name <n>] [type <t>] [layer <l>]``.
+
+ :param name: Filter by IPCP name (exact match).
+ :param layer: Filter by layer name (exact match).
+ :param ipcp_type: Filter by IPCP type.
+ :return: List of matching :class:`IpcpInfo` objects.
+ """
+ result = _irm_list_ipcps()
+ if name is not None:
+ result = [i for i in result if i.name == name]
+ if layer is not None:
+ result = [i for i in result if i.layer == layer]
+ if ipcp_type is not None:
+ result = [i for i in result if i.type == ipcp_type]
+ return result
+
+
+def reg_name(name: str,
+ ipcp: Optional[str] = None,
+ ipcps: Optional[List[str]] = None,
+ layer: Optional[str] = None,
+ layers: Optional[List[str]] = None) -> None:
+ """
+ Register a name with IPCP(s), creating it first if needed.
+
+ Mirrors ``irm name register <name> ipcp <ipcp> [ipcp ...]
+ layer <layer> [layer ...]``.
+
+ The C CLI tool resolves IPCP names and layer names to pids,
+ checks whether the name already exists and calls
+ ``irm_create_name`` before ``irm_reg_name`` for each IPCP.
+
+ The function accepts flexible input:
+
+ - A single *ipcp* name or list of *ipcps* names.
+ - A single *layer* name or list of *layers* names (registers
+ with every IPCP in each of those layers).
+ - Any combination of the above.
+
+ :param name: The name to register.
+ :param ipcp: Single IPCP name to register with.
+ :param ipcps: List of IPCP names to register with.
+ :param layer: Single layer name to register with.
+ :param layers: List of layer names to register with.
+ """
+ existing = {n.name for n in _irm_list_names()}
+ if name not in existing:
+ _irm_create_name(NameInfo(name=name))
+
+ pids = set()
+
+ # Collect IPCP names into a single list
+ ipcp_names = []
+ if ipcp is not None:
+ ipcp_names.append(ipcp)
+ if ipcps is not None:
+ ipcp_names.extend(ipcps)
+
+ # Collect layer names into a single list
+ layer_names = []
+ if layer is not None:
+ layer_names.append(layer)
+ if layers is not None:
+ layer_names.extend(layers)
+
+ if ipcp_names or layer_names:
+ all_ipcps = _irm_list_ipcps()
+ for ipcp_name in ipcp_names:
+ for i in all_ipcps:
+ if i.name == ipcp_name:
+ pids.add(i.pid)
+ break
+ for lyr in layer_names:
+ for i in all_ipcps:
+ if i.layer == lyr:
+ pids.add(i.pid)
+
+ for p in pids:
+ _irm_reg_name(name, p)
+
+
+def bind_program(prog: str,
+ name: str,
+ opts: int = 0,
+ argv: Optional[List[str]] = None) -> None:
+ """
+ Bind a program to a name, resolving bare names to full paths.
+
+ Mirrors ``irm bind program <prog> name <name>``.
+
+ The ``irm bind program`` CLI tool calls ``realpath()`` on *prog*
+ before passing it to the library. The raw C function
+ ``irm_bind_program`` contains a ``check_prog_path`` helper that
+ corrupts the ``PATH`` environment variable (writes NUL over ``:``
+ separators) when given a bare program name. Only the first such
+ call would succeed in a long-running process.
+
+ This wrapper resolves *prog* via ``shutil.which()`` before calling
+ the library, avoiding the bug entirely.
+
+ :param prog: Program name or path. Bare names (without ``/``)
+ are resolved on ``PATH`` via ``shutil.which()``.
+ :param name: Name to bind to.
+ :param opts: Bind options (e.g. ``BIND_AUTO``).
+ :param argv: Arguments to pass when the program is auto-started.
+ :raises BindError: If the program cannot be found or the bind
+ call fails.
+ """
+ if '/' not in prog:
+ resolved = shutil.which(prog)
+ if resolved is None:
+ raise BindError(f"Program {prog!r} not found on PATH")
+ prog = resolved
+ _irm_bind_program(prog, name, opts=opts, argv=argv)
+
+
+def unbind_program(prog: str, name: str) -> None:
+ """
+ Unbind a program from a name.
+
+ Mirrors ``irm unbind program <prog> name <name>``.
+
+ :param prog: Path to the program.
+ :param name: Name to unbind from.
+ """
+ _irm_unbind_program(prog, name)
+
+
+def bind_process(pid: int, name: str) -> None:
+ """
+ Bind a running process to a name.
+
+ Mirrors ``irm bind process <pid> name <name>``.
+
+ :param pid: PID of the process.
+ :param name: Name to bind to.
+ """
+ _irm_bind_process(pid, name)
+
+
+def unbind_process(pid: int, name: str) -> None:
+ """
+ Unbind a process from a name.
+
+ Mirrors ``irm unbind process <pid> name <name>``.
+
+ :param pid: PID of the process.
+ :param name: Name to unbind from.
+ """
+ _irm_unbind_process(pid, name)
+
+
+def bind_ipcp(ipcp: str, name: str) -> None:
+ """
+ Bind an IPCP to a name.
+
+ Mirrors ``irm bind ipcp <ipcp> name <name>``.
+
+ Resolves the IPCP name to a pid, then calls ``bind_process``.
+
+ :param ipcp: IPCP instance name.
+ :param name: Name to bind to.
+ :raises ValueError: If no IPCP with *ipcp* exists.
+ """
+ _irm_bind_process(_pid_of(ipcp), name)
+
+
+def unbind_ipcp(ipcp: str, name: str) -> None:
+ """
+ Unbind an IPCP from a name.
+
+ Mirrors ``irm unbind ipcp <ipcp> name <name>``.
+
+ Resolves the IPCP name to a pid, then calls ``unbind_process``.
+
+ :param ipcp: IPCP instance name.
+ :param name: Name to unbind from.
+ :raises ValueError: If no IPCP with *ipcp* exists.
+ """
+ _irm_unbind_process(_pid_of(ipcp), name)
+
+
+def create_name(name: str,
+ pol_lb: Optional[int] = None,
+ info: Optional[NameInfo] = None) -> None:
+ """
+ Create a registered name.
+
+ Mirrors ``irm name create <name> [lb <policy>]``.
+
+ :param name: The name to create.
+ :param pol_lb: Load-balance policy (optional).
+ :param info: Full :class:`NameInfo` (overrides *name*/*pol_lb*
+ if given).
+ """
+ if info is not None:
+ _irm_create_name(info)
+ else:
+ ni = NameInfo(name=name)
+ if pol_lb is not None:
+ ni.pol_lb = pol_lb
+ _irm_create_name(ni)
+
+
+def list_names(name: Optional[str] = None) -> List[NameInfo]:
+ """
+ List all registered names, optionally filtered.
+
+ Mirrors ``irm name list [<name>]``.
+
+ :param name: Filter by name (exact match).
+ :return: List of :class:`NameInfo` objects.
+ """
+ result = _irm_list_names()
+ if name is not None:
+ result = [n for n in result if n.name == name]
+ return result
+
+
+def unreg_name(name: str,
+ ipcp: Optional[str] = None,
+ ipcps: Optional[List[str]] = None,
+ layer: Optional[str] = None,
+ layers: Optional[List[str]] = None) -> None:
+ """
+ Unregister a name from IPCP(s).
+
+ Mirrors ``irm name unregister <name> ipcp <ipcp> [ipcp ...]
+ layer <layer> [layer ...]``.
+
+ Accepts the same flexible input as :func:`reg_name`.
+
+ :param name: The name to unregister.
+ :param ipcp: Single IPCP name to unregister from.
+ :param ipcps: List of IPCP names to unregister from.
+ :param layer: Single layer name to unregister from.
+ :param layers: List of layer names to unregister from.
+ """
+ pids = set()
+
+ ipcp_names = []
+ if ipcp is not None:
+ ipcp_names.append(ipcp)
+ if ipcps is not None:
+ ipcp_names.extend(ipcps)
+
+ layer_names = []
+ if layer is not None:
+ layer_names.append(layer)
+ if layers is not None:
+ layer_names.extend(layers)
+
+ if ipcp_names or layer_names:
+ all_ipcps = _irm_list_ipcps()
+ for ipcp_name in ipcp_names:
+ for i in all_ipcps:
+ if i.name == ipcp_name:
+ pids.add(i.pid)
+ break
+ for lyr in layer_names:
+ for i in all_ipcps:
+ if i.layer == lyr:
+ pids.add(i.pid)
+
+ for p in pids:
+ _irm_unreg_name(name, p)
+
+
+def bootstrap_ipcp(name: str,
+ conf: IpcpConfig,
+ autobind: bool = False) -> None:
+ """
+ Bootstrap an IPCP, optionally binding it to its name and layer.
+
+ Mirrors ``irm ipcp bootstrap name <n> layer <l> [autobind]``.
+
+ When *autobind* is ``True`` and the IPCP type is ``UNICAST`` or
+ ``BROADCAST``, the sequence is::
+
+ bind_process(pid, ipcp_name) # accept flows for ipcp name
+ bind_process(pid, layer_name) # accept flows for layer name
+ bootstrap_ipcp(pid, conf) # bootstrap into the layer
+
+ This matches the C ``irm ipcp bootstrap`` tool exactly. If
+ bootstrap fails after autobind, the bindings are rolled back.
+
+ :param name: Name of the IPCP.
+ :param conf: IPCP configuration (includes layer name & type).
+ :param autobind: Bind the IPCP process to its name and layer.
+ """
+ pid = _pid_of(name)
+ layer_name = conf.layer_name
+
+ if autobind and conf.ipcp_type in (IpcpType.UNICAST,
+ IpcpType.BROADCAST):
+ _irm_bind_process(pid, name)
+ _irm_bind_process(pid, layer_name)
+
+ try:
+ _irm_bootstrap_ipcp(pid, conf)
+ except Exception:
+ if autobind and conf.ipcp_type in (IpcpType.UNICAST,
+ IpcpType.BROADCAST):
+ _irm_unbind_process(pid, name)
+ _irm_unbind_process(pid, layer_name)
+ raise
+
+
+def enroll_ipcp(name: str, dst: str,
+ autobind: bool = False) -> None:
+ """
+ Enroll an IPCP, optionally binding it to its name and layer.
+
+ Mirrors ``irm ipcp enroll name <n> layer <dst> [autobind]``.
+
+ When *autobind* is ``True``, the sequence is::
+
+ enroll_ipcp(pid, dst)
+ bind_process(pid, ipcp_name)
+ bind_process(pid, layer_name) # layer learned from enrollment
+
+ This matches the C ``irm ipcp enroll`` tool exactly.
+
+ :param name: Name of the IPCP.
+ :param dst: Destination name or layer to enroll with.
+ :param autobind: Bind the IPCP process to its name and layer
+ after successful enrollment.
+ """
+ pid = _pid_of(name)
+ _irm_enroll_ipcp(pid, dst)
+
+ if autobind:
+ # Look up enrolled layer from the IPCP list
+ for info in _irm_list_ipcps():
+ if info.pid == pid:
+ _irm_bind_process(pid, info.name)
+ _irm_bind_process(pid, info.layer)
+ break
+
+
+def connect_ipcp(name: str, dst: str, comp: str = "*",
+ qos: Optional[QoSSpec] = None) -> None:
+ """
+ Connect IPCP components to a destination.
+
+ Mirrors ``irm ipcp connect name <n> dst <dst> [component <c>]
+ [qos <qos>]``.
+
+ When *comp* is ``"*"`` (default), both ``dt`` and ``mgmt``
+ components are connected, matching the CLI default.
+
+ :param name: Name of the IPCP.
+ :param dst: Destination IPCP name.
+ :param comp: Component to connect: ``"dt"``, ``"mgmt"``, or
+ ``"*"`` for both (default).
+ :param qos: QoS specification for the dt component.
+ """
+ pid = _pid_of(name)
+ if comp in ("*", "mgmt"):
+ _irm_connect_ipcp(pid, MGMT_COMP, dst)
+ if comp in ("*", "dt"):
+ _irm_connect_ipcp(pid, DT_COMP, dst, qos=qos)
+
+
+def disconnect_ipcp(name: str, dst: str, comp: str = "*") -> None:
+ """
+ Disconnect IPCP components from a destination.
+
+ Mirrors ``irm ipcp disconnect name <n> dst <dst>
+ [component <c>]``.
+
+ When *comp* is ``"*"`` (default), both ``dt`` and ``mgmt``
+ components are disconnected, matching the CLI default.
+
+ :param name: Name of the IPCP.
+ :param dst: Destination IPCP name.
+ :param comp: Component to disconnect: ``"dt"``, ``"mgmt"``, or
+ ``"*"`` for both (default).
+ """
+ pid = _pid_of(name)
+ if comp in ("*", "mgmt"):
+ _irm_disconnect_ipcp(pid, MGMT_COMP, dst)
+ if comp in ("*", "dt"):
+ _irm_disconnect_ipcp(pid, DT_COMP, dst)
+
+
+def autoboot(name: str,
+ ipcp_type: IpcpType,
+ layer: str,
+ conf: Optional[IpcpConfig] = None) -> None:
+ """
+ Create, autobind and bootstrap an IPCP in one step.
+
+ Convenience wrapper equivalent to::
+
+ irm ipcp bootstrap name <name> type <type> layer <layer> autobind
+
+ (with an implicit ``create`` if the IPCP does not yet exist).
+
+ :param name: Name for the IPCP.
+ :param ipcp_type: Type of IPCP to create.
+ :param layer: Layer name to bootstrap into.
+ :param conf: Optional IPCP configuration. If *None*, a
+ default ``IpcpConfig`` is created for the given
+ *ipcp_type* and *layer*.
+ """
+ create_ipcp(name, ipcp_type)
+ if conf is None:
+ conf = IpcpConfig(ipcp_type=ipcp_type, layer_name=layer)
+ else:
+ conf.layer_name = layer
+ bootstrap_ipcp(name, conf, autobind=True)
diff --git a/ouroboros/dev.py b/ouroboros/dev.py
index bc5d133..a2b58cf 100644
--- a/ouroboros/dev.py
+++ b/ouroboros/dev.py
@@ -1,7 +1,7 @@
#
-# Ouroboros - Copyright (C) 2016 - 2020
+# Ouroboros - Copyright (C) 2016 - 2026
#
-# Python API for applications
+# Python API for Ouroboros
#
# Dimitri Staessens <dimitri@ouroboros.rocks>
#
@@ -21,14 +21,72 @@
import errno
from enum import IntFlag
+from math import modf
+from typing import Optional
-from _ouroboros_cffi import ffi, lib
+from _ouroboros_dev_cffi import ffi, lib
from ouroboros.qos import *
-from ouroboros.qos import _qos_to_qosspec, _fl_to_timespec, _qosspec_to_qos, _timespec_to_fl
# Some constants
-MILLION = 1000_1000
-BILLION = 1000_1000_1000
+MILLION = 1000 * 1000
+BILLION = 1000 * 1000 * 1000
+
+
+def _fl_to_timespec(timeo: float):
+ if timeo is None:
+ return ffi.NULL
+ elif timeo <= 0:
+ return ffi.new("struct timespec *", [0, 0])
+ else:
+ frac, whole = modf(timeo)
+ _timeo = ffi.new("struct timespec *")
+ _timeo.tv_sec = int(whole)
+ _timeo.tv_nsec = int(frac * BILLION)
+ return _timeo
+
+
+def _timespec_to_fl(_timeo) -> Optional[float]:
+ if _timeo is ffi.NULL:
+ return None
+ elif _timeo.tv_sec <= 0 and _timeo.tv_nsec == 0:
+ return 0
+ else:
+ return _timeo.tv_sec + _timeo.tv_nsec / BILLION
+
+
+# Intentionally duplicated, dev uses a separate FFI (ouroboros-dev).
+def _qos_to_qosspec(qos: QoSSpec):
+ if qos is None:
+ return ffi.NULL
+ else:
+ return ffi.new("qosspec_t *",
+ [qos.delay,
+ qos.bandwidth,
+ qos.availability,
+ qos.loss,
+ qos.ber,
+ qos.in_order,
+ qos.max_gap,
+ qos.timeout])
+
+
+def _qosspec_to_qos(_qos) -> Optional[QoSSpec]:
+ if _qos is ffi.NULL:
+ return None
+ else:
+ return QoSSpec(delay=_qos.delay,
+ bandwidth=_qos.bandwidth,
+ availability=_qos.availability,
+ loss=_qos.loss,
+ ber=_qos.ber,
+ in_order=_qos.in_order,
+ max_gap=_qos.max_gap,
+ timeout=_qos.timeout)
+
+# FRCT flags
+FRCT_RETRANSMIT = 0o1
+FRCT_RESCNTL = 0o2
+FRCT_LINGER = 0o4
# ouroboros exceptions
@@ -248,7 +306,7 @@ class Flow:
"""
_timeo = _fl_to_timespec(timeo)
- if lib.flow_set_snd_timout(self.__fd, _timeo) != 0:
+ if lib.flow_set_snd_timeout(self.__fd, _timeo) != 0:
raise FlowPermissionException()
def get_snd_timeout(self) -> float:
@@ -271,7 +329,7 @@ class Flow:
"""
_timeo = _fl_to_timespec(timeo)
- if lib.flow_set_rcv_timout(self.__fd, _timeo) != 0:
+ if lib.flow_set_rcv_timeout(self.__fd, _timeo) != 0:
raise FlowPermissionException()
def get_rcv_timeout(self) -> float:
@@ -331,9 +389,7 @@ class Flow:
:param flags:
"""
- _flags = ffi.new("uint32_t *", int(flags))
-
- if lib.flow_set_flag(self.__fd, _flags):
+ if lib.flow_set_flags(self.__fd, int(flags)):
raise FlowPermissionException()
def get_flags(self) -> FlowProperties:
@@ -341,12 +397,34 @@ class Flow:
Get the flags for this flow
"""
- flags = lib.flow_get_flag(self.__fd)
+ flags = lib.flow_get_flags(self.__fd)
if flags < 0:
raise FlowPermissionException()
return FlowProperties(int(flags))
+ def set_frct_flags(self, flags: int):
+ """
+ Set FRCT flags for this flow.
+ :param flags: Bitmask of FRCT_RETRANSMIT, FRCT_RESCNTL, FRCT_LINGER
+ """
+
+ if lib.flow_set_frct_flags(self.__fd, flags):
+ raise FlowPermissionException()
+
+ def get_frct_flags(self) -> int:
+ """
+ Get the FRCT flags for this flow
+
+ :return: Bitmask of FRCT flags
+ """
+
+ flags = lib.flow_get_frct_flags(self.__fd)
+ if flags < 0:
+ raise FlowPermissionException()
+
+ return int(flags)
+
def flow_alloc(dst: str,
qos: QoSSpec = None,
diff --git a/ouroboros/event.py b/ouroboros/event.py
index b707c1b..ee0127e 100644
--- a/ouroboros/event.py
+++ b/ouroboros/event.py
@@ -1,7 +1,7 @@
#
-# Ouroboros - Copyright (C) 2016 - 2020
+# Ouroboros - Copyright (C) 2016 - 2026
#
-# Python API for applications
+# Python API for Ouroboros
#
# Dimitri Staessens <dimitri@ouroboros.rocks>
#
@@ -20,7 +20,7 @@
#
from ouroboros.dev import *
-from ouroboros.qos import _fl_to_timespec
+from ouroboros.dev import _fl_to_timespec
# async API
@@ -34,6 +34,7 @@ class FEventType(IntFlag):
FlowUp = lib.FLOW_UP
FlowAlloc = lib.FLOW_ALLOC
FlowDealloc = lib.FLOW_DEALLOC
+ FlowPeer = lib.FLOW_PEER
class FEventQueue:
@@ -101,7 +102,7 @@ class FlowSet:
if self.__set is ffi.NULL:
raise ValueError
- if lib.fset_add(self.__set, flow._Flow___fd) != 0:
+ if lib.fset_add(self.__set, flow._Flow__fd) != 0:
raise MemoryError("Failed to add flow")
def zero(self):
diff --git a/ouroboros/irm.py b/ouroboros/irm.py
new file mode 100644
index 0000000..1e4fc2e
--- /dev/null
+++ b/ouroboros/irm.py
@@ -0,0 +1,662 @@
+#
+# Ouroboros - Copyright (C) 2016 - 2026
+#
+# Python API for Ouroboros - IRM
+#
+# Dimitri Staessens <dimitri@ouroboros.rocks>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public License
+# version 2.1 as published by the Free Software Foundation.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., http://www.fsf.org/about/contact/.
+#
+
+from enum import IntEnum
+from typing import List, Optional
+
+from _ouroboros_irm_cffi import ffi, lib
+from ouroboros.qos import QoSSpec
+
+
+# Intentionally duplicated: irm uses a separate FFI (ouroboros-irm).
+def _qos_to_qosspec(qos: QoSSpec):
+ if qos is None:
+ return ffi.NULL
+ else:
+ return ffi.new("qosspec_t *",
+ [qos.delay,
+ qos.bandwidth,
+ qos.availability,
+ qos.loss,
+ qos.ber,
+ qos.in_order,
+ qos.max_gap,
+ qos.timeout])
+
+# --- Enumerations ---
+
+class IpcpType(IntEnum):
+ """IPCP types available in Ouroboros."""
+ LOCAL = lib.IPCP_LOCAL
+ UNICAST = lib.IPCP_UNICAST
+ BROADCAST = lib.IPCP_BROADCAST
+ ETH_LLC = lib.IPCP_ETH_LLC
+ ETH_DIX = lib.IPCP_ETH_DIX
+ UDP4 = lib.IPCP_UDP4
+ UDP6 = lib.IPCP_UDP6
+
+
+class AddressAuthPolicy(IntEnum):
+ """Address authority policies for unicast IPCPs."""
+ FLAT_RANDOM = lib.ADDR_AUTH_FLAT_RANDOM
+
+
+class LinkStatePolicy(IntEnum):
+ """Link state routing policies."""
+ SIMPLE = lib.LS_SIMPLE
+ LFA = lib.LS_LFA
+ ECMP = lib.LS_ECMP
+
+
+class RoutingPolicy(IntEnum):
+ """Routing policies."""
+ LINK_STATE = lib.ROUTING_LINK_STATE
+
+
+class CongestionAvoidPolicy(IntEnum):
+ """Congestion avoidance policies."""
+ NONE = lib.CA_NONE
+ MB_ECN = lib.CA_MB_ECN
+
+
+class DirectoryPolicy(IntEnum):
+ """Directory policies."""
+ DHT = lib.DIR_DHT
+
+
+class DirectoryHashAlgo(IntEnum):
+ """Directory hash algorithms."""
+ SHA3_224 = lib.DIR_HASH_SHA3_224
+ SHA3_256 = lib.DIR_HASH_SHA3_256
+ SHA3_384 = lib.DIR_HASH_SHA3_384
+ SHA3_512 = lib.DIR_HASH_SHA3_512
+
+
+class LoadBalancePolicy(IntEnum):
+ """Load balancing policies for names."""
+ ROUND_ROBIN = lib.LB_RR
+ SPILL = lib.LB_SPILL
+
+
+BIND_AUTO = lib.BIND_AUTO
+
+# Unicast IPCP component names
+DT_COMP = "Data Transfer"
+MGMT_COMP = "Management"
+
+
+# --- Exceptions ---
+
+class IrmError(Exception):
+ """General IRM error."""
+ pass
+
+
+class IpcpCreateError(IrmError):
+ pass
+
+
+class IpcpBootstrapError(IrmError):
+ pass
+
+
+class IpcpEnrollError(IrmError):
+ pass
+
+
+class IpcpConnectError(IrmError):
+ pass
+
+
+class NameError(IrmError):
+ pass
+
+
+class BindError(IrmError):
+ pass
+
+
+# --- Configuration classes ---
+
+class LinkStateConfig:
+ """Configuration for link state routing."""
+
+ def __init__(self,
+ pol: LinkStatePolicy = LinkStatePolicy.SIMPLE,
+ t_recalc: int = 4,
+ t_update: int = 15,
+ t_timeo: int = 60):
+ self.pol = pol
+ self.t_recalc = t_recalc
+ self.t_update = t_update
+ self.t_timeo = t_timeo
+
+
+class RoutingConfig:
+ """Routing configuration."""
+
+ def __init__(self,
+ pol: RoutingPolicy = RoutingPolicy.LINK_STATE,
+ ls: LinkStateConfig = None):
+ self.pol = pol
+ self.ls = ls or LinkStateConfig()
+
+
+class DtConfig:
+ """Data transfer configuration for unicast IPCPs."""
+
+ def __init__(self,
+ addr_size: int = 4,
+ eid_size: int = 8,
+ max_ttl: int = 60,
+ routing: RoutingConfig = None):
+ self.addr_size = addr_size
+ self.eid_size = eid_size
+ self.max_ttl = max_ttl
+ self.routing = routing or RoutingConfig()
+
+
+class DhtConfig:
+ """DHT directory configuration."""
+
+ def __init__(self,
+ alpha: int = 3,
+ k: int = 8,
+ t_expire: int = 86400,
+ t_refresh: int = 900,
+ t_replicate: int = 900):
+ self.alpha = alpha
+ self.k = k
+ self.t_expire = t_expire
+ self.t_refresh = t_refresh
+ self.t_replicate = t_replicate
+
+
+class DirConfig:
+ """Directory configuration."""
+
+ def __init__(self,
+ pol: DirectoryPolicy = DirectoryPolicy.DHT,
+ dht: DhtConfig = None):
+ self.pol = pol
+ self.dht = dht or DhtConfig()
+
+
+class UnicastConfig:
+ """Configuration for unicast IPCPs."""
+
+ def __init__(self,
+ dt: DtConfig = None,
+ dir: DirConfig = None,
+ addr_auth: AddressAuthPolicy = AddressAuthPolicy.FLAT_RANDOM,
+ cong_avoid: CongestionAvoidPolicy = CongestionAvoidPolicy.MB_ECN):
+ self.dt = dt or DtConfig()
+ self.dir = dir or DirConfig()
+ self.addr_auth = addr_auth
+ self.cong_avoid = cong_avoid
+
+
+class EthConfig:
+ """Configuration for Ethernet IPCPs (LLC or DIX)."""
+
+ def __init__(self,
+ dev: str = "",
+ ethertype: int = 0xA000):
+ self.dev = dev
+ self.ethertype = ethertype
+
+
+class Udp4Config:
+ """Configuration for UDP over IPv4 IPCPs."""
+
+ def __init__(self,
+ ip_addr: str = "0.0.0.0",
+ dns_addr: str = "0.0.0.0",
+ port: int = 3435):
+ self.ip_addr = ip_addr
+ self.dns_addr = dns_addr
+ self.port = port
+
+
+class Udp6Config:
+ """Configuration for UDP over IPv6 IPCPs."""
+
+ def __init__(self,
+ ip_addr: str = "::",
+ dns_addr: str = "::",
+ port: int = 3435):
+ self.ip_addr = ip_addr
+ self.dns_addr = dns_addr
+ self.port = port
+
+
+class IpcpConfig:
+ """
+ Configuration for bootstrapping an IPCP.
+
+ Depending on the IPCP type, set the appropriate sub-configuration:
+ - UNICAST: unicast (UnicastConfig)
+ - ETH_LLC: eth (EthConfig)
+ - ETH_DIX: eth (EthConfig)
+ - UDP4: udp4 (Udp4Config)
+ - UDP6: udp6 (Udp6Config)
+ - LOCAL: no extra config needed
+ - BROADCAST: no extra config needed
+ """
+
+ def __init__(self,
+ ipcp_type: IpcpType,
+ layer_name: str = "",
+ dir_hash_algo: DirectoryHashAlgo = DirectoryHashAlgo.SHA3_256,
+ unicast: UnicastConfig = None,
+ eth: EthConfig = None,
+ udp4: Udp4Config = None,
+ udp6: Udp6Config = None):
+ self.ipcp_type = ipcp_type
+ self.layer_name = layer_name
+ self.dir_hash_algo = dir_hash_algo
+ self.unicast = unicast
+ self.eth = eth
+ self.udp4 = udp4
+ self.udp6 = udp6
+
+
+class NameSecPaths:
+ """Security paths for a name (encryption, key, certificate)."""
+
+ def __init__(self,
+ enc: str = "",
+ key: str = "",
+ crt: str = ""):
+ self.enc = enc
+ self.key = key
+ self.crt = crt
+
+
+class NameInfo:
+ """Information about a registered name."""
+
+ def __init__(self,
+ name: str,
+ pol_lb: LoadBalancePolicy = LoadBalancePolicy.ROUND_ROBIN,
+ server_sec: NameSecPaths = None,
+ client_sec: NameSecPaths = None):
+ self.name = name
+ self.pol_lb = pol_lb
+ self.server_sec = server_sec or NameSecPaths()
+ self.client_sec = client_sec or NameSecPaths()
+
+
+class IpcpInfo:
+ """Information about a running IPCP (from list_ipcps)."""
+
+ def __init__(self,
+ pid: int,
+ ipcp_type: IpcpType,
+ name: str,
+ layer: str):
+ self.pid = pid
+ self.type = ipcp_type
+ self.name = name
+ self.layer = layer
+
+ def __repr__(self):
+ return (f"IpcpInfo(pid={self.pid}, type={self.type.name}, "
+ f"name='{self.name}', layer='{self.layer}')")
+
+
+# --- Internal conversion functions ---
+
+def _ipcp_config_to_c(conf: IpcpConfig):
+ """Convert an IpcpConfig to a C struct ipcp_config *."""
+ _conf = ffi.new("struct ipcp_config *")
+
+ # Layer info
+ layer_name = conf.layer_name.encode()
+ ffi.memmove(_conf.layer_info.name, layer_name,
+ min(len(layer_name), 255))
+ _conf.layer_info.dir_hash_algo = conf.dir_hash_algo
+
+ _conf.type = conf.ipcp_type
+
+ if conf.ipcp_type == IpcpType.UNICAST:
+ uc = conf.unicast or UnicastConfig()
+ _conf.unicast.dt.addr_size = uc.dt.addr_size
+ _conf.unicast.dt.eid_size = uc.dt.eid_size
+ _conf.unicast.dt.max_ttl = uc.dt.max_ttl
+ _conf.unicast.dt.routing.pol = uc.dt.routing.pol
+ _conf.unicast.dt.routing.ls.pol = uc.dt.routing.ls.pol
+ _conf.unicast.dt.routing.ls.t_recalc = uc.dt.routing.ls.t_recalc
+ _conf.unicast.dt.routing.ls.t_update = uc.dt.routing.ls.t_update
+ _conf.unicast.dt.routing.ls.t_timeo = uc.dt.routing.ls.t_timeo
+ _conf.unicast.dir.pol = uc.dir.pol
+ _conf.unicast.dir.dht.params.alpha = uc.dir.dht.alpha
+ _conf.unicast.dir.dht.params.k = uc.dir.dht.k
+ _conf.unicast.dir.dht.params.t_expire = uc.dir.dht.t_expire
+ _conf.unicast.dir.dht.params.t_refresh = uc.dir.dht.t_refresh
+ _conf.unicast.dir.dht.params.t_replicate = uc.dir.dht.t_replicate
+ _conf.unicast.addr_auth_type = uc.addr_auth
+ _conf.unicast.cong_avoid = uc.cong_avoid
+
+ elif conf.ipcp_type == IpcpType.ETH_LLC or conf.ipcp_type == IpcpType.ETH_DIX:
+ ec = conf.eth or EthConfig()
+ dev = ec.dev.encode()
+ ffi.memmove(_conf.eth.dev, dev, min(len(dev), 255))
+ _conf.eth.ethertype = ec.ethertype
+
+ elif conf.ipcp_type == IpcpType.UDP4:
+ uc = conf.udp4 or Udp4Config()
+ _conf.udp4.port = uc.port
+ if lib.ipcp_config_udp4_set_ip(_conf, uc.ip_addr.encode()) != 0:
+ raise ValueError(f"Invalid IPv4 address: {uc.ip_addr}")
+ if lib.ipcp_config_udp4_set_dns(_conf, uc.dns_addr.encode()) != 0:
+ raise ValueError(f"Invalid IPv4 DNS address: {uc.dns_addr}")
+
+ elif conf.ipcp_type == IpcpType.UDP6:
+ uc = conf.udp6 or Udp6Config()
+ _conf.udp6.port = uc.port
+ if lib.ipcp_config_udp6_set_ip(_conf, uc.ip_addr.encode()) != 0:
+ raise ValueError(f"Invalid IPv6 address: {uc.ip_addr}")
+ if lib.ipcp_config_udp6_set_dns(_conf, uc.dns_addr.encode()) != 0:
+ raise ValueError(f"Invalid IPv6 DNS address: {uc.dns_addr}")
+
+ return _conf
+
+
+def _name_info_to_c(info: NameInfo):
+ """Convert a NameInfo to a C struct name_info *."""
+ _info = ffi.new("struct name_info *")
+
+ name = info.name.encode()
+ ffi.memmove(_info.name, name, min(len(name), 255))
+ _info.pol_lb = info.pol_lb
+
+ for attr, sec in [('s', info.server_sec), ('c', info.client_sec)]:
+ sec_paths = getattr(_info, attr)
+ for field in ('enc', 'key', 'crt'):
+ val = getattr(sec, field).encode()
+ ffi.memmove(getattr(sec_paths, field), val,
+ min(len(val), 511))
+
+ return _info
+
+
+# --- IRM API functions ---
+
+def create_ipcp(name: str,
+ ipcp_type: IpcpType) -> int:
+ """
+ Create a new IPCP.
+
+ :param name: Name for the IPCP
+ :param ipcp_type: Type of IPCP to create
+ :return: PID of the created IPCP
+ """
+ ret = lib.irm_create_ipcp(name.encode(), ipcp_type)
+ if ret < 0:
+ raise IpcpCreateError(f"Failed to create IPCP '{name}' "
+ f"of type {ipcp_type.name}")
+
+ # The C function returns 0 on success, not the pid.
+ # Look up the actual pid by name.
+ for info in list_ipcps():
+ if info.name == name:
+ return info.pid
+
+ raise IpcpCreateError(f"IPCP '{name}' created but not found in list")
+
+
+def destroy_ipcp(pid: int) -> None:
+ """
+ Destroy an IPCP.
+
+ :param pid: PID of the IPCP to destroy
+ """
+ if lib.irm_destroy_ipcp(pid) != 0:
+ raise IrmError(f"Failed to destroy IPCP with pid {pid}")
+
+
+def list_ipcps() -> List[IpcpInfo]:
+ """
+ List all running IPCPs.
+
+ :return: List of IpcpInfo objects
+ """
+ _ipcps = ffi.new("struct ipcp_list_info **")
+ n = lib.irm_list_ipcps(_ipcps)
+ if n < 0:
+ raise IrmError("Failed to list IPCPs")
+
+ result = []
+ for i in range(n):
+ info = _ipcps[0][i]
+ result.append(IpcpInfo(
+ pid=info.pid,
+ ipcp_type=IpcpType(info.type),
+ name=ffi.string(info.name).decode(),
+ layer=ffi.string(info.layer).decode()
+ ))
+
+ if n > 0:
+ lib.free(_ipcps[0])
+
+ return result
+
+
+def enroll_ipcp(pid: int, dst: str) -> None:
+ """
+ Enroll an IPCP in a layer.
+
+ :param pid: PID of the IPCP to enroll
+ :param dst: Name to use for enrollment
+ """
+ if lib.irm_enroll_ipcp(pid, dst.encode()) != 0:
+ raise IpcpEnrollError(f"Failed to enroll IPCP {pid} to '{dst}'")
+
+
+def bootstrap_ipcp(pid: int, conf: IpcpConfig) -> None:
+ """
+ Bootstrap an IPCP.
+
+ :param pid: PID of the IPCP to bootstrap
+ :param conf: Configuration for the IPCP
+ """
+ _conf = _ipcp_config_to_c(conf)
+ if lib.irm_bootstrap_ipcp(pid, _conf) != 0:
+ raise IpcpBootstrapError(f"Failed to bootstrap IPCP {pid}")
+
+
+def connect_ipcp(pid: int,
+ component: str,
+ dst: str,
+ qos: QoSSpec = None) -> None:
+ """
+ Connect an IPCP component to a destination.
+
+ :param pid: PID of the IPCP
+ :param component: Component to connect (DT_COMP or MGMT_COMP)
+ :param dst: Destination name
+ :param qos: QoS specification for the connection
+ """
+ _qos = _qos_to_qosspec(qos)
+ if _qos == ffi.NULL:
+ _qos = ffi.new("qosspec_t *")
+ if lib.irm_connect_ipcp(pid, dst.encode(), component.encode(),
+ _qos[0]) != 0:
+ raise IpcpConnectError(f"Failed to connect IPCP {pid} "
+ f"component '{component}' to '{dst}'")
+
+
+def disconnect_ipcp(pid: int,
+ component: str,
+ dst: str) -> None:
+ """
+ Disconnect an IPCP component from a destination.
+
+ :param pid: PID of the IPCP
+ :param component: Component to disconnect
+ :param dst: Destination name
+ """
+ if lib.irm_disconnect_ipcp(pid, dst.encode(),
+ component.encode()) != 0:
+ raise IpcpConnectError(f"Failed to disconnect IPCP {pid} "
+ f"component '{component}' from '{dst}'")
+
+
+def bind_program(prog: str,
+ name: str,
+ opts: int = 0,
+ argv: List[str] = None) -> None:
+ """
+ Bind a program to a name.
+
+ :param prog: Path to the program
+ :param name: Name to bind to
+ :param opts: Bind options (e.g. BIND_AUTO)
+ :param argv: Arguments to pass when the program is started
+ """
+ if argv:
+ argc = len(argv)
+ _argv = ffi.new("char *[]", [ffi.new("char[]", a.encode())
+ for a in argv])
+ else:
+ argc = 0
+ _argv = ffi.NULL
+
+ if lib.irm_bind_program(prog.encode(), name.encode(),
+ opts, argc, _argv) != 0:
+ raise BindError(f"Failed to bind program '{prog}' to name '{name}'")
+
+
+def unbind_program(prog: str, name: str) -> None:
+ """
+ Unbind a program from a name.
+
+ :param prog: Path to the program
+ :param name: Name to unbind from
+ """
+ if lib.irm_unbind_program(prog.encode(), name.encode()) != 0:
+ raise BindError(f"Failed to unbind program '{prog}' "
+ f"from name '{name}'")
+
+
+def bind_process(pid: int, name: str) -> None:
+ """
+ Bind a running process to a name.
+
+ :param pid: PID of the process
+ :param name: Name to bind to
+ """
+ if lib.irm_bind_process(pid, name.encode()) != 0:
+ raise BindError(f"Failed to bind process {pid} to name '{name}'")
+
+
+def unbind_process(pid: int, name: str) -> None:
+ """
+ Unbind a process from a name.
+
+ :param pid: PID of the process
+ :param name: Name to unbind from
+ """
+ if lib.irm_unbind_process(pid, name.encode()) != 0:
+ raise BindError(f"Failed to unbind process {pid} "
+ f"from name '{name}'")
+
+
+def create_name(info: NameInfo) -> None:
+ """
+ Create a name in the IRM.
+
+ :param info: NameInfo describing the name to create
+ """
+ _info = _name_info_to_c(info)
+ if lib.irm_create_name(_info) != 0:
+ raise NameError(f"Failed to create name '{info.name}'")
+
+
+def destroy_name(name: str) -> None:
+ """
+ Destroy a name in the IRM.
+
+ :param name: The name to destroy
+ """
+ if lib.irm_destroy_name(name.encode()) != 0:
+ raise NameError(f"Failed to destroy name '{name}'")
+
+
+def list_names() -> List[NameInfo]:
+ """
+ List all registered names.
+
+ :return: List of NameInfo objects
+ """
+ _names = ffi.new("struct name_info **")
+ n = lib.irm_list_names(_names)
+ if n < 0:
+ raise IrmError("Failed to list names")
+
+ result = []
+ for i in range(n):
+ info = _names[0][i]
+ ni = NameInfo(
+ name=ffi.string(info.name).decode(),
+ pol_lb=LoadBalancePolicy(info.pol_lb)
+ )
+ ni.server_sec = NameSecPaths(
+ enc=ffi.string(info.s.enc).decode(),
+ key=ffi.string(info.s.key).decode(),
+ crt=ffi.string(info.s.crt).decode()
+ )
+ ni.client_sec = NameSecPaths(
+ enc=ffi.string(info.c.enc).decode(),
+ key=ffi.string(info.c.key).decode(),
+ crt=ffi.string(info.c.crt).decode()
+ )
+ result.append(ni)
+
+ if n > 0:
+ lib.free(_names[0])
+
+ return result
+
+
+def reg_name(name: str, pid: int) -> None:
+ """
+ Register an IPCP to a name.
+
+ :param name: The name to register
+ :param pid: PID of the IPCP to register
+ """
+ if lib.irm_reg_name(name.encode(), pid) != 0:
+ raise NameError(f"Failed to register name '{name}' "
+ f"with IPCP {pid}")
+
+
+def unreg_name(name: str, pid: int) -> None:
+ """
+ Unregister an IPCP from a name.
+
+ :param name: The name to unregister
+ :param pid: PID of the IPCP to unregister
+ """
+ if lib.irm_unreg_name(name.encode(), pid) != 0:
+ raise NameError(f"Failed to unregister name '{name}' "
+ f"from IPCP {pid}")
diff --git a/ouroboros/qos.py b/ouroboros/qos.py
index a43d87d..bb4ecc4 100644
--- a/ouroboros/qos.py
+++ b/ouroboros/qos.py
@@ -1,7 +1,7 @@
#
-# Ouroboros - Copyright (C) 2016 - 2020
+# Ouroboros - Copyright (C) 2016 - 2026
#
-# Python API for applications - QoS
+# Python API for Ouroboros - QoS
#
# Dimitri Staessens <dimitri@ouroboros.rocks>
#
@@ -19,36 +19,35 @@
# Foundation, Inc., http://www.fsf.org/about/contact/.
#
-from _ouroboros_cffi import ffi
-from math import modf
-from typing import Optional
-
# Some constants
MILLION = 1000 * 1000
BILLION = 1000 * 1000 * 1000
+DEFAULT_PEER_TIMEOUT = 120000
+UINT32_MAX = 0xFFFFFFFF
+
class QoSSpec:
"""
- delay: In ms, default 1000s
+ delay: In ms, default UINT32_MAX
bandwidth: In bits / s, default 0
availability: Class of 9s, default 0
- loss: Packet loss in ppm, default MILLION
- ber: Bit error rate, errors per billion bits. default BILLION
+ loss: Packet loss, default 1
+ ber: Bit error rate, errors per billion bits, default 1
in_order: In-order delivery, enables FRCT, default 0
- max_gap: Maximum interruption in ms, default MILLION
+ max_gap: Maximum interruption in ms, default UINT32_MAX
timeout: Peer timeout (ms), default 120000 (2 minutes)
"""
def __init__(self,
- delay: int = MILLION,
+ delay: int = UINT32_MAX,
bandwidth: int = 0,
availability: int = 0,
loss: int = 1,
- ber: int = MILLION,
+ ber: int = 1,
in_order: int = 0,
- max_gap: int = MILLION,
- timeout: int = 120000):
+ max_gap: int = UINT32_MAX,
+ timeout: int = DEFAULT_PEER_TIMEOUT):
self.delay = delay
self.bandwidth = bandwidth
self.availability = availability
@@ -57,54 +56,3 @@ class QoSSpec:
self.in_order = in_order
self.max_gap = max_gap
self.timeout = timeout
-
-
-def _fl_to_timespec(timeo: float):
- if timeo is None:
- return ffi.NULL
- elif timeo <= 0:
- return ffi.new("struct timespec *", [0, 0])
- else:
- frac, whole = modf(timeo)
- _timeo = ffi.new("struct timespec *")
- _timeo.tv_sec = whole
- _timeo.tv_nsec = frac * BILLION
- return _timeo
-
-
-def _timespec_to_fl(_timeo) -> Optional[float]:
- if _timeo is ffi.NULL:
- return None
- elif _timeo.tv_sec <= 0 and _timeo.tv_nsec == 0:
- return 0
- else:
- return _timeo.tv_sec + _timeo.tv_nsec / BILLION
-
-
-def _qos_to_qosspec(qos: QoSSpec):
- if qos is None:
- return ffi.NULL
- else:
- return ffi.new("qosspec_t *",
- [qos.delay,
- qos.bandwidth,
- qos.availability,
- qos.loss,
- qos.ber,
- qos.in_order,
- qos.max_gap,
- qos.timeout])
-
-
-def _qosspec_to_qos(_qos) -> Optional[QoSSpec]:
- if _qos is ffi.NULL:
- return None
- else:
- return QoSSpec(delay=_qos.delay,
- bandwidth=_qos.bandwidth,
- availability=_qos.availability,
- loss=_qos.loss,
- ber=_qos.ber,
- in_order=_qos.in_order,
- max_gap=_qos.max_gap,
- timeout=_qos.timeout)
diff --git a/setup.py b/setup.py
index 876f782..a22b7bd 100755
--- a/setup.py
+++ b/setup.py
@@ -4,7 +4,7 @@ import setuptools
setuptools.setup(
name='PyOuroboros',
- version=0.19,
+ version='0.23.0',
url='https://ouroboros.rocks',
keywords='ouroboros IPC subsystem',
author='Dimitri Staessens',
@@ -18,7 +18,8 @@ setuptools.setup(
"cffi>=1.0.0"
],
cffi_modules=[
- "ffi/pyouroboros_build.py:ffibuilder"
+ "ffi/pyouroboros_build_dev.py:ffibuilder",
+ "ffi/pyouroboros_build_irm.py:ffibuilder"
],
install_requires=[
"cffi>=1.0.0"