You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

176 lines
4.8 KiB
Go

package bilibili
import (
"bytes"
"compress/zlib"
"encoding/binary"
"errors"
"fmt"
"github.com/andybalholm/brotli"
"github.com/gorilla/websocket"
"io"
"live-gateway/ws"
)
const (
wsPackHeaderTotalLen = 16 // 头部字节大小
wsPackageLen = 4
wsHeaderLen = 2
wsVerLen = 2
wsOpLen = 4
wsSequenceLen = 4
)
const (
WsHeaderDefaultSequence = 1
)
var _ ws.Packer = (*PackBilibili)(nil)
// PackBilibili bilibili packer
// |packageLen|headerLen|protoVer|operation|sequenceId|data|
//
// | segment | type | size | offset | remark |
// | ---------- | ------ | ------- | -------| ------------------------------------------- |
// | `packageLen` | uint32 | 4 | 0 | the size of `data` and `header` |
// | `headerLen` | uint16 | 2 | 4 | 16 |
// | `protoVer` | uint16 | 2 | 6 | 0:json 1:人气值 2:zlib.inflate 3:brotli |
// | `operation` | uint32 | 4 | 8 | 2:hb 3:hb-reply 5:danmaku 7:room 8:room-rep |
// | `sequenceId` | uint32 | 4 | 12 | 1 |
// | `data` | []byte | dynamic | 16 | :数据内容 |
// WsEntry -> ws.Entry#Raw
type PackBilibili struct {
}
func NewPackBilibili() ws.Packer {
return &PackBilibili{}
}
func (*PackBilibili) byteOrder() binary.ByteOrder {
return binary.BigEndian
}
// Unpack unpacks bilibili.WsEntry to ws.Entry
func (p *PackBilibili) Unpack(v interface{}) (*ws.Entry, error) {
result := &ws.Entry{
MessageType: websocket.BinaryMessage,
}
// v must be bilibili.WsEntry
entry, ok := v.(*WsEntry)
if !ok {
return nil, fmt.Errorf("[Pack-Bilibili] 写入值类型必须为%T", WsEntry{})
}
header := make([]byte, 0, wsPackHeaderTotalLen)
header = append(header, p.write(wsPackageLen, len(entry.data)+wsPackHeaderTotalLen)...)
header = append(header, p.write(wsHeaderLen, wsPackHeaderTotalLen)...)
header = append(header, p.write(wsVerLen, int(entry.protoVer))...)
header = append(header, p.write(wsOpLen, int(entry.operation))...)
header = append(header, p.write(wsSequenceLen, WsHeaderDefaultSequence)...)
data := make([]byte, 0, len(entry.data)+wsPackHeaderTotalLen)
data = append(data, header...)
data = append(data, entry.data...)
result.Raw = data
return result, nil
}
// Pack packs ws.Entry to bilibili.WsEntry
func (p *PackBilibili) Pack(entry *ws.Entry) (interface{}, error) {
if entry.MessageType != websocket.BinaryMessage || len(entry.Raw) < wsPackHeaderTotalLen {
return nil, errors.New("err of msg")
}
var (
offsetVer = wsPackageLen + wsHeaderLen
offsetOp = offsetVer + wsVerLen
offsetSeqId = offsetOp + wsOpLen
)
v := &WsEntry{}
v.protoVer = p.byteOrder().Uint16(entry.Raw[offsetVer : offsetVer+wsVerLen])
v.operation = p.byteOrder().Uint32(entry.Raw[offsetOp : offsetOp+wsOpLen])
v.sequenceId = p.byteOrder().Uint32(entry.Raw[offsetSeqId : offsetSeqId+wsSequenceLen])
v.data = entry.Raw[wsPackHeaderTotalLen:]
if v.operation == WsOpMessage {
switch v.protoVer {
case WsVerZlib:
de, err := p.zlibDecode(v.data)
if err != nil {
return nil, err
}
split := p.split(de)
entries := p.combine(split, entry.MessageType)
return entries, nil
case WsVerBrotli:
de, err := p.brotliDecode(v.data)
if err != nil {
return nil, err
}
split := p.split(de)
entries := p.combine(split, entry.MessageType)
return entries, nil
case WsVerPlain:
return v, nil
}
}
return v, nil
}
func (p *PackBilibili) write(len, v int) []byte {
b := make([]byte, len)
switch len {
case 2:
p.byteOrder().PutUint16(b, uint16(v))
case 4:
p.byteOrder().PutUint32(b, uint32(v))
}
return b
}
func (*PackBilibili) zlibDecode(src []byte) ([]byte, error) {
var (
r io.ReadCloser
o bytes.Buffer
err error
)
if r, err = zlib.NewReader(bytes.NewReader(src)); err != nil {
return nil, err
}
if _, err = io.Copy(&o, r); err != nil {
return nil, err
}
return o.Bytes(), nil
}
func (*PackBilibili) brotliDecode(src []byte) ([]byte, error) {
o := new(bytes.Buffer)
r := brotli.NewReader(bytes.NewReader(src))
if _, err := io.Copy(o, r); err != nil {
return nil, err
}
return o.Bytes(), nil
}
// split 解压后的body需要拆包
func (*PackBilibili) split(b []byte) [][]byte {
var packs [][]byte
for i, size := uint32(0), uint32(0); i < uint32(len(b)); i += size {
size = binary.BigEndian.Uint32(b[i : i+4])
packs = append(packs, b[i:i+size])
}
return packs
}
func (*PackBilibili) combine(bs [][]byte, msgType int) []*ws.Entry {
entries := make([]*ws.Entry, len(bs))
for i, bs := range bs {
entries[i] = &ws.Entry{
MessageType: msgType,
Raw: bs,
}
}
return entries
}