首次提交

This commit is contained in:
Eigeen 2024-01-07 23:09:04 +08:00
commit 5eb60dbbc6
Signed by: eigeen
GPG Key ID: B730E75AFABD2ED8
11 changed files with 462 additions and 0 deletions

10
.gitignore vendored Normal file
View File

@ -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

16
Cargo.toml Normal file
View File

@ -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"

13
LICENSE Normal file
View File

@ -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
src/core/liveroom.rs Normal file
View File

76
src/core/message.rs Normal file
View File

@ -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)
}
}
}

3
src/core/mod.rs Normal file
View File

@ -0,0 +1,3 @@
pub mod message;
pub mod liveroom;
pub mod packet;

321
src/core/packet.rs Normal file
View File

@ -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);
}
}

6
src/main.rs Normal file
View File

@ -0,0 +1,6 @@
mod core;
mod utils;
fn main() {
println!("Hello, world!");
}

9
src/utils/brotli.rs Normal file
View File

@ -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)
}

6
src/utils/macros.rs Normal file
View File

@ -0,0 +1,6 @@
#[macro_export]
macro_rules! str_to_u8_array {
($str:expr) => {
$str.as_bytes()
}
}

2
src/utils/mod.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod brotli;
pub mod macros;