Add chat session
This commit is contained in:
47
bot/msg/cache.go
Normal file
47
bot/msg/cache.go
Normal file
@ -0,0 +1,47 @@
|
||||
package msg
|
||||
|
||||
import (
|
||||
"github.com/Tnze/go-mc/chat/sign"
|
||||
)
|
||||
|
||||
type signatureCache struct {
|
||||
signatures [128]*sign.Signature
|
||||
signIndexes map[sign.Signature]int
|
||||
cachedBuffer []*sign.Signature
|
||||
}
|
||||
|
||||
func newSignatureCache() signatureCache {
|
||||
return signatureCache{
|
||||
signIndexes: make(map[sign.Signature]int),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *signatureCache) popOrInsert(self *sign.Signature, lastSeen []sign.PackedSignature) error {
|
||||
var tmp *sign.Signature
|
||||
s.cachedBuffer = s.cachedBuffer[:0] // clear buffer
|
||||
if self != nil {
|
||||
s.cachedBuffer = append(s.cachedBuffer, self)
|
||||
}
|
||||
for _, v := range lastSeen {
|
||||
if v.Signature != nil {
|
||||
s.cachedBuffer = append(s.cachedBuffer, v.Signature)
|
||||
} else if v.ID >= 0 && int(v.ID) < len(s.signatures) {
|
||||
s.cachedBuffer = append(s.cachedBuffer, s.signatures[v.ID])
|
||||
} else {
|
||||
return InvalidChatPacket
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(s.cachedBuffer) && i < len(s.signatures); i++ {
|
||||
v := s.cachedBuffer[i]
|
||||
if i, ok := s.signIndexes[*v]; ok {
|
||||
s.signatures[i] = nil
|
||||
}
|
||||
tmp, s.signatures[i] = s.signatures[i], v
|
||||
s.signIndexes[*v] = i
|
||||
if tmp != nil {
|
||||
s.cachedBuffer = append(s.cachedBuffer, tmp)
|
||||
delete(s.signIndexes, *tmp)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
84
bot/msg/cache_test.go
Normal file
84
bot/msg/cache_test.go
Normal file
@ -0,0 +1,84 @@
|
||||
package msg
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/Tnze/go-mc/chat/sign"
|
||||
)
|
||||
|
||||
func TestSignatureCache(t *testing.T) {
|
||||
cache := newSignatureCache()
|
||||
s1 := &sign.Signature{1}
|
||||
s2 := &sign.Signature{2}
|
||||
s3 := &sign.Signature{3}
|
||||
s4 := &sign.Signature{4}
|
||||
// t.Logf("%p, %p, %p, %p", s1, s2, s3, s4)
|
||||
err := cache.popOrInsert(nil, []sign.PackedSignature{
|
||||
{Signature: s1},
|
||||
{Signature: s2},
|
||||
{Signature: s3},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// cache: [s1, s2, s3, nil...]
|
||||
if cache.signatures[0] != s1 || cache.signatures[1] != s2 || cache.signatures[2] != s3 {
|
||||
t.Log(cache.signatures)
|
||||
t.Fatal("insert s1~3 error")
|
||||
}
|
||||
err = cache.popOrInsert(s4, []sign.PackedSignature{{Signature: s3}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// cache: [s4, s3, s1, s2, nil...]
|
||||
if cache.signatures[0] != s4 {
|
||||
t.Log(cache.signatures)
|
||||
t.Fatal("insert s4 error")
|
||||
}
|
||||
if cache.signatures[1] != s3 {
|
||||
t.Log(cache.signatures)
|
||||
t.Fatal("pop s3 error")
|
||||
}
|
||||
if cache.signatures[2] != s1 || cache.signatures[3] != s2 {
|
||||
t.Log(cache.signatures)
|
||||
t.Fatal("s1~2 position error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignatureCache_2(t *testing.T) {
|
||||
cache := newSignatureCache()
|
||||
signs := make([]sign.PackedSignature, len(cache.signatures)+5)
|
||||
for i := range signs {
|
||||
newSign := new(sign.Signature)
|
||||
_, _ = rand.Read(newSign[:])
|
||||
signs[i] = sign.PackedSignature{Signature: newSign}
|
||||
}
|
||||
err := cache.popOrInsert(nil, signs[:len(cache.signatures)])
|
||||
if !signatureEquals(cache.signatures[:], signs[:len(cache.signatures)]) {
|
||||
t.Fatal("insert error")
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
insert2 := signs[len(cache.signatures)-5:]
|
||||
err = cache.popOrInsert(nil, insert2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !signatureEquals(cache.signatures[:10], insert2) {
|
||||
t.Fatal("insert and pop error")
|
||||
}
|
||||
}
|
||||
|
||||
func signatureEquals(a []*sign.Signature, b []sign.PackedSignature) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i].Signature {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
@ -19,14 +19,23 @@ import (
|
||||
type Manager struct {
|
||||
c *bot.Client
|
||||
p *basic.Player
|
||||
pl *playerlist.PlayerList
|
||||
|
||||
signatureCache
|
||||
}
|
||||
|
||||
func New(c *bot.Client, p *basic.Player, pl *playerlist.PlayerList, events EventsHandler) *Manager {
|
||||
attachPlayerMsg(c, p, pl, events.PlayerChatMessage)
|
||||
return &Manager{c, p}
|
||||
m := &Manager{
|
||||
c: c,
|
||||
p: p,
|
||||
pl: pl,
|
||||
signatureCache: newSignatureCache(),
|
||||
}
|
||||
m.attachPlayerMsg(c, p, pl, events.PlayerChatMessage)
|
||||
return m
|
||||
}
|
||||
|
||||
func attachPlayerMsg(c *bot.Client, p *basic.Player, _ *playerlist.PlayerList, handler func(msg chat.Message) error) {
|
||||
func (m *Manager) attachPlayerMsg(c *bot.Client, p *basic.Player, pl *playerlist.PlayerList, handler func(msg chat.Message) error) {
|
||||
c.Events.AddListener(
|
||||
bot.PacketHandler{
|
||||
Priority: 64, ID: packetid.ClientboundPlayerChat,
|
||||
@ -35,7 +44,7 @@ func attachPlayerMsg(c *bot.Client, p *basic.Player, _ *playerlist.PlayerList, h
|
||||
sender pk.UUID
|
||||
index pk.VarInt
|
||||
signature pk.Option[sign.Signature, *sign.Signature]
|
||||
body sign.MessageBody
|
||||
body sign.PackedMessageBody
|
||||
unsignedContent pk.Option[chat.Message, *chat.Message]
|
||||
filter sign.FilterMask
|
||||
chatType chat.Type
|
||||
@ -44,6 +53,18 @@ func attachPlayerMsg(c *bot.Client, p *basic.Player, _ *playerlist.PlayerList, h
|
||||
return err
|
||||
}
|
||||
|
||||
//senderInfo, ok := pl.PlayerInfos[uuid.UUID(sender)]
|
||||
//if !ok {
|
||||
// return bot.DisconnectErr(chat.TranslateMsg("multiplayer.disconnect.chat_validation_failed"))
|
||||
//}
|
||||
//senderInfo.ChatSession.Update()
|
||||
//if err := senderInfo.ChatSession.Validate(); err != nil {
|
||||
// return bot.DisconnectErr(chat.TranslateMsg("multiplayer.disconnect.chat_validation_failed"))
|
||||
//}
|
||||
// store signature into signatureCache
|
||||
//if err := m.popOrInsert(signature.Pointer(), body.LastSeen); err != nil {
|
||||
// return err
|
||||
//}
|
||||
var content chat.Message
|
||||
if unsignedContent.Has {
|
||||
content = unsignedContent.Val
|
||||
|
@ -2,12 +2,12 @@ package playerlist
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/Tnze/go-mc/bot"
|
||||
"github.com/Tnze/go-mc/chat"
|
||||
"github.com/Tnze/go-mc/chat/sign"
|
||||
"github.com/Tnze/go-mc/data/packetid"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
"github.com/Tnze/go-mc/yggdrasil/user"
|
||||
@ -74,7 +74,11 @@ func (pl *PlayerList) handlePlayerInfoUpdatePacket(p pk.Packet) error {
|
||||
}
|
||||
// initialize chat
|
||||
if action.Get(1) {
|
||||
return errors.New("unsupported initialize chat yet")
|
||||
var chatSession pk.Option[sign.Session, *sign.Session]
|
||||
if _, err := chatSession.ReadFrom(r); err != nil {
|
||||
return err
|
||||
}
|
||||
player.ChatSession = chatSession.Pointer()
|
||||
}
|
||||
// update gamemode
|
||||
if action.Get(2) {
|
||||
@ -123,7 +127,7 @@ func (pl *PlayerList) handlePlayerInfoRemovePacket(p pk.Packet) error {
|
||||
|
||||
type PlayerInfo struct {
|
||||
GameProfile
|
||||
// chatSession
|
||||
ChatSession *sign.Session
|
||||
Gamemode int32
|
||||
Latency int32
|
||||
Listed bool
|
||||
|
50
chat/sign/session.go
Normal file
50
chat/sign/session.go
Normal file
@ -0,0 +1,50 @@
|
||||
package sign
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/Tnze/go-mc/chat"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
"github.com/Tnze/go-mc/yggdrasil/user"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
Link struct {
|
||||
Index int
|
||||
Sender uuid.UUID
|
||||
Session uuid.UUID
|
||||
}
|
||||
Signature []byte
|
||||
PackedMessageBody
|
||||
Unsigned *chat.Message
|
||||
FilterMask
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
sessionID uuid.UUID
|
||||
publicKey user.PublicKey
|
||||
}
|
||||
|
||||
func (s Session) WriteTo(w io.Writer) (n int64, err error) {
|
||||
n1, err := pk.UUID(s.sessionID).WriteTo(w)
|
||||
if err != nil {
|
||||
return n1, err
|
||||
}
|
||||
n2, err := s.publicKey.WriteTo(w)
|
||||
return n1 + n2, err
|
||||
}
|
||||
|
||||
func (s *Session) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
n1, err := ((*pk.UUID)(&s.sessionID)).ReadFrom(r)
|
||||
if err != nil {
|
||||
return n1, err
|
||||
}
|
||||
n2, err := s.publicKey.ReadFrom(r)
|
||||
return n1 + n2, err
|
||||
}
|
||||
|
||||
func (s *Session) Update(msg Message) bool {
|
||||
panic("todo")
|
||||
}
|
@ -4,18 +4,19 @@ import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
"github.com/google/uuid"
|
||||
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
)
|
||||
|
||||
type MessageBody struct {
|
||||
type PackedMessageBody struct {
|
||||
PlainMsg string
|
||||
Timestamp time.Time
|
||||
Salt int64
|
||||
LastSeen []PackedSignature
|
||||
}
|
||||
|
||||
func (m *MessageBody) WriteTo(w io.Writer) (n int64, err error) {
|
||||
func (m *PackedMessageBody) WriteTo(w io.Writer) (n int64, err error) {
|
||||
return pk.Tuple{
|
||||
pk.String(m.PlainMsg),
|
||||
pk.Long(m.Timestamp.UnixMilli()),
|
||||
@ -24,7 +25,7 @@ func (m *MessageBody) WriteTo(w io.Writer) (n int64, err error) {
|
||||
}.WriteTo(w)
|
||||
}
|
||||
|
||||
func (m *MessageBody) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
func (m *PackedMessageBody) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
var timestamp pk.Long
|
||||
n, err = pk.Tuple{
|
||||
(*pk.String)(&m.PlainMsg),
|
||||
|
@ -1 +0,0 @@
|
||||
package sign
|
@ -205,6 +205,14 @@ func (o *Option[T, P]) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
return n1 + n2, err
|
||||
}
|
||||
|
||||
// Pointer returns the pointer of Val if Has is true, otherwise return nil.
|
||||
func (o *Option[T, P]) Pointer() (p *T) {
|
||||
if o.Has {
|
||||
p = &o.Val
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// OptionDecoder is basically same with [Option], but support [FieldDecoder] only.
|
||||
// This allowed wrapping a [FieldDecoder] type (which isn't a [FieldEncoder]) to an Option.
|
||||
type OptionDecoder[T any, P fieldPointer[T]] struct {
|
||||
|
@ -24,6 +24,13 @@ import (
|
||||
"github.com/Tnze/go-mc/yggdrasil/user"
|
||||
)
|
||||
|
||||
// Deprecated: Moved to go-mc/yggdrasil/user, because go-mc/bot also needs them
|
||||
type (
|
||||
Texture = user.Texture
|
||||
Property = user.Property
|
||||
PublicKey = user.PublicKey
|
||||
)
|
||||
|
||||
const verifyTokenLen = 16
|
||||
|
||||
// Encrypt a connection, with authentication
|
||||
@ -173,11 +180,6 @@ func twosComplement(p []byte) []byte {
|
||||
return p
|
||||
}
|
||||
|
||||
type (
|
||||
Texture = user.Texture
|
||||
Property = user.Property
|
||||
)
|
||||
|
||||
// Resp is the response of authentication
|
||||
type Resp struct {
|
||||
Name string
|
||||
|
@ -4,7 +4,8 @@ import (
|
||||
_ "embed"
|
||||
|
||||
"github.com/Tnze/go-mc/net"
|
||||
"github.com/Tnze/go-mc/server/auth"
|
||||
"github.com/Tnze/go-mc/yggdrasil/user"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
@ -13,5 +14,5 @@ type GamePlay interface {
|
||||
//
|
||||
// Note: the connection will be closed after this function returned.
|
||||
// You don't need to close the connection, but to keep not returning while the player is playing.
|
||||
AcceptPlayer(name string, id uuid.UUID, profilePubKey *auth.PublicKey, properties []auth.Property, protocol int32, conn *net.Conn)
|
||||
AcceptPlayer(name string, id uuid.UUID, profilePubKey *user.PublicKey, properties []user.Property, protocol int32, conn *net.Conn)
|
||||
}
|
||||
|
@ -13,13 +13,15 @@ import (
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
"github.com/Tnze/go-mc/offline"
|
||||
"github.com/Tnze/go-mc/server/auth"
|
||||
"github.com/Tnze/go-mc/yggdrasil/user"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// LoginHandler is used to handle player login process, that is,
|
||||
// from clientbound "LoginStart" packet to serverbound "LoginSuccess" packet.
|
||||
type LoginHandler interface {
|
||||
AcceptLogin(conn *net.Conn, protocol int32) (name string, id uuid.UUID, profilePubKey *auth.PublicKey, properties []auth.Property, err error)
|
||||
AcceptLogin(conn *net.Conn, protocol int32) (name string, id uuid.UUID, profilePubKey *user.PublicKey, properties []user.Property, err error)
|
||||
}
|
||||
|
||||
// LoginChecker is the interface to check if a player is allowed to log in the server.
|
||||
@ -87,7 +89,7 @@ func (d *MojangLoginHandler) getPrivateKey() (key *rsa.PrivateKey, err error) {
|
||||
*/
|
||||
|
||||
// AcceptLogin implement LoginHandler for MojangLoginHandler
|
||||
func (d *MojangLoginHandler) AcceptLogin(conn *net.Conn, protocol int32) (name string, id uuid.UUID, profilePubKey *auth.PublicKey, properties []auth.Property, err error) {
|
||||
func (d *MojangLoginHandler) AcceptLogin(conn *net.Conn, protocol int32) (name string, id uuid.UUID, profilePubKey *user.PublicKey, properties []user.Property, err error) {
|
||||
// login start
|
||||
var p pk.Packet
|
||||
err = conn.ReadPacket(&p)
|
||||
|
@ -1,4 +1,4 @@
|
||||
package auth
|
||||
package user
|
||||
|
||||
import (
|
||||
"crypto"
|
@ -1,4 +1,4 @@
|
||||
package auth
|
||||
package user
|
||||
|
||||
import (
|
||||
"crypto"
|
Reference in New Issue
Block a user