Support chat validation

This commit is contained in:
Tnze
2023-01-01 18:43:21 +08:00
parent fa83b762bf
commit bb98d90db3
9 changed files with 239 additions and 152 deletions

View File

@ -1,47 +0,0 @@
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
}

View File

@ -1,84 +0,0 @@
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
}

View File

@ -4,9 +4,10 @@ import (
"crypto/rand"
"encoding/binary"
"errors"
"fmt"
"time"
"github.com/google/uuid"
"github.com/Tnze/go-mc/bot"
"github.com/Tnze/go-mc/bot/basic"
"github.com/Tnze/go-mc/bot/playerlist"
@ -17,11 +18,12 @@ import (
)
type Manager struct {
c *bot.Client
p *basic.Player
pl *playerlist.PlayerList
c *bot.Client
p *basic.Player
pl *playerlist.PlayerList
events EventsHandler
signatureCache
sign.SignatureCache
}
func New(c *bot.Client, p *basic.Player, pl *playerlist.PlayerList, events EventsHandler) *Manager {
@ -29,58 +31,86 @@ func New(c *bot.Client, p *basic.Player, pl *playerlist.PlayerList, events Event
c: c,
p: p,
pl: pl,
signatureCache: newSignatureCache(),
events: events,
SignatureCache: sign.NewSignatureCache(),
}
m.attachPlayerMsg(c, p, pl, events.PlayerChatMessage)
return m
}
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,
F: func(packet pk.Packet) error {
var (
sender pk.UUID
index pk.VarInt
signature pk.Option[sign.Signature, *sign.Signature]
body sign.PackedMessageBody
unsignedContent pk.Option[chat.Message, *chat.Message]
filter sign.FilterMask
chatType chat.Type
)
if err := packet.Scan(&sender, &index, &signature, &body, &unsignedContent, &filter, &chatType); err != nil {
return err
}
F: m.handlePacket,
},
)
return m
}
//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
} else {
content = chat.Text(body.PlainMsg)
}
func (m *Manager) handlePacket(packet pk.Packet) error {
var (
sender pk.UUID
index pk.VarInt
signature pk.Option[sign.Signature, *sign.Signature]
body sign.PackedMessageBody
unsignedContent pk.Option[chat.Message, *chat.Message]
filter sign.FilterMask
chatType chat.Type
)
if err := packet.Scan(&sender, &index, &signature, &body, &unsignedContent, &filter, &chatType); err != nil {
return err
}
ct := p.WorldInfo.RegistryCodec.ChatType.FindByID(chatType.ID)
if ct == nil {
return fmt.Errorf("chat type %d not found", chatType.ID)
}
unpackedMsg, err := body.Unpack(&m.SignatureCache)
if err != nil {
return InvalidChatPacket
}
senderInfo, ok := m.pl.PlayerInfos[uuid.UUID(sender)]
if !ok {
return InvalidChatPacket
}
ct := m.p.WorldInfo.RegistryCodec.ChatType.FindByID(chatType.ID)
if ct == nil {
return InvalidChatPacket
}
msg := chatType.Decorate(content, &ct.Chat)
return handler(msg)
},
})
var message sign.Message
if senderInfo.ChatSession != nil {
message.Prev = sign.Prev{
Index: int(index),
Sender: uuid.UUID(sender),
Session: senderInfo.ChatSession.SessionID,
}
} else {
message.Prev = sign.Prev{
Index: 0,
Sender: uuid.UUID(sender),
Session: uuid.Nil,
}
}
message.Signature = signature.Pointer()
message.MessageBody = unpackedMsg
message.Unsigned = unsignedContent.Pointer()
message.FilterMask = filter
var validated bool
if senderInfo.ChatSession != nil {
if !senderInfo.ChatSession.VerifyAndUpdate(&message) {
return ValidationFailed
}
validated = true
// store signature into signatureCache
m.PopOrInsert(signature.Pointer(), message.LastSeen)
}
var content chat.Message
if unsignedContent.Has {
content = unsignedContent.Val
} else {
content = chat.Text(body.PlainMsg)
}
if m.events.PlayerChatMessage == nil {
return nil
}
msg := chatType.Decorate(content, &ct.Chat)
return m.events.PlayerChatMessage(msg, validated)
}
// SendMessage send chat message to server.
@ -108,4 +138,7 @@ func (m *Manager) SendMessage(msg string) error {
return err
}
var InvalidChatPacket = errors.New("invalid chat packet")
var (
InvalidChatPacket = errors.New("invalid chat packet")
ValidationFailed error = bot.DisconnectErr(chat.TranslateMsg("multiplayer.disconnect.chat_validation_failed"))
)

View File

@ -3,5 +3,5 @@ package msg
import "github.com/Tnze/go-mc/chat"
type EventsHandler struct {
PlayerChatMessage func(msg chat.Message) error
PlayerChatMessage func(msg chat.Message, validated bool) error
}

View File

@ -78,7 +78,12 @@ func (pl *PlayerList) handlePlayerInfoUpdatePacket(p pk.Packet) error {
if _, err := chatSession.ReadFrom(r); err != nil {
return err
}
player.ChatSession = chatSession.Pointer()
if chatSession.Has {
player.ChatSession = chatSession.Pointer()
player.ChatSession.InitValidate()
} else {
player.ChatSession = nil
}
}
// update gamemode
if action.Get(2) {