// Package zlib provides abstractions on top of compress/zlib to work with // Discord's method of compressing websocket packets. package zlib import ( "bytes" "fmt" "log" "errors" ) var Suffix = [4]byte{'\x00', '\x00', '\xff', '\xff'} var ErrPartial = errors.New("only partial payload in buffer") type Inflator struct { zlib Reader wbuf bytes.Buffer // write buffer for writing compressed bytes rbuf bytes.Buffer // read buffer for writing uncompressed bytes } func NewInflator() *Inflator { return &Inflator{ wbuf: bytes.Buffer{}, rbuf: bytes.Buffer{}, } } func (i *Inflator) Write(p []byte) (n int, err error) { log.Println(p) // Write to buffer normally. return i.wbuf.Write(p) } // CanFlush returns if Flush() should be called. func (i *Inflator) CanFlush() bool { if i.wbuf.Len() < 4 { return false } p := i.wbuf.Bytes() return bytes.Equal(p[len(p)-4:], Suffix[:]) } func (i *Inflator) Flush() ([]byte, error) { // Check if close frames are there: // if !i.CanFlush() { // return nil, ErrPartial // } // log.Println(i.wbuf.Bytes()) // We should reset the write buffer after flushing. // defer i.wbuf.Reset() // We can reset the read buffer while returning its byte slice. This works // as long as we copy the byte slice before resetting. defer i.rbuf.Reset() // Guarantee there's a zlib writer. Since Discord streams zlib, we have to // reuse the same Reader. Only the first packet has the zlib header. if i.zlib == nil { r, err := zlibStreamer(&i.wbuf) if err != nil { return nil, fmt.Errorf("failed to make a FLATE reader: %w", err) } // safe assertion i.zlib = r // } else { // // Reset the FLATE reader for future use: // if err := i.zlib.Reset(&i.wbuf, nil); err != nil { // return nil, errors.Wrap(err, "failed to reset zlib reader") // } } // We can ignore zlib.Read's error, as zlib.Close would return them. _, err := i.rbuf.ReadFrom(i.zlib) // ErrUnexpectedEOF happens because zlib tries to find the last 4 bytes // to verify checksum. Discord doesn't send this. if err != nil { // Unexpected error, try and close. return nil, fmt.Errorf("failed to read from FLATE reader: %w", err) } // if err := i.zlib.Close(); err != nil && err != io.ErrUnexpectedEOF { // // Try and close anyway. // return nil, errors.Wrap(err, "failed to read from zlib reader") // } // Copy the bytes. return bytecopy(i.rbuf.Bytes()), nil } // func (d *Deflator) TryFlush() ([]byte, error) { // // Check if the buffer ends with the zlib close suffix. // if d.wbuf.Len() < 4 { // return nil, nil // } // if p := d.wbuf.Bytes(); !bytes.Equal(p[len(p)-4:], Suffix[:]) { // return nil, nil // } // // Guarantee there's a zlib writer. Since Discord streams zlib, we have to // // reuse the same Reader. Only the first packet has the zlib header. // if d.zlib == nil { // r, err := zlib.NewReader(&d.wbuf) // if err != nil { // return nil, errors.Wrap(err, "failed to make a zlib reader") // } // // safe assertion // d.zlib = r // } // // We can reset the read buffer while returning its byte slice. This works // // as long as we copy the byte slice before resetting. // defer d.rbuf.Reset() // defer d.wbuf.Reset() // // We can ignore zlib.Read's error, as zlib.Close would return them. // _, err := d.rbuf.ReadFrom(d.zlib) // log.Println("Read:", err, d.rbuf.String()) // // ErrUnexpectedEOF happens because zlib tries to find the last 4 bytes // // to verify checksum. Discord doesn't send this. // // if err != nil && err != io.ErrUnexpectedEOF { // // // Unexpected error, try and close. // // return nil, errors.Wrap(err, "failed to read from zlib reader") // // } // if err := d.zlib.Close(); err != nil && err != io.ErrUnexpectedEOF { // // Try and close anyway. // return nil, errors.Wrap(err, "failed to read from zlib reader") // } // // Copy the bytes. // return bytecopy(d.rbuf.Bytes()), nil // } func bytecopy(p []byte) []byte { cpy := make([]byte, len(p)) copy(cpy, p) return cpy }