|
|
@@ -0,0 +1,2523 @@
|
|
|
+package icq_legacy
|
|
|
+
|
|
|
+import (
|
|
|
+ "bytes"
|
|
|
+ "encoding/binary"
|
|
|
+ "errors"
|
|
|
+ "fmt"
|
|
|
+ "log/slog"
|
|
|
+ "net"
|
|
|
+ "strconv"
|
|
|
+ "testing"
|
|
|
+ "time"
|
|
|
+
|
|
|
+ "github.com/mk6i/open-oscar-server/config"
|
|
|
+ "github.com/mk6i/open-oscar-server/state"
|
|
|
+ "github.com/mk6i/open-oscar-server/wire"
|
|
|
+ "github.com/stretchr/testify/assert"
|
|
|
+ "github.com/stretchr/testify/mock"
|
|
|
+ "github.com/stretchr/testify/require"
|
|
|
+)
|
|
|
+
|
|
|
+const (
|
|
|
+ testV5UIN = uint32(12345)
|
|
|
+ testV5SessionID = uint32(0xBEEF)
|
|
|
+ testV5Seq1 = uint16(42)
|
|
|
+ testV5Seq2 = uint16(7)
|
|
|
+)
|
|
|
+
|
|
|
+var testV5Addr = &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 4000}
|
|
|
+
|
|
|
+// noopSessionRegistry satisfies SessionRegistry for tests that remove legacy sessions.
|
|
|
+type noopSessionRegistry struct{}
|
|
|
+
|
|
|
+func (noopSessionRegistry) RemoveSession(*state.Session) {}
|
|
|
+
|
|
|
+func newTestV5SessionManager() *LegacySessionManager {
|
|
|
+ return NewLegacySessionManager(noopSessionRegistry{}, config.ICQLegacyConfig{}, slog.Default())
|
|
|
+}
|
|
|
+
|
|
|
+func newTestV5Handler(t *testing.T, sender PacketSender, svc LegacyService, sessions *LegacySessionManager) *V5Handler {
|
|
|
+ t.Helper()
|
|
|
+ if sessions == nil {
|
|
|
+ sessions = newTestV5SessionManager()
|
|
|
+ }
|
|
|
+ return NewV5Handler(sessions, svc, sender, NewV5PacketBuilder(sessions, nil), slog.Default())
|
|
|
+}
|
|
|
+
|
|
|
+func buildPlainV5ClientPacket(pkt V5ClientPacket) []byte {
|
|
|
+ size := 24 + len(pkt.Data)
|
|
|
+ buf := make([]byte, size)
|
|
|
+ binary.LittleEndian.PutUint16(buf[0:2], pkt.Version)
|
|
|
+ binary.LittleEndian.PutUint32(buf[2:6], pkt.Zero)
|
|
|
+ binary.LittleEndian.PutUint32(buf[6:10], pkt.UIN)
|
|
|
+ binary.LittleEndian.PutUint32(buf[10:14], pkt.SessionID)
|
|
|
+ binary.LittleEndian.PutUint16(buf[14:16], pkt.Command)
|
|
|
+ binary.LittleEndian.PutUint16(buf[16:18], pkt.SeqNum1)
|
|
|
+ binary.LittleEndian.PutUint16(buf[18:20], pkt.SeqNum2)
|
|
|
+ if len(pkt.Data) > 0 {
|
|
|
+ copy(buf[24:], pkt.Data)
|
|
|
+ }
|
|
|
+ return buf
|
|
|
+}
|
|
|
+
|
|
|
+// buildEncryptedV5ClientPacket builds a wire-format V5 client packet for Handle().
|
|
|
+// Handle applies DecryptV5Packet, which is an XOR-based involution, so we pre-apply
|
|
|
+// DecryptV5Packet to plaintext to produce bytes that decrypt back to the intended fields.
|
|
|
+func buildEncryptedV5ClientPacket(t *testing.T, pkt V5ClientPacket) []byte {
|
|
|
+ t.Helper()
|
|
|
+ buf := buildPlainV5ClientPacket(pkt)
|
|
|
+ // Place a valid checkcode at 0x14 (required for decryption key derivation).
|
|
|
+ cc := calculateV5CheckCode(buf)
|
|
|
+ binary.LittleEndian.PutUint32(buf[20:24], cc)
|
|
|
+ DecryptV5Packet(buf, 0)
|
|
|
+ // Verify Handle's decrypt path recovers the intended command.
|
|
|
+ verify := make([]byte, len(buf))
|
|
|
+ copy(verify, buf)
|
|
|
+ DecryptV5Packet(verify, 0)
|
|
|
+ require.Equal(t, pkt.Command, binary.LittleEndian.Uint16(verify[14:16]),
|
|
|
+ "V5 test packet builder failed for command 0x%04X", pkt.Command)
|
|
|
+ return buf
|
|
|
+}
|
|
|
+
|
|
|
+func v5ServerCommand(packet []byte) uint16 {
|
|
|
+ if len(packet) < 9 {
|
|
|
+ return 0
|
|
|
+ }
|
|
|
+ return binary.LittleEndian.Uint16(packet[7:9])
|
|
|
+}
|
|
|
+
|
|
|
+func v5ServerPacketData(packet []byte) []byte {
|
|
|
+ if len(packet) <= 21 {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ return packet[21:]
|
|
|
+}
|
|
|
+
|
|
|
+func v5ServerSeq1(packet []byte) uint16 {
|
|
|
+ if len(packet) < 11 {
|
|
|
+ return 0
|
|
|
+ }
|
|
|
+ return binary.LittleEndian.Uint16(packet[9:11])
|
|
|
+}
|
|
|
+
|
|
|
+func v5ServerSeq2(packet []byte) uint16 {
|
|
|
+ if len(packet) < 13 {
|
|
|
+ return 0
|
|
|
+ }
|
|
|
+ return binary.LittleEndian.Uint16(packet[11:13])
|
|
|
+}
|
|
|
+
|
|
|
+func newTestV5HandlerWithDispatcher(t *testing.T, sender PacketSender, svc LegacyService, sessions *LegacySessionManager) *V5Handler {
|
|
|
+ t.Helper()
|
|
|
+ logger := slog.Default()
|
|
|
+ v1 := NewV1Handler(sessions, svc, sender, logger)
|
|
|
+ v2 := NewV2Handler(sessions, svc, sender, NewV2PacketBuilder(), logger)
|
|
|
+ v3 := NewV3Handler(sessions, svc, sender, NewV3PacketBuilder(sessions, nil), logger)
|
|
|
+ v4 := NewV4Handler(sessions, svc, sender, NewV4PacketBuilder(sessions, nil), logger)
|
|
|
+ v5 := NewV5Handler(sessions, svc, sender, NewV5PacketBuilder(sessions, nil), logger)
|
|
|
+ cfg := config.ICQLegacyConfig{SupportedVersions: []int{3, 4, 5}}
|
|
|
+ dispatcher := NewProtocolDispatcher(v1, v2, v3, v4, v5, cfg, logger)
|
|
|
+ v5.SetDispatcher(dispatcher)
|
|
|
+ return v5
|
|
|
+}
|
|
|
+
|
|
|
+func sessionWithDirectConn(uin uint32, tcpPort, internalIP uint32, dcType uint8) *LegacySession {
|
|
|
+ s := sessionWithInstance(uin)
|
|
|
+ s.SetDirectConnectionInfo(tcpPort, internalIP, 5, dcType)
|
|
|
+ return s
|
|
|
+}
|
|
|
+
|
|
|
+func sessionWithInstance(uin uint32) *LegacySession {
|
|
|
+ s := newTestLegacySession(uin, legacySessionOptOSCARSess, func(ls *LegacySession) {
|
|
|
+ ls.SessionID = testV5SessionID
|
|
|
+ ls.Version = ICQLegacyVersionV5
|
|
|
+ })
|
|
|
+ return s
|
|
|
+}
|
|
|
+
|
|
|
+func expectV5NotConnected(sender *mockPacketSender) {
|
|
|
+ sender.EXPECT().SendPacket(testV5Addr, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *net.UDPAddr, p []byte) {
|
|
|
+ if got := v5ServerCommand(p); got != ICQLegacySrvNotConnected {
|
|
|
+ panic(fmt.Sprintf("got command 0x%04X, want NOT_CONNECTED", got))
|
|
|
+ }
|
|
|
+ }).Return(nil).Once()
|
|
|
+}
|
|
|
+
|
|
|
+func registerSession(mgr *LegacySessionManager, session *LegacySession) {
|
|
|
+ mgr.mu.Lock()
|
|
|
+ defer mgr.mu.Unlock()
|
|
|
+ mgr.sessions[session.UIN] = session
|
|
|
+ if session.Addr != nil {
|
|
|
+ mgr.addrIndex[session.Addr.String()] = session
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func defaultV5Pkt(command uint16, data []byte) V5ClientPacket {
|
|
|
+ return V5ClientPacket{
|
|
|
+ Version: ICQLegacyVersionV5,
|
|
|
+ UIN: testV5UIN,
|
|
|
+ SessionID: testV5SessionID,
|
|
|
+ Command: command,
|
|
|
+ SeqNum1: testV5Seq1,
|
|
|
+ SeqNum2: testV5Seq2,
|
|
|
+ Data: data,
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func v5LPString(s string) []byte {
|
|
|
+ buf := new(bytes.Buffer)
|
|
|
+ writeLegacyString(buf, s)
|
|
|
+ return buf.Bytes()
|
|
|
+}
|
|
|
+
|
|
|
+func v5GetDepsData(uin uint32, password string) []byte {
|
|
|
+ pwd := []byte(password)
|
|
|
+ buf := make([]byte, 4+2+len(pwd))
|
|
|
+ binary.LittleEndian.PutUint32(buf[0:4], uin)
|
|
|
+ binary.LittleEndian.PutUint16(buf[4:6], uint16(len(pwd)))
|
|
|
+ copy(buf[6:], pwd)
|
|
|
+ return buf
|
|
|
+}
|
|
|
+
|
|
|
+func v5LoginData(password string, minimal bool) []byte {
|
|
|
+ pwd := []byte(password)
|
|
|
+ if minimal {
|
|
|
+ buf := make([]byte, 4+4+2+len(pwd))
|
|
|
+ binary.LittleEndian.PutUint32(buf[4:8], 4000)
|
|
|
+ binary.LittleEndian.PutUint16(buf[8:10], uint16(len(pwd)))
|
|
|
+ copy(buf[10:], pwd)
|
|
|
+ return buf
|
|
|
+ }
|
|
|
+ buf := make([]byte, 4+4+2+len(pwd)+4+4+1+4+4+28)
|
|
|
+ off := 0
|
|
|
+ off += 4 // time
|
|
|
+ binary.LittleEndian.PutUint32(buf[off:], 4000)
|
|
|
+ off += 4
|
|
|
+ binary.LittleEndian.PutUint16(buf[off:], uint16(len(pwd)))
|
|
|
+ off += 2
|
|
|
+ copy(buf[off:], pwd)
|
|
|
+ off += len(pwd)
|
|
|
+ binary.LittleEndian.PutUint32(buf[off:], 0x98)
|
|
|
+ off += 4
|
|
|
+ binary.LittleEndian.PutUint32(buf[off:], 0x7F000001)
|
|
|
+ off += 4
|
|
|
+ buf[off] = 0
|
|
|
+ off++
|
|
|
+ binary.LittleEndian.PutUint32(buf[off:], 0)
|
|
|
+ off += 4
|
|
|
+ binary.LittleEndian.PutUint32(buf[off:], 0)
|
|
|
+ return buf
|
|
|
+}
|
|
|
+
|
|
|
+func v5ContactListData(uins []uint32) []byte {
|
|
|
+ buf := make([]byte, 1+4*len(uins))
|
|
|
+ buf[0] = byte(len(uins))
|
|
|
+ for i, u := range uins {
|
|
|
+ binary.LittleEndian.PutUint32(buf[1+4*i:], u)
|
|
|
+ }
|
|
|
+ return buf
|
|
|
+}
|
|
|
+
|
|
|
+func v5MessageData(toUIN uint32, msgType uint16, message string) []byte {
|
|
|
+ msg := []byte(message)
|
|
|
+ buf := make([]byte, 4+2+2+len(msg))
|
|
|
+ binary.LittleEndian.PutUint32(buf[0:4], toUIN)
|
|
|
+ binary.LittleEndian.PutUint16(buf[4:6], msgType)
|
|
|
+ binary.LittleEndian.PutUint16(buf[6:8], uint16(len(msg)))
|
|
|
+ copy(buf[8:], msg)
|
|
|
+ return buf
|
|
|
+}
|
|
|
+
|
|
|
+func v5MetaUserData(subCmd uint16, subData []byte) []byte {
|
|
|
+ buf := make([]byte, 2+len(subData))
|
|
|
+ binary.LittleEndian.PutUint16(buf[0:2], subCmd)
|
|
|
+ copy(buf[2:], subData)
|
|
|
+ return buf
|
|
|
+}
|
|
|
+
|
|
|
+func metaUserPkt(subCmd uint16, subData []byte) V5ClientPacket {
|
|
|
+ return defaultV5Pkt(ICQLegacyCmdMetaUser, v5MetaUserData(subCmd, subData))
|
|
|
+}
|
|
|
+
|
|
|
+func v5MetaWireMarshal(t *testing.T, body any) []byte {
|
|
|
+ t.Helper()
|
|
|
+ var buf bytes.Buffer
|
|
|
+ require.NoError(t, wire.MarshalLE(body, &buf))
|
|
|
+ return buf.Bytes()
|
|
|
+}
|
|
|
+
|
|
|
+func v5MetaSetBasicData() []byte {
|
|
|
+ buf := new(bytes.Buffer)
|
|
|
+ for _, s := range []string{
|
|
|
+ "nick", "first", "last", "a@b.com", "", "", "city", "ST",
|
|
|
+ "phone", "fax", "addr", "cell", "zip",
|
|
|
+ } {
|
|
|
+ writeLegacyString(buf, s)
|
|
|
+ }
|
|
|
+ tail := []byte{0x01, 0x00, 0, 1} // country=1, gmt=0, publish=1
|
|
|
+ return append(buf.Bytes(), tail...)
|
|
|
+}
|
|
|
+
|
|
|
+func v5MetaUnregisterData(uin uint32, password string) []byte {
|
|
|
+ pwd := []byte(password)
|
|
|
+ buf := make([]byte, 4+2+len(pwd))
|
|
|
+ binary.LittleEndian.PutUint32(buf[0:4], uin)
|
|
|
+ binary.LittleEndian.PutUint16(buf[4:6], uint16(len(pwd)))
|
|
|
+ copy(buf[6:], pwd)
|
|
|
+ return buf
|
|
|
+}
|
|
|
+
|
|
|
+func v5MetaSetPassData(password string) []byte {
|
|
|
+ pwd := []byte(password)
|
|
|
+ buf := make([]byte, 2+len(pwd))
|
|
|
+ binary.LittleEndian.PutUint16(buf[0:2], uint16(len(pwd)))
|
|
|
+ copy(buf[2:], pwd)
|
|
|
+ return buf
|
|
|
+}
|
|
|
+
|
|
|
+func v5MetaSetHPCatData(enabled bool, index uint16, desc string) []byte {
|
|
|
+ d := []byte(desc)
|
|
|
+ buf := make([]byte, 5+len(d))
|
|
|
+ if enabled {
|
|
|
+ buf[0] = 1
|
|
|
+ }
|
|
|
+ binary.LittleEndian.PutUint16(buf[1:3], index)
|
|
|
+ binary.LittleEndian.PutUint16(buf[3:5], uint16(len(d)))
|
|
|
+ copy(buf[5:], d)
|
|
|
+ return buf
|
|
|
+}
|
|
|
+
|
|
|
+func v5MetaSearchNameData() []byte {
|
|
|
+ return append(append(v5LPString("nick"), v5LPString("first")...), v5LPString("last")...)
|
|
|
+}
|
|
|
+
|
|
|
+func v5MetaSearchEmailData(email string) []byte {
|
|
|
+ return v5LPString(email)
|
|
|
+}
|
|
|
+
|
|
|
+func v5MetaInterestsWireData(t *testing.T) []byte {
|
|
|
+ t.Helper()
|
|
|
+ return v5MetaWireMarshal(t, wire.ICQ_0x07D0_0x0410_DBQueryMetaReqSetInterests{
|
|
|
+ Interests: []struct {
|
|
|
+ Code uint16
|
|
|
+ Keyword string `oscar:"len_prefix=uint16,nullterm"`
|
|
|
+ }{
|
|
|
+ {Code: 1, Keyword: "kw1"},
|
|
|
+ {Code: 2, Keyword: "kw2"},
|
|
|
+ {Code: 3, Keyword: "kw3"},
|
|
|
+ {Code: 4, Keyword: "kw4"},
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func v5MetaAffiliationsWireData(t *testing.T) []byte {
|
|
|
+ t.Helper()
|
|
|
+ return v5MetaWireMarshal(t, wire.ICQ_0x07D0_0x041A_DBQueryMetaReqSetAffiliations{
|
|
|
+ PastAffiliations: []struct {
|
|
|
+ Code uint16
|
|
|
+ Keyword string `oscar:"len_prefix=uint16,nullterm"`
|
|
|
+ }{
|
|
|
+ {Code: 1, Keyword: "p1"},
|
|
|
+ {Code: 2, Keyword: "p2"},
|
|
|
+ {Code: 3, Keyword: "p3"},
|
|
|
+ },
|
|
|
+ Affiliations: []struct {
|
|
|
+ Code uint16
|
|
|
+ Keyword string `oscar:"len_prefix=uint16,nullterm"`
|
|
|
+ }{
|
|
|
+ {Code: 4, Keyword: "c1"},
|
|
|
+ {Code: 5, Keyword: "c2"},
|
|
|
+ {Code: 6, Keyword: "c3"},
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func v5TargetUINData(target uint32) []byte {
|
|
|
+ buf := make([]byte, 4)
|
|
|
+ binary.LittleEndian.PutUint32(buf, target)
|
|
|
+ return buf
|
|
|
+}
|
|
|
+
|
|
|
+func testAuthSuccessInstance() *state.SessionInstance {
|
|
|
+ return newTestOSCARInstance(state.DisplayScreenName(strconv.FormatUint(uint64(testV5UIN), 10)))
|
|
|
+}
|
|
|
+
|
|
|
+func testMinimalUser() *state.User {
|
|
|
+ u := &state.User{}
|
|
|
+ u.ICQInfo.Basic.Nickname = "nick"
|
|
|
+ u.ICQInfo.Basic.FirstName = "first"
|
|
|
+ u.ICQInfo.Basic.LastName = "last"
|
|
|
+ return u
|
|
|
+}
|
|
|
+
|
|
|
+// v5HandleEnv groups per-case dependencies for runV5HandleCases.
|
|
|
+type v5HandleEnv struct {
|
|
|
+ T *testing.T
|
|
|
+ Sender *mockPacketSender
|
|
|
+ Service *mockLegacyService
|
|
|
+ Sessions *LegacySessionManager
|
|
|
+ Session *LegacySession // same pointer passed to Handle
|
|
|
+}
|
|
|
+
|
|
|
+// v5HandleCase is one row in a table-driven V5Handler.Handle test.
|
|
|
+type v5HandleCase struct {
|
|
|
+ name string
|
|
|
+ session *LegacySession
|
|
|
+ pkt V5ClientPacket
|
|
|
+ // packet, when set, supplies wire bytes instead of encrypting pkt.
|
|
|
+ packet func(t *testing.T) []byte
|
|
|
+ // setup configures mocks and session manager before Handle; the returned
|
|
|
+ // function validates the Handle result (and optional post-conditions).
|
|
|
+ setup func(v5HandleEnv) func(error)
|
|
|
+}
|
|
|
+
|
|
|
+// v5CaseSetup runs pre-Handle wiring and returns the post-Handle check.
|
|
|
+// If after is nil, Handle is expected to return nil.
|
|
|
+func v5CaseSetup(pre func(v5HandleEnv), after func(e v5HandleEnv, err error)) func(v5HandleEnv) func(error) {
|
|
|
+ return func(e v5HandleEnv) func(error) {
|
|
|
+ if pre != nil {
|
|
|
+ pre(e)
|
|
|
+ }
|
|
|
+ return func(err error) {
|
|
|
+ if after != nil {
|
|
|
+ after(e, err)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ assert.NoError(e.T, err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func runV5HandleCases(t *testing.T, cases []v5HandleCase) {
|
|
|
+ t.Helper()
|
|
|
+ for _, tt := range cases {
|
|
|
+ t.Run(tt.name, func(t *testing.T) {
|
|
|
+ sender := newMockPacketSender(t)
|
|
|
+ svc := newMockLegacyService(t)
|
|
|
+ sessions := newTestV5SessionManager()
|
|
|
+ e := v5HandleEnv{
|
|
|
+ T: t,
|
|
|
+ Sender: sender,
|
|
|
+ Service: svc,
|
|
|
+ Sessions: sessions,
|
|
|
+ Session: tt.session,
|
|
|
+ }
|
|
|
+ var check func(error)
|
|
|
+ if tt.setup != nil {
|
|
|
+ check = tt.setup(e)
|
|
|
+ } else {
|
|
|
+ check = func(err error) { assert.NoError(t, err) }
|
|
|
+ }
|
|
|
+ h := newTestV5Handler(t, sender, svc, sessions)
|
|
|
+ var wire []byte
|
|
|
+ if tt.packet != nil {
|
|
|
+ wire = tt.packet(t)
|
|
|
+ } else {
|
|
|
+ wire = buildEncryptedV5ClientPacket(t, tt.pkt)
|
|
|
+ }
|
|
|
+ check(h.Handle(tt.session, testV5Addr, wire))
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_Handle(t *testing.T) {
|
|
|
+ runV5HandleCases(t, []v5HandleCase{
|
|
|
+ {
|
|
|
+ name: "packet too short",
|
|
|
+ packet: func(_ *testing.T) []byte { return []byte{0x01, 0x02, 0x03} },
|
|
|
+ setup: v5CaseSetup(nil, func(e v5HandleEnv, err error) {
|
|
|
+ require.Error(e.T, err)
|
|
|
+ assert.Contains(e.T, err.Error(), "V5 packet too short")
|
|
|
+ }),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "nil session disallowed command",
|
|
|
+ session: nil,
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdKeepAlive, nil),
|
|
|
+ setup: v5CaseSetup(func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendPacket(testV5Addr, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *net.UDPAddr, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvNotConnected, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ }, nil),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "nil session ACK allowed",
|
|
|
+ session: nil,
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdAck, nil),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "session state updated",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdAck, nil),
|
|
|
+ setup: v5CaseSetup(nil, func(e v5HandleEnv, err error) {
|
|
|
+ assert.NoError(e.T, err)
|
|
|
+ assert.Equal(t, testV5Seq1, e.Session.SeqNumClient)
|
|
|
+ assert.WithinDuration(e.T, time.Now(), e.Session.GetLastActivity(), time.Second)
|
|
|
+ }),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "unknown command",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(0xFFFF, nil),
|
|
|
+ setup: v5CaseSetup(func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *LegacySession, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvAck, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ }, nil),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "inline ACK command",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdAck, nil),
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleFirstLogin(t *testing.T) {
|
|
|
+ runV5HandleCases(t, []v5HandleCase{
|
|
|
+ {
|
|
|
+ name: "with session_id2 in data",
|
|
|
+ session: nil,
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdFirstLogin, func() []byte {
|
|
|
+ b := make([]byte, 4)
|
|
|
+ binary.LittleEndian.PutUint32(b, 0xCAFEBABE)
|
|
|
+ return b
|
|
|
+ }()),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendPacket(testV5Addr, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *net.UDPAddr, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvAck, v5ServerCommand(p))
|
|
|
+ assert.GreaterOrEqual(t, len(p), 28)
|
|
|
+ }).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "empty data",
|
|
|
+ session: nil,
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdFirstLogin, nil),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendPacket(testV5Addr, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *net.UDPAddr, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvAck, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleGetDeps(t *testing.T) {
|
|
|
+ runV5HandleCases(t, []v5HandleCase{
|
|
|
+ {
|
|
|
+ name: "valid credentials",
|
|
|
+ session: nil,
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdGetDeps, v5GetDepsData(testV5UIN, "secret")),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().ValidateCredentials(mock.Anything, testV5UIN, "secret").Return(true, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendPacket(testV5Addr, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "invalid credentials",
|
|
|
+ session: nil,
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdGetDeps, v5GetDepsData(testV5UIN, "wrong")),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().ValidateCredentials(mock.Anything, testV5UIN, "wrong").Return(false, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendPacket(testV5Addr, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *net.UDPAddr, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvWrongPasswd, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "service error",
|
|
|
+ session: nil,
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdGetDeps, v5GetDepsData(testV5UIN, "secret")),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().ValidateCredentials(mock.Anything, testV5UIN, "secret").
|
|
|
+ Return(false, errors.New("db down")).Once()
|
|
|
+ e.Sender.EXPECT().SendPacket(testV5Addr, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *net.UDPAddr, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvWrongPasswd, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "packet too short",
|
|
|
+ session: nil,
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdGetDeps, []byte{0x01}),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendPacket(testV5Addr, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *net.UDPAddr, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvWrongPasswd, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleLogin(t *testing.T) {
|
|
|
+ runV5HandleCases(t, []v5HandleCase{
|
|
|
+ {
|
|
|
+ name: "auth success",
|
|
|
+ session: nil,
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdLogin, v5LoginData("secret", true)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().AuthenticateUser(mock.Anything, mock.MatchedBy(func(req AuthRequest) bool {
|
|
|
+ return req.UIN == testV5UIN && req.Password == "secret"
|
|
|
+ })).Return(&AuthResult{Success: true, oscarSession: testAuthSuccessInstance()}, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendPacket(testV5Addr, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(mock.AnythingOfType("*icq_legacy.LegacySession"), mock.AnythingOfType("[]uint8")).
|
|
|
+ Return(nil).Once()
|
|
|
+ },
|
|
|
+ func(e v5HandleEnv, err error) {
|
|
|
+ assert.NoError(e.T, err)
|
|
|
+ assert.NotNil(t, e.Sessions.GetSession(testV5UIN))
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "bad password",
|
|
|
+ session: nil,
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdLogin, v5LoginData("wrong", true)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().AuthenticateUser(mock.Anything, mock.Anything).
|
|
|
+ Return(&AuthResult{Success: false, ErrorCode: 0x0001}, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendPacket(testV5Addr, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ e.Sender.EXPECT().SendPacket(testV5Addr, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *net.UDPAddr, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvWrongPasswd, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "auth service error",
|
|
|
+ session: nil,
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdLogin, v5LoginData("secret", true)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().AuthenticateUser(mock.Anything, mock.Anything).
|
|
|
+ Return(nil, errors.New("auth failed")).Once()
|
|
|
+ e.Sender.EXPECT().SendPacket(testV5Addr, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ e.Sender.EXPECT().SendPacket(testV5Addr, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *net.UDPAddr, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvWrongPasswd, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "packet too short",
|
|
|
+ session: nil,
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdLogin, []byte{0, 0, 0}),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendPacket(testV5Addr, mock.AnythingOfType("[]uint8")).Return(nil).Once() // login ACK
|
|
|
+ e.Sender.EXPECT().SendPacket(testV5Addr, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *net.UDPAddr, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvWrongPasswd, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "truncated password field",
|
|
|
+ session: nil,
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdLogin, func() []byte {
|
|
|
+ buf := make([]byte, 12)
|
|
|
+ binary.LittleEndian.PutUint32(buf[4:8], 4000)
|
|
|
+ binary.LittleEndian.PutUint16(buf[8:10], 20) // claims 20 bytes but only 2 available
|
|
|
+ return buf
|
|
|
+ }()),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendPacket(testV5Addr, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ e.Sender.EXPECT().SendPacket(testV5Addr, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *net.UDPAddr, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvWrongPasswd, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleContactList(t *testing.T) {
|
|
|
+ runV5HandleCases(t, []v5HandleCase{
|
|
|
+ {
|
|
|
+ name: "valid contact list",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdContactList, v5ContactListData([]uint32{99999})),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().ProcessContactList(mock.Anything, mock.Anything, mock.Anything).
|
|
|
+ Return(&ContactListResult{}, nil).Once()
|
|
|
+ e.Service.EXPECT().NotifyUserOnline(mock.Anything, testV5UIN, mock.Anything).Return(nil).Once()
|
|
|
+ e.Sender.EXPECT().SendPacket(e.Session.Addr, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *LegacySession, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvUserListDone, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "nil session",
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdContactList, v5ContactListData(nil)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendPacket(testV5Addr, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *net.UDPAddr, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvNotConnected, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "parse error empty data",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdContactList, nil),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendPacket(e.Session.Addr, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *LegacySession, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvUserListDone, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "service error",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdContactList, v5ContactListData(nil)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().ProcessContactList(mock.Anything, mock.Anything, mock.Anything).
|
|
|
+ Return(nil, errors.New("fail")).Once()
|
|
|
+ e.Sender.EXPECT().SendPacket(e.Session.Addr, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *LegacySession, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvUserListDone, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandlePing(t *testing.T) {
|
|
|
+ runV5HandleCases(t, []v5HandleCase{
|
|
|
+ {
|
|
|
+ name: "keep alive",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdKeepAlive, nil),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *LegacySession, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvAck, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "keep alive 2",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdKeepAlive2, nil),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *LegacySession, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvAck, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "nil session",
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdKeepAlive, nil),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendPacket(testV5Addr, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *net.UDPAddr, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvNotConnected, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleLogoff(t *testing.T) {
|
|
|
+ runV5HandleCases(t, []v5HandleCase{
|
|
|
+ {
|
|
|
+ name: "active session",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdLogoff, nil),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ registerSession(e.Sessions, e.Session)
|
|
|
+ e.Service.EXPECT().NotifyUserOffline(mock.Anything, testV5UIN).Return(nil).Once()
|
|
|
+ },
|
|
|
+ func(e v5HandleEnv, err error) {
|
|
|
+ assert.NoError(e.T, err)
|
|
|
+ assert.Nil(t, e.Sessions.GetSession(testV5UIN))
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "nil session",
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdLogoff, nil),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ expectV5NotConnected(e.Sender)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleSetStatus(t *testing.T) {
|
|
|
+ runV5HandleCases(t, []v5HandleCase{
|
|
|
+ {
|
|
|
+ name: "valid status change",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdSetStatus, func() []byte {
|
|
|
+ b := make([]byte, 4)
|
|
|
+ binary.LittleEndian.PutUint32(b, 0x00000001)
|
|
|
+ return b
|
|
|
+ }()),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().ProcessStatusChange(mock.Anything, mock.Anything).
|
|
|
+ Return(&StatusChangeResult{}, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendPacket(e.Session.Addr, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ },
|
|
|
+ func(e v5HandleEnv, err error) {
|
|
|
+ assert.NoError(e.T, err)
|
|
|
+ assert.Equal(t, uint32(0x00000001), e.Session.GetStatus())
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "nil session",
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdSetStatus, func() []byte {
|
|
|
+ b := make([]byte, 4)
|
|
|
+ binary.LittleEndian.PutUint32(b, 1)
|
|
|
+ return b
|
|
|
+ }()),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ expectV5NotConnected(e.Sender)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "data too short",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdSetStatus, nil),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendPacket(e.Session.Addr, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleMessage(t *testing.T) {
|
|
|
+ target := sessionWithInstance(99999)
|
|
|
+ target.Addr = &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 4001}
|
|
|
+
|
|
|
+ runV5HandleCases(t, []v5HandleCase{
|
|
|
+ {
|
|
|
+ name: "online target",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdThruServer, v5MessageData(99999, 0x0001, "hi")),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ registerSession(e.Sessions, target)
|
|
|
+ e.Service.EXPECT().ProcessMessage(mock.Anything, mock.Anything, mock.Anything).
|
|
|
+ Return(&MessageResult{TargetOnline: true}, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendPacket(e.Session.Addr, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(mock.AnythingOfType("*icq_legacy.LegacySession"), mock.AnythingOfType("[]uint8")).
|
|
|
+ Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "offline stored",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdThruServer, v5MessageData(99999, 0x0001, "hi")),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().ProcessMessage(mock.Anything, mock.Anything, mock.Anything).
|
|
|
+ Return(&MessageResult{StoredOffline: true}, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendPacket(e.Session.Addr, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "nil session",
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdThruServer, v5MessageData(99999, 0x0001, "hi")),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ expectV5NotConnected(e.Sender)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "parse error",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdThruServer, []byte{1, 2, 3}),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendPacket(e.Session.Addr, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "service error",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdThruServer, v5MessageData(99999, 0x0001, "hi")),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().ProcessMessage(mock.Anything, mock.Anything, mock.Anything).
|
|
|
+ Return(nil, errors.New("fail")).Once()
|
|
|
+ e.Sender.EXPECT().SendPacket(e.Session.Addr, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleUserAdd(t *testing.T) {
|
|
|
+ runV5HandleCases(t, []v5HandleCase{
|
|
|
+ {
|
|
|
+ name: "success",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdUserAdd, v5TargetUINData(99999)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().ProcessUserAdd(mock.Anything, mock.Anything, mock.Anything).
|
|
|
+ Return(&UserAddResult{}, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendPacket(e.Session.Addr, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ },
|
|
|
+ func(e v5HandleEnv, err error) {
|
|
|
+ assert.NoError(e.T, err)
|
|
|
+ assert.Contains(t, e.Session.GetContactList(), uint32(99999))
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "nil session",
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdUserAdd, v5TargetUINData(99999)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ expectV5NotConnected(e.Sender)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "parse error",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdUserAdd, []byte{1, 2}),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendPacket(e.Session.Addr, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "service error",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdUserAdd, v5TargetUINData(99999)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().ProcessUserAdd(mock.Anything, mock.Anything, mock.Anything).
|
|
|
+ Return(nil, errors.New("fail")).Once()
|
|
|
+ e.Sender.EXPECT().SendPacket(e.Session.Addr, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_sendV5UserOnline(t *testing.T) {
|
|
|
+ const (
|
|
|
+ onlineUIN = uint32(67890)
|
|
|
+ tcpPort = uint32(5190)
|
|
|
+ internalIP = uint32(0x7F000001)
|
|
|
+ dcType = uint8(2)
|
|
|
+ userStatus = uint32(0x00000010)
|
|
|
+ )
|
|
|
+
|
|
|
+ t.Run("V5 peer includes connection info", func(t *testing.T) {
|
|
|
+ sender := newMockPacketSender(t)
|
|
|
+ svc := newMockLegacyService(t)
|
|
|
+ sessions := newTestV5SessionManager()
|
|
|
+
|
|
|
+ onlineSess := sessionWithDirectConn(onlineUIN, tcpPort, internalIP, dcType)
|
|
|
+ registerSession(sessions, onlineSess)
|
|
|
+
|
|
|
+ recipient := sessionWithInstance(testV5UIN)
|
|
|
+ wantExtIP := onlineSess.GetExternalIP()
|
|
|
+
|
|
|
+ sender.EXPECT().SendToSession(recipient, mock.MatchedBy(func(p []byte) bool {
|
|
|
+ if v5ServerCommand(p) != ICQLegacySrvUserOnline {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ data := v5ServerPacketData(p)
|
|
|
+ return len(data) >= 17 &&
|
|
|
+ binary.LittleEndian.Uint32(data[0:4]) == onlineUIN &&
|
|
|
+ binary.LittleEndian.Uint32(data[4:8]) == wantExtIP &&
|
|
|
+ binary.LittleEndian.Uint32(data[8:12]) == tcpPort &&
|
|
|
+ binary.LittleEndian.Uint32(data[12:16]) == internalIP &&
|
|
|
+ data[16] == dcType
|
|
|
+ })).Return(nil).Once()
|
|
|
+
|
|
|
+ h := newTestV5Handler(t, sender, svc, sessions)
|
|
|
+ assert.NoError(t, h.sendV5UserOnline(recipient, onlineUIN, userStatus))
|
|
|
+ })
|
|
|
+
|
|
|
+ t.Run("non-V5 online user zeros connection info", func(t *testing.T) {
|
|
|
+ sender := newMockPacketSender(t)
|
|
|
+ svc := newMockLegacyService(t)
|
|
|
+ sessions := newTestV5SessionManager()
|
|
|
+
|
|
|
+ onlineSess := newTestLegacySession(onlineUIN, legacySessionOptVersion(ICQLegacyVersionV3))
|
|
|
+ onlineSess.SetDirectConnectionInfo(tcpPort, internalIP, 5, dcType)
|
|
|
+ registerSession(sessions, onlineSess)
|
|
|
+
|
|
|
+ recipient := sessionWithInstance(testV5UIN)
|
|
|
+
|
|
|
+ sender.EXPECT().SendToSession(recipient, mock.MatchedBy(func(p []byte) bool {
|
|
|
+ data := v5ServerPacketData(p)
|
|
|
+ return v5ServerCommand(p) == ICQLegacySrvUserOnline &&
|
|
|
+ len(data) >= 17 &&
|
|
|
+ binary.LittleEndian.Uint32(data[4:8]) == 0 &&
|
|
|
+ binary.LittleEndian.Uint32(data[8:12]) == 0
|
|
|
+ })).Return(nil).Once()
|
|
|
+
|
|
|
+ h := newTestV5Handler(t, sender, svc, sessions)
|
|
|
+ assert.NoError(t, h.sendV5UserOnline(recipient, onlineUIN, userStatus))
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_sendV5AckToAddr(t *testing.T) {
|
|
|
+ sender := newMockPacketSender(t)
|
|
|
+ svc := newMockLegacyService(t)
|
|
|
+ h := newTestV5Handler(t, sender, svc, newTestV5SessionManager())
|
|
|
+
|
|
|
+ sender.EXPECT().SendPacket(testV5Addr, mock.MatchedBy(func(p []byte) bool {
|
|
|
+ return v5ServerCommand(p) == ICQLegacySrvAck &&
|
|
|
+ v5ServerSeq1(p) == testV5Seq1 &&
|
|
|
+ v5ServerSeq2(p) == testV5Seq2 &&
|
|
|
+ binary.LittleEndian.Uint32(p[3:7]) == testV5SessionID &&
|
|
|
+ binary.LittleEndian.Uint32(p[13:17]) == testV5UIN &&
|
|
|
+ len(v5ServerPacketData(p)) == 0
|
|
|
+ })).Return(nil).Once()
|
|
|
+
|
|
|
+ assert.NoError(t, h.sendV5AckToAddr(testV5Addr, testV5SessionID, testV5UIN, testV5Seq1, testV5Seq2))
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_sendV5UserOffline(t *testing.T) {
|
|
|
+ const offlineUIN = uint32(88888)
|
|
|
+
|
|
|
+ sender := newMockPacketSender(t)
|
|
|
+ svc := newMockLegacyService(t)
|
|
|
+ recipient := sessionWithInstance(testV5UIN)
|
|
|
+
|
|
|
+ sender.EXPECT().SendToSession(recipient, mock.MatchedBy(func(p []byte) bool {
|
|
|
+ data := v5ServerPacketData(p)
|
|
|
+ return v5ServerCommand(p) == ICQLegacySrvUserOffline &&
|
|
|
+ len(data) == 4 &&
|
|
|
+ binary.LittleEndian.Uint32(data[0:4]) == offlineUIN
|
|
|
+ })).Return(nil).Once()
|
|
|
+
|
|
|
+ h := newTestV5Handler(t, sender, svc, newTestV5SessionManager())
|
|
|
+ assert.NoError(t, h.sendV5UserOffline(recipient, offlineUIN))
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_sendV5UserStatus(t *testing.T) {
|
|
|
+ const (
|
|
|
+ changedUIN = uint32(88888)
|
|
|
+ status = uint32(0x00010002) // estat=1, status=2
|
|
|
+ )
|
|
|
+
|
|
|
+ sender := newMockPacketSender(t)
|
|
|
+ svc := newMockLegacyService(t)
|
|
|
+ recipient := sessionWithInstance(testV5UIN)
|
|
|
+
|
|
|
+ sender.EXPECT().SendToSession(recipient, mock.MatchedBy(func(p []byte) bool {
|
|
|
+ data := v5ServerPacketData(p)
|
|
|
+ return v5ServerCommand(p) == ICQLegacySrvUserStatus &&
|
|
|
+ len(data) == 8 &&
|
|
|
+ binary.LittleEndian.Uint32(data[0:4]) == changedUIN &&
|
|
|
+ binary.LittleEndian.Uint16(data[4:6]) == uint16(status&0xFFFF) &&
|
|
|
+ binary.LittleEndian.Uint16(data[6:8]) == uint16(status>>16)
|
|
|
+ })).Return(nil).Once()
|
|
|
+
|
|
|
+ h := newTestV5Handler(t, sender, svc, newTestV5SessionManager())
|
|
|
+ assert.NoError(t, h.sendV5UserStatus(recipient, changedUIN, status))
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleLogoff_notifiesContacts(t *testing.T) {
|
|
|
+ const contactUIN = uint32(88888)
|
|
|
+
|
|
|
+ sender := newMockPacketSender(t)
|
|
|
+ svc := newMockLegacyService(t)
|
|
|
+ sessions := newTestV5SessionManager()
|
|
|
+
|
|
|
+ loggingOff := sessionWithInstance(testV5UIN)
|
|
|
+ loggingOff.SetContactList([]uint32{contactUIN})
|
|
|
+ contact := sessionWithInstance(contactUIN)
|
|
|
+ contact.SetContactList([]uint32{testV5UIN})
|
|
|
+ registerSession(sessions, loggingOff)
|
|
|
+ registerSession(sessions, contact)
|
|
|
+
|
|
|
+ svc.EXPECT().NotifyUserOffline(mock.Anything, testV5UIN).Return(nil).Once()
|
|
|
+ sender.EXPECT().SendToSession(contact, mock.MatchedBy(func(p []byte) bool {
|
|
|
+ data := v5ServerPacketData(p)
|
|
|
+ return v5ServerCommand(p) == ICQLegacySrvUserOffline &&
|
|
|
+ len(data) == 4 &&
|
|
|
+ binary.LittleEndian.Uint32(data[0:4]) == testV5UIN
|
|
|
+ })).Return(nil).Once()
|
|
|
+
|
|
|
+ h := newTestV5HandlerWithDispatcher(t, sender, svc, sessions)
|
|
|
+ pkt := buildEncryptedV5ClientPacket(t, defaultV5Pkt(ICQLegacyCmdLogoff, nil))
|
|
|
+ assert.NoError(t, h.Handle(loggingOff, testV5Addr, pkt))
|
|
|
+ assert.Nil(t, sessions.GetSession(testV5UIN))
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleSetStatus_notifiesContacts(t *testing.T) {
|
|
|
+ const (
|
|
|
+ contactUIN = uint32(88888)
|
|
|
+ newStatus = uint32(0x00010002)
|
|
|
+ )
|
|
|
+
|
|
|
+ sender := newMockPacketSender(t)
|
|
|
+ svc := newMockLegacyService(t)
|
|
|
+ sessions := newTestV5SessionManager()
|
|
|
+
|
|
|
+ changer := sessionWithInstance(testV5UIN)
|
|
|
+ contact := sessionWithInstance(contactUIN)
|
|
|
+ registerSession(sessions, changer)
|
|
|
+ registerSession(sessions, contact)
|
|
|
+
|
|
|
+ svc.EXPECT().ProcessStatusChange(mock.Anything, mock.Anything).
|
|
|
+ Return(&StatusChangeResult{
|
|
|
+ NotifyTargets: []NotifyTarget{{UIN: contactUIN}},
|
|
|
+ }, nil).Once()
|
|
|
+
|
|
|
+ sender.EXPECT().SendPacket(changer.Addr, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ sender.EXPECT().SendToSession(contact, mock.MatchedBy(func(p []byte) bool {
|
|
|
+ data := v5ServerPacketData(p)
|
|
|
+ return v5ServerCommand(p) == ICQLegacySrvUserStatus &&
|
|
|
+ len(data) == 8 &&
|
|
|
+ binary.LittleEndian.Uint32(data[0:4]) == testV5UIN &&
|
|
|
+ binary.LittleEndian.Uint16(data[4:6]) == uint16(newStatus&0xFFFF) &&
|
|
|
+ binary.LittleEndian.Uint16(data[6:8]) == uint16(newStatus>>16)
|
|
|
+ })).Return(nil).Once()
|
|
|
+
|
|
|
+ h := newTestV5HandlerWithDispatcher(t, sender, svc, sessions)
|
|
|
+ pkt := buildEncryptedV5ClientPacket(t, defaultV5Pkt(ICQLegacyCmdSetStatus, func() []byte {
|
|
|
+ b := make([]byte, 4)
|
|
|
+ binary.LittleEndian.PutUint32(b, newStatus)
|
|
|
+ return b
|
|
|
+ }()))
|
|
|
+ assert.NoError(t, h.Handle(changer, testV5Addr, pkt))
|
|
|
+ assert.Equal(t, newStatus, changer.GetStatus())
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleUserAdd_notifyTarget(t *testing.T) {
|
|
|
+ const targetUIN = uint32(99999)
|
|
|
+
|
|
|
+ userAddResult := &UserAddResult{
|
|
|
+ TargetOnline: true,
|
|
|
+ TargetStatus: 0x00000010,
|
|
|
+ SendYouWereAdded: true,
|
|
|
+ }
|
|
|
+
|
|
|
+ t.Run("without dispatcher uses packet builder", func(t *testing.T) {
|
|
|
+ sender := newMockPacketSender(t)
|
|
|
+ svc := newMockLegacyService(t)
|
|
|
+ sessions := newTestV5SessionManager()
|
|
|
+
|
|
|
+ adder := sessionWithDirectConn(testV5UIN, 5191, 0x7F000002, 1)
|
|
|
+ adder.SetStatus(0x00000020)
|
|
|
+ target := sessionWithInstance(targetUIN)
|
|
|
+ registerSession(sessions, adder)
|
|
|
+ registerSession(sessions, target)
|
|
|
+
|
|
|
+ svc.EXPECT().ProcessUserAdd(mock.Anything, mock.Anything, mock.Anything).
|
|
|
+ Return(userAddResult, nil).Once()
|
|
|
+
|
|
|
+ sender.EXPECT().SendPacket(adder.Addr, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ sender.EXPECT().SendToSession(adder, mock.MatchedBy(func(p []byte) bool {
|
|
|
+ return v5ServerCommand(p) == ICQLegacySrvUserOnline &&
|
|
|
+ len(v5ServerPacketData(p)) >= 4 &&
|
|
|
+ binary.LittleEndian.Uint32(v5ServerPacketData(p)[0:4]) == targetUIN
|
|
|
+ })).Return(nil).Once()
|
|
|
+ sender.EXPECT().SendToSession(target, mock.MatchedBy(func(p []byte) bool {
|
|
|
+ if v5ServerCommand(p) != ICQLegacySrvSysMsgOnline {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ data := v5ServerPacketData(p)
|
|
|
+ return len(data) >= 8 &&
|
|
|
+ binary.LittleEndian.Uint32(data[0:4]) == testV5UIN &&
|
|
|
+ binary.LittleEndian.Uint16(data[4:6]) == ICQLegacyMsgAdded
|
|
|
+ })).Return(nil).Once()
|
|
|
+ sender.EXPECT().SendToSession(target, mock.MatchedBy(func(p []byte) bool {
|
|
|
+ data := v5ServerPacketData(p)
|
|
|
+ return v5ServerCommand(p) == ICQLegacySrvUserOnline &&
|
|
|
+ len(data) >= 4 &&
|
|
|
+ binary.LittleEndian.Uint32(data[0:4]) == testV5UIN
|
|
|
+ })).Return(nil).Once()
|
|
|
+
|
|
|
+ h := newTestV5Handler(t, sender, svc, sessions)
|
|
|
+ pkt := buildEncryptedV5ClientPacket(t, defaultV5Pkt(ICQLegacyCmdUserAdd, v5TargetUINData(targetUIN)))
|
|
|
+ assert.NoError(t, h.Handle(adder, testV5Addr, pkt))
|
|
|
+ assert.Contains(t, adder.GetContactList(), targetUIN)
|
|
|
+ })
|
|
|
+
|
|
|
+ t.Run("with dispatcher sends user online via sendV5UserOnline", func(t *testing.T) {
|
|
|
+ sender := newMockPacketSender(t)
|
|
|
+ svc := newMockLegacyService(t)
|
|
|
+ sessions := newTestV5SessionManager()
|
|
|
+
|
|
|
+ adder := sessionWithDirectConn(testV5UIN, 5191, 0x7F000002, 1)
|
|
|
+ adder.SetStatus(0x00000020)
|
|
|
+ target := sessionWithInstance(targetUIN)
|
|
|
+ registerSession(sessions, adder)
|
|
|
+ registerSession(sessions, target)
|
|
|
+
|
|
|
+ svc.EXPECT().ProcessUserAdd(mock.Anything, mock.Anything, mock.Anything).
|
|
|
+ Return(userAddResult, nil).Once()
|
|
|
+
|
|
|
+ sender.EXPECT().SendPacket(adder.Addr, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ sender.EXPECT().SendToSession(adder, mock.MatchedBy(func(p []byte) bool {
|
|
|
+ return v5ServerCommand(p) == ICQLegacySrvUserOnline
|
|
|
+ })).Return(nil).Once()
|
|
|
+ sender.EXPECT().SendToSession(target, mock.MatchedBy(func(p []byte) bool {
|
|
|
+ return v5ServerCommand(p) == ICQLegacySrvSysMsgOnline
|
|
|
+ })).Return(nil).Once()
|
|
|
+ sender.EXPECT().SendToSession(target, mock.MatchedBy(func(p []byte) bool {
|
|
|
+ data := v5ServerPacketData(p)
|
|
|
+ return v5ServerCommand(p) == ICQLegacySrvUserOnline &&
|
|
|
+ len(data) >= 17 &&
|
|
|
+ binary.LittleEndian.Uint32(data[0:4]) == testV5UIN &&
|
|
|
+ binary.LittleEndian.Uint32(data[8:12]) == 5191 &&
|
|
|
+ data[16] == 1
|
|
|
+ })).Return(nil).Once()
|
|
|
+
|
|
|
+ h := newTestV5HandlerWithDispatcher(t, sender, svc, sessions)
|
|
|
+ pkt := buildEncryptedV5ClientPacket(t, defaultV5Pkt(ICQLegacyCmdUserAdd, v5TargetUINData(targetUIN)))
|
|
|
+ assert.NoError(t, h.Handle(adder, testV5Addr, pkt))
|
|
|
+ })
|
|
|
+
|
|
|
+ t.Run("SendYouWereAdded false skips target notifications", func(t *testing.T) {
|
|
|
+ sender := newMockPacketSender(t)
|
|
|
+ svc := newMockLegacyService(t)
|
|
|
+ sessions := newTestV5SessionManager()
|
|
|
+
|
|
|
+ adder := sessionWithInstance(testV5UIN)
|
|
|
+ target := sessionWithInstance(targetUIN)
|
|
|
+ registerSession(sessions, adder)
|
|
|
+ registerSession(sessions, target)
|
|
|
+
|
|
|
+ svc.EXPECT().ProcessUserAdd(mock.Anything, mock.Anything, mock.Anything).
|
|
|
+ Return(&UserAddResult{
|
|
|
+ TargetOnline: true,
|
|
|
+ TargetStatus: 0x10,
|
|
|
+ SendYouWereAdded: false,
|
|
|
+ }, nil).Once()
|
|
|
+
|
|
|
+ sender.EXPECT().SendPacket(adder.Addr, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ sender.EXPECT().SendToSession(adder, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+
|
|
|
+ h := newTestV5Handler(t, sender, svc, sessions)
|
|
|
+ pkt := buildEncryptedV5ClientPacket(t, defaultV5Pkt(ICQLegacyCmdUserAdd, v5TargetUINData(targetUIN)))
|
|
|
+ assert.NoError(t, h.Handle(adder, testV5Addr, pkt))
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleOfflineMsgReq(t *testing.T) {
|
|
|
+ runV5HandleCases(t, []v5HandleCase{
|
|
|
+ {
|
|
|
+ name: "no messages",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdSysMsgReq, nil),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().GetOfflineMessages(mock.Anything, testV5UIN).
|
|
|
+ Return([]LegacyOfflineMessage{}, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once() // ack
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *LegacySession, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvSysMsgDone, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "with messages",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdSysMsgReq, nil),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().GetOfflineMessages(mock.Anything, testV5UIN).
|
|
|
+ Return([]LegacyOfflineMessage{{FromUIN: 99999, ToUIN: testV5UIN, MsgType: 1, Message: "hi"}}, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(3) // ack + msg + done
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "nil session",
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdSysMsgReq, nil),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ expectV5NotConnected(e.Sender)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "service error",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdSysMsgReq, nil),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().GetOfflineMessages(mock.Anything, testV5UIN).
|
|
|
+ Return(nil, errors.New("fail")).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once() // ack
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *LegacySession, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvSysMsgDone, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleOfflineMsgAck(t *testing.T) {
|
|
|
+ runV5HandleCases(t, []v5HandleCase{
|
|
|
+ {
|
|
|
+ name: "success",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdSysMsgDoneAck, nil),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().AckOfflineMessages(mock.Anything, testV5UIN).Return(nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "nil session",
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdSysMsgDoneAck, nil),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ expectV5NotConnected(e.Sender)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "service error",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdSysMsgDoneAck, nil),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().AckOfflineMessages(mock.Anything, testV5UIN).
|
|
|
+ Return(errors.New("fail")).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleMetaUser(t *testing.T) {
|
|
|
+ runV5HandleCases(t, []v5HandleCase{
|
|
|
+ {
|
|
|
+ name: "login info found",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdMetaUser, v5MetaUserData(0x04CE, v5TargetUINData(testV5UIN))),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().GetFullUserInfo(mock.Anything, testV5UIN).Return(testMinimalUser(), nil).Once()
|
|
|
+ e.Service.EXPECT().GetUserInfo(mock.Anything, testV5UIN).
|
|
|
+ Return(&LegacyUserSearchResult{UIN: testV5UIN}, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(8)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "login info not found",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdMetaUser, v5MetaUserData(0x04CE, v5TargetUINData(testV5UIN))),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().GetFullUserInfo(mock.Anything, testV5UIN).Return(nil, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2) // meta ack + fail
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "packet too short",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdMetaUser, []byte{0x01}),
|
|
|
+ setup: v5CaseSetup(nil, func(e v5HandleEnv, err error) {
|
|
|
+ require.Error(e.T, err)
|
|
|
+ assert.Contains(e.T, err.Error(), "META_USER packet too short")
|
|
|
+ }),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "nil session",
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdMetaUser, v5MetaUserData(0x04CE, v5TargetUINData(testV5UIN))),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ expectV5NotConnected(e.Sender)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleMetaUser_Login(t *testing.T) {
|
|
|
+ runV5HandleCases(t, []v5HandleCase{
|
|
|
+ {
|
|
|
+ name: "meta login reply",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaLogin, nil),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "meta login empty subData",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaLogin, []byte{}),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleMetaUser_GetInfo(t *testing.T) {
|
|
|
+ runV5HandleCases(t, []v5HandleCase{
|
|
|
+ {
|
|
|
+ name: "login info alt opcode",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaLoginInfo2, v5TargetUINData(testV5UIN)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().GetFullUserInfo(mock.Anything, testV5UIN).Return(testMinimalUser(), nil).Once()
|
|
|
+ e.Service.EXPECT().GetUserInfo(mock.Anything, testV5UIN).
|
|
|
+ Return(&LegacyUserSearchResult{UIN: testV5UIN}, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(8)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "user full info found",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaUserFullInfo, v5TargetUINData(99999)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().GetFullUserInfo(mock.Anything, uint32(99999)).Return(testMinimalUser(), nil).Once()
|
|
|
+ e.Service.EXPECT().GetUserInfo(mock.Anything, uint32(99999)).
|
|
|
+ Return(&LegacyUserSearchResult{UIN: 99999}, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(8)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "user full info data too short",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaUserFullInfo, []byte{0x01, 0x02, 0x03}),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "user full info2 found",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaUserFullInfo2, v5TargetUINData(99999)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().GetFullUserInfo(mock.Anything, uint32(99999)).Return(testMinimalUser(), nil).Once()
|
|
|
+ e.Service.EXPECT().GetUserInfo(mock.Anything, uint32(99999)).
|
|
|
+ Return(&LegacyUserSearchResult{UIN: 99999}, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(8)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "user full info2 not found",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaUserFullInfo2, v5TargetUINData(99999)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().GetFullUserInfo(mock.Anything, uint32(99999)).Return(nil, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "user info found",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaUserInfo, v5TargetUINData(99999)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().GetUserInfo(mock.Anything, uint32(99999)).
|
|
|
+ Return(&LegacyUserSearchResult{UIN: 99999, Nickname: "nick"}, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "user info not found",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaUserInfo, v5TargetUINData(99999)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().GetUserInfo(mock.Anything, uint32(99999)).Return(nil, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleMetaUser_SetProfile(t *testing.T) {
|
|
|
+ runV5HandleCases(t, []v5HandleCase{
|
|
|
+ {
|
|
|
+ name: "set basic success",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaSetBasic2, v5MetaSetBasicData()),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().UpdateBasicInfo(mock.Anything, testV5UIN, mock.Anything).Return(nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "set basic service error",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaSetBasic2, v5MetaSetBasicData()),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().UpdateBasicInfo(mock.Anything, testV5UIN, mock.Anything).
|
|
|
+ Return(errors.New("fail")).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "set work success",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaSetWork2, v5MetaWireMarshal(t, wire.ICQ_0x07D0_0x03F3_DBQueryMetaReqSetWorkInfo{
|
|
|
+ Company: "co",
|
|
|
+ })),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().UpdateWorkInfo(mock.Anything, testV5UIN, mock.Anything).Return(nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "set work parse error",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaSetWork2, nil),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "set more success",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaSetMore2, v5MetaWireMarshal(t, wire.ICQ_0x07D0_0x03FD_DBQueryMetaReqSetMoreInfo{
|
|
|
+ Gender: 1,
|
|
|
+ })),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().UpdateMoreInfo(mock.Anything, testV5UIN, mock.Anything).Return(nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "set more parse error",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaSetMore2, nil),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "set about success",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaSetAbout, v5MetaWireMarshal(t, wire.ICQ_0x07D0_0x0406_DBQueryMetaReqSetNotes{
|
|
|
+ Notes: "about me",
|
|
|
+ })),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().SetNotes(mock.Anything, testV5UIN, "about me").Return(nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "set about parse error",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaSetAbout, nil),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "set interests success",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaSetInterests, v5MetaInterestsWireData(t)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().SetInterests(mock.Anything, testV5UIN, mock.Anything).Return(nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "set interests wrong count",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaSetInterests, v5MetaWireMarshal(t, wire.ICQ_0x07D0_0x0410_DBQueryMetaReqSetInterests{
|
|
|
+ Interests: []struct {
|
|
|
+ Code uint16
|
|
|
+ Keyword string `oscar:"len_prefix=uint16,nullterm"`
|
|
|
+ }{{Code: 1, Keyword: "only"}},
|
|
|
+ })),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "set affiliations success",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaSetAffiliations, v5MetaAffiliationsWireData(t)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().SetAffiliations(mock.Anything, testV5UIN, mock.Anything).Return(nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "set affiliations wrong count",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaSetAffiliations, v5MetaWireMarshal(t, wire.ICQ_0x07D0_0x041A_DBQueryMetaReqSetAffiliations{
|
|
|
+ PastAffiliations: []struct {
|
|
|
+ Code uint16
|
|
|
+ Keyword string `oscar:"len_prefix=uint16,nullterm"`
|
|
|
+ }{{Code: 1, Keyword: "p1"}},
|
|
|
+ Affiliations: []struct {
|
|
|
+ Code uint16
|
|
|
+ Keyword string `oscar:"len_prefix=uint16,nullterm"`
|
|
|
+ }{{Code: 2, Keyword: "c1"}},
|
|
|
+ })),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "set security success",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaSetSecurity, v5MetaWireMarshal(t, wire.ICQ_0x07D0_0x0424_DBQueryMetaReqSetPermissions{
|
|
|
+ Authorization: 0,
|
|
|
+ WebAware: 1,
|
|
|
+ })),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().UpdatePermissions(mock.Anything, testV5UIN, mock.Anything).Return(nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "set security parse error",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaSetSecurity, nil),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "set password success",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaSetPass, v5MetaSetPassData("newpass")),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().SetPassword(mock.Anything, testV5UIN, "", "newpass").Return(nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "set password truncated",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaSetPass, []byte{0x01}),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "set hpcat success",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaSetHPCat, v5MetaSetHPCatData(true, 1, "home")),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().SetHomepageCategory(mock.Anything, testV5UIN, mock.Anything).Return(nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "set hpcat data too short",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaSetHPCat, []byte{0x01, 0x02}),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleMetaUser_Search(t *testing.T) {
|
|
|
+ runV5HandleCases(t, []v5HandleCase{
|
|
|
+ {
|
|
|
+ name: "search name found",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaSearchName, v5MetaSearchNameData()),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().SearchByName(mock.Anything, "nick", "first", "last", "").
|
|
|
+ Return([]LegacyUserSearchResult{{UIN: 99999}}, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "search name empty",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaSearchName, v5MetaSearchNameData()),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().SearchByName(mock.Anything, "nick", "first", "last", "").
|
|
|
+ Return(nil, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "search uin found",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaSearchUIN, v5TargetUINData(99999)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().GetUserInfoForProtocol(mock.Anything, uint32(99999)).
|
|
|
+ Return(&UserInfoResult{UIN: 99999}, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "search uin data too short",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaSearchUIN, []byte{0x01, 0x02, 0x03}),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "search email found",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaSearchEmail, v5MetaSearchEmailData("a@b.com")),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().SearchByName(mock.Anything, "", "", "", "a@b.com").
|
|
|
+ Return([]LegacyUserSearchResult{{UIN: 99999}}, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "search email empty",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaSearchEmail, v5MetaSearchEmailData("a@b.com")),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().SearchByName(mock.Anything, "", "", "", "a@b.com").
|
|
|
+ Return(nil, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "search white found",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaSearchWhite, v5LPString("alice")),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().WhitePagesSearch(mock.Anything, mock.Anything).
|
|
|
+ Return([]LegacyUserSearchResult{{UIN: 99999}}, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "search white no criteria",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaSearchWhite, nil),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "search white2 found",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaSearchWhite2, v5LPString("alice")),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().SearchByName(mock.Anything, "", "alice", "", "").
|
|
|
+ Return([]LegacyUserSearchResult{{UIN: 99999}}, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "search white2 empty",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaSearchWhite2, v5LPString("alice")),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().SearchByName(mock.Anything, "", "alice", "", "").
|
|
|
+ Return(nil, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleMetaUser_Unregister(t *testing.T) {
|
|
|
+ runV5HandleCases(t, []v5HandleCase{
|
|
|
+ {
|
|
|
+ name: "unregister success",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaUserUnreg, v5MetaUnregisterData(testV5UIN, "secret")),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().DeleteUser(mock.Anything, testV5UIN, "secret").Return(nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "unregister data too short",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(ICQLegacyMetaUserUnreg, []byte{0x01, 0x02, 0x03, 0x04, 0x05}),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleMetaUser_Default(t *testing.T) {
|
|
|
+ runV5HandleCases(t, []v5HandleCase{
|
|
|
+ {
|
|
|
+ name: "unknown sub-command",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: metaUserPkt(0xFFFF, nil),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleVisibleList(t *testing.T) {
|
|
|
+ runV5HandleCases(t, []v5HandleCase{
|
|
|
+ {
|
|
|
+ name: "valid list",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdVisibleList, v5ContactListData([]uint32{11111})),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *LegacySession, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvAck, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ },
|
|
|
+ func(e v5HandleEnv, err error) {
|
|
|
+ assert.NoError(e.T, err)
|
|
|
+ assert.True(t, e.Session.IsOnVisibleList(11111))
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "nil session",
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdVisibleList, v5ContactListData([]uint32{11111})),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ expectV5NotConnected(e.Sender)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "parse error",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdVisibleList, nil),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ },
|
|
|
+ func(e v5HandleEnv, err error) {
|
|
|
+ assert.NoError(e.T, err)
|
|
|
+ assert.False(t, e.Session.IsOnVisibleList(11111))
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleInvisibleList(t *testing.T) {
|
|
|
+ runV5HandleCases(t, []v5HandleCase{
|
|
|
+ {
|
|
|
+ name: "valid list",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdInvisibleList, v5ContactListData([]uint32{22222})),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ },
|
|
|
+ func(e v5HandleEnv, err error) {
|
|
|
+ assert.NoError(e.T, err)
|
|
|
+ assert.True(t, e.Session.IsOnInvisibleList(22222))
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "nil session",
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdInvisibleList, v5ContactListData([]uint32{22222})),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ expectV5NotConnected(e.Sender)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "parse error",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdInvisibleList, nil),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ },
|
|
|
+ func(e v5HandleEnv, err error) {
|
|
|
+ assert.NoError(e.T, err)
|
|
|
+ assert.False(t, e.Session.IsOnInvisibleList(22222))
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleChangeVILists(t *testing.T) {
|
|
|
+ runV5HandleCases(t, []v5HandleCase{
|
|
|
+ {
|
|
|
+ name: "valid action",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdChangeVILists, func() []byte {
|
|
|
+ b := make([]byte, 5)
|
|
|
+ b[0] = 1
|
|
|
+ binary.LittleEndian.PutUint32(b[1:], 33333)
|
|
|
+ return b
|
|
|
+ }()),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "nil session",
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdChangeVILists, func() []byte {
|
|
|
+ b := make([]byte, 5)
|
|
|
+ b[0] = 1
|
|
|
+ binary.LittleEndian.PutUint32(b[1:], 33333)
|
|
|
+ return b
|
|
|
+ }()),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ expectV5NotConnected(e.Sender)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "data too short",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdChangeVILists, []byte{1}),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleOldSearchUIN(t *testing.T) {
|
|
|
+ runV5HandleCases(t, []v5HandleCase{
|
|
|
+ {
|
|
|
+ name: "user found",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdSearchUIN, v5TargetUINData(99999)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().SearchByUIN(mock.Anything, uint32(99999)).
|
|
|
+ Return(&LegacyUserSearchResult{UIN: 99999}, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once() // ack
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once() // found
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *LegacySession, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvSearchDone, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "user not found",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdSearchUIN, v5TargetUINData(99999)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().SearchByUIN(mock.Anything, uint32(99999)).Return(nil, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once() // ack
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *LegacySession, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvSearchDone, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "service error",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdSearchUIN, v5TargetUINData(99999)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().SearchByUIN(mock.Anything, uint32(99999)).
|
|
|
+ Return(nil, errors.New("fail")).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *LegacySession, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvSearchDone, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "data too short",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdSearchUIN, []byte{1, 2}),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *LegacySession, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvSearchDone, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "nil session",
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdSearchUIN, v5TargetUINData(99999)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ expectV5NotConnected(e.Sender)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleOldSearch(t *testing.T) {
|
|
|
+ runV5HandleCases(t, []v5HandleCase{
|
|
|
+ {
|
|
|
+ name: "any data",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdSearchUser, []byte{0x01}),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *LegacySession, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvSearchDone, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "nil session",
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdSearchUser, []byte{0x01}),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ expectV5NotConnected(e.Sender)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleOldInfoReq(t *testing.T) {
|
|
|
+ runV5HandleCases(t, []v5HandleCase{
|
|
|
+ {
|
|
|
+ name: "user found",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdInfoReq, v5TargetUINData(99999)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().GetUserInfo(mock.Anything, uint32(99999)).
|
|
|
+ Return(&LegacyUserSearchResult{UIN: 99999}, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once() // ack
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *LegacySession, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvInfoReply, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "user not found",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdInfoReq, v5TargetUINData(99999)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().GetUserInfo(mock.Anything, uint32(99999)).Return(nil, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *LegacySession, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvInvalidUIN, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "service error",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdInfoReq, v5TargetUINData(99999)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().GetUserInfo(mock.Anything, uint32(99999)).
|
|
|
+ Return(nil, errors.New("fail")).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *LegacySession, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvInvalidUIN, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "data too short",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdInfoReq, []byte{1, 2}),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "nil session",
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdInfoReq, v5TargetUINData(99999)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ expectV5NotConnected(e.Sender)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleOldExtInfoReq(t *testing.T) {
|
|
|
+ runV5HandleCases(t, []v5HandleCase{
|
|
|
+ {
|
|
|
+ name: "user found",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdExtInfoReq, v5TargetUINData(99999)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().GetFullUserInfo(mock.Anything, uint32(99999)).Return(testMinimalUser(), nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *LegacySession, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvExtInfoReply, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "user not found",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdExtInfoReq, v5TargetUINData(99999)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().GetFullUserInfo(mock.Anything, uint32(99999)).Return(nil, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *LegacySession, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvInvalidUIN, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "service error",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdExtInfoReq, v5TargetUINData(99999)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().GetFullUserInfo(mock.Anything, uint32(99999)).
|
|
|
+ Return(nil, errors.New("fail")).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *LegacySession, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvInvalidUIN, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "data too short",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdExtInfoReq, []byte{1}),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "nil session",
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdExtInfoReq, v5TargetUINData(99999)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ expectV5NotConnected(e.Sender)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleAckNewUIN(t *testing.T) {
|
|
|
+ runV5HandleCases(t, []v5HandleCase{
|
|
|
+ {
|
|
|
+ name: "with session",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdRegNewUser, nil),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).
|
|
|
+ Run(func(_ *LegacySession, p []byte) {
|
|
|
+ assert.Equal(t, ICQLegacySrvAck, v5ServerCommand(p))
|
|
|
+ }).Return(nil).Once()
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "nil session",
|
|
|
+ pkt: defaultV5Pkt(ICQLegacyCmdRegNewUser, nil),
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleDirectWhiteSearch(t *testing.T) {
|
|
|
+ runV5HandleCases(t, []v5HandleCase{
|
|
|
+ {
|
|
|
+ name: "results found",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(0x0532, v5LPString("alice")),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().WhitePagesSearch(mock.Anything, mock.Anything).
|
|
|
+ Return([]LegacyUserSearchResult{{UIN: 99999}}, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2) // ack + result
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "nil session",
|
|
|
+ pkt: defaultV5Pkt(0x0532, v5LPString("alice")),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ expectV5NotConnected(e.Sender)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleDirectNameSearch(t *testing.T) {
|
|
|
+ nameData := append(append(v5LPString("nick"), v5LPString("first")...), v5LPString("last")...)
|
|
|
+
|
|
|
+ runV5HandleCases(t, []v5HandleCase{
|
|
|
+ {
|
|
|
+ name: "results found",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(0x0514, nameData),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().SearchByName(mock.Anything, "nick", "first", "last", "").
|
|
|
+ Return([]LegacyUserSearchResult{{UIN: 99999}}, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(3)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "nil session",
|
|
|
+ pkt: defaultV5Pkt(0x0514, nameData),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ expectV5NotConnected(e.Sender)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleDirectUINSearch(t *testing.T) {
|
|
|
+ runV5HandleCases(t, []v5HandleCase{
|
|
|
+ {
|
|
|
+ name: "user found",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(0x051F, v5TargetUINData(99999)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().SearchByUIN(mock.Anything, uint32(99999)).
|
|
|
+ Return(&LegacyUserSearchResult{UIN: 99999}, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(3)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "user not found",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(0x051F, v5TargetUINData(99999)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().SearchByUIN(mock.Anything, uint32(99999)).Return(nil, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(2)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "nil session",
|
|
|
+ pkt: defaultV5Pkt(0x051F, v5TargetUINData(99999)),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ expectV5NotConnected(e.Sender)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+func TestV5Handler_HandleDirectEmailSearch(t *testing.T) {
|
|
|
+ runV5HandleCases(t, []v5HandleCase{
|
|
|
+ {
|
|
|
+ name: "results found",
|
|
|
+ session: sessionWithInstance(testV5UIN),
|
|
|
+ pkt: defaultV5Pkt(0x0528, v5LPString("a@b.com")),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ e.Service.EXPECT().SearchByName(mock.Anything, "", "", "", "a@b.com").
|
|
|
+ Return([]LegacyUserSearchResult{{UIN: 99999}}, nil).Once()
|
|
|
+ e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Times(3)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "nil session",
|
|
|
+ pkt: defaultV5Pkt(0x0528, v5LPString("a@b.com")),
|
|
|
+ setup: v5CaseSetup(
|
|
|
+ func(e v5HandleEnv) {
|
|
|
+ expectV5NotConnected(e.Sender)
|
|
|
+ },
|
|
|
+ nil,
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ })
|
|
|
+}
|