410 lines
12 KiB
C
410 lines
12 KiB
C
#define PY_SSIZE_T_CLEAN
|
|
#include <Python.h>
|
|
|
|
#include "omnisocket_client.h"
|
|
|
|
typedef struct PyOmniSession {
|
|
PyObject_HEAD
|
|
omnisocket_session_t session;
|
|
} PyOmniSession;
|
|
|
|
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."
|
|
);
|
|
|
|
PyDoc_STRVAR(
|
|
PyOmniSession_kcp_metrics_doc,
|
|
"kcp_metrics() -> dict\n"
|
|
"\n"
|
|
"Return a snapshot of low-level KCP metrics for the current session."
|
|
);
|
|
|
|
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_recv(PyOmniSession *self, PyObject *args, PyObject *kwargs) {
|
|
int timeout_ms = -1;
|
|
int rc;
|
|
message_t msg;
|
|
PyObject *body = NULL;
|
|
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);
|
|
}
|
|
|
|
body = PyBytes_FromStringAndSize((const char *) msg.body, (Py_ssize_t) msg.body_len);
|
|
if (body == NULL) {
|
|
protocol_message_clear(&msg);
|
|
return NULL;
|
|
}
|
|
result = Py_BuildValue("(siO)", msg.from, (int) msg.type, body);
|
|
Py_DECREF(body);
|
|
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 = Py_BuildValue(
|
|
"{s:s,s:s,s:s,s:i,s:K,s:K}",
|
|
"from",
|
|
meta.from,
|
|
"to",
|
|
meta.to,
|
|
"file_name",
|
|
meta.file_name,
|
|
"msg_type",
|
|
(int) meta.type,
|
|
"message_id",
|
|
(unsigned long long) meta.id,
|
|
"body_len",
|
|
(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 Py_BuildValue(
|
|
"{s:K,s:K,s:K,s:K,s:K,s:K,s:K,s:i}",
|
|
"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
|
|
);
|
|
}
|
|
|
|
static PyObject *PyOmniSession_kcp_metrics(PyOmniSession *self, PyObject *Py_UNUSED(ignored)) {
|
|
omnisocket_session_kcp_metrics_t metrics;
|
|
|
|
memset(&metrics, 0, sizeof(metrics));
|
|
if (omnisocket_session_kcp_metrics_snapshot(&self->session, &metrics) != 0) {
|
|
return PyErr_SetFromErrno(PyExc_OSError);
|
|
}
|
|
|
|
return Py_BuildValue(
|
|
"{s:i,s:i,s:I,s:s,s:s,s:I,s:i,s:i,s:K,s:K,s:K,s:K,s:K,s:K,s:K,s:K,s:K,s:K,s:K,s:K,s:K,s:K,s:K}",
|
|
"connected",
|
|
metrics.connected,
|
|
"has_conv",
|
|
metrics.has_conv,
|
|
"conv",
|
|
metrics.conv,
|
|
"local_addr",
|
|
metrics.local_addr,
|
|
"remote_addr",
|
|
metrics.remote_addr,
|
|
"rto_ms",
|
|
metrics.rto_ms,
|
|
"srtt_ms",
|
|
metrics.srtt_ms,
|
|
"srttvar_ms",
|
|
metrics.srttvar_ms,
|
|
"bytes_sent",
|
|
(unsigned long long) metrics.bytes_sent,
|
|
"bytes_received",
|
|
(unsigned long long) metrics.bytes_received,
|
|
"in_pkts",
|
|
(unsigned long long) metrics.in_pkts,
|
|
"out_pkts",
|
|
(unsigned long long) metrics.out_pkts,
|
|
"in_segs",
|
|
(unsigned long long) metrics.in_segs,
|
|
"out_segs",
|
|
(unsigned long long) metrics.out_segs,
|
|
"retrans_segs",
|
|
(unsigned long long) metrics.retrans_segs,
|
|
"fast_retrans_segs",
|
|
(unsigned long long) metrics.fast_retrans_segs,
|
|
"early_retrans_segs",
|
|
(unsigned long long) metrics.early_retrans_segs,
|
|
"lost_segs",
|
|
(unsigned long long) metrics.lost_segs,
|
|
"repeat_segs",
|
|
(unsigned long long) metrics.repeat_segs,
|
|
"in_errs",
|
|
(unsigned long long) metrics.in_errs,
|
|
"kcp_in_errs",
|
|
(unsigned long long) metrics.kcp_in_errs,
|
|
"ring_buffer_snd_queue",
|
|
(unsigned long long) metrics.ring_buffer_snd_queue,
|
|
"ring_buffer_rcv_queue",
|
|
(unsigned long long) metrics.ring_buffer_rcv_queue,
|
|
"ring_buffer_snd_buffer",
|
|
(unsigned long long) metrics.ring_buffer_snd_buffer
|
|
);
|
|
}
|
|
|
|
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},
|
|
{"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_metrics", (PyCFunction) PyOmniSession_kcp_metrics, METH_NOARGS, PyOmniSession_kcp_metrics_doc},
|
|
{NULL, NULL, 0, NULL}
|
|
};
|
|
|
|
static PyTypeObject PyOmniSessionType = {
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|