summaryrefslogtreecommitdiff
path: root/danmaku/dmpkg/package.go
blob: 639bde874f182416ea8cfb6db791f45c49a1f829 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package dmpkg

import (
	"bytes"
	"compress/zlib"
	"fmt"
	"github.com/andybalholm/brotli"
	"github.com/lunixbochs/struc"
	"io"
)

type DanmakuExchangeHeader struct {
	// Length total remaining bytes of this exchange, excluding `Length` itself
	Length uint32
	// HeaderLength = Length - len(Body) + 4, always equals to 16
	HeaderLength uint16
	ProtocolVer  ProtocolVer
	Operation    Operation
	// SequenceId is always 1
	SequenceId uint32
}

// DanmakuExchange represents an actual message sent from client or server. This is an atomic unit.
type DanmakuExchange struct {
	DanmakuExchangeHeader
	Body []byte
}

func (e *DanmakuExchange) String() string {
	return fmt.Sprintf("DanmakuExchange{length=%v, protocol=%v, operation=%v, body=%v}",
		e.Length, e.ProtocolVer, e.Operation, e.Body)
}

func (e *DanmakuExchange) PrettyString() string {
	return fmt.Sprintf("DanmakuExchange{length=%v, protocol=%v, operation=%v, body=%v}",
		e.Length, e.ProtocolVer, e.Operation.String(), string(e.Body))
}

const (
	HeaderLength = 16
	SequenceId   = 1
)

type ProtocolVer uint16

const (
	// ProtoPlainJson the body is plain JSON text
	ProtoPlainJson ProtocolVer = 0
	// ProtoMinimal the body is uint32 watcher count (big endian)
	ProtoMinimal ProtocolVer = 1
	// ProtoZlib the body is a zlib compressed package
	ProtoZlib ProtocolVer = 2
	// ProtoBrotli the body is a brotli compressed package
	ProtoBrotli ProtocolVer = 3
)

const (
	UidGuest    = 0
	PlatformWeb = "web"
	// AuthTypeDefault magic number, not sure what does it mean
	AuthTypeDefault = 2
)

func (e *DanmakuExchange) Marshal() (data []byte, err error) {
	var buffer bytes.Buffer
	// only unpack header with struc, since it does not support indirect variable field length calculation
	err = struc.Pack(&buffer, &e.DanmakuExchangeHeader)
	if err != nil {
		err = fmt.Errorf("cannot pack an exchange into binary form: %w", err)
		return
	}
	data = buffer.Bytes()
	data = append(data, e.Body...)
	return
}

// Inflate decompresses the body if it is compressed
func (e *DanmakuExchange) Inflate() (ret DanmakuExchange, err error) {
	switch e.ProtocolVer {
	case ProtoMinimal:
		fallthrough
	case ProtoPlainJson:
		ret = *e
	case ProtoBrotli:
		var data []byte
		rd := brotli.NewReader(bytes.NewReader(e.Body))
		data, err = io.ReadAll(rd)
		if err != nil {
			err = fmt.Errorf("cannot decompress exchange body: %w", err)
			return
		}
		var nestedExchange DanmakuExchange
		nestedExchange, err = DecodeExchange(data)
		if err != nil {
			err = fmt.Errorf("cannot decode nested exchange: %w", err)
			return
		}
		return nestedExchange.Inflate()
	case ProtoZlib:
		var data []byte
		var rd io.ReadCloser
		rd, err = zlib.NewReader(bytes.NewReader(e.Body))
		if err != nil {
			err = fmt.Errorf("cannot create zlib reader: %w", err)
			return
		}
		data, err = io.ReadAll(rd)
		if err != nil {
			err = fmt.Errorf("cannot decompress exchange body: %w", err)
			return
		}
		var nestedExchange DanmakuExchange
		nestedExchange, err = DecodeExchange(data)
		if err != nil {
			err = fmt.Errorf("cannot decode nested exchange: %w", err)
			return
		}
		return nestedExchange.Inflate()
	}
	return
}