首次提交
This commit is contained in:
commit
5eb60dbbc6
|
@ -0,0 +1,10 @@
|
|||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
|
@ -0,0 +1,16 @@
|
|||
[package]
|
||||
name = "bili-live-danmaku-rs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.3.3"
|
||||
brotli = "3.4.0"
|
||||
futures = "0.3.30"
|
||||
serde = { version = "1.0.195", features = ["derive"] }
|
||||
serde_bytes = "0.11.14"
|
||||
serde_json = "1.0.111"
|
||||
strum_macros = "0.25.3"
|
||||
thiserror = "1.0.56"
|
|
@ -0,0 +1,13 @@
|
|||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
Version 2, December 2004
|
||||
|
||||
Copyright (C) 2024 Eigeen <dengyk2002@qq.com>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim or modified
|
||||
copies of this license document, and changing it is allowed as long
|
||||
as the name is changed.
|
||||
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
|
@ -0,0 +1,76 @@
|
|||
use std::io;
|
||||
use serde::Serialize;
|
||||
use strum_macros::{AsRefStr, Display, EnumString};
|
||||
use thiserror::Error;
|
||||
use crate::core::packet::{Packet, PacketError};
|
||||
|
||||
// #[allow(unused_variables, dead_code)]
|
||||
#[derive(Error, Debug)]
|
||||
pub enum MessageError {
|
||||
#[error("{0}")]
|
||||
InvalidPacket(#[from] PacketError),
|
||||
#[error("no message available")]
|
||||
NoMessage,
|
||||
#[error("unknown message error")]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// 命令名对照表,用于对应解析命令名称
|
||||
// #[allow(unused_variables, dead_code)]
|
||||
#[derive(Debug, PartialEq, Display, AsRefStr, EnumString)]
|
||||
pub enum Command {
|
||||
Undefined,
|
||||
#[strum(serialize = "DANMU_MSG")]
|
||||
DanmuMsg,
|
||||
#[strum(serialize = "SUPER_CHAT_MESSAGE")]
|
||||
SuperChatMessage,
|
||||
#[strum(serialize = "SUPER_CHAT_MESSAGE_DELETE")]
|
||||
SuperChatMessageDelete,
|
||||
#[strum(serialize = "SEND_GIFT")]
|
||||
SendGift,
|
||||
}
|
||||
|
||||
impl Default for Command {
|
||||
fn default() -> Self {
|
||||
Self::Undefined
|
||||
}
|
||||
}
|
||||
|
||||
impl Command {
|
||||
pub fn from_str(cmd: &str) -> Self {
|
||||
cmd.parse().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables, dead_code)]
|
||||
#[derive(Serialize, Debug)]
|
||||
pub enum Message {
|
||||
DanmuMsg {},
|
||||
SuperChatMessage,
|
||||
SuperChatMessageDelete,
|
||||
}
|
||||
|
||||
impl Message {
|
||||
fn deserialize_json() {}
|
||||
|
||||
fn parse_packet_skip_err(single_packet: &Packet) -> Message {}
|
||||
|
||||
/// 从 Packet 中提取 Message
|
||||
///
|
||||
/// 只有 Json 格式的非业务消息会被提取,即心跳包回复,认证请求回复等不会被解析。
|
||||
pub fn from_packet(packet: &mut Packet) -> Result<Vec<Self>, MessageError> {
|
||||
let mut packets = packet.get_body_packets()?;
|
||||
if packets.len() == 0 {
|
||||
return Err(MessageError::NoMessage);
|
||||
};
|
||||
|
||||
let messages = packets.iter().map(|packet| {
|
||||
Self::parse_packet_skip_err(packet)
|
||||
}).collect();
|
||||
if messages.len() == 0 {
|
||||
Err(MessageError::NoMessage)
|
||||
} else {
|
||||
Ok(messages)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
pub mod message;
|
||||
pub mod liveroom;
|
||||
pub mod packet;
|
|
@ -0,0 +1,321 @@
|
|||
use std::io;
|
||||
|
||||
use bincode::Options;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{str_to_u8_array, utils};
|
||||
|
||||
// #[allow(unused_variables, dead_code)]
|
||||
#[derive(Error, Debug)]
|
||||
pub enum PacketError {
|
||||
#[error("deserialize packet header error")]
|
||||
InvalidHeader,
|
||||
#[error("operation code {0} is unsupported")]
|
||||
UnsupportedOpCode(u32),
|
||||
#[error("protocol version {0} is unsupported")]
|
||||
UnsupportedProtoVer(u16),
|
||||
#[error("{0}")]
|
||||
DecompressError(#[from] io::Error),
|
||||
#[error("parse packet error")]
|
||||
UnpackError,
|
||||
#[error("unknown packet error")]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// 操作码
|
||||
#[allow(unused_variables)]
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
pub enum OperationCode {
|
||||
Unknown = 0,
|
||||
Heartbeat = 2,
|
||||
HeartbeatResponse = 3,
|
||||
Message = 5,
|
||||
Auth = 7,
|
||||
AuthResponse = 8,
|
||||
}
|
||||
|
||||
/// 协议版本
|
||||
///
|
||||
/// 0 业务通信消息,无压缩 \
|
||||
/// 1 连接通信消息,无压缩 (比如心跳包、认证包等与业务无关的数据包) \
|
||||
/// 2 无压缩消息 \
|
||||
/// 3 Brotli 压缩
|
||||
#[allow(unused_variables)]
|
||||
pub enum ProtocolVersion {
|
||||
Unknown = -1,
|
||||
Business = 0,
|
||||
Connection = 1,
|
||||
Normal = 2,
|
||||
Brotli = 3,
|
||||
}
|
||||
|
||||
/// 数据包头
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
||||
struct PacketHeader {
|
||||
total_size: u32,
|
||||
header_size: u16,
|
||||
protocol_version: u16,
|
||||
operation_code: u32,
|
||||
sequence: u32,
|
||||
}
|
||||
|
||||
const PACKET_HEADER_SIZE: usize = std::mem::size_of::<PacketHeader>();
|
||||
|
||||
impl PacketHeader {
|
||||
fn new(payload_size: usize, operation_code: OperationCode) -> Self {
|
||||
PacketHeader {
|
||||
total_size: payload_size as u32 + PACKET_HEADER_SIZE as u32,
|
||||
header_size: PACKET_HEADER_SIZE as u16,
|
||||
protocol_version: 0,
|
||||
operation_code: operation_code as u32,
|
||||
sequence: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_bytes(bytes: &[u8]) -> Result<PacketHeader, PacketError> {
|
||||
let result = bincode::DefaultOptions::new()
|
||||
.with_big_endian()
|
||||
.with_fixint_encoding()
|
||||
.allow_trailing_bytes()
|
||||
.deserialize(&bytes[..PACKET_HEADER_SIZE]);
|
||||
match result {
|
||||
Ok(res) => Ok(res),
|
||||
Err(_) => Err(PacketError::InvalidHeader),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn operation_code(&self) -> OperationCode {
|
||||
match self.operation_code {
|
||||
2 => OperationCode::Heartbeat,
|
||||
3 => OperationCode::HeartbeatResponse,
|
||||
5 => OperationCode::Message,
|
||||
7 => OperationCode::Auth,
|
||||
8 => OperationCode::AuthResponse,
|
||||
_ => OperationCode::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
fn protocol_version(&self) -> ProtocolVersion {
|
||||
match self.protocol_version {
|
||||
0 => ProtocolVersion::Business,
|
||||
1 => ProtocolVersion::Connection,
|
||||
2 => ProtocolVersion::Normal,
|
||||
3 => ProtocolVersion::Brotli,
|
||||
_ => ProtocolVersion::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct AuthPayload {
|
||||
uid: u64,
|
||||
#[serde(rename = "roomid")]
|
||||
room_id: u64,
|
||||
#[serde(rename = "protover")]
|
||||
proto_ver: u8,
|
||||
buvid: String,
|
||||
platform: String,
|
||||
#[serde(rename = "type")]
|
||||
auth_type: u8,
|
||||
key: String,
|
||||
}
|
||||
|
||||
impl Default for AuthPayload {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
uid: 0,
|
||||
room_id: 0,
|
||||
proto_ver: 3,
|
||||
buvid: "".to_owned(),
|
||||
platform: "web".to_owned(),
|
||||
auth_type: 2,
|
||||
key: "".to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 数据包
|
||||
///
|
||||
/// payload 通常是 json 格式
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Packet {
|
||||
header: PacketHeader,
|
||||
payload: Vec<u8>,
|
||||
parsed_packets: Option<Vec<Packet>>,
|
||||
}
|
||||
|
||||
impl Packet {
|
||||
pub fn new_auth(auth: &AuthPayload) -> Self {
|
||||
// {
|
||||
// "uid": 90931399,
|
||||
// "roomid": 15028238,
|
||||
// "protover": 3,
|
||||
// "buvid": "FB7F3F89-C9DA-C58B-25F4-2EECA80D6A6F73048infoc",
|
||||
// "platform": "web",
|
||||
// "type": 2,
|
||||
// "key": "hm7gVA1C1f-4NNM9IGhpT_ucAF7L3OcO7QVNdNaTKwFRwIUv1Dc1HJLJI3G24TMJEZ9r_eUpbl78gOaNjCHuyDsU1eZyQA_4vPlQj5o0X6cQNScrrh9134Uxwnc5qvoirspInBWyrioK3LKzNXG4Mg3sIcvewEslBz6OWNTQVJvXCm8Y3rXdTepPXQ=="
|
||||
// }
|
||||
|
||||
// 省略参数校验
|
||||
let payload = serde_json::to_string(&auth).unwrap()
|
||||
.into_bytes();
|
||||
Self {
|
||||
header: PacketHeader::new(payload.len(), OperationCode::Auth),
|
||||
payload,
|
||||
parsed_packets: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_heartbeat() -> Self {
|
||||
Self {
|
||||
header: PacketHeader::new(31, OperationCode::Heartbeat),
|
||||
payload: "[object Object]".as_bytes().to_vec(),
|
||||
parsed_packets: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, PacketError> {
|
||||
let header = PacketHeader::from_bytes(&bytes[..PACKET_HEADER_SIZE])?;
|
||||
// 由于 brotli header长度是记录的第一个分包的长度,因此需要将后面所有字节都写入
|
||||
let payload_slice = &bytes[PACKET_HEADER_SIZE..];
|
||||
Ok(Self {
|
||||
header,
|
||||
payload: payload_slice.to_vec(),
|
||||
parsed_packets: None,
|
||||
})
|
||||
}
|
||||
|
||||
// #[inline]
|
||||
// pub fn get_operation_code(&self) -> OperationCode {
|
||||
// self.header.operation_code()
|
||||
// }
|
||||
|
||||
fn decompress_brotli(input: &[u8]) -> Result<Vec<u8>, PacketError> {
|
||||
let result = utils::brotli::decompress_to_vec(input);
|
||||
match result {
|
||||
Ok(res) => Ok(res),
|
||||
Err(err) => Err(PacketError::DecompressError(err))
|
||||
}
|
||||
}
|
||||
|
||||
/// 解析 Brotli 压缩格式的封包
|
||||
///
|
||||
/// 递归解析,分块解析为多个Normal消息
|
||||
fn parse_body_brotli(&self) -> Result<Vec<Packet>, PacketError> {
|
||||
let dec_bytes = Self::decompress_brotli(&*self.payload)?;
|
||||
// 分块解析为多个Normal消息
|
||||
let mut packets: Vec<Packet> = Vec::new();
|
||||
let mut offset = 0;
|
||||
while offset < dec_bytes.len() {
|
||||
let header = PacketHeader::from_bytes(&dec_bytes[offset..offset + PACKET_HEADER_SIZE])?;
|
||||
let block_bytes = &dec_bytes[offset..offset + header.total_size as usize];
|
||||
packets.push(Packet::from_bytes(block_bytes)?);
|
||||
offset += header.total_size as usize;
|
||||
}
|
||||
|
||||
Ok(packets)
|
||||
}
|
||||
|
||||
pub fn parse_body(&mut self) -> Result<(), PacketError> {
|
||||
match self.header.protocol_version() {
|
||||
ProtocolVersion::Brotli => {
|
||||
self.parsed_packets = Some(self.parse_body_brotli()?);
|
||||
Ok(())
|
||||
}
|
||||
// 未加密消息:在 parsed_packets 中复制一份自身 此处可优化
|
||||
ProtocolVersion::Business |
|
||||
ProtocolVersion::Connection |
|
||||
ProtocolVersion::Normal => {
|
||||
self.parsed_packets = Some(vec![self.clone()]);
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(PacketError::UnsupportedProtoVer(self.header.protocol_version))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_body(&mut self) -> Result<Vec<Vec<u8>>, PacketError> {
|
||||
if self.parsed_packets.is_none() {
|
||||
self.parse_body()?;
|
||||
}
|
||||
|
||||
let payloads = self.parsed_packets.as_ref().unwrap().iter().map(|packet| {
|
||||
packet.payload.clone()
|
||||
}).collect();
|
||||
Ok(payloads)
|
||||
}
|
||||
|
||||
pub fn get_body_packets(&mut self) -> Result<&Vec<Packet>, PacketError> {
|
||||
if self.parsed_packets.is_none() {
|
||||
self.parse_body()?;
|
||||
}
|
||||
|
||||
Ok(self.parsed_packets.as_ref().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_heartbeat_resp() {
|
||||
// [object Object]
|
||||
let expect = vec![vec![0, 0, 0, 1, 91, 111, 98, 106, 101, 99, 116, 32, 79, 98, 106, 101, 99, 116, 93]];
|
||||
let buffer = [0, 0, 0, 20, 0, 16, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 91, 111, 98, 106, 101, 99, 116, 32, 79, 98, 106, 101, 99, 116, 93];
|
||||
let mut packet = Packet::from_bytes(&buffer).unwrap();
|
||||
let body = packet.get_body().unwrap();
|
||||
assert_eq!(packet.header.operation_code(), OperationCode::HeartbeatResponse);
|
||||
assert_eq!(body, expect);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_heartbeat() {
|
||||
// [object Object]
|
||||
let expect = vec![vec![91, 111, 98, 106, 101, 99, 116, 32, 79, 98, 106, 101, 99, 116, 93]];
|
||||
let mut packet = Packet::new_heartbeat();
|
||||
let body = packet.get_body().unwrap();
|
||||
assert_eq!(packet.header.operation_code(), OperationCode::Heartbeat);
|
||||
assert_eq!(body, expect);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_auth_ok() {
|
||||
let expect = vec![str_to_u8_array!("{\"code\":0}").to_vec()];
|
||||
let buffer = [0, 0, 0, 26, 0, 16, 0, 1, 0, 0, 0, 8, 0, 0, 0, 1, 123, 34, 99, 111, 100, 101, 34, 58, 48, 125];
|
||||
let mut packet = Packet::from_bytes(&buffer).unwrap();
|
||||
let body = packet.get_body().unwrap();
|
||||
assert_eq!(packet.header.operation_code(), OperationCode::AuthResponse);
|
||||
assert_eq!(body, expect);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_auth() {
|
||||
let mut packet = Packet::new_auth(&AuthPayload {
|
||||
uid: 999,
|
||||
room_id: 999,
|
||||
buvid: "ABC".to_string(),
|
||||
key: "DEF".to_string(),
|
||||
..AuthPayload::default()
|
||||
});
|
||||
assert_eq!(packet.header.operation_code(), OperationCode::Auth);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_brotli_1() {
|
||||
let expect = str_to_u8_array!("{\"cmd\":\"INTERACT_WORD\",\"data\":{\"contribution\":");
|
||||
let buffer = [0, 0, 3, 5, 0, 16, 0, 3, 0, 0, 0, 5, 0, 0, 0, 0, 27, 130, 13, 0, 60, 20, 111, 44, 38, 213, 160, 39, 87, 39, 26, 178, 131, 23, 216, 96, 168, 81, 191, 142, 184, 5, 82, 42, 187, 193, 243, 128, 227, 68, 130, 32, 200, 138, 131, 210, 240, 20, 72, 68, 177, 253, 149, 206, 45, 228, 76, 162, 192, 216, 32, 208, 217, 44, 58, 229, 201, 89, 180, 220, 150, 250, 133, 2, 200, 160, 68, 67, 189, 129, 255, 234, 111, 64, 224, 13, 49, 107, 234, 42, 17, 15, 142, 78, 99, 123, 252, 33, 92, 21, 36, 187, 89, 181, 160, 180, 228, 116, 193, 18, 58, 37, 18, 83, 0, 125, 53, 36, 101, 245, 251, 105, 228, 55, 50, 202, 197, 147, 242, 153, 8, 151, 219, 255, 47, 64, 32, 36, 97, 225, 189, 82, 80, 87, 151, 29, 218, 178, 35, 163, 42, 117, 133, 172, 241, 79, 199, 85, 184, 78, 93, 85, 134, 115, 42, 23, 240, 20, 114, 147, 70, 128, 14, 44, 6, 191, 153, 95, 95, 250, 20, 140, 116, 150, 56, 40, 237, 63, 196, 141, 15, 43, 71, 225, 141, 135, 37, 72, 13, 248, 93, 4, 62, 218, 135, 248, 127, 140, 241, 9, 103, 63, 97, 110, 192, 112, 126, 132, 133, 206, 193, 106, 126, 43, 35, 4, 35, 176, 144, 24, 51, 97, 52, 64, 106, 18, 109, 3, 255, 84, 218, 112, 85, 23, 162, 159, 224, 126, 96, 2, 113, 104, 169, 213, 244, 43, 5, 3, 140, 36, 220, 33, 108, 139, 252, 149, 243, 9, 103, 116, 125, 58, 106, 2, 7, 3, 4, 207, 61, 95, 153, 234, 46, 174, 19, 36, 114, 50, 6, 178, 103, 201, 224, 138, 83, 168, 13, 128, 189, 173, 77, 14, 87, 90, 169, 72, 82, 251, 8, 67, 118, 106, 235, 127, 223, 141, 56, 55, 56, 32, 130, 40, 25, 121, 75, 71, 76, 23, 33, 166, 23, 141, 194, 22, 183, 252, 12, 169, 50, 60, 200, 144, 97, 47, 35, 121, 193, 212, 241, 149, 231, 111, 0, 12, 70, 49, 33, 149, 66, 89, 79, 223, 64, 168, 52, 253, 129, 95, 147, 77, 175, 253, 217, 36, 17, 1, 222, 44, 161, 119, 210, 25, 14, 23, 42, 115, 114, 214, 79, 190, 64, 208, 89, 145, 146, 59, 199, 37, 111, 131, 194, 193, 0, 21, 78, 12, 25, 63, 56, 139, 28, 215, 208, 174, 205, 140, 41, 185, 251, 105, 100, 179, 110, 163, 11, 59, 37, 184, 51, 129, 193, 25, 168, 240, 104, 13, 170, 193, 215, 248, 27, 89, 132, 78, 83, 6, 52, 46, 157, 136, 254, 135, 44, 189, 94, 241, 209, 51, 110, 125, 223, 30, 204, 124, 94, 136, 3, 122, 220, 120, 64, 59, 187, 37, 189, 119, 9, 226, 212, 238, 35, 151, 157, 71, 180, 149, 115, 34, 1, 139, 161, 101, 217, 62, 2, 83, 246, 43, 43, 243, 97, 112, 104, 113, 138, 43, 98, 34, 10, 32, 163, 98, 248, 196, 129, 161, 236, 70, 28, 251, 223, 120, 125, 123, 132, 96, 106, 89, 145, 162, 12, 106, 7, 184, 245, 13, 251, 127, 20, 80, 101, 170, 69, 180, 89, 18, 24, 202, 26, 230, 0, 55, 205, 2, 43, 73, 240, 209, 52, 48, 209, 52, 52, 155, 248, 103, 78, 16, 207, 162, 32, 28, 165, 59, 102, 133, 159, 101, 93, 58, 49, 17, 128, 119, 234, 32, 161, 244, 198, 102, 252, 182, 213, 210, 171, 109, 53, 105, 30, 102, 182, 141, 236, 181, 175, 28, 221, 241, 220, 121, 85, 229, 109, 117, 23, 119, 16, 252, 71, 215, 143, 32, 26, 245, 130, 38, 8, 135, 152, 99, 212, 247, 124, 90, 217, 144, 218, 118, 6, 101, 136, 101, 77, 237, 194, 145, 240, 251, 77, 163, 21, 145, 93, 110, 167, 53, 149, 21, 215, 125, 170, 26, 166, 227, 244, 232, 164, 180, 49, 111, 191, 242, 60, 173, 171, 75, 212, 166, 156, 26, 142, 89, 89, 205, 64, 57, 133, 29, 172, 11, 18, 209, 239, 75, 130, 133, 125, 105, 17, 90, 230, 214, 122, 23, 146, 21, 153, 217, 209, 202, 93, 176, 63, 158, 231, 84, 187, 118, 175, 43, 129, 205, 92, 37, 242, 149, 21, 94, 104, 123, 90, 15, 86, 220, 174, 167, 119, 125, 23, 15, 247, 141, 22, 162, 90, 185, 160, 137, 194, 33, 174, 212, 247, 124, 106, 225, 144, 226, 182, 70, 133, 92, 4];
|
||||
let body = Packet::from_bytes(&buffer).unwrap().get_body().unwrap();
|
||||
assert_eq!(body.len(), 3); // 主包长度检查
|
||||
assert_eq!(&body[0][..46], expect); // 子包0内容检查
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uncommon() {
|
||||
let expect = vec![str_to_u8_array!(r#"{"cmd":"ONLINE_RANK_COUNT","data":{"count":419}}"#).to_vec()];
|
||||
let buffer = [0, 0, 0, 84, 0, 16, 0, 3, 0, 0, 0, 5, 0, 0, 0, 0, 139, 31, 128, 0, 0, 0, 64, 0, 16, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 123, 34, 99, 109, 100, 34, 58, 34, 79, 78, 76, 73, 78, 69, 95, 82, 65, 78, 75, 95, 67, 79, 85, 78, 84, 34, 44, 34, 100, 97, 116, 97, 34, 58, 123, 34, 99, 111, 117, 110, 116, 34, 58, 52, 49, 57, 125, 125, 3];
|
||||
let body = Packet::from_bytes(&buffer).unwrap().get_body().unwrap();
|
||||
assert_eq!(body, expect);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
mod core;
|
||||
mod utils;
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
use std::io::{Error, Read};
|
||||
use brotli::Decompressor;
|
||||
|
||||
pub fn decompress_to_vec(input: &[u8]) -> Result<Vec<u8>, Error> {
|
||||
let mut reader = Decompressor::new(input, 4096);
|
||||
let mut result: Vec<u8> = Vec::new();
|
||||
reader.read_to_end(&mut result)?;
|
||||
Ok(result)
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
#[macro_export]
|
||||
macro_rules! str_to_u8_array {
|
||||
($str:expr) => {
|
||||
$str.as_bytes()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
pub mod brotli;
|
||||
pub mod macros;
|
Loading…
Reference in New Issue