From 55bf5eddbb62410a20c3d9d17309d35a0fe6e716 Mon Sep 17 00:00:00 2001 From: Tnze Date: Tue, 6 Dec 2022 02:14:38 +0800 Subject: [PATCH] Add pk.Option API --- bot/mcbot.go | 32 ++++++++++++------------- chat/message.go | 2 ++ net/packet/util.go | 44 +++++++++++++++++++++++++++++----- net/packet/util_test.go | 53 ++++++++++++++++++++++++++++++++++------- server/keepalive.go | 2 -- server/login.go | 24 +++++++------------ 6 files changed, 107 insertions(+), 50 deletions(-) diff --git a/bot/mcbot.go b/bot/mcbot.go index 22dff86..cc74c24 100644 --- a/bot/mcbot.go +++ b/bot/mcbot.go @@ -79,22 +79,20 @@ func (c *Client) join(ctx context.Context, d *mcnet.Dialer, addr string) error { } // Login Start c.KeyPair, err = user.GetOrFetchKeyPair(c.Auth.AsTk) - HasSignature := err == nil + KeyPair := pk.Option[keyPair]{ + Has: err == nil, + Val: keyPair(c.KeyPair), + } c.UUID, err = uuid.Parse(c.Auth.UUID) - HasPlayerUUID := err == nil + PlayerUUID := pk.Option[pk.UUID]{ + Has: err == nil, + Val: pk.UUID(c.UUID), + } err = c.Conn.WritePacket(pk.Marshal( packetid.LoginStart, pk.String(c.Auth.Name), - pk.Boolean(HasSignature), - pk.Opt{ - Has: HasSignature, - Field: keyPair(c.KeyPair), - }, - pk.Boolean(HasPlayerUUID), - pk.Opt{ - Has: HasPlayerUUID, - Field: pk.UUID(c.UUID), - }, + KeyPair, + PlayerUUID, )) if err != nil { return LoginErr{"login start", err} @@ -148,9 +146,10 @@ func (c *Client) join(ctx context.Context, d *mcnet.Dialer, addr string) error { return LoginErr{"Login Plugin", err} } - handler, ok := c.LoginPlugin[string(channel)] - if ok { - data, err = handler(data) + var PluginMessageData pk.Option[pk.PluginMessageData] + if handler, ok := c.LoginPlugin[string(channel)]; ok { + PluginMessageData.Has = true + PluginMessageData.Val, err = handler(data) if err != nil { return LoginErr{"Login Plugin", err} } @@ -158,8 +157,7 @@ func (c *Client) join(ctx context.Context, d *mcnet.Dialer, addr string) error { if err := c.Conn.WritePacket(pk.Marshal( packetid.LoginPluginResponse, - msgid, pk.Boolean(ok), - pk.Opt{Has: ok, Field: data}, + msgid, PluginMessageData, )); err != nil { return LoginErr{"login Plugin", err} } diff --git a/chat/message.go b/chat/message.go index 2195900..f8cab08 100644 --- a/chat/message.go +++ b/chat/message.go @@ -11,6 +11,7 @@ package chat import ( + "bytes" "encoding/json" "errors" "fmt" @@ -113,6 +114,7 @@ func (m Message) MarshalJSON() ([]byte, error) { // UnmarshalJSON decode json to Message func (m *Message) UnmarshalJSON(raw []byte) (err error) { + raw = bytes.TrimSpace(raw) if len(raw) == 0 { return io.EOF } diff --git a/net/packet/util.go b/net/packet/util.go index 2ef0d17..a591664 100644 --- a/net/packet/util.go +++ b/net/packet/util.go @@ -7,6 +7,12 @@ import ( "reflect" ) +var ( + _ Field = (*Option[Field])(nil) + _ Field = (*Ary[VarInt])(nil) + _ Field = Tuple(nil) +) + // Ary is used to send or receive the packet field like "Array of X" // which has a count must be known from the context. // @@ -19,16 +25,16 @@ import ( // So it's allowed to directly set an integer type Len, but not a pointer. // // Note that Ary DO read or write the Len. You aren't need to do so by your self. -type Ary[T VarInt | VarLong | Byte | UnsignedByte | Short | UnsignedShort | Int | Long] struct { +type Ary[LEN VarInt | VarLong | Byte | UnsignedByte | Short | UnsignedShort | Int | Long] struct { Ary interface{} // Slice or Pointer of Slice of FieldEncoder, FieldDecoder or both (Field) } -func (a Ary[T]) WriteTo(w io.Writer) (n int64, err error) { +func (a Ary[LEN]) WriteTo(w io.Writer) (n int64, err error) { array := reflect.ValueOf(a.Ary) for array.Kind() == reflect.Ptr { array = array.Elem() } - Len := T(array.Len()) + Len := LEN(array.Len()) if nn, err := any(&Len).(FieldEncoder).WriteTo(w); err != nil { return n, err } else { @@ -45,8 +51,8 @@ func (a Ary[T]) WriteTo(w io.Writer) (n int64, err error) { return n, nil } -func (a Ary[T]) ReadFrom(r io.Reader) (n int64, err error) { - var Len T +func (a Ary[LEN]) ReadFrom(r io.Reader) (n int64, err error) { + var Len LEN if nn, err := any(&Len).(FieldDecoder).ReadFrom(r); err != nil { return nn, err } else { @@ -133,7 +139,33 @@ func (o Opt) ReadFrom(r io.Reader) (int64, error) { return 0, nil } -type Tuple []interface{} // FieldEncoder, FieldDecoder or both (Field) +type Option[T any] struct { + Has Boolean + Val T +} + +func (o Option[T]) WriteTo(w io.Writer) (n int64, err error) { + n1, err := o.Has.WriteTo(w) + if err != nil || !o.Has { + return n1, err + } + n2, err := any(&o.Val).(FieldEncoder).WriteTo(w) + return n1 + n2, err +} + +func (o *Option[T]) ReadFrom(r io.Reader) (n int64, err error) { + n1, err := o.Has.ReadFrom(r) + if err != nil || !o.Has { + return n1, err + } + // This lose performance, but current Golang doesn't provide any better solution. + // I hope Go support we declare type constraint like `Option[*T Field]` in the future + // and then we can prevent using any dynamic dispatch. + n2, err := any(&o.Val).(FieldDecoder).ReadFrom(r) + return n1 + n2, err +} + +type Tuple []any // FieldEncoder, FieldDecoder or both (Field) // WriteTo write Tuple to io.Writer, panic when any of filed don't implement FieldEncoder func (t Tuple) WriteTo(w io.Writer) (n int64, err error) { diff --git a/net/packet/util_test.go b/net/packet/util_test.go index dc147bf..7087b9e 100644 --- a/net/packet/util_test.go +++ b/net/packet/util_test.go @@ -138,16 +138,16 @@ func ExampleOpt_ReadFrom() { // WILL NOT BE READ, WILL NOT BE COVERED } +// As an example, we define this packet as this: +// +------+-----------------+----------------------------------+ +// | Name | Type | Notes | +// +------+-----------------+----------------------------------+ +// | Flag | Unsigned Byte | Odd if the following is present. | +// +------+-----------------+----------------------------------+ +// | User | Optional String | The player's name. | +// +------+-----------------+----------------------------------+ +// So we need a function to decide if the User field is present. func ExampleOpt_ReadFrom_func() { - // As an example, we define this packet as this: - // +------+-----------------+----------------------------------+ - // | Name | Type | Notes | - // +------+-----------------+----------------------------------+ - // | Flag | Unsigned Byte | Odd if the following is present. | - // +------+-----------------+----------------------------------+ - // | User | Optional String | The player's name. | - // +------+-----------------+----------------------------------+ - // So we need a function to decide if the User field is present. var flag pk.Byte var data pk.String p := pk.Packet{Data: []byte{ @@ -188,3 +188,38 @@ func ExampleTuple_ReadFrom() { panic(err) } } + +// As an example, we define this packet as this: +// +------+-----------------+-----------------------------------+ +// | Name | Type | Notes | +// +------+-----------------+-----------------------------------+ +// | Has | Boolean | True if the following is present. | +// +------+-----------------+-----------------------------------+ +// | User | Optional String | The player's name. | +// +------+-----------------+-----------------------------------+ +// So we need a function to decide if the User field is present. +func ExampleOption_ReadFrom_func() { + p1 := pk.Packet{Data: []byte{ + 0x01, // pk.Boolean(true) + 4, 'T', 'n', 'z', 'e', // pk.String("Tnze") + }} + p2 := pk.Packet{Data: []byte{ + 0x00, // pk.Boolean(false) + // empty + }} + + var User1, User2 pk.Option[pk.String] + if err := p1.Scan(&User1); err != nil { + panic(err) + } + if err := p2.Scan(&User2); err != nil { + panic(err) + } + + fmt.Println(User1.Has, User1.Val) + fmt.Println(User2.Has, User2.Val) + + // Output: + // true Tnze + // false +} diff --git a/server/keepalive.go b/server/keepalive.go index 2837476..a7b1cba 100644 --- a/server/keepalive.go +++ b/server/keepalive.go @@ -118,7 +118,6 @@ func (k *KeepAlive) tickPlayer(c KeepAliveClient) { elem, ok := k.listIndex[c] if !ok { panic(errors.New("keepalive: fail to tick player: client not found")) - return } if elem.Prev() == nil { if !k.waitTimer.Stop() { @@ -156,7 +155,6 @@ func keepAliveSetTimer(l *list.List, timer *time.Timer, interval time.Duration) } } timer.Reset(interval) - return } type keepAliveItem struct { diff --git a/server/login.go b/server/login.go index a256a62..50e84c5 100644 --- a/server/login.go +++ b/server/login.go @@ -64,21 +64,13 @@ func (d *MojangLoginHandler) AcceptLogin(conn *net.Conn, protocol int32) (name s } var ( - hasPubKey pk.Boolean - pubKey auth.PublicKey - hasUUID pk.Boolean - profileUUID pk.UUID // ignored + pubKey pk.Option[auth.PublicKey] + profileUUID pk.Option[pk.UUID] // ignored ) err = p.Scan( (*pk.String)(&name), // decode username as pk.String - &hasPubKey, pk.Opt{ - Has: &hasPubKey, - Field: &pubKey, - }, - &hasUUID, pk.Opt{ - Has: &hasUUID, - Field: &profileUUID, - }, + &pubKey, + &profileUUID, ) if err != nil { return @@ -86,12 +78,12 @@ func (d *MojangLoginHandler) AcceptLogin(conn *net.Conn, protocol int32) (name s // auth if d.OnlineMode { - if hasPubKey { - if !pubKey.Verify() { + if pubKey.Has { + if !pubKey.Val.Verify() { err = LoginFailErr{reason: chat.TranslateMsg("multiplayer.disconnect.invalid_public_key_signature")} return } - profilePubKey = &pubKey + profilePubKey = &pubKey.Val } else if d.EnforceSecureProfile { err = LoginFailErr{reason: chat.TranslateMsg("multiplayer.disconnect.missing_public_key")} return @@ -99,7 +91,7 @@ func (d *MojangLoginHandler) AcceptLogin(conn *net.Conn, protocol int32) (name s var resp *auth.Resp // Auth, Encrypt - resp, err = auth.Encrypt(conn, name, pubKey.PubKey) + resp, err = auth.Encrypt(conn, name, pubKey.Val.PubKey) if err != nil { return }