Files
strom/proto/connection.go
T

288 lines
7.3 KiB
Go

package proto
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"log/slog"
"reflect"
"slices"
"strings"
"sync"
"github.com/admin-else/strom/data"
"github.com/admin-else/strom/event"
"github.com/admin-else/strom/proto_base"
"github.com/admin-else/strom/proto_generated"
)
const (
BufferNPackets = 100
)
var (
BadPacketTypeErr = errors.New("bad packet type")
BadPacketIdErr = errors.New("bad packet id")
PacketNotFullyDecodedErr = errors.New("packet not fully decoded")
WrongStateErr = errors.New("wrong state")
WrongDirectionErr = errors.New("wrong direction")
CantTranslateErr = errors.New("cant translate packet")
PacketDoesntExistInFutureVersionErr = errors.New("packet doesnt exist in future version")
NoHandlerRegisteredErr = errors.New("no handler registered")
)
// LoginTick is used as a subsite for the old event system because it is very useful in login contexts
type LoginTick struct{}
var LatestVersion = proto_generated.SupportedVersions[len(proto_generated.SupportedVersions)-1]
var EarliestVersion = proto_generated.SupportedVersions[0]
// UnCodablePacket represents a packet that could not be decoded.
type UnCodablePacket struct {
Err error
Data []byte
Info proto_base.PacketInfo
}
func (u *UnCodablePacket) Encode(w io.Writer) (err error) {
_, err = w.Write(u.Data)
return
}
func (u *UnCodablePacket) Decode(r io.ReadSeeker) (err error) {
err = errors.New("how did you even get here")
return
}
type Conn struct {
RawConn
*event.Loop
state proto_base.State
stateMutex sync.RWMutex
Actor proto_base.Actor
Version string
ProtocolVersion int32
DebugPrintPackets []string
asyncPackets bool
asyncPacketsMutex sync.RWMutex
Log *slog.Logger
}
func (c *Conn) StartConn() (err error) {
c.RegisterEventSource(c.ActivateReceiveRoutine())
c.RegisterCritical(c.OnLoginTick)
err = c.Loop.StartLoop()
c.Loop = event.NewLoop()
return
}
func (c *Conn) OnLoginTick(_ LoginTick) (err error) {
packet, err := c.Receive()
if err != nil {
return
}
err = c.Fire(packet)
return
}
func (c *Conn) SetState(state proto_base.State) {
c.stateMutex.Lock()
defer c.stateMutex.Unlock()
c.state = state
}
func (c *Conn) State() proto_base.State {
c.stateMutex.RLock()
defer c.stateMutex.RUnlock()
return c.state
}
func (c *Conn) SetAsyncPackets(asyncPackets bool) {
c.asyncPacketsMutex.Lock()
defer c.asyncPacketsMutex.Unlock()
c.asyncPackets = asyncPackets
}
func (c *Conn) GetAsyncPackets() bool {
c.asyncPacketsMutex.RLock()
defer c.asyncPacketsMutex.RUnlock()
return c.asyncPackets
}
func (c *Conn) SetVersion(version string) (err error) {
versionData, err := data.LookUpVersionByName(version)
if err != nil {
return
}
c.ProtocolVersion = versionData.Version
c.Version = version
return
}
func (c *Conn) SetProtocolVersion(version int32) (err error) {
versionData, err := data.LookUpVersionByProtocolVersion(version)
if err != nil {
return
}
c.ProtocolVersion = versionData.Version
c.Version = versionData.MinecraftVersion
return
}
// ActivateReceiveRoutine activates the receive-routine.
// It ensures that the receive-routine is only started once and creates a buffered channel for packet handling.
func (c *Conn) ActivateReceiveRoutine() (chSending <-chan any) {
// This expects you to start a new routine when switching into play state
ch := make(chan any, BufferNPackets)
go func() {
ctx := c.Ctx // this prevents c.Ctx being reassigned
for {
select {
case <-ctx.Done():
return
default:
if !c.GetAsyncPackets() {
ch <- LoginTick{}
} else {
packet, err := c.Receive()
if err != nil {
ch <- err
return
}
ch <- packet
}
}
}
}()
chSending = ch
return
}
func (c *Conn) translatePacketVersion(packet proto_base.EncodeDecodeAble, packetInfo proto_base.PacketInfo) (convertedPacket proto_base.EncodeDecodeAble, packetInfoReal proto_base.PacketInfo, err error) {
packetInfoReal, found := LookupPacketInfoByNameProtocolVersionAndState(packetInfo.Name, c.ProtocolVersion, c.State())
if !found {
err = PacketDoesntExistInFutureVersionErr
return
}
dstType := reflect.TypeOf(packetInfoReal.Type)
if !SmartConvertibleTo(reflect.TypeOf(packetInfo.Type), dstType) {
err = CantTranslateErr
return
}
// can't fail cause pacetinfo.type is proto_base.EncodeDecodeAble
convertedPacket = SmartConvert(reflect.ValueOf(packet), dstType).Interface().(proto_base.EncodeDecodeAble)
return
}
func (c *Conn) sendRegisteredPacket(packet proto_base.EncodeDecodeAble) (err error) {
i, ok := LookupPacketInfoByTypeAndState(packet, c.State())
if !ok {
err = BadPacketTypeErr
return
}
if i.Direction != c.Actor.SendDirection() {
err = WrongDirectionErr
return
}
if i.State != c.State() {
err = WrongStateErr
return
}
if i.ProtocolVersion != c.ProtocolVersion {
packet, i, err = c.translatePacketVersion(packet, i)
if err != nil {
return
}
}
var packetBuff = bytes.NewBuffer(nil)
err = proto_base.EncodeVarInt(packetBuff, i.PacketId)
if err != nil {
return
}
err = packet.Encode(packetBuff)
if err != nil {
return
}
debugPrint := slog.Default().Enabled(context.Background(), slog.LevelDebug) &&
slices.ContainsFunc(c.DebugPrintPackets, func(s string) bool { return strings.Contains(i.Name, s) })
if debugPrint {
c.Log.Debug("send", "actor", c.Actor, "packet", fmt.Sprintf("%#v", packet))
}
return c.SendRaw(packetBuff.Bytes())
}
func (c *Conn) Send(packet proto_base.EncodeDecodeAble) (err error) {
switch packet := packet.(type) {
case *UnCodablePacket:
err = c.SendRaw(packet.Data)
default:
return c.sendRegisteredPacket(packet)
}
return
}
func (c *Conn) Receive() (packet proto_base.EncodeDecodeAble, err error) {
if c.R == nil {
return
}
packetBytes, err := c.ReceiveRaw()
if err != nil {
return
}
b := bytes.NewReader(packetBytes)
id, err := proto_base.DecodeVarInt(b)
if err != nil {
return
}
i, ok := LookUpTypeByPacketInfo(c.Actor.ReceiveDirection(), c.State(), id, c.ProtocolVersion)
if !ok {
packet = &UnCodablePacket{Err: BadPacketIdErr, Data: packetBytes, Info: i}
return
}
packet = reflect.New(reflect.TypeOf(i.Type).Elem()).Interface().(proto_base.EncodeDecodeAble)
debugPrint := slog.Default().Enabled(context.Background(), slog.LevelDebug) &&
slices.ContainsFunc(c.DebugPrintPackets, func(s string) bool { return strings.Contains(i.Name, s) })
isListenerRegistered := c.IsTypeRegistered(reflect.TypeOf(packet))
if !isListenerRegistered && !debugPrint {
packet = &UnCodablePacket{Err: NoHandlerRegisteredErr, Data: packetBytes, Info: i}
return
}
err = packet.Decode(b)
if err != nil {
packet = &UnCodablePacket{Err: err, Data: packetBytes, Info: i}
err = nil
return
}
if b.Len() != 0 {
err = nil
packet = &UnCodablePacket{Err: PacketNotFullyDecodedErr, Data: packetBytes, Info: i}
return
}
if debugPrint {
c.Log.Debug("recv", "packet", fmt.Sprintf("%#v", packet), "isListenerRegistered", isListenerRegistered)
}
return
}
func NewConn() (ret *Conn) {
return NewConnCtx(context.Background())
}
func NewConnCtx(ctx context.Context) (ret *Conn) {
ret = &Conn{}
ret.SetState(proto_base.Handshaking)
ret.SetCompressionThreshold(-1)
err := ret.SetVersion(LatestVersion)
if err != nil {
panic(err)
}
ret.Loop = event.NewLoopCtx(ctx)
ret.Log = slog.Default()
return
}