fix(signalr): use custom msgpack to encode/decode

This commit is contained in:
MingxuanGame
2025-07-30 06:01:17 +00:00
parent a53c63a33a
commit 4a5a1c86c6
17 changed files with 1191 additions and 892 deletions

View File

@@ -0,0 +1,315 @@
use crate::APIMod;
use chrono::{TimeZone, Utc};
use pyo3::types::PyDict;
use pyo3::{prelude::*, IntoPyObjectExt};
use std::collections::HashMap;
use std::io::Read;
pub fn read_object(
py: Python<'_>,
cursor: &mut std::io::Cursor<&[u8]>,
api_mod: bool,
) -> PyResult<PyObject> {
match rmp::decode::read_marker(cursor) {
Ok(marker) => match marker {
rmp::Marker::Null => Ok(py.None()),
rmp::Marker::FixPos(val) => Ok(val.into_pyobject(py)?.into_any().unbind()),
rmp::Marker::FixNeg(val) => Ok(val.into_pyobject(py)?.into_any().unbind()),
rmp::Marker::U8 => {
let mut buf = [0u8; 1];
cursor.read_exact(&mut buf).map_err(to_py_err)?;
Ok(buf[0].into_pyobject(py)?.into_any().unbind())
}
rmp::Marker::U16 => {
let mut buf = [0u8; 2];
cursor.read_exact(&mut buf).map_err(to_py_err)?;
let val = u16::from_be_bytes(buf);
Ok(val.into_pyobject(py)?.into_any().unbind())
}
rmp::Marker::U32 => {
let mut buf = [0u8; 4];
cursor.read_exact(&mut buf).map_err(to_py_err)?;
let val = u32::from_be_bytes(buf);
Ok(val.into_pyobject(py)?.into_any().unbind())
}
rmp::Marker::U64 => {
let mut buf = [0u8; 8];
cursor.read_exact(&mut buf).map_err(to_py_err)?;
let val = u64::from_be_bytes(buf);
Ok(val.into_pyobject(py)?.into_any().unbind())
}
rmp::Marker::I8 => {
let mut buf = [0u8; 1];
cursor.read_exact(&mut buf).map_err(to_py_err)?;
let val = i8::from_be_bytes(buf);
Ok(val.into_pyobject(py)?.into_any().unbind())
}
rmp::Marker::I16 => {
let mut buf = [0u8; 2];
cursor.read_exact(&mut buf).map_err(to_py_err)?;
let val = i16::from_be_bytes(buf);
Ok(val.into_pyobject(py)?.into_any().unbind())
}
rmp::Marker::I32 => {
let mut buf = [0u8; 4];
cursor.read_exact(&mut buf).map_err(to_py_err)?;
let val = i32::from_be_bytes(buf);
Ok(val.into_pyobject(py)?.into_any().unbind())
}
rmp::Marker::I64 => {
let mut buf = [0u8; 8];
cursor.read_exact(&mut buf).map_err(to_py_err)?;
let val = i64::from_be_bytes(buf);
Ok(val.into_pyobject(py)?.into_any().unbind())
}
rmp::Marker::Bin8 => {
let mut buf = [0u8; 1];
cursor.read_exact(&mut buf).map_err(to_py_err)?;
let len = buf[0] as u32;
let mut data = vec![0u8; len as usize];
cursor.read_exact(&mut data).map_err(to_py_err)?;
Ok(data.into_pyobject(py)?.into_any().unbind())
}
rmp::Marker::Bin16 => {
let mut buf = [0u8; 2];
cursor.read_exact(&mut buf).map_err(to_py_err)?;
let len = u16::from_be_bytes(buf) as u32;
let mut data = vec![0u8; len as usize];
cursor.read_exact(&mut data).map_err(to_py_err)?;
Ok(data.into_pyobject(py)?.into_any().unbind())
}
rmp::Marker::Bin32 => {
let mut buf = [0u8; 4];
cursor.read_exact(&mut buf).map_err(to_py_err)?;
let len = u32::from_be_bytes(buf);
let mut data = vec![0u8; len as usize];
cursor.read_exact(&mut data).map_err(to_py_err)?;
Ok(data.into_pyobject(py)?.into_any().unbind())
}
rmp::Marker::True => Ok(true.into_py_any(py)?),
rmp::Marker::False => Ok(false.into_py_any(py)?),
rmp::Marker::FixStr(len) => read_string(py, cursor, len as u32),
rmp::Marker::Str8 => {
let mut buf = [0u8; 1];
cursor.read_exact(&mut buf).map_err(to_py_err)?;
let len = buf[0] as u32;
read_string(py, cursor, len)
}
rmp::Marker::Str16 => {
let mut buf = [0u8; 2];
cursor.read_exact(&mut buf).map_err(to_py_err)?;
let len = u16::from_be_bytes(buf) as u32;
read_string(py, cursor, len)
}
rmp::Marker::Str32 => {
let mut buf = [0u8; 4];
cursor.read_exact(&mut buf).map_err(to_py_err)?;
let len = u32::from_be_bytes(buf);
read_string(py, cursor, len)
}
rmp::Marker::FixArray(len) => read_array(py, cursor, len as u32, api_mod),
rmp::Marker::Array16 => {
let mut buf = [0u8; 2];
cursor.read_exact(&mut buf).map_err(to_py_err)?;
let len = u16::from_be_bytes(buf) as u32;
read_array(py, cursor, len, api_mod)
}
rmp::Marker::Array32 => {
let mut buf = [0u8; 4];
cursor.read_exact(&mut buf).map_err(to_py_err)?;
let len = u32::from_be_bytes(buf);
read_array(py, cursor, len, api_mod)
}
rmp::Marker::FixMap(len) => read_map(py, cursor, len as u32),
rmp::Marker::Map16 => {
let mut buf = [0u8; 2];
cursor.read_exact(&mut buf).map_err(to_py_err)?;
let len = u16::from_be_bytes(buf) as u32;
read_map(py, cursor, len)
}
rmp::Marker::Map32 => {
let mut buf = [0u8; 4];
cursor.read_exact(&mut buf).map_err(to_py_err)?;
let len = u32::from_be_bytes(buf);
read_map(py, cursor, len)
}
rmp::Marker::F32 => {
let mut buf = [0u8; 4];
cursor.read_exact(&mut buf).map_err(to_py_err)?;
let val = f32::from_be_bytes(buf);
Ok(val.into_pyobject(py)?.into_any().unbind())
}
rmp::Marker::F64 => {
let mut buf = [0u8; 8];
cursor.read_exact(&mut buf).map_err(to_py_err)?;
let val = f64::from_be_bytes(buf);
Ok(val.into_pyobject(py)?.into_any().unbind())
}
rmp::Marker::FixExt1 => read_ext(py, cursor, 1),
rmp::Marker::FixExt2 => read_ext(py, cursor, 2),
rmp::Marker::FixExt4 => read_ext(py, cursor, 4),
rmp::Marker::FixExt8 => read_ext(py, cursor, 8),
rmp::Marker::FixExt16 => read_ext(py, cursor, 16),
rmp::Marker::Ext8 => {
let mut buf = [0u8; 1];
cursor.read_exact(&mut buf).map_err(to_py_err)?;
let len = buf[0] as u32;
read_ext(py, cursor, len)
}
rmp::Marker::Ext16 => {
let mut buf = [0u8; 2];
cursor.read_exact(&mut buf).map_err(to_py_err)?;
let len = u16::from_be_bytes(buf) as u32;
read_ext(py, cursor, len)
}
rmp::Marker::Ext32 => {
let mut buf = [0u8; 4];
cursor.read_exact(&mut buf).map_err(to_py_err)?;
let len = u32::from_be_bytes(buf);
read_ext(py, cursor, len)
}
_ => Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
"Unsupported MessagePack marker",
)),
},
Err(e) => Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
"Failed to read marker: {:?}",
e
))),
}
}
fn read_string(
py: Python<'_>,
cursor: &mut std::io::Cursor<&[u8]>,
len: u32,
) -> PyResult<PyObject> {
let mut buf = vec![0u8; len as usize];
cursor.read_exact(&mut buf).map_err(to_py_err)?;
let s = String::from_utf8(buf)
.map_err(|_| PyErr::new::<pyo3::exceptions::PyUnicodeDecodeError, _>("Invalid UTF-8"))?;
Ok(s.into_pyobject(py)?.into_any().unbind())
}
fn read_array(
py: Python,
cursor: &mut std::io::Cursor<&[u8]>,
len: u32,
api_mod: bool,
) -> PyResult<PyObject> {
let mut items = Vec::new();
let array_len = if api_mod { len * 2 } else { len };
let dict = PyDict::new(py);
let mut i = 0;
if len == 2 && !api_mod {
// 姑且这样判断列表长度为2第一个元素为长度为2的字符串api_mod 模式未启用(不存在嵌套 APIMod
let obj1 = read_object(py, cursor, false)?;
if obj1.extract::<String>(py).map_or(false, |k| k.len() == 2) {
let obj2 = read_object(py, cursor, true)?;
return Ok(APIMod {
acronym: obj1.extract::<String>(py)?,
settings: obj2.extract::<HashMap<String, PyObject>>(py)?,
}
.into_pyobject(py)?
.into_any()
.unbind());
} else {
items.push(obj1);
i += 1;
}
}
while i < array_len {
if api_mod && i % 2 == 0 {
let key = read_object(py, cursor, false)?;
let value = read_object(py, cursor, false)?;
dict.set_item(key, value)?;
i += 2;
} else {
let item = read_object(py, cursor, api_mod)?;
items.push(item);
i += 1;
}
}
if api_mod {
return Ok(dict.into_pyobject(py)?.into_any().unbind());
} else {
Ok(items.into_pyobject(py)?.into_any().unbind())
}
}
fn read_map(py: Python, cursor: &mut std::io::Cursor<&[u8]>, len: u32) -> PyResult<PyObject> {
let mut pairs = Vec::new();
for _ in 0..len {
let key = read_object(py, cursor, false)?;
let value = read_object(py, cursor, false)?;
pairs.push((key, value));
}
let dict = PyDict::new(py);
for (key, value) in pairs {
dict.set_item(key, value)?;
}
return Ok(dict.into_pyobject(py)?.into_any().unbind());
}
fn to_py_err(err: std::io::Error) -> PyErr {
PyErr::new::<pyo3::exceptions::PyIOError, _>(format!("IO error: {}", err))
}
fn read_ext(py: Python, cursor: &mut std::io::Cursor<&[u8]>, len: u32) -> PyResult<PyObject> {
// Read the extension type
let mut type_buf = [0u8; 1];
cursor.read_exact(&mut type_buf).map_err(to_py_err)?;
let ext_type = type_buf[0] as i8;
// Read the extension data
let mut data = vec![0u8; len as usize];
cursor.read_exact(&mut data).map_err(to_py_err)?;
// Handle timestamp extension (type = -1)
if ext_type == -1 {
read_timestamp(py, &data)
} else {
// For other extension types, return as bytes or handle as needed
Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
"Unsupported extension type: {}",
ext_type
)))
}
}
fn read_timestamp(py: Python, data: &[u8]) -> PyResult<PyObject> {
let (secs, nsec) = match data.len() {
4 => {
// timestamp32: 4-byte big endian seconds
let secs = u32::from_be_bytes([data[0], data[1], data[2], data[3]]) as u64;
(secs, 0u32)
}
8 => {
// timestamp64: 8-byte packed => upper 34 bits nsec, lower 30 bits secs
let packed = u64::from_be_bytes([
data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
]);
let nsec = (packed >> 34) as u32;
let secs = packed & 0x3FFFFFFFF; // lower 34 bits
(secs, nsec)
}
12 => {
// timestamp96: 12 bytes = 4-byte nsec + 8-byte seconds signed
let nsec = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
let secs = i64::from_be_bytes([
data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11],
]) as u64;
(secs, nsec)
}
_ => {
return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
"Invalid timestamp data length: {}",
data.len()
)));
}
};
let time = Utc.timestamp_opt(secs as i64, nsec).single();
Ok(time.into_pyobject(py)?.into_any().unbind())
}

View File

@@ -0,0 +1,132 @@
use crate::APIMod;
use chrono::{DateTime, Utc};
use pyo3::prelude::{PyAnyMethods, PyDictMethods, PyListMethods, PyStringMethods};
use pyo3::types::{PyBool, PyBytes, PyDateTime, PyDict, PyFloat, PyInt, PyList, PyNone, PyString};
use pyo3::{Bound, PyAny, PyRef, Python};
use std::io::Write;
fn write_list(buf: &mut Vec<u8>, obj: &Bound<'_, PyList>) {
rmp::encode::write_array_len(buf, obj.len() as u32).unwrap();
for item in obj.iter() {
write_object(buf, &item);
}
}
fn write_string(buf: &mut Vec<u8>, obj: &Bound<'_, PyString>) {
let s = obj.to_string_lossy();
rmp::encode::write_str(buf, &s).unwrap();
}
fn write_integer(buf: &mut Vec<u8>, obj: &Bound<'_, PyInt>) {
if let Ok(val) = obj.extract::<i32>() {
rmp::encode::write_i32(buf, val).unwrap();
} else if let Ok(val) = obj.extract::<i64>() {
rmp::encode::write_i64(buf, val).unwrap();
} else {
panic!("Unsupported integer type");
}
}
fn write_float(buf: &mut Vec<u8>, obj: &Bound<'_, PyAny>) {
if let Ok(val) = obj.extract::<f32>() {
rmp::encode::write_f32(buf, val).unwrap();
} else if let Ok(val) = obj.extract::<f64>() {
rmp::encode::write_f64(buf, val).unwrap();
} else {
panic!("Unsupported float type");
}
}
fn write_bool(buf: &mut Vec<u8>, obj: &Bound<'_, PyBool>) {
if let Ok(b) = obj.extract::<bool>() {
rmp::encode::write_bool(buf, b).unwrap();
} else {
panic!("Unsupported boolean type");
}
}
fn write_bin(buf: &mut Vec<u8>, obj: &Bound<'_, PyBytes>) {
if let Ok(bytes) = obj.extract::<Vec<u8>>() {
rmp::encode::write_bin(buf, &bytes).unwrap();
} else {
panic!("Unsupported binary type");
}
}
fn write_hashmap(buf: &mut Vec<u8>, obj: &Bound<'_, PyDict>) {
rmp::encode::write_map_len(buf, obj.len() as u32).unwrap();
for (key, value) in obj.iter() {
write_object(buf, &key);
write_object(buf, &value);
}
}
fn write_nil(buf: &mut Vec<u8>){
rmp::encode::write_nil(buf).unwrap();
}
// https://github.com/ppy/osu/blob/3dced3/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs
fn write_api_mod(buf: &mut Vec<u8>, api_mod: PyRef<APIMod>) {
rmp::encode::write_array_len(buf, 2).unwrap();
rmp::encode::write_str(buf, &api_mod.acronym).unwrap();
rmp::encode::write_array_len(buf, api_mod.settings.len() as u32).unwrap();
for (k, v) in api_mod.settings.iter() {
rmp::encode::write_str(buf, k).unwrap();
Python::with_gil(|py| write_object(buf, &v.bind(py)));
}
}
fn write_datetime(buf: &mut Vec<u8>, obj: &Bound<'_, PyDateTime>) {
if let Ok(dt) = obj.extract::<DateTime<Utc>>() {
let secs = dt.timestamp();
let nsec = dt.timestamp_subsec_nanos();
write_timestamp(buf, secs, nsec);
} else {
panic!("Unsupported datetime type. Check your input, timezone is needed.");
}
}
fn write_timestamp(wr: &mut Vec<u8>, secs: i64, nsec: u32) {
let buf: Vec<u8> = if nsec == 0 && secs >= 0 && secs <= u32::MAX as i64 {
// timestamp32: 4-byte big endian seconds
secs.to_be_bytes()[4..].to_vec()
} else if secs >= -(1 << 34) && secs < (1 << 34) {
// timestamp64: 8-byte packed => upper 34 bits nsec, lower 34 bits secs
let packed = ((nsec as u64) << 34) | (secs as u64 & ((1 << 34) - 1));
packed.to_be_bytes().to_vec()
} else {
// timestamp96: 12 bytes = 4-byte nsec + 8-byte seconds signed
let mut v = Vec::with_capacity(12);
v.extend_from_slice(&nsec.to_be_bytes());
v.extend_from_slice(&secs.to_be_bytes());
v
};
rmp::encode::write_ext_meta(wr, buf.len() as u32, -1).unwrap();
wr.write_all(&buf).unwrap();
}
pub fn write_object(buf: &mut Vec<u8>, obj: &Bound<'_, PyAny>) {
if let Ok(list) = obj.downcast::<PyList>() {
write_list(buf, list);
} else if let Ok(string) = obj.downcast::<PyString>() {
write_string(buf, string);
} else if let Ok(integer) = obj.downcast::<PyInt>() {
write_integer(buf, integer);
} else if let Ok(float) = obj.downcast::<PyFloat>() {
write_float(buf, float);
} else if let Ok(boolean) = obj.downcast::<PyBool>() {
write_bool(buf, boolean);
} else if let Ok(bytes) = obj.downcast::<PyBytes>() {
write_bin(buf, bytes);
} else if let Ok(dict) = obj.downcast::<PyDict>() {
write_hashmap(buf, dict);
} else if let Ok(_none) = obj.downcast::<PyNone>() {
write_nil(buf);
} else if let Ok(datetime) = obj.downcast::<PyDateTime>() {
write_datetime(buf, datetime);
} else if let Ok(api_mod) = obj.extract::<PyRef<APIMod>>() {
write_api_mod(buf, api_mod);
} else {
panic!("Unsupported type");
}
}

View File

@@ -0,0 +1,51 @@
mod decode;
mod encode;
use pyo3::prelude::*;
use std::collections::HashMap;
#[pyclass]
struct APIMod {
#[pyo3(get, set)]
acronym: String,
#[pyo3(get, set)]
settings: HashMap<String, PyObject>,
}
#[pymethods]
impl APIMod {
#[new]
fn new(acronym: String, settings: HashMap<String, PyObject>) -> Self {
APIMod { acronym, settings }
}
fn __repr__(&self) -> String {
format!(
"APIMod(acronym='{}', settings={:?})",
self.acronym, self.settings
)
}
}
#[pyfunction]
#[pyo3(name = "encode")]
fn encode_py(obj: &Bound<'_, PyAny>) -> PyResult<Vec<u8>> {
let mut buf = Vec::new();
encode::write_object(&mut buf, obj);
Ok(buf)
}
#[pyfunction]
#[pyo3(name = "decode")]
fn decode_py(py: Python, data: &[u8]) -> PyResult<PyObject> {
let mut cursor = std::io::Cursor::new(data);
decode::read_object(py, &mut cursor, false)
}
#[pymodule]
fn msgpack_lazer_api(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(encode_py, m)?)?;
m.add_function(wrap_pyfunction!(decode_py, m)?)?;
m.add_class::<APIMod>()?;
Ok(())
}