diff --git a/mc/client/login.go b/mc/client/login.go index 5cf2c2d..ea23ce1 100644 --- a/mc/client/login.go +++ b/mc/client/login.go @@ -14,6 +14,7 @@ import ( "github.com/admin-else/strom/mc/proto" "github.com/admin-else/strom/mc/proto_base" "github.com/admin-else/strom/mc/proto_generated/v1_21_8" + "github.com/admin-else/strom/mc/proto_generated/v26_2" "github.com/admin-else/strom/mc/text" ) @@ -121,6 +122,14 @@ func (s *LoginClient) OnSuccess(success *v1_21_8.LoginToClientPacketSuccess) (er return } +func (s *LoginClient) OnSuccess26_2(success *v26_2.LoginToClientPacketSuccess) (err error) { + return s.OnSuccess(&v1_21_8.LoginToClientPacketSuccess{ + Uuid: success.Uuid, + Username: success.Username, + Properties: success.Properties, + }) +} + func (s *LoginClient) OnStart() (err error) { return } @@ -135,7 +144,8 @@ func LoginRaw(c *proto.Conn, account *api.Account) (err error) { lc.RegisterCriticalUntilLatest(lc.OnCompress) lc.RegisterCriticalUntilLatest(lc.OnEncrypt) lc.RegisterCriticalUntilLatest(lc.OnDisconnect) - lc.RegisterCriticalUntilLatest(lc.OnSuccess) + lc.RegisterCriticalUntil("26.1", lc.OnSuccess) + lc.RegisterCriticalUntilLatest(lc.OnSuccess26_2) p, err := MakeHandshakePacket(lc.Conn, proto_base.Login) if err != nil { diff --git a/mc/client/login_test.go b/mc/client/login_test.go new file mode 100644 index 0000000..456fb21 --- /dev/null +++ b/mc/client/login_test.go @@ -0,0 +1,55 @@ +package client_test + +import ( + "reflect" + "testing" + + "github.com/admin-else/strom/mc/data" + "github.com/admin-else/strom/mc/proto" + "github.com/admin-else/strom/mc/proto_base" + "github.com/admin-else/strom/mc/proto_generated/v1_21_8" +) + +func assertClientSendConvertible(t *testing.T, srcType reflect.Type, name string, state proto_base.State, direction proto_base.Direction, targetVersion int32) { + t.Helper() + targetInfo, ok := proto.LookupPacketInfoByNameProtocolVersionStateAndDirection(name, targetVersion, state, direction) + if !ok { + t.Fatalf("packet %s not found for version %d", name, targetVersion) + } + targetType := reflect.TypeOf(targetInfo.Type) + if !proto.SmartConvertibleTo(srcType, targetType) { + t.Fatalf("%s is not smart-convertible from v1_21_8 to version %d", name, targetVersion) + } +} + +func assertClientReceiveConvertible(t *testing.T, dstType reflect.Type, name string, state proto_base.State, direction proto_base.Direction, targetVersion int32) { + t.Helper() + targetInfo, ok := proto.LookupPacketInfoByNameProtocolVersionStateAndDirection(name, targetVersion, state, direction) + if !ok { + t.Fatalf("packet %s not found for version %d", name, targetVersion) + } + targetType := reflect.TypeOf(targetInfo.Type) + if !proto.SmartConvertibleTo(targetType, dstType) { + t.Fatalf("%s is not smart-convertible from version %d to v1_21_8", name, targetVersion) + } +} + +func TestLoginClientSendAssumptions(t *testing.T) { + v26_1Version := data.MustLookupProtocolVersion("26.1") + + // Packets sent by LoginClient as v1_21_8. + assertClientSendConvertible(t, reflect.TypeFor[*v1_21_8.HandshakingToServerPacketSetProtocol](), "set_protocol", proto_base.Handshaking, proto_base.ToServer, v26_1Version) + assertClientSendConvertible(t, reflect.TypeFor[*v1_21_8.LoginToServerPacketLoginStart](), "login_start", proto_base.Login, proto_base.ToServer, v26_1Version) + assertClientSendConvertible(t, reflect.TypeFor[*v1_21_8.LoginToServerPacketLoginAcknowledged](), "login_acknowledged", proto_base.Login, proto_base.ToServer, v26_1Version) + assertClientSendConvertible(t, reflect.TypeFor[*v1_21_8.LoginToServerPacketEncryptionBegin](), "encryption_begin", proto_base.Login, proto_base.ToServer, v26_1Version) +} + +func TestLoginClientReceiveAssumptions(t *testing.T) { + v26_1Version := data.MustLookupProtocolVersion("26.1") + + // Packets received by LoginClient via RegisterCriticalUntilLatest. + // login_success is excluded because 26.2 is not convertible. + assertClientReceiveConvertible(t, reflect.TypeFor[*v1_21_8.LoginToClientPacketCompress](), "compress", proto_base.Login, proto_base.ToClient, v26_1Version) + assertClientReceiveConvertible(t, reflect.TypeFor[*v1_21_8.LoginToClientPacketEncryptionBegin](), "encryption_begin", proto_base.Login, proto_base.ToClient, v26_1Version) + assertClientReceiveConvertible(t, reflect.TypeFor[*v1_21_8.LoginToClientPacketDisconnect](), "disconnect", proto_base.Login, proto_base.ToClient, v26_1Version) +} diff --git a/mc/proto/connection.go b/mc/proto/connection.go index 0b33486..02321bb 100644 --- a/mc/proto/connection.go +++ b/mc/proto/connection.go @@ -166,13 +166,14 @@ func (c *Conn) ActivateReceiveRoutine() (chSending <-chan any) { func (c *Conn) translatePacketVersion(packet proto_base.EncodeDecodeAble, packetInfo proto_base.PacketInfo) (convertedPacket proto_base.EncodeDecodeAble, packetInfoReal proto_base.PacketInfo, err error) { packetInfoReal, found := LookupPacketInfoByNameProtocolVersionStateAndDirection(packetInfo.Name, c.ProtocolVersion, c.State(), packetInfo.Direction) if !found { - err = PacketDoesntExistInFutureVersionErr + err = fmt.Errorf("%w: %s from pv%d to pv%d in %s/%s not found", PacketDoesntExistInFutureVersionErr, packetInfo.Name, packetInfo.ProtocolVersion, c.ProtocolVersion, packetInfo.State, packetInfo.Direction) return } dstType := reflect.TypeOf(packetInfoReal.Type) - if !SmartConvertibleTo(reflect.TypeOf(packetInfo.Type), dstType) { - err = CantTranslateErr + srcType := reflect.TypeOf(packetInfo.Type) + if !SmartConvertibleTo(srcType, dstType) { + err = fmt.Errorf("%w: %s from pv%d to pv%d in %s/%s (src=%s dst=%s)", CantTranslateErr, packetInfo.Name, packetInfo.ProtocolVersion, c.ProtocolVersion, packetInfo.State, packetInfo.Direction, srcType, dstType) return } // can't fail cause pacetinfo.type is proto_base.EncodeDecodeAble diff --git a/mc/proto/smart_convert_test.go b/mc/proto/smart_convert_test.go index 5187bc6..ebd3773 100644 --- a/mc/proto/smart_convert_test.go +++ b/mc/proto/smart_convert_test.go @@ -8,6 +8,7 @@ import ( "github.com/admin-else/strom/mc/proto_generated/v1_21_11" "github.com/admin-else/strom/mc/proto_generated/v1_21_8" "github.com/admin-else/strom/mc/proto_generated/v1_21_9" + "github.com/admin-else/strom/mc/proto_generated/v26_2" ) func TestCanSmartConvert(t *testing.T) { @@ -66,3 +67,15 @@ func TestSmartConvert(t *testing.T) { } } } + +func TestLoginSuccessNotConvertibleAcross26_2(t *testing.T) { + // login_success gained an extra SessionId field in 26.2, so it is not + // smart-convertible in either direction. This is why the login server has + // version-gated send code and the login client has a dedicated 26.2 handler. + if proto2.SmartConvertibleTo(reflect.TypeFor[*v1_21_8.LoginToClientPacketSuccess](), reflect.TypeFor[*v26_2.LoginToClientPacketSuccess]()) { + t.Fatal("expected v1_21_8 login_success NOT to be convertible to v26_2") + } + if proto2.SmartConvertibleTo(reflect.TypeFor[*v26_2.LoginToClientPacketSuccess](), reflect.TypeFor[*v1_21_8.LoginToClientPacketSuccess]()) { + t.Fatal("expected v26_2 login_success NOT to be convertible to v1_21_8") + } +} diff --git a/mc/server/login_server.go b/mc/server/login_server.go index 6ec014f..c61b6a5 100644 --- a/mc/server/login_server.go +++ b/mc/server/login_server.go @@ -12,10 +12,12 @@ import ( api2 "github.com/admin-else/strom/mc/api" "github.com/admin-else/strom/mc/crypto" + "github.com/admin-else/strom/mc/data" "github.com/admin-else/strom/mc/event" "github.com/admin-else/strom/mc/proto" "github.com/admin-else/strom/mc/proto_base" "github.com/admin-else/strom/mc/proto_generated/v1_21_8" + "github.com/admin-else/strom/mc/proto_generated/v26_2" "github.com/google/uuid" ) @@ -183,11 +185,20 @@ func (l *LoginServer) FinishLogin() (err error) { if l.Given == nil { l.Given = &l.Requested } - err = l.Send(&v1_21_8.LoginToClientPacketSuccess{ - Uuid: l.Given.UUID, - Username: l.Given.Name, - Properties: nil, - }) + if l.ProtocolVersion >= data.MustLookupProtocolVersion("26.2") { + err = l.Send(&v26_2.LoginToClientPacketSuccess{ + Uuid: l.Given.UUID, + Username: l.Given.Name, + Properties: nil, + SessionId: uuid.New(), + }) + } else { + err = l.Send(&v1_21_8.LoginToClientPacketSuccess{ + Uuid: l.Given.UUID, + Username: l.Given.Name, + Properties: nil, + }) + } return } @@ -195,9 +206,6 @@ type LoginServerSetting func(*LoginServer) func WithOtherAccount(a *api2.Account) LoginServerSetting { return func(s *LoginServer) { - if s.Given == nil { - return - } s.Given = &NameAndUUID{ Name: a.Name, UUID: a.Uuid, diff --git a/mc/server/login_server_test.go b/mc/server/login_server_test.go new file mode 100644 index 0000000..6638c4d --- /dev/null +++ b/mc/server/login_server_test.go @@ -0,0 +1,60 @@ +package server_test + +import ( + "reflect" + "testing" + + "github.com/admin-else/strom/mc/data" + "github.com/admin-else/strom/mc/proto" + "github.com/admin-else/strom/mc/proto_base" + "github.com/admin-else/strom/mc/proto_generated/v1_21_8" +) + +func assertSendConvertible(t *testing.T, srcType reflect.Type, name string, state proto_base.State, direction proto_base.Direction, targetVersion int32) { + t.Helper() + targetInfo, ok := proto.LookupPacketInfoByNameProtocolVersionStateAndDirection(name, targetVersion, state, direction) + if !ok { + t.Fatalf("packet %s not found for version %d", name, targetVersion) + } + targetType := reflect.TypeOf(targetInfo.Type) + if !proto.SmartConvertibleTo(srcType, targetType) { + t.Fatalf("%s is not smart-convertible from v1_21_8 to version %d", name, targetVersion) + } +} + +func assertReceiveConvertible(t *testing.T, dstType reflect.Type, name string, state proto_base.State, direction proto_base.Direction, targetVersion int32) { + t.Helper() + targetInfo, ok := proto.LookupPacketInfoByNameProtocolVersionStateAndDirection(name, targetVersion, state, direction) + if !ok { + t.Fatalf("packet %s not found for version %d", name, targetVersion) + } + targetType := reflect.TypeOf(targetInfo.Type) + if !proto.SmartConvertibleTo(targetType, dstType) { + t.Fatalf("%s is not smart-convertible from version %d to v1_21_8", name, targetVersion) + } +} + +func TestLoginServerSendAssumptions(t *testing.T) { + v26_1Version := data.MustLookupProtocolVersion("26.1") + + // Packets sent by LoginServer as v1_21_8 must be convertible to every version + // they are actually sent to. login_success is excluded because it is not + // convertible across the 26.2 boundary; it has dedicated version-gated send code. + assertSendConvertible(t, reflect.TypeFor[*v1_21_8.StatusToClientPacketServerInfo](), "server_info", proto_base.Status, proto_base.ToClient, v26_1Version) + assertSendConvertible(t, reflect.TypeFor[*v1_21_8.StatusToClientPacketPing](), "ping", proto_base.Status, proto_base.ToClient, v26_1Version) + assertSendConvertible(t, reflect.TypeFor[*v1_21_8.LoginToClientPacketCompress](), "compress", proto_base.Login, proto_base.ToClient, v26_1Version) + assertSendConvertible(t, reflect.TypeFor[*v1_21_8.LoginToClientPacketEncryptionBegin](), "encryption_begin", proto_base.Login, proto_base.ToClient, v26_1Version) +} + +func TestLoginServerReceiveAssumptions(t *testing.T) { + v26_1Version := data.MustLookupProtocolVersion("26.1") + + // Packets received by LoginServer via RegisterCriticalUntilLatest must be + // convertible from each later version back to v1_21_8. + assertReceiveConvertible(t, reflect.TypeFor[*v1_21_8.HandshakingToServerPacketSetProtocol](), "set_protocol", proto_base.Handshaking, proto_base.ToServer, v26_1Version) + assertReceiveConvertible(t, reflect.TypeFor[*v1_21_8.StatusToServerPacketPingStart](), "ping_start", proto_base.Status, proto_base.ToServer, v26_1Version) + assertReceiveConvertible(t, reflect.TypeFor[*v1_21_8.StatusToServerPacketPing](), "ping", proto_base.Status, proto_base.ToServer, v26_1Version) + assertReceiveConvertible(t, reflect.TypeFor[*v1_21_8.LoginToServerPacketLoginStart](), "login_start", proto_base.Login, proto_base.ToServer, v26_1Version) + assertReceiveConvertible(t, reflect.TypeFor[*v1_21_8.LoginToServerPacketEncryptionBegin](), "encryption_begin", proto_base.Login, proto_base.ToServer, v26_1Version) + assertReceiveConvertible(t, reflect.TypeFor[*v1_21_8.LoginToServerPacketLoginAcknowledged](), "login_acknowledged", proto_base.Login, proto_base.ToServer, v26_1Version) +}