package src import ( "bufio" "bytes" "context" "fmt" "hash/crc32" "io" "log" "net" "strconv" ) func adapt(ctx context.Context, config Config, conn net.Conn) error { reader := bufio.NewReader(conn) for ctx.Err() == nil { if done, err := func() (bool, error) { raw, message, err := readMessage(reader) log.Printf("%q", raw) if err != nil { if err == io.EOF { return true, nil } return true, err } if len(message) > 0 { hashKey := message[max(0, len(message)-1)].(string) hash := int(crc32.ChecksumIEEE([]byte(hashKey))) forward := config.forwards[hash%len(config.forwards)] forwardCon := forward.Get() if forwardCon == nil { return true, io.EOF } forwardConn := forwardCon.(net.Conn) if _, err := forwardConn.Write(raw); err != nil { return true, err } replyer := bufio.NewReader(forwardConn) raw, _, err := readMessage(replyer) if err != nil { return true, err } log.Printf("%q", raw) if _, err := conn.Write(raw); err != nil { return true, err } } return false, nil }(); err != nil { return err } else if done { return nil } } return io.EOF } func readMessage(reader *bufio.Reader) ([]byte, []any, error) { w := bytes.NewBuffer(nil) firstLine, _, err := reader.ReadLine() w.Write(firstLine) if err != nil { return w.Bytes(), nil, err } firstLine = bytes.TrimSuffix(firstLine, []byte("\r\n")) if len(firstLine) == 0 { return w.Bytes(), nil, nil } switch firstLine[0] { case '+': // simple string, like +OK return w.Bytes(), []any{string(firstLine[1:])}, nil case '-': // simple error, like -message return w.Bytes(), nil, fmt.Errorf("error: %s", firstLine[1:]) case ':': // number, like /[+-][0-9]+/ firstLine = bytes.TrimPrefix(firstLine[1:], []byte("+")) n, err := strconv.Atoi(string(firstLine[1:])) return w.Bytes(), []any{n}, err case '$': // long string, like $-1 for nil, like $LEN\r\nSTRING\r\n if firstLine[1] == '-' { return w.Bytes(), []any{nil}, nil } nextLine, _, err := reader.ReadLine() w.Write(nextLine) nextLine = bytes.TrimSuffix(nextLine, []byte("\r\n")) return w.Bytes(), []any{string(nextLine)}, err case '*': // array, like *-1 for nil, like *4 for [1,2,3,4] n, err := strconv.Atoi(string(firstLine[1:])) if err != nil { return w.Bytes(), nil, err } else if n == -1 { return w.Bytes(), nil, nil } var result []any for i := 0; i < n; i++ { moreBytes, more, err := readMessage(reader) w.Write(moreBytes) if err != nil { return w.Bytes(), nil, err } result = append(result, more...) } return w.Bytes(), result, nil case '_': // nil return w.Bytes(), nil, nil case '#': // boolean, like #t or #f return w.Bytes(), []any{firstLine[1] == 't'}, nil case ',': // double log.Fatal("not impl") } log.Fatalf("not impl: %q", firstLine) return w.Bytes(), nil, nil }