206 lines
6.1 KiB
Go
206 lines
6.1 KiB
Go
package mtprotoedge
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"testing"
|
|
|
|
"github.com/gotd/td/bin"
|
|
"github.com/gotd/td/proto"
|
|
"github.com/gotd/td/tg"
|
|
"go.uber.org/zap/zaptest"
|
|
)
|
|
|
|
// TestConnDowngradedClone verifies the outbound seam downgrades a canonical
|
|
// (227) object to the connection's negotiated layer, is a no-op for 227, and
|
|
// — critically for push fan-out — never mutates the shared input message (one
|
|
// pre-encoded update is reused across many connections of differing layers).
|
|
func TestConnDowngradedClone(t *testing.T) {
|
|
const (
|
|
message227CRC = 0x7600b9d3
|
|
message220CRC = 0xb92f76cf
|
|
)
|
|
msg := &tg.Message{
|
|
ID: 2,
|
|
FromID: &tg.PeerUser{UserID: 3},
|
|
PeerID: &tg.PeerUser{UserID: 3},
|
|
Date: 1,
|
|
Message: "hi",
|
|
}
|
|
|
|
// layer 220: returns a NEW message rewritten to the 220 constructor id,
|
|
// leaving the shared input untouched (227).
|
|
enc, err := encodeOutboundMessage(msg)
|
|
if err != nil {
|
|
t.Fatalf("encode: %v", err)
|
|
}
|
|
c := &Conn{metrics: NopMetrics{}}
|
|
c.SetClientLayer(220)
|
|
out := c.downgradedClone(enc)
|
|
|
|
if id, _ := (&bin.Buffer{Buf: out.body}).PeekID(); id != message220CRC {
|
|
t.Fatalf("downgraded id = %#08x, want %#08x", id, message220CRC)
|
|
}
|
|
if out.typeID != message220CRC {
|
|
t.Fatalf("downgraded typeID = %#08x, want %#08x", out.typeID, message220CRC)
|
|
}
|
|
// Input must be unmodified — this is what makes shared push fan-out safe.
|
|
if id, _ := (&bin.Buffer{Buf: enc.body}).PeekID(); id != message227CRC {
|
|
t.Fatalf("input message was mutated: id now %#08x, want 227 %#08x", id, message227CRC)
|
|
}
|
|
|
|
// Two connections sharing one pre-encoded message get independent results.
|
|
encShared, _ := encodeOutboundMessage(msg)
|
|
c220 := &Conn{metrics: NopMetrics{}}
|
|
c220.SetClientLayer(220)
|
|
c227 := &Conn{metrics: NopMetrics{}} // ClientLayer() defaults to 227
|
|
out220 := c220.downgradedClone(encShared)
|
|
out227 := c227.downgradedClone(encShared)
|
|
if id, _ := (&bin.Buffer{Buf: out220.body}).PeekID(); id != message220CRC {
|
|
t.Fatalf("shared->220 id = %#08x, want %#08x", id, message220CRC)
|
|
}
|
|
if out227 != encShared {
|
|
t.Errorf("227 connection should pass the shared message through unchanged (same pointer)")
|
|
}
|
|
if !bytes.Equal(encShared.body, out227.body) {
|
|
t.Errorf("227 passthrough altered bytes")
|
|
}
|
|
}
|
|
|
|
func TestEncodeRPCResultDowngradesDifferenceMessagesForNegotiatedLayer225(t *testing.T) {
|
|
const (
|
|
message227CRC = 0x7600b9d3
|
|
message225CRC = 0x95ef6f2b
|
|
)
|
|
c := &Conn{metrics: NopMetrics{}}
|
|
c.SetClientLayer(225)
|
|
diff := &tg.UpdatesDifference{
|
|
NewMessages: []tg.MessageClass{
|
|
&tg.Message{
|
|
ID: 2,
|
|
FromID: &tg.PeerUser{UserID: 3},
|
|
PeerID: &tg.PeerUser{UserID: 3},
|
|
Date: 1,
|
|
Message: "hi",
|
|
},
|
|
},
|
|
NewEncryptedMessages: []tg.EncryptedMessageClass{},
|
|
OtherUpdates: []tg.UpdateClass{},
|
|
Chats: []tg.ChatClass{},
|
|
Users: []tg.UserClass{},
|
|
State: tg.UpdatesState{Pts: 2, Date: 1},
|
|
}
|
|
|
|
s := &Server{log: zaptest.NewLogger(t)}
|
|
encoded, err := s.encodeRPCResult(c, 12345, diff)
|
|
if err != nil {
|
|
t.Fatalf("encode rpc_result: %v", err)
|
|
}
|
|
var result proto.Result
|
|
if err := result.Decode(&bin.Buffer{Buf: encoded.body}); err != nil {
|
|
t.Fatalf("decode rpc_result: %v", err)
|
|
}
|
|
if result.RequestMessageID != 12345 {
|
|
t.Fatalf("req_msg_id = %d, want 12345", result.RequestMessageID)
|
|
}
|
|
if !bytes.Contains(result.Result, littleEndianID(message225CRC)) {
|
|
t.Fatalf("rpc_result inner object does not contain layer 225 message id %#08x", message225CRC)
|
|
}
|
|
if bytes.Contains(result.Result, littleEndianID(message227CRC)) {
|
|
t.Fatalf("rpc_result inner object still contains canonical message id %#08x", message227CRC)
|
|
}
|
|
}
|
|
|
|
func TestEncodeRPCResultDowngradesDialogMessagesForNegotiatedLayer225(t *testing.T) {
|
|
const (
|
|
message227CRC = 0x7600b9d3
|
|
message225CRC = 0x95ef6f2b
|
|
)
|
|
c := &Conn{metrics: NopMetrics{}}
|
|
c.SetClientLayer(225)
|
|
dialogs := &tg.MessagesDialogs{
|
|
Dialogs: []tg.DialogClass{
|
|
&tg.Dialog{
|
|
Peer: &tg.PeerUser{UserID: 3},
|
|
TopMessage: 2,
|
|
NotifySettings: tg.PeerNotifySettings{},
|
|
},
|
|
},
|
|
Messages: []tg.MessageClass{
|
|
&tg.Message{
|
|
ID: 2,
|
|
FromID: &tg.PeerUser{UserID: 3},
|
|
PeerID: &tg.PeerUser{UserID: 3},
|
|
Date: 1,
|
|
Message: "hi",
|
|
},
|
|
},
|
|
Chats: []tg.ChatClass{},
|
|
Users: []tg.UserClass{
|
|
&tg.User{ID: 3, AccessHash: 5, FirstName: "A"},
|
|
},
|
|
}
|
|
|
|
s := &Server{log: zaptest.NewLogger(t)}
|
|
encoded, err := s.encodeRPCResult(c, 12345, dialogs)
|
|
if err != nil {
|
|
t.Fatalf("encode rpc_result: %v", err)
|
|
}
|
|
var result proto.Result
|
|
if err := result.Decode(&bin.Buffer{Buf: encoded.body}); err != nil {
|
|
t.Fatalf("decode rpc_result: %v", err)
|
|
}
|
|
if !bytes.Contains(result.Result, littleEndianID(message225CRC)) {
|
|
t.Fatalf("rpc_result inner object does not contain layer 225 message id %#08x", message225CRC)
|
|
}
|
|
if bytes.Contains(result.Result, littleEndianID(message227CRC)) {
|
|
t.Fatalf("rpc_result inner object still contains canonical message id %#08x", message227CRC)
|
|
}
|
|
}
|
|
|
|
func TestConnDowngradedCloneDowngradesUpdateNewMessageForLayer225(t *testing.T) {
|
|
const (
|
|
message227CRC = 0x7600b9d3
|
|
message225CRC = 0x95ef6f2b
|
|
)
|
|
updates := &tg.Updates{
|
|
Updates: []tg.UpdateClass{
|
|
&tg.UpdateNewMessage{
|
|
Message: &tg.Message{
|
|
ID: 2,
|
|
FromID: &tg.PeerUser{UserID: 3},
|
|
PeerID: &tg.PeerUser{UserID: 3},
|
|
Date: 1,
|
|
Message: "hi",
|
|
},
|
|
Pts: 2,
|
|
PtsCount: 1,
|
|
},
|
|
},
|
|
Users: []tg.UserClass{
|
|
&tg.User{ID: 3, AccessHash: 5, FirstName: "A"},
|
|
},
|
|
Chats: []tg.ChatClass{},
|
|
Date: 1,
|
|
Seq: 1,
|
|
}
|
|
enc, err := encodeOutboundMessage(updates)
|
|
if err != nil {
|
|
t.Fatalf("encode updates: %v", err)
|
|
}
|
|
c := &Conn{metrics: NopMetrics{}}
|
|
c.SetClientLayer(225)
|
|
out := c.downgradedClone(enc)
|
|
if !bytes.Contains(out.body, littleEndianID(message225CRC)) {
|
|
t.Fatalf("push update does not contain layer 225 message id %#08x", message225CRC)
|
|
}
|
|
if bytes.Contains(out.body, littleEndianID(message227CRC)) {
|
|
t.Fatalf("push update still contains canonical message id %#08x", message227CRC)
|
|
}
|
|
}
|
|
|
|
func littleEndianID(id uint32) []byte {
|
|
buf := make([]byte, 4)
|
|
binary.LittleEndian.PutUint32(buf, id)
|
|
return buf
|
|
}
|