// Package bytesutil provides utility functions for working with bytes and byte streams that are useful when // working with the RESP protocol. package bytesutil import ( "bufio" "fmt" "io" "strconv" "sync" "errors" "github.com/mediocregopher/radix/v3/resp" ) // AnyIntToInt64 converts a value of any of Go's integer types (signed and unsigned) into a signed int64. // // If m is not of one of Go's built in integer types the call will panic. func AnyIntToInt64(m interface{}) int64 { switch mt := m.(type) { case int: return int64(mt) case int8: return int64(mt) case int16: return int64(mt) case int32: return int64(mt) case int64: return mt case uint: return int64(mt) case uint8: return int64(mt) case uint16: return int64(mt) case uint32: return int64(mt) case uint64: return int64(mt) } panic(fmt.Sprintf("anyIntToInt64 got bad arg: %#v", m)) } var bytePool = sync.Pool{ New: func() interface{} { b := make([]byte, 0, 64) return &b }, } // GetBytes returns a non-nil pointer to a byte slice from a pool of byte slices. // // The returned byte slice should be put back into the pool using PutBytes after usage. func GetBytes() *[]byte { return bytePool.Get().(*[]byte) } // PutBytes puts the given byte slice pointer into a pool that can be accessed via GetBytes. // // After calling PutBytes the given pointer and byte slice must not be accessed anymore. func PutBytes(b *[]byte) { *b = (*b)[:0] bytePool.Put(b) } // ParseInt is a specialized version of strconv.ParseInt that parses a base-10 encoded signed integer from a []byte. // // This can be used to avoid allocating a string, since strconv.ParseInt only takes a string. func ParseInt(b []byte) (int64, error) { if len(b) == 0 { return 0, errors.New("empty slice given to parseInt") } var neg bool if b[0] == '-' || b[0] == '+' { neg = b[0] == '-' b = b[1:] } n, err := ParseUint(b) if err != nil { return 0, err } if neg { return -int64(n), nil } return int64(n), nil } // ParseUint is a specialized version of strconv.ParseUint that parses a base-10 encoded integer from a []byte. // // This can be used to avoid allocating a string, since strconv.ParseUint only takes a string. func ParseUint(b []byte) (uint64, error) { if len(b) == 0 { return 0, errors.New("empty slice given to parseUint") } var n uint64 for i, c := range b { if c < '0' || c > '9' { return 0, fmt.Errorf("invalid character %c at position %d in parseUint", c, i) } n *= 10 n += uint64(c - '0') } return n, nil } // Expand expands the given byte slice to exactly n bytes. // // If cap(b) < n, a new slice will be allocated and filled with the bytes from b. func Expand(b []byte, n int) []byte { if cap(b) < n { nb := make([]byte, n) copy(nb, b) return nb } return b[:n] } // BufferedBytesDelim reads a line from br and checks that the line ends with \r\n, returning the line without \r\n. func BufferedBytesDelim(br *bufio.Reader) ([]byte, error) { b, err := br.ReadSlice('\n') if err != nil { return nil, err } else if len(b) < 2 || b[len(b)-2] != '\r' { return nil, fmt.Errorf("malformed resp %q", b) } return b[:len(b)-2], err } // BufferedIntDelim reads the current line from br as an integer. func BufferedIntDelim(br *bufio.Reader) (int64, error) { b, err := BufferedBytesDelim(br) if err != nil { return 0, err } return ParseInt(b) } // ReadNAppend appends exactly n bytes from r into b. func ReadNAppend(r io.Reader, b []byte, n int) ([]byte, error) { if n == 0 { return b, nil } m := len(b) b = Expand(b, len(b)+n) _, err := io.ReadFull(r, b[m:]) return b, err } // ReadNDiscard discards exactly n bytes from r. func ReadNDiscard(r io.Reader, n int) error { type discarder interface { Discard(int) (int, error) } if n == 0 { return nil } switch v := r.(type) { case discarder: _, err := v.Discard(n) return err case io.Seeker: _, err := v.Seek(int64(n), io.SeekCurrent) return err } scratch := GetBytes() defer PutBytes(scratch) *scratch = (*scratch)[:cap(*scratch)] if len(*scratch) < n { *scratch = make([]byte, 8192) } for { buf := *scratch if len(buf) > n { buf = buf[:n] } nr, err := r.Read(buf) n -= nr if n == 0 || err != nil { return err } } } // ReadInt reads the next n bytes from r as a signed 64 bit integer. func ReadInt(r io.Reader, n int) (int64, error) { scratch := GetBytes() defer PutBytes(scratch) var err error if *scratch, err = ReadNAppend(r, *scratch, n); err != nil { return 0, err } i, err := ParseInt(*scratch) if err != nil { return 0, resp.ErrDiscarded{Err: err} } return i, nil } // ReadUint reads the next n bytes from r as an unsigned 64 bit integer. func ReadUint(r io.Reader, n int) (uint64, error) { scratch := GetBytes() defer PutBytes(scratch) var err error if *scratch, err = ReadNAppend(r, *scratch, n); err != nil { return 0, err } ui, err := ParseUint(*scratch) if err != nil { return 0, resp.ErrDiscarded{Err: err} } return ui, nil } // ReadFloat reads the next n bytes from r as a 64 bit floating point number with the given precision. func ReadFloat(r io.Reader, precision, n int) (float64, error) { scratch := GetBytes() defer PutBytes(scratch) var err error if *scratch, err = ReadNAppend(r, *scratch, n); err != nil { return 0, err } f, err := strconv.ParseFloat(string(*scratch), precision) if err != nil { return 0, resp.ErrDiscarded{Err: err} } return f, nil }