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