CI / build (push) Successful in 45s
The client sends it every HTTP request and its IMQ connection; it makes them with current TLS and certificates, upgrades IMVU's plain-HTTP hosts to HTTPS, and refuses IMVU's reporting endpoints. CI tests it and publishes t5de-proxy.exe as nightlies and tagged releases. Co-Authored-By: Claude Opus 5.5 <[email protected]>
141 lines
3.3 KiB
Go
141 lines
3.3 KiB
Go
// Package imq reads the framing of IMQ, IMVU's real-time messaging protocol (chat, presence, room state).
|
|
//
|
|
// The stream is a sequence of frames, each a protobuf message with the message type in field 1 (varint) and the
|
|
// message itself in field 2 (bytes), with nothing between frames. Only the framing is decoded here: enough to log
|
|
// what goes by, by type and size, without the messages' contents.
|
|
package imq
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
)
|
|
|
|
// Message types, as the client numbers them: C2g are sent by the client, G2c by the gateway.
|
|
var typeNames = map[uint64]string{
|
|
1301: "C2gPing",
|
|
1302: "C2gConnect",
|
|
1303: "C2gChallengeResponse",
|
|
1304: "C2gSendMessage",
|
|
1305: "C2gStateChange",
|
|
1306: "C2gOpenFloodgates",
|
|
1307: "C2gSuicide",
|
|
1308: "C2gUnsubscribe",
|
|
1309: "C2gSubscribe",
|
|
3101: "G2cPong",
|
|
3102: "G2cResult",
|
|
3103: "G2cChallenge",
|
|
3104: "G2cSendMessage",
|
|
3105: "G2cStateChange",
|
|
3106: "G2cJoinedQueue",
|
|
3107: "G2cLeftQueue",
|
|
3108: "G2cCreateMount",
|
|
3109: "G2cConnectionClosed",
|
|
}
|
|
|
|
// TypeName is a message type's name, or its number if it's unknown.
|
|
func TypeName(t uint64) string {
|
|
if name, ok := typeNames[t]; ok {
|
|
return name
|
|
}
|
|
return fmt.Sprintf("type %d", t)
|
|
}
|
|
|
|
// Frame is one message: its type and the size of its data.
|
|
type Frame struct {
|
|
Type uint64
|
|
Size int
|
|
}
|
|
|
|
// ErrMalformed means the stream isn't IMQ framing; a Reader stops decoding after it.
|
|
var ErrMalformed = errors.New("imq: malformed frame")
|
|
|
|
// Reader decodes frames from a stream given in pieces of any size.
|
|
type Reader struct {
|
|
buf []byte
|
|
err error
|
|
}
|
|
|
|
// Write adds data from the stream and returns the frames it completed.
|
|
func (r *Reader) Write(data []byte) ([]Frame, error) {
|
|
if r.err != nil {
|
|
return nil, r.err
|
|
}
|
|
r.buf = append(r.buf, data...)
|
|
var frames []Frame
|
|
for {
|
|
f, n, err := parse(r.buf)
|
|
if err != nil {
|
|
r.err, r.buf = err, nil
|
|
return frames, err
|
|
}
|
|
if n == 0 {
|
|
return frames, nil
|
|
}
|
|
frames = append(frames, f)
|
|
r.buf = r.buf[n:]
|
|
}
|
|
}
|
|
|
|
// maxFrame bounds a frame's size, so a stream that isn't IMQ isn't buffered without end.
|
|
const maxFrame = 16 << 20
|
|
|
|
// parse reads the frame at the start of b: the frame and its length, or a length of 0 if b holds only part of it.
|
|
func parse(b []byte) (Frame, int, error) {
|
|
var f Frame
|
|
i := 0
|
|
for i < len(b) {
|
|
key, n := varint(b[i:])
|
|
if n == 0 {
|
|
return f, 0, nil
|
|
}
|
|
if n < 0 {
|
|
return f, 0, ErrMalformed
|
|
}
|
|
i += n
|
|
switch key {
|
|
case 1<<3 | 0: // field 1, varint: the type
|
|
v, n := varint(b[i:])
|
|
if n == 0 {
|
|
return f, 0, nil
|
|
}
|
|
if n < 0 {
|
|
return f, 0, ErrMalformed
|
|
}
|
|
f.Type = v
|
|
i += n
|
|
case 2<<3 | 2: // field 2, bytes: the message, the frame's last field
|
|
size, n := varint(b[i:])
|
|
if n == 0 {
|
|
return f, 0, nil
|
|
}
|
|
if n < 0 || size > maxFrame {
|
|
return f, 0, ErrMalformed
|
|
}
|
|
i += n
|
|
if len(b)-i < int(size) {
|
|
return f, 0, nil
|
|
}
|
|
f.Size = int(size)
|
|
return f, i + int(size), nil
|
|
default:
|
|
return f, 0, ErrMalformed
|
|
}
|
|
}
|
|
return f, 0, nil
|
|
}
|
|
|
|
// varint decodes a protobuf varint: its value and length, a length of 0 if b ends first, or -1 if it's too long.
|
|
func varint(b []byte) (uint64, int) {
|
|
var v uint64
|
|
for i := 0; i < len(b); i++ {
|
|
if i == 10 {
|
|
return 0, -1
|
|
}
|
|
v |= uint64(b[i]&0x7f) << (7 * i)
|
|
if b[i]&0x80 == 0 {
|
|
return v, i + 1
|
|
}
|
|
}
|
|
return 0, 0
|
|
}
|