From f94e48aa032f4578ce3ade24cf3fd9425ae2987d Mon Sep 17 00:00:00 2001 From: Max Lerebourg Date: Wed, 19 Oct 2022 08:22:00 +0200 Subject: [PATCH] not working... --- go.mod | 18 --- pkg/redis/redis.go | 375 +++++++++++++++++++++++++++++++++++++++++++++ vendor/modules.txt | 95 ------------ 3 files changed, 375 insertions(+), 113 deletions(-) create mode 100644 pkg/redis/redis.go diff --git a/go.mod b/go.mod index 1c2707d..f5d4f6d 100644 --- a/go.mod +++ b/go.mod @@ -3,21 +3,3 @@ module github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin go 1.17 require github.com/leprosus/golang-ttl-map v1.1.7 - -require ( - github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.1.2 // indirect - github.com/coreos/go-semver v0.3.0 // indirect - github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/go-redis/redis/v8 v8.11.5 // indirect - github.com/golang/protobuf v1.5.0 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/prometheus/client_golang v1.11.0 // indirect - github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.26.0 // indirect - github.com/prometheus/procfs v0.6.0 // indirect - github.com/sandwich-go/redisson v1.1.16 // indirect - github.com/sandwich-go/rueidis v0.1.11 // indirect - golang.org/x/sys v0.0.0-20220731174439-a90be440212d // indirect - google.golang.org/protobuf v1.27.1 // indirect -) diff --git a/pkg/redis/redis.go b/pkg/redis/redis.go new file mode 100644 index 0000000..ed0c89e --- /dev/null +++ b/pkg/redis/redis.go @@ -0,0 +1,375 @@ +package redis + +// My intepretation of the RESP protocol. +// Referenced from https://redis.io/docs/reference/protocol-spec/ +// 8-7-2022 + +import ( + "errors" + "fmt" + "io" + "log" + "net/textproto" + "net" + "bufio" + "strings" + "sync" +) + +type RespT byte + +const ( + SimpleString RespT = '+' + Error RespT = '-' + Integer RespT = ':' + BulkString RespT = '$' + Array RespT = '*' +) + +type Message interface { + String() string +} + +type MsgSimpleStr string +type MsgError string +type MsgBulkStr string +type MsgInteger int64 +type MsgArray []Message + +func (m MsgInteger) String() string { + return fmt.Sprintf("%d", m) +} + +func (m MsgBulkStr) String() string { + return string(m) +} + +func (m MsgError) String() string { + return string(m) +} + +func (m MsgSimpleStr) String() string { + return string(m) +} + +// func (m MsgArray) String() string { +// return fmt.Sprintf("[%s]", strings.Join(Map(m, Message.String), ",")) +// } + +func (m RedisMessage) String() string { + return m.Choice.String() +} + +type RedisMessage struct { + RedisType RespT + Raw string + Choice Message +} + +func NewParseError(got RespT, expected ...RespT) error { + return fmt.Errorf("got %v but expected %v", got, expected) +} + +func (msg RedisMessage) Symbol() rune { + return rune(msg.RedisType) +} + +func (msg RedisMessage) AsString() (string, error) { + if conv, ok := msg.Choice.(MsgSimpleStr); ok { + return string(conv), nil + } else if conv, ok := msg.Choice.(MsgBulkStr); ok { + return string(conv), nil + } else { + return "", NewParseError(msg.RedisType, SimpleString, BulkString) + } +} + +func (msg RedisMessage) AsInteger() (int64, error) { + if conv, ok := msg.Choice.(MsgInteger); ok { + return int64(conv), nil + } else { + return 0, NewParseError(msg.RedisType, Integer) + } +} + +func (msg RedisMessage) AsError() (error, error) { + if conv, ok := msg.Choice.(MsgError); ok { + return errors.New(string(conv)), nil + } else { + return nil, NewParseError(msg.RedisType, Error) + } +} + +// func (msg RedisMessage) AsArray() ([]Message, error) { +// if conv, ok := (msg.Choice).(MsgArray); ok { +// return conv, nil +// } else { +// return nil, NewParseError(msg.RedisType, Array) +// } +// } + +type RedisReader struct { + Rd *textproto.Reader + Out chan RedisMessage +} + +// Reset the parsing state of the reader +func (rr *RedisReader) Reset() { + +} + +func NewRespReader(tp *textproto.Reader, out chan RedisMessage) RedisReader { + return RedisReader{ + Rd: tp, + Out: out, + } +} + +// Fetch grabs the next incoming RESP type from the IO input +func Fetch(rr *RedisReader) *RedisMessage { + byteCode, err := rr.TryReadByte() + if err != nil { + return nil + } + switch RespT(byteCode) { + case BulkString: + return RespBulkStr(rr) + case SimpleString: + return RespSimpleStr(rr) + case Integer: + return RespInt(rr) + case Error: + return RespError(rr) + // case Array: + // return RespArray(rr) + default: + panic(fmt.Sprint("Unknown bytecode: ", byteCode)) + } +} + +// Scan is the main API to send redis messages out to a channel. +func (rr *RedisReader) Scan() { + rr.Out <- *Fetch(rr) +} + +// RespSimpleStr attempts to parse a string from the resulting line. +// +// TODO: I don't think we need the high level facilities from net/textproto anymore +// since the parsing is quite trivial. +func RespSimpleStr(rr *RedisReader) *RedisMessage { + s, err := rr.Rd.ReadLine() + if err != nil { + log.Printf("Fatal RespSimpleStr: %+v", err) + return nil + } + + return &RedisMessage{ + RedisType: RespT(SimpleString), + Raw: s, + Choice: MsgSimpleStr(s), + } +} + +func RespError(rr *RedisReader) *RedisMessage { + s, err := rr.Rd.ReadLine() + if err != nil { + log.Printf("Fatal RespError: %+v", err) + return nil + } + + return &RedisMessage{ + RedisType: RespT(Error), + Raw: s, + Choice: MsgError(s), + } +} + +// RespSimpleStr attempts to parse a string from the resulting line. +// +// It first expects a length value parsed as an integer, +// then directly copies the subsequent payload into a string +// buffer +func RespBulkStr(rr *RedisReader) *RedisMessage { + len, err := loopReadInt(rr) + if err != nil { + return nil + } + + // Read the string data into buffer + bulkStr := make([]byte, len) + io.ReadFull(rr.Rd.R, bulkStr) + + // Discard two bytes since we don't need the '\r\n' + _, err = rr.Rd.R.Discard(2) + if err != nil { + log.Printf("Fatal: %+v", err) + } + + return &RedisMessage{ + RedisType: BulkString, + Raw: string(bulkStr), + Choice: MsgBulkStr(string(bulkStr)), + } +} + +// RespInt reads an integer from the IO stream +func RespInt(rr *RedisReader) *RedisMessage { + val, err := loopReadInt(rr) + if err != nil { + return nil + } + return &RedisMessage{ + RedisType: Integer, + Raw: fmt.Sprintf("%d", val), + Choice: MsgInteger(val), + } +} + +// RespArray reads an array of RESP objects from the IO stream +// func RespArray(rr *RedisReader) *RedisMessage { +// len, err := loopReadInt(rr) +// if err != nil { +// return nil +// } +// fetched := make([]RedisMessage, 0, len) +// for i := 0; i < len; i++ { +// fetched[i] = *Fetch(rr) +// } +// return &RedisMessage{ +// RedisType: Array, +// Raw: fmt.Sprint(Map(fetched, func(rm RedisMessage) string { return rm.Raw })), +// Choice: MsgArray(Map(fetched, func(rm RedisMessage) Message { return rm.Choice })), +// } +// } + +// loopReadInt reads from IO and returns an integer, discarding \r\n. +func loopReadInt(rr *RedisReader) (int, error) { + val := 0 + for b, _ := rr.TryReadByte(); b != '\r'; b, _ = rr.TryReadByte() { + val = (val * 10) + int(b-byte('0')) + } + _, err := rr.Rd.R.Discard(1) + if err != nil { + return 0, fmt.Errorf("Fatal loopReadInt: %+v", err) + } + return val, nil +} + +// TryReadByte attempts to read a single byte from IO and panics on any error +func (rr *RedisReader) TryReadByte() (byte, error) { + b, err := rr.Rd.R.ReadByte() + if err != nil { + return byte('0'), fmt.Errorf("Fatal TryReadByte: %+v", err) + } + return b, nil +} + +// Map. Your standard good old fashioned generic map function :) +// +// A la []T -> []K +func Map[T any, K any](slice []T, fn func(T) K) []K { + out := make([]K, 0, len(slice)) + for i := range out { + out[i] = fn(slice[i]) + } + return out +} + + + + + + + + +func loopScan(reader *textproto.Reader, msg_ch chan RedisMessage) { + rr := NewRespReader(reader, msg_ch) + for { + rr.Scan() + } +} + +// repl starts an read input loop on the standard input. +// +// Commands are sent out after a new line is parsed. +// +// TODO: Improve ergonomics of the repl. +func repl(input *textproto.Reader, wr *textproto.Writer) { + for { + print("> ") + input_str, err := input.ReadLine() + if len(input_str) > 0 && err == nil { + // The redis server expects arrays of bulk strings for sending commands. + commands := strings.Split(input_str, " ") + // Specify the array length and create the bulk strings + pipe := []string{fmt.Sprintf("*%d", len(commands))} + pipe = append(pipe, createBulkStrings(commands)...) + for _, out := range pipe { + wr.PrintfLine(out) + } + } + } +} + +// createBulkStrings converts normal strings into bulk strings to send to redis. +func createBulkStrings(commands []string) []string { + cmds := make([]string, len(commands)*2) + for i, v := range commands { + cmds[i*2] = fmt.Sprintf("$%d", len(commands[i])) + cmds[(i*2)+1] = v + } + return cmds +} + + +// cmd/client starts a simple Read-Send-Print-Loop (RSPL?) +// with a redis server. +// +// TODO: Implement command flags +func Init(host string, password string) (*textproto.Writer, *textproto.Reader) { + log.Printf("yo") + // Create the connection to the redis server + // TODO: cli, rm hardcode + conn, err := net.Dial("tcp", host) + if err != nil { + log.Printf("[net] unable to connect: %v", err) + return nil, nil + } + defer conn.Close() + + // Create the readers and writers we will pass to our subroutines + msg_channel := make(chan RedisMessage) + write_sock := textproto.NewWriter(bufio.NewWriter(conn)) + read_sock := textproto.NewReader(bufio.NewReader(conn)) + + go runAsWaitGroup( + // Subscribe to redis connection + func() { + loopScan(read_sock, msg_channel) + }, + // Write to output + func() { + for { + msg := <-msg_channel + log.Printf("%v", msg) + } + }, + ).Wait() + log.Printf("yolo") + return write_sock, read_sock +} + +// runAsWaitGroup runs closures within a sync.WaitGroup +// +// Calling .Wait() on the resultant WaitGroup is a blocking operation until all closures finish running. +func runAsWaitGroup(closures ...func()) *sync.WaitGroup { + wg := sync.WaitGroup{} + for _, fn := range closures { + wg.Add(1) + go func(fn func()) { + fn() + wg.Done() + }(fn) + } + return &wg +} \ No newline at end of file diff --git a/vendor/modules.txt b/vendor/modules.txt index 45d67d2..5e64cb5 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,98 +1,3 @@ -# github.com/beorn7/perks v1.0.1 -## explicit; go 1.11 -github.com/beorn7/perks/quantile -# github.com/cespare/xxhash/v2 v2.1.2 -## explicit; go 1.11 -github.com/cespare/xxhash/v2 -# github.com/coreos/go-semver v0.3.0 -## explicit -github.com/coreos/go-semver/semver -# github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f -## explicit -github.com/dgryski/go-rendezvous -# github.com/go-redis/redis/v8 v8.11.5 -## explicit; go 1.17 -github.com/go-redis/redis/v8 -github.com/go-redis/redis/v8/internal -github.com/go-redis/redis/v8/internal/hashtag -github.com/go-redis/redis/v8/internal/hscan -github.com/go-redis/redis/v8/internal/pool -github.com/go-redis/redis/v8/internal/proto -github.com/go-redis/redis/v8/internal/rand -github.com/go-redis/redis/v8/internal/util -# github.com/golang/protobuf v1.5.0 -## explicit; go 1.9 -github.com/golang/protobuf/proto -github.com/golang/protobuf/ptypes -github.com/golang/protobuf/ptypes/any -github.com/golang/protobuf/ptypes/duration -github.com/golang/protobuf/ptypes/timestamp # github.com/leprosus/golang-ttl-map v1.1.7 ## explicit; go 1.15 github.com/leprosus/golang-ttl-map -# github.com/matttproud/golang_protobuf_extensions v1.0.1 -## explicit -github.com/matttproud/golang_protobuf_extensions/pbutil -# github.com/prometheus/client_golang v1.11.0 -## explicit; go 1.13 -github.com/prometheus/client_golang/prometheus -github.com/prometheus/client_golang/prometheus/internal -# github.com/prometheus/client_model v0.2.0 -## explicit; go 1.9 -github.com/prometheus/client_model/go -# github.com/prometheus/common v0.26.0 -## explicit; go 1.11 -github.com/prometheus/common/expfmt -github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg -github.com/prometheus/common/model -# github.com/prometheus/procfs v0.6.0 -## explicit; go 1.13 -github.com/prometheus/procfs -github.com/prometheus/procfs/internal/fs -github.com/prometheus/procfs/internal/util -# github.com/sandwich-go/redisson v1.1.16 -## explicit; go 1.18 -github.com/sandwich-go/redisson -# github.com/sandwich-go/rueidis v0.1.11 -## explicit; go 1.18 -github.com/sandwich-go/rueidis -github.com/sandwich-go/rueidis/internal/cmds -github.com/sandwich-go/rueidis/internal/util -github.com/sandwich-go/rueidis/rueidiscompat -# golang.org/x/sys v0.0.0-20220731174439-a90be440212d -## explicit; go 1.17 -golang.org/x/sys/internal/unsafeheader -golang.org/x/sys/unix -golang.org/x/sys/windows -# google.golang.org/protobuf v1.27.1 -## explicit; go 1.9 -google.golang.org/protobuf/encoding/prototext -google.golang.org/protobuf/encoding/protowire -google.golang.org/protobuf/internal/descfmt -google.golang.org/protobuf/internal/descopts -google.golang.org/protobuf/internal/detrand -google.golang.org/protobuf/internal/encoding/defval -google.golang.org/protobuf/internal/encoding/messageset -google.golang.org/protobuf/internal/encoding/tag -google.golang.org/protobuf/internal/encoding/text -google.golang.org/protobuf/internal/errors -google.golang.org/protobuf/internal/filedesc -google.golang.org/protobuf/internal/filetype -google.golang.org/protobuf/internal/flags -google.golang.org/protobuf/internal/genid -google.golang.org/protobuf/internal/impl -google.golang.org/protobuf/internal/order -google.golang.org/protobuf/internal/pragma -google.golang.org/protobuf/internal/set -google.golang.org/protobuf/internal/strs -google.golang.org/protobuf/internal/version -google.golang.org/protobuf/proto -google.golang.org/protobuf/reflect/protodesc -google.golang.org/protobuf/reflect/protoreflect -google.golang.org/protobuf/reflect/protoregistry -google.golang.org/protobuf/runtime/protoiface -google.golang.org/protobuf/runtime/protoimpl -google.golang.org/protobuf/types/descriptorpb -google.golang.org/protobuf/types/known/anypb -google.golang.org/protobuf/types/known/durationpb -google.golang.org/protobuf/types/known/timestamppb