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