Organize host and robot streaming releases

This commit is contained in:
Mike Mi
2026-08-09 15:56:20 +08:00
commit decff19daf
532 changed files with 127054 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
try:
from ._omnisocket import (
MSG_TYPE_BINARY,
MSG_TYPE_ERROR,
MSG_TYPE_FILE,
MSG_TYPE_REGISTER,
MSG_TYPE_TEXT,
Session,
UdpSession,
)
except ImportError as exc:
raise ImportError(
"omnisocket extension is not built; run `make python-ext` on a Linux host first"
) from exc
CONTROL_DEFAULTS = {
"nodelay": 1,
"interval_ms": 5,
"resend": 2,
"nc": 1,
"sndwnd": 32,
"rcvwnd": 32,
"mtu": 1400,
}
VIDEO_DEFAULTS = {
"nodelay": 1,
"interval_ms": 10,
"resend": 2,
"nc": 1,
"sndwnd": 256,
"rcvwnd": 256,
"mtu": 1400,
}
TELEMETRY_DEFAULTS = {
"nodelay": 0,
"interval_ms": 50,
"resend": 0,
"nc": 0,
"sndwnd": 64,
"rcvwnd": 64,
"mtu": 1400,
}
__all__ = [
"CONTROL_DEFAULTS",
"TELEMETRY_DEFAULTS",
"VIDEO_DEFAULTS",
"MSG_TYPE_BINARY",
"MSG_TYPE_ERROR",
"MSG_TYPE_FILE",
"MSG_TYPE_REGISTER",
"MSG_TYPE_TEXT",
"Session",
"UdpSession",
]

View File

@@ -0,0 +1,684 @@
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "omnisocket_client.h"
typedef struct PyOmniSession {
PyObject_HEAD
omnisocket_session_t session;
} PyOmniSession;
typedef struct PyOmniUdpSession {
PyObject_HEAD
omnisocket_udp_session_t session;
} PyOmniUdpSession;
PyDoc_STRVAR(
PyOmniSession_recv_doc,
"recv(timeout_ms=-1) -> (from_peer, msg_type, payload) | None"
);
PyDoc_STRVAR(
PyOmniSession_recv_into_doc,
"recv_into(buffer, timeout_ms=-1) -> dict | None\n"
"\n"
"The writable buffer must be large enough for the full message body.\n"
"If it is too small, BufferError reports the required size but the\n"
"current frame has already been consumed and is lost."
);
static PyObject *build_recv_result(const message_t *msg) {
PyObject *body = NULL;
PyObject *result = NULL;
body = PyBytes_FromStringAndSize((const char *) msg->body, (Py_ssize_t) msg->body_len);
if (body == NULL) {
return NULL;
}
result = Py_BuildValue("(siO)", msg->from, (int) msg->type, body);
Py_DECREF(body);
return result;
}
static PyObject *build_recv_meta_dict(
const char *from_peer,
const char *to_peer,
const char *file_name,
int msg_type,
unsigned long long message_id,
unsigned long long body_len
) {
return Py_BuildValue(
"{s:s,s:s,s:s,s:i,s:K,s:K}",
"from",
from_peer,
"to",
to_peer,
"file_name",
file_name,
"msg_type",
msg_type,
"message_id",
message_id,
"body_len",
body_len
);
}
static PyObject *build_stats_dict(const omnisocket_session_stats_t *stats) {
return Py_BuildValue(
"{s:K,s:K,s:K,s:K,s:K,s:K,s:K,s:i,s:i,s:s}",
"send_calls",
(unsigned long long) stats->send_calls,
"send_bytes",
(unsigned long long) stats->send_bytes,
"send_errors",
(unsigned long long) stats->send_errors,
"recv_calls",
(unsigned long long) stats->recv_calls,
"recv_bytes",
(unsigned long long) stats->recv_bytes,
"recv_timeouts",
(unsigned long long) stats->recv_timeouts,
"recv_errors",
(unsigned long long) stats->recv_errors,
"connected",
stats->connected,
"registered",
stats->registered,
"last_server_error",
stats->last_server_error
);
}
static PyObject *build_kcp_stats_dict(const omnisocket_session_kcp_stats_t *stats) {
PyObject *dict = PyDict_New();
PyObject *value = NULL;
if (dict == NULL) {
return NULL;
}
#define SET_KCP_STAT(key, expr) \
do { \
value = (expr); \
if (value == NULL) { \
Py_DECREF(dict); \
return NULL; \
} \
if (PyDict_SetItemString(dict, (key), value) != 0) { \
Py_DECREF(value); \
Py_DECREF(dict); \
return NULL; \
} \
Py_DECREF(value); \
value = NULL; \
} while (0)
SET_KCP_STAT("connected", PyLong_FromLong(stats->connected));
SET_KCP_STAT("conv", PyLong_FromUnsignedLong(stats->conv));
SET_KCP_STAT("rto_ms", PyLong_FromUnsignedLong(stats->rto_ms));
SET_KCP_STAT("srtt_ms", PyLong_FromLong(stats->srtt_ms));
SET_KCP_STAT("min_srtt_ms", PyLong_FromLong(stats->min_srtt_ms));
SET_KCP_STAT("srttvar_ms", PyLong_FromLong(stats->srttvar_ms));
SET_KCP_STAT("last_feedback_age_ms", PyLong_FromUnsignedLong(stats->last_feedback_age_ms));
SET_KCP_STAT("snd_wnd", PyLong_FromUnsignedLong(stats->snd_wnd));
SET_KCP_STAT("rmt_wnd", PyLong_FromUnsignedLong(stats->rmt_wnd));
SET_KCP_STAT("inflight", PyLong_FromUnsignedLong(stats->inflight));
SET_KCP_STAT("window_limit", PyLong_FromUnsignedLong(stats->window_limit));
SET_KCP_STAT("window_pressure_pct", PyFloat_FromDouble(stats->window_pressure_pct));
SET_KCP_STAT("snd_queue", PyLong_FromUnsignedLong(stats->snd_queue));
SET_KCP_STAT("rcv_queue", PyLong_FromUnsignedLong(stats->rcv_queue));
SET_KCP_STAT("snd_buffer", PyLong_FromUnsignedLong(stats->snd_buffer));
SET_KCP_STAT("out_segs_total", PyLong_FromUnsignedLongLong((unsigned long long) stats->out_segs_total));
SET_KCP_STAT("retrans_total", PyLong_FromUnsignedLongLong((unsigned long long) stats->retrans_total));
SET_KCP_STAT("fast_retrans_total", PyLong_FromUnsignedLongLong((unsigned long long) stats->fast_retrans_total));
SET_KCP_STAT("lost_total", PyLong_FromUnsignedLongLong((unsigned long long) stats->lost_total));
SET_KCP_STAT("repeat_total", PyLong_FromUnsignedLongLong((unsigned long long) stats->repeat_total));
SET_KCP_STAT("xmit_total", PyLong_FromUnsignedLong(stats->xmit_total));
#undef SET_KCP_STAT
return dict;
}
static PyObject *PyOmniSession_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) {
PyOmniSession *self;
(void) args;
(void) kwargs;
self = (PyOmniSession *) type->tp_alloc(type, 0);
if (self == NULL) {
return NULL;
}
if (omnisocket_session_init(&self->session) != 0) {
type->tp_free((PyObject *) self);
return PyErr_SetFromErrno(PyExc_OSError);
}
return (PyObject *) self;
}
static void PyOmniSession_dealloc(PyOmniSession *self) {
omnisocket_session_destroy(&self->session);
Py_TYPE(self)->tp_free((PyObject *) self);
}
static PyObject *PyOmniSession_connect(PyOmniSession *self, PyObject *args, PyObject *kwargs) {
const char *server_addr;
const char *peer_id;
const char *relay_via = "";
const char *bind_ip = "";
const char *bind_device = "";
int nodelay = KCP_DEFAULT_NODELAY;
int interval_ms = KCP_DEFAULT_INTERVAL_MS;
int resend = KCP_DEFAULT_RESEND;
int nc = KCP_DEFAULT_NC;
int sndwnd = KCP_DEFAULT_SND_WND;
int rcvwnd = KCP_DEFAULT_RCV_WND;
int mtu = KCP_DEFAULT_MTU;
int stats_interval_ms = KCP_DEFAULT_STATS_INTERVAL_MS;
kcp_conn_options_t options;
int rc;
static char *kwlist[] = {
"server_addr",
"peer_id",
"relay_via",
"bind_ip",
"bind_device",
"nodelay",
"interval_ms",
"resend",
"nc",
"sndwnd",
"rcvwnd",
"mtu",
"stats_interval_ms",
NULL
};
if (!PyArg_ParseTupleAndKeywords(
args,
kwargs,
"ss|sssiiiiiiii",
kwlist,
&server_addr,
&peer_id,
&relay_via,
&bind_ip,
&bind_device,
&nodelay,
&interval_ms,
&resend,
&nc,
&sndwnd,
&rcvwnd,
&mtu,
&stats_interval_ms)) {
return NULL;
}
kcp_conn_options_init(&options);
options.nodelay = nodelay;
options.interval_ms = interval_ms;
options.resend = resend;
options.nc = nc;
options.sndwnd = sndwnd;
options.rcvwnd = rcvwnd;
options.mtu = mtu;
Py_BEGIN_ALLOW_THREADS
rc = omnisocket_session_connect(
&self->session,
server_addr,
relay_via,
peer_id,
bind_ip,
bind_device,
&options,
stats_interval_ms
);
Py_END_ALLOW_THREADS
if (rc != 0) {
return PyErr_SetFromErrno(PyExc_OSError);
}
Py_RETURN_NONE;
}
static PyObject *PyOmniSession_close(PyOmniSession *self, PyObject *Py_UNUSED(ignored)) {
int rc;
Py_BEGIN_ALLOW_THREADS
rc = omnisocket_session_close(&self->session);
Py_END_ALLOW_THREADS
if (rc != 0) {
return PyErr_SetFromErrno(PyExc_OSError);
}
Py_RETURN_NONE;
}
static PyObject *PyOmniSession_send(PyOmniSession *self, PyObject *args, PyObject *kwargs) {
const char *to;
Py_buffer payload;
int rc;
static char *kwlist[] = {"to", "data", NULL};
memset(&payload, 0, sizeof(payload));
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sy*", kwlist, &to, &payload)) {
return NULL;
}
Py_BEGIN_ALLOW_THREADS
rc = omnisocket_session_send(&self->session, to, payload.buf, (size_t) payload.len);
Py_END_ALLOW_THREADS
PyBuffer_Release(&payload);
if (rc != 0) {
return PyErr_SetFromErrno(PyExc_OSError);
}
Py_RETURN_NONE;
}
static PyObject *PyOmniSession_send_with_id(PyOmniSession *self, PyObject *args, PyObject *kwargs) {
const char *to;
Py_buffer payload;
int rc;
uint64_t message_id = 0;
static char *kwlist[] = {"to", "data", NULL};
memset(&payload, 0, sizeof(payload));
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sy*", kwlist, &to, &payload)) {
return NULL;
}
Py_BEGIN_ALLOW_THREADS
rc = omnisocket_session_send_with_id(&self->session, to, payload.buf, (size_t) payload.len, &message_id);
Py_END_ALLOW_THREADS
PyBuffer_Release(&payload);
if (rc != 0) {
return PyErr_SetFromErrno(PyExc_OSError);
}
return PyLong_FromUnsignedLongLong((unsigned long long) message_id);
}
static PyObject *PyOmniSession_recv(PyOmniSession *self, PyObject *args, PyObject *kwargs) {
int timeout_ms = -1;
int rc;
message_t msg;
PyObject *result = NULL;
static char *kwlist[] = {"timeout_ms", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|i", kwlist, &timeout_ms)) {
return NULL;
}
protocol_message_init(&msg);
Py_BEGIN_ALLOW_THREADS
rc = omnisocket_session_recv(&self->session, &msg, timeout_ms);
Py_END_ALLOW_THREADS
if (rc == 1) {
protocol_message_clear(&msg);
Py_RETURN_NONE;
}
if (rc != 0) {
protocol_message_clear(&msg);
return PyErr_SetFromErrno(PyExc_OSError);
}
result = build_recv_result(&msg);
protocol_message_clear(&msg);
return result;
}
static PyObject *PyOmniSession_recv_into(PyOmniSession *self, PyObject *args, PyObject *kwargs) {
PyObject *buffer_obj;
Py_buffer view;
int timeout_ms = -1;
int rc;
kcp_client_recv_meta_t meta;
PyObject *result = NULL;
static char *kwlist[] = {"buffer", "timeout_ms", NULL};
memset(&view, 0, sizeof(view));
memset(&meta, 0, sizeof(meta));
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|i", kwlist, &buffer_obj, &timeout_ms)) {
return NULL;
}
if (PyObject_GetBuffer(buffer_obj, &view, PyBUF_WRITABLE) != 0) {
return NULL;
}
Py_BEGIN_ALLOW_THREADS
rc = omnisocket_session_recv_into(&self->session, view.buf, (size_t) view.len, &meta, timeout_ms);
Py_END_ALLOW_THREADS
PyBuffer_Release(&view);
if (rc == 1) {
Py_RETURN_NONE;
}
if (rc == 2) {
PyErr_Format(
PyExc_BufferError,
"buffer too small: need %zu bytes; current frame was already consumed and dropped",
meta.body_len
);
return NULL;
}
if (rc != 0) {
return PyErr_SetFromErrno(PyExc_OSError);
}
result = build_recv_meta_dict(
meta.from,
meta.to,
meta.file_name,
(int) meta.type,
(unsigned long long) meta.id,
(unsigned long long) meta.body_len
);
return result;
}
static PyObject *PyOmniSession_stats(PyOmniSession *self, PyObject *Py_UNUSED(ignored)) {
omnisocket_session_stats_t stats;
memset(&stats, 0, sizeof(stats));
omnisocket_session_stats_snapshot(&self->session, &stats);
return build_stats_dict(&stats);
}
static PyObject *PyOmniSession_kcp_stats(PyOmniSession *self, PyObject *Py_UNUSED(ignored)) {
omnisocket_session_kcp_stats_t stats;
memset(&stats, 0, sizeof(stats));
omnisocket_session_kcp_stats_snapshot(&self->session, &stats);
return build_kcp_stats_dict(&stats);
}
static PyMethodDef PyOmniSession_methods[] = {
{"connect", (PyCFunction) PyOmniSession_connect, METH_VARARGS | METH_KEYWORDS, NULL},
{"close", (PyCFunction) PyOmniSession_close, METH_NOARGS, NULL},
{"send", (PyCFunction) PyOmniSession_send, METH_VARARGS | METH_KEYWORDS, NULL},
{"send_with_id", (PyCFunction) PyOmniSession_send_with_id, METH_VARARGS | METH_KEYWORDS, NULL},
{"recv", (PyCFunction) PyOmniSession_recv, METH_VARARGS | METH_KEYWORDS, PyOmniSession_recv_doc},
{"recv_into", (PyCFunction) PyOmniSession_recv_into, METH_VARARGS | METH_KEYWORDS, PyOmniSession_recv_into_doc},
{"stats", (PyCFunction) PyOmniSession_stats, METH_NOARGS, NULL},
{"kcp_stats", (PyCFunction) PyOmniSession_kcp_stats, METH_NOARGS, NULL},
{NULL, NULL, 0, NULL}
};
static PyTypeObject PyOmniSessionType = {
PyVarObject_HEAD_INIT(NULL, 0)
};
static PyObject *PyOmniUdpSession_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) {
PyOmniUdpSession *self;
(void) args;
(void) kwargs;
self = (PyOmniUdpSession *) type->tp_alloc(type, 0);
if (self == NULL) {
return NULL;
}
if (omnisocket_udp_session_init(&self->session) != 0) {
type->tp_free((PyObject *) self);
return PyErr_SetFromErrno(PyExc_OSError);
}
return (PyObject *) self;
}
static void PyOmniUdpSession_dealloc(PyOmniUdpSession *self) {
omnisocket_udp_session_destroy(&self->session);
Py_TYPE(self)->tp_free((PyObject *) self);
}
static PyObject *PyOmniUdpSession_connect(PyOmniUdpSession *self, PyObject *args, PyObject *kwargs) {
const char *server_addr;
const char *peer_id;
const char *bind_ip = "";
const char *bind_device = "";
int enable_timestamping = 0;
int rc;
static char *kwlist[] = {
"server_addr",
"peer_id",
"bind_ip",
"bind_device",
"enable_timestamping",
NULL
};
if (!PyArg_ParseTupleAndKeywords(
args,
kwargs,
"ss|ssi",
kwlist,
&server_addr,
&peer_id,
&bind_ip,
&bind_device,
&enable_timestamping)) {
return NULL;
}
Py_BEGIN_ALLOW_THREADS
rc = omnisocket_udp_session_connect(
&self->session,
server_addr,
peer_id,
bind_ip,
bind_device,
enable_timestamping
);
Py_END_ALLOW_THREADS
if (rc != 0) {
return PyErr_SetFromErrno(PyExc_OSError);
}
Py_RETURN_NONE;
}
static PyObject *PyOmniUdpSession_close(PyOmniUdpSession *self, PyObject *Py_UNUSED(ignored)) {
int rc;
Py_BEGIN_ALLOW_THREADS
rc = omnisocket_udp_session_close(&self->session);
Py_END_ALLOW_THREADS
if (rc != 0) {
return PyErr_SetFromErrno(PyExc_OSError);
}
Py_RETURN_NONE;
}
static PyObject *PyOmniUdpSession_send(PyOmniUdpSession *self, PyObject *args, PyObject *kwargs) {
const char *to;
Py_buffer payload;
int rc;
static char *kwlist[] = {"to", "data", NULL};
memset(&payload, 0, sizeof(payload));
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sy*", kwlist, &to, &payload)) {
return NULL;
}
Py_BEGIN_ALLOW_THREADS
rc = omnisocket_udp_session_send(&self->session, to, payload.buf, (size_t) payload.len);
Py_END_ALLOW_THREADS
PyBuffer_Release(&payload);
if (rc != 0) {
return PyErr_SetFromErrno(PyExc_OSError);
}
Py_RETURN_NONE;
}
static PyObject *PyOmniUdpSession_recv(PyOmniUdpSession *self, PyObject *args, PyObject *kwargs) {
int timeout_ms = -1;
int rc;
message_t msg;
PyObject *result = NULL;
static char *kwlist[] = {"timeout_ms", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|i", kwlist, &timeout_ms)) {
return NULL;
}
protocol_message_init(&msg);
Py_BEGIN_ALLOW_THREADS
rc = omnisocket_udp_session_recv(&self->session, &msg, timeout_ms);
Py_END_ALLOW_THREADS
if (rc == 1) {
protocol_message_clear(&msg);
Py_RETURN_NONE;
}
if (rc != 0) {
protocol_message_clear(&msg);
return PyErr_SetFromErrno(PyExc_OSError);
}
result = build_recv_result(&msg);
protocol_message_clear(&msg);
return result;
}
static PyObject *PyOmniUdpSession_recv_into(PyOmniUdpSession *self, PyObject *args, PyObject *kwargs) {
PyObject *buffer_obj;
Py_buffer view;
int timeout_ms = -1;
int rc;
udp_client_recv_meta_t meta;
PyObject *result = NULL;
static char *kwlist[] = {"buffer", "timeout_ms", NULL};
memset(&view, 0, sizeof(view));
memset(&meta, 0, sizeof(meta));
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|i", kwlist, &buffer_obj, &timeout_ms)) {
return NULL;
}
if (PyObject_GetBuffer(buffer_obj, &view, PyBUF_WRITABLE) != 0) {
return NULL;
}
Py_BEGIN_ALLOW_THREADS
rc = omnisocket_udp_session_recv_into(&self->session, view.buf, (size_t) view.len, &meta, timeout_ms);
Py_END_ALLOW_THREADS
PyBuffer_Release(&view);
if (rc == 1) {
Py_RETURN_NONE;
}
if (rc == 2) {
PyErr_Format(
PyExc_BufferError,
"buffer too small: need %zu bytes; current frame was already consumed and dropped",
meta.body_len
);
return NULL;
}
if (rc != 0) {
return PyErr_SetFromErrno(PyExc_OSError);
}
result = build_recv_meta_dict(
meta.from,
meta.to,
meta.file_name,
(int) meta.type,
(unsigned long long) meta.id,
(unsigned long long) meta.body_len
);
return result;
}
static PyObject *PyOmniUdpSession_stats(PyOmniUdpSession *self, PyObject *Py_UNUSED(ignored)) {
omnisocket_session_stats_t stats;
memset(&stats, 0, sizeof(stats));
omnisocket_udp_session_stats_snapshot(&self->session, &stats);
return build_stats_dict(&stats);
}
static PyMethodDef PyOmniUdpSession_methods[] = {
{"connect", (PyCFunction) PyOmniUdpSession_connect, METH_VARARGS | METH_KEYWORDS, NULL},
{"close", (PyCFunction) PyOmniUdpSession_close, METH_NOARGS, NULL},
{"send", (PyCFunction) PyOmniUdpSession_send, METH_VARARGS | METH_KEYWORDS, NULL},
{"recv", (PyCFunction) PyOmniUdpSession_recv, METH_VARARGS | METH_KEYWORDS, PyOmniSession_recv_doc},
{"recv_into", (PyCFunction) PyOmniUdpSession_recv_into, METH_VARARGS | METH_KEYWORDS, PyOmniSession_recv_into_doc},
{"stats", (PyCFunction) PyOmniUdpSession_stats, METH_NOARGS, NULL},
{NULL, NULL, 0, NULL}
};
static PyTypeObject PyOmniUdpSessionType = {
PyVarObject_HEAD_INIT(NULL, 0)
};
static PyModuleDef omnisocket_module = {
PyModuleDef_HEAD_INIT,
.m_name = "_omnisocket",
.m_size = -1,
};
PyMODINIT_FUNC PyInit__omnisocket(void) {
PyObject *module;
PyOmniSessionType.tp_name = "omnisocket.Session";
PyOmniSessionType.tp_basicsize = sizeof(PyOmniSession);
PyOmniSessionType.tp_flags = Py_TPFLAGS_DEFAULT;
PyOmniSessionType.tp_new = PyOmniSession_new;
PyOmniSessionType.tp_dealloc = (destructor) PyOmniSession_dealloc;
PyOmniSessionType.tp_methods = PyOmniSession_methods;
if (PyType_Ready(&PyOmniSessionType) < 0) {
return NULL;
}
PyOmniUdpSessionType.tp_name = "omnisocket.UdpSession";
PyOmniUdpSessionType.tp_basicsize = sizeof(PyOmniUdpSession);
PyOmniUdpSessionType.tp_flags = Py_TPFLAGS_DEFAULT;
PyOmniUdpSessionType.tp_new = PyOmniUdpSession_new;
PyOmniUdpSessionType.tp_dealloc = (destructor) PyOmniUdpSession_dealloc;
PyOmniUdpSessionType.tp_methods = PyOmniUdpSession_methods;
if (PyType_Ready(&PyOmniUdpSessionType) < 0) {
return NULL;
}
module = PyModule_Create(&omnisocket_module);
if (module == NULL) {
return NULL;
}
Py_INCREF(&PyOmniSessionType);
if (PyModule_AddObject(module, "Session", (PyObject *) &PyOmniSessionType) != 0) {
Py_DECREF(&PyOmniSessionType);
Py_DECREF(module);
return NULL;
}
Py_INCREF(&PyOmniUdpSessionType);
if (PyModule_AddObject(module, "UdpSession", (PyObject *) &PyOmniUdpSessionType) != 0) {
Py_DECREF(&PyOmniUdpSessionType);
Py_DECREF(module);
return NULL;
}
if (PyModule_AddIntConstant(module, "MSG_TYPE_TEXT", MSG_TYPE_TEXT) != 0 ||
PyModule_AddIntConstant(module, "MSG_TYPE_FILE", MSG_TYPE_FILE) != 0 ||
PyModule_AddIntConstant(module, "MSG_TYPE_REGISTER", MSG_TYPE_REGISTER) != 0 ||
PyModule_AddIntConstant(module, "MSG_TYPE_ERROR", MSG_TYPE_ERROR) != 0 ||
PyModule_AddIntConstant(module, "MSG_TYPE_BINARY", MSG_TYPE_BINARY) != 0) {
Py_DECREF(module);
return NULL;
}
return module;
}

View File

@@ -0,0 +1,572 @@
#include "omnisocket_client.h"
static void omnisocket_session_sync_client_state_locked(omnisocket_session_t *session, kcp_client_t *client) {
kcp_client_state_t client_state;
if (session == NULL) {
return;
}
memset(&client_state, 0, sizeof(client_state));
if (client != NULL) {
kcp_client_state_snapshot(client, &client_state);
}
session->stats.connected = client_state.connected;
session->stats.registered = client_state.registered;
snprintf(
session->stats.last_server_error,
sizeof(session->stats.last_server_error),
"%s",
client_state.last_server_error
);
}
static void omnisocket_session_mark_disconnected_locked(omnisocket_session_t *session) {
if (session == NULL) {
return;
}
session->stats.connected = 0;
session->stats.registered = 0;
}
int omnisocket_session_init(omnisocket_session_t *session) {
int rc;
if (session == NULL) {
errno = EINVAL;
return -1;
}
memset(session, 0, sizeof(*session));
rc = pthread_mutex_init(&session->mutex, NULL);
if (rc != 0) {
errno = rc;
return -1;
}
rc = pthread_cond_init(&session->idle_cond, NULL);
if (rc != 0) {
pthread_mutex_destroy(&session->mutex);
errno = rc;
return -1;
}
return 0;
}
void omnisocket_session_destroy(omnisocket_session_t *session) {
if (session == NULL) {
return;
}
(void) omnisocket_session_close(session);
pthread_cond_destroy(&session->idle_cond);
pthread_mutex_destroy(&session->mutex);
}
static int omnisocket_session_begin_client_op(omnisocket_session_t *session, kcp_client_t **out_client) {
if (session == NULL || out_client == NULL) {
errno = EINVAL;
return -1;
}
pthread_mutex_lock(&session->mutex);
if (session->closing) {
pthread_mutex_unlock(&session->mutex);
errno = ECANCELED;
return -1;
}
if (session->client == NULL) {
pthread_mutex_unlock(&session->mutex);
errno = ENOTCONN;
return -1;
}
*out_client = session->client;
session->active_ops += 1;
pthread_mutex_unlock(&session->mutex);
return 0;
}
int omnisocket_session_connect(
omnisocket_session_t *session,
const char *server_addr,
const char *relay_via,
const char *peer_id,
const char *bind_ip,
const char *bind_device,
const kcp_conn_options_t *options,
int stats_interval_ms
) {
kcp_client_t *client;
if (session == NULL || server_addr == NULL || peer_id == NULL) {
errno = EINVAL;
return -1;
}
pthread_mutex_lock(&session->mutex);
while (session->closing) {
pthread_cond_wait(&session->idle_cond, &session->mutex);
}
if (session->client != NULL) {
pthread_mutex_unlock(&session->mutex);
errno = EISCONN;
return -1;
}
client = kcp_client_dial_with_options(
server_addr,
relay_via,
peer_id,
bind_ip,
bind_device,
options,
NULL,
NULL,
NULL,
stats_interval_ms
);
if (client == NULL) {
pthread_mutex_unlock(&session->mutex);
return -1;
}
session->client = client;
omnisocket_session_sync_client_state_locked(session, client);
pthread_mutex_unlock(&session->mutex);
return 0;
}
int omnisocket_session_close(omnisocket_session_t *session) {
kcp_client_t *client;
if (session == NULL) {
errno = EINVAL;
return -1;
}
pthread_mutex_lock(&session->mutex);
while (session->closing) {
pthread_cond_wait(&session->idle_cond, &session->mutex);
}
client = session->client;
if (client != NULL) {
session->closing = 1;
session->client = NULL;
}
omnisocket_session_mark_disconnected_locked(session);
pthread_mutex_unlock(&session->mutex);
if (client != NULL) {
kcp_client_close(client);
pthread_mutex_lock(&session->mutex);
while (session->active_ops > 0) {
pthread_cond_wait(&session->idle_cond, &session->mutex);
}
pthread_mutex_unlock(&session->mutex);
kcp_client_free(client);
pthread_mutex_lock(&session->mutex);
session->closing = 0;
pthread_cond_broadcast(&session->idle_cond);
pthread_mutex_unlock(&session->mutex);
}
return 0;
}
int omnisocket_session_send(omnisocket_session_t *session, const char *to, const void *data, size_t data_len) {
return omnisocket_session_send_with_id(session, to, data, data_len, NULL);
}
int omnisocket_session_send_with_id(
omnisocket_session_t *session,
const char *to,
const void *data,
size_t data_len,
uint64_t *out_message_id
) {
kcp_client_t *client;
int rc;
if (session == NULL || to == NULL || (data == NULL && data_len > 0)) {
errno = EINVAL;
return -1;
}
if (omnisocket_session_begin_client_op(session, &client) != 0) {
return -1;
}
rc = kcp_client_send_binary_with_id(client, to, data, data_len, out_message_id);
pthread_mutex_lock(&session->mutex);
if (rc == 0) {
session->stats.send_calls += 1;
session->stats.send_bytes += (uint64_t) data_len;
} else {
session->stats.send_errors += 1;
}
omnisocket_session_sync_client_state_locked(session, client);
if (session->active_ops > 0) {
session->active_ops -= 1;
}
if (session->closing && session->active_ops == 0) {
pthread_cond_broadcast(&session->idle_cond);
}
pthread_mutex_unlock(&session->mutex);
return rc;
}
int omnisocket_session_recv(omnisocket_session_t *session, message_t *out_msg, int timeout_ms) {
kcp_client_t *client;
int rc;
if (session == NULL || out_msg == NULL) {
errno = EINVAL;
return -1;
}
if (omnisocket_session_begin_client_op(session, &client) != 0) {
return -1;
}
rc = kcp_client_receive_timed(client, out_msg, timeout_ms);
pthread_mutex_lock(&session->mutex);
if (rc == 0) {
session->stats.recv_calls += 1;
session->stats.recv_bytes += (uint64_t) out_msg->body_len;
} else if (rc == 1) {
session->stats.recv_timeouts += 1;
} else {
session->stats.recv_errors += 1;
}
omnisocket_session_sync_client_state_locked(session, client);
if (session->active_ops > 0) {
session->active_ops -= 1;
}
if (session->closing && session->active_ops == 0) {
pthread_cond_broadcast(&session->idle_cond);
}
pthread_mutex_unlock(&session->mutex);
return rc;
}
int omnisocket_session_recv_into(
omnisocket_session_t *session,
void *buffer,
size_t buffer_len,
kcp_client_recv_meta_t *out_meta,
int timeout_ms
) {
kcp_client_t *client;
int rc;
if (session == NULL || out_meta == NULL || (buffer == NULL && buffer_len > 0)) {
errno = EINVAL;
return -1;
}
if (omnisocket_session_begin_client_op(session, &client) != 0) {
return -1;
}
rc = kcp_client_receive_binary_into(client, buffer, buffer_len, out_meta, timeout_ms);
pthread_mutex_lock(&session->mutex);
if (rc == 0) {
session->stats.recv_calls += 1;
session->stats.recv_bytes += (uint64_t) out_meta->body_len;
} else if (rc == 1) {
session->stats.recv_timeouts += 1;
} else {
session->stats.recv_errors += 1;
}
omnisocket_session_sync_client_state_locked(session, client);
if (session->active_ops > 0) {
session->active_ops -= 1;
}
if (session->closing && session->active_ops == 0) {
pthread_cond_broadcast(&session->idle_cond);
}
pthread_mutex_unlock(&session->mutex);
return rc;
}
void omnisocket_session_stats_snapshot(omnisocket_session_t *session, omnisocket_session_stats_t *out_stats) {
if (session == NULL || out_stats == NULL) {
return;
}
pthread_mutex_lock(&session->mutex);
*out_stats = session->stats;
pthread_mutex_unlock(&session->mutex);
}
void omnisocket_session_kcp_stats_snapshot(omnisocket_session_t *session, omnisocket_session_kcp_stats_t *out_stats) {
kcp_runtime_stats_t runtime_stats;
if (session == NULL || out_stats == NULL) {
return;
}
memset(&runtime_stats, 0, sizeof(runtime_stats));
pthread_mutex_lock(&session->mutex);
if (session->client != NULL) {
kcp_client_runtime_stats_snapshot(session->client, &runtime_stats);
}
pthread_mutex_unlock(&session->mutex);
memset(out_stats, 0, sizeof(*out_stats));
out_stats->connected = runtime_stats.connected;
out_stats->conv = runtime_stats.conv;
out_stats->rto_ms = runtime_stats.rto_ms;
out_stats->srtt_ms = runtime_stats.srtt_ms;
out_stats->min_srtt_ms = runtime_stats.min_srtt_ms;
out_stats->srttvar_ms = runtime_stats.srttvar_ms;
out_stats->last_feedback_age_ms = runtime_stats.last_feedback_age_ms;
out_stats->snd_wnd = runtime_stats.snd_wnd;
out_stats->rmt_wnd = runtime_stats.rmt_wnd;
out_stats->inflight = runtime_stats.inflight;
out_stats->window_limit = runtime_stats.window_limit;
out_stats->window_pressure_pct = runtime_stats.window_pressure_pct;
out_stats->snd_queue = runtime_stats.snd_queue;
out_stats->rcv_queue = runtime_stats.rcv_queue;
out_stats->snd_buffer = runtime_stats.snd_buffer;
out_stats->out_segs_total = runtime_stats.out_segs_total;
out_stats->retrans_total = runtime_stats.retrans_total;
out_stats->fast_retrans_total = runtime_stats.fast_retrans_total;
out_stats->lost_total = runtime_stats.lost_total;
out_stats->repeat_total = runtime_stats.repeat_total;
out_stats->xmit_total = runtime_stats.xmit_total;
}
int omnisocket_udp_session_init(omnisocket_udp_session_t *session) {
int rc;
if (session == NULL) {
errno = EINVAL;
return -1;
}
memset(session, 0, sizeof(*session));
rc = pthread_mutex_init(&session->mutex, NULL);
if (rc != 0) {
errno = rc;
return -1;
}
rc = pthread_cond_init(&session->idle_cond, NULL);
if (rc != 0) {
pthread_mutex_destroy(&session->mutex);
errno = rc;
return -1;
}
return 0;
}
void omnisocket_udp_session_destroy(omnisocket_udp_session_t *session) {
if (session == NULL) {
return;
}
(void) omnisocket_udp_session_close(session);
pthread_cond_destroy(&session->idle_cond);
pthread_mutex_destroy(&session->mutex);
}
static int omnisocket_udp_session_begin_client_op(omnisocket_udp_session_t *session, udp_client_t **out_client) {
if (session == NULL || out_client == NULL) {
errno = EINVAL;
return -1;
}
pthread_mutex_lock(&session->mutex);
if (session->closing) {
pthread_mutex_unlock(&session->mutex);
errno = ECANCELED;
return -1;
}
if (session->client == NULL) {
pthread_mutex_unlock(&session->mutex);
errno = ENOTCONN;
return -1;
}
*out_client = session->client;
session->active_ops += 1;
pthread_mutex_unlock(&session->mutex);
return 0;
}
int omnisocket_udp_session_connect(
omnisocket_udp_session_t *session,
const char *server_addr,
const char *peer_id,
const char *bind_ip,
const char *bind_device,
int enable_timestamping
) {
udp_client_t *client;
if (session == NULL || server_addr == NULL || peer_id == NULL) {
errno = EINVAL;
return -1;
}
pthread_mutex_lock(&session->mutex);
while (session->closing) {
pthread_cond_wait(&session->idle_cond, &session->mutex);
}
if (session->client != NULL) {
pthread_mutex_unlock(&session->mutex);
errno = EISCONN;
return -1;
}
client = udp_client_dial_with_options(
server_addr,
peer_id,
bind_ip,
bind_device,
NULL,
NULL,
enable_timestamping
);
if (client == NULL) {
pthread_mutex_unlock(&session->mutex);
return -1;
}
session->client = client;
session->stats.connected = 1;
session->stats.registered = 1;
session->stats.last_server_error[0] = '\0';
pthread_mutex_unlock(&session->mutex);
return 0;
}
int omnisocket_udp_session_close(omnisocket_udp_session_t *session) {
udp_client_t *client;
if (session == NULL) {
errno = EINVAL;
return -1;
}
pthread_mutex_lock(&session->mutex);
while (session->closing) {
pthread_cond_wait(&session->idle_cond, &session->mutex);
}
client = session->client;
if (client != NULL) {
session->closing = 1;
session->client = NULL;
}
session->stats.connected = 0;
session->stats.registered = 0;
pthread_mutex_unlock(&session->mutex);
if (client != NULL) {
udp_client_close(client);
pthread_mutex_lock(&session->mutex);
while (session->active_ops > 0) {
pthread_cond_wait(&session->idle_cond, &session->mutex);
}
pthread_mutex_unlock(&session->mutex);
udp_client_free(client);
pthread_mutex_lock(&session->mutex);
session->closing = 0;
pthread_cond_broadcast(&session->idle_cond);
pthread_mutex_unlock(&session->mutex);
}
return 0;
}
int omnisocket_udp_session_send(omnisocket_udp_session_t *session, const char *to, const void *data, size_t data_len) {
udp_client_t *client;
int rc;
if (session == NULL || to == NULL || (data == NULL && data_len > 0)) {
errno = EINVAL;
return -1;
}
if (omnisocket_udp_session_begin_client_op(session, &client) != 0) {
return -1;
}
rc = udp_client_send_binary(client, to, data, data_len);
pthread_mutex_lock(&session->mutex);
if (rc == 0) {
session->stats.send_calls += 1;
session->stats.send_bytes += (uint64_t) data_len;
} else {
session->stats.send_errors += 1;
}
if (session->active_ops > 0) {
session->active_ops -= 1;
}
if (session->closing && session->active_ops == 0) {
pthread_cond_broadcast(&session->idle_cond);
}
pthread_mutex_unlock(&session->mutex);
return rc;
}
int omnisocket_udp_session_recv(omnisocket_udp_session_t *session, message_t *out_msg, int timeout_ms) {
udp_client_t *client;
int rc;
if (session == NULL || out_msg == NULL) {
errno = EINVAL;
return -1;
}
if (omnisocket_udp_session_begin_client_op(session, &client) != 0) {
return -1;
}
rc = udp_client_receive_timed(client, out_msg, timeout_ms);
pthread_mutex_lock(&session->mutex);
if (rc == 0) {
session->stats.recv_calls += 1;
session->stats.recv_bytes += (uint64_t) out_msg->body_len;
} else if (rc == 1) {
session->stats.recv_timeouts += 1;
} else {
session->stats.recv_errors += 1;
}
if (session->active_ops > 0) {
session->active_ops -= 1;
}
if (session->closing && session->active_ops == 0) {
pthread_cond_broadcast(&session->idle_cond);
}
pthread_mutex_unlock(&session->mutex);
return rc;
}
int omnisocket_udp_session_recv_into(
omnisocket_udp_session_t *session,
void *buffer,
size_t buffer_len,
udp_client_recv_meta_t *out_meta,
int timeout_ms
) {
udp_client_t *client;
int rc;
if (session == NULL || out_meta == NULL || (buffer == NULL && buffer_len > 0)) {
errno = EINVAL;
return -1;
}
if (omnisocket_udp_session_begin_client_op(session, &client) != 0) {
return -1;
}
rc = udp_client_receive_into(client, buffer, buffer_len, out_meta, timeout_ms);
pthread_mutex_lock(&session->mutex);
if (rc == 0) {
session->stats.recv_calls += 1;
session->stats.recv_bytes += (uint64_t) out_meta->body_len;
} else if (rc == 1) {
session->stats.recv_timeouts += 1;
} else {
session->stats.recv_errors += 1;
}
if (session->active_ops > 0) {
session->active_ops -= 1;
}
if (session->closing && session->active_ops == 0) {
pthread_cond_broadcast(&session->idle_cond);
}
pthread_mutex_unlock(&session->mutex);
return rc;
}
void omnisocket_udp_session_stats_snapshot(omnisocket_udp_session_t *session, omnisocket_session_stats_t *out_stats) {
if (session == NULL || out_stats == NULL) {
return;
}
pthread_mutex_lock(&session->mutex);
*out_stats = session->stats;
pthread_mutex_unlock(&session->mutex);
}

View File

@@ -0,0 +1,118 @@
#ifndef OMNISOCKET_PY_CLIENT_H
#define OMNISOCKET_PY_CLIENT_H
#include "peer_kcp_client.h"
#include "peer_udp_client.h"
typedef struct omnisocket_session_stats {
uint64_t send_calls;
uint64_t send_bytes;
uint64_t send_errors;
uint64_t recv_calls;
uint64_t recv_bytes;
uint64_t recv_timeouts;
uint64_t recv_errors;
int connected;
int registered;
char last_server_error[256];
} omnisocket_session_stats_t;
typedef struct omnisocket_session_kcp_stats {
int connected;
uint32_t conv;
uint32_t rto_ms;
int32_t srtt_ms;
int32_t min_srtt_ms;
int32_t srttvar_ms;
uint32_t last_feedback_age_ms;
uint32_t snd_wnd;
uint32_t rmt_wnd;
uint32_t inflight;
uint32_t window_limit;
double window_pressure_pct;
uint32_t snd_queue;
uint32_t rcv_queue;
uint32_t snd_buffer;
uint64_t out_segs_total;
uint64_t retrans_total;
uint64_t fast_retrans_total;
uint64_t lost_total;
uint64_t repeat_total;
uint32_t xmit_total;
} omnisocket_session_kcp_stats_t;
typedef struct omnisocket_session {
pthread_mutex_t mutex;
pthread_cond_t idle_cond;
kcp_client_t *client;
size_t active_ops;
int closing;
omnisocket_session_stats_t stats;
} omnisocket_session_t;
typedef struct omnisocket_udp_session {
pthread_mutex_t mutex;
pthread_cond_t idle_cond;
udp_client_t *client;
size_t active_ops;
int closing;
omnisocket_session_stats_t stats;
} omnisocket_udp_session_t;
int omnisocket_session_init(omnisocket_session_t *session);
void omnisocket_session_destroy(omnisocket_session_t *session);
int omnisocket_session_connect(
omnisocket_session_t *session,
const char *server_addr,
const char *relay_via,
const char *peer_id,
const char *bind_ip,
const char *bind_device,
const kcp_conn_options_t *options,
int stats_interval_ms
);
int omnisocket_session_close(omnisocket_session_t *session);
int omnisocket_session_send(omnisocket_session_t *session, const char *to, const void *data, size_t data_len);
int omnisocket_session_send_with_id(
omnisocket_session_t *session,
const char *to,
const void *data,
size_t data_len,
uint64_t *out_message_id
);
int omnisocket_session_recv(omnisocket_session_t *session, message_t *out_msg, int timeout_ms);
int omnisocket_session_recv_into(
omnisocket_session_t *session,
void *buffer,
size_t buffer_len,
kcp_client_recv_meta_t *out_meta,
int timeout_ms
);
void omnisocket_session_stats_snapshot(omnisocket_session_t *session, omnisocket_session_stats_t *out_stats);
void omnisocket_session_kcp_stats_snapshot(omnisocket_session_t *session, omnisocket_session_kcp_stats_t *out_stats);
int omnisocket_udp_session_init(omnisocket_udp_session_t *session);
void omnisocket_udp_session_destroy(omnisocket_udp_session_t *session);
int omnisocket_udp_session_connect(
omnisocket_udp_session_t *session,
const char *server_addr,
const char *peer_id,
const char *bind_ip,
const char *bind_device,
int enable_timestamping
);
int omnisocket_udp_session_close(omnisocket_udp_session_t *session);
int omnisocket_udp_session_send(omnisocket_udp_session_t *session, const char *to, const void *data, size_t data_len);
int omnisocket_udp_session_recv(omnisocket_udp_session_t *session, message_t *out_msg, int timeout_ms);
int omnisocket_udp_session_recv_into(
omnisocket_udp_session_t *session,
void *buffer,
size_t buffer_len,
udp_client_recv_meta_t *out_meta,
int timeout_ms
);
void omnisocket_udp_session_stats_snapshot(omnisocket_udp_session_t *session, omnisocket_session_stats_t *out_stats);
#endif