Fix 26.2 login_success protocol handling and add convertibility tests

- Server sends v26_2 login_success with SessionId for protocol >= 776

- Client adds v26_2 login_success handler and scopes v1_21_8 handler to <= 26.1

- Fix WithOtherAccount guard in login server

- Add smart-convert assumption tests for login server/client

- Improve translatePacketVersion error messages
This commit is contained in:
adman
2026-07-23 17:00:13 +02:00
parent 94301a4a50
commit b90830881f
6 changed files with 159 additions and 12 deletions
+11 -1
View File
@@ -14,6 +14,7 @@ import (
"github.com/admin-else/strom/mc/proto" "github.com/admin-else/strom/mc/proto"
"github.com/admin-else/strom/mc/proto_base" "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/v1_21_8"
"github.com/admin-else/strom/mc/proto_generated/v26_2"
"github.com/admin-else/strom/mc/text" "github.com/admin-else/strom/mc/text"
) )
@@ -121,6 +122,14 @@ func (s *LoginClient) OnSuccess(success *v1_21_8.LoginToClientPacketSuccess) (er
return 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) { func (s *LoginClient) OnStart() (err error) {
return return
} }
@@ -135,7 +144,8 @@ func LoginRaw(c *proto.Conn, account *api.Account) (err error) {
lc.RegisterCriticalUntilLatest(lc.OnCompress) lc.RegisterCriticalUntilLatest(lc.OnCompress)
lc.RegisterCriticalUntilLatest(lc.OnEncrypt) lc.RegisterCriticalUntilLatest(lc.OnEncrypt)
lc.RegisterCriticalUntilLatest(lc.OnDisconnect) 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) p, err := MakeHandshakePacket(lc.Conn, proto_base.Login)
if err != nil { if err != nil {
+55
View File
@@ -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)
}
+4 -3
View File
@@ -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) { 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) packetInfoReal, found := LookupPacketInfoByNameProtocolVersionStateAndDirection(packetInfo.Name, c.ProtocolVersion, c.State(), packetInfo.Direction)
if !found { 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 return
} }
dstType := reflect.TypeOf(packetInfoReal.Type) dstType := reflect.TypeOf(packetInfoReal.Type)
if !SmartConvertibleTo(reflect.TypeOf(packetInfo.Type), dstType) { srcType := reflect.TypeOf(packetInfo.Type)
err = CantTranslateErr 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 return
} }
// can't fail cause pacetinfo.type is proto_base.EncodeDecodeAble // can't fail cause pacetinfo.type is proto_base.EncodeDecodeAble
+13
View File
@@ -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_11"
"github.com/admin-else/strom/mc/proto_generated/v1_21_8" "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/v1_21_9"
"github.com/admin-else/strom/mc/proto_generated/v26_2"
) )
func TestCanSmartConvert(t *testing.T) { 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")
}
}
+11 -3
View File
@@ -12,10 +12,12 @@ import (
api2 "github.com/admin-else/strom/mc/api" api2 "github.com/admin-else/strom/mc/api"
"github.com/admin-else/strom/mc/crypto" "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/event"
"github.com/admin-else/strom/mc/proto" "github.com/admin-else/strom/mc/proto"
"github.com/admin-else/strom/mc/proto_base" "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/v1_21_8"
"github.com/admin-else/strom/mc/proto_generated/v26_2"
"github.com/google/uuid" "github.com/google/uuid"
) )
@@ -183,11 +185,20 @@ func (l *LoginServer) FinishLogin() (err error) {
if l.Given == nil { if l.Given == nil {
l.Given = &l.Requested l.Given = &l.Requested
} }
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{ err = l.Send(&v1_21_8.LoginToClientPacketSuccess{
Uuid: l.Given.UUID, Uuid: l.Given.UUID,
Username: l.Given.Name, Username: l.Given.Name,
Properties: nil, Properties: nil,
}) })
}
return return
} }
@@ -195,9 +206,6 @@ type LoginServerSetting func(*LoginServer)
func WithOtherAccount(a *api2.Account) LoginServerSetting { func WithOtherAccount(a *api2.Account) LoginServerSetting {
return func(s *LoginServer) { return func(s *LoginServer) {
if s.Given == nil {
return
}
s.Given = &NameAndUUID{ s.Given = &NameAndUUID{
Name: a.Name, Name: a.Name,
UUID: a.Uuid, UUID: a.Uuid,
+60
View File
@@ -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)
}