package protocol import ( "bufio" "bytes" "compress/zlib" "io" ) // Packet is a decoded frame: a packet ID plus its raw body bytes. // The body excludes the ID and any length/compression prefixes. type Packet struct { ID int32 Data []byte } // Body returns a Reader positioned at the start of the packet body. func (p Packet) Body() *Reader { return NewReader(p.Data) } // ReadPacket reads one frame from br. // // When threshold < 0 the uncompressed format is used: // // VarInt length | VarInt packet ID | body // // When threshold >= 0 the compressed format is used: // // VarInt packet length | VarInt data length | (zlib or raw) packet ID + body // // A data length of 0 means the payload is stored uncompressed (its // uncompressed size was below the threshold). func ReadPacket(br *bufio.Reader, threshold int32) (Packet, error) { length, _, err := ReadVarInt(br) if err != nil { return Packet{}, err } if length < 0 || int(length) > MaxPacketSize { return Packet{}, ErrPacketTooLarge } frame := make([]byte, length) if _, err := io.ReadFull(br, frame); err != nil { return Packet{}, err } if threshold < 0 { return parseIDBody(frame) } return parseCompressed(frame) } // parseCompressed handles a frame that begins with a Data Length VarInt. func parseCompressed(frame []byte) (Packet, error) { r := NewReader(frame) dataLen, err := r.VarInt() if err != nil { return Packet{}, err } payload := frame[r.pos:] if dataLen == 0 { // Stored uncompressed. return parseIDBody(payload) } if dataLen < 0 || int(dataLen) > MaxPacketSize { return Packet{}, ErrPacketTooLarge } zr, err := zlib.NewReader(bytes.NewReader(payload)) if err != nil { return Packet{}, err } defer zr.Close() out := make([]byte, dataLen) if _, err := io.ReadFull(zr, out); err != nil { return Packet{}, err } return parseIDBody(out) } // parseIDBody splits a VarInt packet ID off the front of buf. func parseIDBody(buf []byte) (Packet, error) { r := NewReader(buf) id, err := r.VarInt() if err != nil { return Packet{}, err } return Packet{ID: id, Data: buf[r.pos:]}, nil } // WritePacket writes one frame to w with the given ID and body, using the // uncompressed format when threshold < 0 and the compressed format otherwise. func WritePacket(w io.Writer, threshold int32, id int32, body []byte) error { _, err := w.Write(AppendPacket(nil, threshold, id, body)) return err } // AppendPacket appends one fully-framed packet to dst and returns the result. // The produced bytes are identical to what WritePacket would write, so callers // may cache them and replay via a raw write. func AppendPacket(dst []byte, threshold int32, id int32, body []byte) []byte { if threshold < 0 { return appendUncompressed(dst, id, body) } return appendCompressed(dst, threshold, id, body) } func appendUncompressed(dst []byte, id int32, body []byte) []byte { total := VarIntLen(id) + len(body) dst = AppendVarInt(dst, int32(total)) dst = AppendVarInt(dst, id) return append(dst, body...) } func appendCompressed(dst []byte, threshold int32, id int32, body []byte) []byte { // raw = packet ID + body, the unit that compression applies to. raw := make([]byte, 0, VarIntLen(id)+len(body)) raw = AppendVarInt(raw, id) raw = append(raw, body...) var payload []byte if len(raw) >= int(threshold) { var buf bytes.Buffer zw := zlib.NewWriter(&buf) zw.Write(raw) zw.Close() // Data Length = uncompressed size, then the compressed bytes. payload = AppendVarInt(make([]byte, 0, VarIntLen(int32(len(raw)))+buf.Len()), int32(len(raw))) payload = append(payload, buf.Bytes()...) } else { // Below threshold: Data Length = 0, raw stored verbatim. payload = AppendVarInt(make([]byte, 0, 1+len(raw)), 0) payload = append(payload, raw...) } dst = AppendVarInt(dst, int32(len(payload))) return append(dst, payload...) }