v1_handler_test.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. package icq_legacy
  2. import (
  3. "encoding/binary"
  4. "errors"
  5. "log/slog"
  6. "net"
  7. "testing"
  8. "github.com/stretchr/testify/assert"
  9. "github.com/stretchr/testify/mock"
  10. "github.com/stretchr/testify/require"
  11. )
  12. const (
  13. testV1UIN = uint32(12345)
  14. testV1Seq = uint16(42)
  15. )
  16. var testV1Addr = &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 4000}
  17. type v1HandleEnv struct {
  18. T *testing.T
  19. Sender *mockPacketSender
  20. Service *mockLegacyService
  21. Sessions *LegacySessionManager
  22. Session *LegacySession
  23. }
  24. type v1HandleCase struct {
  25. name string
  26. session *LegacySession
  27. pkt []byte
  28. setup func(v1HandleEnv) func(error)
  29. }
  30. func v1CaseSetup(pre func(v1HandleEnv), after func(e v1HandleEnv, err error)) func(v1HandleEnv) func(error) {
  31. return func(e v1HandleEnv) func(error) {
  32. if pre != nil {
  33. pre(e)
  34. }
  35. return func(err error) {
  36. if after != nil {
  37. after(e, err)
  38. return
  39. }
  40. assert.NoError(e.T, err)
  41. }
  42. }
  43. }
  44. func runV1HandleCases(t *testing.T, cases []v1HandleCase) {
  45. t.Helper()
  46. for _, tt := range cases {
  47. t.Run(tt.name, func(t *testing.T) {
  48. sender := newMockPacketSender(t)
  49. svc := newMockLegacyService(t)
  50. sessions := newTestLegacySessionManager()
  51. e := v1HandleEnv{
  52. T: t,
  53. Sender: sender,
  54. Service: svc,
  55. Sessions: sessions,
  56. Session: tt.session,
  57. }
  58. var check func(error)
  59. if tt.setup != nil {
  60. check = tt.setup(e)
  61. } else {
  62. check = func(err error) { assert.NoError(t, err) }
  63. }
  64. h := newTestV1Handler(t, sender, svc, sessions)
  65. check(h.Handle(tt.session, testV1Addr, tt.pkt))
  66. })
  67. }
  68. }
  69. func newTestV1Handler(t *testing.T, sender PacketSender, svc LegacyService, sessions *LegacySessionManager) *V1Handler {
  70. t.Helper()
  71. if sessions == nil {
  72. sessions = newTestLegacySessionManager()
  73. }
  74. return NewV1Handler(sessions, svc, sender, slog.Default())
  75. }
  76. func buildV1LoginPacket(uin uint32, seq uint16, password string, status uint32) []byte {
  77. pwd := []byte(password)
  78. data := make([]byte, 2+len(pwd)+4)
  79. binary.LittleEndian.PutUint16(data[0:2], uint16(len(pwd)))
  80. copy(data[2:], pwd)
  81. binary.LittleEndian.PutUint32(data[2+len(pwd):], status)
  82. buf := make([]byte, 10+len(data))
  83. binary.LittleEndian.PutUint16(buf[0:2], ICQLegacyVersionV1)
  84. binary.LittleEndian.PutUint16(buf[2:4], ICQLegacyCmdGetDeps)
  85. binary.LittleEndian.PutUint16(buf[4:6], seq)
  86. binary.LittleEndian.PutUint32(buf[6:10], uin)
  87. copy(buf[10:], data)
  88. return buf
  89. }
  90. func TestV1Handler_Handle_V1Login(t *testing.T) {
  91. runV1HandleCases(t, []v1HandleCase{
  92. {
  93. name: "auth success",
  94. pkt: buildV1LoginPacket(testV1UIN, testV1Seq, "secret", 0),
  95. setup: v1CaseSetup(func(e v1HandleEnv) {
  96. e.Service.EXPECT().AuthenticateUser(mock.Anything, mock.MatchedBy(func(req AuthRequest) bool {
  97. return req.UIN == testV1UIN && req.Password == "secret" && req.Version == ICQLegacyVersionV1
  98. })).Return(&AuthResult{Success: true, oscarSession: testAuthSuccessInstance()}, nil).Once()
  99. e.Service.EXPECT().NotifyStatusChange(mock.Anything, testV1UIN, uint32(0)).Return(nil).Once()
  100. e.Sender.EXPECT().SendToSession(mock.AnythingOfType("*icq_legacy.LegacySession"), mock.AnythingOfType("[]uint8")).Return(nil).Twice()
  101. }, func(e v1HandleEnv, err error) {
  102. assert.NoError(e.T, err)
  103. assert.NotNil(e.T, e.Sessions.GetSession(testV1UIN))
  104. }),
  105. },
  106. {
  107. name: "bad password",
  108. pkt: buildV1LoginPacket(testV1UIN, testV1Seq, "wrong", 0),
  109. setup: v1CaseSetup(func(e v1HandleEnv) {
  110. e.Service.EXPECT().AuthenticateUser(mock.Anything, mock.Anything).
  111. Return(&AuthResult{Success: false}, nil).Once()
  112. e.Sender.EXPECT().SendPacket(testV1Addr, mock.AnythingOfType("[]uint8")).
  113. Run(func(_ *net.UDPAddr, p []byte) {
  114. assert.Equal(e.T, ICQLegacySrvWrongPasswd, v2ServerCommand(p))
  115. }).Return(nil).Once()
  116. }, nil),
  117. },
  118. {
  119. name: "packet too short",
  120. pkt: []byte{0x01, 0x00, 0xF2, 0x03, 0x2A, 0x00},
  121. setup: v1CaseSetup(nil, func(e v1HandleEnv, err error) {
  122. assert.NoError(e.T, err)
  123. }),
  124. },
  125. {
  126. name: "auth service error",
  127. pkt: buildV1LoginPacket(testV1UIN, testV1Seq, "secret", 0),
  128. setup: v1CaseSetup(func(e v1HandleEnv) {
  129. e.Service.EXPECT().AuthenticateUser(mock.Anything, mock.Anything).
  130. Return(nil, errors.New("auth failed")).Once()
  131. e.Sender.EXPECT().SendPacket(testV1Addr, mock.AnythingOfType("[]uint8")).
  132. Run(func(_ *net.UDPAddr, p []byte) {
  133. assert.Equal(e.T, ICQLegacySrvWrongPasswd, v2ServerCommand(p))
  134. }).Return(nil).Once()
  135. }, nil),
  136. },
  137. })
  138. }
  139. func TestV1Handler_Handle_delegatesToV2(t *testing.T) {
  140. sess := v2Session(testV1UIN)
  141. sess.Version = ICQLegacyVersionV1
  142. runV1HandleCases(t, []v1HandleCase{
  143. {
  144. name: "keep alive uses V2 handler",
  145. session: sess,
  146. pkt: buildV2ClientPacket(defaultV2Pkt(ICQLegacyCmdKeepAlive, nil)),
  147. setup: v1CaseSetup(func(e v1HandleEnv) {
  148. e.Sender.EXPECT().SendToSession(e.Session, mock.AnythingOfType("[]uint8")).Return(nil).Once()
  149. }, nil),
  150. },
  151. })
  152. }
  153. func TestV1Handler_Handle_GetDeps_notIntercepted(t *testing.T) {
  154. // V1 always routes 0x03F2 to handleV1Login; it never calls V2 ValidateCredentials (getdeps).
  155. pwd := []byte("secret")
  156. data := make([]byte, 2+len(pwd)+4)
  157. binary.LittleEndian.PutUint16(data[0:2], uint16(len(pwd)))
  158. copy(data[2:], pwd)
  159. pkt := make([]byte, 10+len(data))
  160. binary.LittleEndian.PutUint16(pkt[0:2], ICQLegacyVersionV1)
  161. binary.LittleEndian.PutUint16(pkt[2:4], ICQLegacyCmdGetDeps)
  162. binary.LittleEndian.PutUint16(pkt[4:6], testV1Seq)
  163. binary.LittleEndian.PutUint32(pkt[6:10], testV1UIN)
  164. copy(pkt[10:], data)
  165. runV1HandleCases(t, []v1HandleCase{
  166. {
  167. name: "0x03F2 on V1 uses login not V2 getdeps",
  168. pkt: pkt,
  169. setup: v1CaseSetup(func(e v1HandleEnv) {
  170. e.Service.EXPECT().AuthenticateUser(mock.Anything, mock.MatchedBy(func(req AuthRequest) bool {
  171. return req.UIN == testV1UIN && req.Password == "secret"
  172. })).Return(&AuthResult{Success: false}, nil).Once()
  173. e.Sender.EXPECT().SendPacket(testV1Addr, mock.AnythingOfType("[]uint8")).Return(nil).Once()
  174. }, nil),
  175. },
  176. })
  177. }
  178. func TestV1Handler_Handle_sessionCreateFailure(t *testing.T) {
  179. sender := newMockPacketSender(t)
  180. svc := newMockLegacyService(t)
  181. sessions := newTestLegacySessionManager()
  182. svc.EXPECT().AuthenticateUser(mock.Anything, mock.Anything).
  183. Return(&AuthResult{Success: true, oscarSession: nil}, nil).Once()
  184. sender.EXPECT().SendPacket(testV1Addr, mock.AnythingOfType("[]uint8")).Return(nil).Once()
  185. h := newTestV1Handler(t, sender, svc, sessions)
  186. pkt := buildV1LoginPacket(testV1UIN, testV1Seq, "secret", 0)
  187. require.NoError(t, h.Handle(nil, testV1Addr, pkt))
  188. }