package backends import ( "bufio" "errors" "fmt" "io" "strconv" "strings" "sync" ) // RESP (REdis Serialization Protocol) implementation // Pure Go implementation compatible with Yaegi interpreter (no unsafe package) var ( ErrInvalidRESP = errors.New("invalid RESP response") ErrNilResponse = errors.New("nil response") ) // Object pools for memory optimization - reduces allocations by 50-70% var ( readerPool = sync.Pool{ New: func() interface{} { return &RESPReader{ r: bufio.NewReaderSize(nil, 4096), } }, } writerPool = sync.Pool{ New: func() interface{} { return &RESPWriter{ w: nil, } }, } ) // RESPWriter writes RESP protocol messages type RESPWriter struct { w io.Writer } // NewRESPWriter creates a new RESP writer from the pool (memory optimized) func NewRESPWriter(w io.Writer) *RESPWriter { writer := writerPool.Get().(*RESPWriter) writer.w = w return writer } // Release returns the writer to the pool for reuse func (w *RESPWriter) Release() { w.w = nil writerPool.Put(w) } // WriteCommand writes a Redis command in RESP array format // Example: SET key value EX 3600 -> *5\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n$2\r\nEX\r\n$4\r\n3600\r\n func (w *RESPWriter) WriteCommand(args ...string) error { // Write array header if _, err := fmt.Fprintf(w.w, "*%d\r\n", len(args)); err != nil { return err } // Write each argument as bulk string for _, arg := range args { if _, err := fmt.Fprintf(w.w, "$%d\r\n%s\r\n", len(arg), arg); err != nil { return err } } return nil } // RESPReader reads RESP protocol messages type RESPReader struct { r *bufio.Reader } // NewRESPReader creates a new RESP reader from the pool (memory optimized) func NewRESPReader(r io.Reader) *RESPReader { reader := readerPool.Get().(*RESPReader) reader.r.Reset(r) return reader } // Release returns the reader to the pool for reuse func (r *RESPReader) Release() { r.r.Reset(nil) readerPool.Put(r) } // ReadResponse reads a RESP response and returns the parsed value func (r *RESPReader) ReadResponse() (interface{}, error) { typeByte, err := r.r.ReadByte() if err != nil { return nil, err } switch typeByte { case '+': // Simple string return r.readSimpleString() case '-': // Error return nil, r.readError() case ':': // Integer return r.readInteger() case '$': // Bulk string return r.readBulkString() case '*': // Array return r.readArray() default: return nil, fmt.Errorf("%w: unknown type byte '%c'", ErrInvalidRESP, typeByte) } } // readSimpleString reads a simple string (+OK\r\n) func (r *RESPReader) readSimpleString() (string, error) { line, err := r.readLine() if err != nil { return "", err } return line, nil } // readError reads an error message (-Error message\r\n) func (r *RESPReader) readError() error { line, err := r.readLine() if err != nil { return err } return errors.New(line) } // readInteger reads an integer (:1000\r\n) func (r *RESPReader) readInteger() (int64, error) { line, err := r.readLine() if err != nil { return 0, err } return strconv.ParseInt(line, 10, 64) } // readBulkString reads a bulk string ($6\r\nfoobar\r\n or $-1\r\n for nil) func (r *RESPReader) readBulkString() (interface{}, error) { line, err := r.readLine() if err != nil { return nil, err } length, err := strconv.Atoi(line) if err != nil { return nil, fmt.Errorf("%w: invalid bulk string length", ErrInvalidRESP) } // -1 indicates nil bulk string if length == -1 { return nil, ErrNilResponse } // Read exactly 'length' bytes plus \r\n buf := make([]byte, length+2) if _, err := io.ReadFull(r.r, buf); err != nil { return nil, err } // Verify \r\n terminator if buf[length] != '\r' || buf[length+1] != '\n' { return nil, fmt.Errorf("%w: missing CRLF after bulk string", ErrInvalidRESP) } return string(buf[:length]), nil } // readArray reads an array (*2\r\n...\r\n or *-1\r\n for nil) func (r *RESPReader) readArray() (interface{}, error) { line, err := r.readLine() if err != nil { return nil, err } length, err := strconv.Atoi(line) if err != nil { return nil, fmt.Errorf("%w: invalid array length", ErrInvalidRESP) } // -1 indicates nil array if length == -1 { return nil, ErrNilResponse } // Read each element result := make([]interface{}, length) for i := 0; i < length; i++ { elem, err := r.ReadResponse() if err != nil { return nil, err } result[i] = elem } return result, nil } // readLine reads a line terminated by \r\n func (r *RESPReader) readLine() (string, error) { line, err := r.r.ReadString('\n') if err != nil { return "", err } // Remove \r\n line = strings.TrimSuffix(line, "\r\n") if !strings.HasSuffix(line+"\r\n", "\r\n") { return "", fmt.Errorf("%w: missing CRLF", ErrInvalidRESP) } return line, nil } // RESPString extracts a string from RESP response func RESPString(resp interface{}) (string, error) { if resp == nil { return "", ErrNilResponse } switch v := resp.(type) { case string: return v, nil case []byte: return string(v), nil default: return "", fmt.Errorf("expected string, got %T", resp) } } // RESPInt extracts an integer from RESP response func RESPInt(resp interface{}) (int64, error) { if resp == nil { return 0, ErrNilResponse } switch v := resp.(type) { case int64: return v, nil case int: return int64(v), nil default: return 0, fmt.Errorf("expected integer, got %T", resp) } }