diff --git a/connection.go b/connection.go new file mode 100644 index 0000000..c855423 --- /dev/null +++ b/connection.go @@ -0,0 +1,108 @@ +package strom + +import ( + "bytes" + "errors" + "io" + "net" + + "github.com/admin-else/queser" + "github.com/admin-else/queser/generated" +) + +var BadPacketTypeError = errors.New("bad packet type") + +type Actor int + +const ( + Server Actor = iota + Client +) + +func (a Actor) SendDirection() queser.Direction { + switch a { + case Server: + return queser.ToClient + case Client: + return queser.ToServer + default: + panic("invalid actor") + } +} + +func (a Actor) ReceiveDirection() queser.Direction { + switch a { + case Server: + return queser.ToServer + case Client: + return queser.ToClient + default: + panic("invalid actor") + } +} + +type Connection struct { + io.ReadWriteCloser + CompressionThreshold int32 + State queser.State + Actor Actor + Version string +} + +func (c *Connection) Send(packet any) (err error) { + packetIdentifier := generated.TypeToPacketIdentifier(c.Version, c.Actor.SendDirection(), c.State, packet) + if packetIdentifier == "" { + err = BadPacketTypeError + return + } + rawPacketBuffer := bytes.NewBuffer(nil) + err = generated.EncodePacket(c.Version, c.Actor.SendDirection(), c.State, packetIdentifier, packet, rawPacketBuffer) + if err != nil { + return + } + var packetBytes []byte + if c.CompressionThreshold > 0 { + panic("todo") + } else { + packetBytes = rawPacketBuffer.Bytes() + } + err = queser.VarInt(len(packetBytes)).Encode(c) + if err != nil { + return + } + _, err = c.Write(packetBytes) + return +} + +func (c *Connection) Receive() (packet any, err error) { + var packetLen queser.VarInt + packetLen, err = packetLen.Decode(c) + if err != nil { + return + } + rawPacketBytes, err := io.ReadAll(io.LimitReader(c, int64(packetLen))) + if err != nil { + return + } + var packetBytes []byte + if c.CompressionThreshold > 0 { + panic("todo") + } else { + packetBytes = rawPacketBytes + } + packet, err = generated.DecodePacket(c.Version, c.Actor.ReceiveDirection(), c.State, bytes.NewReader(packetBytes)) + return +} + +func ClientConnect(addr string) (ret *Connection, err error) { + ret = &Connection{} + ret.Version = "1.21.8" + ret.State = queser.Handshaking + ret.CompressionThreshold = -1 + ret.Actor = Client + ret.ReadWriteCloser, err = net.Dial("tcp", addr) + if err != nil { + return + } + return +} diff --git a/event_bus.go b/event_bus.go new file mode 100644 index 0000000..0ebdc3c --- /dev/null +++ b/event_bus.go @@ -0,0 +1,58 @@ +package strom + +import ( + "errors" + "reflect" +) + +type HandlerInst interface { + Default(event any) (err error) +} + +type OnStart struct{} + +var HandlerDone = errors.New("handler done") + +func FindHandlers(inst HandlerInst) (handlers map[reflect.Type]reflect.Value) { + handlers = make(map[reflect.Type]reflect.Value) + t := reflect.TypeOf(inst) + v := reflect.ValueOf(inst) + for i := 0; i < t.NumMethod(); i++ { + method := t.Method(i) + if method.Type.NumIn() != 2 || method.Type.NumOut() != 1 { + continue + } + if method.Type.Out(0) != reflect.TypeFor[error]() { + continue + } + eventType := method.Type.In(1) + handlers[eventType] = v.Method(i) + } + return +} + +func FireEvent(inst HandlerInst, event any, handlers map[reflect.Type]reflect.Value) (err error) { + if handler, ok := handlers[reflect.TypeOf(event)]; ok { + errV := handler.Call([]reflect.Value{reflect.ValueOf(event)})[0] + if !errV.IsNil() { + err = errV.Interface().(error) + } + } else { + err = inst.Default(event) + } + return +} + +func (c *Connection) Start(inst HandlerInst) (err error) { + handlers := FindHandlers(inst) + err = FireEvent(inst, OnStart{}, handlers) + for err == nil { + var event any + event, err = c.Receive() + if err != nil { + return + } + err = FireEvent(inst, event, handlers) + } + return +} diff --git a/examples/status/main.go b/examples/status/main.go new file mode 100644 index 0000000..3f2eb73 --- /dev/null +++ b/examples/status/main.go @@ -0,0 +1,52 @@ +package main + +import ( + "fmt" + "strom" + + "github.com/admin-else/queser" + "github.com/admin-else/queser/generated/v1_21_8" +) + +type StatusClient struct { + *strom.Connection +} + +func (s *StatusClient) Default(event any) (err error) { + err = fmt.Errorf("unexpected event: %v", event) + return +} + +func (s *StatusClient) OnStart(_ strom.OnStart) (err error) { + err = s.Send(v1_21_8.HandshakingToServerPacketSetProtocol{ + ProtocolVersion: 772, + ServerHost: "127.0.0.1", + ServerPort: 25566, + NextState: queser.VarInt(queser.Status), + }) + if err != nil { + return + } + s.State = queser.Status + err = s.Send(v1_21_8.StatusToServerPacketPingStart{}) + return +} + +func (s *StatusClient) OnStatus(p v1_21_8.StatusToClientPacketServerInfo) (err error) { + fmt.Println(p) + err = strom.HandlerDone + return +} + +func must(err error) { + if err != nil { + panic(err) + } +} + +func main() { + c, err := strom.ClientConnect("127.0.0.1:25566") + must(err) + err = c.Start(&StatusClient{c}) + must(err) +} diff --git a/go.mod b/go.mod index 1f882a8..c0d3625 100644 --- a/go.mod +++ b/go.mod @@ -3,3 +3,7 @@ module strom replace github.com/admin-else/queser => ../queser go 1.24 + +require github.com/admin-else/queser v0.0.0-00010101000000-000000000000 + +require github.com/google/uuid v1.6.0 // indirect diff --git a/main.go b/main.go deleted file mode 100644 index 493abc5..0000000 --- a/main.go +++ /dev/null @@ -1,57 +0,0 @@ -package main - -import ( - "io" - "log" - "net" -) - -func OnConnect(conn net.Conn) (err error) { - target, err := net.Dial("tcp", "127.0.0.1:25566") - if err != nil { - return - } - errChan := make(chan error) - go func() { - _, innerErr := io.Copy(target, conn) - errChan <- innerErr - }() - go func() { - _, innerErr := io.Copy(conn, target) - errChan <- innerErr - }() - return <-errChan -} - -func OnConnectWrapper(conn net.Conn) { - defer conn.Close() - err := OnConnect(conn) - if err != nil { - log.Println("Error: ", err) - } -} - -func startListener() (err error) { - var l net.Listener - l, err = net.Listen("tcp", ":25565") - if err != nil { - return - } - defer l.Close() - for { - var client net.Conn - client, err = l.Accept() - if err != nil { - break - } - go OnConnectWrapper(client) - } - return -} - -func main() { - err := startListener() - if err != nil { - panic(err) - } -}