From ea76e5a7132ef24591c3409b911503d00f0fb957 Mon Sep 17 00:00:00 2001 From: Tnze Date: Tue, 6 Dec 2022 11:07:19 +0800 Subject: [PATCH] pk.Option improvement --- bot/mcbot.go | 29 +++------------------- net/packet/types.go | 4 +-- net/packet/util.go | 54 ++++++++++++++++++++++++++++++++++------- net/packet/util_test.go | 2 +- server/auth/pubkey.go | 2 +- server/login.go | 4 +-- yggdrasil/user/user.go | 22 +++++++++++++++++ 7 files changed, 77 insertions(+), 40 deletions(-) diff --git a/bot/mcbot.go b/bot/mcbot.go index cc74c24..2411d40 100644 --- a/bot/mcbot.go +++ b/bot/mcbot.go @@ -6,10 +6,7 @@ package bot import ( "context" - "encoding/base64" - "encoding/pem" "errors" - "io" "net" "strconv" @@ -79,12 +76,12 @@ func (c *Client) join(ctx context.Context, d *mcnet.Dialer, addr string) error { } // Login Start c.KeyPair, err = user.GetOrFetchKeyPair(c.Auth.AsTk) - KeyPair := pk.Option[keyPair]{ + KeyPair := pk.OptionEncoder[user.KeyPairResp]{ Has: err == nil, - Val: keyPair(c.KeyPair), + Val: c.KeyPair, } c.UUID, err = uuid.Parse(c.Auth.UUID) - PlayerUUID := pk.Option[pk.UUID]{ + PlayerUUID := pk.Option[pk.UUID, *pk.UUID]{ Has: err == nil, Val: pk.UUID(c.UUID), } @@ -146,7 +143,7 @@ func (c *Client) join(ctx context.Context, d *mcnet.Dialer, addr string) error { return LoginErr{"Login Plugin", err} } - var PluginMessageData pk.Option[pk.PluginMessageData] + var PluginMessageData pk.Option[pk.PluginMessageData, *pk.PluginMessageData] if handler, ok := c.LoginPlugin[string(channel)]; ok { PluginMessageData.Has = true PluginMessageData.Val, err = handler(data) @@ -165,24 +162,6 @@ func (c *Client) join(ctx context.Context, d *mcnet.Dialer, addr string) error { } } -type keyPair user.KeyPairResp - -func (k keyPair) WriteTo(w io.Writer) (int64, error) { - block, _ := pem.Decode([]byte(k.KeyPair.PublicKey)) - if block == nil { - return 0, errors.New("pem decode error: no data is found") - } - signature, err := base64.StdEncoding.DecodeString(k.PublicKeySignature) - if err != nil { - return 0, err - } - return pk.Tuple{ - pk.Long(k.ExpiresAt.UnixMilli()), - pk.ByteArray(block.Bytes), - pk.ByteArray(signature), - }.WriteTo(w) -} - type LoginErr struct { Stage string Err error diff --git a/net/packet/types.go b/net/packet/types.go index 4d522f1..37eb68f 100644 --- a/net/packet/types.go +++ b/net/packet/types.go @@ -479,8 +479,8 @@ func (u *UUID) ReadFrom(r io.Reader) (n int64, err error) { return int64(nn), err } -func (p *PluginMessageData) WriteTo(w io.Writer) (n int64, err error) { - nn, err := w.Write(*p) +func (p PluginMessageData) WriteTo(w io.Writer) (n int64, err error) { + nn, err := w.Write(p) return int64(nn), err } diff --git a/net/packet/util.go b/net/packet/util.go index a591664..1455d53 100644 --- a/net/packet/util.go +++ b/net/packet/util.go @@ -8,7 +8,7 @@ import ( ) var ( - _ Field = (*Option[Field])(nil) + _ Field = (*Option[VarInt, *VarInt])(nil) _ Field = (*Ary[VarInt])(nil) _ Field = Tuple(nil) ) @@ -139,29 +139,65 @@ func (o Opt) ReadFrom(r io.Reader) (int64, error) { return 0, nil } -type Option[T any] struct { +type fieldPointer[T any] interface { + *T + FieldDecoder +} + +// Currently we have to repeat T in the type arguments. +// +// var opt Option[String, *String] +// +// Constraint type will inference makes it less awkward in the future. +// See: https://github.com/golang/go/issues/54469 +type Option[T FieldEncoder, P fieldPointer[T]] struct { Has Boolean Val T } -func (o Option[T]) WriteTo(w io.Writer) (n int64, err error) { +func (o Option[T, P]) 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) + n2, err := o.Val.WriteTo(w) return n1 + n2, err } -func (o *Option[T]) ReadFrom(r io.Reader) (n int64, err error) { +func (o *Option[T, P]) 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) + n2, err := P(&o.Val).ReadFrom(r) + return n1 + n2, err +} + +type OptionDecoder[T any, P fieldPointer[T]] struct { + Has Boolean + Val T +} + +func (o *OptionDecoder[T, P]) ReadFrom(r io.Reader) (n int64, err error) { + n1, err := o.Has.ReadFrom(r) + if err != nil || !o.Has { + return n1, err + } + n2, err := P(&o.Val).ReadFrom(r) + return n1 + n2, err +} + +type OptionEncoder[T FieldEncoder] struct { + Has Boolean + Val T +} + +func (o OptionEncoder[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 := o.Val.WriteTo(w) return n1 + n2, err } diff --git a/net/packet/util_test.go b/net/packet/util_test.go index 7087b9e..101d3f1 100644 --- a/net/packet/util_test.go +++ b/net/packet/util_test.go @@ -208,7 +208,7 @@ func ExampleOption_ReadFrom_func() { // empty }} - var User1, User2 pk.Option[pk.String] + var User1, User2 pk.Option[pk.String, *pk.String] if err := p1.Scan(&User1); err != nil { panic(err) } diff --git a/server/auth/pubkey.go b/server/auth/pubkey.go index 0001b8c..15824fc 100644 --- a/server/auth/pubkey.go +++ b/server/auth/pubkey.go @@ -17,7 +17,7 @@ type PublicKey struct { Signature []byte } -func (p *PublicKey) WriteTo(w io.Writer) (n int64, err error) { +func (p PublicKey) WriteTo(w io.Writer) (n int64, err error) { pubKeyEncoded, err := x509.MarshalPKIXPublicKey(p.PubKey) if err != nil { return 0, err diff --git a/server/login.go b/server/login.go index 50e84c5..acd0cf3 100644 --- a/server/login.go +++ b/server/login.go @@ -64,8 +64,8 @@ func (d *MojangLoginHandler) AcceptLogin(conn *net.Conn, protocol int32) (name s } var ( - pubKey pk.Option[auth.PublicKey] - profileUUID pk.Option[pk.UUID] // ignored + pubKey pk.Option[auth.PublicKey, *auth.PublicKey] + profileUUID pk.Option[pk.UUID, *pk.UUID] // ignored ) err = p.Scan( (*pk.String)(&name), // decode username as pk.String diff --git a/yggdrasil/user/user.go b/yggdrasil/user/user.go index e86b204..b062ea2 100644 --- a/yggdrasil/user/user.go +++ b/yggdrasil/user/user.go @@ -1,10 +1,16 @@ package user import ( + "encoding/base64" "encoding/json" + "encoding/pem" + "errors" "fmt" + "io" "net/http" "time" + + pk "github.com/Tnze/go-mc/net/packet" ) var ServicesURL = "https://api.minecraftservices.com" @@ -21,6 +27,22 @@ type KeyPairResp struct { RefreshedAfter time.Time `json:"refreshedAfter"` } +func (k KeyPairResp) WriteTo(w io.Writer) (int64, error) { + block, _ := pem.Decode([]byte(k.KeyPair.PublicKey)) + if block == nil { + return 0, errors.New("pem decode error: no data is found") + } + signature, err := base64.StdEncoding.DecodeString(k.PublicKeySignature) + if err != nil { + return 0, err + } + return pk.Tuple{ + pk.Long(k.ExpiresAt.UnixMilli()), + pk.ByteArray(block.Bytes), + pk.ByteArray(signature), + }.WriteTo(w) +} + func GetOrFetchKeyPair(accessToken string) (KeyPairResp, error) { return fetchKeyPair(accessToken) // TODO: cache }